Adding a click handler for an html element? - gwt

I have a page pregenerated for me using html, it looks like a scrollable list of divs, something like:
<html>
<div id="a">
<div>Item A</div>
</div>
<div id="b">
<div>Item B</div>
</div>
</html>
I'd like to grab them in my entry point method, and add a click handler to each. I can't figure out how to do that. I have something like:
public void onModuleLoaded() {
RootPanel rp1 = RootPanel.get("a");
rp1.addClickHandler(...); // can't do this.
}
how can I listen for a click on one of those items in GWT? Is there a way I can just install a global click handler and just watch for the ID of clicked items and filter on that? I don't necessarily need to add a click handler for each element (and I think the docs recommend against this if you have many elements which require click handlers),
Thanks
Thanks

I haven't tested this, but the general idea is right, and easy enough to extend for more than one target element. You might like to store the elements returned by DOM.getElementById() beforehand to keep things fast. Bear in mind that onPreviewNativeEvent() will be called for every user event, so keep it light.
Event.addNativePreviewHandler(new NativePreviewHandler() {
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (Event.as(event).getTypeInt() == Event.ONCLICK &&
DOM.isOrHasChild(DOM.getElementById("A"), Element.as(event.getEventTarget()))) {
// Element 'A' was clicked.
}
}
}

The problem using wrap() is that if the parent element is already a widget, the wrapping is not allowed. You can still do it and will work, but if you run the application in development mode the assertion will fail, stopping the application.
The right (but tedious and in my opinion incomplete) way is something like
Element elem = DOM.getElementById(“billing-component”);
DOM.sinkEvents(elem, Event.ONCLICK | Event.ONMOUSEOUT | Event.ONMOUSEOVER);
DOM.setEventListener(elem, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if (Event.ONCLICK == event.getTypeInt()) {
…
}
}
});
I know doesn't look nice, and actually it isn't because you can only attach a single listener to the element and have to check for the event type.

Related

Polymer 2 - Perform action every time element is shown via iron-pages/iron-selector

I'm attempting to create a logout page that will work even after that element has been attached once to the DOM. This occurs when you get a login, then logout, then login again, and attempt to log back out.
For instance, the shell has
<iron-selector selected="[[page]]" attr-for-selected="name">
<a name="logout" href="[[rootPath]]logout">
<paper-icon-button icon="my-icons:sign-out" title="Logout" hidden$="[[!loggedIn]]"></paper-icon-button>
</a>
<a name="login" href="[[rootPath]]login">
<paper-icon-button icon="my-icons:sign-in" title="Login" hidden$="[[loggedIn]]"></paper-icon-button>
</a>
</iron-selector>
<<SNIP>>
<iron-pages selected="[[page]]" attr-for-selected="name" fallback-selection="view404" role="main">
<my-search name="search"></my-search>
<my-login name="login"></my-login>
<my-logout name="logout"></my-logout>
<my-view404 name="view404"></my-view404>
</iron-pages>
I also have an observer for page changes in the shell:
static get observers() {
return [
'_routePageChanged(routeData.page)',
];
}
_routePageChanged(page) {
this.loggedIn = MyApp._computeLogin();
if (this.loggedIn) {
this.page = page || 'search';
} else {
window.history.pushState({}, 'Login', '/login');
window.dispatchEvent(new CustomEvent('location-changed'));
sessionStorage.clear();
this.page = 'login';
}
}
This works well as when I click on the icon to logout, it attaches the my-logout element just fine and performs what in ready() or connectedCallback() just fine.
my-logout has
ready() {
super.ready();
this._performLogout();
}
The issue comes when, without refreshing the browser and causing a DOM refresh, you log back in and attempt to log out a second time. Since the DOM never cleared, my-logout is still attached, so neither ready() nor connectedCallback() fire.
I've figured out a way of working around this, but it feels very kludgy. Basically, I can add an event listener to the element that will perform this._performLogout(); when the icon is selected:
ready() {
super.ready();
this._performLogout();
document.addEventListener('iron-select', (event) => {
if (event.detail.item === this) {
this._performLogout();
}
});
}
Like I said, it works, but I dislike having a global event listener, plus I have to call the logout function the first time the element attaches and I have to listen as the listener isn't active till after the first time the element is attached.
There does not appear to be a "one size fits all" solution to this. The central question is, "Do you want the parent to tell the child, or for the child to listen on the parent?". The "answer" I came up with in the question works if you want to listen to the parent, but because I don't like the idea of a global event listener, the below is how to use <iron-pages> to tell a child element that it has been selected for viewing.
We add the selected-attribute property to <iron-pages>:
<iron-pages selected="[[page]]" attr-for-selected="name" selected-attribute="selected" fallback-selection="view404" role="main">
<my-search name="search"></my-search>
<my-login name="login"></my-login>
<my-logout name="logout"></my-logout>
<my-view404 name="view404"></my-view404>
</iron-pages>
Yes, this looks a little confusing considering the attr-for-selected property. attr-for-selected says, "What attribute should I match on these child elements with the value of my selected property?" So when I click on
<iron-selector selected="[[page]]" attr-for-selected="name">
<a name="logout" href="[[rootPath]]logout"><paper-icon-button icon="my-icons:sign-out" title="Logout" hidden$="[[!loggedIn]]"></paper-icon-button></a>
</iron-selector>
it will set the <my-logout> internally as the selected element and display it. What selected-attribute="selected" does is to set an attribute on the child element. If you look in the browser JS console, you will see that the element now looks like
<my-login name="login"></my-logout>
<my-logout name="login" class="iron-selected" selected></my-logout>
We can define an observer in that in the <my-logout> element that checks for changes
static get properties() {
return {
// Other properties
selected: {
type: Boolean,
value: false,
observer: '_selectedChanged',
},
};
}
_selectedChanged(selected) {
if (selected) {
this._performLogout();
}
}
The if statement is so that we only fire the logic when we are displayed, not when we leave. One advantage of this is that we don't care if the element has already been attached to the DOM or not. When <iron-selector>/<iron-pages> selects the <my-logout> the first time, the attribute is set, the element attaches, the observer fires, the observer sees that selected is now true (as opposed to the defined false) and runs the logic.

