4fce0b7a7fc509250a2b1601bd6e8898cbf9aa94
[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     dojo.requireLocalization('openils.widget', 'AutoFieldWidget');
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.nls = dojo.i18n.getLocalization('openils.widget', 'AutoFieldWidget');
37             this.grid = grid;
38             this.persistKey = this.USER_PERSIST_SETTING+'.'+persistKey;
39             this.authtoken = authtoken || openils.User.authtoken;
40             this.structure = structure || this.grid.structure;
41             this.cells = this.structure[0].cells[0].slice();
42
43             this.dialog = this.buildDialog();
44             this.dialogTable = this.dialog.containerNode.getElementsByTagName('tbody')[0];
45
46
47             // replace: called after any sort changes
48             this.onSortChange = function(list) {console.log('onSortChange()')}
49             // replace:  called after user settings are first retrieved
50             this.onLoad = function(opts) {console.log('onLoad()')};
51
52             // internal onload handler
53             this.loaded = false;
54             this._onLoad = function(opts) {_this.loaded = true; _this.onLoad(opts)};
55
56             this.grid.onHeaderContextMenu = function(e) { 
57                 _this.build();
58                 _this.dialog.show(); 
59                 dojo.stopEvent(e);
60             };
61         },
62
63         /** Loads any grid column label changes, clears any 
64          * non-visible fields from the structure, and passes 
65          * the structure back to the grid to force a UI refresh.
66          */
67         reloadStructure : function() {
68
69             // update our copy of the column labels
70             var _this = this;
71             dojo.forEach(
72                 this.grid.structure[0].cells[0],
73                 function(gcell) {
74                     var cell = _this.cells.filter(
75                         function(c) { return c.field == gcell.field }
76                     )[0];
77                     cell.name = gcell.name;
78                 }
79             );
80
81             this.pruneInvisibleFields();
82             this.grid.setStructure(this.structure);
83         },
84
85
86         // determine the visible sorting from the 
87         // view and update our list of cells to match
88         refreshCells : function() {
89             var cells = this.cells;
90             this.cells = [];
91             var _this = this;
92
93             dojo.forEach(
94                  _this.grid.views.views[0].structure.cells[0],
95                  function(vCell) {
96                     for (var i = 0; i < cells.length; i++) {
97                         if (cells[i].field == vCell.field) {
98                             cells[i]._visible = true;
99                             _this.cells.push(cells[i]);
100                             break;
101                         }
102                     }
103                 }
104             );
105
106             // Depending on how the grid structure is built, there may be
107             // cells in the structure that are not yet in the view.  Push
108             // any remaining cells onto the end.
109             dojo.forEach(
110                 cells,
111                 function(cell) {
112                     existing = _this.cells.filter(function(s){return s.field == cell.field})[0]
113                     if (!existing) {
114                         cell._visible = false;
115                         _this.cells.push(cell);
116                     }
117                 }
118             );
119         },
120
121         buildDialog : function() {
122             var self = this;
123
124             var dialog = new dijit.Dialog({title : this.nls.COLUMN_PICKER});
125             var table = dojo.create('table', {'class':'oils-generic-table', innerHTML : 
126                 "<table><thead><tr><th width='30%'>" + this.nls.COLUMN + "</th>" +
127                 "<th width='23%'>" + this.nls.DISPLAY + "</th>" +
128                 "<th width='23%'>" + this.nls.AUTO_WIDTH + "</th>" +
129                 "<th width='23%'>" + this.nls.SORT_PRIORITY + "</th></tr></thead>" +
130                 "<tbody />"});
131
132             var tDiv = dojo.create('div');
133             tDiv.appendChild(table);
134
135             var bDiv = dojo.create('div', {style : 'text-align:right; width:100%;',
136                 innerHTML : "<span name='cancel_button'></span><span name='save_button'></span>"});
137
138             var textDiv = dojo.create('div', {style : 'padding:5px; margin-top:5px; border-top:1px solid #333', 
139                 innerHTML :
140                     "<i>" + this.nls.SORT_PRIORITY_ZERO + "<br/>" +
141                     "<i>" + this.nls.SORT_PRIORITY_MINUS});
142             
143             var wrapper = dojo.create('div');
144             wrapper.appendChild(tDiv);
145             wrapper.appendChild(textDiv);
146             wrapper.appendChild(bDiv);
147             dialog.containerNode.appendChild(wrapper);
148
149             var button = new dijit.form.Button({label: this.nls.SAVE },
150                 dojo.query('[name=save_button]', bDiv)[0]);
151             button.onClick = function() { dialog.hide(); self.update(true); };
152
153             button = new dijit.form.Button({label: this.nls.CANCEL },
154                 dojo.query('[name=cancel_button]', bDiv)[0]);
155             button.onClick = function() { dialog.hide(); };
156
157             return dialog;
158         },
159
160         // builds the column-picker dialog table
161         build : function() {
162             this.refreshCells();
163             var rows = dojo.query('tr', this.dialogTable);
164
165             for(var i = 0; i < rows.length; i++) {
166                 if(rows[i].getAttribute('picker'))
167                     this.dialogTable.removeChild(rows[i]);
168             }
169
170             rows = dojo.query('tr', this.dialogTable);
171             var lastChild = null;
172             if(rows.length > 0)
173                 lastChild = rows[rows.length-1];
174
175             for(var i = 0; i < this.cells.length; i++) {
176                 // setting table.innerHTML breaks stuff, so do it the hard way
177                 var cell = this.cells[i];
178                 tr = document.createElement('tr');
179                 tr.setAttribute('picker', 'picker');
180                 td0 = document.createElement('td');
181                 td1 = document.createElement('td');
182                 td2 = document.createElement('td');
183                 td3 = document.createElement('td');
184
185                 ipt = document.createElement('input');
186                 ipt.setAttribute('type', 'checkbox');
187                 ipt.setAttribute('name', 'selector');
188
189                 ipt2 = document.createElement('input');
190                 ipt2.setAttribute('type', 'checkbox');
191                 ipt2.setAttribute('name', 'width');
192
193                 ipt3 = document.createElement('div');
194
195                 if (cell.nonSelectable) {
196                     ipt.setAttribute('checked', 'checked');
197                     ipt.setAttribute('disabled', true);
198                     ipt2.setAttribute('disabled', true);
199
200                 } else {
201                     if (cell._visible) {
202                         ipt.setAttribute('checked', 'checked');
203                         if (cell.width == 'auto') 
204                             ipt2.setAttribute('checked', 'checked');
205                     } else {
206                         ipt.removeAttribute('checked');
207                     }
208                 }
209
210                 if (cell.field == '+selector') {
211                     // pick up the unescaped unicode checkmark char
212                     td0.innerHTML = cell.name;
213                 } else {
214                     td0.appendChild(document.createTextNode(cell.name));
215                 }
216                 td1.appendChild(ipt);
217                 td2.appendChild(ipt2);
218                 td3.appendChild(ipt3);
219                 tr.appendChild(td0);
220                 tr.appendChild(td1);
221                 tr.appendChild(td2);
222                 tr.appendChild(td3);
223
224                 if(lastChild)
225                     this.dialogTable.insertBefore(tr, lastChild);
226                 else
227                     this.dialogTable.appendChild(tr);
228
229                 if (this.grid.canSort(
230                     i + 1,  /* column index is 1-based */
231                     true    /* skip structure test (API abuse) */
232                 )) { 
233
234                     /* Ugly kludge. When using with FlattenerGrid the
235                      * conditional is needed. Shouldn't hurt usage with
236                      * AutoGrid. */
237                     if (typeof cell.fsort == "undefined" || cell.fsort) {
238                         // must be added after its parent node is inserted into the DOM.
239                         var ns = new dijit.form.NumberSpinner(
240                             {   constraints : {places : 0}, 
241                                 value : cell._sort || 0,
242                                 style : 'width:4em',
243                                 name : 'sort',
244                             }, ipt3
245                         );
246                     }
247                 }
248             }
249         },
250
251         // update the grid based on the items selected in the picker dialog
252         update : function(persist) {
253             var rows = dojo.query('[picker=picker]', this.dialogTable);
254             var _this = this;
255             var displayCells = [];
256             var sortUpdated = false;
257
258             for (var i = 0; i < rows.length; i++) {
259                 var row = rows[i];
260                 var selector = dojo.query('[name=selector]', row)[0];
261                 var width = dojo.query('[name=width]', row)[0];
262                 var sort = dojo.query('[name=sort]', row)[0];
263                 var cell = this.cells[i]; // index should match dialog
264
265                 if (sort && cell._sort != sort.value) {
266                     sortUpdated = true;
267                     cell._sort = sort.value;
268                 }
269
270                 if (selector.checked) {
271                     cell._visible = true;
272                     if (width.checked) {
273                         cell.width = 'auto';
274                     } else if(cell.width == 'auto') {
275                         delete cell.width;
276                     }
277                     displayCells.push(cell);
278
279                 } else {
280                     cell._visible = false;
281                     delete cell.width;
282                 }
283             }
284
285             if (sortUpdated && this.onSortChange) 
286                 this.onSortChange(this.buildSortList());
287
288             this.structure[0].cells[0] = displayCells;
289             this.grid.setStructure(this.structure);
290             this.grid.update();
291
292             if (persist) this.persist(true);
293         },
294
295         // extract cells that have sorting applied, order lowest to highest
296         buildSortList : function() {
297             var sortList = this.cells.filter(
298                 function(cella) { return Number(cella._sort) }
299             ).sort( 
300                 function(a, b) { 
301                     if (Math.abs(a._sort) < Math.abs(b._sort)) return -1; 
302                     return 1; 
303                 }
304             );
305
306             return sortList.map(function(f){
307                 var dir = f._sort < 0 ? 'desc' : 'asc';
308                 return {field : f.field, direction : dir};
309             });
310         },
311
312         // save me as a user setting
313         persist : function(noRefresh) {
314             var list = [];
315             var autos = [];
316             if (!noRefresh) this.refreshCells();
317
318             for(var i = 0; i < this.cells.length; i++) {
319                 var cell = this.cells[i];
320                 if (cell._visible) {
321                     list.push(cell.field);
322                     if(cell.width == 'auto')
323                         autos.push(cell.field);
324                 } 
325             }
326
327             var setting = {};
328             setting[this.persistKey] = {
329                 'columns' : list, 
330                 'auto' : autos,
331                 'sort' : this.buildSortList().map(function(f){return f.field})
332             };
333
334             var _this = this;
335             fieldmapper.standardRequest(
336                 ['open-ils.actor', 'open-ils.actor.patron.settings.update'],
337                 {   async: true,
338                     params: [this.authtoken, null, setting],
339                     oncomplete: function(r) {
340                         var stat = openils.Util.readResponse(r);
341                     },
342                     onmethoderror : function() {},
343                     onerror : function() { 
344                         console.log("No user setting '" + _this.persistKey + "' configured.  Cannot persist") 
345                     }
346                 }
347             );
348         }, 
349
350         loadColsFromSetting : function(setting) {
351             var _this = this;
352             this.setting = setting;
353             var displayCells = [];
354             
355             // new component, existing settings may not have this
356             if (!setting.sort) setting.sort = [];
357
358             dojo.forEach(setting.columns,
359                 function(col) {
360                     var cell = _this.cells.filter(function(c){return c.field == col})[0];
361                     if (cell) {
362                         cell._visible = true;
363                         displayCells.push(cell);
364
365                         if(setting.auto.indexOf(cell.field) > -1) {
366                             cell.width = 'auto';
367                         } else {
368                             if(cell.width == 'auto')
369                                 delete cell.width;
370                         }
371                         cell._sort = setting.sort.indexOf(cell.field) + 1;
372
373                     } else {
374                         console.log('Unknown setting column '+col+'.  Ignoring...');
375                     }
376                 }
377             );
378             
379             // any cells not in the setting must be marked as non-visible
380             dojo.forEach(this.cells, function(cell) { 
381                 if (setting.columns.indexOf(cell.field) == -1) {
382                     cell._visible = false;
383                     cell._sort = 0;
384                 }
385             });
386
387             this.structure[0].cells[0] = displayCells;
388             this.grid.setStructure(this.structure);
389             this.grid.update();
390         },
391
392         // *only* call this when no usr setting tells us what columns
393         // are visible or not.
394         pruneInvisibleFields : function() {
395             this.structure[0].cells[0] = dojo.filter(
396                 this.structure[0].cells[0],
397                 dojo.hitch(this, function(c) {
398                     // keep true or undef, lose false
399                     return typeof c._visible == "undefined" || c._visible;
400                 })
401             );
402         },
403
404         load : function() {
405             var _this = this;
406
407             // if load is called before the user has logged in,
408             // queue the loading up for after authentication.
409             if (!this.authtoken) {
410                 var _this = this;
411                 openils.Util.addOnLoad(function() {
412                     _this.authtoken = openils.User.authtoken;
413                     _this.load();
414                 }); 
415                 return;
416             }
417
418             if(this.setting) {
419                 this.loadColsFromSetting(this.setting);
420                 this._onLoad({sortFields : this.buildSortList()});
421                 return;
422             }
423
424             fieldmapper.standardRequest(
425                 ['open-ils.actor', 'open-ils.actor.patron.settings.retrieve'],
426                 {   async: true,
427                     params: [this.authtoken, null, this.persistKey],
428                     oncomplete: function(r) {
429                         var set = openils.Util.readResponse(r);
430                         if(set) {
431                             _this.loadColsFromSetting(set);
432                         } else {
433                             _this.grid.setStructure(_this.structure);
434                             _this.grid.update();
435                         }
436                         _this._onLoad({sortFields : _this.buildSortList()});
437                     }
438                 }
439             );
440         },
441     });
442 }
443