]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/src/view/MultiSelector.js
bump version to 7.0.0-4
[extjs.git] / extjs / classic / classic / src / view / MultiSelector.js
1 /**
2 * This component provides a grid holding selected items from a second store of potential
3 * members. The `store` of this component represents the selected items. The "search store"
4 * represents the potentially selected items.
5 *
6 * While this component is a grid and so you can configure `columns`, it is best to leave
7 * that to this class in its `initComponent` method. That allows this class to create the
8 * extra column that allows the user to remove rows. Instead use `{@link #fieldName}` and
9 * `{@link #fieldTitle}` to configure the primary column's `dataIndex` and column `text`,
10 * respectively.
11 *
12 * @since 5.0.0
13 */
14 Ext.define('Ext.view.MultiSelector', {
15 extend: 'Ext.grid.Panel',
16
17 xtype: 'multiselector',
18
19 config: {
20 /**
21 * @cfg {Object} search
22 * This object configures the search popup component. By default this contains the
23 * `xtype` for a `Ext.view.MultiSelectorSearch` component and specifies `autoLoad`
24 * for its `store`.
25 */
26 search: {
27 xtype: 'multiselector-search',
28 width: 200,
29 height: 200,
30 store: {
31 autoLoad: true
32 }
33 }
34 },
35
36 /**
37 * @cfg {String} [fieldName="name"]
38 * The name of the data field to display in the primary column of the grid.
39 * @since 5.0.0
40 */
41 fieldName: 'name',
42
43 /**
44 * @cfg {String} [fieldTitle]
45 * The text to display in the column header for the primary column of the grid.
46 * @since 5.0.0
47 */
48 fieldTitle: null,
49
50 /**
51 * @cfg {String} removeRowText
52 * The text to display in the "remove this row" column. By default this is a Unicode
53 * "X" looking glyph.
54 * @since 5.0.0
55 */
56 removeRowText: '\u2716',
57
58 /**
59 * @cfg {String} removeRowTip
60 * The tooltip to display when the user hovers over the remove cell.
61 * @since 5.0.0
62 */
63 removeRowTip: 'Remove this item',
64
65 emptyText: 'Nothing selected',
66
67 /**
68 * @cfg {String} addToolText
69 * The tooltip to display when the user hovers over the "+" tool in the panel header.
70 * @since 5.0.0
71 */
72 addToolText: 'Search for items to add',
73
74 initComponent: function() {
75 var me = this,
76 emptyText = me.emptyText,
77 store = me.getStore(),
78 search = me.getSearch(),
79 fieldTitle = me.fieldTitle,
80 searchStore, model;
81
82 //<debug>
83 if (!search) {
84 Ext.raise('The search configuration is required for the multi selector');
85 }
86 //</debug>
87
88 searchStore = search.store;
89
90 if (searchStore.isStore) {
91 model = searchStore.getModel();
92 }
93 else {
94 model = searchStore.model;
95 }
96
97 if (!store) {
98 me.store = {
99 model: model
100 };
101 }
102
103 if (emptyText && !me.viewConfig) {
104 me.viewConfig = {
105 deferEmptyText: false,
106 emptyText: emptyText
107 };
108 }
109
110 if (!me.columns) {
111 me.hideHeaders = !fieldTitle;
112 me.columns = [
113 { text: fieldTitle, dataIndex: me.fieldName, flex: 1 },
114 me.makeRemoveRowColumn()
115 ];
116 }
117
118 me.callParent();
119 },
120
121 addTools: function() {
122 var me = this;
123
124 me.addTool({
125 type: 'plus',
126 tooltip: me.addToolText,
127 callback: 'onShowSearch',
128 scope: me
129 });
130 me.searchTool = me.tools[me.tools.length - 1];
131 },
132
133 convertSearchRecord: Ext.identityFn,
134
135 convertSelectionRecord: Ext.identityFn,
136
137 makeRemoveRowColumn: function() {
138 var me = this;
139
140 return {
141 width: 32,
142 align: 'center',
143 menuDisabled: true,
144 tdCls: Ext.baseCSSPrefix + 'multiselector-remove',
145 processEvent: me.processRowEvent.bind(me),
146 renderer: me.renderRemoveRow,
147 updater: Ext.emptyFn,
148 scope: me
149 };
150 },
151
152 processRowEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {
153 var body = Ext.getBody();
154
155 if (e.type === 'click' ||
156 (e.type === 'keydown' && (e.keyCode === e.SPACE || e.keyCode === e.ENTER))) {
157 // Deleting the focused row will momentarily focusLeave
158 // That would dismiss the popup, so disable that.
159 body.suspendFocusEvents();
160 this.store.remove(record);
161 body.resumeFocusEvents();
162
163 if (this.searchPopup) {
164 this.searchPopup.deselectRecords(record);
165 }
166 }
167 },
168
169 renderRemoveRow: function() {
170 return '<span data-qtip="' + this.removeRowTip + '" role="button" tabIndex="0">' +
171 this.removeRowText + '</span>';
172 },
173
174 onFocusLeave: function(e) {
175 this.onDismissSearch();
176 this.callParent([e]);
177 },
178
179 afterComponentLayout: function(width, height, prevWidth, prevHeight) {
180 var me = this,
181 popup = me.searchPopup;
182
183 me.callParent([width, height, prevWidth, prevHeight]);
184
185 if (popup && popup.isVisible()) {
186 popup.showBy(me, me.popupAlign);
187 }
188 },
189
190 privates: {
191 popupAlign: 'tl-tr?',
192
193 onGlobalScroll: function(scroller) {
194 // Collapse if the scroll is anywhere but inside this selector or the popup
195 if (!this.owns(scroller.getElement())) {
196 this.onDismissSearch();
197 }
198 },
199
200 onDismissSearch: function(e) {
201 var searchPopup = this.searchPopup;
202
203 if (searchPopup &&
204 (!e || !(searchPopup.owns(e.getTarget()) || this.owns(e.getTarget())))) {
205 this.scrollListeners.destroy();
206 this.touchListeners.destroy();
207 searchPopup.hide();
208 }
209 },
210
211 onShowSearch: function(panel, tool, event) {
212 var me = this,
213 searchPopup = me.searchPopup,
214 store = me.getStore();
215
216 if (!searchPopup) {
217 searchPopup = Ext.merge({
218 owner: me,
219 field: me.fieldName,
220 floating: true,
221 alignOnScroll: false
222 }, me.getSearch());
223 me.searchPopup = searchPopup = me.add(searchPopup);
224
225 // If we were configured with records prior to the UI requesting the popup,
226 // ensure that the records are selected in the popup.
227 if (store.getCount()) {
228 searchPopup.selectRecords(store.getRange());
229 }
230 }
231
232 searchPopup.invocationEvent = event;
233 searchPopup.showBy(me, me.popupAlign);
234
235 // It only autofocuses its defaultFocus target if it was hidden.
236 // If they're reactivating the show tool, they'll expect to focus the search.
237 if (!event || event.pointerType !== 'touch') {
238 searchPopup.lookupReference('searchField').focus();
239 }
240
241 me.scrollListeners = Ext.on({
242 scroll: 'onGlobalScroll',
243 scope: me,
244 destroyable: true
245 });
246
247 // Dismiss on touch outside this component tree.
248 // Because touch platforms do not focus document.body on touch
249 // so no focusleave would occur to trigger a collapse.
250 me.touchListeners = Ext.getDoc().on({
251 // Do not translate on non-touch platforms.
252 // mousedown will blur the field.
253 translate: false,
254 touchstart: me.onDismissSearch,
255 scope: me,
256 delegated: false,
257 destroyable: true
258 });
259 }
260 }
261 });