Form Destruction

When a form with multiple children that interact with each other via change event handlers is destroyed, those handlers get fired one last time and can cause exceptions depending on the order of the children in the DOM.

by Thomas Upton

HTML

<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.10.4/dojo/resources/dojo.css">
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/dojo/1.10.4/dijit/themes/tundra/tundra.css">
<body class="tundra"></body>

JavaScript

require([
    'dojo/_base/declare',
    'dijit/form/Select',
    'dijit/form/Form'
], function(declare, Select, Form) {
    
    // A form with two children. The parent form creates a handle to listen to changes on the *second* child.
    // That listener updates the *first* child when that event fires. Since the children get removed
    // in order when the form is destroyed, the "one-last-change-event" firing can cause
    // issues if that listener tries to interact with an already-destroyed child.
    // https://github.com/dojo/dijit/blob/03e1c2418c1eae2aa96bb536cc778499827e07aa/form/_FormWidgetMixin.js#L234-L237
    var MyForm = declare([Form], {
        postCreate: function() {
            var select1 = new Select({
                options: [
                    {value: 1, label: 'One'},
                    {value: 2, label: 'Two'}
                ]
            });
            
            var select2 = new Select({
                options: [
                    {value: 3, label: 'Three'},
                    {value: 4, label: 'Four'}
                ]
            });
            
            var handle = select2.on('change', function() {
                console.log('select2 change', arguments);
                console.log('this._destroyed', this._destroyed, 'this._beingDestroyed', this._beingDestroyed);
                console.log('select1._destroyed', select1._destroyed, 'select1._beingDestroyed', select1._beingDestroyed);
                console.log('select2._destroyed', select2._destroyed, 'select2._beingDestroyed', select2._beingDestroyed);
                console.trace();
                var opt = select1.getOptions('2');
                opt.disabled = true;
                select1.updateOption(opt); // interacts with select1's dom, but it doesn't exist any more so it throws
            }.bind(this));
            
            this._select1 = select1;
            this._select2 = select2;
            
            this.own(select1, select2, handle); //...