How to get the value of a textbox adjoining a button with gwt query

I have a custom widget like this, it's basically a couple of text boxes and buttons along with them.
<g:VerticalPanel>
<b:Label>Lower Limit</b:Label>
<b:TextBox ui:field="lowerLimit" addStyleNames="lowerLimitTextBox"></b:TextBox>
<b:Button icon="COPY" addStyleNames="copyAll copyAllLower">Copy To All</b:Button>
</g:VerticalPanel>
<g:VerticalPanel>
<b:Label>Upper Limit</b:Label>
<b:TextBox ui:field="upperLimit" addStyleNames="upperLimitTextBox"></b:TextBox>
<b:Button icon="COPY" addStyleNames="copyAll copyAllUpper">Copy To All</b:Button>
</g:VerticalPanel>
There are many of these widgets on a page.
When a button is clicked I want to be able to select the text box to it's left and copy that value to all the corresponding widgets.
I can select the buttons but don't know how to select the textbox to it's immediate left.
Can anyone help with this.
I'm adding the jQuery-selectors tag as the selector might be similar to that of GwtQuery.
Well, first you have to know how a VerticalPanel is rendered so as you can figure out where is the text box in the dom.
VerticalPanel is rendered as a table, and each widget is positioned into a structure: <tr><td>widget</td></tr>
In your case you can use closest() and prev() to get the previous tr of your button:
$(button).closest("tr").prev();
Then use find() to get the input inside the previous tr
$(button).closest("tr").prev().find("input")
So using the gquery ability of finding Widgets, the code for each button in your UI could look like:
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
TextBox box = $(button).closest("tr").prev().find("input").widget();
for (TextBox b : $(".gwt-TextBox").widgets(TextBox.class) ) {
b.setValue(box.getValue());
}
}
});
Although if you wanted to use gquery to enhance all buttons at once, everything is much simpler:
$(".gwt-Button").click(new Function(){
public void f() {
String value = $(this).closest("tr").prev().find("input").val();
$(".gwt-TextBox").val(value);
}
});

In a GWT HTML widget displaying complex HTML, how do I add an event handler for specific subelements?

