In a blog post I ran across recently, someone was trying to intercept the closure of a window via the “X” icon in the top right corner. The idea was to be able to run additional processing before closing the window (such as confirming save/cancel of data, resetting a form, updating a record, or whatever else).

Turns out this is pretty simple to accomplish, but it’s good to understand what’s going on behind the scenes before implementing the solution.

The “Close” Tool

When you specify that your Window (a glorified extension of Panel) is “closable” (the default behavior, btw), here’s what happens in the background:

initTools: function() {
    var me = this;
    me.tools = me.tools || [];
    ......
    // Add subclass-specific tools.
    me.addTools();
    // Make Panel closable.
    if (me.closable) {
        me.addClsWithUI('closable');
        me.addTool({
            type: 'close',
            handler: Ext.Function.bind(me.close, this, [])
        });
    }
    // Append collapse tool if needed.
    if (me.collapseTool && !me.collapseFirst) {
        me.tools.push(me.collapseTool);
    }
}

As you can see, if the Window is closable, a tool of “close” type is added automatically, and fires the panel’s “close” method when clicked. Here’s what close() looks like:

close: function() {
    if (this.fireEvent('beforeclose', this) !== false) {
        this.doClose();
    }
}

The important thing to note here is that the “beforeclose” event is fired before the actual closing of the window occurs. As the docs point out, if you return false from “beforeclose”, you can actually prevent the closure of the window.

Obviously, this is what we’ll want to tap into, so let’s see an example of this in action.

The Example

First, here’s my view:

Ext.define("Example.view.example.Window", {
    extend: "Ext.window.Window",
    alias: "widget.examplewindow",
    title: "Add/Edit Stuff",
    closeAction:"hide",
    fbar: [
         '->',
         {text:"Save Stuff"}
    ],
    initComponent: function() {
       ....
       this.callParent(arguments);
    }
});

Nothing much here. Just a simple window, with a “close” tool (via closable being set to true by default) and another proper button in the footer that I’ll use to show the difference in how we can close the window. More on that later.

And now bits from the controller:

// figure 1
"examplewindow button": {
    click: function() {
        ....
        this.getExampleWindow().hide();
    }
},
// figure 2
"noteswindow tool": {
    click: function() {
        this.closewindow();
    }
}
// figure 3
"examplewindow": {
    beforeclose: function() {
        alert('im cancelling the window closure by returning false...')
        return false;
    },
    hide: function() {
        alert('closed')
    }
},

......
closewindow: function() {
    //... do some custom stuff here
    this.getExampleWindow().hide()
}

Obviously, you’d probably want to go about this a bit more elegantly. However, I’ve broken things out like this in order to illustrate what’s going on in the example. So let’s step through this.

First, If I click the proper button in the footer of the window (figure 1), the handler for the button’s click event will fire, and I’ll close the window by executing the panel’s hide() method. This will effectually close the window *without* destroying the underlying HTML. At the end of the process, the hide() event of the window itself will be fired (figure 3), and I’ll get a nice alert.

Next, if I click the “close” tool in the top-right of the window (figure 2), some interesting things happen. You’ll notice immediately that I’ve defined a method of the controller (closewindow()) to execute. This will certainly happen. However, before we follow this down the line, remember back to the original discussion about the default set up of the “close” tool. Unless we override it, the “click” action of the close tool will automatically call the panel’s close() method. So if we were *only* to try to call our own custom function in the click handler (this.closewindow()), we would get unexpected results since the default close() method would continue to execute.

However, remember also that before the act of closing the window is followed completely through, the default setup of the close tool fires the “beforeclose” event, which we can use to cancel the close action itself.

And that’s exactly what we’ve done. In figure 3, the handler for the window’s events has a listener for “beforeclose”. If we click the close tool, we’ll get the alert, return false, and then the window will be prevented from closing.

If you run this example, though, the window will, in fact, close. That’s because of the custom method (closewindow) that we’re using to do extra functionality before closing has its own means of closing the window. This is desirable, though, since it allows us to potentially cancel the closure of the window if needed (e.g., to prevent form data loss without save, etc.)

Wrapping Up

Clear as mud? 🙂 A couple things I want to note to wrap up this discussion.

The first is that you’ll notice the “beforeclose” event is *only* triggered (in this example, at least) when the close tool is clicked, and NOT when our footer button is clicked. This is because the “beforeclose’ event is tied directly to the panel’s close() method. If we used close() on our footer button instead of hide() to close the window, the beforeclose event would fire, precisely as if we clicked the close tool in the header.

And finally, as with most things, you should be extremely careful with this. Not because you’ll “break” something, or because it’s a “bad” practice. No, you should be careful because you’re messing with user-interface expectations. That is, when people click the “X” in a window/folder/application/whatever, they’ve been trained to expect that this will actually close something. So if you really need to do something special on the close tool’s click event, do it–but make sure that you keep the user’s expectation in mind. Prompting them to save their progress before closing a window is great; preventing them from being able to close a window without a good reason is not. Don’t be that guy.