ItemSelector.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  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. * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
  8. */
  9. Ext.define('Ext.ux.form.ItemSelector', {
  10. extend: 'Ext.ux.form.MultiSelect',
  11. alias: ['widget.itemselectorfield', 'widget.itemselector'],
  12. alternateClassName: ['Ext.ux.ItemSelector'],
  13. requires: [
  14. 'Ext.button.Button',
  15. 'Ext.ux.form.MultiSelect'
  16. ],
  17. /**
  18. * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
  19. */
  20. hideNavIcons:false,
  21. /**
  22. * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
  23. * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
  24. * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
  25. * This can be overridden with a custom Array to change which buttons are displayed or their order.
  26. */
  27. buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],
  28. /**
  29. * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
  30. * Labels for buttons.
  31. */
  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. initComponent: function() {
  41. var me = this;
  42. me.ddGroup = me.id + '-dd';
  43. me.callParent();
  44. // bindStore must be called after the fromField has been created because
  45. // it copies records from our configured Store into the fromField's Store
  46. me.bindStore(me.store);
  47. },
  48. createList: function(){
  49. var me = this;
  50. return Ext.create('Ext.ux.form.MultiSelect', {
  51. submitValue: false,
  52. flex: 1,
  53. dragGroup: me.ddGroup,
  54. dropGroup: me.ddGroup,
  55. store: {
  56. model: me.store.model,
  57. data: []
  58. },
  59. displayField: me.displayField,
  60. disabled: me.disabled,
  61. listeners: {
  62. boundList: {
  63. scope: me,
  64. itemdblclick: me.onItemDblClick,
  65. drop: me.syncValue
  66. }
  67. }
  68. });
  69. },
  70. setupItems: function() {
  71. var me = this;
  72. me.fromField = me.createList();
  73. me.toField = me.createList();
  74. return {
  75. layout: {
  76. type: 'hbox',
  77. align: 'stretch'
  78. },
  79. items: [
  80. me.fromField,
  81. {
  82. xtype: 'container',
  83. margins: '0 4',
  84. width: 22,
  85. layout: {
  86. type: 'vbox',
  87. pack: 'center'
  88. },
  89. items: me.createButtons()
  90. },
  91. me.toField
  92. ]
  93. };
  94. },
  95. createButtons: function(){
  96. var me = this,
  97. buttons = [];
  98. if (!me.hideNavIcons) {
  99. Ext.Array.forEach(me.buttons, function(name) {
  100. buttons.push({
  101. xtype: 'button',
  102. tooltip: me.buttonsText[name],
  103. handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
  104. cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
  105. iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
  106. navBtn: true,
  107. scope: me,
  108. margin: '4 0 0 0'
  109. });
  110. });
  111. }
  112. return buttons;
  113. },
  114. getSelections: function(list){
  115. var store = list.getStore(),
  116. selections = list.getSelectionModel().getSelection();
  117. return Ext.Array.sort(selections, function(a, b){
  118. a = store.indexOf(a);
  119. b = store.indexOf(b);
  120. if (a < b) {
  121. return -1;
  122. } else if (a > b) {
  123. return 1;
  124. }
  125. return 0;
  126. });
  127. },
  128. onTopBtnClick : function() {
  129. var list = this.toField.boundList,
  130. store = list.getStore(),
  131. selected = this.getSelections(list);
  132. store.suspendEvents();
  133. store.remove(selected, true);
  134. store.insert(0, selected);
  135. store.resumeEvents();
  136. list.refresh();
  137. this.syncValue();
  138. list.getSelectionModel().select(selected);
  139. },
  140. onBottomBtnClick : function() {
  141. var list = this.toField.boundList,
  142. store = list.getStore(),
  143. selected = this.getSelections(list);
  144. store.suspendEvents();
  145. store.remove(selected, true);
  146. store.add(selected);
  147. store.resumeEvents();
  148. list.refresh();
  149. this.syncValue();
  150. list.getSelectionModel().select(selected);
  151. },
  152. onUpBtnClick : function() {
  153. var list = this.toField.boundList,
  154. store = list.getStore(),
  155. selected = this.getSelections(list),
  156. i = 0,
  157. len = selected.length,
  158. index = store.getCount();
  159. // Find index of first selection
  160. for (; i < len; ++i) {
  161. index = Math.min(index, store.indexOf(selected[i]));
  162. }
  163. // If first selection is not at the top, move the whole lot up
  164. if (index > 0) {
  165. store.suspendEvents();
  166. store.remove(selected, true);
  167. store.insert(index - 1, selected);
  168. store.resumeEvents();
  169. list.refresh();
  170. this.syncValue();
  171. list.getSelectionModel().select(selected);
  172. }
  173. },
  174. onDownBtnClick : function() {
  175. var list = this.toField.boundList,
  176. store = list.getStore(),
  177. selected = this.getSelections(list),
  178. i = 0,
  179. len = selected.length,
  180. index = 0;
  181. // Find index of last selection
  182. for (; i < len; ++i) {
  183. index = Math.max(index, store.indexOf(selected[i]));
  184. }
  185. // If last selection is not at the bottom, move the whole lot down
  186. if (index < store.getCount() - 1) {
  187. store.suspendEvents();
  188. store.remove(selected, true);
  189. store.insert(index + 2 - len, selected);
  190. store.resumeEvents();
  191. list.refresh();
  192. this.syncValue();
  193. list.getSelectionModel().select(selected);
  194. }
  195. },
  196. onAddBtnClick : function() {
  197. var me = this,
  198. fromList = me.fromField.boundList,
  199. selected = this.getSelections(fromList);
  200. fromList.getStore().remove(selected);
  201. this.toField.boundList.getStore().add(selected);
  202. this.syncValue();
  203. },
  204. onRemoveBtnClick : function() {
  205. var me = this,
  206. toList = me.toField.boundList,
  207. selected = this.getSelections(toList);
  208. toList.getStore().remove(selected);
  209. this.fromField.boundList.getStore().add(selected);
  210. this.syncValue();
  211. },
  212. syncValue: function() {
  213. this.setValue(this.toField.store.getRange());
  214. },
  215. onItemDblClick: function(view, rec){
  216. var me = this,
  217. from = me.fromField.store,
  218. to = me.toField.store,
  219. current,
  220. destination;
  221. if (view === me.fromField.boundList) {
  222. current = from;
  223. destination = to;
  224. } else {
  225. current = to;
  226. destination = from;
  227. }
  228. current.remove(rec);
  229. destination.add(rec);
  230. me.syncValue();
  231. },
  232. setValue: function(value){
  233. var me = this,
  234. fromStore = me.fromField.store,
  235. toStore = me.toField.store,
  236. selected;
  237. // Wait for from store to be loaded
  238. if (!me.fromField.store.getCount()) {
  239. me.fromField.store.on({
  240. load: Ext.Function.bind(me.setValue, me, [value]),
  241. single: true
  242. });
  243. return;
  244. }
  245. value = me.setupValue(value);
  246. me.mixins.field.setValue.call(me, value);
  247. selected = me.getRecordsForValue(value);
  248. Ext.Array.forEach(toStore.getRange(), function(rec){
  249. if (!Ext.Array.contains(selected, rec)) {
  250. // not in the selected group, remove it from the toStore
  251. toStore.remove(rec);
  252. fromStore.add(rec);
  253. }
  254. });
  255. toStore.removeAll();
  256. Ext.Array.forEach(selected, function(rec){
  257. // In the from store, move it over
  258. if (fromStore.indexOf(rec) > -1) {
  259. fromStore.remove(rec);
  260. }
  261. toStore.add(rec);
  262. });
  263. },
  264. onBindStore: function(store, initial) {
  265. var me = this;
  266. if (me.fromField) {
  267. me.fromField.store.removeAll()
  268. me.toField.store.removeAll();
  269. // Add everything to the from field as soon as the Store is loaded
  270. if (store.getCount()) {
  271. me.populateFromStore(store);
  272. } else {
  273. me.store.on('load', me.populateFromStore, me);
  274. }
  275. }
  276. },
  277. populateFromStore: function(store) {
  278. this.fromField.store.add(store.getRange());
  279. // setValue wait for the from Store to be loaded
  280. this.fromField.store.fireEvent('load', this.fromField.store);
  281. },
  282. onEnable: function(){
  283. var me = this;
  284. me.callParent();
  285. me.fromField.enable();
  286. me.toField.enable();
  287. Ext.Array.forEach(me.query('[navBtn]'), function(btn){
  288. btn.enable();
  289. });
  290. },
  291. onDisable: function(){
  292. var me = this;
  293. me.callParent();
  294. me.fromField.disable();
  295. me.toField.disable();
  296. Ext.Array.forEach(me.query('[navBtn]'), function(btn){
  297. btn.disable();
  298. });
  299. },
  300. onDestroy: function(){
  301. this.bindStore(null);
  302. this.callParent();
  303. }
  304. });