Using render selectors to capture element references

August 14, 2013

One of the little known features of Ext JS is renderSelectors. It is used mostly internally but can come handy from time to time on the application side, too. The main purpose of the renderSelectors is to support compound Component templates: when you create a Component that consists of several DOM elements, grabbing the references to them manually is kind of boring. So suppose that you want to create a Component that has an image and a piece of text, some kind of a banner:

<div class="mycomponent">
    <img src="/foo.jpg" style="float:left">
    <p>This is some text</p>
</div>

You can probably implement it like that:

Ext.define('MyComponent', {
    extend: 'Ext.Component',

    cls: 'mycomponent',
    tpl: [
        '<img src="/foo.jpg" style="float:left">',
        '<p>This is some text</p>',
    ],
    data: {} // Needed for the tpl to kick in on rendering
});

That would work as expected, right? You can create an instance of this Component, and it will render the structure we wanted to the DOM. But what if you need to change the image and text over time? Let’s make this Component dynamic by using the DOM selectors:

Ext.define('MyComponent', {
    extend: 'Ext.Component',

    cls: 'mycomponent',
    tpl: [
        '<img src="/foo.jpg" style="float:left">',
        '<p>This is some text</p>',
    ],
    data: {},

    setImage: function(newSrc) {
        this.getEl().down('img').set({ src: newSrc });
    },

    setText: function(newText) {
        this.getEl().down('p').setHTML(newText);
    }
});

There is nothing wrong with this approach, but doing DOM lookups every time is a bit inefficient. Taking references and reusing them would be much more effective, and this is the way it was done before Ext JS 4.x:

Ext.define('MyComponent', {
    extend: 'Ext.Component',

    cls: 'mycomponent',
    tpl: [
        '<img src="/foo.jpg" style="float:left">',
        '<p>This is some text</p>',
    ],
    data: {},

    onAfterRender: function() {
        this.callParent();

        this.imgEl = this.getEl().down('img');
        this.textEl = this.getEl().down('p');
    },

    setImage: function(newSrc) {
        this.imgEl.set({ src: newSrc });
    },

    setText: function(newText) {
        this.textEl.setHTML(newText);
    }
});

Since that is a fairly common task, we shouldn’t do it manually every time, right? That is exactly what renderSelectors are for, and let’s use them:

Ext.define('MyComponent', {
    extend: 'Ext.Component',

    cls: 'mycomponent',
    tpl: [
        '<img src="/foo.jpg" style="float:left">',
        '<p>This is some text</p>',
    ],
    data: {},

    renderSelectors: {
        imgEl: 'img',
        textEl: 'p'
    },

    setImage: function(newSrc) {
        this.imgEl.set({ src: newSrc });
    },

    setText: function(newText) {
        this.textEl.setHTML(newText);
    }
});

No manual work anymore, it’s all nice and declarative. But wait, there’s more! Since renderSelectors are just DOM queries that run down the DOM tree from the Component’s main element, you can actually use them to look up the elements in your Component’s children!

Wait, what? Why would I want to do anything like that?

One fairly common example is showing an Ext JS window with some external content in it, like receipts generated by 3rd party system, document print previews, etc. The way to do that is to use an IFrame, but how do we make it look neat and ExtJS-y? Here’s how:

Ext.define('MyIframeWindow', {
    extend: 'Ext.window.Window',

    // Window is a floating component, it cannot size itself
    // So we will size it to a percentage of the Viewport dimensions
    width: Ext.Element.getViewportWidth() * 0.8,
    height: Ext.Element.getViewportHeight() * 0.8,

    // The easiest way to make IFrame fit into the window
    // is to make it a Component and use fit layout on it
    layout: 'fit',
    items: [{
        xtype: 'box',
        autoEl: {
            tag: 'iframe'
        }
    }],

    // IFrame element is in the child Component but who cares?
    renderSelectors: {
        iframeEl: 'iframe'
    },

    loadDocument: function(uri) {
        var el = this.iframeEl;

        // Windows are lazy and render themselves to the DOM
        // only when they're shown the first time, so if you
        // call loadDocument before first show(), the iframeEl
        // will be missing. This is to avoid the kaboom.
        if (el) {
            el.set({ src: uri });
        }
    }
});

Easy, right? Now you have an IFrame that neatly fits the window, can be resized and dragged around, etc. Just make sure you show() it before calling loadDocument():

var myWindow = new MyIframeWindow();

myWindow.show();
myWindow.loadDocument('foo.html');

Neat, huh? Happy coding! :)

tags: , ,
posted in Software development by nohuhu

Follow comments via the RSS Feed | Leave a comment | Trackback URL

Leave Your Comment

 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org