Sencha Touch

Dynamic Sencha Touch Forms – Part 1 – Enabling/Disabling Fields based on form selections

Sencha Touch,Web Design,Web Design & Development Blog 23 Comments

This tutorial is going to form a small series about creating dynamic forms that react to previous user input so you can keep your forms tidy and simple without the bloat of extra fields and so you can populate fields dynamically based on other selections.

In this tutorial we will start with the simple case of only wanting to show/enable fields when users have selected a specific value from, for example, a dropdown list.

As with all our tutorials there is a Tutorial Package that contains all the files you need to try the examples and follow the tutorial step by step. If you click on the Step’s header you can view the demo for that step.

Our Fake Scenario

We like to keep things as near to a real-life situation as we can so these tips and tutorials aren’t horrendously abstract so we’re going to model a form that will…..

Step 1 – Create our Form

We’re going to create our form as an extension of the Ext.form.FormPanel class and then instatiate and show it in our onReady function. We are going to add one Ext.form.Select field and an Ext.form.Toggle field that will be enabled/disabled based on the Select field’s value.


Ext.ns('DynamicForms'); // register our namespace

DynamicForms.MyForm = Ext.extend(Ext.form.FormPanel, {

    initComponent: function(){

        Ext.apply(this, {
            floating: true,
            width: 350,
            height: 370,
            centered: true,
            modal: true,
            hideOnMaskTap: false,

            items: [{
                xtype: 'selectfield',
                options: [{
                    text: 'Test', value: 'Test'
                }, {
                    text: 'Test2', value: 'Test2'
                }, {
                    text: 'Test3', value: 'Test3'
                }]
            }, {
                xtype: 'togglefield',
                label: 'Toggle Test',
                disabled: true
            }]
        });

        DynamicForms.MyForm.superclass.initComponent.call(this);
    }

});

Firstly, we’ve registered our DynamicForms namespace so that we have somewhere to put our MyForm class.

Within our Ext.apply call we’re adding our configuration. The first 6 options are there to size and position our panel so that we can see it and use it – nothing too fancy there.

Next we define our form’s items which contains a selectfield (Ext.form.Select) with 3 options and a togglefield (Ext.form.Toggle).

In the onReady function of our application we create a new instance of our MyForm class and call the show method on it.


Ext.setup({
    tabletStartupScreen: 'tablet_startup.png',
    phoneStartupScreen: 'phone_startup.png',
    icon: 'icon.png',
    glossOnIcon: false,
    onReady: function(){

        var myForm = new DynamicForms.MyForm(); //instatiate our new class
        myForm.show(); // show the form

    }
});

If you run this just now you should see a panel in the middle of your browser with the two form fields we created in it.

Step 2 – Make it Dynamic!

Now we can do the fun bit. We’re going to attach an event handler to the selectfield’s change event and enable/disable the toggle field based on the value that is selected.

First we create a function that will be executed when the selectfield’s value is changed. This event takes 2 parameters – a reference to the selectfield itself and the value that was selected. We will use the second parameter to determine if we want the togglefield to become active or not. The code listing below shows this function. In our little example we are going to enable the Toggle Field only when the user selects ‘Test2′.


onChange: function(selectField, value){

    if(value === 'Test2'){ // enable the togglefield if 'Test2' was selected
        this.items.get(1).enable();
    } else { // otherwise we disable it
        this.items.get(1).disable();
    }

}

Because in our example the default state of the togglefield is disabled we must have the else block in there to reset the togglefield to disabled if we are selecting a value other than ‘Test2′.

We could also use the setDisabled method of form fields which would allow us to cut down some lines of code. This method takes in a boolean value determining the disabled state of the field. In the previous code snippet we use an if/else statement based on the expression ‘value === ‘Test2′ but we could use the outcome of this (either true or false) as the input to the setDisabled method. If you wrap an expression in brackets it will be evaluated and the outcome can be used as if it were a variable. So our function becomes:


onChange: function(selectField, value){

    this.items.get(1).setDisabled((value !== 'Test2'));

}

