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