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