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