Let's break down the major views that will make up the main screen of the application:
The login view is the simplest; a self-contained view, view controller, and a view model to bind with the values of the login form. It's not shown in the preceding mockup because it's the only one on the screen at the time, almost standalone.
There's a caveat to this. For the first time, we'll be using an over-arching controller to deal with the interactions between views. In the previous chapter, this was left to the "main" view controller, since the "main" view was the container for every part of our application. Here, the login view and the rest of the application are effectively independent from each other and so it makes sense to have a "third party" helping them to work together.
We'll call this top-level controller our "root" controller. It's not a view controller, but a completely self-contained class that is responsible for showing the login view and reacting to a successful login. To formalize this:
Postcard.controller.RootController: extends Ext.app.Controller - onLaunch -> check for valid login - onLoginSuccess -> show main view
The login view controller is responsible for processing a login attempt and after doing so, it will fire off the appropriate actions. Along with its view and view model, it looks like this:
Postcard.view.login.Login: extends Ext.window.Window - items[] - e-mail: extends Ext.form.Text - password: extends Ext.form.Text - rememberMe: extends Ext.form.Checkbox - submit: extends Ext.Button Postcard.view.login.LoginModel: extends Ext.app.ViewModel - e-mail - password - rememberMe Postcard.view.login.LoginController: extends Ext.app.ViewController - onLoginClick
Assuming the onLoginClick
method is successful, we'll move on to the main screen of the application.
As in previous chapters, the main view is the viewport that contains the other views in the application, such as the application header and the list of threads. According to our design, the view should look like this:
Postcard.view.main.Main: extends Ext.Panel - items[] - app-header: extends Ext.Container - threads: extends Ext.DataView - container: extends Ext.Container - items[] - messages: Ext.Container - composer: Ext.form.Panel
A couple of things to note here, the primary views that make up our application are mentioned here: header, threads, messages and, composer. We're also doing a bit of forward thinking regarding our design, in that the composer and messages views are enclosed in a separate container.
This will allow us to more easily work with the Ext JS layout system, having the threads view and this anonymous container in an hbox
arrangement. The view model looks like this:
Postcard.view.main.MainModel: extends Ext.app.ViewModel - currentTag - searchTerm
It's just convenient for a few pieces of state that need to be shared between the views contained in the main view. The view controller looks like this:
Postcard.view.main.MainController: extends Ext.app.ViewController - onLogout - onHome - onShowThread - onNewThread - onNewMessage
The first method (onLogout
) will handle clicks on a logout button. The next four methods on the main view controller will be triggered by routing, and will be responsible for setting changes in the state of the application.
Remember that the main view and its associated classes don't really have any functionality of their own; they're responsible for orchestrating all of the other application parts contained within.
The first child view of the main viewport is the header view, containing a number of components that are available anywhere in the application as follows:
Postcard.view.header.Header: Ext.Toolbar - items[] - homebutton: extends Ext.Button - searchfield: extends Ext.form.TextField - tagfilter: extends Ext.form.ComboBox - newmessagebutton: extends Ext.Button - menubutton: extends Ext.Button
There's actually a surprising amount happening here. We also have to bear in mind that this is the target for one of our portrait orientation pieces of functionality, so there will be some usage of the responsive plugin in our implementation, as shown in the following code:
Postcard.view.header.HeaderController: extends Ext.app.ViewController - onHomeClick - onNewMessageClick
These methods are event listeners that will in turn trigger further functionality. You might wonder why we don't have handlers to toggle the menu open and closed or to choose an item from the combo box. Think about data binding. If we bind the state of the menu button and the combo box to a view model, other components can bind to the values in the view model and will receive updates without us having to write any glue code. To this end, the header view model will look like:
Postcard.view.header.HeaderModel: extends Ext.app.ViewModel - tags
Nothing more than a store to populate the tag filter combo box. We'll talk about this use of data binding further when we come to implement the header.
A thread is just a fancy way of saying "a collection of e-mail messages". We're going to use
Ext.DataView
for this:
Postcard.view.threads.Threads: extends Ext.DataView - stripHtml
We're going to support HTML e-mails in this application, but to prevent the thread view from looking messy, we'll strip out this HTML before presenting it to the user. Other than this, it's a normal implementation of DataView.
Postbox.view.threads.ThreadsModel: extends Ext.app.ViewModel - threads
The view model contains the thread store that powers the view as follows:
Postcard.view.threads.ThreadsController: extends Ext.app.ViewController - onThreadClick
There's only a single method here, one that is triggered by the itemclick
event on the thread DataView. It'll be responsible for redirecting the user to a list of messages in this thread.
The message view is responsible for showing the messages that make up a thread. As such, it's mainly based on DataView. It's a little more complicated than this though because DataView doesn't inherit from Ext.Panel
; it can't have its own child items or docked toolbar.
In this case, we need to have some tools at the bottom of the message list in order to change the thread tag and send a reply. Therefore, we wrap the DataView in a panel:
Postcard.view.messages.Messages: extends Ext.Panel - items[] - panel: extends Ext.Panel - items[] - messagelist: extends Ext.DataView - bbar[] - tagpicker: extends Ext.form.ComboBox - reply: extends Ext.Button
In the view model, we need two stores: one for the messages in the thread, and one for the tags that are available to choose from.
Postcode.view.messages.MessagesModel: extends Ext.app.ViewModel - messages - threads
The view controller has a couple of event handlers to manage the user's interactions with the message view:
Postcard.view.messages.MessagesController: extends Ext.app.ViewController - onReplyClick - onNewThread - onShwThread - onTagChange
There's now only one missing piece to this application—how do we write new messages?
The composer view is responsible for writing new messages and writing replies. It needs several UI components to accomplish this:
Postcard.view.composer.Composer: extends Ext.form.Panel - items[] - recipients: extends Ext.form.ComboBox - subject: extends Ext.form.TextField - message: extends Ext.form.HtmlEditor
Recipients and subject won't be used if the composer is replying to an existing thread. It will only be used when creating a new thread:
Postcard.view.Composer.ComposerModel: extends Ext.app.ViewModel - items[] - contacts - newMessage
We have a store of contacts to power the recipients' field, and an object to store the form values as the user enters them:
Postcard.view.composer.ComposerController: extends Ext.app.ViewController - onSendClick
The view controller will be responsible for saving the message to the server, which in turn would send it to the designated recipients.
We don't exactly have an address book in this application; instead, any previously used e-mail addresses are just saved and are available to pick in future messages.
We skipped over a lot of the data layer design this time around because it was very "boilerplate" in nature and we'd discussed such things in previous chapters. Why go through the class design process for the views and their associated view controllers and view models then? We've done this in previous chapters as well.
Clearly, every application is different. Breaking it down in this way helps us flesh out the code we're going to write without actually writing any code. This is important, because we'll avoid thinking too hard about the details of the implementation and have a better understanding of the shape of the larger pieces in the puzzle.
The next step is to revisit routes, events, and data flow and see how these large pieces will work together.