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