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,
27 selectorWidth : '1.5',
28 showColumnPicker : false,
29 columnPickerPrefix : null,
32 requiredFields : null,
33 hidePaginator : false,
34 showLoadFilter : false,
35 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
37 /* by default, don't show auto-generated (sequence) fields */
38 showSequenceFields : false,
40 startup : function() {
41 this.selectionMode = 'single';
42 this.sequence = openils.widget.AutoGrid.sequence++;
43 openils.widget.AutoGrid.gridCache[this.sequence] = this;
44 this.inherited(arguments);
46 this.attr('structure', this._compileStructure());
47 this.setStore(this.buildAutoStore());
48 this.cachedQueryOpts = {};
49 this._showing_create_pane = false;
51 if(this.showColumnPicker) {
52 if(!this.columnPickerPrefix) {
53 console.error("No columnPickerPrefix defined");
55 var picker = new openils.widget.GridColumnPicker(
56 openils.User.authtoken, this.columnPickerPrefix, this);
57 if(openils.User.authtoken) {
60 openils.Util.addOnLoad(function() { picker.load() });
65 this.overrideEditWidgets = {};
66 this.overrideEditWidgetClass = {};
67 this.overrideWidgetArgs = {};
70 this._applyEditOnEnter();
71 else if(this.singleEditStyle)
72 this._applySingleEditStyle();
74 if(!this.hideSelector) {
75 dojo.connect(this, 'onHeaderCellClick',
78 this.toggleSelectAll();
83 if(!this.hidePaginator) {
85 this.paginator = new dijit.layout.ContentPane();
88 var back = dojo.create('a', {
89 innerHTML : 'Back', // TODO i18n
90 style : 'padding-right:6px;',
91 href : 'javascript:void(0);',
92 onclick : function() {
93 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
94 if(self.displayOffset < 0)
95 self.cachedQueryOpts.offset = self.displayOffset = 0;
100 var forw = dojo.create('a', {
101 innerHTML : 'Next', // TODO i18n
102 style : 'padding-right:6px;',
103 href : 'javascript:void(0);',
104 onclick : function() {
105 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
110 dojo.place(this.paginator.domNode, this.domNode, 'before');
111 dojo.place(back, this.paginator.domNode);
112 dojo.place(forw, this.paginator.domNode);
114 if(this.showLoadFilter) {
115 dojo.require('openils.widget.PCrudFilterDialog');
118 innerHTML : 'Filter', // TODO i18n
119 style : 'padding-right:6px;',
120 href : 'javascript:void(0);',
121 onclick : function() {
122 var dialog = new openils.widget.PCrudFilterDialog({fmClass:self.fmClass})
123 dialog.onApply = function(filter) {
125 self.loadAll(self.cachedQueryOpts, filter);
131 this.paginator.domNode
136 this.loadProgressIndicator = dojo.create('img', {
137 src:'/opac/images/progressbar_green.gif', // TODO configured path
138 style:'height:16px;width:16px;'
140 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
144 hideLoadProgressIndicator : function() {
145 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
148 showLoadProgressIndicator : function() {
149 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
152 /* Don't allow sorting on the selector column */
153 canSort : function(rowIdx) {
154 if(rowIdx == 1 && !this.hideSelector)
159 _compileStructure : function() {
160 var existing = (this.structure && this.structure[0].cells[0]) ?
161 this.structure[0].cells[0] : [];
165 function pushEntry(entry) {
166 if(self.suppressFields) {
167 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
171 entry.get = openils.widget.AutoGrid.defaultGetter
172 if(!entry.width && self.defaultCellWidth)
173 entry.width = self.defaultCellWidth;
177 if(!this.hideSelector) {
178 // insert the selector column
181 formatter : function(rowIdx) { return self._formatRowSelectInput(rowIdx); },
182 get : function(rowIdx, item) { if(item) return rowIdx; },
183 width : this.selectorWidth,
190 if(!this.fieldOrder) {
191 /* no order defined, start with any explicit grid fields */
192 for(var e in existing) {
193 var entry = existing[e];
194 var field = this.fmIDL.fields.filter(
195 function(i){return (i.name == entry.field)})[0];
196 if(field) entry.name = entry.name || field.label;
201 for(var f in this.sortedFieldList) {
202 var field = this.sortedFieldList[f];
203 if(!field || field.virtual) continue;
205 // field was already added above
206 if(fields.filter(function(i){return (i.field == field.name)})[0])
209 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
211 entry.name = entry.name || field.label;
213 // unless specifically requested, hide sequence fields
214 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
217 entry = {field:field.name, name:field.label};
222 if(this.fieldOrder) {
223 /* append any explicit non-IDL grid fields to the end */
224 for(var e in existing) {
225 var entry = existing[e];
226 var field = fields.filter(
227 function(i){return (i.field == entry.field)})[0];
228 if(field) continue; // don't duplicate
233 return [{cells: [fields]}];
236 toggleSelectAll : function() {
237 var selected = this.getSelectedRows();
238 for(var i = 0; i < this.rowCount; i++) {
246 getSelectedRows : function() {
249 dojo.query('[name=autogrid.selector]', this.domNode),
252 rows.push(input.getAttribute('row'));
258 getFirstSelectedRow : function() {
259 return this.getSelectedRows()[0];
262 getSelectedItems : function() {
265 dojo.forEach(this.getSelectedRows(), function(idx) { items.push(self.getItem(idx)); });
269 selectRow : function(rowIdx) {
270 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
271 for(var i = 0; i < inputs.length; i++) {
272 if(inputs[i].getAttribute('row') == rowIdx) {
273 if(!inputs[i].disabled)
274 inputs[i].checked = true;
280 deSelectRow : function(rowIdx) {
281 var inputs = dojo.query('[name=autogrid.selector]', this.domNode);
282 for(var i = 0; i < inputs.length; i++) {
283 if(inputs[i].getAttribute('row') == rowIdx) {
284 inputs[i].checked = false;
291 * @return {Array} List of every fieldmapper object in the data store
293 getAllObjects : function() {
297 onComplete : function(list) {
300 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
309 * Deletes the underlying object for all selected rows
311 deleteSelected : function() {
312 var items = this.getSelectedItems();
313 var total = items.length;
315 dojo.require('openils.PermaCrud');
318 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
319 new openils.PermaCrud()['eliminate'](fmObject, {oncomplete : function(r) { self.store.deleteItem(item) }});
324 _formatRowSelectInput : function(rowIdx) {
325 if(rowIdx === null || rowIdx === undefined) return '';
326 var s = "<input type='checkbox' name='autogrid.selector' row='" + rowIdx + "'";
327 if(this.disableSelectorForRow && this.disableSelectorForRow(rowIdx))
328 s += " disabled='disabled'";
332 _applySingleEditStyle : function() {
333 this.onMouseOverRow = function(e) {};
334 this.onMouseOutRow = function(e) {};
335 this.onCellFocus = function(cell, rowIndex) {
336 this.selection.deselectAll();
337 this.selection.select(this.focus.rowIndex);
341 /* capture keydown and launch edit dialog on enter */
342 _applyEditOnEnter : function() {
343 this._applySingleEditStyle();
345 dojo.connect(this, 'onRowDblClick',
347 if(this.editStyle == 'pane')
348 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
350 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
354 dojo.connect(this, 'onKeyDown',
356 if(e.keyCode == dojo.keys.ENTER) {
357 this.selection.deselectAll();
358 this.selection.select(this.focus.rowIndex);
359 if(this.editStyle == 'pane')
360 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
362 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
368 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
370 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
371 var idents = grid.store.getIdentityAttributes();
374 var pane = new openils.widget.EditPane({
376 hideSaveButton : this.editReadOnly,
377 readOnly : this.editReadOnly,
378 overrideWidgets : this.overrideEditWidgets,
379 overrideWidgetClass : this.overrideEditWidgetClass,
380 overrideWidgetArgs : this.overrideWidgetArgs,
381 disableWidgetTest : this.disableWidgetTest,
382 requiredFields : this.requiredFields,
383 suppressFields : this.suppressEditFields,
384 onPostSubmit : function() {
385 for(var i in fmObject._fields) {
386 var field = fmObject._fields[i];
387 if(idents.filter(function(j){return (j == field)})[0])
388 continue; // don't try to edit an identifier field
389 grid.store.setValue(storeItem, field, fmObject[field]());
391 if(self.onPostUpdate)
392 self.onPostUpdate(storeItem, rowIndex);
396 grid.views.views[0].getCellNode(rowIndex, 0).focus();
403 onCancel : function() {
404 setTimeout(function(){
405 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
406 if(onCancel) onCancel();
410 if (typeof this.editPaneOnSubmit == "function")
411 pane.onSubmit = this.editPaneOnSubmit;
412 pane.fieldOrder = this.fieldOrder;
413 pane.mode = 'update';
417 _makeCreatePane : function(onPostSubmit, onCancel) {
419 var pane = new openils.widget.EditPane({
420 fmClass : this.fmClass,
421 overrideWidgets : this.overrideEditWidgets,
422 overrideWidgetClass : this.overrideEditWidgetClass,
423 overrideWidgetArgs : this.overrideWidgetArgs,
424 disableWidgetTest : this.disableWidgetTest,
425 requiredFields : this.requiredFields,
426 suppressFields : this.suppressEditFields,
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 if (typeof this.createPaneOnSubmit == "function")
447 pane.onSubmit = this.createPaneOnSubmit;
448 pane.fieldOrder = this.fieldOrder;
449 pane.mode = 'create';
454 * Creates an EditPane with a copy of the data from the provided store
455 * item for cloning said item
456 * @param {Object} storeItem Dojo data item
457 * @param {Number} rowIndex The Grid row index of the item to be cloned
458 * @param {Function} onPostSubmit Optional callback for post-submit behavior
459 * @param {Function} onCancel Optional callback for clone cancelation
460 * @return {Object} The clone EditPane
462 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
463 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
464 var origPane = this._makeEditPane(storeItem, rowIndex);
467 dojo.forEach(origPane.fieldList,
469 if(field.widget.widget.attr('disabled')) return;
470 var w = clonePane.fieldList.filter(
471 function(i) { return (i.name == field.name) })[0];
472 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
473 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
481 _drawEditDialog : function(storeItem, rowIndex) {
483 var done = function() { self.hideDialog(); };
484 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
485 this.editDialog = new openils.widget.EditDialog({editPane:pane});
486 this.editDialog.startup();
487 this.editDialog.show();
491 * Generates an EditDialog for object creation and displays it to the user
493 showCreateDialog : function() {
495 var done = function() { self.hideDialog(); };
496 var pane = this._makeCreatePane(done, done);
497 this.editDialog = new openils.widget.EditDialog({editPane:pane});
498 this.editDialog.startup();
499 this.editDialog.show();
502 _drawEditPane : function(storeItem, rowIndex) {
504 var done = function() { self.hidePane(); };
505 dojo.style(this.domNode, 'display', 'none');
506 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
507 this.editPane.startup();
508 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
509 if(this.onEditPane) this.onEditPane(this.editPane);
512 showClonePane : function(onPostSubmit) {
514 var done = function() { self.hidePane(); };
517 var row = this.getFirstSelectedRow();
520 var postSubmit = (onPostSubmit) ?
521 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
524 dojo.style(this.domNode, 'display', 'none');
525 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
526 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
527 if(this.onEditPane) this.onEditPane(this.editPane);
530 showCreatePane : function() {
531 if (this._showing_create_pane)
533 this._showing_create_pane = true;
536 var done = function() {
537 self._showing_create_pane = false;
540 dojo.style(this.domNode, 'display', 'none');
541 this.editPane = this._makeCreatePane(done, done);
542 this.editPane.startup();
543 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
544 if(this.onEditPane) this.onEditPane(this.editPane);
547 hideDialog : function() {
548 this.editDialog.hide();
549 this.editDialog.destroy();
550 delete this.editDialog;
554 hidePane : function() {
555 this.domNode.parentNode.removeChild(this.editPane.domNode);
556 this.editPane.destroy();
557 delete this.editPane;
558 dojo.style(this.domNode, 'display', 'block');
562 resetStore : function() {
563 this.setStore(this.buildAutoStore());
566 refresh : function() {
571 this.loadAll(this.cachedQueryOpts, this.cachedQuerySearch);
574 loadAll : function(opts, search) {
575 dojo.require('openils.PermaCrud');
576 if(this.loadProgressIndicator)
577 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
580 {limit : this.displayLimit, offset : this.displayOffset},
583 opts = dojo.mixin(opts, {
586 onresponse : function(r) {
587 var item = openils.Util.readResponse(r);
588 self.store.newItem(item.toStoreItem());
590 oncomplete : function() {
591 if(self.loadProgressIndicator)
592 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
596 this.cachedQuerySearch = search;
597 this.cachedQueryOpts = opts;
599 new openils.PermaCrud().search(this.fmClass, search, opts);
601 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
606 // static ID generater seed
607 openils.widget.AutoGrid.sequence = 0;
608 openils.widget.AutoGrid.gridCache = {};
610 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
612 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
614 if(!this.grid.overrideWidgetArgs[this.field])
615 this.grid.overrideWidgetArgs[this.field] = {};
616 var val = this.grid.store.getValue(item, this.field);
617 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
618 fmClass: this.grid.fmClass,
622 forceSync : true, // prevents many simultaneous requests for the same data
623 suppressLinkedFields : this.grid.suppressLinkedFields
624 },this.grid.overrideWidgetArgs[this.field]));
629 // With proper caching, this should not be necessary to prevent grid render flickering
634 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
636 node.innerHTML = ww.getDisplayString();
642 return autoWidget.getDisplayString();
645 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
646 if (!item) return "";
647 return fieldmapper.aou.findOrgUnit(
648 this.grid.store.getValue(item, this.field)