Project

General

Profile

Download (12.3 KB) Statistics
| Branch: | Revision:
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
});
(1-1/14)