]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/selection/CheckboxModel.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / selection / CheckboxModel.js
CommitLineData
6527f429
DM
1/**\r
2 * A selection model that renders a column of checkboxes that can be toggled to\r
3 * select or deselect rows. The default mode for this selection model is MULTI.\r
4 *\r
5 * @example\r
6 * var store = Ext.create('Ext.data.Store', {\r
7 * fields: ['name', 'email', 'phone'],\r
8 * data: [{\r
9 * name: 'Lisa',\r
10 * email: 'lisa@simpsons.com',\r
11 * phone: '555-111-1224'\r
12 * }, {\r
13 * name: 'Bart',\r
14 * email: 'bart@simpsons.com',\r
15 * phone: '555-222-1234'\r
16 * }, {\r
17 * name: 'Homer',\r
18 * email: 'homer@simpsons.com',\r
19 * phone: '555-222-1244'\r
20 * }, {\r
21 * name: 'Marge',\r
22 * email: 'marge@simpsons.com',\r
23 * phone: '555-222-1254'\r
24 * }]\r
25 * });\r
26 *\r
27 * Ext.create('Ext.grid.Panel', {\r
28 * title: 'Simpsons',\r
29 * store: store,\r
30 * columns: [{\r
31 * text: 'Name',\r
32 * dataIndex: 'name'\r
33 * }, {\r
34 * text: 'Email',\r
35 * dataIndex: 'email',\r
36 * flex: 1\r
37 * }, {\r
38 * text: 'Phone',\r
39 * dataIndex: 'phone'\r
40 * }],\r
41 * height: 200,\r
42 * width: 400,\r
43 * renderTo: Ext.getBody(),\r
44 * selModel: {\r
45 * selType: 'checkboxmodel'\r
46 * }\r
47 * });\r
48 *\r
49 * The selection model will inject a header for the checkboxes in the first view\r
50 * and according to the {@link #injectCheckbox} configuration.\r
51 */\r
52Ext.define('Ext.selection.CheckboxModel', {\r
53 alias: 'selection.checkboxmodel',\r
54 extend: 'Ext.selection.RowModel',\r
55\r
56 /**\r
57 * @cfg {"SINGLE"/"SIMPLE"/"MULTI"} mode\r
58 * Modes of selection.\r
59 * Valid values are `"SINGLE"`, `"SIMPLE"`, and `"MULTI"`.\r
60 */\r
61 mode: 'MULTI',\r
62\r
63 /**\r
64 * @cfg {Number/String} [injectCheckbox=0]\r
65 * The index at which to insert the checkbox column.\r
66 * Supported values are a numeric index, and the strings 'first' and 'last'.\r
67 */\r
68 injectCheckbox: 0,\r
69\r
70 /**\r
71 * @cfg {Boolean} checkOnly\r
72 * True if rows can only be selected by clicking on the checkbox column, not by clicking\r
73 * on the row itself. Note that this only refers to selection via the UI, programmatic\r
74 * selection will still occur regardless.\r
75 */\r
76 checkOnly: false,\r
77 \r
78 /**\r
79 * @cfg {Boolean} showHeaderCheckbox\r
80 * Configure as `false` to not display the header checkbox at the top of the column.\r
81 * When the store is a {@link Ext.data.BufferedStore BufferedStore}, this configuration will\r
82 * not be available because the buffered data set does not always contain all data. \r
83 */\r
84 showHeaderCheckbox: undefined,\r
85 \r
86 /**\r
87 * @cfg {String} [checkSelector="x-grid-row-checker"]\r
88 * The selector for determining whether the checkbox element is clicked. This may be changed to\r
89 * allow for a wider area to be clicked, for example, the whole cell for the selector.\r
90 */\r
91 checkSelector: '.' + Ext.baseCSSPrefix + 'grid-row-checker',\r
92 \r
93 allowDeselect: true,\r
94\r
95 headerWidth: 24,\r
96\r
97 /**\r
98 * @private\r
99 */\r
100 checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',\r
101 \r
102 tdCls: Ext.baseCSSPrefix + 'grid-cell-special ' + Ext.baseCSSPrefix + 'grid-cell-row-checker',\r
103 \r
104\r
105 constructor: function() {\r
106 var me = this;\r
107 me.callParent(arguments);\r
108\r
109 // If mode is single and showHeaderCheck isn't explicity set to\r
110 // true, hide it.\r
111 if (me.mode === 'SINGLE') {\r
112 //<debug>\r
113 if (me.showHeaderCheckbox) {\r
114 Ext.Error.raise('The header checkbox is not supported for SINGLE mode selection models.');\r
115 }\r
116 //</debug>\r
117 me.showHeaderCheckbox = false;\r
118 }\r
119 },\r
120\r
121 beforeViewRender: function(view) {\r
122 var me = this,\r
123 owner;\r
124\r
125 me.callParent(arguments);\r
126\r
127 // if we have a locked header, only hook up to the first\r
128 if (!me.hasLockedHeader() || view.headerCt.lockedCt) {\r
129 me.addCheckbox(view, true);\r
130 owner = view.ownerCt;\r
131 // Listen to the outermost reconfigure event\r
132 if (view.headerCt.lockedCt) {\r
133 owner = owner.ownerCt;\r
134 }\r
135 me.mon(owner, 'reconfigure', me.onReconfigure, me);\r
136 }\r
137 },\r
138\r
139 bindComponent: function(view) {\r
140 this.sortable = false;\r
141 this.callParent(arguments);\r
142 },\r
143\r
144 hasLockedHeader: function(){\r
145 var views = this.views,\r
146 vLen = views.length,\r
147 v;\r
148\r
149 for (v = 0; v < vLen; v++) {\r
150 if (views[v].headerCt.lockedCt) {\r
151 return true;\r
152 }\r
153 }\r
154 return false;\r
155 },\r
156\r
157 /**\r
158 * Add the header checkbox to the header row\r
159 * @private\r
160 * @param {Boolean} initial True if we're binding for the first time.\r
161 */\r
162 addCheckbox: function(view, initial){\r
163 var me = this,\r
164 checkbox = me.injectCheckbox,\r
165 headerCt = view.headerCt;\r
166\r
167 // Preserve behaviour of false, but not clear why that would ever be done.\r
168 if (checkbox !== false) {\r
169 if (checkbox === 'first') {\r
170 checkbox = 0;\r
171 } else if (checkbox === 'last') {\r
172 checkbox = headerCt.getColumnCount();\r
173 }\r
174 Ext.suspendLayouts();\r
175 if (view.getStore().isBufferedStore) {\r
176 me.showHeaderCheckbox = false;\r
177 }\r
178 me.column = headerCt.add(checkbox, me.getHeaderConfig());\r
179 Ext.resumeLayouts();\r
180 }\r
181\r
182 if (initial !== true) {\r
183 view.refresh();\r
184 }\r
185 },\r
186\r
187 /**\r
188 * Handles the grid's reconfigure event. Adds the checkbox header if the columns have been reconfigured.\r
189 * @private\r
190 * @param {Ext.panel.Table} grid\r
191 * @param {Ext.data.Store} store\r
192 * @param {Object[]} columns\r
193 */\r
194 onReconfigure: function(grid, store, columns) {\r
195 if (columns) {\r
196 this.addCheckbox(this.views[0]);\r
197 }\r
198 },\r
199\r
200 /**\r
201 * Toggle the ui header between checked and unchecked state.\r
202 * @param {Boolean} isChecked\r
203 * @private\r
204 */\r
205 toggleUiHeader: function(isChecked) {\r
206 var view = this.views[0],\r
207 headerCt = view.headerCt,\r
208 checkHd = headerCt.child('gridcolumn[isCheckerHd]'),\r
209 cls = this.checkerOnCls;\r
210\r
211 if (checkHd) {\r
212 if (isChecked) {\r
213 checkHd.addCls(cls);\r
214 } else {\r
215 checkHd.removeCls(cls);\r
216 }\r
217 }\r
218 },\r
219\r
220 /**\r
221 * Toggle between selecting all and deselecting all when clicking on\r
222 * a checkbox header.\r
223 */\r
224 onHeaderClick: function(headerCt, header, e) {\r
225 var me = this,\r
226 isChecked;\r
227\r
228 if (header === me.column && me.mode !== 'SINGLE') {\r
229 e.stopEvent();\r
230 isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');\r
231\r
232 if (isChecked) {\r
233 me.deselectAll();\r
234 } else {\r
235 me.selectAll();\r
236 }\r
237 }\r
238 },\r
239\r
240 /**\r
241 * Retrieve a configuration to be used in a HeaderContainer.\r
242 * This should be used when injectCheckbox is set to false.\r
243 */\r
244 getHeaderConfig: function() {\r
245 var me = this,\r
246 showCheck = me.showHeaderCheckbox !== false;\r
247\r
248 return {\r
249 xtype: 'gridcolumn',\r
250 ignoreExport: true,\r
251 isCheckerHd: showCheck,\r
252 text : '&#160;',\r
253 clickTargetName: 'el',\r
254 width: me.headerWidth,\r
255 sortable: false,\r
256 draggable: false,\r
257 resizable: false,\r
258 hideable: false,\r
259 menuDisabled: true,\r
260 dataIndex: '',\r
261 tdCls: me.tdCls,\r
262 cls: showCheck ? Ext.baseCSSPrefix + 'column-header-checkbox ' : '',\r
263 defaultRenderer: me.renderer.bind(me),\r
264 editRenderer: me.editRenderer || me.renderEmpty,\r
265 locked: me.hasLockedHeader(),\r
266 processEvent: me.processColumnEvent\r
267 };\r
268 },\r
269\r
270 /**\r
271 * @private\r
272 * Process and refire events routed from the Ext.panel.Table's processEvent method.\r
273 * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.\r
274 * Returns the event handler's status to allow canceling of GridView's bubbling process.\r
275 */\r
276 processColumnEvent : function(type, view, cell, recordIndex, cellIndex, e, record, row) {\r
277 var navModel = view.getNavigationModel();\r
278\r
279 // Fire a navigate event upon SPACE in acvtionable mode.\r
280 // SPACE events are ignored by the NavModel in actionable mode.\r
281 if (e.type === 'keydown' && view.actionableMode && e.getKey() === e.SPACE) {\r
282 navModel.fireEvent('navigate', {\r
283 view: view,\r
284 navigationModel: navModel,\r
285 keyEvent: e,\r
286 position: e.position,\r
287 recordIndex: recordIndex,\r
288 record: record,\r
289 item: e.item,\r
290 cell: e.position.cellElement,\r
291 columnIndex: e.position.colIdx,\r
292 column: e.position.column\r
293 });\r
294 }\r
295 },\r
296\r
297 renderEmpty: function() {\r
298 return '&#160;';\r
299 },\r
300\r
301 // After refresh, ensure that the header checkbox state matches\r
302 refresh: function() {\r
303 this.callParent(arguments);\r
304 this.updateHeaderState();\r
305 },\r
306\r
307 /**\r
308 * Generates the HTML to be rendered in the injected checkbox column for each row.\r
309 * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.\r
310 * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.\r
311 */\r
312 renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {\r
313 return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker" role="button" tabIndex="0">&#160;</div>';\r
314 },\r
315 \r
316 selectByPosition: function (position, keepExisting) {\r
317 if (!position.isCellContext) {\r
318 position = new Ext.grid.CellContext(this.view).setPosition(position.row, position.column);\r
319 }\r
320\r
321 // Do not select if checkOnly, and the requested position is not the check column\r
322 if (!this.checkOnly || position.column === this.column) {\r
323 this.callParent([position, keepExisting]);\r
324 }\r
325 },\r
326\r
327 /**\r
328 * Synchronize header checker value as selection changes.\r
329 * @private\r
330 */\r
331 onSelectChange: function() {\r
332 this.callParent(arguments);\r
333 if (!this.suspendChange) {\r
334 this.updateHeaderState();\r
335 }\r
336 },\r
337\r
338 /**\r
339 * @private\r
340 */\r
341 onStoreLoad: function() {\r
342 this.callParent(arguments);\r
343 this.updateHeaderState();\r
344 },\r
345\r
346 onStoreAdd: function() {\r
347 this.callParent(arguments);\r
348 this.updateHeaderState();\r
349 },\r
350\r
351 onStoreRemove: function() {\r
352 this.callParent(arguments);\r
353 this.updateHeaderState();\r
354 },\r
355 \r
356 onStoreRefresh: function(){\r
357 this.callParent(arguments); \r
358 this.updateHeaderState();\r
359 },\r
360 \r
361 maybeFireSelectionChange: function(fireEvent) {\r
362 if (fireEvent && !this.suspendChange) {\r
363 this.updateHeaderState();\r
364 }\r
365 this.callParent(arguments);\r
366 },\r
367 \r
368 resumeChanges: function(){\r
369 this.callParent();\r
370 if (!this.suspendChange) {\r
371 this.updateHeaderState();\r
372 }\r
373 },\r
374\r
375 /**\r
376 * @private\r
377 */\r
378 updateHeaderState: function() {\r
379 // check to see if all records are selected\r
380 var me = this,\r
381 store = me.store,\r
382 storeCount = store.getCount(),\r
383 views = me.views,\r
384 hdSelectStatus = false,\r
385 selectedCount = 0,\r
386 selected, len, i;\r
387 \r
388 if (!store.isBufferedStore && storeCount > 0) {\r
389 selected = me.selected;\r
390 hdSelectStatus = true;\r
391 for (i = 0, len = selected.getCount(); i < len; ++i) {\r
392 if (store.indexOfId(selected.getAt(i).id) === -1) {\r
393 break;\r
394 }\r
395 ++selectedCount;\r
396 }\r
397 hdSelectStatus = storeCount === selectedCount;\r
398 }\r
399 \r
400 if (views && views.length) {\r
401 me.toggleUiHeader(hdSelectStatus);\r
402 }\r
403 },\r
404\r
405 vetoSelection: function(e) {\r
406 var me = this,\r
407 column = me.column,\r
408 veto, isClick, isSpace;\r
409\r
410 if (me.checkOnly) {\r
411 isClick = e.type === 'click' && e.getTarget(me.checkSelector);\r
412 isSpace = e.getKey() === e.SPACE && e.position.column === column;\r
413 veto = !(isClick || isSpace);\r
414 }\r
415 return veto || me.callParent([e]);\r
416 },\r
417\r
418 destroy: function() {\r
419 this.column = null;\r
420 this.callParent();\r
421 },\r
422\r
423 privates: {\r
424 onBeforeNavigate: function(metaEvent) {\r
425 var e = metaEvent.keyEvent;\r
426 if (this.selectionMode !== 'SINGLE') {\r
427 metaEvent.ctrlKey = metaEvent.ctrlKey || e.ctrlKey || (e.type === 'click' && !e.shiftKey) || e.getKey() === e.SPACE;\r
428 }\r
429 },\r
430\r
431 selectWithEventMulti: function(record, e, isSelected) {\r
432 var me = this;\r
433\r
434 if (!e.shiftKey && !e.ctrlKey && e.getTarget(me.checkSelector)) {\r
435 if (isSelected) {\r
436 me.doDeselect(record);\r
437 } else {\r
438 me.doSelect(record, true);\r
439 }\r
440 } else {\r
441 me.callParent([record, e, isSelected]);\r
442 }\r
443 }\r
444 }\r
445});\r