1. #1
    Sencha User
    Join Date
    Feb 2011
    Posts
    74
    Vote Rating
    3
    shaneavery is on a distinguished road

      1  

    Smile New "Multi-select field" extension for ST2 (RC)

    New "Multi-select field" extension for ST2 (RC)


    I created a new class that extends/overrides Select.js in a minimalistic way to provide a basic "Multi-select" field. It is based on Mitchell Simoens' concepts for his ST 1.1 multi-select field. The component stores the selected items as a comma separated string in the text field portion of itself. It uses a "List" dataview panel to provide a GUI for multiple selection of values. Other than that, it behaves identically to a "Select" field. It is designed to be added to an MVC folder structure, since I don't really consider it a "real" extension at this point. It has been tested in ST2 RC, and it seems to be working well so far. The code is below:

    Form field item definition (add this to your "Form Panel" config, note the xtype):

    Code:
                        {
                            xtype        : 'multiselectfield',
                            name         : 'Tags',
                            label        : 'Tags',
                            store        : 'Tags',
                            displayField : 'text', //don't change this property
                            valueField   : 'value', //don't change this property
                            usePicker    : false //required
                        }
    Multiselect class (create a file called "MultiSelect.js" and put it in your "app/view/" folder - then paste in this code - modify as necessary):

    Code:
    Ext.define('app.view.MultiSelect', {
        extend: 'Ext.field.Select',
        alias : 'widget.multiselectfield',
        xtype : 'multiselectfield',
        usePicker : false,  //force list panel, not picker
    
        getTabletPicker: function() {  //override with modified function
            var config = this.getDefaultTabletPickerConfig();
            if (!this.listPanel) {
                this.listPanel = Ext.create('Ext.Panel', Ext.apply({
                    centered: true,
                    modal: true,
                    cls: Ext.baseCSSPrefix + 'select-overlay',
                    layout: 'fit',
                    hideOnMaskTap: false,
                    items: {
                        xtype: 'list',
                        mode: 'MULTI', //set list to multi-select mode
                        store: this.getStore(),
                        itemTpl: '<span class="x-list-label">{' + this.getDisplayField() + '}</span>',
                        listeners: {
                            select : this.onListSelect,
                            itemtap  : this.onListTap,
                            hide : this.onListHide, //new listener
                            scope  : this
                        },
                        items: { //new button to trigger formatting/setting new field value with joined string
                            xtype: 'button',
                            text: 'Done',
                            ui: 'action',
                            height: '20px',
                            width: '50%',
                            docked: 'bottom',
                            style: 'margin-top: 10px; margin-bottom: 10px; margin-left: auto; margin-right: auto;',
                            listeners: {
                                tap: this.onButtonTap,
                                scope: this
                            }
                        }
                    }
                }, config));
            }
            return this.listPanel;
        },
        
        applyValue: function(value) {  //override with modified function
            var record = value,
                index;
            this.getOptions();
            if (!(value instanceof Ext.data.Model)) {
                index = this.getStore().find(this.getValueField(), value, null, null, null, true);
    
                if (index == -1) {
                    index = this.getStore().find(this.getDisplayField(), value, null, null, null, true);
                }
                //We do not want to get record from store //record = this.getStore().getAt(index);
                 this.element.dom.lastChild.firstChild.firstChild.value = value; //display csv string in field when value applied
            }
            return record;
        },
    
        updateValue: function(newValue, oldValue) {  //override with modified function
            this.previousRecord = oldValue;
            this.record = newValue;
            // String does not have methods //this.callParent([newValue ? newValue.get(this.getDisplayField()) : '']);
            this.fireEvent('change', this, newValue, oldValue);
        },
    
        getValue: function() {  //override with modified function
            var record = this.record;
            return (record) // Use literal string value of field // ? record.get(this.getValueField()) : null;
        },
    
        showPicker: function() {  //override with modified function
            //check if the store is empty, if it is, return
            if (this.getStore().getCount() === 0) {
                return;
            }
            if (this.getReadOnly()) {
                return;
            }
            this.isFocused = true;
            //hide the keyboard
            //the causes https://sencha.jira.com/browse/TOUCH-1679
            // Ext.Viewport.hideKeyboard();
            if (this.getUsePicker()) {
                var picker = this.getPhonePicker(),
                    name   = this.getName(),
                    value  = {};
    
                value[name] = this.record.get(this.getValueField());
                picker.setValue(value);
                if (!picker.getParent()) {
                    Ext.Viewport.add(picker);
                }
                picker.show();
            } else { //reworked code to split csv string into array and select correct list items
                var listPanel = this.getTabletPicker(),
                    list = listPanel.down('list'),
                    store = list.getStore(),
                    itemStringArray = new Array(),
                    values = this.getValue().split(','),
                    v = 0,
                    vNum = values.length;
                if (!listPanel.getParent()) {
                    Ext.Viewport.add(listPanel);
                }
                for (; v < vNum; v++) {
                    itemStringArray.push(values[v]);
                }
                v = 0;
                for (; v < vNum; v++) {
                    var record = store.findRecord(this.getDisplayField(), itemStringArray[v], 0, true, false, false );
                    list.select(record, true, false);
                }
                listPanel.showBy(this.getComponent());
                listPanel.down('list').show();
            }
        },
    
        onListSelect: function(item, record) {  //override with empty function
        },
    
        onListTap: function() {  //override with empty function
        },
    
        onButtonTap: function() {
            this.setValue('');
            this.listPanel.down('list').hide(); //force list hide event
            this.listPanel.hide({
                type : 'fade',
                out  : true,
                scope: this
            });
        },
    
        onListHide: function(cmp, opts) {
            var me = this,
                recordArray = this.listPanel.down('list').selected.items,
                itemStringArray = new Array(),
                v = 0,
                vNum = recordArray.length;
            for (; v < vNum; v++) {
                var value = this.getDisplayField();
                itemStringArray.push(recordArray[v].data.value);
            }
            if (itemStringArray.length > 0) {
                me.setValue(itemStringArray.join(','));
                this.listPanel.down('list').deselectAll();
            } else {
                me.setValue('None');
            }
        }
    });
    I am sure there are some things that may be better optimized in the code, but this is at least functional and tested. Hope this proves useful to whomever may need it until ST2 comes with its own Multi-select field. Let me know what you think. If there is an interest, I may post it on GitHub at a later date.

    Cheers!

    Sencha Fiddle Live Test:

    Last edited by mitchellsimoens; 8 Oct 2013 at 4:32 AM. Reason: Upgraded to RC1

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    35,672
    Vote Rating
    748
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    Two things, you have an id set on the list which is bad for an extension. What if someone uses that id in their app or has two of these fields? They would conflict. Other thing is you should put this up on GitHub so that people can fork it and do pull requests to make it better.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User
    Join Date
    Feb 2011
    Posts
    74
    Vote Rating
    3
    shaneavery is on a distinguished road

      0  

    Thumbs up Good points, thanks Mitchell...

    Good points, thanks Mitchell...


    I updated the code above to eliminate the id property and the dependance I unnecessarily created for it. I intend to post a GitHub link to this project on this thread as soon as I get a few more comments. This is my first stab at an extension, so I want a little more initial input in order to update this code before I call this an "official" extension. Your advice is a good start.

    Thanks.

  4. #4
    Sencha Premium Member
    Join Date
    Dec 2011
    Posts
    13
    Vote Rating
    1
    DodgyDave is on a distinguished road

      0  

    Default


    There's a couple of hard-coded references to the displayfield 'text'. These should be changed to this.getDisplayField().

    Code:
    var record = store.findRecord('text', itemStringArray[v], 0, true, false, false );
    
    itemStringArray.push(recordArray[v].data.text);

  5. #5
    Sencha User
    Join Date
    Feb 2011
    Posts
    74
    Vote Rating
    3
    shaneavery is on a distinguished road

      0  

    Thumbs up Thanks DodgyDave...

    Thanks DodgyDave...


    I updated the code above to reflect your corrections. Nice catch. This is the kind of constructive criticism I am looking for.

    Thanks.

  6. #6
    Sencha User
    Join Date
    Feb 2011
    Posts
    74
    Vote Rating
    3
    shaneavery is on a distinguished road

      0  

    Exclamation Updated for ST2 RC

    Updated for ST2 RC


    FYI...

    I upgraded the code to fix a bug for the RC release. This code will not work for Beta3 anymore.

  7. #7
    Sencha User
    Join Date
    Feb 2012
    Posts
    253
    Vote Rating
    0
    ci11111 is on a distinguished road

      0  

    Default


    Really nice work,
    for tablets/phones is it impossible to have a multiselect Picker?

  8. #8
    Sencha User
    Join Date
    Apr 2012
    Posts
    6
    Vote Rating
    0
    danielpizarro is on a distinguished road

      0  

    Default It was very useful for me. Thanks!

    It was very useful for me. Thanks!


    Hello, I modified a little the script to support the DisplayField and ValueField, and get in Value a list of valueField values instead of a list of DisplayFields.

    Thank you!

    PHP Code:
    Ext.define('app.view.Multiselect', {
        
    extend'Ext.field.Select',
        
    alias 'widget.multiselectfield',
        
    xtype 'multiselectfield',
        
    usePicker false,  //force list panel, not picker
        
    getTabletPicker: function() {  //override with modified function
            
    var config this.getDefaultTabletPickerConfig();
            if (!(
    this.listPanel)) {
                
    this.listPanel Ext.create('Ext.Panel'Ext.apply({
                    
    centeredtrue,
                    
    modaltrue,
                    
    clsExt.baseCSSPrefix 'select-overlay',
                    
    layout'fit',
                    
    hideOnMaskTapfalse,
                    
    items: {
                        
    xtype'list',
                        
    mode'MULTI'//set list to multi-select mode
                        
    storethis.getStore(),
                        
    //data:this.getOptions(),
                        
    itemTpl'<span class="x-list-label">{' this.getDisplayField() + '}</span>',
                        
    listeners: {
                            
    select this.onListSelect,
                            
    itemtap  this.onListTap,
                            
    hide this.onListHide//new listener
                            
    scope  this
                        
    },
                        
    items: { //new button to trigger formatting/setting new field value with joined string
                            
    xtype'button',
                            
    text'Done',
                            
    ui'action',
                            
    height'20px',
                            
    width'50%',
                            
    docked'bottom',
                            
    style'margin-top: 10px; margin-bottom: 10px; margin-left: auto; margin-right: auto;',
                            
    listeners: {
                                
    tapthis.onButtonTap,
                                
    scopethis
                            
    }
                        }
                    }
                }, 
    config));
            }
            return 
    this.listPanel;
        },


        
    applyValue: function(value) {  //override with modified function
            
    var record value,
            
    displayStringArray = new Array(),
            
    valueStringArray = new Array();
            
    valueStringArray = (value == null'':value.split(','));
            if ((
    value != null )&& value.trim().localeCompare('')){
                for (
    v=0;<valueStringArray.length;v++){
                    
    item this.getStore().find(this.getValueField(), valueStringArray[v], nullnullnulltrue);
                    
    displayStringArray.push(this.getOptions()[item][this.getDisplayField()]);
                }
            }
            
    this.element.dom.lastChild.firstChild.firstChild.value displayStringArray.join(','); //display csv string in field when value applied
            
    return record;
        },


        
    updateValue: function(newValueoldValue) {  //override with modified function
            
    this.previousRecord oldValue;
            
    this.record newValue;
            
    // String does not have methods //this.callParent([newValue ? newValue.get(this.getDisplayField()) : '']);
            
    this.fireEvent('change'thisnewValueoldValue);
        },


        
    getValue: function() {  //override with modified function
            
    var record this.record;
            return (
    record// Use literal string value of field // ? record.get(this.getValueField()) : null;
        
    },


        
    showPicker: function() {  //override with modified function
            //check if the store is empty, if it is, return
            
    if (this.getStore().getCount() === 0) {
                return;
            }
            if (
    this.getReadOnly()) {
                return;
            }
            
    this.isFocused true;
            
    //hide the keyboard
            //the causes https://sencha.jira.com/browse/TOUCH-1679
            // Ext.Viewport.hideKeyboard();
            
    if (this.getUsePicker()) {
                var 
    picker this.getPhonePicker(),
                
    name   this.getName(),
                
    value  = {};
                
    value[name] = this.record.get(this.getValueField());
                
    picker.setValue(value);
                if (!
    picker.getParent()) {
                    
    Ext.Viewport.add(picker);
                }
                
    picker.show();
            } else { 
    //reworked code to split csv string into array and select correct list items
                
    var listPanel this.getTabletPicker(),
                list = 
    listPanel.down('list'),
                
    store = list.getStore(),
                
    itemStringArray = new Array(),
                
    values = (this.getValue() == null'':this.getValue().split(',')),
                
    0,
                
    vNum values.length;
                if (!
    listPanel.getParent()) {
                    
    Ext.Viewport.add(listPanel);
                }
                for (
    0vNumv++) {
                    
    itemStringArray.push(values[v]);
                }
                
    0;
                for (
    0vNumv++) {
                    
    //var record = store.findRecord(this.getDisplayField(), itemStringArray[v], 0, true, false, false );
                    
    var record store.findRecord(this.getValueField(), itemStringArray[v], 0true,false,false );
                    list.
    select(recordtruefalse);
                }
                
    listPanel.showBy(this.getComponent());
                
    listPanel.down('list').show();
            }
        },


        
    onListSelect: function(itemrecord) {  //override with empty function
        
    },


        
    onListTap: function() {  //override with empty function
        
    },


        
    onButtonTap: function() {
            
    this.setValue('');
            
    this.listPanel.down('list').hide(); //force list hide event
            
    this.listPanel.hide({
                
    type 'fade',
                
    out  true,
                
    scopethis
            
    });
        },


        
    onListHide: function(cmpopts) {
            var 
    me this,
            
    recordArray this.listPanel.down('list').selected.items,
            
    //itemStringArray = new Array(),
            
    valueStringArray = new Array(),
            
    0,
            
    vNum recordArray.length;
            for (
    0vNumv++) {
                if  (
    recordArray[v].data[this.getDisplayField()]){
                    
    valueStringArray.push(recordArray[v].data[this.getValueField()]);
                }
            }
            if (
    valueStringArray.length 0) {
                
    //me.setValue(itemStringArray.join(','));
                
    me.setValue(valueStringArray.join(','));
                
    this.listPanel.down('list').deselectAll();
            } else {
                
    me.setValue(null);
            }
        }
    }); 

  9. #9
    Sencha User
    Join Date
    Aug 2011
    Posts
    10
    Vote Rating
    0
    eistrati is on a distinguished road

      0  

    Default Great work, guys!

    Great work, guys!


    It works pretty nice!

    One question: What happens if I want to predefine some values? I used Mitch's ST1 original version long time ago and << value: 'v1,v2,v3' >> was working perfectly.

  10. #10
    Sencha User
    Join Date
    Feb 2011
    Posts
    74
    Vote Rating
    3
    shaneavery is on a distinguished road

      0  

    Default Here are a couple of ways...

    Here are a couple of ways...


    Eistrati,

    You can pre-define values in your store by using the "data" config property in a store:

    Code:
    Ext.define('example.store.Tags', {
        extend: 'Ext.data.Store',
        config: {
            model: 'example.model.Tag',
            autoSync: true,
            autoLoad: true,
            proxy: {
                type: 'memory'
            },
            data : [
                {text: 'Ed',    value: 'Spencer'},
                {text: 'Tommy', value: 'Maintz'},
                {text: 'Aaron', value: 'Conran'},
                {text: 'Jamie', value: 'Avins'}
                 ]
            }
    });
    Or by adding the "options" config property to your form panel:

    Code:
        {
            xtype        : 'multiselectfield',
            name         : 'Tags',
            label        : 'Tags',
            displayField : 'text', //don't change this property
            valueField   : 'value', //don't change this property
            usePicker    : false //required
            options      : [
                        {text: 'Ed',    value: 'Spencer'},
                        {text: 'Tommy', value: 'Maintz'},
                        {text: 'Aaron', value: 'Conran'},
                        {text: 'Jamie', value: 'Avins'}
            ]
        }

film izle

hd film izle

film sitesi

takipci kazanma sitesi

takipci kazanma sitesi

güzel olan herşey

takipci alma sitesi

komik eğlenceli videolar