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