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');
13 'openils.widget.AutoGrid',
14 [dojox.grid.DataGrid, openils.widget.AutoWidget],
17 /* if true, pop up an edit dialog when user hits Enter on a give row */
18 editPaneOnSubmit : null,
19 onPostSubmit : null, // called after any CRUD actions are complete
20 createPaneOnSubmit : null,
22 defaultCellWidth : null,
25 suppressFields : null,
26 suppressEditFields : null,
27 suppressFilterFields : null,
29 hideLineNumber : false,
30 selectorWidth : '1.5',
31 lineNumberWidth : '1.5',
32 showColumnPicker : false,
33 columnPickerPrefix : null,
36 requiredFields : null,
37 hidePaginator : false,
38 showLoadFilter : false,
39 onItemReceived : null,
40 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
42 /* by default, don't show auto-generated (sequence) fields */
43 showSequenceFields : false,
45 // style the cells in the line number column
46 onStyleRow : function(row) {
47 if (!this.hideLineNumber) {
48 var cellIdx = this.hideSelector ? 0 : 1;
49 dojo.addClass(this.views.views[0].getCellNode(row.index, cellIdx), 'autoGridLineNumber');
53 startup : function() {
54 this.selectionMode = 'single';
55 this.sequence = openils.widget.AutoGrid.sequence++;
56 openils.widget.AutoGrid.gridCache[this.sequence] = this;
57 this.inherited(arguments);
59 this.attr('structure', this._compileStructure());
60 this.setStore(this.buildAutoStore());
61 this.cachedQueryOpts = {};
62 this._showing_create_pane = false;
64 if(this.showColumnPicker) {
65 if(!this.columnPickerPrefix) {
66 console.error("No columnPickerPrefix defined");
68 var picker = new openils.widget.GridColumnPicker(
69 openils.User.authtoken, this.columnPickerPrefix, this);
70 if(openils.User.authtoken) {
73 openils.Util.addOnLoad(function() { picker.load() });
78 this.overrideEditWidgets = {};
79 this.overrideEditWidgetClass = {};
80 this.overrideWidgetArgs = {};
83 this._applyEditOnEnter();
84 else if(this.singleEditStyle)
85 this._applySingleEditStyle();
87 if(!this.hideSelector) {
88 dojo.connect(this, 'onHeaderCellClick',
91 this.toggleSelectAll();
96 if(!this.hidePaginator) {
98 this.paginator = new dijit.layout.ContentPane();
101 var back = dojo.create('a', {
102 innerHTML : 'Back', // TODO i18n
103 style : 'padding-right:6px;',
104 href : 'javascript:void(0);',
105 onclick : function() {
106 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
107 if(self.displayOffset < 0)
108 self.cachedQueryOpts.offset = self.displayOffset = 0;
113 var forw = dojo.create('a', {
114 innerHTML : 'Next', // TODO i18n
115 style : 'padding-right:6px;',
116 href : 'javascript:void(0);',
117 onclick : function() {
118 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
123 dojo.place(this.paginator.domNode, this.domNode, 'before');
124 dojo.place(back, this.paginator.domNode);
125 dojo.place(forw, this.paginator.domNode);
127 if(this.showLoadFilter) {
128 dojo.require('openils.widget.PCrudFilterDialog');
131 innerHTML : 'Filter', // TODO i18n
132 style : 'padding-right:6px;',
133 href : 'javascript:void(0);',
134 onclick : function() {
135 if (!self.filterDialog) {
136 self.filterDialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
137 self.filterDialog.onApply = function(filter) {
139 self.loadAll(self.cachedQueryOpts, filter);
141 self.filterDialog.startup();
143 self.filterDialog.show();
146 this.paginator.domNode
151 this.loadProgressIndicator = dojo.create('img', {
152 src:'/opac/images/progressbar_green.gif', // TODO configured path
153 style:'height:16px;width:16px;'
155 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
159 hideLoadProgressIndicator : function() {
160 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
163 showLoadProgressIndicator : function() {
164 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
167 /* Don't allow sorting on the selector column */
168 canSort : function(rowIdx) {
169 if(rowIdx == 1 && !this.hideSelector)
171 if(this.hideSelector && rowIdx == 1 && !this.hideLineNumber)
173 if(!this.hideSelector && rowIdx == 2 && !this.hideLineNumber)
178 _compileStructure : function() {
179 var existing = (this.structure && this.structure[0].cells[0]) ?
180 this.structure[0].cells[0] : [];
184 function pushEntry(entry) {
185 if(self.suppressFields) {
186 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
190 entry.get = openils.widget.AutoGrid.defaultGetter
191 if(!entry.width && self.defaultCellWidth)
192 entry.width = self.defaultCellWidth;
196 if(!this.hideSelector) {
197 // insert the selector column
200 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
201 get : function(rowIdx, item) { if(item) return rowIdx; },
202 width : this.selectorWidth,
208 if(!this.hideLineNumber) {
209 // insert the line number column
212 get : function(rowIdx, item) { if(item) return 1 + rowIdx; },
213 width : this.lineNumberWidth,
215 nonSelectable : false
219 if(!this.fieldOrder) {
220 /* no order defined, start with any explicit grid fields */
221 for(var e in existing) {
222 var entry = existing[e];
223 var field = this.fmIDL.fields.filter(
224 function(i){return (i.name == entry.field)})[0];
225 if(field) entry.name = entry.name || field.label;
230 for(var f in this.sortedFieldList) {
231 var field = this.sortedFieldList[f];
232 if(!field || field.virtual) continue;
234 // field was already added above
235 if(fields.filter(function(i){return (i.field == field.name)})[0])
238 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
240 entry.name = entry.name || field.label;
242 // unless specifically requested, hide sequence fields
243 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
246 entry = {field:field.name, name:field.label};
251 if(this.fieldOrder) {
252 /* append any explicit non-IDL grid fields to the end */
253 for(var e in existing) {
254 var entry = existing[e];
255 var field = fields.filter(
256 function(i){return (i.field == entry.field)})[0];
257 if(field) continue; // don't duplicate
262 return [{cells: [fields]}];
265 toggleSelectAll : function() {
266 var selected = this.getSelectedRows();
267 for(var i = 0; i < this.rowCount; i++) {
275 getSelectedRows : function() {
278 dojo.query('[name=autogrid.selector]', this.domNode),
281 rows.push(input.getAttribute('row'));
287 getFirstSelectedRow : function() {
288 return this.getSelectedRows()[0];
291 getSelectedItems : function() {
294 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
298 selectRow : function(rowIdx) {
299 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
300 for(var i = 0; i < inputs.length; i++) {
301 if(inputs[i].getAttribute('row') == rowIdx) {
302 if(!inputs[i].disabled)
303 inputs[i].checked = true;
309 deSelectRow : function(rowIdx) {
310 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
311 for(var i = 0; i < inputs.length; i++) {
312 if(inputs[i].getAttribute('row') == rowIdx) {
313 inputs[i].checked = false;
320 * @return {Array} List of every fieldmapper object in the data store
322 getAllObjects : function() {
326 onComplete : function(list) {
329 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
338 * Deletes the underlying object for all selected rows
340 deleteSelected : function() {
341 var items = this.getSelectedItems();
342 var total = items.length;
344 dojo.require('openils.PermaCrud');
347 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
348 new openils.PermaCrud()['eliminate'](
350 oncomplete : function(r) {
351 self.store.deleteItem(item);
352 if (--total < 1 && self.onPostSubmit) {
362 _formatRowSelectInput : function(rowIdx) {
363 if(rowIdx === null || rowIdx === undefined) return '';
364 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
365 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
366 s += " disabled='disabled'";
370 _applySingleEditStyle : function() {
371 this.onMouseOverRow = function(e) {};
372 this.onMouseOutRow = function(e) {};
373 this.onCellFocus = function(cell, rowIndex) {
374 this.selection.deselectAll();
375 this.selection.select(this.focus.rowIndex);
379 /* capture keydown and launch edit dialog on enter */
380 _applyEditOnEnter : function() {
381 this._applySingleEditStyle();
383 dojo.connect(this, 'onRowDblClick',
385 if(this.editStyle == 'pane')
386 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
388 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
392 dojo.connect(this, 'onKeyDown',
394 if(e.keyCode == dojo.keys.ENTER) {
395 this.selection.deselectAll();
396 this.selection.select(this.focus.rowIndex);
397 if(this.editStyle == 'pane')
398 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
400 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
406 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
408 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
409 var idents = grid.store.getIdentityAttributes();
411 var pane = new openils.widget.EditPane({
413 hideSaveButton : this.editReadOnly,
414 readOnly : this.editReadOnly,
415 overrideWidgets : this.overrideEditWidgets,
416 overrideWidgetClass : this.overrideEditWidgetClass,
417 overrideWidgetArgs : this.overrideWidgetArgs,
418 disableWidgetTest : this.disableWidgetTest,
419 requiredFields : this.requiredFields,
420 suppressFields : this.suppressEditFields,
421 onPostSubmit : function() {
422 for(var i in fmObject._fields) {
423 var field = fmObject._fields[i];
424 if(idents.filter(function(j){return (j == field)})[0])
425 continue; // don't try to edit an identifier field
426 grid.store.setValue(storeItem, field, fmObject[field]());
428 if(grid.onPostUpdate)
429 grid.onPostUpdate(storeItem, rowIndex);
433 grid.views.views[0].getCellNode(rowIndex, 0).focus();
439 if (grid.onPostSubmit)
443 onCancel : function() {
444 setTimeout(function(){
445 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
446 if(onCancel) onCancel();
450 if (typeof this.editPaneOnSubmit == "function")
451 pane.onSubmit = this.editPaneOnSubmit;
452 pane.fieldOrder = this.fieldOrder;
453 pane.mode = 'update';
457 _makeCreatePane : function(onPostSubmit, onCancel) {
459 var pane = new openils.widget.EditPane({
460 fmClass : this.fmClass,
461 overrideWidgets : this.overrideEditWidgets,
462 overrideWidgetClass : this.overrideEditWidgetClass,
463 overrideWidgetArgs : this.overrideWidgetArgs,
464 disableWidgetTest : this.disableWidgetTest,
465 requiredFields : this.requiredFields,
466 suppressFields : this.suppressEditFields,
467 onPostSubmit : function(req, cudResults) {
468 var fmObject = cudResults[0];
469 if(grid.onPostCreate)
470 grid.onPostCreate(fmObject);
472 grid.store.newItem(fmObject.toStoreItem());
473 setTimeout(function(){
475 grid.selection.select(grid.rowCount-1);
476 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
480 onPostSubmit(fmObject);
481 if (grid.onPostSubmit)
484 onCancel : function() {
485 if(onCancel) onCancel();
488 if (typeof this.createPaneOnSubmit == "function")
489 pane.onSubmit = this.createPaneOnSubmit;
490 pane.fieldOrder = this.fieldOrder;
491 pane.mode = 'create';
496 * Creates an EditPane with a copy of the data from the provided store
497 * item for cloning said item
498 * @param {Object} storeItem Dojo data item
499 * @param {Number} rowIndex The Grid row index of the item to be cloned
500 * @param {Function} onPostSubmit Optional callback for post-submit behavior
501 * @param {Function} onCancel Optional callback for clone cancelation
502 * @return {Object} The clone EditPane
504 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
505 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
506 var origPane = this._makeEditPane(storeItem, rowIndex);
509 dojo.forEach(origPane.fieldList,
511 if(field.widget.widget.attr('disabled')) return;
512 var w = clonePane.fieldList.filter(
513 function(i) { return (i.name == field.name) })[0];
514 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
515 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
523 _drawEditDialog : function(storeItem, rowIndex) {
525 var done = function() { self.hideDialog(); };
526 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
527 this.editDialog = new openils.widget.EditDialog({editPane:pane});
528 this.editDialog.startup();
529 this.editDialog.show();
533 * Generates an EditDialog for object creation and displays it to the user
535 showCreateDialog : function() {
537 var done = function() { self.hideDialog(); };
538 var pane = this._makeCreatePane(done, done);
539 this.editDialog = new openils.widget.EditDialog({editPane:pane});
540 this.editDialog.startup();
541 this.editDialog.show();
544 _drawEditPane : function(storeItem, rowIndex) {
546 var done = function() { self.hidePane(); };
547 dojo.style(this.domNode, 'display', 'none');
548 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
549 this.editPane.startup();
550 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
551 if(this.onEditPane) this.onEditPane(this.editPane);
554 showClonePane : function(onPostSubmit) {
556 var done = function() { self.hidePane(); };
559 var row = this.getFirstSelectedRow();
562 var postSubmit = (onPostSubmit) ?
563 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
566 dojo.style(this.domNode, 'display', 'none');
567 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
568 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
569 if(this.onEditPane) this.onEditPane(this.editPane);
572 showCreatePane : function() {
573 if (this._showing_create_pane)
575 this._showing_create_pane = true;
578 var done = function() {
579 self._showing_create_pane = false;
582 dojo.style(this.domNode, 'display', 'none');
583 this.editPane = this._makeCreatePane(done, done);
584 this.editPane.startup();
585 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
586 if(this.onEditPane) this.onEditPane(this.editPane);
589 hideDialog : function() {
590 this.editDialog.hide();
591 this.editDialog.destroy();
592 delete this.editDialog;
596 hidePane : function() {
597 this.domNode.parentNode.removeChild(this.editPane.domNode);
598 this.editPane.destroy();
599 delete this.editPane;
600 dojo.style(this.domNode, 'display', 'block');
604 resetStore : function() {
605 this.setStore(this.buildAutoStore());
608 refresh : function() {
613 this.loadAll(this.cachedQueryOpts, this.cachedQuerySearch);
616 loadAll : function(opts, search) {
617 dojo.require('openils.PermaCrud');
618 if(this.loadProgressIndicator)
619 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
622 {limit : this.displayLimit, offset : this.displayOffset},
625 opts = dojo.mixin(opts, {
628 onresponse : function(r) {
629 var item = openils.Util.readResponse(r);
630 if (self.onItemReceived)
631 self.onItemReceived(item);
632 self.store.newItem(item.toStoreItem());
634 oncomplete : function() {
635 if(self.loadProgressIndicator)
636 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
640 this.cachedQuerySearch = search;
641 this.cachedQueryOpts = opts;
643 new openils.PermaCrud().search(this.fmClass, search, opts);
645 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
650 // static ID generater seed
651 openils.widget.AutoGrid.sequence = 0;
652 openils.widget.AutoGrid.gridCache = {};
654 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
656 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
658 if(!this.grid.overrideWidgetArgs[this.field])
659 this.grid.overrideWidgetArgs[this.field] = {};
660 var val = this.grid.store.getValue(item, this.field);
661 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
662 fmClass: this.grid.fmClass,
666 forceSync : true, // prevents many simultaneous requests for the same data
667 suppressLinkedFields : this.grid.suppressLinkedFields
668 },this.grid.overrideWidgetArgs[this.field]));
673 // With proper caching, this should not be necessary to prevent grid render flickering
678 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
680 node.innerHTML = ww.getDisplayString();
686 return autoWidget.getDisplayString();
689 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
690 if (!item) return "";
692 var aou_id = this.grid.store.getValue(item, this.field);
694 return fieldmapper.aou.findOrgUnit(aou_id).shortname();