ItemSelector.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. This file is part of Ext JS 4
  3. Copyright (c) 2011 Sencha Inc
  4. Contact: http://www.sencha.com/contact
  5. GNU General Public License Usage
  6. This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
  7. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
  8. */
  9. /*
  10. * Note that this control will most likely remain as an example, and not as a core Ext form
  11. * control. However, the API will be changing in a future release and so should not yet be
  12. * treated as a final, stable API at this time.
  13. */
  14. /**
  15. * @class Ext.ux.form.ItemSelector
  16. * @extends Ext.form.field.Base
  17. * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
  18. *
  19. * @history
  20. * 2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
  21. *
  22. * @constructor
  23. * Create a new ItemSelector
  24. * @param {Object} config Configuration options
  25. * @xtype itemselector
  26. */
  27. Ext.define('Ext.ux.form.ItemSelector', {
  28. extend: 'Ext.ux.form.MultiSelect',
  29. alias: ['widget.itemselectorfield', 'widget.itemselector'],
  30. alternateClassName: ['Ext.ux.ItemSelector'],
  31. requires: ['Ext.ux.layout.component.form.ItemSelector', 'Ext.button.Button'],
  32. hideNavIcons:false,
  33. /**
  34. * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
  35. * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
  36. * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
  37. * This can be overridden with a custom Array to change which buttons are displayed or their order.
  38. */
  39. buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
  40. buttonsText: {
  41. top: "Move to Top",
  42. up: "Move Up",
  43. add: "Add to Selected",
  44. remove: "Remove from Selected",
  45. down: "Move Down",
  46. bottom: "Move to Bottom"
  47. },
  48. /**
  49. * @cfg {Array} multiselects An optional array of {@link Ext.ux.form.MultiSelect} config objects, containing
  50. * additional configuration to be applied to the internal MultiSelect fields.
  51. */
  52. multiselects: [],
  53. componentLayout: 'itemselectorfield',
  54. fieldBodyCls: Ext.baseCSSPrefix + 'form-itemselector-body',
  55. bindStore: function(store, initial) {
  56. var me = this,
  57. toField = me.toField,
  58. fromField = me.fromField,
  59. models;
  60. me.callParent(arguments);
  61. if (toField) {
  62. // Clear both field stores
  63. toField.store.removeAll();
  64. fromField.store.removeAll();
  65. // Clone the contents of the main store into the fromField
  66. models = [];
  67. me.store.each(function(model) {
  68. models.push(model.copy(model.getId()));
  69. });
  70. fromField.store.add(models);
  71. }
  72. },
  73. onRender: function(ct, position) {
  74. var me = this,
  75. baseCSSPrefix = Ext.baseCSSPrefix,
  76. ddGroup = 'ItemSelectorDD-' + Ext.id(),
  77. commonConfig = {
  78. displayField: me.displayField,
  79. valueField: me.valueField,
  80. dragGroup: ddGroup,
  81. dropGroup: ddGroup,
  82. flex: 1,
  83. hideLabel: true,
  84. disabled: me.disabled
  85. },
  86. fromConfig = Ext.apply({
  87. listTitle: 'Available',
  88. store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
  89. listeners: {
  90. boundList: {
  91. itemdblclick: me.onItemDblClick,
  92. scope: me
  93. }
  94. }
  95. }, me.multiselects[0], commonConfig),
  96. toConfig = Ext.apply({
  97. listTitle: 'Selected',
  98. store: Ext.create('Ext.data.Store', {model: me.store.model}), //blank store to begin
  99. listeners: {
  100. boundList: {
  101. itemdblclick: me.onItemDblClick,
  102. scope: me
  103. },
  104. change: me.onToFieldChange,
  105. scope: me
  106. }
  107. }, me.multiselects[1], commonConfig),
  108. fromField = Ext.widget('multiselect', fromConfig),
  109. toField = Ext.widget('multiselect', toConfig),
  110. innerCt,
  111. buttons = [];
  112. // Skip MultiSelect's onRender as we don't want its content
  113. Ext.ux.form.MultiSelect.superclass.onRender.call(me, ct, position);
  114. me.fromField = fromField;
  115. me.toField = toField;
  116. if (!me.hideNavIcons) {
  117. Ext.Array.forEach(me.buttons, function(name) {
  118. buttons.push({
  119. xtype: 'button',
  120. tooltip: me.buttonsText[name],
  121. handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
  122. cls: baseCSSPrefix + 'form-itemselector-btn',
  123. iconCls: baseCSSPrefix + 'form-itemselector-' + name,
  124. scope: me
  125. });
  126. //div separator to force vertical stacking
  127. buttons.push({xtype: 'component', height: 3, width: 1, style: 'font-size:0;line-height:0'});
  128. });
  129. }
  130. innerCt = me.innerCt = Ext.widget('container', {
  131. renderTo: me.bodyEl,
  132. layout: {
  133. type: 'hbox',
  134. align: 'middle'
  135. },
  136. items: [
  137. me.fromField,
  138. {
  139. xtype: 'container',
  140. margins: '0 4',
  141. items: buttons
  142. },
  143. me.toField
  144. ]
  145. });
  146. // Must set upward link after first render
  147. innerCt.ownerCt = me;
  148. // Rebind the store so it gets cloned to the fromField
  149. me.bindStore(me.store);
  150. // Set the initial value
  151. me.setRawValue(me.rawValue);
  152. },
  153. onToFieldChange: function() {
  154. this.checkChange();
  155. },
  156. getSelections: function(list){
  157. var store = list.getStore(),
  158. selections = list.getSelectionModel().getSelection(),
  159. i = 0,
  160. len = selections.length;
  161. return Ext.Array.sort(selections, function(a, b){
  162. a = store.indexOf(a);
  163. b = store.indexOf(b);
  164. if (a < b) {
  165. return -1;
  166. } else if (a > b) {
  167. return 1;
  168. }
  169. return 0;
  170. });
  171. },
  172. onTopBtnClick : function() {
  173. var list = this.toField.boundList,
  174. store = list.getStore(),
  175. selected = this.getSelections(list),
  176. i = selected.length - 1,
  177. selection;
  178. store.suspendEvents();
  179. for (; i > -1; --i) {
  180. selection = selected[i];
  181. store.remove(selected);
  182. store.insert(0, selected);
  183. }
  184. store.resumeEvents();
  185. list.refresh();
  186. },
  187. onBottomBtnClick : function() {
  188. var list = this.toField.boundList,
  189. store = list.getStore(),
  190. selected = this.getSelections(list),
  191. i = 0,
  192. len = selected.length,
  193. selection;
  194. store.suspendEvents();
  195. for (; i < len; ++i) {
  196. selection = selected[i];
  197. store.remove(selection);
  198. store.add(selection);
  199. }
  200. store.resumeEvents();
  201. list.refresh();
  202. },
  203. onUpBtnClick : function() {
  204. var list = this.toField.boundList,
  205. store = list.getStore(),
  206. selected = this.getSelections(list),
  207. i = 0,
  208. len = selected.length,
  209. selection,
  210. index;
  211. store.suspendEvents();
  212. for (; i < len; ++i) {
  213. selection = selected[i];
  214. index = Math.max(0, store.indexOf(selection) - 1);
  215. store.remove(selection);
  216. store.insert(index, selection);
  217. }
  218. store.resumeEvents();
  219. list.refresh();
  220. },
  221. onDownBtnClick : function() {
  222. var list = this.toField.boundList,
  223. store = list.getStore(),
  224. selected = this.getSelections(list),
  225. i = 0,
  226. len = selected.length,
  227. max = store.getCount(),
  228. selection,
  229. index;
  230. store.suspendEvents();
  231. for (; i < len; ++i) {
  232. selection = selected[i];
  233. index = Math.min(max, store.indexOf(selection) + 1);
  234. store.remove(selection);
  235. store.insert(index, selection);
  236. }
  237. store.resumeEvents();
  238. list.refresh();
  239. },
  240. onAddBtnClick : function() {
  241. var me = this,
  242. fromList = me.fromField.boundList,
  243. selected = this.getSelections(fromList);
  244. fromList.getStore().remove(selected);
  245. this.toField.boundList.getStore().add(selected);
  246. },
  247. onRemoveBtnClick : function() {
  248. var me = this,
  249. toList = me.toField.boundList,
  250. selected = this.getSelections(toList);
  251. toList.getStore().remove(selected);
  252. this.fromField.boundList.getStore().add(selected);
  253. },
  254. onItemDblClick : function(view) {
  255. var me = this;
  256. if (view == me.toField.boundList){
  257. me.onRemoveBtnClick();
  258. }
  259. else if (view == me.fromField.boundList) {
  260. me.onAddBtnClick();
  261. }
  262. },
  263. setRawValue: function(value) {
  264. var me = this,
  265. Array = Ext.Array,
  266. toStore, fromStore, models;
  267. value = Array.from(value);
  268. me.rawValue = value;
  269. if (me.toField) {
  270. toStore = me.toField.boundList.getStore();
  271. fromStore = me.fromField.boundList.getStore();
  272. // Move any selected values back to the fromField
  273. fromStore.add(toStore.getRange());
  274. toStore.removeAll();
  275. // Move the new values over to the toField
  276. models = [];
  277. Ext.Array.forEach(value, function(val) {
  278. var undef,
  279. model = fromStore.findRecord(me.valueField, val, undef, undef, true, true);
  280. if (model) {
  281. models.push(model);
  282. }
  283. });
  284. fromStore.remove(models);
  285. toStore.add(models);
  286. }
  287. return value;
  288. },
  289. getRawValue: function() {
  290. var me = this,
  291. toField = me.toField,
  292. rawValue = me.rawValue;
  293. if (toField) {
  294. rawValue = Ext.Array.map(toField.boundList.getStore().getRange(), function(model) {
  295. return model.get(me.valueField);
  296. });
  297. }
  298. me.rawValue = rawValue;
  299. return rawValue;
  300. },
  301. /**
  302. * @private Cascade readOnly/disabled state to the sub-fields and buttons
  303. */
  304. updateReadOnly: function() {
  305. var me = this,
  306. readOnly = me.readOnly || me.disabled;
  307. if (me.rendered) {
  308. me.toField.setReadOnly(readOnly);
  309. me.fromField.setReadOnly(readOnly);
  310. Ext.Array.forEach(me.innerCt.query('button'), function(button) {
  311. button.setDisabled(readOnly);
  312. });
  313. }
  314. },
  315. onDisable: function(){
  316. this.callParent();
  317. var fromField = this.fromField;
  318. // if we have one, we have both, they get created at the same time
  319. if (fromField) {
  320. fromField.disable();
  321. this.toField.disable();
  322. }
  323. },
  324. onEnable: function(){
  325. this.callParent();
  326. var fromField = this.fromField;
  327. // if we have one, we have both, they get created at the same time
  328. if (fromField) {
  329. fromField.enable();
  330. this.toField.enable();
  331. }
  332. },
  333. onDestroy: function() {
  334. Ext.destroyMembers(this, 'innerCt');
  335. this.callParent();
  336. }
  337. });