1
|
define([
|
2
|
"dojo/_base/declare",
|
3
|
"dojo/_base/array",
|
4
|
"dojo/_base/lang",
|
5
|
"dojo/_base/connect",
|
6
|
"dojo/cache",
|
7
|
"dojo/string",
|
8
|
"dojo/query",
|
9
|
"dojox/grid/DataGrid",
|
10
|
"dojox/lang/functional",
|
11
|
"dojox/lang/functional/fold",
|
12
|
"dijit/Dialog",
|
13
|
"dijit/form/CheckBox",
|
14
|
"dijit/form/TextBox",
|
15
|
"dijit/form/Button",
|
16
|
"steam2/user",
|
17
|
"steam2/statusColorMap"
|
18
|
], function(declare, arrayUtil, lang, connect, cache, string, query, DataGrid, funcUtil, _fold, Dialog, CheckBox, TextBox, Button, user, statusColorMap){
|
19
|
|
20
|
var ActionGrid = declare('steam2.ActionGrid', DataGrid, {
|
21
|
|
22
|
store: null,
|
23
|
model: null,
|
24
|
|
25
|
queryOptions: {
|
26
|
jsonQuery: true
|
27
|
},
|
28
|
|
29
|
gridId: null,
|
30
|
|
31
|
widgetsInTemplate: true,
|
32
|
|
33
|
// NOTE: watch out changing this to extended
|
34
|
// since additional row clicks does not trigger onSelected!?!
|
35
|
selectionMode: 'multiple',
|
36
|
|
37
|
templateString: dojo.cache('steam2','resources/_Grid.html'),
|
38
|
|
39
|
searchPlaceholder: 'Type to search',
|
40
|
|
41
|
constructor: function(args){
|
42
|
// enable reflective lookup of grids...
|
43
|
ActionGrid.grids.push(this);
|
44
|
this.gridId = ActionGrid.gridId;
|
45
|
ActionGrid.gridId++;
|
46
|
|
47
|
// on every fetch update the bulk info.
|
48
|
connect.connect(this, '_onFetchComplete', this, function(){
|
49
|
this.setBulkInfo();
|
50
|
});
|
51
|
|
52
|
},
|
53
|
|
54
|
_onRevert: function(){
|
55
|
this.selection.deselectAll();
|
56
|
this._selectAllCheckBox.checked = false;
|
57
|
|
58
|
// NOTE: when the running filter is selected
|
59
|
// dojo tries to fetch those items by issuing a request at
|
60
|
// servers.cgi/[?status~'running'][:25]
|
61
|
// and that is not supported by the backend.
|
62
|
|
63
|
// we remove the query, thereby fetching all items
|
64
|
// and filtering them client side afterwards.
|
65
|
|
66
|
// NOTE: the client side filtering is because the store
|
67
|
// uses ClientFilter and caching.
|
68
|
this.query = "";
|
69
|
var h = connect.connect(this, "_onFetchComplete", this, function(){
|
70
|
connect.disconnect(h);
|
71
|
this.filter(this._toJsonQuery(), true);
|
72
|
});
|
73
|
this.inherited(arguments);
|
74
|
},
|
75
|
|
76
|
|
77
|
_getHeaderHeight: function(){
|
78
|
// Overwritten method that adds the height of the search node.
|
79
|
/* remember doesn't include the margins */
|
80
|
var hh = this.inherited(arguments);
|
81
|
var hs = this.searchNode.offsetHeight;
|
82
|
return hh + hs + /*one magic missing pixel*/1;
|
83
|
},
|
84
|
|
85
|
renderTooltip: function(){
|
86
|
var q = dojo.query('.irigo-tooltip', this.domNode);
|
87
|
if(q.irigoTooltip){q.irigoTooltip();}
|
88
|
},
|
89
|
|
90
|
render: function(){
|
91
|
var searchQueryInput = new TextBox({
|
92
|
placeholder: this.searchPlaceholder,
|
93
|
intermediateChanges:true,
|
94
|
onInput: function(e){
|
95
|
// NOTE: for some fu... reason spaces doesn't go through
|
96
|
// when text box is inside the grid.
|
97
|
// we set it ourself
|
98
|
if(e.keyCode === 32){
|
99
|
this.set('value', this.get('value') + ' ');
|
100
|
}
|
101
|
}
|
102
|
}, this.searchQueryInputNode);
|
103
|
connect.connect(searchQueryInput, "onChange", this, this._onSearchQueryChange);
|
104
|
|
105
|
|
106
|
|
107
|
// NOTE: the grid steals back the focus _onFetchComplete, see. FocusManager
|
108
|
// this is triggered by a click on a header in the grid (sorting).
|
109
|
this.focus._delayedHeaderFocus = function(){
|
110
|
// summary: overwriting the focus on the headers since that removes
|
111
|
// focus from the query input.
|
112
|
};
|
113
|
|
114
|
this.inherited(arguments);
|
115
|
},
|
116
|
|
117
|
postrender: function(){
|
118
|
this.inherited(arguments);
|
119
|
|
120
|
this._selectAllCheckBox = query('.gridSelectAllCheckbox', this.viewsHeaderNode)[0];
|
121
|
connect.connect(this._selectAllCheckBox, "onchange", this, this._onSelectAllChange);
|
122
|
|
123
|
this.views.views[0].onAfterRow = lang.hitch(this, this.onAfterRow);
|
124
|
this.renderTooltip();
|
125
|
},
|
126
|
|
127
|
onAfterRow: function(rowIdx, cells, rowNode){
|
128
|
// summary:
|
129
|
// render header tooltip when header is re-rendered
|
130
|
// that happens when there are no items to show in the grid after filter
|
131
|
if(rowIdx === -1){
|
132
|
// maintain the help icon
|
133
|
this.renderTooltip();
|
134
|
|
135
|
// maintain the select all checkbox in the
|
136
|
// re-created checkbox
|
137
|
var checked = this._selectAllCheckBox.checked;
|
138
|
this._selectAllCheckBox = query('.gridSelectAllCheckbox', this.viewsHeaderNode)[0];
|
139
|
this._selectAllCheckBox.checked = checked;
|
140
|
connect.connect(this._selectAllCheckBox, "onchange", this, this._onSelectAllChange);
|
141
|
}
|
142
|
},
|
143
|
|
144
|
getBulkActions: function(items){
|
145
|
// FIXME: move helpers somewhere else!
|
146
|
function intersect(o1,o2){
|
147
|
var intersected = {}, k;
|
148
|
for(k in o2){
|
149
|
if(o1[k]){
|
150
|
intersected[k] = k;
|
151
|
}
|
152
|
}
|
153
|
return intersected;
|
154
|
}
|
155
|
|
156
|
function set(array){
|
157
|
var s = {};
|
158
|
for(var i = 0; i < array.length; i++){
|
159
|
s[array[i]] = true;
|
160
|
}
|
161
|
return s;
|
162
|
}
|
163
|
|
164
|
var action_sets = [];
|
165
|
|
166
|
// get actions for each item
|
167
|
arrayUtil.forEach(items, function(item){
|
168
|
var actions = item.getActions();
|
169
|
// actions may just be a string... namely the loader
|
170
|
if(lang.isArray(actions)){
|
171
|
action_sets.push(set(actions));
|
172
|
}
|
173
|
else{
|
174
|
action_sets.push(actions);
|
175
|
}
|
176
|
});
|
177
|
|
178
|
var intersected = funcUtil.reduce(action_sets, intersect);
|
179
|
return intersected;
|
180
|
},
|
181
|
|
182
|
getBulkActionButtons: function(items){
|
183
|
|
184
|
var self = this;
|
185
|
var selected = items;
|
186
|
var selectedCount = items.length;
|
187
|
if(selectedCount === 0){
|
188
|
return '';
|
189
|
}
|
190
|
else if(selectedCount == 1){
|
191
|
var item = selected[0];
|
192
|
return item.getActionButtons();
|
193
|
}
|
194
|
// Intersect the actions
|
195
|
else{
|
196
|
var intersected = this.getBulkActions(items);
|
197
|
var action_buttons = [];
|
198
|
for(var action in intersected){
|
199
|
if(action === 'loading'){
|
200
|
return '';
|
201
|
}
|
202
|
var t = '<button type="button" class="action_button ${action}_icon" onclick="${onClickAction}"><span>${action}</span></button>';
|
203
|
var args = {
|
204
|
onClickAction: string.substitute(
|
205
|
"steam2.ActionGrid.bulkActionHandler('${0}', '${1}');return false;",
|
206
|
[
|
207
|
action,
|
208
|
this.gridId
|
209
|
]),
|
210
|
action: action
|
211
|
};
|
212
|
action_buttons.push(string.substitute(t, args));
|
213
|
}
|
214
|
return action_buttons.join('');
|
215
|
}
|
216
|
|
217
|
},
|
218
|
|
219
|
getSelectedItems: function(){
|
220
|
// NOTE: some items in the selection are null after filtering!?!
|
221
|
return arrayUtil.filter(this.selection.getSelected(), function(item){
|
222
|
return item === null ? false : true;
|
223
|
});
|
224
|
},
|
225
|
|
226
|
filter: function(query, reRender){
|
227
|
if(this._selectAllCheckBox.checked){
|
228
|
this._onFetchCompleteSelectAll();
|
229
|
}
|
230
|
this.inherited(arguments);
|
231
|
},
|
232
|
|
233
|
setBulkInfo: function(eventType){
|
234
|
var selectedItems = this.getSelectedItems();
|
235
|
var selectedCount = selectedItems.length;
|
236
|
|
237
|
if(selectedCount === 0){
|
238
|
this.bulkOperationsNode.style.display = 'none';
|
239
|
return;
|
240
|
}
|
241
|
|
242
|
var actionButtons = this.getBulkActionButtons(selectedItems);
|
243
|
var text = "";
|
244
|
|
245
|
if(!actionButtons){
|
246
|
actionButtons = "<em>No actions in common</em>";
|
247
|
}
|
248
|
else{
|
249
|
text = "Available bulk operations:";
|
250
|
}
|
251
|
|
252
|
var t = string.substitute(
|
253
|
'${0} Selected. ${1} ${2}',
|
254
|
[selectedCount, text, actionButtons]
|
255
|
);
|
256
|
|
257
|
this.bulkOperationsNode.style.display = '';
|
258
|
this.bulkOperationsNode.innerHTML = t;
|
259
|
},
|
260
|
|
261
|
onSelected: function(inIndex){
|
262
|
this.updateRow(inIndex);
|
263
|
this.setBulkInfo('select');
|
264
|
},
|
265
|
|
266
|
onDeselected: function(inIndex){
|
267
|
this._selectAllCheckBox.checked = false;
|
268
|
this.updateRow(inIndex);
|
269
|
this.setBulkInfo('deselect');
|
270
|
},
|
271
|
|
272
|
onStyleRow: function(row){
|
273
|
this.inherited(arguments);
|
274
|
var item = this.getItem(row.index);
|
275
|
if(item){
|
276
|
var status = item.status;
|
277
|
var color = statusColorMap.get(status);
|
278
|
// the old style is cached there for some reason
|
279
|
// clear it
|
280
|
row.node._style = '';
|
281
|
row.customStyles = 'cursor:pointer;color:' + color + ';';
|
282
|
}
|
283
|
},
|
284
|
|
285
|
_onSet: function(item, attribute, oldValue, newValue){
|
286
|
this.inherited(arguments);
|
287
|
// update the bulk icons when items are updated.
|
288
|
if(attribute == 'status'){
|
289
|
this.setBulkInfo();
|
290
|
}
|
291
|
},
|
292
|
|
293
|
setQuery: function(query){
|
294
|
if(!query){
|
295
|
delete this.queries['name'];
|
296
|
delete this.queries['status'];
|
297
|
}
|
298
|
else{
|
299
|
this.queries['name'] = {prop: 'name', value:query, type:'or'};
|
300
|
this.queries['status'] = {prop: 'status', value:query, type:'or'};
|
301
|
}
|
302
|
},
|
303
|
|
304
|
_onSearchQueryChangeTimeout: function(query){
|
305
|
// if a request is pending the following requests are ignored by the DataGrid
|
306
|
// wait for finish and execute...
|
307
|
if(this._pending_requests[0]){
|
308
|
var h = connect.connect(this, '_onFetchComplete', this, function(){
|
309
|
this._onSearchQueryChangeTimeout();
|
310
|
connect.disconnect(h);
|
311
|
});
|
312
|
return;
|
313
|
}
|
314
|
this.setQuery(query);
|
315
|
this.filter(this._toJsonQuery(), true);
|
316
|
},
|
317
|
|
318
|
_onSearchQueryChange: function(query){
|
319
|
// summary:
|
320
|
// handle search query input.
|
321
|
// we delay executing the queries on sequential inputs.
|
322
|
if(this.__searchQueryTimeout){
|
323
|
window.clearTimeout(this.__searchQueryTimeout);
|
324
|
}
|
325
|
var _onSearchQueryChangeTimout = lang.hitch(this, this._onSearchQueryChangeTimeout, query);
|
326
|
this.__searchQueryTimeout = window.setTimeout(_onSearchQueryChangeTimout, 200);
|
327
|
},
|
328
|
|
329
|
_clearSelection: function(){
|
330
|
var self = this;
|
331
|
arrayUtil.forEach(this.selection.selected, function(item, idx){
|
332
|
// We are not using selection.clear();
|
333
|
// since that triggers setBulkInfo on each item.
|
334
|
delete self.selection.selected[idx];
|
335
|
});
|
336
|
},
|
337
|
|
338
|
selectAll: function(){
|
339
|
this.selection.selectRange(0, this._by_idx.length-1);
|
340
|
},
|
341
|
|
342
|
_onFetchCompleteSelectAll: function(){
|
343
|
this._clearSelection();
|
344
|
// onFetchCompleteSelectAll
|
345
|
var h = dojo.connect(this, '_onFetchComplete', this, function(){
|
346
|
this.selectAll();
|
347
|
dojo.disconnect(h);
|
348
|
});
|
349
|
},
|
350
|
|
351
|
_onSelectAllChange: function(event){
|
352
|
// summary:
|
353
|
// Handle clicks on the 'select all' checkbox
|
354
|
// event: DOMEvent
|
355
|
this._clearSelection();
|
356
|
this.filter(this._toJsonQuery());
|
357
|
},
|
358
|
|
359
|
queries: {
|
360
|
},
|
361
|
|
362
|
_toJsonQuery: function(){
|
363
|
// query += '[?status~"' + this.statusQuery + '"]';
|
364
|
|
365
|
function match(query){
|
366
|
// case insensitive match
|
367
|
return query.prop + "~\"*" + query.value + "*\"";
|
368
|
}
|
369
|
var orQueries = [];
|
370
|
var andQueries = [];
|
371
|
var query = '';
|
372
|
for(var key in this.queries){
|
373
|
var q = this.queries[key];
|
374
|
if(q.type === "or"){
|
375
|
orQueries.push(match(q));
|
376
|
}
|
377
|
else if(q.type === "and"){
|
378
|
andQueries.push(match(q));
|
379
|
}
|
380
|
}
|
381
|
if(orQueries.length > 0){
|
382
|
query = '[?' + orQueries.join('|') + ']';
|
383
|
}
|
384
|
arrayUtil.forEach(andQueries, function(q){
|
385
|
query += '[?' + q + ']';
|
386
|
});
|
387
|
return query;
|
388
|
}
|
389
|
});
|
390
|
|
391
|
|
392
|
//
|
393
|
// 'static' stuff
|
394
|
//
|
395
|
|
396
|
ActionGrid.gridId = 0;
|
397
|
ActionGrid.grids = [];
|
398
|
|
399
|
ActionGrid.bulkActionHandler = function(action, gridId){
|
400
|
var grid = ActionGrid.grids[gridId];
|
401
|
var store = grid.store;
|
402
|
var selected = grid.selection.getSelected();
|
403
|
|
404
|
grid.model.save(selected, action);
|
405
|
};
|
406
|
|
407
|
return ActionGrid;
|
408
|
|
409
|
});
|