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 selectorWidth : '1.5',
29 showColumnPicker : false,
30 columnPickerPrefix : null,
33 requiredFields : null,
34 hidePaginator : false,
35 showLoadFilter : false,
36 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
38 /* by default, don't show auto-generated (sequence) fields */
39 showSequenceFields : false,
41 startup : function() {
42 this.selectionMode = 'single';
43 this.sequence = openils.widget.AutoGrid.sequence++;
44 openils.widget.AutoGrid.gridCache[this.sequence] = this;
45 this.inherited(arguments);
47 this.attr('structure', this._compileStructure());
48 this.setStore(this.buildAutoStore());
49 this.cachedQueryOpts = {};
50 this._showing_create_pane = false;
52 if(this.showColumnPicker) {
53 if(!this.columnPickerPrefix) {
54 console.error("No columnPickerPrefix defined");
56 var picker = new openils.widget.GridColumnPicker(
57 openils.User.authtoken, this.columnPickerPrefix, this);
58 if(openils.User.authtoken) {
61 openils.Util.addOnLoad(function() { picker.load() });
66 this.overrideEditWidgets = {};
67 this.overrideEditWidgetClass = {};
68 this.overrideWidgetArgs = {};
71 this._applyEditOnEnter();
72 else if(this.singleEditStyle)
73 this._applySingleEditStyle();
75 if(!this.hideSelector) {
76 dojo.connect(this, 'onHeaderCellClick',
79 this.toggleSelectAll();
84 if(!this.hidePaginator) {
86 this.paginator = new dijit.layout.ContentPane();
89 var back = dojo.create('a', {
90 innerHTML : 'Back', // TODO i18n
91 style : 'padding-right:6px;',
92 href : 'javascript:void(0);',
93 onclick : function() {
94 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
95 if(self.displayOffset < 0)
96 self.cachedQueryOpts.offset = self.displayOffset = 0;
101 var forw = dojo.create('a', {
102 innerHTML : 'Next', // TODO i18n
103 style : 'padding-right:6px;',
104 href : 'javascript:void(0);',
105 onclick : function() {
106 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
111 dojo.place(this.paginator.domNode, this.domNode, 'before');
112 dojo.place(back, this.paginator.domNode);
113 dojo.place(forw, this.paginator.domNode);
115 if(this.showLoadFilter) {
116 dojo.require('openils.widget.PCrudFilterDialog');
119 innerHTML : 'Filter', // TODO i18n
120 style : 'padding-right:6px;',
121 href : 'javascript:void(0);',
122 onclick : function() {
123 if (!self.filterDialog) {
124 self.filterDialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
125 self.filterDialog.onApply = function(filter) {
127 self.loadAll(self.cachedQueryOpts, filter);
129 self.filterDialog.startup();
131 self.filterDialog.show();
134 this.paginator.domNode
139 this.loadProgressIndicator = dojo.create('img', {
140 src:'/opac/images/progressbar_green.gif', // TODO configured path
141 style:'height:16px;width:16px;'
143 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
147 hideLoadProgressIndicator : function() {
148 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
151 showLoadProgressIndicator : function() {
152 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
155 /* Don't allow sorting on the selector column */
156 canSort : function(rowIdx) {
157 if(rowIdx == 1 && !this.hideSelector)
162 _compileStructure : function() {
163 var existing = (this.structure && this.structure[0].cells[0]) ?
164 this.structure[0].cells[0] : [];
168 function pushEntry(entry) {
169 if(self.suppressFields) {
170 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
174 entry.get = openils.widget.AutoGrid.defaultGetter
175 if(!entry.width && self.defaultCellWidth)
176 entry.width = self.defaultCellWidth;
180 if(!this.hideSelector) {
181 // insert the selector column
184 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
185 get : function(rowIdx, item) { if(item) return rowIdx; },
186 width : this.selectorWidth,
193 if(!this.fieldOrder) {
194 /* no order defined, start with any explicit grid fields */
195 for(var e in existing) {
196 var entry = existing[e];
197 var field = this.fmIDL.fields.filter(
198 function(i){return (i.name == entry.field)})[0];
199 if(field) entry.name = entry.name || field.label;
204 for(var f in this.sortedFieldList) {
205 var field = this.sortedFieldList[f];
206 if(!field || field.virtual) continue;
208 // field was already added above
209 if(fields.filter(function(i){return (i.field == field.name)})[0])
212 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
214 entry.name = entry.name || field.label;
216 // unless specifically requested, hide sequence fields
217 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
220 entry = {field:field.name, name:field.label};
225 if(this.fieldOrder) {
226 /* append any explicit non-IDL grid fields to the end */
227 for(var e in existing) {
228 var entry = existing[e];
229 var field = fields.filter(
230 function(i){return (i.field == entry.field)})[0];
231 if(field) continue; // don't duplicate
236 return [{cells: [fields]}];
239 toggleSelectAll : function() {
240 var selected = this.getSelectedRows();
241 for(var i = 0; i < this.rowCount; i++) {
249 getSelectedRows : function() {
252 dojo.query('[name=autogrid.selector]', this.domNode),
255 rows.push(input.getAttribute('row'));
261 getFirstSelectedRow : function() {
262 return this.getSelectedRows()[0];
265 getSelectedItems : function() {
268 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
272 selectRow : function(rowIdx) {
273 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
274 for(var i = 0; i < inputs.length; i++) {
275 if(inputs[i].getAttribute('row') == rowIdx) {
276 if(!inputs[i].disabled)
277 inputs[i].checked = true;
283 deSelectRow : function(rowIdx) {
284 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
285 for(var i = 0; i < inputs.length; i++) {
286 if(inputs[i].getAttribute('row') == rowIdx) {
287 inputs[i].checked = false;
294 * @return {Array} List of every fieldmapper object in the data store
296 getAllObjects : function() {
300 onComplete : function(list) {
303 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
312 * Deletes the underlying object for all selected rows
314 deleteSelected : function() {
315 var items = this.getSelectedItems();
316 var total = items.length;
318 dojo.require('openils.PermaCrud');
321 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
322 new openils.PermaCrud()['eliminate'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
327 _formatRowSelectInput : function(rowIdx) {
328 if(rowIdx === null || rowIdx === undefined) return '';
329 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
330 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
331 s += " disabled='disabled'";
335 _applySingleEditStyle : function() {
336 this.onMouseOverRow = function(e) {};
337 this.onMouseOutRow = function(e) {};
338 this.onCellFocus = function(cell, rowIndex) {
339 this.selection.deselectAll();
340 this.selection.select(this.focus.rowIndex);
344 /* capture keydown and launch edit dialog on enter */
345 _applyEditOnEnter : function() {
346 this._applySingleEditStyle();
348 dojo.connect(this, 'onRowDblClick',
350 if(this.editStyle == 'pane')
351 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
353 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
357 dojo.connect(this, 'onKeyDown',
359 if(e.keyCode == dojo.keys.ENTER) {
360 this.selection.deselectAll();
361 this.selection.select(this.focus.rowIndex);
362 if(this.editStyle == 'pane')
363 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
365 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
371 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
373 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
374 var idents = grid.store.getIdentityAttributes();
377 var pane = new openils.widget.EditPane({
379 hideSaveButton : this.editReadOnly,
380 readOnly : this.editReadOnly,
381 overrideWidgets : this.overrideEditWidgets,
382 overrideWidgetClass : this.overrideEditWidgetClass,
383 overrideWidgetArgs : this.overrideWidgetArgs,
384 disableWidgetTest : this.disableWidgetTest,
385 requiredFields : this.requiredFields,
386 suppressFields : this.suppressEditFields,
387 onPostSubmit : function() {
388 for(var i in fmObject._fields) {
389 var field = fmObject._fields[i];
390 if(idents.filter(function(j){return (j == field)})[0])
391 continue; // don't try to edit an identifier field
392 grid.store.setValue(storeItem, field, fmObject[field]());
394 if(self.onPostUpdate)
395 self.onPostUpdate(storeItem, rowIndex);
399 grid.views.views[0].getCellNode(rowIndex, 0).focus();
406 onCancel : function() {
407 setTimeout(function(){
408 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
409 if(onCancel) onCancel();
413 if (typeof this.editPaneOnSubmit == "function")
414 pane.onSubmit = this.editPaneOnSubmit;
415 pane.fieldOrder = this.fieldOrder;
416 pane.mode = 'update';
420 _makeCreatePane : function(onPostSubmit, onCancel) {
422 var pane = new openils.widget.EditPane({
423 fmClass : this.fmClass,
424 overrideWidgets : this.overrideEditWidgets,
425 overrideWidgetClass : this.overrideEditWidgetClass,
426 overrideWidgetArgs : this.overrideWidgetArgs,
427 disableWidgetTest : this.disableWidgetTest,
428 requiredFields : this.requiredFields,
429 suppressFields : this.suppressEditFields,
430 onPostSubmit : function(req, cudResults) {
431 var fmObject = cudResults[0];
432 if(grid.onPostCreate)
433 grid.onPostCreate(fmObject);
435 grid.store.newItem(fmObject.toStoreItem());
436 setTimeout(function(){
438 grid.selection.select(grid.rowCount-1);
439 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
443 onPostSubmit(fmObject);
445 onCancel : function() {
446 if(onCancel) onCancel();
449 if (typeof this.createPaneOnSubmit == "function")
450 pane.onSubmit = this.createPaneOnSubmit;
451 pane.fieldOrder = this.fieldOrder;
452 pane.mode = 'create';
457 * Creates an EditPane with a copy of the data from the provided store
458 * item for cloning said item
459 * @param {Object} storeItem Dojo data item
460 * @param {Number} rowIndex The Grid row index of the item to be cloned
461 * @param {Function} onPostSubmit Optional callback for post-submit behavior
462 * @param {Function} onCancel Optional callback for clone cancelation
463 * @return {Object} The clone EditPane
465 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
466 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
467 var origPane = this._makeEditPane(storeItem, rowIndex);
470 dojo.forEach(origPane.fieldList,
472 if(field.widget.widget.attr('disabled')) return;
473 var w = clonePane.fieldList.filter(
474 function(i) { return (i.name == field.name) })[0];
475 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
476 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
484 _drawEditDialog : function(storeItem, rowIndex) {
486 var done = function() { self.hideDialog(); };
487 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
488 this.editDialog = new openils.widget.EditDialog({editPane:pane});
489 this.editDialog.startup();
490 this.editDialog.show();
494 * Generates an EditDialog for object creation and displays it to the user
496 showCreateDialog : function() {
498 var done = function() { self.hideDialog(); };
499 var pane = this._makeCreatePane(done, done);
500 this.editDialog = new openils.widget.EditDialog({editPane:pane});
501 this.editDialog.startup();
502 this.editDialog.show();
505 _drawEditPane : function(storeItem, rowIndex) {
507 var done = function() { self.hidePane(); };
508 dojo.style(this.domNode, 'display', 'none');
509 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
510 this.editPane.startup();
511 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
512 if(this.onEditPane) this.onEditPane(this.editPane);
515 showClonePane : function(onPostSubmit) {
517 var done = function() { self.hidePane(); };
520 var row = this.getFirstSelectedRow();
523 var postSubmit = (onPostSubmit) ?
524 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
527 dojo.style(this.domNode, 'display', 'none');
528 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
529 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
530 if(this.onEditPane) this.onEditPane(this.editPane);
533 showCreatePane : function() {
534 if (this._showing_create_pane)
536 this._showing_create_pane = true;
539 var done = function() {
540 self._showing_create_pane = false;
543 dojo.style(this.domNode, 'display', 'none');
544 this.editPane = this._makeCreatePane(done, done);
545 this.editPane.startup();
546 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
547 if(this.onEditPane) this.onEditPane(this.editPane);
550 hideDialog : function() {
551 this.editDialog.hide();
552 this.editDialog.destroy();
553 delete this.editDialog;
557 hidePane : function() {
558 this.domNode.parentNode.removeChild(this.editPane.domNode);
559 this.editPane.destroy();
560 delete this.editPane;
561 dojo.style(this.domNode, 'display', 'block');
565 resetStore : function() {
566 this.setStore(this.buildAutoStore());
569 refresh : function() {
574 this.loadAll(this.cachedQueryOpts, this.cachedQuerySearch);
577 loadAll : function(opts, search) {
578 dojo.require('openils.PermaCrud');
579 if(this.loadProgressIndicator)
580 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
583 {limit : this.displayLimit, offset : this.displayOffset},
586 opts = dojo.mixin(opts, {
589 onresponse : function(r) {
590 var item = openils.Util.readResponse(r);
591 self.store.newItem(item.toStoreItem());
593 oncomplete : function() {
594 if(self.loadProgressIndicator)
595 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
599 this.cachedQuerySearch = search;
600 this.cachedQueryOpts = opts;
602 new openils.PermaCrud().search(this.fmClass, search, opts);
604 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
609 // static ID generater seed
610 openils.widget.AutoGrid.sequence = 0;
611 openils.widget.AutoGrid.gridCache = {};
613 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
615 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
617 if(!this.grid.overrideWidgetArgs[this.field])
618 this.grid.overrideWidgetArgs[this.field] = {};
619 var val = this.grid.store.getValue(item, this.field);
620 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
621 fmClass: this.grid.fmClass,
625 forceSync : true, // prevents many simultaneous requests for the same data
626 suppressLinkedFields : this.grid.suppressLinkedFields
627 },this.grid.overrideWidgetArgs[this.field]));
632 // With proper caching, this should not be necessary to prevent grid render flickering
637 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
639 node.innerHTML = ww.getDisplayString();
645 return autoWidget.getDisplayString();
648 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
649 if (!item) return "";
650 return fieldmapper.aou.findOrgUnit(
651 this.grid.store.getValue(item, this.field)