Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    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();
    				}
    				
    			}
    		});				
    		
    	}
    });

    #2
    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

    Comment


      #3
      Yes I understand it, but how can I do it?
      Can you give me an example?

      thx.

      Comment


        #4
        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
        				}
        				
        			}
        		});				
        		
        	}
        });

        Comment


          #5
          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:

          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);
          							}
          								
          						}
          						
          					});
          				}
          			}
          		}
          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.

          Any ideas?

          Comment


            #6
            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:

            Code:
            public class X extends Tab
            {
            
               Controller controller;
              public X()
            {
             controller = new Controller();
            }
            
            public class Controller(){
            
              public void destroy()
             {
            
             }
            }
            
            }
            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....

            I guess I should try renaming my inner class method to tearDown() or something and see what that gives...

            Comment


              #7
              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.

              Comment


                #8
                Try adding the removeTab code in a DeferredCommand.

                Comment


                  #9
                  Thanks smartgwt.dev.

                  I changed the code to this:

                  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);
                  										
                  									}
                  									
                  									
                  								});
                  							 
                  							}
                  							
                  						}
                  						
                  					});
                  				}
                  			}
                  		}
                  Still the same problem though: works fine in DevMode, does not work in HostedMode.

                  Comment


                    #10
                    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?

                    Comment


                      #11
                      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:

                      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);
                      		
                      	}
                      
                      
                      }
                      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.

                      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:

                      Code:
                      if(!formChanged)
                      {
                      	controller.tearDown();
                      	tabSet.removeTab(AbstractDIDOrderRecordView.this);
                      }
                      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.

                      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...

                      Comment


                        #12
                        I think I've found what the problem is. It's this line of code in the controller:

                        Code:
                        		LinkedHashSet<Controller> componentsToDestroy = (LinkedHashSet<Controller>)componentControllers.clone();
                        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.

                        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:

                        Code:
                        LinkedHashSet<Controller> componentsToTearDown =  new LinkedHashSet<Controller>();
                        			
                        				for(Controller component : componentControllers)
                        				{
                        					componentsToTearDown.add(component);
                        				}
                        it should work.

                        Comment


                          #13
                          Yup, that's what the problem was. So, lesson learned : stay the hell away from .clone() or Cloneable in GWT :)

                          Comment

                          Working...
                          X