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 createPaneOnSubmit : null,
21 defaultCellWidth : null,
24 suppressFields : null,
25 suppressEditFields : null,
26 suppressFilterFields : null,
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');
40 dojo.addClass(row.node.firstChild.firstChild.firstChild.childNodes[1],'autoGridLineNumber');
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
51 /* by default, don't show auto-generated (sequence) fields */
52 showSequenceFields : false,
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);
60 this.attr('structure', this._compileStructure());
61 this.setStore(this.buildAutoStore());
62 this.cachedQueryOpts = {};
63 this._showing_create_pane = false;
65 if(this.showColumnPicker) {
66 if(!this.columnPickerPrefix) {
67 console.error("No columnPickerPrefix defined");
69 var picker = new openils.widget.GridColumnPicker(
70 openils.User.authtoken, this.columnPickerPrefix, this);
71 if(openils.User.authtoken) {
74 openils.Util.addOnLoad(function() { picker.load() });
79 this.overrideEditWidgets = {};
80 this.overrideEditWidgetClass = {};
81 this.overrideWidgetArgs = {};
84 this._applyEditOnEnter();
85 else if(this.singleEditStyle)
86 this._applySingleEditStyle();
88 if(!this.hideSelector) {
89 dojo.connect(this, 'onHeaderCellClick',
92 this.toggleSelectAll();
97 if(!this.hidePaginator) {
99 this.paginator = new dijit.layout.ContentPane();
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;
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;
124 dojo.place(this.paginator.domNode, this.domNode, 'before');
125 dojo.place(back, this.paginator.domNode);
126 dojo.place(forw, this.paginator.domNode);
128 if(this.showLoadFilter) {
129 dojo.require('openils.widget.PCrudFilterDialog');
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) {
140 self.loadAll(self.cachedQueryOpts, filter);
142 self.filterDialog.startup();
144 self.filterDialog.show();
147 this.paginator.domNode
152 this.loadProgressIndicator = dojo.create('img', {
153 src:'/opac/images/progressbar_green.gif', // TODO configured path
154 style:'height:16px;width:16px;'
156 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
160 hideLoadProgressIndicator : function() {
161 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
164 showLoadProgressIndicator : function() {
165 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
168 /* Don't allow sorting on the selector column */
169 canSort : function(rowIdx) {
170 if(rowIdx == 1 && !this.hideSelector)
172 if(this.hideSelector && rowIdx == 1 && !this.hideLineNumber)
174 if(!this.hideSelector && rowIdx == 2 && !this.hideLineNumber)
179 _compileStructure : function() {
180 var existing = (this.structure && this.structure[0].cells[0]) ?
181 this.structure[0].cells[0] : [];
185 function pushEntry(entry) {
186 if(self.suppressFields) {
187 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
191 entry.get = openils.widget.AutoGrid.defaultGetter
192 if(!entry.width && self.defaultCellWidth)
193 entry.width = self.defaultCellWidth;
197 if(!this.hideSelector) {
198 // insert the selector column
201 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
202 get : function(rowIdx, item) { if(item) return rowIdx; },
203 width : this.selectorWidth,
209 if(!this.hideLineNumber) {
210 // insert the line number column
213 get : function(rowIdx, item) { if(item) return 1 + rowIdx; },
214 width : this.lineNumberWidth,
216 nonSelectable : false
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;
231 for(var f in this.sortedFieldList) {
232 var field = this.sortedFieldList[f];
233 if(!field || field.virtual) continue;
235 // field was already added above
236 if(fields.filter(function(i){return (i.field == field.name)})[0])
239 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
241 entry.name = entry.name || field.label;
243 // unless specifically requested, hide sequence fields
244 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
247 entry = {field:field.name, name:field.label};
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
263 return [{cells: [fields]}];
266 toggleSelectAll : function() {
267 var selected = this.getSelectedRows();
268 for(var i = 0; i < this.rowCount; i++) {
276 getSelectedRows : function() {
279 dojo.query('[name=autogrid.selector]', this.domNode),
282 rows.push(input.getAttribute('row'));
288 getFirstSelectedRow : function() {
289 return this.getSelectedRows()[0];
292 getSelectedItems : function() {
295 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
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;
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;
321 * @return {Array} List of every fieldmapper object in the data store
323 getAllObjects : function() {
327 onComplete : function(list) {
330 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
339 * Deletes the underlying object for all selected rows
341 deleteSelected : function() {
342 var items = this.getSelectedItems();
343 var total = items.length;
345 dojo.require('openils.PermaCrud');
348 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
349 new openils.PermaCrud()['eliminate'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
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'";
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);
371 /* capture keydown and launch edit dialog on enter */
372 _applyEditOnEnter : function() {
373 this._applySingleEditStyle();
375 dojo.connect(this, 'onRowDblClick',
377 if(this.editStyle == 'pane')
378 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
380 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
384 dojo.connect(this, 'onKeyDown',
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);
392 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
398 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
400 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
401 var idents = grid.store.getIdentityAttributes();
404 var pane = new openils.widget.EditPane({
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]());
421 if(self.onPostUpdate)
422 self.onPostUpdate(storeItem, rowIndex);
426 grid.views.views[0].getCellNode(rowIndex, 0).focus();
433 onCancel : function() {
434 setTimeout(function(){
435 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
436 if(onCancel) onCancel();
440 if (typeof this.editPaneOnSubmit == "function")
441 pane.onSubmit = this.editPaneOnSubmit;
442 pane.fieldOrder = this.fieldOrder;
443 pane.mode = 'update';
447 _makeCreatePane : function(onPostSubmit, onCancel) {
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);
462 grid.store.newItem(fmObject.toStoreItem());
463 setTimeout(function(){
465 grid.selection.select(grid.rowCount-1);
466 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
470 onPostSubmit(fmObject);
472 onCancel : function() {
473 if(onCancel) onCancel();
476 if (typeof this.createPaneOnSubmit == "function")
477 pane.onSubmit = this.createPaneOnSubmit;
478 pane.fieldOrder = this.fieldOrder;
479 pane.mode = 'create';
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
492 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
493 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
494 var origPane = this._makeEditPane(storeItem, rowIndex);
497 dojo.forEach(origPane.fieldList,
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
511 _drawEditDialog : function(storeItem, rowIndex) {
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();
521 * Generates an EditDialog for object creation and displays it to the user
523 showCreateDialog : function() {
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();
532 _drawEditPane : function(storeItem, rowIndex) {
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);
542 showClonePane : function(onPostSubmit) {
544 var done = function() { self.hidePane(); };
547 var row = this.getFirstSelectedRow();
550 var postSubmit = (onPostSubmit) ?
551 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
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);
560 showCreatePane : function() {
561 if (this._showing_create_pane)
563 this._showing_create_pane = true;
566 var done = function() {
567 self._showing_create_pane = false;
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);
577 hideDialog : function() {
578 this.editDialog.hide();
579 this.editDialog.destroy();
580 delete this.editDialog;
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');
592 resetStore : function() {
593 this.setStore(this.buildAutoStore());
596 refresh : function() {
601 this.loadAll(this.cachedQueryOpts, this.cachedQuerySearch);
604 loadAll : function(opts, search) {
605 dojo.require('openils.PermaCrud');
606 if(this.loadProgressIndicator)
607 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
610 {limit : this.displayLimit, offset : this.displayOffset},
613 opts = dojo.mixin(opts, {
616 onresponse : function(r) {
617 var item = openils.Util.readResponse(r);
618 self.store.newItem(item.toStoreItem());
620 oncomplete : function() {
621 if(self.loadProgressIndicator)
622 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
626 this.cachedQuerySearch = search;
627 this.cachedQueryOpts = opts;
629 new openils.PermaCrud().search(this.fmClass, search, opts);
631 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
636 // static ID generater seed
637 openils.widget.AutoGrid.sequence = 0;
638 openils.widget.AutoGrid.gridCache = {};
640 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
642 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
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,
652 forceSync : true, // prevents many simultaneous requests for the same data
653 suppressLinkedFields : this.grid.suppressLinkedFields
654 },this.grid.overrideWidgetArgs[this.field]));
659 // With proper caching, this should not be necessary to prevent grid render flickering
664 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
666 node.innerHTML = ww.getDisplayString();
672 return autoWidget.getDisplayString();
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)