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