Looking back at our design, the main view is the container for the rest of the UI in our application. It looks like this:
// app/view/main/Main.js Ext.define('Postcard.view.main.Main', { extend: 'Ext.Panel', xtype: 'app-main', plugins: ['viewport', 'responsive'], controller: 'main', viewModel: 'main', session: true, responsiveConfig: { 'tall': { layout: { type: 'card' } }, 'wide': { layout: { type: 'hbox', align: 'stretch' } } }, dockedItems: [ { xtype: 'app-header' }, { dock: 'bottom', xtype: 'button', cls: 'logout', overCls: '', focusCls: '', text: 'Logout' } ], items: [ { xtype: 'threads', flex: 1 }, { xtype: 'container', flex: 1, defaults: { hidden: true }, items: [ { xtype: 'messages' }, { xtype: 'composer' } ] } ], isCard: function() { return this.getLayout().type === 'card'; } });
There's a lot going on here, but only a couple of new concepts. Note that we have added a couple of plugins to this class: viewport
and responsive
. As we didn't let our application auto create a view as a viewport
, adding the viewport
plugin will do just that. The responsive
plugin allows you to use the responsiveConfig
option, which we discussed earlier in the chapter.
In this instance, on screens that are tall, that is, higher than they are wide, such as portrait screens, we use a card layout. On screens that are wide, that is, wider than they are high, we use an hbox
layout because there's a lot more horizontal space. This simple declarative way of setting up a responsive view has allowed us to make very distinct changes to our application with only a few lines of configuration.
We've added a utility method to this view to help with manipulating our responsive setup; the isCard
view will let us neatly determine whether this view is using a card
layout or hbox
layout.
The rest of this configuration should be very familiar: two dockedItems
, one is the application header view and another supplying a logout button, and the three other views of this application in the items array.
On first glance, the code for this will look pretty standard, but when you look back at the code for the main view itself, you notice that currentTag
or searchTerm
is not going to be used anywhere. So, why define them if they're not going to be used? Refer to ViewModel
in the following code:
// app/view/main/MainModel.js Ext.define('Postcard.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', data: { currentTag: 'Inbox', searchTerm: null } });
In Ext JS, we have the concept of parent and child view models. The main view model, configured on the main view, will become available to all child components of the main view. This means that subviews can get data on the main view and also pass information back up to it. This is a fantastic way of passing data between two child components.
Refer to ViewController
in the following code:
// app/view/main/MainController.js Ext.define('Postcard.view.main.MainController', { extend: 'Ext.app.ViewController', alias: 'controller.main', routes: { 'thread/new': 'showRightPane', 'thread/:id/messages': 'showRightPane', 'thread/:id/messages/new': 'showRightPane' }, listen: { component: { 'button[cls="logout"]': { click: function() { window.localStorage.removeItem('loggedin'), window.location = '/'; } } } }, showRightPane: function(id) { if(this.getView().isCard()) { this.getView().setActiveItem(1); } } });
The most interesting thing happening in here is the route handlers; we're giving several routes to the showRightPane
handler. Looking back at our examination of user flow and routes in our application, many of the routes need us to ensure that the right-hand panel is in view. This only applies to the responsive portrait view, so we only change the active panel if the portrait view's card layout is available.
The interesting part is that we've got route handlers that only do part of what we'd expect. Where are the bits that pass IDs and show subviews? Don't worry, we'll revisit this shortly.