Skip to content

Instantly share code, notes, and snippets.

@vadimpopa
Last active August 4, 2019 11:36
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save vadimpopa/fcebedfc2570e6fe197188257e235e01 to your computer and use it in GitHub Desktop.

Sencha ViewModels tips and considerations.

  1. Always destroy your bindings in the destroy method of your view. In the example below, the grid is a child of a parent view which has a ViewModel. But if the child is removed from the parent the binding will still be called and can lead to unexpected surprises. So you could destroy them manually, or automatically by adding a ViewModel to the view

    Ext.define('MyApp.view.MyGridTab', {
        extend: 'Ext.grid.Panel',
        xtype: 'mygrid',
        columns: [],
        beforeRender: function () {
            var me = this;
            var vm = this.lookupViewModel();
                        
            me.callParent(arguments);
            me.myFieldBinding = vm.bind('{obj.myfield}', me.onMyFieldBindingChange,me);
        },
        onMyFieldBindingChange: function(myfield) {
          // Do some stuff with myfield build the search query, then load the store
          var searchRequest = this.buildSearchRequest(myfield);
          this.searchByRequest(searchRequest)
        },
        searchByRequest: function (searchRequest) {
            var me = this;
            var store = this.getStore();
    
            if (searchRequest) {
                store.getProxy().setExtraParam('search_request', searchRequest);
                store.load();
            }
        },
        destroyViewBindings: function () {
            if (this.myFieldBinding) {
                this.myFieldBinding.destroy();
                this.myFieldBinding = null;
            }
        },
        destroy: function () {
            this.destroyViewBindings();
            this.callParent();
        }
    });
  2. If possible avoid creating bindings in initComponent. The component may not be rendered but instantiated, like in tabpanels’ case, and the binding is called every time a change is detected. This occurs even if it is not needed, which decreases app performance. Depending on the context, you could use the solution below or create your bindings in beforerender.

  3. Only activate bindings when needed. In above example, the store of the grid tab should load the data only when the tab is visible and/or activated. Currently the ViewModel can't be disabled and enabled as needed. By default, it is initialized to run in the beforerender phase until destroy time. But like above, sometimes we need to optimize this so the performance is not affecteded by the bindings. So you may find useful having a more generic & flexible solution like the one below. A way to disable bindings running, is to remove them from the ViewModel -> Scheduler or by destroing them, which costs less than destroying & creating the ViewModel. A fiddle with the destroy approach here: https://fiddle.sencha.com/#fiddle/1bcf and https://github.com/vadimpopa/Ux.mixin.CustomBindable, and with the remove from scheduler, here: https://fiddle.sencha.com/#fiddle/1daj

        scheduler = bindings[i].getScheduler();
        scheduler.remove(bindings[i]);
  4. Use explicit binding in formulas to speed up formula parsing

    Ext.define('MyApp.view.MyModel', {
        extend: 'Ext.app.ViewModel',
    
        formulas: {
            // Ok
            isPdfUseDisabled: function (get) {
                var country = get('patentData.country');
    
                if (country) {
                    return !MyApp.util.isPdfCollection(country);
                }
    
                return false;
            }, 
    
            // Much better
            isPdfUseDisabled: {
                bind: '{patentData.country}',
                get: function (country) {
                    if (country) {
                        return !MyApp.util.isPdfCollection(country);
                    }
    
                    return false;
                }
            },
            
            // or multiple binding
            something: {
                bind: {
                    x: '{foo.bar.x}',
                    y: '{bar.foo.thing.zip.y}'
                },
    
                get: function (data) {
                    return data.x + data.y;
                }
            }
        }
    });
  5. Careful with stores data binding (with a memory proxy) because the nature of store.setData, the grid is not cleared if the bound data is changed to undefined or null

    Ext.define('MyApp.view.MyModel', {
        extend: 'Ext.app.ViewModel',
        formulas: {
            myCollectionsData: {
                bind: '{myData.anObject.myCollections}',
                get: function (myCollections) {
                    return myCollections || [];
                }
            }
        },
        store: {
            myCollections: {
                autoDestroy: true,
                fields: [],
                proxy: {
                    type: 'memory',
                    reader: {
                        type: 'json'
                    }
                },
                // This bind is going to leave your grid un-cleared because of the how
                // store.setData is coded.
                data: '{myData.anObject.myCollections}'
                
                // Much better, if to use a formula to accept default data
                data: '{myCollectionsData}'
            }
        }
    });
  6. As a naming rule use onBindingChange pattern when naming your bound handlers. Same for the bind references, see myFieldBinding

    me.myFieldBinding = vm.bind('{obj.myfield}', me.onMyFieldBindingChange,me);
  7. Don't overload your ViewModels with too much code or fragment the code. The balance should be between cleaning up and parent polluting. ViewModels can inherit from each other in a parent - child relation, so you can still access the parent's data in the child. Also, overloading means more dependencies to track.

    Ext.define('MyApp.view.MyChildModel', {
        extend: 'Ext.app.ViewModel',
        
        alias: 'viewmodel.mychild',
        
        formulas: {
            myCollectionsData: {
                bind: '{myData.anObject.myCollections}',
                get: function (myCollections) {
                    return myCollections || [];
                }
            }
        }
    });
    
    Ext.define('MyApp.view.MyParentModel', {
        extend: 'Ext.app.ViewModel',
        alias: 'viewmodel.myparent',
        data: {
            myData: null
        }
    });
    
    Ext.define('MyApp.view.MyWindow', {
        extend: 'Ext.window.Window',
        
        viewModel: {
            type: 'myparent'
        },
        
        items: [{
            xtype: 'mygrid'
        }]
    });
    
    Ext.define('MyApp.view.MyGrid', {
        extend: 'Ext.grid.Panel',
        xtype: 'mygrid'
        viewModel: {
            type: 'mychild'
        }
    });
  8. Don't repeat yourself. Sometimes you have to call one property a few times, like with dude in below example. Creating a formula to shorten bindings path won't add much benefit. Instead it will create additional bindings for the formula itself which means additional overhead for nothing. Here's a fiddle to experiment this https://fiddle.sencha.com/#fiddle/1bmk

        viewModel: {
            data: {
                simpsons: {
                    dude: {
                        name: 'jon simpson',
                        role: 'developer',
                        phone: '3434343'
                    }
                }
            },
            
            formulas: {
                dude: function(get) {
                    return get('simpsons.dude');
                } 
            }
        },
        ........
        
            items: [
                {
                    xtype: 'component',
                    bind: {
                        html: 'hey {dude.name}'
                    }
                },
                {
                    xtype: 'component',
                    bind: {
                        html: 'is your phone {dude.phone} ?'
                    }                
                }
            ]
  9. A quick & easy custom Ext.form.field.Display could be done by using a Bind and a Renderer

        xtype: 'displayfield',
        fieldLabel: 'Custom display field',
        bind: {
            value: '{fooArray}'
        },
        customFieldTpl: new Ext.XTemplate(
            '<tpl for="." between=";&nbsp;">',
                '<span class="search-field-link" data-value="{xindex}"' ,
                    ' style="{[values.invention === "YES" ? "font-weight: bold;" : ""]}">{name}</span>',
            '</tpl>'
        ),
        renderer: function (value, field) {
            var html = field.customFieldTpl.apply(value);
            return field.htmlEncode ? Ext.util.Format.htmlEncode(html) : html;
        }
  10. A way to bind your extraParams (proxy.extraParams) is using a formula:

            formulas: {
            	extraParams: {
            		bind: '{properties}',
            		get: function() {
            			return {
            				code: this.get('properties.code'),
                    		        versionId: this.get('properties.versionId'),
                    		        cmd: 'getTermsAndAbbreviations'
            			}
            		}
            	}
            },
            stores: {
            	mystore: {
            		proxy: {
            			extraParams: '{extraParams}'
            		}
            	}
            }
@michalzielanski
Copy link

  1. Alternative:
links: {
    dude: '{simpsons.dude}'
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment