prevent negative paging display offset. added paginator to event def grids
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / widget / AutoGrid.js
1 if(!dojo._hasResource['openils.widget.AutoGrid']) {
2     dojo.provide('openils.widget.AutoGrid');
3     dojo.require('dojox.grid.DataGrid');
4     dojo.require('dijit.layout.ContentPane');
5     dojo.require('openils.widget.AutoWidget');
6     dojo.require('openils.widget.AutoFieldWidget');
7     dojo.require('openils.widget.EditPane');
8     dojo.require('openils.widget.EditDialog');
9     dojo.require('openils.widget.GridColumnPicker');
10     dojo.require('openils.Util');
11
12     dojo.declare(
13         'openils.widget.AutoGrid',
14         [dojox.grid.DataGrid, openils.widget.AutoWidget],
15         {
16
17             /* if true, pop up an edit dialog when user hits Enter on a give row */
18             editOnEnter : false, 
19             defaultCellWidth : null,
20             editStyle : 'dialog',
21             suppressFields : null,
22             hideSelector : false,
23             selectorWidth : '1.5',
24             showColumnPicker : false,
25             columnPickerPrefix : null,
26             displayLimit : 15,
27             displayOffset : 0,
28             showPaginator : false,
29             showLoadFilter : false,
30             suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
31
32             /* by default, don't show auto-generated (sequence) fields */
33             showSequenceFields : false, 
34
35             startup : function() {
36                 this.selectionMode = 'single';
37                 this.sequence = openils.widget.AutoGrid.sequence++;
38                 openils.widget.AutoGrid.gridCache[this.sequence] = this;
39                 this.inherited(arguments);
40                 this.initAutoEnv();
41                 this.attr('structure', this._compileStructure());
42                 this.setStore(this.buildAutoStore());
43                 this.cachedQueryOpts = {};
44
45                 if(this.showColumnPicker) {
46                     if(!this.columnPickerPrefix) {
47                         console.error("No columnPickerPrefix defined");
48                     } else {
49                         var picker = new openils.widget.GridColumnPicker(
50                             openils.User.authtoken, this.columnPickerPrefix, this);
51                         if(openils.User.authtoken) {
52                             picker.load();
53                         } else {
54                             openils.Util.addOnLoad(function() { picker.load() });
55                         }
56                     }
57                 }
58
59                 this.overrideEditWidgets = {};
60                 this.overrideEditWidgetClass = {};
61
62                 if(this.editOnEnter) 
63                     this._applyEditOnEnter();
64                 else if(this.singleEditStyle) 
65                     this._applySingleEditStyle();
66
67                 if(!this.hideSelector) {
68                     dojo.connect(this, 'onHeaderCellClick', 
69                         function(e) {
70                             if(e.cell.index == 0)
71                                 this.toggleSelectAll();
72                         }
73                     );
74                 }
75
76                 if(this.showPaginator) {
77                     var self = this;
78                     this.paginator = new dijit.layout.ContentPane();
79
80
81                     var back = dojo.create('a', {
82                         innerHTML : 'Back',  // TODO i18n
83                         style : 'padding-right:6px;',
84                         href : 'javascript:void(0);', 
85                         onclick : function() { 
86                             self.resetStore();
87                             self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
88                             if(self.displayOffset < 0)
89                                 self.cachedQueryOpts.offset = self.displayOffset = 0;
90                             if(self.dataLoader)
91                                 self.dataLoader()
92                             else
93                                 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
94                         }
95                     });
96
97                     var forw = dojo.create('a', {
98                         innerHTML : 'Next',  // TODO i18n
99                         style : 'padding-right:6px;',
100                         href : 'javascript:void(0);', 
101                         onclick : function() { 
102                             self.resetStore();
103                             self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
104                             if(self.dataLoader)
105                                 self.dataLoader()
106                             else
107                                 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
108                         }
109                     });
110
111                     dojo.place(this.paginator.domNode, this.domNode, 'before');
112                     dojo.place(back, this.paginator.domNode);
113                     dojo.place(forw, this.paginator.domNode);
114
115                     if(this.showLoadFilter) {
116                         dojo.require('openils.widget.PCrudFilterDialog');
117                         dojo.place(
118                             dojo.create('a', {
119                                 innerHTML : 'Filter', // TODO i18n
120                                 style : 'padding-right:6px;',
121                                 href : 'javascript:void(0);', 
122                                 onclick : function() { 
123                                     var dialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass})
124                                     dialog.onApply = function(filter) {
125                                         self.resetStore();
126                                         self.loadAll(self.cachedQueryOpts, filter);
127                                     };
128                                     dialog.startup();
129                                     dialog.show();
130                                 }
131                             }),
132                             this.paginator.domNode
133                         );
134                     }
135
136                     // progress image
137                     this.loadProgressIndicator = dojo.create('img', {
138                         src:'/opac/images/progressbar_green.gif', // TODO configured path
139                         style:'height:16px;width:16px;'
140                     });
141                     dojo.place(this.loadProgressIndicator, this.paginator.domNode);
142                 }
143             },
144
145             hideLoadProgressIndicator : function() {
146                 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
147             },
148
149             showLoadProgressIndicator : function() {
150                 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
151             },
152
153             /* Don't allow sorting on the selector column */
154             canSort : function(rowIdx) {
155                 if(rowIdx == 1 && !this.hideSelector)
156                     return false;
157                 return true;
158             },
159
160             _compileStructure : function() {
161                 var existing = (this.structure && this.structure[0].cells[0]) ? 
162                     this.structure[0].cells[0] : [];
163                 var fields = [];
164
165                 var self = this;
166                 function pushEntry(entry) {
167                     if(self.suppressFields) {
168                         if(dojo.indexOf(self.suppressFields, entry.field) != -1)
169                             return;
170                     }
171                     if(!entry.get) 
172                         entry.get = openils.widget.AutoGrid.defaultGetter
173                     if(!entry.width && self.defaultCellWidth)
174                         entry.width = self.defaultCellWidth;
175                     fields.push(entry);
176                 }
177
178                 if(!this.hideSelector) {
179                     // insert the selector column
180                     pushEntry({
181                         field : '+selector',
182                         formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
183                         get : function(rowIdx, item) { if(item) return rowIdx; },
184                         width : this.selectorWidth,
185                         name : '&#x2713',
186                         nonSelectable : true
187                     });
188                 }
189
190
191                 if(!this.fieldOrder) {
192                     /* no order defined, start with any explicit grid fields */
193                     for(var e in existing) {
194                         var entry = existing[e];
195                         var field = this.fmIDL.fields.filter(
196                             function(i){return (i.name == entry.field)})[0];
197                         if(field) entry.name = entry.name || field.label;
198                         pushEntry(entry);
199                     }
200                 }
201
202                 for(var f in this.sortedFieldList) {
203                     var field = this.sortedFieldList[f];
204                     if(!field || field.virtual) continue;
205                     
206                     // field was already added above
207                     if(fields.filter(function(i){return (i.field == field.name)})[0]) 
208                         continue;
209
210                     var entry = existing.filter(function(i){return (i.field == field.name)})[0];
211                     if(entry) {
212                         entry.name = field.label;
213                     } else {
214                         // unless specifically requested, hide sequence fields
215                         if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
216                             continue; 
217
218                         entry = {field:field.name, name:field.label};
219                     }
220                     pushEntry(entry);
221                 }
222
223                 if(this.fieldOrder) {
224                     /* append any explicit non-IDL grid fields to the end */
225                     for(var e in existing) {
226                         var entry = existing[e];
227                         var field = fields.filter(
228                             function(i){return (i.field == entry.field)})[0];
229                         if(field) continue; // don't duplicate
230                         pushEntry(entry);
231                     }
232                 }
233
234                 return [{cells: [fields]}];
235             },
236
237             toggleSelectAll : function() {
238                 var selected = this.getSelectedRows();
239                 for(var i = 0; i < this.rowCount; i++) {
240                     if(selected[0])
241                         this.deSelectRow(i);
242                     else
243                         this.selectRow(i);
244                 }
245             },
246
247             getSelectedRows : function() {
248                 var rows = []; 
249                 dojo.forEach(
250                     dojo.query('[name=autogrid.selector]', this.domNode),
251                     function(input) {
252                         if(input.checked)
253                             rows.push(input.getAttribute('row'));
254                     }
255                 );
256                 return rows;
257             },
258
259             getFirstSelectedRow : function() {
260                 return this.getSelectedRows()[0];
261             },
262
263             getSelectedItems : function() {
264                 var items = [];
265                 var self = this;
266                 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
267                 return items;
268             },
269
270             selectRow : function(rowIdx) {
271                 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
272                 for(var i = 0; i < inputs.length; i++) {
273                     if(inputs[i].getAttribute('row') == rowIdx) {
274                         if(!inputs[i].disabled)
275                             inputs[i].checked = true;
276                         break;
277                     }
278                 }
279             },
280
281             deSelectRow : function(rowIdx) {
282                 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
283                 for(var i = 0; i < inputs.length; i++) {
284                     if(inputs[i].getAttribute('row') == rowIdx) {
285                         inputs[i].checked = false;
286                         break;
287                     }
288                 }
289             },
290
291             /**
292              * @return {Array} List of every fieldmapper object in the data store
293              */
294             getAllObjects : function() {
295                 var objs = [];
296                 var self = this;
297                 this.store.fetch({
298                     onComplete : function(list) {
299                         dojo.forEach(list, 
300                             function(item) {
301                                 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
302                             }
303                         )
304                     }
305                 });
306                 return objs;
307             },
308
309             /**
310              * Deletes the underlying object for all selected rows
311              */
312             deleteSelected : function() {
313                 var items = this.getSelectedItems();
314                 var total = items.length;
315                 var self = this;
316                 dojo.require('openils.PermaCrud');
317                 dojo.forEach(items,
318                     function(item) {
319                         var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
320                         new openils.PermaCrud()['delete'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
321                     }
322                 );
323             },
324
325             _formatRowSelectInput : function(rowIdx) {
326                 if(rowIdx === null || rowIdx === undefined) return '';
327                 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
328                 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx)) 
329                     s += " disabled='disabled'";
330                 return s + "/>";
331             },
332
333             _applySingleEditStyle : function() {
334                 this.onMouseOverRow = function(e) {};
335                 this.onMouseOutRow = function(e) {};
336                 this.onCellFocus = function(cell, rowIndex) { 
337                     this.selection.deselectAll();
338                     this.selection.select(this.focus.rowIndex);
339                 };
340             },
341
342             /* capture keydown and launch edit dialog on enter */
343             _applyEditOnEnter : function() {
344                 this._applySingleEditStyle();
345
346                 dojo.connect(this, 'onRowDblClick',
347                     function(e) {
348                         if(this.editStyle == 'pane')
349                             this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
350                         else
351                             this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
352                     }
353                 );
354
355                 dojo.connect(this, 'onKeyDown',
356                     function(e) {
357                         if(e.keyCode == dojo.keys.ENTER) {
358                             this.selection.deselectAll();
359                             this.selection.select(this.focus.rowIndex);
360                             if(this.editStyle == 'pane')
361                                 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
362                             else
363                                 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
364                         }
365                     }
366                 );
367             },
368
369             _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
370                 var grid = this;
371                 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
372                 var idents = grid.store.getIdentityAttributes();
373
374                 var pane = new openils.widget.EditPane({
375                     fmObject:fmObject,
376                     overrideWidgets : this.overrideEditWidgets,
377                     overrideWidgetClass : this.overrideEditWidgetClass,
378                     disableWidgetTest : this.disableWidgetTest,
379                     onPostSubmit : function() {
380                         for(var i in fmObject._fields) {
381                             var field = fmObject._fields[i];
382                             if(idents.filter(function(j){return (j == field)})[0])
383                                 continue; // don't try to edit an identifier field
384                             grid.store.setValue(storeItem, field, fmObject[field]());
385                         }
386                         if(self.onPostUpdate)
387                             self.onPostUpdate(storeItem, rowIndex);
388                         setTimeout(
389                             function(){
390                                 try { 
391                                     grid.views.views[0].getCellNode(rowIndex, 0).focus(); 
392                                 } catch (E) {}
393                             },200
394                         );
395                         if(onPostSubmit) 
396                             onPostSubmit();
397                     },
398                     onCancel : function() {
399                         setTimeout(function(){
400                             grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
401                         if(onCancel) onCancel();
402                     }
403                 });
404
405                 pane.fieldOrder = this.fieldOrder;
406                 pane.mode = 'update';
407                 return pane;
408             },
409
410             _makeCreatePane : function(onPostSubmit, onCancel) {
411                 var grid = this;
412                 var pane = new openils.widget.EditPane({
413                     fmClass : this.fmClass,
414                     overrideWidgets : this.overrideEditWidgets,
415                     overrideWidgetClass : this.overrideEditWidgetClass,
416                     disableWidgetTest : this.disableWidgetTest,
417                     onPostSubmit : function(r) {
418                         var fmObject = openils.Util.readResponse(r);
419                         if(grid.onPostCreate)
420                             grid.onPostCreate(fmObject);
421                         if(fmObject) 
422                             grid.store.newItem(fmObject.toStoreItem());
423                         setTimeout(function(){
424                             try {
425                                 grid.selection.select(grid.rowCount-1);
426                                 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
427                             } catch (E) {}
428                         },200);
429                         if(onPostSubmit)
430                             onPostSubmit(fmObject);
431                     },
432                     onCancel : function() {
433                         if(onCancel) onCancel();
434                     }
435                 });
436                 pane.fieldOrder = this.fieldOrder;
437                 pane.mode = 'create';
438                 return pane;
439             },
440
441             /**
442              * Creates an EditPane with a copy of the data from the provided store
443              * item for cloning said item
444              * @param {Object} storeItem Dojo data item
445              * @param {Number} rowIndex The Grid row index of the item to be cloned
446              * @param {Function} onPostSubmit Optional callback for post-submit behavior
447              * @param {Function} onCancel Optional callback for clone cancelation
448              * @return {Object} The clone EditPane
449              */
450             _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
451                 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
452                 var origPane = this._makeEditPane(storeItem, rowIndex);
453                 clonePane.startup();
454                 origPane.startup();
455                 dojo.forEach(origPane.fieldList,
456                     function(field) {
457                         if(field.widget.widget.attr('disabled')) return;
458                         var w = clonePane.fieldList.filter(
459                             function(i) { return (i.name == field.name) })[0];
460                         w.widget.baseWidgetValue(field.widget.widgetValue); // sync widgets
461                         w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widgetValue)}; // async widgets
462                     }
463                 );
464                 origPane.destroy();
465                 return clonePane;
466             },
467
468
469             _drawEditDialog : function(storeItem, rowIndex) {
470                 var self = this;
471                 var done = function() { self.hideDialog(); };
472                 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
473                 this.editDialog = new openils.widget.EditDialog({editPane:pane});
474                 this.editDialog.startup();
475                 this.editDialog.show();
476             },
477
478             /**
479              * Generates an EditDialog for object creation and displays it to the user
480              */
481             showCreateDialog : function() {
482                 var self = this;
483                 var done = function() { self.hideDialog(); };
484                 var pane = this._makeCreatePane(done, done);
485                 this.editDialog = new openils.widget.EditDialog({editPane:pane});
486                 this.editDialog.startup();
487                 this.editDialog.show();
488             },
489
490             _drawEditPane : function(storeItem, rowIndex) {
491                 var self = this;
492                 var done = function() { self.hidePane(); };
493                 dojo.style(this.domNode, 'display', 'none');
494                 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
495                 this.editPane.startup();
496                 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
497                 if(this.onEditPane) this.onEditPane(this.editPane);
498             },
499
500             showClonePane : function(onPostSubmit) {
501                 var self = this;
502                 var done = function() { self.hidePane(); };
503
504                                     
505                 var row = this.getFirstSelectedRow();
506                 if(!row) return;
507
508                 var postSubmit = (onPostSubmit) ? 
509                     function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
510                     done;
511
512                 dojo.style(this.domNode, 'display', 'none');
513                 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
514                 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
515                 if(this.onEditPane) this.onEditPane(this.editPane);
516             },
517
518             showCreatePane : function() {
519                 var self = this;
520                 var done = function() { self.hidePane(); };
521                 dojo.style(this.domNode, 'display', 'none');
522                 this.editPane = this._makeCreatePane(done, done);
523                 this.editPane.startup();
524                 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
525                 if(this.onEditPane) this.onEditPane(this.editPane);
526             },
527
528             hideDialog : function() {
529                 this.editDialog.hide(); 
530                 this.editDialog.destroy(); 
531                 delete this.editDialog;
532                 this.update();
533             },
534
535             hidePane : function() {
536                 this.domNode.parentNode.removeChild(this.editPane.domNode);
537                 this.editPane.destroy();
538                 delete this.editPane;
539                 dojo.style(this.domNode, 'display', 'block');
540                 this.update();
541             },
542             
543             resetStore : function() {
544                 this.setStore(this.buildAutoStore());
545             },
546
547             loadAll : function(opts, search) {
548                 dojo.require('openils.PermaCrud');
549                 if(this.loadProgressIndicator)
550                     dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
551                 var self = this;
552                 opts = dojo.mixin(
553                     {limit : this.displayLimit, offset : this.displayOffset}, 
554                     opts || {}
555                 );
556                 opts = dojo.mixin(opts, {
557                     async : true,
558                     streaming : true,
559                     onresponse : function(r) {
560                         var item = openils.Util.readResponse(r);
561                         self.store.newItem(item.toStoreItem());
562                     },
563                     oncomplete : function() {
564                         if(self.loadProgressIndicator) 
565                             dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
566                     }
567                 });
568
569                 this.cachedQuerySearch = search;
570                 this.cachedQueryOpts = opts;
571                 if(search)
572                     new openils.PermaCrud().search(this.fmClass, search, opts);
573                 else
574                     new openils.PermaCrud().retrieveAll(this.fmClass, opts);
575             }
576         } 
577     );
578
579     // static ID generater seed
580     openils.widget.AutoGrid.sequence = 0;
581     openils.widget.AutoGrid.gridCache = {};
582
583     openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
584
585     openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
586         if(!item) return '';
587         var val = this.grid.store.getValue(item, this.field);
588         var autoWidget = new openils.widget.AutoFieldWidget({
589             fmClass: this.grid.fmClass,
590             fmField: this.field,
591             widgetValue : val,
592             readOnly : true,
593             forceSync : true, // prevents many simultaneous requests for the same data
594             suppressLinkedFields : this.grid.suppressLinkedFields
595         });
596
597         autoWidget.build();
598
599         /*
600         // With proper caching, this should not be necessary to prevent grid render flickering
601         var _this = this;
602         autoWidget.build(
603             function(w, ww) {
604                 try {
605                     var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
606                     if(node) 
607                         node.innerHTML = ww.getDisplayString();
608                 } catch(E) {}
609             }
610         );
611         */
612
613         return autoWidget.getDisplayString();
614     }
615 }
616