123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- /**
- * @class Ext.ux.form.MultiSelect
- * @extends Ext.form.field.Base
- * A control that allows selection and form submission of multiple list items.
- *
- * @history
- * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
- * 2008-06-19 bpm Docs and demo code clean up
- *
- * @constructor
- * Create a new MultiSelect
- * @param {Object} config Configuration options
- * @xtype multiselect
- */
- Ext.define('Ext.ux.form.MultiSelect', {
- extend: 'Ext.form.field.Base',
- alternateClassName: 'Ext.ux.Multiselect',
- alias: ['widget.multiselect', 'widget.multiselectfield'],
- uses: [
- 'Ext.view.BoundList',
- 'Ext.form.FieldSet',
- 'Ext.ux.layout.component.form.MultiSelect',
- 'Ext.view.DragZone',
- 'Ext.view.DropZone'
- ],
- /**
- * @cfg {String} listTitle An optional title to be displayed at the top of the selection list.
- */
- /**
- * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).
- */
- /**
- * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).
- */
- /**
- * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).
- */
- ddReorder: false,
- /**
- * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's selection list.
- * This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config, or an array of buttons/button configs
- * to be added to the toolbar. See {@link Ext.panel.Panel#tbar}.
- */
- /**
- * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled
- * (use for lists which are sorted, defaults to false).
- */
- appendOnly: false,
- /**
- * @cfg {String} displayField Name of the desired display field in the dataset (defaults to 'text').
- */
- displayField: 'text',
- /**
- * @cfg {String} valueField Name of the desired value field in the dataset (defaults to the
- * value of {@link #displayField}).
- */
- /**
- * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no
- * selection (defaults to true).
- */
- allowBlank: true,
- /**
- * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).
- */
- minSelections: 0,
- /**
- * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).
- */
- maxSelections: Number.MAX_VALUE,
- /**
- * @cfg {String} blankText Default text displayed when the control contains no items (defaults to 'This field is required')
- */
- blankText: 'This field is required',
- /**
- * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}
- * item(s) required'). The {0} token will be replaced by the value of {@link #minSelections}.
- */
- minSelectionsText: 'Minimum {0} item(s) required',
- /**
- * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}
- * item(s) allowed'). The {0} token will be replaced by the value of {@link #maxSelections}.
- */
- maxSelectionsText: 'Maximum {0} item(s) allowed',
- /**
- * @cfg {String} delimiter The string used to delimit the selected values when {@link #getSubmitValue submitting}
- * the field as part of a form. Defaults to ','. If you wish to have the selected values submitted as separate
- * parameters rather than a single delimited parameter, set this to <tt>null</tt>.
- */
- delimiter: ',',
- /**
- * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).
- * Acceptable values for this property are:
- * <div class="mdetail-params"><ul>
- * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
- * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
- * <div class="mdetail-params"><ul>
- * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
- * A 1-dimensional array will automatically be expanded (each array item will be the combo
- * {@link #valueField value} and {@link #displayField text})</div></li>
- * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
- * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
- * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
- * </div></li></ul></div></li></ul></div>
- */
- componentLayout: 'multiselectfield',
- fieldBodyCls: Ext.baseCSSPrefix + 'form-multiselect-body',
- // private
- initComponent: function(){
- var me = this;
- me.bindStore(me.store, true);
- if (me.store.autoCreated) {
- me.valueField = me.displayField = 'field1';
- if (!me.store.expanded) {
- me.displayField = 'field2';
- }
- }
- if (!Ext.isDefined(me.valueField)) {
- me.valueField = me.displayField;
- }
- me.callParent();
- },
- bindStore: function(store, initial) {
- var me = this,
- oldStore = me.store,
- boundList = me.boundList;
- if (oldStore && !initial && oldStore !== store && oldStore.autoDestroy) {
- oldStore.destroy();
- }
- me.store = store ? Ext.data.StoreManager.lookup(store) : null;
- if (boundList) {
- boundList.bindStore(store || null);
- }
- },
- // private
- onRender: function(ct, position) {
- var me = this,
- panel, boundList, selModel;
- me.callParent(arguments);
- boundList = me.boundList = Ext.create('Ext.view.BoundList', {
- multiSelect: true,
- store: me.store,
- displayField: me.displayField,
- border: false
- });
- selModel = boundList.getSelectionModel();
- me.mon(selModel, {
- selectionChange: me.onSelectionChange,
- scope: me
- });
- panel = me.panel = Ext.create('Ext.panel.Panel', {
- title: me.listTitle,
- tbar: me.tbar,
- items: [boundList],
- renderTo: me.bodyEl,
- layout: 'fit'
- });
- // Must set upward link after first render
- panel.ownerCt = me;
- // Set selection to current value
- me.setRawValue(me.rawValue);
- },
- // No content generated via template, it's all added components
- getSubTplMarkup: function() {
- return '';
- },
- // private
- afterRender: function() {
- var me = this;
- me.callParent();
- if (me.ddReorder && !me.dragGroup && !me.dropGroup){
- me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
- }
- if (me.draggable || me.dragGroup){
- me.dragZone = Ext.create('Ext.view.DragZone', {
- view: me.boundList,
- ddGroup: me.dragGroup,
- dragText: '{0} Item{1}'
- });
- }
- if (me.droppable || me.dropGroup){
- me.dropZone = Ext.create('Ext.view.DropZone', {
- view: me.boundList,
- ddGroup: me.dropGroup,
- handleNodeDrop: function(data, dropRecord, position) {
- var view = this.view,
- store = view.getStore(),
- records = data.records,
- index;
- // remove the Models from the source Store
- data.view.store.remove(records);
- index = store.indexOf(dropRecord);
- if (position === 'after') {
- index++;
- }
- store.insert(index, records);
- view.getSelectionModel().select(records);
- }
- });
- }
- },
- onSelectionChange: function() {
- this.checkChange();
- },
- /**
- * Clears any values currently selected.
- */
- clearValue: function() {
- this.setValue([]);
- },
- /**
- * Return the value(s) to be submitted for this field. The returned value depends on the {@link #delimiter}
- * config: If it is set to a String value (like the default ',') then this will return the selected values
- * joined by the delimiter. If it is set to <tt>null</tt> then the values will be returned as an Array.
- */
- getSubmitValue: function() {
- var me = this,
- delimiter = me.delimiter,
- val = me.getValue();
- return Ext.isString(delimiter) ? val.join(delimiter) : val;
- },
- // inherit docs
- getRawValue: function() {
- var me = this,
- boundList = me.boundList;
- if (boundList) {
- me.rawValue = Ext.Array.map(boundList.getSelectionModel().getSelection(), function(model) {
- return model.get(me.valueField);
- });
- }
- return me.rawValue;
- },
- // inherit docs
- setRawValue: function(value) {
- var me = this,
- boundList = me.boundList,
- models;
- value = Ext.Array.from(value);
- me.rawValue = value;
- if (boundList) {
- models = [];
- Ext.Array.forEach(value, function(val) {
- var undef,
- model = me.store.findRecord(me.valueField, val, undef, undef, true, true);
- if (model) {
- models.push(model);
- }
- });
- boundList.getSelectionModel().select(models, false, true);
- }
- return value;
- },
- // no conversion
- valueToRaw: function(value) {
- return value;
- },
- // compare array values
- isEqual: function(v1, v2) {
- var fromArray = Ext.Array.from,
- i, len;
- v1 = fromArray(v1);
- v2 = fromArray(v2);
- len = v1.length;
- if (len !== v2.length) {
- return false;
- }
- for(i = 0; i < len; i++) {
- if (v2[i] !== v1[i]) {
- return false;
- }
- }
- return true;
- },
- getErrors : function(value) {
- var me = this,
- format = Ext.String.format,
- errors = me.callParent(arguments),
- numSelected;
- value = Ext.Array.from(value || me.getValue());
- numSelected = value.length;
- if (!me.allowBlank && numSelected < 1) {
- errors.push(me.blankText);
- }
- if (numSelected < this.minSelections) {
- errors.push(format(me.minSelectionsText, me.minSelections));
- }
- if (numSelected > this.maxSelections) {
- errors.push(format(me.maxSelectionsText, me.maxSelections));
- }
- return errors;
- },
- onDisable: function() {
- this.callParent();
- this.disabled = true;
- this.updateReadOnly();
- },
- onEnable: function() {
- this.callParent();
- this.disabled = false;
- this.updateReadOnly();
- },
- setReadOnly: function(readOnly) {
- this.readOnly = readOnly;
- this.updateReadOnly();
- },
- /**
- * @private Lock or unlock the BoundList's selection model to match the current disabled/readonly state
- */
- updateReadOnly: function() {
- var me = this,
- boundList = me.boundList,
- readOnly = me.readOnly || me.disabled;
- if (boundList) {
- boundList.getSelectionModel().setLocked(readOnly);
- }
- },
- onDestroy: function(){
- Ext.destroyMembers(this, 'panel', 'boundList', 'dragZone', 'dropZone');
- this.callParent();
- }
- });
|