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.widget._GridHelperColumns');
11 dojo.require('openils.Util');
14 'openils.widget.AutoGrid',
15 [dojox.grid.DataGrid, openils.widget.AutoWidget, openils.widget._GridHelperColumns],
18 /* if true, pop up an edit dialog when user hits Enter on a give row */
19 editPaneOnSubmit : null,
20 onPostSubmit : null, // called after any CRUD actions are complete
21 createPaneOnSubmit : null,
23 defaultCellWidth : null,
26 suppressFields : null,
27 suppressEditFields : null,
28 suppressFilterFields : null,
29 showColumnPicker : false,
30 columnPersistKey : null,
33 requiredFields : null,
34 hidePaginator : false,
35 showLoadFilter : false,
36 onItemReceived : null,
37 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
39 /* by default, don't show auto-generated (sequence) fields */
40 showSequenceFields : false,
42 startup : function() {
44 this.selectionMode = 'single';
45 this.sequence = openils.widget.AutoGrid.sequence++;
46 openils.widget.AutoGrid.gridCache[this.sequence] = this;
47 this.inherited(arguments);
50 this.setStructure(this._compileStructure());
51 this._startupGridHelperColumns();
52 this.setStructure(this.structure); // required after _startupGridHelper()
54 this.setStore(this.buildAutoStore());
55 this.cachedQueryOpts = {};
56 this._showing_create_pane = false;
57 this.overrideEditWidgets = {};
58 this.overrideEditWidgetClass = {};
59 this.overrideWidgetArgs = {};
62 this._applyEditOnEnter();
63 else if(this.singleEditStyle)
64 this._applySingleEditStyle();
66 if(!this.hidePaginator) {
68 this.paginator = new dijit.layout.ContentPane();
71 var back = dojo.create('a', {
72 innerHTML : 'Back', // TODO i18n
73 style : 'padding-right:6px;',
74 href : 'javascript:void(0);',
75 onclick : function() {
76 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
77 if(self.displayOffset < 0)
78 self.cachedQueryOpts.offset = self.displayOffset = 0;
83 var forw = dojo.create('a', {
84 innerHTML : 'Next', // TODO i18n
85 style : 'padding-right:6px;',
86 href : 'javascript:void(0);',
87 onclick : function() {
88 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
93 dojo.place(this.paginator.domNode, this.domNode, 'before');
94 dojo.place(back, this.paginator.domNode);
95 dojo.place(forw, this.paginator.domNode);
97 if(this.showLoadFilter) {
98 dojo.require('openils.widget.PCrudFilterDialog');
101 innerHTML : 'Filter', // TODO i18n
102 style : 'padding-right:6px;',
103 href : 'javascript:void(0);',
104 onclick : function() {
105 if (!self.filterDialog) {
107 self.filterDialog = new openils.widget.PCrudFilterDialog(
108 {fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
110 self.filterDialog.onApply = function(filter) {
111 self.cachedQuerySearch = dojo.mixin( filter, self.preFilterSearch );
114 dojo.mixin( { offset : 0 }, self.cachedQueryOpts ),
115 self.cachedQuerySearch,
120 self.filterDialog.startup();
122 self.filterDialog.show();
125 this.paginator.domNode
130 this.loadProgressIndicator = dojo.create('img', {
131 src:'/opac/images/progressbar_green.gif', // TODO configured path
132 style:'height:16px;width:16px;'
134 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
138 hideLoadProgressIndicator : function() {
139 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
142 showLoadProgressIndicator : function() {
143 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
146 _compileStructure : function() {
147 var existing = (this.structure && this.structure[0].cells[0]) ?
148 this.structure[0].cells[0] : [];
152 function pushEntry(entry) {
153 if(self.suppressFields) {
154 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
158 entry.get = openils.widget.AutoGrid.defaultGetter
159 if(!entry.width && self.defaultCellWidth)
160 entry.width = self.defaultCellWidth;
164 if(!this.fieldOrder) {
165 /* no order defined, start with any explicit grid fields */
166 for(var e in existing) {
167 var entry = existing[e];
168 var field = this.fmIDL.fields.filter(
169 function(i){return (i.name == entry.field)})[0];
170 if(field) entry.name = entry.name || field.label;
175 for(var f in this.sortedFieldList) {
176 var field = this.sortedFieldList[f];
177 if(!field || field.virtual) continue;
179 // field was already added above
180 if(fields.filter(function(i){return (i.field == field.name)})[0])
183 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
185 entry.name = entry.name || field.label;
187 // unless specifically requested, hide sequence fields
188 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
191 entry = {field:field.name, name:field.label};
196 if(this.fieldOrder) {
197 /* append any explicit non-IDL grid fields to the end */
198 for(var e in existing) {
199 var entry = existing[e];
200 var field = fields.filter(
201 function(i){return (i.field == entry.field)})[0];
202 if(field) continue; // don't duplicate
207 return [{cells: [fields]}];
211 * @return {Array} List of every fieldmapper object in the data store
213 getAllObjects : function() {
217 onComplete : function(list) {
220 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
229 * Deletes the underlying object for all selected rows
231 deleteSelected : function() {
232 var items = this.getSelectedItems();
233 var total = items.length;
235 dojo.require('openils.PermaCrud');
238 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
239 new openils.PermaCrud()['eliminate'](
241 oncomplete : function(r) {
242 self.store.deleteItem(item);
243 if (--total < 1 && self.onPostSubmit) {
253 _applySingleEditStyle : function() {
254 this.onMouseOverRow = function(e) {};
255 this.onMouseOutRow = function(e) {};
256 this.onCellFocus = function(cell, rowIndex) {
257 this.selection.deselectAll();
258 this.selection.select(this.focus.rowIndex);
262 /* capture keydown and launch edit dialog on enter */
263 _applyEditOnEnter : function() {
264 this._applySingleEditStyle();
266 dojo.connect(this, 'onRowDblClick',
268 if(this.editStyle == 'pane')
269 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
271 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
275 dojo.connect(this, 'onKeyDown',
277 if(e.keyCode == dojo.keys.ENTER) {
278 this.selection.deselectAll();
279 this.selection.select(this.focus.rowIndex);
280 if(this.editStyle == 'pane')
281 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
283 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
289 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
291 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
292 var idents = grid.store.getIdentityAttributes();
294 var pane = new openils.widget.EditPane({
296 hideSaveButton : this.editReadOnly,
297 readOnly : this.editReadOnly,
298 overrideWidgets : this.overrideEditWidgets,
299 overrideWidgetClass : this.overrideEditWidgetClass,
300 overrideWidgetArgs : this.overrideWidgetArgs,
301 disableWidgetTest : this.disableWidgetTest,
302 requiredFields : this.requiredFields,
303 suppressFields : this.suppressEditFields,
304 onPostSubmit : function() {
305 for(var i in fmObject._fields) {
306 var field = fmObject._fields[i];
307 if(idents.filter(function(j){return (j == field)})[0])
308 continue; // don't try to edit an identifier field
309 grid.store.setValue(storeItem, field, fmObject[field]());
311 if(grid.onPostUpdate)
312 grid.onPostUpdate(storeItem, rowIndex);
316 grid.views.views[0].getCellNode(rowIndex, 0).focus();
322 if (grid.onPostSubmit)
326 onCancel : function() {
327 setTimeout(function(){
328 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
329 if(onCancel) onCancel();
333 if (typeof this.editPaneOnSubmit == "function")
334 pane.onSubmit = this.editPaneOnSubmit;
335 pane.fieldOrder = this.fieldOrder;
336 pane.mode = 'update';
340 _makeCreatePane : function(onPostSubmit, onCancel) {
342 var pane = new openils.widget.EditPane({
343 fmClass : this.fmClass,
344 overrideWidgets : this.overrideEditWidgets,
345 overrideWidgetClass : this.overrideEditWidgetClass,
346 overrideWidgetArgs : this.overrideWidgetArgs,
347 disableWidgetTest : this.disableWidgetTest,
348 requiredFields : this.requiredFields,
349 suppressFields : this.suppressEditFields,
350 onPostSubmit : function(req, cudResults) {
351 var fmObject = cudResults[0];
352 if(grid.onPostCreate)
353 grid.onPostCreate(fmObject);
355 grid.store.newItem(fmObject.toStoreItem());
356 setTimeout(function(){
358 grid.selection.select(grid.rowCount-1);
359 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
363 onPostSubmit(fmObject);
364 if (grid.onPostSubmit)
367 onCancel : function() {
368 if(onCancel) onCancel();
371 if (typeof this.createPaneOnSubmit == "function")
372 pane.onSubmit = this.createPaneOnSubmit;
373 pane.fieldOrder = this.fieldOrder;
374 pane.mode = 'create';
379 * Creates an EditPane with a copy of the data from the provided store
380 * item for cloning said item
381 * @param {Object} storeItem Dojo data item
382 * @param {Number} rowIndex The Grid row index of the item to be cloned
383 * @param {Function} onPostSubmit Optional callback for post-submit behavior
384 * @param {Function} onCancel Optional callback for clone cancelation
385 * @return {Object} The clone EditPane
387 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
388 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
389 var origPane = this._makeEditPane(storeItem, rowIndex);
392 dojo.forEach(origPane.fieldList,
394 if(field.widget.widget.attr('disabled')) return;
395 var w = clonePane.fieldList.filter(
396 function(i) { return (i.name == field.name) })[0];
397 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
398 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
406 _drawEditDialog : function(storeItem, rowIndex) {
408 var done = function() { self.hideDialog(); };
409 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
410 this.editDialog = new openils.widget.EditDialog({editPane:pane});
411 this.editDialog.startup();
412 this.editDialog.show();
416 * Generates an EditDialog for object creation and displays it to the user
418 showCreateDialog : function() {
420 var done = function() { self.hideDialog(); };
421 var pane = this._makeCreatePane(done, done);
422 this.editDialog = new openils.widget.EditDialog({editPane:pane});
423 this.editDialog.startup();
424 this.editDialog.show();
427 _drawEditPane : function(storeItem, rowIndex) {
429 var done = function() { self.hidePane(); };
430 dojo.style(this.domNode, 'display', 'none');
431 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
432 this.editPane.startup();
433 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
434 if(this.onEditPane) this.onEditPane(this.editPane);
437 showClonePane : function(onPostSubmit) {
439 var done = function() { self.hidePane(); };
442 var row = this.getFirstSelectedRow();
445 var postSubmit = (onPostSubmit) ?
446 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
449 dojo.style(this.domNode, 'display', 'none');
450 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
451 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
452 if(this.onEditPane) this.onEditPane(this.editPane);
455 showCreatePane : function() {
456 if (this._showing_create_pane)
458 this._showing_create_pane = true;
461 var done = function() {
462 self._showing_create_pane = false;
465 dojo.style(this.domNode, 'display', 'none');
466 this.editPane = this._makeCreatePane(done, done);
467 this.editPane.startup();
468 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
469 if(this.onEditPane) this.onEditPane(this.editPane);
472 hideDialog : function() {
473 this.editDialog.hide();
474 this.editDialog.destroy();
475 delete this.editDialog;
479 hidePane : function() {
480 this.domNode.parentNode.removeChild(this.editPane.domNode);
481 this.editPane.destroy();
482 delete this.editPane;
483 dojo.style(this.domNode, 'display', 'block');
487 resetStore : function() {
488 this.setStore(this.buildAutoStore());
491 refresh : function(opts, search) {
492 opts = opts || this.cachedQueryOpts;
493 search = search || this.cachedQuerySearch;
498 this._loadAll(opts, search);
501 // called after a sort change occurs within the column picker
502 cpSortHandler : function(fields) {
503 console.log("AutoGrod cpSortHandler(): " + js2JSON(fields));
504 // user-defined sort handler
505 if (this.onSortChange) {
506 this.onSortChange(fields)
508 // default sort handler
510 if (!this.cachedQueryOpts)
511 this.cachedQueryOpts = {};
513 dojo.forEach(fields, function(f) {
514 if (order_by) order_by += ',';
515 order_by += f.field + ' ' + f.direction
517 this.cachedQueryOpts.order_by = {};
518 this.cachedQueryOpts.order_by[this.fmClass] = order_by;
523 loadAll : function(opts, search, filter_triggered) {
526 // first we have to load the column picker to determine the sort fields.
528 if(this.showColumnPicker && !this.columnPicker) {
529 if(!this.columnPersistKey) {
530 console.error("No columnPersistKey defined");
531 this.columnPicker = {};
533 this.columnPicker = new openils.widget.GridColumnPicker(
534 openils.User.authtoken, this.columnPersistKey, this);
535 this.columnPicker.onSortChange = function(fields) {_this.cpSortHandler(fields)};
536 this.columnPicker.onLoad = function(cpOpts) {
537 _this.cachedQueryOpts = opts;
538 _this.cachedQuerySearch = search;
539 _this.cpSortHandler(cpOpts.sortFields); // calls refresh() -> _loadAll()
541 this.columnPicker.load();
546 // column picker not wanted or already loaded
547 this._loadAll(opts, search, filter_triggered);
550 _loadAll : function(opts, search, filter_triggered) {
553 dojo.require('openils.PermaCrud');
554 if(this.loadProgressIndicator)
555 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 if (self.onItemReceived)
566 self.onItemReceived(item);
567 self.store.newItem(item.toStoreItem());
569 oncomplete : function() {
570 if(self.loadProgressIndicator)
571 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
575 this.cachedQuerySearch = search;
576 this.cachedQueryOpts = opts;
578 // retain the most recent external loadAll
579 if (!filter_triggered || !this.preFilterSearch)
580 this.preFilterSearch = dojo.clone( this.cachedQuerySearch );
583 new openils.PermaCrud().search(this.fmClass, search, opts);
585 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
590 // static ID generater seed
591 openils.widget.AutoGrid.sequence = 0;
592 openils.widget.AutoGrid.gridCache = {};
594 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
596 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
598 if(!this.grid.overrideWidgetArgs[this.field])
599 this.grid.overrideWidgetArgs[this.field] = {};
600 var val = this.grid.store.getValue(item, this.field);
601 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
602 fmClass: this.grid.fmClass,
606 forceSync : true, // prevents many simultaneous requests for the same data
607 suppressLinkedFields : this.grid.suppressLinkedFields
608 },this.grid.overrideWidgetArgs[this.field]));
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();
629 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
630 if (!item) return "";
632 var aou_id = this.grid.store.getValue(item, this.field);
634 return fieldmapper.aou.findOrgUnit(aou_id).shortname();