missing comma. tsbere++
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / widget / GridColumnPicker.js
1 /* ---------------------------------------------------------------------------
2  * Copyright (C) 2008  Georgia Public Library Service
3  * Bill Erickson <erickson@esilibrary.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * ---------------------------------------------------------------------------
15  */
16
17
18 if(!dojo._hasResource["openils.widget.GridColumnPicker"]) {
19     dojo.provide('openils.widget.GridColumnPicker');
20
21     dojo.require('dijit.Dialog');
22     dojo.require('dijit.form.Button');
23     dojo.require('dijit.form.NumberSpinner');
24     dojo.require('openils.User');
25     dojo.require('openils.Event');
26     dojo.require('openils.Util');
27     dojo.require('fieldmapper.Fieldmapper');
28
29
30     dojo.declare('openils.widget.GridColumnPicker', null, {
31
32         USER_PERSIST_SETTING : 'ui.grid_columns',
33
34         constructor : function (authtoken, persistKey, grid, structure) {
35             var _this = this;
36             this.grid = grid;
37             this.persistKey = this.USER_PERSIST_SETTING+'.'+persistKey;
38             this.authtoken = authtoken || openils.User.authtoken;
39             this.structure = structure || this.grid.structure;
40             this.cells = this.structure[0].cells[0].slice();
41
42             this.dialog = this.buildDialog();
43             this.dialogTable = this.dialog.containerNode.getElementsByTagName('tbody')[0];
44
45             // replace: called after any sort changes
46             this.onSortChange = function(list) {console.log('onSortChange()')}
47             // replace:  called after user settings are first retrieved
48             this.onLoad = function(opts) {console.log('onLoad()')};
49
50             // internal onload handler
51             this.loaded = false;
52             this._onLoad = function(opts) {_this.loaded = true; _this.onLoad(opts)};
53
54             this.grid.onHeaderContextMenu = function(e) { 
55                 _this.build();
56                 _this.dialog.show(); 
57                 dojo.stopEvent(e);
58             };
59         },
60
61         // determine the visible sorting from the 
62         // view and update our list of cells to match
63         refreshCells : function() {
64             var cells = this.cells;
65             this.cells = [];
66             var _this = this;
67
68             dojo.forEach(
69                  _this.grid.views.views[0].structure.cells[0],
70                  function(vCell) {
71                     for (var i = 0; i < cells.length; i++) {
72                         if (cells[i].field == vCell.field) {
73                             cells[i]._visible = true;
74                             _this.cells.push(cells[i]);
75                             break;
76                         }
77                     }
78                 }
79             );
80
81             // Depending on how the grid structure is built, there may be
82             // cells in the structure that are not yet in the view.  Push
83             // any remaining cells onto the end.
84             dojo.forEach(
85                 cells,
86                 function(cell) {
87                     existing = _this.cells.filter(function(s){return s.field == cell.field})[0]
88                     if (!existing) {
89                         cell._visible = false;
90                         _this.cells.push(cell);
91                     }
92                 }
93             );
94         },
95
96         buildDialog : function() {
97             var self = this;
98             
99             // TODO i18n
100
101             var dialog = new dijit.Dialog({title : 'Column Picker'});
102             var table = dojo.create('table', {'class':'oils-generic-table', innerHTML : 
103                 "<table><thead><tr><th width='30%'>Column</th><th width='23%'>Display</th>" +
104                 "<th width='23%'>Auto Width</th><th width='23%'>Sort Priority</th></tr></thead>" +
105                 "<tbody />"});
106
107             var tDiv = dojo.create('div', {style : 'height:400px; overflow-y:auto;'});
108             tDiv.appendChild(table);
109
110             var bDiv = dojo.create('div', {style : 'text-align:right; width:100%;',
111                 innerHTML : "<span name='cancel_button'></span><span name='save_button'></span>"});
112
113             var textDiv = dojo.create('div', {style : 'padding:5px; margin-top:5px; border-top:1px solid #333', 
114                 innerHTML :
115                     "<i>A Sort Priority of '0' means no sorting is applied.<br/>" +
116                     "<i>Apply a negative Sort Priority for descending sort."});
117             
118             var wrapper = dojo.create('div');
119             wrapper.appendChild(tDiv);
120             wrapper.appendChild(textDiv);
121             wrapper.appendChild(bDiv);
122             dialog.containerNode.appendChild(wrapper);
123
124             var button = new dijit.form.Button({label:'Save'}, 
125                 dojo.query('[name=save_button]', bDiv)[0]);
126             button.onClick = function() { dialog.hide(); self.update(true); };
127
128             button = new dijit.form.Button({label:'Cancel'}, 
129                 dojo.query('[name=cancel_button]', bDiv)[0]);
130             button.onClick = function() { dialog.hide(); };
131
132             return dialog;
133         },
134
135         // builds the column-picker dialog table
136         build : function() {
137             this.refreshCells();
138             var rows = dojo.query('tr', this.dialogTable);
139
140             for(var i = 0; i < rows.length; i++) {
141                 if(rows[i].getAttribute('picker'))
142                     this.dialogTable.removeChild(rows[i]);
143             }
144
145             rows = dojo.query('tr', this.dialogTable);
146             var lastChild = null;
147             if(rows.length > 0)
148                 lastChild = rows[rows.length-1];
149
150             for(var i = 0; i < this.cells.length; i++) {
151                 // setting table.innerHTML breaks stuff, so do it the hard way
152                 var cell = this.cells[i];
153                 tr = document.createElement('tr');
154                 tr.setAttribute('picker', 'picker');
155                 td0 = document.createElement('td');
156                 td1 = document.createElement('td');
157                 td2 = document.createElement('td');
158                 td3 = document.createElement('td');
159
160                 ipt = document.createElement('input');
161                 ipt.setAttribute('type', 'checkbox');
162                 ipt.setAttribute('name', 'selector');
163
164                 ipt2 = document.createElement('input');
165                 ipt2.setAttribute('type', 'checkbox');
166                 ipt2.setAttribute('name', 'width');
167
168                 ipt3 = document.createElement('div');
169
170                 if (cell.nonSelectable) {
171                     ipt.setAttribute('checked', 'checked');
172                     ipt.setAttribute('disabled', true);
173                     ipt2.setAttribute('disabled', true);
174
175                 } else {
176                     if (cell._visible) {
177                         ipt.setAttribute('checked', 'checked');
178                         if (cell.width == 'auto') 
179                             ipt2.setAttribute('checked', 'checked');
180                     } else {
181                         ipt.removeAttribute('checked');
182                     }
183                 }
184
185                 if (cell.field == '+selector') {
186                     // pick up the unescaped unicode checkmark char
187                     td0.innerHTML = cell.name;
188                 } else {
189                     td0.appendChild(document.createTextNode(cell.name));
190                 }
191                 td1.appendChild(ipt);
192                 td2.appendChild(ipt2);
193                 td3.appendChild(ipt3);
194                 tr.appendChild(td0);
195                 tr.appendChild(td1);
196                 tr.appendChild(td2);
197                 tr.appendChild(td3);
198
199                 if(lastChild)
200                     this.dialogTable.insertBefore(tr, lastChild);
201                 else
202                     this.dialogTable.appendChild(tr);
203
204                 if ( this.grid.canSort(i+1) ) { // column index is 1-based
205
206                     // must be added after its parent node is inserted into the DOM.
207                     var ns = new dijit.form.NumberSpinner(
208                         {   constraints : {places : 0}, 
209                             value : cell._sort || 0,
210                             style : 'width:4em',
211                             name : 'sort',
212                         }, ipt3
213                     );
214                 }
215             }
216         },
217
218         // update the grid based on the items selected in the picker dialog
219         update : function(persist) {
220             var rows = dojo.query('[picker=picker]', this.dialogTable);
221             var _this = this;
222             var displayCells = [];
223             var sortUpdated = false;
224
225             for (var i = 0; i < rows.length; i++) {
226                 var row = rows[i];
227                 var selector = dojo.query('[name=selector]', row)[0];
228                 var width = dojo.query('[name=width]', row)[0];
229                 var sort = dojo.query('[name=sort]', row)[0];
230                 var cell = this.cells[i]; // index should match dialog
231
232                 if (sort && cell._sort != sort.value) {
233                     sortUpdated = true;
234                     cell._sort = sort.value;
235                 }
236
237                 if (selector.checked) {
238                     cell._visible = true;
239                     if (width.checked) {
240                         cell.width = 'auto';
241                     } else if(cell.width == 'auto') {
242                         delete cell.width;
243                     }
244                     displayCells.push(cell);
245
246                 } else {
247                     cell._visible = false;
248                     delete cell.width;
249                 }
250             }
251
252             if (sortUpdated && this.onSortChange) 
253                 this.onSortChange(this.buildSortList());
254
255             this.structure[0].cells[0] = displayCells;
256             this.grid.setStructure(this.structure);
257             this.grid.update();
258
259             if (persist) this.persist(true);
260         },
261
262         // extract cells that have sorting applied, order lowest to highest
263         buildSortList : function() {
264             var sortList = this.cells.filter(
265                 function(cella) { return Number(cella._sort) }
266             ).sort( 
267                 function(a, b) { 
268                     if (Math.abs(a._sort) < Math.abs(b._sort)) return -1; 
269                     return 1; 
270                 }
271             );
272
273             return sortList.map(function(f){
274                 var dir = f._sort < 0 ? 'desc' : 'asc';
275                 return {field : f.field, direction : dir};
276             });
277         },
278
279         // save me as a user setting
280         persist : function(noRefresh) {
281             var list = [];
282             var autos = [];
283             if (!noRefresh) this.refreshCells();
284
285             for(var i = 0; i < this.cells.length; i++) {
286                 var cell = this.cells[i];
287                 if (cell._visible) {
288                     list.push(cell.field);
289                     if(cell.width == 'auto')
290                         autos.push(cell.field);
291                 } 
292             }
293
294             var setting = {};
295             setting[this.persistKey] = {
296                 'columns' : list, 
297                 'auto' : autos,
298                 'sort' : this.buildSortList().map(function(f){return f.field})
299             };
300
301             var _this = this;
302             fieldmapper.standardRequest(
303                 ['open-ils.actor', 'open-ils.actor.patron.settings.update'],
304                 {   async: true,
305                     params: [this.authtoken, null, setting],
306                     oncomplete: function(r) {
307                         var stat = openils.Util.readResponse(r);
308                     },
309                     onmethoderror : function() {},
310                     onerror : function() { 
311                         console.log("No user setting '" + _this.persistKey + "' configured.  Cannot persist") 
312                     }
313                 }
314             );
315         }, 
316
317         loadColsFromSetting : function(setting) {
318             var _this = this;
319             this.setting = setting;
320             var displayCells = [];
321             
322             // new component, existing settings may not have this
323             if (!setting.sort) setting.sort = [];
324
325             dojo.forEach(setting.columns,
326                 function(col) {
327                     var cell = _this.cells.filter(function(c){return c.field == col})[0];
328                     if (cell) {
329                         cell._visible = true;
330                         displayCells.push(cell);
331
332                         if(setting.auto.indexOf(cell.field) > -1) {
333                             cell.width = 'auto';
334                         } else {
335                             if(cell.width == 'auto')
336                                 delete cell.width;
337                         }
338                         cell._sort = setting.sort.indexOf(cell.field) + 1;
339
340                     } else {
341                         console.log('Unknown setting column '+col+'.  Ignoring...');
342                     }
343                 }
344             );
345             
346             // any cells not in the setting must be marked as non-visible
347             dojo.forEach(this.cells, function(cell) { 
348                 if (setting.columns.indexOf(cell.field) == -1) {
349                     cell._visible = false;
350                     cell._sort = 0;
351                 }
352             });
353
354             this.structure[0].cells[0] = displayCells;
355             this.grid.setStructure(this.structure);
356             this.grid.update();
357         },
358
359         load : function() {
360             var _this = this;
361
362             // if load is called before the user has logged in,
363             // queue the loading up for after authentication.
364             if (!this.authtoken) {
365                 var _this = this;
366                 openils.Util.addOnLoad(function() {
367                     _this.authtoken = openils.User.authtoken;
368                     _this.load();
369                 }); 
370                 return;
371             }
372
373             if(this.setting) {
374                 this.loadColsFromSetting(this.setting);
375                 this._onLoad({sortFields : this.buildSortList()});
376                 return;
377             }
378
379             fieldmapper.standardRequest(
380                 ['open-ils.actor', 'open-ils.actor.patron.settings.retrieve'],
381                 {   async: true,
382                     params: [this.authtoken, null, this.persistKey],
383                     oncomplete: function(r) {
384                         var set = openils.Util.readResponse(r);
385                         if(set) {
386                             _this.loadColsFromSetting(set);
387                         } else {
388                             _this.grid.setStructure(_this.structure);
389                             _this.grid.update();
390                         }
391                         _this._onLoad({sortFields : _this.buildSortList()});
392                     }
393                 }
394             );
395         },
396     });
397 }
398