Data flow

With the previous data structure in mind, I started to think more about how the information supplied from an API would flow through view models and allow me to build the user interface.

I had a feeling that validation was going to complicate the application, so I started sketching out some ideas for the parts of the UI that would be affected. The user can't proceed to the next step until the current step is valid. In this, its mandatory questions have been answered so the "next" button needs to be disabled until this happens. I started drawing again, but quickly a degree of "analysis paralysis" kicked in. How should this work? There are several Ext JS features that could help; will model validation binding work here or will it have to be manual handling of validation events. Earlier, I mentioned "checks and balances" to avoid building something that Ext JS won't support; I decided to take some time out and investigate the best way to implement this in Ext JS.

Call this a "spike" as we did in Chapter 5, Practical – a CMS Application, or call it a prototype. It doesn't really matter whether we write this code and then throw it away; if it helps the process, then it's a valuable exercise. I built it standalone (outside of an MVC or MVVM structure) and it's horrible. Look at this code snippet:

s.questions().each(function(q) {
   var input = stepForm.add({ 
          xtype: 'textfield', modelValidation: true,
          viewModel: {
                data: { question: q },
                formulas: {
                      isValid: {
                            bind: {
                                   bindTo: '{question}',
                                   deep: true
                            },
                            get: function() {
                                  return this.get('step').isValid();
                            }
                      }
                }
          },
          bind: '{question.answer}',
          fieldLabel: q.get('text')
   });
});

I don't wish this code to be my worst enemy. Shouldn't every piece of code we write be pristine and well considered? In the real world, almost no one can see this code. It's to test out my ideas and find out what feels right and it doesn't have to be tidy or maintainable. It's a one-off. Here's what I wanted to test out:

  • Will the modelValidation feature be useful with auto-generated form fields?
  • Will I be able to bind to questions to get the validation state of the current step?
  • Will it feel natural to use these features or is there an easier way?

We've seen how view models can be used to create simple declarative applications and they can be extremely powerful when used correctly. In this application, fields are being generated from API data and each field had its own question model. Propagating the validity of this model up to the relevant parts of the user interface via view models seemed like it might be difficult. What did I discover?

  • A view model binding trick
  • A slight difference of opinion with the team at Sencha regarding the way modelValidation behaves
  • A way of binding a button state to the state of an association

This seems like a great result for this grubby code. Let's look at each point in turn.

A binding trick

This snippet of code is from the view model of a question's form field:

formulas: {
   isValid: {
          bind: {
                bindTo: '{question}',
                deep: true
          },
          get: function() {
                return this.get('step').isValid();
          }
   }
}

The view model has a question model and inherits the step model from its parent view model. We tell Ext JS that we'd like to bind to changes in the question model, deep changes, so any of its property changes and then trigger the isValid method on the parent step. This is great because the isValid method can in turn trigger changes on the parent step, propagating the state of the questions up to the step. Here's the isValid code:

isValid: function() {
   var valid = true;
   this.questions().each(function(q) {
          if(q.isValid() === false) {
                valid = false;
          }
   });
   this.set('valid', valid);
   return valid;
}

It checks the validity of all the child questions and sets the valid property of the step accordingly. We can then bind to this valid value and have it affect other things (such as the disabled state of the "next" button in the UI).

This is slightly counterintuitive because we're not binding directly to the isValid formula. Instead, we're using it to watch for changes on the question and then trigger changes on the step model.

A difference of opinion

There was a change in the behavior between Ext JS 5.0 and 5.0.1. The modelValidation feature matches validation in the model with validation in the form, reducing duplication of code. In Ext JS 5, when the form values change, these changes will be synchronized to the model via binding. In Ext JS 5.0.1, this synchronization will only happen when the form field is valid. The idea is that the model should never be left in an invalid state based on form changes, but the problem occurs like this:

  • User completes the form field and it becomes valid
  • The form value is passed to the model's field, which in turn becomes valid
  • User changes the form field to make it invalid again
  • The change is not passed to the model, which remains valid

This means that when binding to model value, you'll get something that doesn't reflect the real state of the UI. The model will say everything is valid when in fact the UI says differently. In the case of this application, the plan is to bind the "next" button to the model state, which will enable the button incorrectly in the preceding situation. My solution was to override this and return to the Ext JS 5.0 behavior as follows:

Ext.form.field.Text.prototype.publishValue = function () {
    var me = this;

    // Don't check for errors when publishing the field value
    //if (me.rendered && !me.getErrors().length) {
    if (me.rendered) {
        me.publishState('value', me.getValue());
    }
}

An alternative workaround would be to bind the "next" button to the value of the form field rather than the model value, but in our case, we need to do further work on the model value, so it's not a great solution.

A means to an end

All of this allows you to have a chain of configuration that goes all the way from each individual question field to the question model, then to the step model, and to higher-level places in the user interface that depend on it.

When I was writing this chapter, there were other ideas kicking around in my head as to how to implement it. A solution that handles validation events and uses listeners to propagate state through the application was another approach, but it ended up being less elegant despite the final idea being a little more complicated than our previous binding code.

Ext JS allows you to watch for deep changes in associations en-masse that would help, but at the time of writing this book, this change is not documented. Hopefully, it's coming in a post-5.0.1 version!

Note

You can see this change documented in the 5.0.1 release notes as EXTJS-13933 at http://dev.sencha.com/extjs/5.0.1/release-notes.html.

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

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