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,
23 suppressEditFields : null,
25 selectorWidth : '1.5',
26 showColumnPicker : false,
27 columnPickerPrefix : null,
30 requiredFields : null,
31 hidePaginator : false,
32 showLoadFilter : false,
33 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
35 /* by default, don't show auto-generated (sequence) fields */
36 showSequenceFields : false,
38 startup : function() {
39 this.selectionMode = 'single';
40 this.sequence = openils.widget.AutoGrid.sequence++;
41 openils.widget.AutoGrid.gridCache[this.sequence] = this;
42 this.inherited(arguments);
44 this.attr('structure', this._compileStructure());
45 this.setStore(this.buildAutoStore());
46 this.cachedQueryOpts = {};
48 if(this.showColumnPicker) {
49 if(!this.columnPickerPrefix) {
50 console.error("No columnPickerPrefix defined");
52 var picker = new openils.widget.GridColumnPicker(
53 openils.User.authtoken, this.columnPickerPrefix, this);
54 if(openils.User.authtoken) {
57 openils.Util.addOnLoad(function() { picker.load() });
62 this.overrideEditWidgets = {};
63 this.overrideEditWidgetClass = {};
64 this.overrideWidgetArgs = {};
67 this._applyEditOnEnter();
68 else if(this.singleEditStyle)
69 this._applySingleEditStyle();
71 if(!this.hideSelector) {
72 dojo.connect(this, 'onHeaderCellClick',
75 this.toggleSelectAll();
80 if(!this.hidePaginator) {
82 this.paginator = new dijit.layout.ContentPane();
85 var back = dojo.create('a', {
86 innerHTML : 'Back', // TODO i18n
87 style : 'padding-right:6px;',
88 href : 'javascript:void(0);',
89 onclick : function() {
90 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
91 if(self.displayOffset < 0)
92 self.cachedQueryOpts.offset = self.displayOffset = 0;
97 var forw = dojo.create('a', {
98 innerHTML : 'Next', // TODO i18n
99 style : 'padding-right:6px;',
100 href : 'javascript:void(0);',
101 onclick : function() {
102 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
107 dojo.place(this.paginator.domNode, this.domNode, 'before');
108 dojo.place(back, this.paginator.domNode);
109 dojo.place(forw, this.paginator.domNode);
111 if(this.showLoadFilter) {
112 dojo.require('openils.widget.PCrudFilterDialog');
115 innerHTML : 'Filter', // TODO i18n
116 style : 'padding-right:6px;',
117 href : 'javascript:void(0);',
118 onclick : function() {
119 var dialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass})
120 dialog.onApply = function(filter) {
122 self.loadAll(self.cachedQueryOpts, filter);
128 this.paginator.domNode
133 this.loadProgressIndicator = dojo.create('img', {
134 src:'/opac/images/progressbar_green.gif', // TODO configured path
135 style:'height:16px;width:16px;'
137 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
141 hideLoadProgressIndicator : function() {
142 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
145 showLoadProgressIndicator : function() {
146 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
149 /* Don't allow sorting on the selector column */
150 canSort : function(rowIdx) {
151 if(rowIdx == 1 && !this.hideSelector)
156 _compileStructure : function() {
157 var existing = (this.structure && this.structure[0].cells[0]) ?
158 this.structure[0].cells[0] : [];
162 function pushEntry(entry) {
163 if(self.suppressFields) {
164 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
168 entry.get = openils.widget.AutoGrid.defaultGetter
169 if(!entry.width && self.defaultCellWidth)
170 entry.width = self.defaultCellWidth;
174 if(!this.hideSelector) {
175 // insert the selector column
178 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
179 get : function(rowIdx, item) { if(item) return rowIdx; },
180 width : this.selectorWidth,
187 if(!this.fieldOrder) {
188 /* no order defined, start with any explicit grid fields */
189 for(var e in existing) {
190 var entry = existing[e];
191 var field = this.fmIDL.fields.filter(
192 function(i){return (i.name == entry.field)})[0];
193 if(field) entry.name = entry.name || field.label;
198 for(var f in this.sortedFieldList) {
199 var field = this.sortedFieldList[f];
200 if(!field || field.virtual) continue;
202 // field was already added above
203 if(fields.filter(function(i){return (i.field == field.name)})[0])
206 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
208 entry.name = field.label;
210 // unless specifically requested, hide sequence fields
211 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
214 entry = {field:field.name, name:field.label};
219 if(this.fieldOrder) {
220 /* append any explicit non-IDL grid fields to the end */
221 for(var e in existing) {
222 var entry = existing[e];
223 var field = fields.filter(
224 function(i){return (i.field == entry.field)})[0];
225 if(field) continue; // don't duplicate
230 return [{cells: [fields]}];
233 toggleSelectAll : function() {
234 var selected = this.getSelectedRows();
235 for(var i = 0; i < this.rowCount; i++) {
243 getSelectedRows : function() {
246 dojo.query('[name=autogrid.selector]', this.domNode),
249 rows.push(input.getAttribute('row'));
255 getFirstSelectedRow : function() {
256 return this.getSelectedRows()[0];
259 getSelectedItems : function() {
262 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
266 selectRow : function(rowIdx) {
267 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
268 for(var i = 0; i < inputs.length; i++) {
269 if(inputs[i].getAttribute('row') == rowIdx) {
270 if(!inputs[i].disabled)
271 inputs[i].checked = true;
277 deSelectRow : function(rowIdx) {
278 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
279 for(var i = 0; i < inputs.length; i++) {
280 if(inputs[i].getAttribute('row') == rowIdx) {
281 inputs[i].checked = false;
288 * @return {Array} List of every fieldmapper object in the data store
290 getAllObjects : function() {
294 onComplete : function(list) {
297 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
306 * Deletes the underlying object for all selected rows
308 deleteSelected : function() {
309 var items = this.getSelectedItems();
310 var total = items.length;
312 dojo.require('openils.PermaCrud');
315 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
316 new openils.PermaCrud()['eliminate'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
321 _formatRowSelectInput : function(rowIdx) {
322 if(rowIdx === null || rowIdx === undefined) return '';
323 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
324 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
325 s += " disabled='disabled'";
329 _applySingleEditStyle : function() {
330 this.onMouseOverRow = function(e) {};
331 this.onMouseOutRow = function(e) {};
332 this.onCellFocus = function(cell, rowIndex) {
333 this.selection.deselectAll();
334 this.selection.select(this.focus.rowIndex);
338 /* capture keydown and launch edit dialog on enter */
339 _applyEditOnEnter : function() {
340 this._applySingleEditStyle();
342 dojo.connect(this, 'onRowDblClick',
344 if(this.editStyle == 'pane')
345 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
347 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
351 dojo.connect(this, 'onKeyDown',
353 if(e.keyCode == dojo.keys.ENTER) {
354 this.selection.deselectAll();
355 this.selection.select(this.focus.rowIndex);
356 if(this.editStyle == 'pane')
357 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
359 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
365 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
367 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
368 var idents = grid.store.getIdentityAttributes();
371 var pane = new openils.widget.EditPane({
373 hideSaveButton : this.editReadOnly,
374 readOnly : this.editReadOnly,
375 overrideWidgets : this.overrideEditWidgets,
376 overrideWidgetClass : this.overrideEditWidgetClass,
377 overrideWidgetArgs : this.overrideWidgetArgs,
378 disableWidgetTest : this.disableWidgetTest,
379 requiredFields : this.requiredFields,
380 suppressFields : this.suppressEditFields,
381 onPostSubmit : function() {
382 for(var i in fmObject._fields) {
383 var field = fmObject._fields[i];
384 if(idents.filter(function(j){return (j == field)})[0])
385 continue; // don't try to edit an identifier field
386 grid.store.setValue(storeItem, field, fmObject[field]());
388 if(self.onPostUpdate)
389 self.onPostUpdate(storeItem, rowIndex);
393 grid.views.views[0].getCellNode(rowIndex, 0).focus();
400 onCancel : function() {
401 setTimeout(function(){
402 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
403 if(onCancel) onCancel();
407 pane.fieldOrder = this.fieldOrder;
408 pane.mode = 'update';
412 _makeCreatePane : function(onPostSubmit, onCancel) {
414 var pane = new openils.widget.EditPane({
415 fmClass : this.fmClass,
416 overrideWidgets : this.overrideEditWidgets,
417 overrideWidgetClass : this.overrideEditWidgetClass,
418 overrideWidgetArgs : this.overrideWidgetArgs,
419 disableWidgetTest : this.disableWidgetTest,
420 requiredFields : this.requiredFields,
421 suppressFields : this.suppressEditFields,
422 onPostSubmit : function(req, cudResults) {
423 var fmObject = cudResults[0];
424 if(grid.onPostCreate)
425 grid.onPostCreate(fmObject);
427 grid.store.newItem(fmObject.toStoreItem());
428 setTimeout(function(){
430 grid.selection.select(grid.rowCount-1);
431 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
435 onPostSubmit(fmObject);
437 onCancel : function() {
438 if(onCancel) onCancel();
441 pane.fieldOrder = this.fieldOrder;
442 pane.mode = 'create';
447 * Creates an EditPane with a copy of the data from the provided store
448 * item for cloning said item
449 * @param {Object} storeItem Dojo data item
450 * @param {Number} rowIndex The Grid row index of the item to be cloned
451 * @param {Function} onPostSubmit Optional callback for post-submit behavior
452 * @param {Function} onCancel Optional callback for clone cancelation
453 * @return {Object} The clone EditPane
455 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
456 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
457 var origPane = this._makeEditPane(storeItem, rowIndex);
460 dojo.forEach(origPane.fieldList,
462 if(field.widget.widget.attr('disabled')) return;
463 var w = clonePane.fieldList.filter(
464 function(i) { return (i.name == field.name) })[0];
465 w.widget.baseWidgetValue(field.widget.widgetValue); // sync widgets
466 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widgetValue)}; // async widgets
474 _drawEditDialog : function(storeItem, rowIndex) {
476 var done = function() { self.hideDialog(); };
477 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
478 this.editDialog = new openils.widget.EditDialog({editPane:pane});
479 this.editDialog.startup();
480 this.editDialog.show();
484 * Generates an EditDialog for object creation and displays it to the user
486 showCreateDialog : function() {
488 var done = function() { self.hideDialog(); };
489 var pane = this._makeCreatePane(done, done);
490 this.editDialog = new openils.widget.EditDialog({editPane:pane});
491 this.editDialog.startup();
492 this.editDialog.show();
495 _drawEditPane : function(storeItem, rowIndex) {
497 var done = function() { self.hidePane(); };
498 dojo.style(this.domNode, 'display', 'none');
499 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
500 this.editPane.startup();
501 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
502 if(this.onEditPane) this.onEditPane(this.editPane);
505 showClonePane : function(onPostSubmit) {
507 var done = function() { self.hidePane(); };
510 var row = this.getFirstSelectedRow();
513 var postSubmit = (onPostSubmit) ?
514 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
517 dojo.style(this.domNode, 'display', 'none');
518 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
519 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
520 if(this.onEditPane) this.onEditPane(this.editPane);
523 showCreatePane : function() {
525 var done = function() { self.hidePane(); };
526 dojo.style(this.domNode, 'display', 'none');
527 this.editPane = this._makeCreatePane(done, done);
528 this.editPane.startup();
529 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
530 if(this.onEditPane) this.onEditPane(this.editPane);
533 hideDialog : function() {
534 this.editDialog.hide();
535 this.editDialog.destroy();
536 delete this.editDialog;
540 hidePane : function() {
541 this.domNode.parentNode.removeChild(this.editPane.domNode);
542 this.editPane.destroy();
543 delete this.editPane;
544 dojo.style(this.domNode, 'display', 'block');
548 resetStore : function() {
549 this.setStore(this.buildAutoStore());
552 refresh : function() {
557 this.loadAll(this.cachedQueryOpts, this.cachedQuerySearch);
560 loadAll : function(opts, search) {
561 dojo.require('openils.PermaCrud');
562 if(this.loadProgressIndicator)
563 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
566 {limit : this.displayLimit, offset : this.displayOffset},
569 opts = dojo.mixin(opts, {
572 onresponse : function(r) {
573 var item = openils.Util.readResponse(r);
574 self.store.newItem(item.toStoreItem());
576 oncomplete : function() {
577 if(self.loadProgressIndicator)
578 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
582 this.cachedQuerySearch = search;
583 this.cachedQueryOpts = opts;
585 new openils.PermaCrud().search(this.fmClass, search, opts);
587 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
592 // static ID generater seed
593 openils.widget.AutoGrid.sequence = 0;
594 openils.widget.AutoGrid.gridCache = {};
596 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
598 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
600 var val = this.grid.store.getValue(item, this.field);
601 var autoWidget = new openils.widget.AutoFieldWidget({
602 fmClass: this.grid.fmClass,
606 forceSync : true, // prevents many simultaneous requests for the same data
607 suppressLinkedFields : this.grid.suppressLinkedFields
613 // With proper caching, this should not be necessary to prevent grid render flickering
618 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
620 node.innerHTML = ww.getDisplayString();
626 return autoWidget.getDisplayString();