I'm optimizing a GWT application that previously used a variety of nested panels to work with DIVs and Spans. I generate the entire table as a single SafeHtml object and then assigning it into a single SafeHtml widget.
I now want to be able to track mouseover/mouseout events at the level of the specific 'cell' spans rather than the entire table, but I'm not sure how to do this.
If I add a handler to the HTML widget itself, I'll get events sourced at various elements.
Since 2.0 there is quite a simple way to do it.
For example if you HTML code is contained in some kind of widget (HTMLPanel or HTML), you can calladdDomHandler(<handler>,<eventtyoe>) on that widget, so you will receive events from inner html.
For example if you have a bunch of anchors inside HTMLPanel and you want to know which one was clicked you can do something like this:
panel.addDomHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
Element element= event.getNativeEvent().getEventTarget().cast();
if(element.getTagName().equals("A")) {
AnchorElement anchor = element.cast();
Window.alert("Anchor with href " + anchor.getHref() + " was clicked");
}
}
}, ClickEvent.getType());
Since you want to track mouseover/out events you will have to use 2 different dom handlers, find out cell you need when event is fired and then change its state.
The way to approach this is:
Find the element you need with one of the DOM methods, like DOM.getElementById(..) or any other means. View Widget.getElement() etc.
Call DOOM.sinkEvents(element,eventBits) or DOM.sinkBitlessEvent(element,eventName) and pass the required events you want to sink in form of a bitmask, like Event.MOUSEEVENTS or using a named event like click or touchstart if using the second method.
set and EventListener on the element, by calling DOM.setEventListerner(element,eventListener) like so:
DOM.setEventListener( element, new EventListener()
{
#Override
public void onBrowserEvent( Event event )
{
if ("click".event.getType()) {
// ..do stuff..
}
}
} );
Only events you've specified in step 2 will be fired to your EventListener, so you need to only handle those.

calling a child page component from parent page component in wicket

I have a problem which I tried to explained in the Image.I hope that will help all to understand what I need.
My Base Page is like this (menuNavPanel is the tree panel):
<div class="colContainer">
<div class="leftColumn" >
<div wicket:id="menuNavPanel"></div>
</div>
<div class="rightColumn">
<wicket:child/>
</div>
</div>
And Ny BIA Page which is a child of Base Page is like this:
<wicket:extend>
<div wicket:id="bodyPanel"></div>
</wicket:extend>
in my Tree Panel, when I click on a node the code is this:
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
// I want to call bodyPanel fo child page passing the unitObject param
}
Now, How can I call bodyPanel fo child page passing the unitObject param from the tree panel of the parent page?
Am I been able to express my problem? Hoping to get some help :)
Instead of doing the override method, upgrade to Wicket 1.5 and utilize the new event bus to communicate between your components. You can create a custom, type-safe, event that is specific to your component's use case: for example "ItemAddedToShoppingCart" or "GlobalThermoNuclearWarStarted".
The linked article in the 1.5 migration guide provides enough information on how to set up things.
I'm not sure I understand que question correctly. Your BasePage defines a left column with the TreePanel and lets subclasses expand themselves inside the right column div. You usually put a BodyPanel inside BasePages's subclasses. And now you want to invoke a BodyPanel's method on some event on the TreePanel.
You could do it with an overridable method on BasePage, which would be called in TreePanel through getPage(). Your child pages would override that method, and its implementation would call the BodyPanel they're holding.
public class BasePage ... {
// Hook
public void treePanelSelected(Object someObject) { }
...
}
public class ChildPage extends BasePage ... {
BodyPanel bodyPanel;
#Override
public void treePanelSelected(Object someObject) {
bodyPanel.selectionChanged/(someObject);
}
...
}
public class TreePanel ... {
...
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
((BasePage)getPage()).treePanelSelected(unitObject);
}
}
From my ignorance on your specific needs and details of implementation, I don't see why is subclassing the BasePage necessary. You could add the BodyPanel right there in the BasePage and control it from the same class.
Thanks all, After reviewing all the nice options I finally opted out for the event bus way defined by martijn. What I did is I have created an event payload and connected the panels for the talking. I also needed to pass the selected Id / entity to the receiving panel.
Is there a way to set a compound property model of the receiving panel according to the model of the tree element so that I don't need to do the model manually ?
I did like this for the time being:
public class TreeNodeClickUpdate {
private final AjaxRequestTarget target;
private final long selectedId;
/**
* Constructor
*
* #param target
* #param selectedId
*/
public TreeNodeClickUpdate(AjaxRequestTarget target, long selectedId)
{
this.target = target;
this.selectedId = selectedId;
}
/** #return ajax request target */
public AjaxRequestTarget getTarget()
{
return target;
}
public long getSelectedId() {
return selectedId;
}
}
On the sender side I've done like this:
send(getPage(), Broadcast.BREADTH,
new TreeNodeClickUpdate(target, unitObject.getId()));
And on the receiving end I got it like this:
#Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof TreeNodeClickUpdate)
{
TreeNodeClickUpdate update = (TreeNodeClickUpdate)event.getPayload();
setSelectedId(update.getSelectedId()); //sets to id field of panel
update.getTarget().add(this);
}
}
and for just as an example in my receiving panel, to get the value I have created a label like this:
label = new Label("label",new PropertyModel<BiaHomePanel>(this,"selectedId"));
Later, in reality I want to get information from the entity and show in form. Is there a nice way to pass models in a better way or I should pass as a parameter in event payload.
There are two ways to do this. One is cleaner, but requires more code. The other is quick and dirty.
Method 1: (Good)
Since your parent page is being extended, you can provide an abstract method in the parent like
protected abstract WebMarkupContainer getBodyPanel();
that is implemented in your child page and returns the appropriate panel. Then, you can call that method from the panel in your parent page. This is similar to the overrideable method suggested by the other user.
Method 2: (Bad)
The Wicket Component Hierarchy is shared between the parent and child pages. So, if you make sure that your bodyPanel has a unique wicketId and is added directly to the root of the page, you can probably just call
get("bodyPanelId");
and it will return the proper panel.
When I was facing the problem, I thought of two ways to solve this (pre 1.5):
a) implement a variation of the observer pattern to notify other component of events like outlined here: Realising complex cross-component ajax actions in wicket - The observer way
b) using wicket visitors to traverse the component tree doing the same.
I decided to go for variant a) but this introduces coupling from your component to your page-implementation which leads to problems when testing panels on their own. So maybe b) might be the better idea but since my application is running quite smoothly with a) implemented and the next big step will be switching over to 1.5 and the event bus, I haven't yet tried b).

