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