We change the operator from ‘===’ to ‘!==’ so the logic is correct.

The last thing we have to do is attach this new function to the selectfield’s change event. We do this in the initComponent method right after we call the class’s superclass.

// get the selectfield then call the 'on' method to attach the handler
this.items.get(0).on({
    change: this.onChange,
    scope: this
});

We add the ‘scope’ option to make sure the onChange method runs in the scope of the FormPanel.

Now if we open the Step 2 HTML file in the Tutorial Package we can see it working. To start with the togglefield is disabled but if we select the Test2 value from the selectfield the field becomes enabled and its value changed. If we select Test3 then the togglefield is disabled again.

The Next Part…

I hope you’ve found this quick tutorial useful and please come back for the next part where I will show you how to dynamically load a select field’s data based on another select field’s value.

The second part of this tutorial can now be found here. We hope it is of use to you!

As always, please leave a comment and show us how you’ve put this tutorial to use!

Share this

23 Comments to "Dynamic Sencha Touch Forms – Part 1 – Enabling/Disabling Fields based on form selections"

  1. edmund

    February 15, 2011

    Hi,
    would like to know how you can integrate a submit button and a reset button into the above code, I am able to enable and disable text field based on the toggle field but I am not able to call for a DynamicForms.MyForm.reset() or a DynamicForms.MyForm.getform.reset().

  2. Andy

    February 15, 2011

    Hi Edmund,

    You’ll need to call the reset on the instance of the Form itself. For example


    {
    xtype: 'button',
    text: 'Reset',
    scope: this,
    handler: function() { this.getForm().reset(); }
    }

    Sorry about the formatting of the above but I hope it gives you an idea.

    Good luck!

  3. edmund

    February 16, 2011

    Hi Andy ,

    Already tried that .. gives an error saying Object [object Object] has no method ‘getForm’ …

  4. edmund

    February 16, 2011

    Ext.ns(‘DynamicForms’);
    // It sets up the namespace for the entire application. It’s a way to keep your application more organized and out-of-scope to other scripts and widgets

    DynamicForms.LogRegForm = Ext.extend(Ext.form.FormPanel, {

    initComponent: function(){

    var d = new Date();
    var curr_date = d.getDate();
    var curr_month = d.getMonth();
    var curr_year = d.getFullYear();
    var start_year = curr_year – 70;

    Ext.apply(this, {
    fullscreen: true,
    scroll: ‘vertical’,
    url : ‘postUser.php’,
    standardSubmit : false,
    items: [
    {
    xtype: 'selectfield',
    name: 'usrchoice',
    label: 'Tap here to Login or Register',
    options: [{
    text: 'Login', value: 'login'
    }, {
    text: 'Register', value: 'register'
    }]
    },

    {
    xtype: ‘emailfield’,
    id : ‘inputemail’,
    name : ‘usremail’,
    label: ‘Email’,
    placeHolder: ‘you@youremail.com’,
    useClearIcon: true,
    disabled: false
    },
    {
    xtype: ‘passwordfield’,
    id : ‘inputpassword’,
    name : ‘usrpassword’,
    label: ‘Password’,
    useClearIcon: false
    },

    {
    fieldLabel: ”,
    labelSeparator: ”
    },

    {
    xtype: ‘textfield’,
    id: ‘inputname’,
    name : ‘usrname’,
    label: ‘Name’,
    disabled: true,
    useClearIcon: true,
    autoCapitalize : false
    },
    {
    xtype: ‘textfield’,
    id : ‘inputcontact’,
    name : ‘usrcontact’,
    label: ‘Contact’,
    disabled: true,
    useClearIcon: true,
    disabled: true
    },
    {
    xtype: ‘datepickerfield’,
    id: ‘inputdob’,
    name : ‘usrdob’,
    disabled: true,
    label: ‘Date of Birth’,
    value: {
    day: curr_date,
    month: curr_month,
    year: start_year
    },
    picker: { yearFrom: start_year, yearTo : curr_year,
    slotOrder:['day', 'month', 'year']
    }
    },

    {
    xtype: ‘button’,
    text: ‘Reset’,
    style: ‘margin:2%;’,
    scope: this,
    // handler: this.tapHandler
    handler: function()
    {
    this.getForm().reset();
    console.log(‘Button Press Reset’);
    }
    },

    {
    xtype: ‘button’,
    text: ‘Submit’,
    id : ‘inputsubmit’,
    disabled: false,
    ui: ‘confirm’,
    style: ‘margin:2%;’,
    handler: this.tapHandler
    // handler: function() {
    // if(DynamicForms.MyForm.user){
    // form.updateRecord(DynamicForms.MyForm.user, true);
    // }
    // DynamicForms.MyForm.submit({
    // waitMsg : {message:’Submitting’, cls : ‘loading’}
    // });
    // }
    },
    ]
    });

    DynamicForms.LogRegForm.superclass.initComponent.call(this);

    this.items.get(0).on({
    change: this.onChange,
    scope: this
    });

    },
    //—————————————————————————
    onChange: function(selectField, value){
    //this.items.get(1).setDisabled((value !== ‘Test2′));
    if(value === ‘register’)
    {
    this.items.get(‘inputname’).enable();
    this.items.get(‘inputcontact’).enable();
    this.items.get(‘inputdob’).enable();
    } else
    {
    this.items.get(‘inputname’).reset();
    this.items.get(‘inputname’).disable();

    this.items.get(‘inputcontact’).reset();
    this.items.get(‘inputcontact’).disable();

    this.items.get(‘inputdob’).reset();
    this.items.get(‘inputdob’).disable();
    }
    },

    tapHandler: function (button, event) {
    switch (button.text)
    {
    case “Reset”:
    // DynamicForms.LogRegForm.getForm().reset(); // —- not working
    // DynamicForms.LogRegForm.reset(); // —- not working

    // this.items.each(function(f){
    // f.reset();
    // });
    // this.fireEvent(“reset”, this)
    //

    console.log(‘Button Press Reset ‘, event);
    return false;
    break;
    case “Submit”:
    //myForm.submit({…config object see API…})
    // to cancel event simply return false
    console.log(‘Button Press Submit’);
    return false;
    break;
    }
    },

    //——Integrate this into the above tap Handler later———————————————————————
    // listeners : {
    // submit : function(form, action){
    // Ext.Msg.alert(‘Status’, ‘Registration Successful!’, function(btn, text){
    // if (btn == ‘ok’){
    // var redirect = ‘index.php’;
    // window.location = redirect;
    // }
    // });
    // console.log(‘success’, Ext.toArray(arguments));
    // },
    // exception : function(form, action){
    // Ext.Msg.alert(‘Registration Failed!’, arguments[1].msg);
    // console.log(‘failure’, Ext.toArray(arguments));
    // }
    // }
    //

    });
    //—————————————————————————

  5. edmund

    February 16, 2011

    ps: how can i share my code here ?

  6. edmund

    February 16, 2011

    hi,
    Figured out … used this.reset();

  7. Andy

    February 16, 2011

    Hi Edmund,

    Pleased to hear you managed to figure it out! Sorry about the “getForm()” mix-up – I clearly had my Ext JS hat on yesterday.

    Andy

  8. mizan_bd

    March 29, 2011

    Is next part is uploaded? really waiting for next part of this tutorial,
    this tutorial is really useful and help me a lot.

  9. Stuart

    March 29, 2011

    Hi

    The next part is in the pipeline and will hopefully be published by the end of the week. We’re glad you found the first part useful and if you have any requests for future articles and tutorials then let us know.

    Sorry for the wait!

    Stuart

  10. robin

    April 11, 2011

    apologies for appearing greedy, but ditto on part 2. gold-standard stuff, many many thanks!!

  11. Stuart

    May 6, 2011

    Apologies for the very late second part to this tutorial – it can now be found here – http://www.swarmonline.com/2011/05/dynamic-sencha-touch-forms-part-2-dynamically-loading-select-fields-based-on-previously-selected-values/

    Thanks for the patience!
    Stuart

  12. Brandon

    May 24, 2011

    Andy/Stuart,

    Thanks for the great tutorials on your site! I have been just introduced to Sencha for my iOS dev needs and it has been great to learn about it from your posts.

    I did have one question which may or may not be related to this post. I have been trying to integrate the ability for the user of my app to add another field to a Sencha form. So, for example they are submitting a recipe and need to add a new ingredient on the fly – currently they are giving a defined amount of textfields for the recipe ingredients. Here is what I have so far

    var formBase = {
    scroll: ‘vertical’,
    url : ‘add_recipe.php’,
    standardSubmit : false,
    items: [
    {
    xtype: 'fieldset',
    title: 'Add Recipe',
    ref: 'fs',
    //instructions: 'Please enter the information above.',
    defaults: {
    required: true,
    labelAlign: 'left',
    labelWidth: '40%'
    },

    items: [

    {
    xtype: 'textfield',
    name : 'name',
    label: 'Recipe Name',
    useClearIcon: true,
    autoCapitalize : false
    },
    {
    xtype: 'textfield',
    name : 'ingredients',
    label: 'Ingredients',
    useClearIcon: true,
    autoCapitalize : false
    },
    {
    xtype: 'textfield',
    name : 'ingredients2',
    label: 'Ingredients 2',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients3',
    label: 'Ingredients 3',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients4',
    label: 'Ingredients 4',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients5',
    label: 'Ingredients 5',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients6',
    label: 'Ingredients 6',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients7',
    label: 'Ingredients 7',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients8',
    label: 'Ingredients 8',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textfield',
    name : 'ingredients9',
    label: 'Ingredients 9',
    useClearIcon: true,
    autoCapitalize : false,
    hidden: true
    },
    {
    xtype: 'textareafield',
    name : 'comments',
    label: 'Comments',
    maxLength: 50,
    maxRows: 5,
    height: 120
    },
    {
    xtype: 'emailfield',
    name : 'email',
    label: 'Email',
    placeHolder: 'user@foo.com',
    useClearIcon: true
    },
    {
    xtype:'button',
    text: 'Add Ingredient',
    handler: function() {

    var p = formBase.fs.items.items + 1;
    formBase.fs.insert(p, {
    xtype: 'textfield',
    name : 'ingredients',
    label: 'Added field'
    });
    formBase.fs.doLayout();
    }},

    ]

    }
    ],

    I get this error when it is submitted – “Uncaught TypeError: Cannot read property ‘items’ of undefined”

    Do you have any ideas? It seems straightforward but I can’t see the solution. Thanks again for your Sencha tutorials!

    Brandon

  13. Stuart

    May 26, 2011

    Do you only get that error when it is actually submitted or when the Add button is clicked (i.e. what line is causing the error?)?

    I think this is a good topic so I’ve thrown together an example and will write a blog post about it in the next couple of days. I’ll send over the example to you so you can have a look…

    Cheers
    Stuart

  14. Stuart

    May 30, 2011

    I have written a short article about creating new fields on the fly – check it out here!

  15. Mihail

    June 4, 2011

    I am glad to see that there are people who create tutorials for sencha touch! Thanks!

  16. Monta

    February 10, 2012

    Hi,
    I use Ext.util.JSONP.request to retrieve JSON response from a servlet, I receive well the response (in chrome’firebug console, using console.log()), then i affected the response (result) to a Ext.data.Store and that prints also the data of the dataStore in the console
    But when I tried to use the get method to show some elements from the dataStore I get this error

    “Uncaught TypeError: Object function (){return this.data.first()} has no method ‘get’”

    please help me!. my code is below

    Ext.regModel(“Currency”, {
    fields:['curr_code','curr_name'],
    hasMany: {model: ‘toCurrencyItem’, name: ‘liste_conv’}
    });

    Ext.regModel(“toCurrencyItem”, {
    fields: [
    'AED',
    'AFN',
    'ALL',
    'AMD',
    'ANG',
    'AOA',
    'ARS',
    'AUD'
    ],
    belongsTo: ‘Currency’

    });

    var matriceConversions = new Ext.data.Store({
    model: “Currency”

    });

    Ext.util.JSONP.request({
    url: ‘//localhost:8080/CurrencyConverterWS/JSONServlet’,

    params: {
    curr_from: form_convertisseur.getValues().curr_from,
    curr_to: form_convertisseur.getValues().curr_to
    },
    callbackKey: ‘callback’,
    callback: function(result) {

    matriceConversions.data = result;
    console.log(matriceConversions.data); // this works well
    var conv = matriceConversions.first; // this doesn’t work if use first() and it //shows the error “Uncaught TypeError: Object # has no method ‘first’”
    console.log(conv.get(‘curr_code’)); // this shows the error
    //”Uncaught TypeError: Object function (){return this.data.first()} has no method ‘get’”

    }

    });

    Monta,

  17. Stuart

    February 10, 2012

    Hi Monta,

    You should use the store’s loadData method to load your JSONP response into it. The store’s data property should contain a MixedCollection but is being given an array from your response instead which is causing the “no method’get’” error.

    So it should become:


    callback: function(result) {
    matriceConversions.loadData(result); // assuming your result is a plain array this may need to be decoded etc

    var conv = matriceConversions.first();

    ...etc
    }

    You might want to consider using the Ext.data.JsonPStore which will handle this functionality for you.

    Cheers
    Stuart

  18. Monta

    February 11, 2012

    Hi Stuart,
    Thank you very much !! it works well with store’s loadData and Ext.data.JsonPStore and without decoding

    but i still have a little problem because it doesn’t accept the store’s getById method like below

    var conv = matriceConversions.getById(“EUR”);

    it shows the error “Uncaught TypeError: Cannot call method ‘getById’ of undefined”

    My json file is below

    {
    “conversions”: [
    {
    "curr_code": "EUR",
    "curr_name": "EURO",
    "liste_conv": [
    {
    "TND": 2,
    "USD": 3
    }
    ]
    },
    {
    “curr_code”: “TND”,
    “curr_name”: “TUNISIAN DINAR”,
    “liste_conv”: [
    {
    "TND": 4,
    "USD": 5
    }
    ]
    }
    ]
    }

    thank you again,

    Cheers
    Monta,

  19. Stuart

    February 11, 2012

    I think this will be because your matriceConversions variable doesn’t contain a reference to a store instance. Try breaking into your code with FireBug/Developer Tools and seeing if the variable is undefined and where it should be set.

  20. Monta

    February 11, 2012

    matriceConversions is defined and firebug shows me the following
    Object {data=Object, events=Object, reader=Object, …}

    i realy don’t know what to do!

  21. Monta

    February 13, 2012

    Hi Stuart,
    i finally figured out how to retrieve data from the JSON response without using JsonPStore’s getById() method,
    with firebug i noticed that there is the object map witch contains the usefull data of the response

    this.matriceConversions.loadRecords(result) ; // or matriceConversions.loadData() also works
    var resultat_conv = matriceConversions.data.map.conversions[1].liste_conv[0].TND;
    console.log(resultat_conv ); // shows 4

    then, i could print “resultat_conv” on the console or a textfield for exemple

    my json response is like the following

    {
    “conversions”: [
    {
    "curr_code": "EUR",
    "curr_name": "EURO",
    "liste_conv": [
    {
    "TND": 2,
    "USD": 3
    }
    ]
    },
    {
    “curr_code”: “TND”,
    “curr_name”: “TUNISIAN DINAR”,
    “liste_conv”: [
    {
    "TND": 4,
    "USD": 5
    }
    ]
    }
    ]
    }

    Thank u again Stuart!

    hope it helps someone,

    Cheers
    Monta,

  22. Stuart

    February 14, 2012

    Glad you found a solution!

    Cheers
    Stuart

Leave a Comment