Announcement
Collapse
No announcement yet.
X
-
Yup, that's what the problem was. So, lesson learned : stay the hell away from .clone() or Cloneable in GWT :)
-
I think I've found what the problem is. It's this line of code in the controller:
I know that the Cloneable interface is not supported in GWT. However, because of the weird way Sun implemented the object.clone() feature, you can actually call .clone() on any object, even if that object isn't really cloneable.Code:LinkedHashSet<Controller> componentsToDestroy = (LinkedHashSet<Controller>)componentControllers.clone();
I used GWTLog to put a pile of debug statements all over the controller code and then changed the configuration to have those debug statements go to the Firebug console in compiled web mode. What I found is that the controller stops executing tearDown() when it gets to that line. There's no error, it just stops (which is kind of weird, but whatever).
Anyway, I think if I change the code to this:
it should work.Code:LinkedHashSet<Controller> componentsToTearDown = new LinkedHashSet<Controller>(); for(Controller component : componentControllers) { componentsToTearDown.add(component); }
Leave a comment:
-
Hi Svjard,
if I comment out the call to controller.tearDown() it works; but it's not clear to me why this would be a problem. Let me explain what that method does:
Every one of my SmartGWT component classes have an inner Controller class; which extends from a base AbstractController.
Here is the code for the AbstractController:
As you can see from the code; the controller mainly deals with registering and unregistering event handlers. Because my application is quite complex and extensive, I often have components which nest other components inside. In the case of my tabs, this can go 4 or 5 layers deep and I needed to make sure that when a user closes a tab, all the different event handlers that got registered when these components were created are gracefully removed. The AbstractController handles this - it's aware of the controllers of any nested components and when tearDown() is called, invokes tearDown() in turn on the nested controllers.Code:public class AbstractController implements Controller { protected final HandlerManager eventBus; protected Controller container; protected final LinkedHashSet<Controller> componentControllers; protected final ArrayList<HandlerRegistration> localHandlerRegistry = new ArrayList<HandlerRegistration>(); public AbstractController(HandlerManager eventBus) { this.eventBus = eventBus; componentControllers = new LinkedHashSet<Controller>(); } public void addHandlerRegistration(HandlerRegistration ... registrations) { Log.debug(this.getClass().getName() + "- Adding handlerRegistrations to localHandlerRegistry"); for(HandlerRegistration registration : registrations) { localHandlerRegistry.add(registration); } } @Override public void bind() { Log.debug(this.getClass().getName() + "- Binding handlers"); } @Override public void tearDown() { container.unregister(this); LinkedHashSet<Controller> componentsToDestroy = (LinkedHashSet<Controller>)componentControllers.clone(); for(Controller component :componentsToDestroy) { component.tearDown(); } unbind(); Log.debug(this.getClass().getName()+ " destroyed."); } @Override public void init(Controller container) { container.register(this); this.container = container; bind(); Log.debug(this.getClass().getName()+" initialized."); } @Override public void unbind() { Log.debug(this.getClass().getName() + " - Unbinding HandlerRegistrations"); for(HandlerRegistration registration : localHandlerRegistry) { registration.removeHandler(); } } @Override public HandlerManager getEventBus() { return eventBus; } @Override public void register(Controller component) { componentControllers.add(component); } @Override public void unregister(Controller component) { componentControllers.remove(component); } }
By doing this, I ensure that when a tab is closed, all eventhandlers for that tab, as well as any eventhandlers for nested tabs are removed as well. This is essential, because I use a centralized HandlerManager eventBus to propagate events to different components and I didn't tear them down when a tab is closed; they'd still be receiving events, executing logic, querying datasource and so forth, even after the tab has been closed and is no longer visible. In an application where users can create and remove tabs at will; you can see how this would quickly become a huge resource drain, both on the client and on the server, if left unchecked.
Anyway, architecture discussion aside - basically, when I create a smartGWT component, it has an inner controller. I override the bind() method to register any EventHandlers and add them to the localHandlerRegistry (basically just an arrayList of HandlerRegistrations). When tearDown() is called, I loop over the arrayList and call remove() on every HandlerRegistration. I also call tearDown() on any nested controllers (which may call tearDown() on nester controllers in turn; until the end of the controller stack is reached).
During a given tearDown() call, as many as 30 or 40 HandlerRegistrations may get removed (depending on the complexity of the tab) but at the end of the day, that's all it's doing - removing handlers. It's not clear to me why this would prevent subsequent code in the BooleanCallBack() from executing. In fact, what confuses me is that the OTHER part of the if-statement works fine:
And this is basically the exact same logic that's execute in the BooleanCallback(). So it appears as if controller.tearDown() is only a problem inside a BooleanCallback()? I'm scratching my head in confusion here.Code:if(!formChanged) { controller.tearDown(); tabSet.removeTab(AbstractDIDOrderRecordView.this); }
I had a hunch that maybe the fact that the closeClickHandler is getting removed as part of the call to controller.tearDown() is what was causing the problem - some kind of scenario where the closeClickHandler invokes controller.tearDown() which tries to remove the same closeClickHandler which casues a deadlock of some sort. So I didn't add the CloseClickHandler to the arrayList of HandlerRegistrations which are to be removed; but it still didn't work.
The only other thing I can think to try at this point is to implement my own Dialog window and not handle the logic in a BooleanCallback(); but directly, in clickhandlers on the yes/no buttons - but that's really just avoiding the problem without really understand what the problem is in the first place...
Leave a comment:
-
I can't replicate this, but since your still seeing the dialog each time, have your tried commenting out the controller.tearDown() since I don't know exactly is in that code?
Leave a comment:
-
Thanks smartgwt.dev.
I changed the code to this:
Still the same problem though: works fine in DevMode, does not work in HostedMode.Code:@Override public void onCloseClick(TabCloseClickEvent event) { event.cancel(); if(event.getTab() == AbstractDIDOrderRecordView.this) { if(!formChanged) { controller.tearDown(); tabSet.removeTab(AbstractDIDOrderRecordView.this); } else { SC.ask("Your DIDOrder has unsaved changes, are you sure you want to close without saving?", new BooleanCallback(){ @Override public void execute(Boolean value) { if(value!=null && value.booleanValue()) { controller.tearDown(); DeferredCommand.addCommand(new Command(){ @Override public void execute() { tabSet.removeTab(AbstractDIDOrderRecordView.this); } }); } } }); } } }
Leave a comment:
-
Well, that didn't solve it either. Not sure what's going on - I verified that the callback is firing by getting it to put up a prompt with SC.say(). For some reason, the tab just isn't getting removed; yet it works fine when I do it outside the callback.
Leave a comment:
-
Hmmm... I'm wondering now... I noticed that the SmartGWT Tab class has a destroy method as well. Is it possible that the fact that I have a method in an inner class with the same name is causing some kind of conflict when compiled to javascript?
I.e., I essentially have:
Both Tab and the inner class Controller have a destroy() method. In my method call, I'm explicitly referring to controller.destroy()... but who knows what that gets compiled to....Code:public class X extends Tab { Controller controller; public X() { controller = new Controller(); } public class Controller(){ public void destroy() { } } }
I guess I should try renaming my inner class method to tearDown() or something and see what that gives...
Leave a comment:
-
Svjard, I've been trying something very similar - the problem I've run into is that while it runs fine in GWT DevMode; in compiled mode it doesn't, and I have no idea why. Here is the source code:
Clicking the "Yes" button in this case does nothing in compiled mode - the tab remains. In fact, it's almost as if the callback never fires - I can tell the controller.destroy() never happens because all widgets inside the tab continue to respond to events - my controller.destroy method in this case uses HandlerRegistration to unbind any event handling that was set up and un-register the component from a central event bus.Code:@Override public void onCloseClick(TabCloseClickEvent event) { event.cancel(); if(event.getTab() == AbstractDIDOrderRecordView.this) { if(!formChanged) { controller.destroy(); tabSet.removeTab(AbstractDIDOrderRecordView.this); } else { SC.ask("Your DIDOrder has unsaved changes, are you sure you want to close without saving?", new BooleanCallback(){ @Override public void execute(Boolean value) { if(value!=null && value) { controller.destroy(); tabSet.removeTab(AbstractDIDOrderRecordView.this); } } }); } } }
Any ideas?
Leave a comment:
-
I just told you.
Code:tabSet.addCloseClickHandler(new CloseClickHandler() { @Override public void onCloseClick(final TabCloseClickEvent event) { event.cancel(); // MUST DO THIS FIRST System.out.println(event.getTab().getTitle()); SC.ask("Close ?", new BooleanCallback() { @Override public void execute(Boolean value) { if(value!=null&&value) { // YES // Close the tab via TabSet.removeTab() }else { //No // DO NOTHING, ALREADY CANCELED EVENT } } }); } });
Leave a comment:
-
Yes I understand it, but how can I do it?
Can you give me an example?
thx.
Leave a comment:
-
Thats because the SC call is asynchronous, you need to cancel the event immediately, then programatically close the tab is the user says go ahead
Leave a comment:
-
Confirm-Dialog for closing tabs
Is it possible to have a SC.confirm box pop up for closing a tab? I have a tab that contains a form, and I would like to be able to close that tab but ask confirmation first.
I have the following code, but it does not work.
When i click on the close icon, then SC.confirm box popped up, but the tab will be removed before i make my chose.
What can i do?
Code:TabSet tabSet = new TabSet(); Tab tab1 = new Tab("Tab1"); tab1.setCanClose(true); Tab tab2 = new Tab("Tab2"); tab2.setCanClose(true); tabSet.addTab(tab1); tabSet.addTab(tab2); tabSet.setWidth100(); tabSet.setHeight100(); tabSet.show(); tabSet.addCloseClickHandler(new CloseClickHandler() { @Override public void onCloseClick(final TabCloseClickEvent event) { System.out.println(event.getTab().getTitle()); SC.ask("Close ?", new BooleanCallback() { @Override public void execute(Boolean value) { if(value!=null&&value) { //Yes event.cancel(); }else { //No event.cancel(); } } }); } });Tags: None
Leave a comment: