When the page with the MessagePanel first renders, the message and the approve link render perfectly. When I click the approve link, all the business logic works as desired, the getNextMessage() method returns the appropriate object, but the message panel does not update on the page in the browser. That is, the message body Label does not update.
JPAEntityModel extends LoadableDetachableModel.
What am I missing? And how do I fix it?
public class MessagePanel(String id, IModel<Message> messageModel) extends Panel {
super(id, messageModel);
add(new Label("messageText", new PropertyModel<Message>(getModelObject(), Message.BODY_FIELD)));
add(new IndicatingAjaxFallbackLink<User>("approveLink", new JPAEntityModel<User> (getActiveUser())) {
#Override
public void onClick(AjaxRequestTarget target) {
Message nextMessage = getNextMessage();
MessagePanel.this.setDefaultModel(new JPAEntityModel<Message>(nextMessage));
target.add(MessagePanel.this);
}
});
setOutputMarkupId(true);
}
It is because you're not using the model properly.
This line takes the value of the panel's model object, as it is set during construction, and uses it to create the component model.
add(new Label("messageText", new PropertyModel<Message>(getModelObject(), Message.BODY_FIELD)));
To make matters worse, when you click the link, the panel is given a new model:
MessagePanel.this.setDefaultModel(new JPAEntityModel<Message>(nextMessage));
But this obviously doesn't affect the model of the label, as it is already set to refer to the original value.
So there are two things you need to change to make it work. First off, your label model should use your panel model directly:
new Model<Message>() {
#Override
public Message getObject() {
return MessagePanel.this.getModelObject().getMessage(); //or something similar
}
}
(Note: the code above isn't necessarily the best solution, but it is a working solution that demonstrates how models can be used dynamically.)
And ideally you shouldn't replace the model when you click the link, just change the model object. If you need a custom model class (JPAEntityModel), you shouldn't be accepting a pre-constructed model in the panel constructor anyway, just the first message object. The reason being the current implementation doesn't enforce the use of JPAEntityModel from the start, only after the first click of the link.
Can you try calling MessagePanel.this.modelChanged() before adding it to the target?
You must use call setOutputMarkupId(true) within you MessagePanel. The panel needs to have a markup identifier to be able to update the markup DOM in the browser.
Related
How to implement pager change event in Tapestry's grid? I've read the documentation, but couldn't find the answer. So what I'm trying to do is, I want to reset the selectedItem to null everytime the selected page changing, and I noticed that actually setupRender() triggered, but I don't want to use it because it does a quite heavy process. There's no point to do this only for page changes. So is there any way to implement it specifically only for pager changes? Thank you. I'm using Tapestry 5.3.8 btw
If I understand your question correctly, you should implement your own GridPager and, for example, emit the event when a page is changing (you can then handle this event within page class). For example:
#Events({ PAGE_CHANGE_EVENT, InternalConstants.GRID_INPLACE_UPDATE + " (internal event)" })
public class CustomeGridPager
{
...
void onAction(int newPage)
{
currentPage = newPage;
this.resources.triggerEvent(PAGE_CHANGE_EVENT, new Object[] { newPage }, null);
}
}
I have a tree viewer in my view, which listens to EMF models from the standard Ecore editor and does further things with it. I have already registered a selection listener, which checks whether the selected elements are the types the tree viewer needs as input. So the problem is that if there any changes in the model (e.g. adding a new element or new information to an existing element etc.) the tree viewer shows the changed model only if the user changes the selection, i.e. clicks to any model element etc.
But what I need to do is, that the tree viewer gets directly notified if the underlying model changes and shows the new model element too without being have to click on the model to listen it.
I have found the following eclipse corner article ( https://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm#inputChanged ) and from "Responding th change" it seems that the inputChanged() and refresh() methods might be the solution I am looking for, isn't it?
Still I was wondering if there is maybe an easier way to do this without being have to change the model code, but only by making changes in the UI code? Thanks!
You can call the TreeViewer refresh() method to get it to refresh the whole tree from the model, or refresh(Object) to refresh the tree starting at the given model object.
If the tree structure has not changed you can call update(Object) to just update the display of a single object.
There are also add and remove methods for when you add and remove objects from the model tree.
Some of the methods also have Object [] variants so you can modify several objects at once.
Update:
Your model should support generating a model changed event which the content provider can listen to. You would set up this listener in the content provider inputChanged method and remove it in the dispose method. When model change events are received use the various TreeViewer methods to update the tree.
An example of how all this is used are the Eclipse views which show the files in the workspace (such as the Navigator view). The content provider for these uses the workspace resource change listener (IResourceChangeListener) to be notified of changes to the workspace and using the information in the event calls the methods I listed above to update the tree.
Update 2:
An example of using IResourceChangeListener in a content provider, extracted from org.eclipse.ui.views.tasklist.TaskListContentProvider
class TaskListContentProvider
implements IStructuredContentProvider, IResourceChangeListener
{
private TableViewer viewer;
private IResource input;
... other methods ....
public void dispose() {
if (input != null) {
input.getWorkspace().removeResourceChangeListener(this);
input = null;
}
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (input != null) {
input.getWorkspace().removeResourceChangeListener(this);
}
input = (IResource) newInput;
if (input != null) {
input.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
}
viewer = (TableViewer) viewer;
}
public void resourceChanged(IResourceChangeEvent event) {
... use resource change event to update viewer
}
}
I am using chrriis.dj.nativeswing.swtimpl.components.JWebBrowser in my swing application to open web page.
The page is going to show "Facebook Authentication" page and I want to prevent user from inputting some other URL other than I specify and also Forward and Back buttons should be visible but not has no affect.
So following functions are applicable for my goal
setButtonBarVisible(false);
setLocationBarVisible(false);
Once user completes the authentication I will handle the locationChanged event.
#Override
public void locationChanged(WebBrowserNavigationEvent arg0) {
System.out.println("locationChanged!");
....
}
}
I think what you want is a custom decorator. Check the demo application, under "JWebBrowser > Custom Decorator".
In your case, you could create a new decorator class, as an adapted copy of DefaultWebBrowserDecorator or a subclass with appropriate override.
You would also have to decide if this decorator is to be used only by one instance of the JWebBrowser or all instances (like child popups, etc.)
I am learning to design a GUI in GWT
I have RootPanel where i have put all the widgets.
In one panel i have put tree Widget where the treeItems are added on the success of RPC call on the selectionHandler
What I want to do:
When click on treemItem , All the other treeItem and the widgets on the same and different panel should not be selected. Like there is a processing going on so no other things are allowed to do.
Please suggest some idea or sample code or example.
If I correctly understand you, you want to disable user clicking some widgets until specific callback finishes. In order to do that you should have a custom panel with bigger z-index and opacity. For example, something like DialogBox's setGlassEnabled(true) method and try using that technique in your callbacks. When asynchronous request starts show that panel in onSuccess and onFailure you should hide it. By the way, my-gwt has LoadingPanel class which implements such feature. And also another mask live demo in GWT-Ext.
This statement is redundant and unnecessary:
"When click on treemItem , All the other treeItem and the widgets on
the same and different panel should not be selected."
Of course, when you click on a tree node, that is the only node you want activated. No other node would have any activity. That is by convention.
And I really don't know what this statement is all about:
"Like there is a processing going on so no other things are allowed to
do."
Your question should simply be:
How do I add child nodes to a tree Widget, where child nodes are added
only when I click on the node? When I click on a node, if it has no
child nodes, an RPC would be triggered to fetch the nodes. How should
my RPC results update the node? After I click on a node, it should be
highlighted. There should be only one highlighted/selected node on the whole tree.
Extend HorizontalPanel to contain an icon and a title.
It should implement IsTreeItem.
It should implement ClickHandler so that it can be added as clickhandlers to the icon and title label.
public class Node
extends HorizontalPanel
implements ClickHandler, IsTreeItem{
Image img;
Label label;
MyData data;
TreeItem nodeWrapper;
The constructor eats in a MyData record, instantiates the icon image and title label, and associates itself as the clickhandler for the icon and label:
public Node(MyData data){
this.nodeWrapper = new TreeItem(this);
this.img = new Image(data.getIconUrl());
this.label = new Label(data.getTitle());
this.data = data;
this.add(img);
this.add(this.label);
// ensure onclick is triggered either by clicking img or label.
this.img.addClickHandler(this);
this.label.addClickHandler(this);
}
Implementing IsTreeItem requires the node to be able to substantiate itself with a TreeItem:
public TreeItem asTreeItem(){
return this.nodeWrapper;
}
The clickhandling will fetch the list of child records and iteratively create new nodes from the child records and attach them to the current node. This is where the RPC callback action takes place.
public void onClick(ClickEvent e){
this.displayRightPanel(this.data);
if(this.nodeWrapper.getChildCount()==0)
fetchNodeData(
this.nodeWrapper,
this.data.getId(),
new AsyncCallBack<List<MyData>>(){
public void onSuccess(List<MyData> nodeDataList){
for(MyData nodeData : nodeDataList){
Node.this.nodeWrapper.addItem(new Node(nodeData));
}
}
public void onFailure(Throwable sorry){
doWhatever();
}
}
);
}
define other relevant methods before closing the class definition:
//blah ... blah ... blah...
}
Since this is not a tutorial on RPC, you should find out how to write the RPC part. You need to define MyData which is a shared POJO between GWT client and RPC servlet. RPC servlet must return a list of MyData. You need to be educated on at least the 2nd Normal Form of database normalisation to understand why you need a getId() method.
public Interface MyData{
String getIconURL();
String getTitle();
String getId();
// among others ...
}
It is presumed that you have a splitpanel. The left side would be the tree widget and the right panel is some sort of display. So that onclick, besides triggering RPC fetch, would also call displayRightPanel(data), and it is left to you how you wish to display that data.
You would create a first root node and associate it as the root of the tree, and the rest of the tree would be populated by user clicking. So the root node would require you to concoct a MyData record that will supply the iconUrl, title, and some root data.
The tree widget will manage the highlighting of its members and ensure only one member is highlighted.
HI guys,
I wanted to add an AJAX Event to my Homepage, but it doesn't work! I figured out, that if I delete the onComponentTag function it works well. I have no clue why this happend, maybe you can help me!
Thats my Code:
final TextField<String> searchInput = new TextField<String>("searchInput", model) {
#Override
protected void onComponentTag(final ComponentTag tag) {
super.onComponentTag(tag);
tag.put("id", this.getId());
if (params.getString("search") != null) {
tag.put("value", params.getString("search"));
}
}
};
searchInput.add(new AjaxFormComponentUpdatingBehavior("onfocus") {
#Override
protected void onUpdate(final AjaxRequestTarget target) {
System.out.print("never saw that message :(");
searchInput.setDefaultModelObject("");
target.addComponent(searchInput);
}
});
Thx a lot for helping me!
CU
Firstly, you don't need to be overriding onComponentTag() at all. As seanizer states, if your really need to specify a markup ID yourself, use setMarkupId(id). You should understand why it is recommended that Wicket manages component IDs.
Secondly, the value attribute that you are adding is unnecessary - Wicket adds this automatically for this component. The value assigned is the value of the component's model object. See the source for TextField.onComponentTag().
Thirdly, again as seanizer states, components that are to be updated by ajax need to output their markup IDs - Wicket's ajax implementation uses the ID as the selector for the element. Additionally, all Wicket ajax behaviours that extend AbstractDefaultAjaxBehavior automatically set outputMarkupId(true) on the component they are bound to (see the source for AbstractDefaultAjaxBehavior.onBind()). This includes AjaxFormComponentUpdatingBehavior.
So:
String id = "searchInput";
final TextField<String> searchInput = new TextField<String>(id, model);
searchInput.setMarkupId(id);
searchInput.add(new AjaxFormComponentUpdatingBehavior("onfocus") {
#Override
protected void onUpdate(final AjaxRequestTarget target) {
System.out.print("never saw that message :(");
searchInput.setDefaultModelObject("");
target.setOutputMarkupId(true);
target.addComponent(searchInput);
}
});
Finally, I'd question what you're actually trying to achieve with this behaviour. I don't see any reason to round-trip this event to the server. Surely some client-side JS is more appropriate?
tag.put("id", this.getId());
is not the way to do it in wicket.
instead, use
component.setOutputMarkupId(true)
(either in your component constructor or in your behavior's bind() method) to make wicket write the id, and if you absolutely need to control what the id is (which is almost never the case) you can do
component.setMarkupId("myId")
also, you probably shouldn't assign the tag value yourself, use a model (model handling is extremely smart in wicket, read more about models). There are valid uses for onComponentTag, but they are way beyond what you are doing. Let wicket do what wicket does best and everything will be fine.
EDIT:
OK, some more clarification
have a look at the source code of AjaxFormComponentUpdatingBehavior, especially the part where the javascript event handler is generated.
protected final CharSequence getEventHandler()
{
return generateCallbackScript(
new AppendingStringBuffer("wicketAjaxPost('")
.append(getCallbackUrl(false)).append(
"', wicketSerialize(Wicket.$('"
+ getComponent().getMarkupId() + "'))"));
}
as you can see, wicket uses getMarkupId() to determine the actual id. The id you set using tag.put(id) is totally unknown to wicket and hence the behavior cannot work.
The standard thing to do is setOutputMarkupId(true). This is the only proper way to tell wicket to render the id (other than setOutputMarkupPlaceholder(true), which internally calls the former method). That way you make sure that the id wicket writes is the id wicket knows about. If this doesn't render the id, you are probably breaking some default behavior by overwriting onComponentTag.
Have a look at the source code of Component, especially at onComponentTag(), the method you are overriding:
protected void onComponentTag(final ComponentTag tag) {
// if(setOutputMarkupId(true) was set)
if (getFlag(FLAG_OUTPUT_MARKUP_ID)) {
// set id attribute
tag.put(MARKUP_ID_ATTR_NAME, getMarkupId());
}
}
[The comments are mine. BTW, this is the source of an ancient version, but I didn't find any current source online, and the functionality hasn't changed.]
Now if, as in your case, you want to set the component id manually, you must use
component.setMarkupId("myId")
and of course
setOutputMarkupId(true)
as well. If that doesn't work, go to the wicket JIRA site and file a bug. But I doubt it, this is standard functionality that works for thousands of users.