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