Now, we come to the view that allows you to actually send e-mails, a pretty important part of this application!
// app/view/composer/Composer.js Ext.define('Postcard.view.composer.Composer', { extend: 'Ext.form.Panel', xtype: 'composer', cls: 'composer', viewModel: 'composer', controller: 'composer', session: true, items: [ { xtype: 'hiddenfield', bind: '{newMessage.parentId}' }, { fieldLabel: 'To', xtype: 'combo', width: '100%', valueField: 'e-mail', displayField: 'e-mail', queryMode: 'local', bind: { hidden: '{newMessage.parentId}', store: '{contacts}', value: '{newMessage.people}' } }, { xtype: 'textfield', fieldLabel: 'Subject', cls: 'subject', emptyText: 'Subject', bind: { value: '{newMessage.subject}', hidden: '{newMessage.parentId}' }, width: '100%' }, { xtype: 'htmleditor', bind: { value: '{newMessage.body}' } } ], bbar: [ '->', { xtype: 'button', text: 'Send' } ] });
Another straightforward component definition, with the values of the form fields being bound to the newMessage
object in the view model for later use. There's another view model trick here, that is, if this newMessage
object has a parentId
value, we know that we're replying to an existing thread. This means that we can hide the subject and recipient form fields, so we bind the parentId
to their hidden value, making this step automatic as follows:
// app/view/composer/ComposerModel.js Ext.define('Postcard.view.Composer.ComposerModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.composer', stores: { contacts: { type: 'contacts' } }, data: { newMessage: {} } });
We have the contacts store that corresponds to the one in the view that was bound to the recipient's combo and then an empty definition for the newMessage
object discussed previously.
// app/view/composer/ComposerController.js Ext.define('Postcard.view.composer.ComposerController', { extend: 'Ext.app.ViewController', alias: 'controller.composer', listen: { component: { 'button': { click: 'onSendClick' } } }, routes: { 'thread/:id/messages': 'hideComposer', 'thread/:id/messages/new': 'showComposer', 'thread/new': 'showComposer' }, hideComposer: function() { this.getView().hide(); }, showComposer: function(parentId) { this.getViewModel().set('newMessage.parentId', parentId); this.getView().show(); }, onSendClick: function() { var session = this.getSession(), data = this.getViewModel().get('newMessage'), session.createRecord('Postcard.model.Message', { people: data.people, subject: data.subject, body: data.body, parentId: data.parentId }); var batch = session.getSaveBatch().start(); batch.on('complete', this.onSaveComplete, this); }, onSaveComplete: function(batch, operation) { var record = operation.getRecords()[0], id = record.getId(), parentId = record.get('parentId'), this.redirectTo('thread/' + (parentId || id) + '/messages'), } });
In component listeners, we handle the click
event of the send button with the onSendClick
method. This creates a new record on the current session and saves it to the server. In the callback
method, we dispatch the application to the route that shows thread messages, but note that we'll use the ID of the new message in the event of it being a brand new thread and the parentID
of the new message if it's a reply.
In terms of handling routes, there's one (hideComposer
) that hides the composer when viewing the messages in a thread because there's no need for it to be visible at that point. Then, there's a second (showComposer
) that sets the parentId
on the newMessage
and shows the composer. For new threads, there's no ID captured by the route, so the parentId
argument will be undefined and newMessage.parentId
will be set as such. This enables the automatic viewing and hiding of the recipient and subject back in the composer view itself. Back when designing the app, we referred to this as currentThreadId
, but we can see now that it makes sense to incorporate it in the newMessage
object and pass this to the server when we save the new record.