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