The head of the family

Refer to the Header.js file:

// app/view/header/Header.js
Ext.define('Postcard.view.header.Header', {
    extend: 'Ext.Toolbar',
    requires: ['Postcard.view.header.HomeButton'],
    xtype: 'app-header',
    height: 60,
    controller: 'header',
    viewModel: 'header',
    session: true,
    items: [
        { 
            xtype: 'home-button', cls: 'title', html: 'Postcard',
            bind: { hidden: '{menuButton.pressed}' }
        },
        {
            xtype: 'tbspacer',
            bind: { hidden: '{menuButton.pressed}' } },
        { 
            xtype: 'textfield', flex: 1,
            cls: 'search-box', emptyText: 'Search', 
            bind: '{searchTerm}',
            plugins: ['responsive'],
            responsiveConfig: {
                'tall': { 
                    hidden: true,
                    bind: { hidden: '{!menuButton.pressed}' }
                }, 
                'wide': { hidden: false } 
            }
        },
        { 
            xtype: 'tbfill',
            bind: { hidden: '{menuButton.pressed}' }
        },
        { 
            xtype: 'combobox', flex: 1, editable: false,
            displayField: 'name', idField: 'name', 
            queryMode: 'local', forceSelection: true,
            bind: {
                store: '{tags}', value: '{currentTag}'
            },
            plugins: ['responsive'],
            responsiveConfig: {
                'tall': {
                    hidden: true,
                    bind: { hidden: '{!menuButton.pressed}' }
                }, 
                'wide': { hidden: false } 
            }
        },
        { 
            xtype: 'button', cls: 'new-message',
            text: 'New Message',
            bind: { 
                hidden: '{menuButton.pressed}'
            }
        },
        { 
            text: 'Menu', reference: 'menuButton',
            width: 30, enableToggle: true,
            plugins: ['responsive'],

            responsiveConfig: {
                'tall': { hidden: false }, 
                'wide': { hidden: true } 
            }
        }
    ]
});

Wow! That's actually a lot of code for a header bar! Look back at our original class design for this view and we did say there was "a surprising amount happening", so we weren't wrong.

In the section, A binding agreement we discussed a cut-down example of what is happening in this class. The reference option on the menu button is used to allow the other header components to bind to the menu's pressed value; look at the previous code and you'll see this approach used in various places to show or hide components when the menu button is toggled.

We're not only using the responsive plugin again mostly to set the initial hidden state of the header components, but also using it to make sure the hidden config is only bound when the viewport is tall. This avoids issues with initial visibility of other components when the menu button isn't even in use. This kind of conditional binding opens up some exciting possibilities.

A couple more things of note: we mentioned the main view model had some values that seemed unused. Well, here they are, bound to the values of the tag filter combo and the search text field. When these values change, they'll be passed up to the main view model and available for use by other components.

There's a final item of note: a mysterious home-button component. The code for this looks like this:

Ext.define('Postcard.view.header.HomeButton', {
    extend: 'Ext.Container',
    xtype: 'home-button',

    afterRender: function() {
        this.callParent(arguments);
        this.getEl().on('click', function() {
            this.fireEvent('click'),
        }, this);
    }
});

We're using this as a fake button, extending the simple container to fire a click event. This allows you to get a lightweight, unstyled, and clickable component to use as a home button.

Header ViewModel

Refer to ViewModel in the following code:

Ext.define('Postcard.view.header.HeaderModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.header',
    stores: {
        tags: {
            type: 'tags',
            session: true
        }
    }
});

This ViewModel class supplies the tags to populate the tag filter combo. We use a session to make sure that we're using the same tag instances across the application.

Header ViewController

Refer to ViewController in the following code:

// app/view/header/HeaderController.js
Ext.define('Postcard.view.header.HeaderController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.header',
    listen: {
        component: {
            'button[cls="new-message"]': {
                click: function() {
                    this.redirectTo('thread/new'),
                }
            },

            'home-button': {
                click: function() {
                    this.redirectTo('home'),
                }
            }
        },

        controller: {
            '*': {
                tagadded: function() {
                    this.getViewModel().get('tags').reload();
                }
            }
        }
    }
});

There are two component event listeners, one on the new message button and one on the home button. Both redirect to routes that will be consumed by other controllers.

There's also a controller listener that waits for a tagadded event and refreshes the tag store on ViewModel. This is great because we don't have to worry about where this event comes from or which component issued it; we just consume it in isolation and perform the action we're interested in.

The reverse applies too, meaning the issuer of the tagadded event doesn't need to work out how to refresh the tag filter combo; instead, it can just declare that a tag was added and rest easy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset