MultiSelect.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-ux-form-MultiSelect'>/**
  19. </span> * A control that allows selection of multiple items in a list
  20. */
  21. Ext.define('Ext.ux.form.MultiSelect', {
  22. extend: 'Ext.form.FieldContainer',
  23. mixins: {
  24. bindable: 'Ext.util.Bindable',
  25. field: 'Ext.form.field.Field'
  26. },
  27. alternateClassName: 'Ext.ux.Multiselect',
  28. alias: ['widget.multiselectfield', 'widget.multiselect'],
  29. requires: ['Ext.panel.Panel', 'Ext.view.BoundList', 'Ext.layout.container.Fit'],
  30. uses: ['Ext.view.DragZone', 'Ext.view.DropZone'],
  31. layout: 'fit',
  32. <span id='Ext-ux-form-MultiSelect-cfg-dragGroup'> /**
  33. </span> * @cfg {String} [dragGroup=&quot;&quot;] The ddgroup name for the MultiSelect DragZone.
  34. */
  35. <span id='Ext-ux-form-MultiSelect-cfg-dropGroup'> /**
  36. </span> * @cfg {String} [dropGroup=&quot;&quot;] The ddgroup name for the MultiSelect DropZone.
  37. */
  38. <span id='Ext-ux-form-MultiSelect-cfg-title'> /**
  39. </span> * @cfg {String} [title=&quot;&quot;] A title for the underlying panel.
  40. */
  41. <span id='Ext-ux-form-MultiSelect-cfg-ddReorder'> /**
  42. </span> * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop reorderable.
  43. */
  44. ddReorder: false,
  45. <span id='Ext-ux-form-MultiSelect-cfg-tbar'> /**
  46. </span> * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's selection list.
  47. * This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config, or an array of buttons/button configs
  48. * to be added to the toolbar. See {@link Ext.panel.Panel#tbar}.
  49. */
  50. <span id='Ext-ux-form-MultiSelect-cfg-appendOnly'> /**
  51. </span> * @cfg {String} [appendOnly=false] True if the list should only allow append drops when drag/drop is enabled.
  52. * This is useful for lists which are sorted.
  53. */
  54. appendOnly: false,
  55. <span id='Ext-ux-form-MultiSelect-cfg-displayField'> /**
  56. </span> * @cfg {String} [displayField=&quot;text&quot;] Name of the desired display field in the dataset.
  57. */
  58. displayField: 'text',
  59. <span id='Ext-ux-form-MultiSelect-cfg-valueField'> /**
  60. </span> * @cfg {String} [valueField=&quot;text&quot;] Name of the desired value field in the dataset.
  61. */
  62. <span id='Ext-ux-form-MultiSelect-cfg-allowBlank'> /**
  63. </span> * @cfg {Boolean} [allowBlank=true] False to require at least one item in the list to be selected, true to allow no
  64. * selection.
  65. */
  66. allowBlank: true,
  67. <span id='Ext-ux-form-MultiSelect-cfg-minSelections'> /**
  68. </span> * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
  69. */
  70. minSelections: 0,
  71. <span id='Ext-ux-form-MultiSelect-cfg-maxSelections'> /**
  72. </span> * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
  73. */
  74. maxSelections: Number.MAX_VALUE,
  75. <span id='Ext-ux-form-MultiSelect-cfg-blankText'> /**
  76. </span> * @cfg {String} [blankText=&quot;This field is required&quot;] Default text displayed when the control contains no items.
  77. */
  78. blankText: 'This field is required',
  79. <span id='Ext-ux-form-MultiSelect-cfg-minSelectionsText'> /**
  80. </span> * @cfg {String} [minSelectionsText=&quot;Minimum {0}item(s) required&quot;]
  81. * Validation message displayed when {@link #minSelections} is not met.
  82. * The {0} token will be replaced by the value of {@link #minSelections}.
  83. */
  84. minSelectionsText: 'Minimum {0} item(s) required',
  85. <span id='Ext-ux-form-MultiSelect-cfg-maxSelectionsText'> /**
  86. </span> * @cfg {String} [maxSelectionsText=&quot;Maximum {0}item(s) allowed&quot;]
  87. * Validation message displayed when {@link #maxSelections} is not met
  88. * The {0} token will be replaced by the value of {@link #maxSelections}.
  89. */
  90. maxSelectionsText: 'Minimum {0} item(s) required',
  91. <span id='Ext-ux-form-MultiSelect-cfg-delimiter'> /**
  92. </span> * @cfg {String} [delimiter=&quot;,&quot;] The string used to delimit the selected values when {@link #getSubmitValue submitting}
  93. * the field as part of a form. If you wish to have the selected values submitted as separate
  94. * parameters rather than a single delimited parameter, set this to &lt;tt&gt;null&lt;/tt&gt;.
  95. */
  96. delimiter: ',',
  97. <span id='Ext-ux-form-MultiSelect-cfg-store'> /**
  98. </span> * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to &lt;tt&gt;undefined&lt;/tt&gt;).
  99. * Acceptable values for this property are:
  100. * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
  101. * &lt;li&gt;&lt;b&gt;any {@link Ext.data.Store Store} subclass&lt;/b&gt;&lt;/li&gt;
  102. * &lt;li&gt;&lt;b&gt;an Array&lt;/b&gt; : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
  103. * &lt;div class=&quot;mdetail-params&quot;&gt;&lt;ul&gt;
  104. * &lt;li&gt;&lt;b&gt;1-dimensional array&lt;/b&gt; : (e.g., &lt;tt&gt;['Foo','Bar']&lt;/tt&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
  105. * A 1-dimensional array will automatically be expanded (each array item will be the combo
  106. * {@link #valueField value} and {@link #displayField text})&lt;/div&gt;&lt;/li&gt;
  107. * &lt;li&gt;&lt;b&gt;2-dimensional array&lt;/b&gt; : (e.g., &lt;tt&gt;[['f','Foo'],['b','Bar']]&lt;/tt&gt;)&lt;div class=&quot;sub-desc&quot;&gt;
  108. * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
  109. * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
  110. * &lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
  111. */
  112. ignoreSelectChange: 0,
  113. <span id='Ext-ux-form-MultiSelect-cfg-listConfig'> /**
  114. </span> * @cfg {Object} listConfig
  115. * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
  116. * Any configuration that is valid for BoundList can be included.
  117. */
  118. initComponent: function(){
  119. var me = this;
  120. me.bindStore(me.store, true);
  121. if (me.store.autoCreated) {
  122. me.valueField = me.displayField = 'field1';
  123. if (!me.store.expanded) {
  124. me.displayField = 'field2';
  125. }
  126. }
  127. if (!Ext.isDefined(me.valueField)) {
  128. me.valueField = me.displayField;
  129. }
  130. me.items = me.setupItems();
  131. me.callParent();
  132. me.initField();
  133. me.addEvents('drop');
  134. },
  135. setupItems: function() {
  136. var me = this;
  137. me.boundList = Ext.create('Ext.view.BoundList', Ext.apply({
  138. deferInitialRefresh: false,
  139. border: false,
  140. multiSelect: true,
  141. store: me.store,
  142. displayField: me.displayField,
  143. disabled: me.disabled
  144. }, me.listConfig));
  145. me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
  146. return {
  147. border: true,
  148. layout: 'fit',
  149. title: me.title,
  150. tbar: me.tbar,
  151. items: me.boundList
  152. };
  153. },
  154. onSelectChange: function(selModel, selections){
  155. if (!this.ignoreSelectChange) {
  156. this.setValue(selections);
  157. }
  158. },
  159. getSelected: function(){
  160. return this.boundList.getSelectionModel().getSelection();
  161. },
  162. // compare array values
  163. isEqual: function(v1, v2) {
  164. var fromArray = Ext.Array.from,
  165. i = 0,
  166. len;
  167. v1 = fromArray(v1);
  168. v2 = fromArray(v2);
  169. len = v1.length;
  170. if (len !== v2.length) {
  171. return false;
  172. }
  173. for(; i &lt; len; i++) {
  174. if (v2[i] !== v1[i]) {
  175. return false;
  176. }
  177. }
  178. return true;
  179. },
  180. afterRender: function(){
  181. var me = this;
  182. me.callParent();
  183. if (me.selectOnRender) {
  184. ++me.ignoreSelectChange;
  185. me.boundList.getSelectionModel().select(me.getRecordsForValue(me.value));
  186. --me.ignoreSelectChange;
  187. delete me.toSelect;
  188. }
  189. if (me.ddReorder &amp;&amp; !me.dragGroup &amp;&amp; !me.dropGroup){
  190. me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
  191. }
  192. if (me.draggable || me.dragGroup){
  193. me.dragZone = Ext.create('Ext.view.DragZone', {
  194. view: me.boundList,
  195. ddGroup: me.dragGroup,
  196. dragText: '{0} Item{1}'
  197. });
  198. }
  199. if (me.droppable || me.dropGroup){
  200. me.dropZone = Ext.create('Ext.view.DropZone', {
  201. view: me.boundList,
  202. ddGroup: me.dropGroup,
  203. handleNodeDrop: function(data, dropRecord, position) {
  204. var view = this.view,
  205. store = view.getStore(),
  206. records = data.records,
  207. index;
  208. // remove the Models from the source Store
  209. data.view.store.remove(records);
  210. index = store.indexOf(dropRecord);
  211. if (position === 'after') {
  212. index++;
  213. }
  214. store.insert(index, records);
  215. view.getSelectionModel().select(records);
  216. me.fireEvent('drop', me, records);
  217. }
  218. });
  219. }
  220. },
  221. isValid : function() {
  222. var me = this,
  223. disabled = me.disabled,
  224. validate = me.forceValidation || !disabled;
  225. return validate ? me.validateValue(me.value) : disabled;
  226. },
  227. validateValue: function(value) {
  228. var me = this,
  229. errors = me.getErrors(value),
  230. isValid = Ext.isEmpty(errors);
  231. if (!me.preventMark) {
  232. if (isValid) {
  233. me.clearInvalid();
  234. } else {
  235. me.markInvalid(errors);
  236. }
  237. }
  238. return isValid;
  239. },
  240. markInvalid : function(errors) {
  241. // Save the message and fire the 'invalid' event
  242. var me = this,
  243. oldMsg = me.getActiveError();
  244. me.setActiveErrors(Ext.Array.from(errors));
  245. if (oldMsg !== me.getActiveError()) {
  246. me.updateLayout();
  247. }
  248. },
  249. <span id='Ext-ux-form-MultiSelect-method-clearInvalid'> /**
  250. </span> * Clear any invalid styles/messages for this field.
  251. *
  252. * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
  253. * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
  254. * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  255. */
  256. clearInvalid : function() {
  257. // Clear the message and fire the 'valid' event
  258. var me = this,
  259. hadError = me.hasActiveError();
  260. me.unsetActiveError();
  261. if (hadError) {
  262. me.updateLayout();
  263. }
  264. },
  265. getSubmitData: function() {
  266. var me = this,
  267. data = null,
  268. val;
  269. if (!me.disabled &amp;&amp; me.submitValue &amp;&amp; !me.isFileUpload()) {
  270. val = me.getSubmitValue();
  271. if (val !== null) {
  272. data = {};
  273. data[me.getName()] = val;
  274. }
  275. }
  276. return data;
  277. },
  278. <span id='Ext-ux-form-MultiSelect-method-getSubmitValue'> /**
  279. </span> * Returns the value that would be included in a standard form submit for this field.
  280. *
  281. * @return {String} The value to be submitted, or null.
  282. */
  283. getSubmitValue: function() {
  284. var me = this,
  285. delimiter = me.delimiter,
  286. val = me.getValue();
  287. return Ext.isString(delimiter) ? val.join(delimiter) : val;
  288. },
  289. getValue: function(){
  290. return this.value;
  291. },
  292. getRecordsForValue: function(value){
  293. var me = this,
  294. records = [],
  295. all = me.store.getRange(),
  296. valueField = me.valueField,
  297. i = 0,
  298. allLen = all.length,
  299. rec,
  300. j,
  301. valueLen;
  302. for (valueLen = value.length; i &lt; valueLen; ++i) {
  303. for (j = 0; j &lt; allLen; ++j) {
  304. rec = all[j];
  305. if (rec.get(valueField) == value[i]) {
  306. records.push(rec);
  307. }
  308. }
  309. }
  310. return records;
  311. },
  312. setupValue: function(value){
  313. var delimiter = this.delimiter,
  314. valueField = this.valueField,
  315. i = 0,
  316. out,
  317. len,
  318. item;
  319. if (Ext.isDefined(value)) {
  320. if (delimiter &amp;&amp; Ext.isString(value)) {
  321. value = value.split(delimiter);
  322. } else if (!Ext.isArray(value)) {
  323. value = [value];
  324. }
  325. for (len = value.length; i &lt; len; ++i) {
  326. item = value[i];
  327. if (item &amp;&amp; item.isModel) {
  328. value[i] = item.get(valueField);
  329. }
  330. }
  331. out = Ext.Array.unique(value);
  332. } else {
  333. out = [];
  334. }
  335. return out;
  336. },
  337. setValue: function(value){
  338. var me = this,
  339. selModel = me.boundList.getSelectionModel();
  340. // Store not loaded yet - we cannot set the value
  341. if (!me.store.getCount()) {
  342. me.store.on({
  343. load: Ext.Function.bind(me.setValue, me, [value]),
  344. single: true
  345. });
  346. return;
  347. }
  348. value = me.setupValue(value);
  349. me.mixins.field.setValue.call(me, value);
  350. if (me.rendered) {
  351. ++me.ignoreSelectChange;
  352. selModel.deselectAll();
  353. selModel.select(me.getRecordsForValue(value));
  354. --me.ignoreSelectChange;
  355. } else {
  356. me.selectOnRender = true;
  357. }
  358. },
  359. clearValue: function(){
  360. this.setValue([]);
  361. },
  362. onEnable: function(){
  363. var list = this.boundList;
  364. this.callParent();
  365. if (list) {
  366. list.enable();
  367. }
  368. },
  369. onDisable: function(){
  370. var list = this.boundList;
  371. this.callParent();
  372. if (list) {
  373. list.disable();
  374. }
  375. },
  376. getErrors : function(value) {
  377. var me = this,
  378. format = Ext.String.format,
  379. errors = [],
  380. numSelected;
  381. value = Ext.Array.from(value || me.getValue());
  382. numSelected = value.length;
  383. if (!me.allowBlank &amp;&amp; numSelected &lt; 1) {
  384. errors.push(me.blankText);
  385. }
  386. if (numSelected &lt; me.minSelections) {
  387. errors.push(format(me.minSelectionsText, me.minSelections));
  388. }
  389. if (numSelected &gt; me.maxSelections) {
  390. errors.push(format(me.maxSelectionsText, me.maxSelections));
  391. }
  392. return errors;
  393. },
  394. onDestroy: function(){
  395. var me = this;
  396. me.bindStore(null);
  397. Ext.destroy(me.dragZone, me.dropZone);
  398. me.callParent();
  399. },
  400. onBindStore: function(store){
  401. var boundList = this.boundList;
  402. if (boundList) {
  403. boundList.bindStore(store);
  404. }
  405. }
  406. });
  407. </pre>
  408. </body>
  409. </html>