GWT - Two Question on Hyperlink - Manage its History token parameter + insert it in a span

I need to manage a Hyperlink object in GWT. What i need is :
1 - add it into a span (like InlineLabel)
I tried Hyperlink affitta_3_span_1=new Hyperlink(result.get(i)[0], "");, but it create somethings like this :
<div class="affitta_3_span_1">
t1
</div>
in fact i need this :
<span class="affitta_3_span_1">
t1
</span>
2 - manage Hyperlink History token
I put my internal links such Hyperlink affitta_3_span_1=new Hyperlink(result.get(i)[1], "article/"+result.get(i)[0]) but i don't know how to get the parameter on token when they call the onValueChange() function. How can I do it?
Cheers
Use an Anchor. The output is just an <a> tag that has no <div> or <span> around it, but if you need a <span> you can add it with an HTML panel.
To set a URL that history can access, just put a # at the beginning. Something like
myAnchor.setText(result.get(i)[1]);
myAnchor.setUrl("#article/"+result.get(i)[0]);
Now, when you click myAnchor, onValueChange will be passed the token "article/whatever". The unfortunate side effect is that your urls look like http://example.com/#article/whatever, but that's the only way to get the token to the History object with just GWT.
For the first question, use an Anchor since it's inlined.
For the second one, you need to 'listen' to history change events by extending ValueChangeHandler and calling History.addValueChangeHandler(this); in your class. For example,
class MyClass implements ValueChangeHandler<String> {
public MyClass {
...
History.addValueChangeHandler(this);
}
#Override
public void onValueChange(ValueChangeEvent<String> event) {
String token = event.getValue();
if (token.equals("foo")) {
// go to one page
} else if token.equals("bar")) {
// go to another page
}
}
}
If you only need a ClickHandler on your link and no history support, you can use the Anchor widget, which is based on an <a> tag that has display: inline by default.