ItemSelector.js 11 KB

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