The data layer

When writing the prototype, I did partially build the data classes that were required, so now let's fully flesh them out:

// packages/wizard/src/model/Questionnaire.js
Ext.define('Wizard.model.Questionnaire', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'title' },
        { name: 'introduction' },
        { name: 'conclusion' }
    ],
    proxy: {
        type: 'rest',
        url: 'http://localhost:3000/questionnaire'
    },

    toJSON: function() {
        return this.getData(true);
    }
});

Standard stuff here with one exception is that a toJSON method that consumes applications can override in order to obtain JSON in a format they can use for further processing. The default implementation returns the data of the questionnaire object along with its association data. Alternatively, they can override the proxy configuration to save the questionnaire data to their own server.

Let's take a look at the model I used to represent a step in the questionnaire:

// packages/wizard/src/model/Step.js
Ext.define('Wizard.model.Step', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'title' },
        { name: 'introduction' },
        {
             name: 'questionnaireId',
             reference: {
                type: 'Wizard.model.Questionnaire',
                inverse: 'steps'
             }
         }
    ],

    isValid: function() {
        var valid = true;

        this.questions().each(function(q) {
            if(q.isValid() === false) {
                valid = false;
            }
        });

        this.set('valid', valid);
        
        return valid;
    }
});

There are a couple of things to note on the Step model; firstly, the use of associations, which I realized would provide a really easy way to load the nested data for the whole questionnaire in a single action.

Tip

The association is created using the reference option on the field with the type option specifying the full class name of the parent model and the inverse is the name of the association store that will be created on this parent Questionnaire. Ext JS 5 associations are a little confusing at first as they're always defined on the child, not the parent.

Secondly, the isValid method enumerates the questions belonging to this step and sets the step's own valid field according to the validity of its questions.

Finally, here's the Question model I built:

// packages/wizard/src/model/Question.js
Ext.define('Wizard.model.Question', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'name' },
        { name: 'required', type: 'boolean' },
        { name: 'questionText' },
        { name: 'type' },
        { name: 'answer' },
        {
             name: 'stepId',
             reference: {
                type: 'Wizard.model.Step',
                inverse: 'questions'
             }
         }
    ],
    
    validators: { answer: 'presence' },

    getValidation: function() {
        if(this.get('required')) {
            return this.callParent();
        } else {
            return new Ext.data.Validation();
        }
    }
});

Again, I have child-side of the Step -> Questions association defined in the same way as Questionnaire -> Steps. Using the validators config, I specify that answer should always be present, but I stumbled on a catch here that I could never have known about when just sitting down with a pencil and paper.

I really wanted to be able to add validators at runtime so that I could check the required field of the Step model and add the presence check to the answer. This enables the end user to toggle whether a particular question is required or not.

Unfortunately, after some intimate time with the Ext JS source code, it turns out that validators can only be defined on model class instances when they're defined and not on each instance of that class. Hopefully, this will be allowed in a later version—which at time of writing this book was 5.0.1—but I managed to come up with workarounds that enable this functionality.

We need to override the Question class's getValidation method. In the event that required is true, I call the getValidation on the superclass to proceed with validation normally. However, if it's false, I return a new Ext.data.Validation instance, but don't actually run its validation in effect, providing the same result as if the validation had passed.

While this works, and it's simple, it's one of these things that should be revisited with each new Ext JS version to see whether there's a more elegant way of solving the issue. I recommend code like this should be commented to let others know exactly why the workaround is needed and which version it applies to.

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

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