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 */
19 defaultCellWidth : null,
22 suppressFields : null,
24 selectorWidth : '1.5',
25 showColumnPicker : false,
26 columnPickerPrefix : null,
29 requiredFields : null,
30 showPaginator : false,
31 showLoadFilter : false,
32 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
34 /* by default, don't show auto-generated (sequence) fields */
35 showSequenceFields : false,
37 startup : function() {
38 this.selectionMode = 'single';
39 this.sequence = openils.widget.AutoGrid.sequence++;
40 openils.widget.AutoGrid.gridCache[this.sequence] = this;
41 this.inherited(arguments);
43 this.attr('structure', this._compileStructure());
44 this.setStore(this.buildAutoStore());
45 this.cachedQueryOpts = {};
47 if(this.showColumnPicker) {
48 if(!this.columnPickerPrefix) {
49 console.error("No columnPickerPrefix defined");
51 var picker = new openils.widget.GridColumnPicker(
52 openils.User.authtoken, this.columnPickerPrefix, this);
53 if(openils.User.authtoken) {
56 openils.Util.addOnLoad(function() { picker.load() });
61 this.overrideEditWidgets = {};
62 this.overrideEditWidgetClass = {};
63 this.overrideWidgetArgs = {};
66 this._applyEditOnEnter();
67 else if(this.singleEditStyle)
68 this._applySingleEditStyle();
70 if(!this.hideSelector) {
71 dojo.connect(this, 'onHeaderCellClick',
74 this.toggleSelectAll();
79 if(this.showPaginator) {
81 this.paginator = new dijit.layout.ContentPane();
84 var back = dojo.create('a', {
85 innerHTML : 'Back', // TODO i18n
86 style : 'padding-right:6px;',
87 href : 'javascript:void(0);',
88 onclick : function() {
90 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
91 if(self.displayOffset < 0)
92 self.cachedQueryOpts.offset = self.displayOffset = 0;
96 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
100 var forw = dojo.create('a', {
101 innerHTML : 'Next', // TODO i18n
102 style : 'padding-right:6px;',
103 href : 'javascript:void(0);',
104 onclick : function() {
106 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
110 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
114 dojo.place(this.paginator.domNode, this.domNode, 'before');
115 dojo.place(back, this.paginator.domNode);
116 dojo.place(forw, this.paginator.domNode);
118 if(this.showLoadFilter) {
119 dojo.require('openils.widget.PCrudFilterDialog');
122 innerHTML : 'Filter', // TODO i18n
123 style : 'padding-right:6px;',
124 href : 'javascript:void(0);',
125 onclick : function() {
126 var dialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass})
127 dialog.onApply = function(filter) {
129 self.loadAll(self.cachedQueryOpts, filter);
135 this.paginator.domNode
140 this.loadProgressIndicator = dojo.create('img', {
141 src:'/opac/images/progressbar_green.gif', // TODO configured path
142 style:'height:16px;width:16px;'
144 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
148 hideLoadProgressIndicator : function() {
149 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
152 showLoadProgressIndicator : function() {
153 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
156 /* Don't allow sorting on the selector column */
157 canSort : function(rowIdx) {
158 if(rowIdx == 1 && !this.hideSelector)
163 _compileStructure : function() {
164 var existing = (this.structure && this.structure[0].cells[0]) ?
165 this.structure[0].cells[0] : [];
169 function pushEntry(entry) {
170 if(self.suppressFields) {
171 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
175 entry.get = openils.widget.AutoGrid.defaultGetter
176 if(!entry.width && self.defaultCellWidth)
177 entry.width = self.defaultCellWidth;
181 if(!this.hideSelector) {
182 // insert the selector column
185 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
186 get : function(rowIdx, item) { if(item) return rowIdx; },
187 width : this.selectorWidth,
194 if(!this.fieldOrder) {
195 /* no order defined, start with any explicit grid fields */
196 for(var e in existing) {
197 var entry = existing[e];
198 var field = this.fmIDL.fields.filter(
199 function(i){return (i.name == entry.field)})[0];
200 if(field) entry.name = entry.name || field.label;
205 for(var f in this.sortedFieldList) {
206 var field = this.sortedFieldList[f];
207 if(!field || field.virtual) continue;
209 // field was already added above
210 if(fields.filter(function(i){return (i.field == field.name)})[0])
213 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
215 entry.name = field.label;
217 // unless specifically requested, hide sequence fields
218 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
221 entry = {field:field.name, name:field.label};
226 if(this.fieldOrder) {
227 /* append any explicit non-IDL grid fields to the end */
228 for(var e in existing) {
229 var entry = existing[e];
230 var field = fields.filter(
231 function(i){return (i.field == entry.field)})[0];
232 if(field) continue; // don't duplicate
237 return [{cells: [fields]}];
240 toggleSelectAll : function() {
241 var selected = this.getSelectedRows();
242 for(var i = 0; i < this.rowCount; i++) {
250 getSelectedRows : function() {
253 dojo.query('[name=autogrid.selector]', this.domNode),
256 rows.push(input.getAttribute('row'));
262 getFirstSelectedRow : function() {
263 return this.getSelectedRows()[0];
266 getSelectedItems : function() {
269 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
273 selectRow : function(rowIdx) {
274 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
275 for(var i = 0; i < inputs.length; i++) {
276 if(inputs[i].getAttribute('row') == rowIdx) {
277 if(!inputs[i].disabled)
278 inputs[i].checked = true;
284 deSelectRow : function(rowIdx) {
285 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
286 for(var i = 0; i < inputs.length; i++) {
287 if(inputs[i].getAttribute('row') == rowIdx) {
288 inputs[i].checked = false;
295 * @return {Array} List of every fieldmapper object in the data store
297 getAllObjects : function() {
301 onComplete : function(list) {
304 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
313 * Deletes the underlying object for all selected rows
315 deleteSelected : function() {
316 var items = this.getSelectedItems();
317 var total = items.length;
319 dojo.require('openils.PermaCrud');
322 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
323 new openils.PermaCrud()['eliminate'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
328 _formatRowSelectInput : function(rowIdx) {
329 if(rowIdx === null || rowIdx === undefined) return '';
330 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
331 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
332 s += " disabled='disabled'";
336 _applySingleEditStyle : function() {
337 this.onMouseOverRow = function(e) {};
338 this.onMouseOutRow = function(e) {};
339 this.onCellFocus = function(cell, rowIndex) {
340 this.selection.deselectAll();
341 this.selection.select(this.focus.rowIndex);
345 /* capture keydown and launch edit dialog on enter */
346 _applyEditOnEnter : function() {
347 this._applySingleEditStyle();
349 dojo.connect(this, 'onRowDblClick',
351 if(this.editStyle == 'pane')
352 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
354 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
358 dojo.connect(this, 'onKeyDown',
360 if(e.keyCode == dojo.keys.ENTER) {
361 this.selection.deselectAll();
362 this.selection.select(this.focus.rowIndex);
363 if(this.editStyle == 'pane')
364 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
366 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
372 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
374 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
375 var idents = grid.store.getIdentityAttributes();
378 var pane = new openils.widget.EditPane({
380 hideSaveButton : this.editReadOnly,
381 readOnly : this.editReadOnly,
382 overrideWidgets : this.overrideEditWidgets,
383 overrideWidgetClass : this.overrideEditWidgetClass,
384 overrideWidgetArgs : this.overrideWidgetArgs,
385 disableWidgetTest : this.disableWidgetTest,
386 requiredFields : this.requiredFields,
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 pane.fieldOrder = this.fieldOrder;
414 pane.mode = 'update';
418 _makeCreatePane : function(onPostSubmit, onCancel) {
420 var pane = new openils.widget.EditPane({
421 fmClass : this.fmClass,
422 overrideWidgets : this.overrideEditWidgets,
423 overrideWidgetClass : this.overrideEditWidgetClass,
424 overrideWidgetArgs : this.overrideWidgetArgs,
425 disableWidgetTest : this.disableWidgetTest,
426 requiredFields : this.requiredFields,
427 onPostSubmit : function(req, cudResults) {
428 var fmObject = cudResults[0];
429 if(grid.onPostCreate)
430 grid.onPostCreate(fmObject);
432 grid.store.newItem(fmObject.toStoreItem());
433 setTimeout(function(){
435 grid.selection.select(grid.rowCount-1);
436 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
440 onPostSubmit(fmObject);
442 onCancel : function() {
443 if(onCancel) onCancel();
446 pane.fieldOrder = this.fieldOrder;
447 pane.mode = 'create';
452 * Creates an EditPane with a copy of the data from the provided store
453 * item for cloning said item
454 * @param {Object} storeItem Dojo data item
455 * @param {Number} rowIndex The Grid row index of the item to be cloned
456 * @param {Function} onPostSubmit Optional callback for post-submit behavior
457 * @param {Function} onCancel Optional callback for clone cancelation
458 * @return {Object} The clone EditPane
460 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
461 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
462 var origPane = this._makeEditPane(storeItem, rowIndex);
465 dojo.forEach(origPane.fieldList,
467 if(field.widget.widget.attr('disabled')) return;
468 var w = clonePane.fieldList.filter(
469 function(i) { return (i.name == field.name) })[0];
470 w.widget.baseWidgetValue(field.widget.widgetValue); // sync widgets
471 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widgetValue)}; // async widgets
479 _drawEditDialog : function(storeItem, rowIndex) {
481 var done = function() { self.hideDialog(); };
482 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
483 this.editDialog = new openils.widget.EditDialog({editPane:pane});
484 this.editDialog.startup();
485 this.editDialog.show();
489 * Generates an EditDialog for object creation and displays it to the user
491 showCreateDialog : function() {
493 var done = function() { self.hideDialog(); };
494 var pane = this._makeCreatePane(done, done);
495 this.editDialog = new openils.widget.EditDialog({editPane:pane});
496 this.editDialog.startup();
497 this.editDialog.show();
500 _drawEditPane : function(storeItem, rowIndex) {
502 var done = function() { self.hidePane(); };
503 dojo.style(this.domNode, 'display', 'none');
504 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
505 this.editPane.startup();
506 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
507 if(this.onEditPane) this.onEditPane(this.editPane);
510 showClonePane : function(onPostSubmit) {
512 var done = function() { self.hidePane(); };
515 var row = this.getFirstSelectedRow();
518 var postSubmit = (onPostSubmit) ?
519 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
522 dojo.style(this.domNode, 'display', 'none');
523 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
524 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
525 if(this.onEditPane) this.onEditPane(this.editPane);
528 showCreatePane : function() {
530 var done = function() { self.hidePane(); };
531 dojo.style(this.domNode, 'display', 'none');
532 this.editPane = this._makeCreatePane(done, done);
533 this.editPane.startup();
534 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
535 if(this.onEditPane) this.onEditPane(this.editPane);
538 hideDialog : function() {
539 this.editDialog.hide();
540 this.editDialog.destroy();
541 delete this.editDialog;
545 hidePane : function() {
546 this.domNode.parentNode.removeChild(this.editPane.domNode);
547 this.editPane.destroy();
548 delete this.editPane;
549 dojo.style(this.domNode, 'display', 'block');
553 resetStore : function() {
554 this.setStore(this.buildAutoStore());
557 loadAll : function(opts, search) {
558 dojo.require('openils.PermaCrud');
559 if(this.loadProgressIndicator)
560 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
563 {limit : this.displayLimit, offset : this.displayOffset},
566 opts = dojo.mixin(opts, {
569 onresponse : function(r) {
570 var item = openils.Util.readResponse(r);
571 self.store.newItem(item.toStoreItem());
573 oncomplete : function() {
574 if(self.loadProgressIndicator)
575 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
579 this.cachedQuerySearch = search;
580 this.cachedQueryOpts = opts;
582 new openils.PermaCrud().search(this.fmClass, search, opts);
584 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
589 // static ID generater seed
590 openils.widget.AutoGrid.sequence = 0;
591 openils.widget.AutoGrid.gridCache = {};
593 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
595 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
597 var val = this.grid.store.getValue(item, this.field);
598 var autoWidget = new openils.widget.AutoFieldWidget({
599 fmClass: this.grid.fmClass,
603 forceSync : true, // prevents many simultaneous requests for the same data
604 suppressLinkedFields : this.grid.suppressLinkedFields
610 // With proper caching, this should not be necessary to prevent grid render flickering
615 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
617 node.innerHTML = ww.getDisplayString();
623 return autoWidget.getDisplayString();