1 if(!dojo._hasResource['openils.widget.AutoGrid']) {
2 dojo.provide('openils.widget.AutoGrid');
3 dojo.require('dojox.grid.DataGrid');
4 dojo.require("dojox.encoding.base64");
5 dojo.require('dijit.layout.ContentPane');
6 dojo.require('openils.widget.AutoWidget');
7 dojo.require('openils.widget.AutoFieldWidget');
8 dojo.require('openils.widget.EditPane');
9 dojo.require('openils.widget.EditDialog');
10 dojo.require('openils.widget.GridColumnPicker');
11 dojo.require('openils.widget._GridHelperColumns');
12 dojo.require('openils.Util');
13 dojo.require('openils.CGI');
14 dojo.requireLocalization('openils.widget', 'AutoFieldWidget');
17 'openils.widget.AutoGrid',
18 [dojox.grid.DataGrid, openils.widget.AutoWidget, openils.widget._GridHelperColumns],
21 /* if true, pop up an edit dialog when user hits Enter on a give row */
22 editPaneOnSubmit : null,
23 onPostSubmit : null, // called after any CRUD actions are complete
24 createPaneOnSubmit : null,
26 defaultCellWidth : null,
29 suppressFields : null,
30 suppressEditFields : null,
31 suppressFilterFields : null,
32 showColumnPicker : false,
33 columnPersistKey : null,
36 requiredFields : null,
37 hidePaginator : false,
38 showLoadFilter : true,
39 onItemReceived : null,
40 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
41 urlBasedFilterPaging : false,
43 /* by default, don't show auto-generated (sequence) fields */
44 showSequenceFields : false,
46 startup : function() {
48 this.selectionMode = 'single';
49 this.sequence = openils.widget.AutoGrid.sequence++;
50 openils.widget.AutoGrid.gridCache[this.sequence] = this;
51 this.inherited(arguments);
54 this.setStructure(this._compileStructure());
55 this._startupGridHelperColumns();
56 this.setStructure(this.structure); // required after _startupGridHelper()
58 this.setStore(this.buildAutoStore());
59 this.cachedQueryOpts = {};
60 this._showing_create_pane = false;
61 this.overrideEditWidgets = {};
62 this.overrideEditWidgetClass = {};
63 this.overrideWidgetArgs = {};
65 this.nls = dojo.i18n.getLocalization('openils.widget', 'AutoFieldWidget');
68 this._applyEditOnEnter();
69 else if(this.singleEditStyle)
70 this._applySingleEditStyle();
72 if(!this.hidePaginator) {
74 this.paginator = new dijit.layout.ContentPane();
77 var back = dojo.create('a', {
78 innerHTML : self.nls.BACK,
79 style : 'padding-right:6px;',
80 href : 'javascript:void(0);',
81 onclick : function() {
82 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
83 if(self.displayOffset < 0)
84 self.cachedQueryOpts.offset = self.displayOffset = 0;
89 // TODO: support self.urlBasedFilterPaging
90 var forw = dojo.create('a', {
91 innerHTML : self.nls.NEXT,
92 style : 'padding-right:6px;',
93 href : 'javascript:void(0);',
94 onclick : function() {
95 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
100 dojo.place(this.paginator.domNode, this.domNode, 'before');
101 dojo.place(back, this.paginator.domNode);
102 dojo.place(forw, this.paginator.domNode);
104 if(this.showLoadFilter) {
105 dojo.require('openils.widget.PCrudFilterDialog');
108 innerHTML : self.nls.FILTER,
109 style : 'padding-right:6px;',
110 href : 'javascript:void(0);',
111 onclick : function() {
112 if (!self.filterDialog) {
114 self.filterDialog = new openils.widget.PCrudFilterDialog(
115 {fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
117 self.filterDialog.onApply = function(filter) {
118 if (self.urlBasedFilterPaging) { // TODO: grid config
119 self.applyFilterByPage(0, filter);
122 self.cachedQuerySearch = filter;
125 dojo.mixin( { offset : 0 }, self.cachedQueryOpts ),
126 self.cachedQuerySearch,
132 self.filterDialog.startup();
134 self.filterDialog.show();
137 this.paginator.domNode
142 this.loadProgressIndicator = dojo.create('img', {
143 src:'/opac/images/progressbar_green.gif', // TODO configured path
144 style:'height:16px;width:16px;'
146 dojo.place(this.loadProgressIndicator, this.paginator.domNode);
150 applyFilterByPage : function(offset, filter) {
152 filter : filter, // TODO: load from query cache on offset-only change
156 var encoded = dojox.encoding.base64.encode(
159 .map(function(c) { return c.charCodeAt(0); })
162 var cgi = new openils.CGI();
163 cgi.param('djgridops', encoded);
164 location.href = cgi.url();
167 extractFilterByPage : function() {
168 // these should only be extracted once per page
169 if (this.urlFiltersExtracted) return;
170 this.urlFiltersExtracted = true;
172 var ops = new openils.CGI().param('djgridops');
176 dojox.encoding.base64.decode(ops)
177 .map(function(b) {return String.fromCharCode(b)})
182 hideLoadProgressIndicator : function() {
183 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
186 showLoadProgressIndicator : function() {
187 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
190 _compileStructure : function() {
191 var existing = (this.structure && this.structure[0].cells[0]) ?
192 this.structure[0].cells[0] : [];
196 function pushEntry(entry) {
197 if(self.suppressFields) {
198 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
202 entry.get = openils.widget.AutoGrid.defaultGetter
203 if(!entry.width && self.defaultCellWidth)
204 entry.width = self.defaultCellWidth;
208 if(!this.fieldOrder) {
209 /* no order defined, start with any explicit grid fields */
210 for(var e in existing) {
211 var entry = existing[e];
212 var field = this.fmIDL.fields.filter(
213 function(i){return (i.name == entry.field)})[0];
214 if(field) entry.name = entry.name || field.label;
219 for(var f in this.sortedFieldList) {
220 var field = this.sortedFieldList[f];
221 if(!field || field.virtual) continue;
223 // field was already added above
224 if(fields.filter(function(i){return (i.field == field.name)})[0])
227 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
229 entry.name = entry.name || field.label;
231 // unless specifically requested, hide sequence fields
232 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
235 entry = {field:field.name, name:field.label};
240 if(this.fieldOrder) {
241 /* append any explicit non-IDL grid fields to the end */
242 for(var e in existing) {
243 var entry = existing[e];
244 var field = fields.filter(
245 function(i){return (i.field == entry.field)})[0];
246 if(field) continue; // don't duplicate
251 return [{cells: [fields]}];
255 * @return {Array} List of every fieldmapper object in the data store
257 getAllObjects : function() {
261 onComplete : function(list) {
264 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
273 * Deletes the underlying object for all selected rows
275 deleteSelected : function() {
276 var items = this.getSelectedItems();
277 var total = items.length;
279 dojo.require('openils.PermaCrud');
282 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
283 new openils.PermaCrud()['eliminate'](
285 oncomplete : function(r) {
286 self.store.deleteItem(item);
287 if (--total < 1 && self.onPostSubmit) {
297 _applySingleEditStyle : function() {
298 this.onMouseOverRow = function(e) {};
299 this.onMouseOutRow = function(e) {};
300 this.onCellFocus = function(cell, rowIndex) {
301 this.selection.deselectAll();
302 this.selection.select(this.focus.rowIndex);
306 /* capture keydown and launch edit dialog on enter */
307 _applyEditOnEnter : function() {
308 this._applySingleEditStyle();
310 dojo.connect(this, 'onRowDblClick',
312 if(this.editStyle == 'pane')
313 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
315 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
319 dojo.connect(this, 'onKeyDown',
321 if(e.keyCode == dojo.keys.ENTER) {
322 this.selection.deselectAll();
323 this.selection.select(this.focus.rowIndex);
324 if(this.editStyle == 'pane')
325 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
327 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
333 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
335 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
336 var idents = grid.store.getIdentityAttributes();
338 var pane = new openils.widget.EditPane({
340 hideSaveButton : this.editReadOnly,
341 readOnly : this.editReadOnly,
342 overrideWidgets : this.overrideEditWidgets,
343 overrideWidgetClass : this.overrideEditWidgetClass,
344 overrideWidgetArgs : this.overrideWidgetArgs,
345 disableWidgetTest : this.disableWidgetTest,
346 requiredFields : this.requiredFields,
347 suppressFields : this.suppressEditFields,
348 onPostSubmit : function() {
349 for(var i in fmObject._fields) {
350 var field = fmObject._fields[i];
351 if(idents.filter(function(j){return (j == field)})[0])
352 continue; // don't try to edit an identifier field
353 grid.store.setValue(storeItem, field, fmObject[field]());
355 if(grid.onPostUpdate)
356 grid.onPostUpdate(storeItem, rowIndex);
360 grid.views.views[0].getCellNode(rowIndex, 0).focus();
366 if (grid.onPostSubmit)
370 onCancel : function() {
371 setTimeout(function(){
372 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
373 if(onCancel) onCancel();
377 if (typeof this.editPaneOnSubmit == "function")
378 pane.onSubmit = this.editPaneOnSubmit;
379 pane.fieldOrder = this.fieldOrder;
380 pane.mode = 'update';
384 _makeCreatePane : function(onPostSubmit, onCancel) {
386 var pane = new openils.widget.EditPane({
387 fmClass : this.fmClass,
388 overrideWidgets : this.overrideEditWidgets,
389 overrideWidgetClass : this.overrideEditWidgetClass,
390 overrideWidgetArgs : this.overrideWidgetArgs,
391 disableWidgetTest : this.disableWidgetTest,
392 requiredFields : this.requiredFields,
393 suppressFields : this.suppressEditFields,
394 onPostSubmit : function(req, cudResults) {
395 var fmObject = cudResults[0];
396 if(grid.onPostCreate)
397 grid.onPostCreate(fmObject);
399 grid.store.newItem(fmObject.toStoreItem());
400 setTimeout(function(){
402 grid.selection.select(grid.rowCount-1);
403 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
407 onPostSubmit(fmObject);
408 if (grid.onPostSubmit)
411 onCancel : function() {
412 if(onCancel) onCancel();
415 if (typeof this.createPaneOnSubmit == "function")
416 pane.onSubmit = this.createPaneOnSubmit;
417 pane.fieldOrder = this.fieldOrder;
418 pane.mode = 'create';
423 * Creates an EditPane with a copy of the data from the provided store
424 * item for cloning said item
425 * @param {Object} storeItem Dojo data item
426 * @param {Number} rowIndex The Grid row index of the item to be cloned
427 * @param {Function} onPostSubmit Optional callback for post-submit behavior
428 * @param {Function} onCancel Optional callback for clone cancelation
429 * @return {Object} The clone EditPane
431 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
432 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
433 var origPane = this._makeEditPane(storeItem, rowIndex);
436 dojo.forEach(origPane.fieldList,
438 if(field.widget.widget.attr('disabled')) return;
439 var w = clonePane.fieldList.filter(
440 function(i) { return (i.name == field.name) })[0];
441 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
442 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
450 _drawEditDialog : function(storeItem, rowIndex) {
452 var done = function() { self.hideDialog(); };
453 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
454 this.editDialog = new openils.widget.EditDialog({editPane:pane});
455 this.editDialog.startup();
456 this.editDialog.show();
457 if(this.onEditPane) this.onEditPane(pane);
461 * Generates an EditDialog for object creation and displays it to the user
463 showCreateDialog : function() {
465 var done = function() { self.hideDialog(); };
466 var pane = this._makeCreatePane(done, done);
467 this.editDialog = new openils.widget.EditDialog({editPane:pane});
468 this.editDialog.startup();
469 this.editDialog.show();
472 _drawEditPane : function(storeItem, rowIndex) {
474 var done = function() { self.hidePane(); };
475 dojo.style(this.domNode, 'display', 'none');
476 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
477 this.editPane.startup();
478 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
479 if(this.onEditPane) this.onEditPane(this.editPane);
482 showClonePane : function(onPostSubmit) {
484 var done = function() { self.hidePane(); };
487 var row = this.getFirstSelectedRow();
490 var postSubmit = (onPostSubmit) ?
491 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
494 dojo.style(this.domNode, 'display', 'none');
495 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
496 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
497 if(this.onEditPane) this.onEditPane(this.editPane);
500 showCreatePane : function() {
501 if (this._showing_create_pane)
503 this._showing_create_pane = true;
506 var done = function() {
507 self._showing_create_pane = false;
510 dojo.style(this.domNode, 'display', 'none');
511 this.editPane = this._makeCreatePane(done, done);
512 this.editPane.startup();
513 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
514 if(this.onEditPane) this.onEditPane(this.editPane);
517 hideDialog : function() {
518 this.editDialog.hide();
519 this.editDialog.destroy();
520 delete this.editDialog;
524 hidePane : function() {
525 this.domNode.parentNode.removeChild(this.editPane.domNode);
526 this.editPane.destroy();
527 delete this.editPane;
528 dojo.style(this.domNode, 'display', 'block');
532 resetStore : function() {
533 this.setStore(this.buildAutoStore());
536 refresh : function(opts, search) {
537 opts = opts || this.cachedQueryOpts;
538 search = search || this.cachedQuerySearch;
543 this._loadAll(opts, search);
546 // called after a sort change occurs within the column picker
547 cpSortHandler : function(fields) {
548 console.log("AutoGrod cpSortHandler(): " + js2JSON(fields));
549 // user-defined sort handler
550 if (this.onSortChange) {
551 this.onSortChange(fields)
553 // default sort handler
555 if (!this.cachedQueryOpts)
556 this.cachedQueryOpts = {};
558 dojo.forEach(fields, function(f) {
559 if (order_by) order_by += ',';
560 order_by += f.field + ' ' + f.direction
562 this.cachedQueryOpts.order_by = {};
563 this.cachedQueryOpts.order_by[this.fmClass] = order_by;
568 loadAll : function(opts, search, filter_triggered) {
571 // first we have to load the column picker to determine the sort fields.
573 if(this.showColumnPicker && !this.columnPicker) {
574 if(!this.columnPersistKey) {
575 console.error("No columnPersistKey defined");
576 this.columnPicker = {};
578 this.columnPicker = new openils.widget.GridColumnPicker(
579 openils.User.authtoken, this.columnPersistKey, this);
580 this.columnPicker.onSortChange = function(fields) {_this.cpSortHandler(fields)};
581 this.columnPicker.onLoad = function(cpOpts) {
582 _this.cachedQueryOpts = opts;
583 _this.cachedQuerySearch = search;
584 _this.cpSortHandler(cpOpts.sortFields); // calls refresh() -> _loadAll()
586 this.columnPicker.load();
591 // column picker not wanted or already loaded
592 this._loadAll(opts, search, filter_triggered);
595 _loadAll : function(opts, search, filter_triggered) {
598 dojo.require('openils.PermaCrud');
599 if(this.loadProgressIndicator)
600 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
602 {limit : this.displayLimit, offset : this.displayOffset},
605 opts = dojo.mixin(opts, {
608 onresponse : function(r) {
609 var item = openils.Util.readResponse(r);
610 if (self.onItemReceived)
611 self.onItemReceived(item);
612 self.store.newItem(item.toStoreItem());
614 oncomplete : function() {
615 if(self.loadProgressIndicator)
616 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
620 this.cachedQuerySearch = search;
621 this.cachedQueryOpts = opts;
623 // retain the most recent external loadAll
624 if (!filter_triggered || !this.preFilterSearch)
625 this.preFilterSearch = dojo.clone( this.cachedQuerySearch );
627 var url_ops = this.extractFilterByPage();
629 opts.offset = url_ops.offset;
630 search = dojo.mixin(search || {}, url_ops.filter);
633 // TODO: remove these debug lines
634 console.log('search = ' + js2JSON(search));
635 console.log('opts = ' + js2JSON(opts));
638 new openils.PermaCrud().search(this.fmClass, search, opts);
640 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
645 // static ID generater seed
646 openils.widget.AutoGrid.sequence = 0;
647 openils.widget.AutoGrid.gridCache = {};
649 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
651 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
653 if(!this.grid.overrideWidgetArgs[this.field])
654 this.grid.overrideWidgetArgs[this.field] = {};
655 var val = this.grid.store.getValue(item, this.field);
656 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
657 fmClass: this.grid.fmClass,
661 forceSync : true, // prevents many simultaneous requests for the same data
662 suppressLinkedFields : this.grid.suppressLinkedFields
663 },this.grid.overrideWidgetArgs[this.field]));
668 // With proper caching, this should not be necessary to prevent grid render flickering
673 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
675 node.innerHTML = ww.getDisplayString();
681 return autoWidget.getDisplayString();
684 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
685 if (!item) return "";
687 var aou_id = this.grid.store.getValue(item, this.field);
689 return fieldmapper.aou.findOrgUnit(aou_id).shortname();