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,
21 suppressFields : null,
23 selectorWidth : '1.5',
24 showColumnPicker : false,
25 columnPickerPrefix : null,
28 showPaginator : false,
29 showLoadFilter : false,
30 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
32 /* by default, don't show auto-generated (sequence) fields */
33 showSequenceFields : false,
35 startup : function() {
36 this.selectionMode = 'single';
37 this.sequence = openils.widget.AutoGrid.sequence++;
38 openils.widget.AutoGrid.gridCache[this.sequence] = this;
39 this.inherited(arguments);
41 this.attr('structure', this._compileStructure());
42 this.setStore(this.buildAutoStore());
43 this.cachedQueryOpts = {};
45 if(this.showColumnPicker) {
46 if(!this.columnPickerPrefix) {
47 console.error("No columnPickerPrefix defined");
49 var picker = new openils.widget.GridColumnPicker(
50 openils.User.authtoken, this.columnPickerPrefix, this);
51 if(openils.User.authtoken) {
54 openils.Util.addOnLoad(function() { picker.load() });
59 this.overrideEditWidgets = {};
60 this.overrideEditWidgetClass = {};
61 this.overrideWidgetArgs = {};
64 this._applyEditOnEnter();
65 else if(this.singleEditStyle)
66 this._applySingleEditStyle();
68 if(!this.hideSelector) {
69 dojo.connect(this, 'onHeaderCellClick',
72 this.toggleSelectAll();
77 if(this.showPaginator) {
79 this.paginator = new dijit.layout.ContentPane();
82 var back = dojo.create('a', {
83 innerHTML : 'Back', // TODO i18n
84 style : 'padding-right:6px;',
85 href : 'javascript:void(0);',
86 onclick : function() {
88 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
89 if(self.displayOffset < 0)
90 self.cachedQueryOpts.offset = self.displayOffset = 0;
94 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
98 var forw = dojo.create('a', {
99 innerHTML : 'Next', // TODO i18n
100 style : 'padding-right:6px;',
101 href : 'javascript:void(0);',
102 onclick : function() {
104 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
108 self.loadAll(self.cachedQueryOpts, self.cachedQuerySearch);
112 dojo.place(this.paginator.domNode, this.domNode, 'before');
113 dojo.place(back, this.paginator.domNode);
114 dojo.place(forw, this.paginator.domNode);
116 if(this.showLoadFilter) {
117 dojo.require('openils.widget.PCrudFilterDialog');
120 innerHTML : 'Filter', // TODO i18n
121 style : 'padding-right:6px;',
122 href : 'javascript:void(0);',
123 onclick : function() {
124 var dialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass})
125 dialog.onApply = function(filter) {
127 self.loadAll(self.cachedQueryOpts, filter);
133 this.paginator.domNode
138 this.loadProgressIndicator = dojo.create('img', {
139 src:'/opac/images/progressbar_green.gif', // TODO configured path
140 style:'height:16px;width:16px;'
142 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
146 hideLoadProgressIndicator : function() {
147 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
150 showLoadProgressIndicator : function() {
151 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
154 /* Don't allow sorting on the selector column */
155 canSort : function(rowIdx) {
156 if(rowIdx == 1 && !this.hideSelector)
161 _compileStructure : function() {
162 var existing = (this.structure && this.structure[0].cells[0]) ?
163 this.structure[0].cells[0] : [];
167 function pushEntry(entry) {
168 if(self.suppressFields) {
169 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
173 entry.get = openils.widget.AutoGrid.defaultGetter
174 if(!entry.width && self.defaultCellWidth)
175 entry.width = self.defaultCellWidth;
179 if(!this.hideSelector) {
180 // insert the selector column
183 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
184 get : function(rowIdx, item) { if(item) return rowIdx; },
185 width : this.selectorWidth,
192 if(!this.fieldOrder) {
193 /* no order defined, start with any explicit grid fields */
194 for(var e in existing) {
195 var entry = existing[e];
196 var field = this.fmIDL.fields.filter(
197 function(i){return (i.name == entry.field)})[0];
198 if(field) entry.name = entry.name || field.label;
203 for(var f in this.sortedFieldList) {
204 var field = this.sortedFieldList[f];
205 if(!field || field.virtual) continue;
207 // field was already added above
208 if(fields.filter(function(i){return (i.field == field.name)})[0])
211 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
213 entry.name = field.label;
215 // unless specifically requested, hide sequence fields
216 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
219 entry = {field:field.name, name:field.label};
224 if(this.fieldOrder) {
225 /* append any explicit non-IDL grid fields to the end */
226 for(var e in existing) {
227 var entry = existing[e];
228 var field = fields.filter(
229 function(i){return (i.field == entry.field)})[0];
230 if(field) continue; // don't duplicate
235 return [{cells: [fields]}];
238 toggleSelectAll : function() {
239 var selected = this.getSelectedRows();
240 for(var i = 0; i < this.rowCount; i++) {
248 getSelectedRows : function() {
251 dojo.query('[name=autogrid.selector]', this.domNode),
254 rows.push(input.getAttribute('row'));
260 getFirstSelectedRow : function() {
261 return this.getSelectedRows()[0];
264 getSelectedItems : function() {
267 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
271 selectRow : function(rowIdx) {
272 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
273 for(var i = 0; i < inputs.length; i++) {
274 if(inputs[i].getAttribute('row') == rowIdx) {
275 if(!inputs[i].disabled)
276 inputs[i].checked = true;
282 deSelectRow : function(rowIdx) {
283 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
284 for(var i = 0; i < inputs.length; i++) {
285 if(inputs[i].getAttribute('row') == rowIdx) {
286 inputs[i].checked = false;
293 * @return {Array} List of every fieldmapper object in the data store
295 getAllObjects : function() {
299 onComplete : function(list) {
302 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
311 * Deletes the underlying object for all selected rows
313 deleteSelected : function() {
314 var items = this.getSelectedItems();
315 var total = items.length;
317 dojo.require('openils.PermaCrud');
320 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
321 new openils.PermaCrud()['delete'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
326 _formatRowSelectInput : function(rowIdx) {
327 if(rowIdx === null || rowIdx === undefined) return '';
328 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
329 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
330 s += " disabled='disabled'";
334 _applySingleEditStyle : function() {
335 this.onMouseOverRow = function(e) {};
336 this.onMouseOutRow = function(e) {};
337 this.onCellFocus = function(cell, rowIndex) {
338 this.selection.deselectAll();
339 this.selection.select(this.focus.rowIndex);
343 /* capture keydown and launch edit dialog on enter */
344 _applyEditOnEnter : function() {
345 this._applySingleEditStyle();
347 dojo.connect(this, 'onRowDblClick',
349 if(this.editStyle == 'pane')
350 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
352 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
356 dojo.connect(this, 'onKeyDown',
358 if(e.keyCode == dojo.keys.ENTER) {
359 this.selection.deselectAll();
360 this.selection.select(this.focus.rowIndex);
361 if(this.editStyle == 'pane')
362 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
364 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
370 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
372 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
373 var idents = grid.store.getIdentityAttributes();
376 var pane = new openils.widget.EditPane({
378 overrideWidgets : this.overrideEditWidgets,
379 overrideWidgetClass : this.overrideEditWidgetClass,
380 overrideWidgetArgs : this.overrideWidgetArgs,
381 disableWidgetTest : this.disableWidgetTest,
382 onPostSubmit : function() {
383 for(var i in fmObject._fields) {
384 var field = fmObject._fields[i];
385 if(idents.filter(function(j){return (j == field)})[0])
386 continue; // don't try to edit an identifier field
387 grid.store.setValue(storeItem, field, fmObject[field]());
389 if(self.onPostUpdate)
390 self.onPostUpdate(storeItem, rowIndex);
394 grid.views.views[0].getCellNode(rowIndex, 0).focus();
401 onCancel : function() {
402 setTimeout(function(){
403 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
404 if(onCancel) onCancel();
408 pane.fieldOrder = this.fieldOrder;
409 pane.mode = 'update';
413 _makeCreatePane : function(onPostSubmit, onCancel) {
415 var pane = new openils.widget.EditPane({
416 fmClass : this.fmClass,
417 overrideWidgets : this.overrideEditWidgets,
418 overrideWidgetClass : this.overrideEditWidgetClass,
419 overrideWidgetArgs : this.overrideWidgetArgs,
420 disableWidgetTest : this.disableWidgetTest,
421 onPostSubmit : function(req, cudResults) {
422 var fmObject = cudResults[0];
423 if(grid.onPostCreate)
424 grid.onPostCreate(fmObject);
426 grid.store.newItem(fmObject.toStoreItem());
427 setTimeout(function(){
429 grid.selection.select(grid.rowCount-1);
430 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
434 onPostSubmit(fmObject);
436 onCancel : function() {
437 if(onCancel) onCancel();
440 pane.fieldOrder = this.fieldOrder;
441 pane.mode = 'create';
446 * Creates an EditPane with a copy of the data from the provided store
447 * item for cloning said item
448 * @param {Object} storeItem Dojo data item
449 * @param {Number} rowIndex The Grid row index of the item to be cloned
450 * @param {Function} onPostSubmit Optional callback for post-submit behavior
451 * @param {Function} onCancel Optional callback for clone cancelation
452 * @return {Object} The clone EditPane
454 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
455 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
456 var origPane = this._makeEditPane(storeItem, rowIndex);
459 dojo.forEach(origPane.fieldList,
461 if(field.widget.widget.attr('disabled')) return;
462 var w = clonePane.fieldList.filter(
463 function(i) { return (i.name == field.name) })[0];
464 w.widget.baseWidgetValue(field.widget.widgetValue); // sync widgets
465 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widgetValue)}; // async widgets
473 _drawEditDialog : function(storeItem, rowIndex) {
475 var done = function() { self.hideDialog(); };
476 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
477 this.editDialog = new openils.widget.EditDialog({editPane:pane});
478 this.editDialog.startup();
479 this.editDialog.show();
483 * Generates an EditDialog for object creation and displays it to the user
485 showCreateDialog : function() {
487 var done = function() { self.hideDialog(); };
488 var pane = this._makeCreatePane(done, done);
489 this.editDialog = new openils.widget.EditDialog({editPane:pane});
490 this.editDialog.startup();
491 this.editDialog.show();
494 _drawEditPane : function(storeItem, rowIndex) {
496 var done = function() { self.hidePane(); };
497 dojo.style(this.domNode, 'display', 'none');
498 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
499 this.editPane.startup();
500 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
501 if(this.onEditPane) this.onEditPane(this.editPane);
504 showClonePane : function(onPostSubmit) {
506 var done = function() { self.hidePane(); };
509 var row = this.getFirstSelectedRow();
512 var postSubmit = (onPostSubmit) ?
513 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
516 dojo.style(this.domNode, 'display', 'none');
517 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
518 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
519 if(this.onEditPane) this.onEditPane(this.editPane);
522 showCreatePane : function() {
524 var done = function() { self.hidePane(); };
525 dojo.style(this.domNode, 'display', 'none');
526 this.editPane = this._makeCreatePane(done, done);
527 this.editPane.startup();
528 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
529 if(this.onEditPane) this.onEditPane(this.editPane);
532 hideDialog : function() {
533 this.editDialog.hide();
534 this.editDialog.destroy();
535 delete this.editDialog;
539 hidePane : function() {
540 this.domNode.parentNode.removeChild(this.editPane.domNode);
541 this.editPane.destroy();
542 delete this.editPane;
543 dojo.style(this.domNode, 'display', 'block');
547 resetStore : function() {
548 this.setStore(this.buildAutoStore());
551 loadAll : function(opts, search) {
552 dojo.require('openils.PermaCrud');
553 if(this.loadProgressIndicator)
554 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
557 {limit : this.displayLimit, offset : this.displayOffset},
560 opts = dojo.mixin(opts, {
563 onresponse : function(r) {
564 var item = openils.Util.readResponse(r);
565 self.store.newItem(item.toStoreItem());
567 oncomplete : function() {
568 if(self.loadProgressIndicator)
569 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
573 this.cachedQuerySearch = search;
574 this.cachedQueryOpts = opts;
576 new openils.PermaCrud().search(this.fmClass, search, opts);
578 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
583 // static ID generater seed
584 openils.widget.AutoGrid.sequence = 0;
585 openils.widget.AutoGrid.gridCache = {};
587 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
589 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
591 var val = this.grid.store.getValue(item, this.field);
592 var autoWidget = new openils.widget.AutoFieldWidget({
593 fmClass: this.grid.fmClass,
597 forceSync : true, // prevents many simultaneous requests for the same data
598 suppressLinkedFields : this.grid.suppressLinkedFields
604 // With proper caching, this should not be necessary to prevent grid render flickering
609 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
611 node.innerHTML = ww.getDisplayString();
617 return autoWidget.getDisplayString();