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