123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457 |
- /**
- * A control that allows selection of multiple items in a list
- */
- Ext.define('Ext.ux.form.MultiSelect', {
-
- extend: 'Ext.form.FieldContainer',
-
- mixins: {
- bindable: 'Ext.util.Bindable',
- field: 'Ext.form.field.Field'
- },
-
- alternateClassName: 'Ext.ux.Multiselect',
- alias: ['widget.multiselectfield', 'widget.multiselect'],
-
- requires: ['Ext.panel.Panel', 'Ext.view.BoundList'],
-
- uses: ['Ext.view.DragZone', 'Ext.view.DropZone'],
-
- /**
- * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
- */
- /**
- * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
- */
-
- /**
- * @cfg {String} [title=""] A title for the underlying panel.
- */
-
- /**
- * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop reorderable.
- */
- 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=false] True if the list should only allow append drops when drag/drop is enabled.
- * This is useful for lists which are sorted.
- */
- appendOnly: false,
- /**
- * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
- */
- displayField: 'text',
- /**
- * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
- */
- /**
- * @cfg {Boolean} [allowBlank=true] False to require at least one item in the list to be selected, true to allow no
- * selection.
- */
- allowBlank: true,
- /**
- * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
- */
- minSelections: 0,
- /**
- * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
- */
- maxSelections: Number.MAX_VALUE,
- /**
- * @cfg {String} [blankText="This field is required"] Default text displayed when the control contains no items.
- */
- blankText: 'This field is required',
- /**
- * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"]
- * Validation message displayed when {@link #minSelections} is not met.
- * The {0} token will be replaced by the value of {@link #minSelections}.
- */
- minSelectionsText: 'Minimum {0} item(s) required',
-
- /**
- * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"]
- * Validation message displayed when {@link #maxSelections} is not met
- * The {0} token will be replaced by the value of {@link #maxSelections}.
- */
- maxSelectionsText: 'Minimum {0} item(s) required',
- /**
- * @cfg {String} [delimiter=","] The string used to delimit the selected values when {@link #getSubmitValue submitting}
- * the field as part of a form. 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>
- */
-
- ignoreSelectChange: 0,
-
- 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;
- }
- Ext.apply(me, me.setupItems());
-
-
- me.callParent();
- me.initField();
- me.addEvents('drop');
- },
-
- setupItems: function() {
- var me = this;
-
- me.boundList = Ext.create('Ext.view.BoundList', {
- deferInitialRefresh: false,
- multiSelect: true,
- store: me.store,
- displayField: me.displayField,
- disabled: me.disabled
- });
-
- me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
- return {
- layout: 'fit',
- title: me.title,
- tbar: me.tbar,
- items: me.boundList
- };
- },
-
- onSelectChange: function(selModel, selections){
- if (!this.ignoreSelectChange) {
- this.setValue(selections);
- }
- },
-
- getSelected: function(){
- return this.boundList.getSelectionModel().getSelection();
- },
-
- // compare array values
- isEqual: function(v1, v2) {
- var fromArray = Ext.Array.from,
- i = 0,
- len;
- v1 = fromArray(v1);
- v2 = fromArray(v2);
- len = v1.length;
- if (len !== v2.length) {
- return false;
- }
- for(; i < len; i++) {
- if (v2[i] !== v1[i]) {
- return false;
- }
- }
- return true;
- },
-
- afterRender: function(){
- var me = this;
-
- me.callParent();
- if (me.selectOnRender) {
- ++me.ignoreSelectChange;
- me.boundList.getSelectionModel().select(me.getRecordsForValue(me.value));
- --me.ignoreSelectChange;
- delete me.toSelect;
- }
-
- 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);
- me.fireEvent('drop', me, records);
- }
- });
- }
- },
-
- isValid : function() {
- var me = this,
- disabled = me.disabled,
- validate = me.forceValidation || !disabled;
-
-
- return validate ? me.validateValue(me.value) : disabled;
- },
-
- validateValue: function(value) {
- var me = this,
- errors = me.getErrors(value),
- isValid = Ext.isEmpty(errors);
-
- if (!me.preventMark) {
- if (isValid) {
- me.clearInvalid();
- } else {
- me.markInvalid(errors);
- }
- }
- return isValid;
- },
-
- markInvalid : function(errors) {
- // Save the message and fire the 'invalid' event
- var me = this,
- oldMsg = me.getActiveError();
- me.setActiveErrors(Ext.Array.from(errors));
- if (oldMsg !== me.getActiveError()) {
- me.updateLayout();
- }
- },
- /**
- * Clear any invalid styles/messages for this field.
- *
- * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
- * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
- * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
- */
- clearInvalid : function() {
- // Clear the message and fire the 'valid' event
- var me = this,
- hadError = me.hasActiveError();
- me.unsetActiveError();
- if (hadError) {
- me.updateLayout();
- }
- },
-
- getSubmitData: function() {
- var me = this,
- data = null,
- val;
- if (!me.disabled && me.submitValue && !me.isFileUpload()) {
- val = me.getSubmitValue();
- if (val !== null) {
- data = {};
- data[me.getName()] = val;
- }
- }
- return data;
- },
- /**
- * Returns the value that would be included in a standard form submit for this field.
- *
- * @return {String} The value to be submitted, or null.
- */
- getSubmitValue: function() {
- var me = this,
- delimiter = me.delimiter,
- val = me.getValue();
-
- return Ext.isString(delimiter) ? val.join(delimiter) : val;
- },
-
- getValue: function(){
- return this.value;
- },
-
- getRecordsForValue: function(value){
- var me = this,
- records = [],
- all = me.store.getRange(),
- valueField = me.valueField,
- i = 0,
- allLen = all.length,
- rec,
- j,
- valueLen;
-
- for (valueLen = value.length; i < valueLen; ++i) {
- for (j = 0; j < allLen; ++j) {
- rec = all[j];
- if (rec.get(valueField) == value[i]) {
- records.push(rec);
- }
- }
- }
-
- return records;
- },
-
- setupValue: function(value){
- var delimiter = this.delimiter,
- valueField = this.valueField,
- i = 0,
- out,
- len,
- item;
-
- if (Ext.isDefined(value)) {
- if (delimiter && Ext.isString(value)) {
- value = value.split(delimiter);
- } else if (!Ext.isArray(value)) {
- value = [value];
- }
-
- for (len = value.length; i < len; ++i) {
- item = value[i];
- if (item && item.isModel) {
- value[i] = item.get(valueField);
- }
- }
- out = Ext.Array.unique(value);
- } else {
- out = [];
- }
- return out;
- },
-
- setValue: function(value){
- var me = this,
- selModel = me.boundList.getSelectionModel();
- // Store not loaded yet - we cannot set the value
- if (!me.store.getCount()) {
- me.store.on({
- load: Ext.Function.bind(me.setValue, me, [value]),
- single: true
- });
- return;
- }
- value = me.setupValue(value);
- me.mixins.field.setValue.call(me, value);
-
- if (me.rendered) {
- ++me.ignoreSelectChange;
- selModel.deselectAll();
- selModel.select(me.getRecordsForValue(value));
- --me.ignoreSelectChange;
- } else {
- me.selectOnRender = true;
- }
- },
-
- clearValue: function(){
- this.setValue([]);
- },
-
- onEnable: function(){
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.enable();
- }
- },
-
- onDisable: function(){
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.disable();
- }
- },
-
- getErrors : function(value) {
- var me = this,
- format = Ext.String.format,
- errors = [],
- numSelected;
- value = Ext.Array.from(value || me.getValue());
- numSelected = value.length;
- if (!me.allowBlank && numSelected < 1) {
- errors.push(me.blankText);
- }
- if (numSelected < me.minSelections) {
- errors.push(format(me.minSelectionsText, me.minSelections));
- }
- if (numSelected > me.maxSelections) {
- errors.push(format(me.maxSelectionsText, me.maxSelections));
- }
- return errors;
- },
-
- onDestroy: function(){
- var me = this;
-
- me.bindStore(null);
- Ext.destroy(me.dragZone, me.dropZone);
- me.callParent();
- },
-
- onBindStore: function(store){
- var boundList = this.boundList;
-
- if (boundList) {
- boundList.bindStore(store);
- }
- }
-
- });
|