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');
12 dojo.requireLocalization('openils.widget', 'AutoFieldWidget');
15 'openils.widget.AutoGrid',
16 [dojox.grid.DataGrid, openils.widget.AutoWidget, openils.widget._GridHelperColumns],
19 /* if true, pop up an edit dialog when user hits Enter on a give row */
20 editPaneOnSubmit : null,
21 onPostSubmit : null, // called after any CRUD actions are complete
22 createPaneOnSubmit : null,
24 defaultCellWidth : null,
27 suppressFields : null,
28 suppressEditFields : null,
29 suppressFilterFields : null,
30 showColumnPicker : false,
31 columnPersistKey : null,
34 requiredFields : null,
35 hidePaginator : false,
36 showLoadFilter : true,
37 onItemReceived : null,
38 suppressLinkedFields : null, // list of fields whose linked display data should not be fetched from the server
40 /* by default, don't show auto-generated (sequence) fields */
41 showSequenceFields : false,
43 startup : function() {
45 this.selectionMode = 'single';
46 this.sequence = openils.widget.AutoGrid.sequence++;
47 openils.widget.AutoGrid.gridCache[this.sequence] = this;
48 this.inherited(arguments);
51 this.setStructure(this._compileStructure());
52 this._startupGridHelperColumns();
53 this.setStructure(this.structure); // required after _startupGridHelper()
55 this.setStore(this.buildAutoStore());
56 this.cachedQueryOpts = {};
57 this._showing_create_pane = false;
58 this.overrideEditWidgets = {};
59 this.overrideEditWidgetClass = {};
60 this.overrideWidgetArgs = {};
62 this.nls = dojo.i18n.getLocalization('openils.widget', 'AutoFieldWidget');
65 this._applyEditOnEnter();
66 else if(this.singleEditStyle)
67 this._applySingleEditStyle();
69 if(!this.hidePaginator) {
71 this.paginator = new dijit.layout.ContentPane();
74 var back = dojo.create('a', {
75 innerHTML : self.nls.BACK,
76 style : 'padding-right:6px;',
77 href : 'javascript:void(0);',
78 onclick : function() {
79 self.cachedQueryOpts.offset = self.displayOffset -= self.displayLimit;
80 if(self.displayOffset < 0)
81 self.cachedQueryOpts.offset = self.displayOffset = 0;
86 var forw = dojo.create('a', {
87 innerHTML : self.nls.NEXT,
88 style : 'padding-right:6px;',
89 href : 'javascript:void(0);',
90 onclick : function() {
91 self.cachedQueryOpts.offset = self.displayOffset += self.displayLimit;
96 dojo.place(this.paginator.domNode, this.domNode, 'before');
97 dojo.place(back, this.paginator.domNode);
98 dojo.place(forw, this.paginator.domNode);
100 if(this.showLoadFilter) {
101 dojo.require('openils.widget.PCrudFilterDialog');
104 innerHTML : self.nls.FILTER,
105 style : 'padding-right:6px;',
106 href : 'javascript:void(0);',
107 onclick : function() {
108 if (!self.filterDialog) {
110 self.filterDialog = new openils.widget.PCrudFilterDialog(
111 {fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
113 self.filterDialog.onApply = function(filter) {
114 self.cachedQuerySearch = filter;
117 dojo.mixin( { offset : 0 }, self.cachedQueryOpts ),
118 self.cachedQuerySearch,
123 self.filterDialog.startup();
125 self.filterDialog.show();
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 _compileStructure : function() {
150 var existing = (this.structure && this.structure[0].cells[0]) ?
151 this.structure[0].cells[0] : [];
155 function pushEntry(entry) {
156 if(self.suppressFields) {
157 if(dojo.indexOf(self.suppressFields, entry.field) != -1)
161 entry.get = openils.widget.AutoGrid.defaultGetter
162 if(!entry.width && self.defaultCellWidth)
163 entry.width = self.defaultCellWidth;
167 if(!this.fieldOrder) {
168 /* no order defined, start with any explicit grid fields */
169 for(var e in existing) {
170 var entry = existing[e];
171 var field = this.fmIDL.fields.filter(
172 function(i){return (i.name == entry.field)})[0];
173 if(field) entry.name = entry.name || field.label;
178 for(var f in this.sortedFieldList) {
179 var field = this.sortedFieldList[f];
180 if(!field || field.virtual) continue;
182 // field was already added above
183 if(fields.filter(function(i){return (i.field == field.name)})[0])
186 var entry = existing.filter(function(i){return (i.field == field.name)})[0];
188 entry.name = entry.name || field.label;
190 // unless specifically requested, hide sequence fields
191 if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
194 entry = {field:field.name, name:field.label};
199 if(this.fieldOrder) {
200 /* append any explicit non-IDL grid fields to the end */
201 for(var e in existing) {
202 var entry = existing[e];
203 var field = fields.filter(
204 function(i){return (i.field == entry.field)})[0];
205 if(field) continue; // don't duplicate
210 return [{cells: [fields]}];
214 * @return {Array} List of every fieldmapper object in the data store
216 getAllObjects : function() {
220 onComplete : function(list) {
223 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
232 * Deletes the underlying object for all selected rows
234 deleteSelected : function() {
235 var items = this.getSelectedItems();
236 var total = items.length;
238 dojo.require('openils.PermaCrud');
241 var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
242 new openils.PermaCrud()['eliminate'](
244 oncomplete : function(r) {
245 self.store.deleteItem(item);
246 if (--total < 1 && self.onPostSubmit) {
256 _applySingleEditStyle : function() {
257 this.onMouseOverRow = function(e) {};
258 this.onMouseOutRow = function(e) {};
259 this.onCellFocus = function(cell, rowIndex) {
260 this.selection.deselectAll();
261 this.selection.select(this.focus.rowIndex);
265 /* capture keydown and launch edit dialog on enter */
266 _applyEditOnEnter : function() {
267 this._applySingleEditStyle();
269 dojo.connect(this, 'onRowDblClick',
271 if(this.editStyle == 'pane')
272 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
274 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
278 dojo.connect(this, 'onKeyDown',
280 if(e.keyCode == dojo.keys.ENTER) {
281 this.selection.deselectAll();
282 this.selection.select(this.focus.rowIndex);
283 if(this.editStyle == 'pane')
284 this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
286 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
292 _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
294 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
295 var idents = grid.store.getIdentityAttributes();
297 var pane = new openils.widget.EditPane({
299 hideSaveButton : this.editReadOnly,
300 readOnly : this.editReadOnly,
301 overrideWidgets : this.overrideEditWidgets,
302 overrideWidgetClass : this.overrideEditWidgetClass,
303 overrideWidgetArgs : this.overrideWidgetArgs,
304 disableWidgetTest : this.disableWidgetTest,
305 requiredFields : this.requiredFields,
306 suppressFields : this.suppressEditFields,
307 onPostSubmit : function() {
308 for(var i in fmObject._fields) {
309 var field = fmObject._fields[i];
310 if(idents.filter(function(j){return (j == field)})[0])
311 continue; // don't try to edit an identifier field
312 grid.store.setValue(storeItem, field, fmObject[field]());
314 if(grid.onPostUpdate)
315 grid.onPostUpdate(storeItem, rowIndex);
319 grid.views.views[0].getCellNode(rowIndex, 0).focus();
325 if (grid.onPostSubmit)
329 onCancel : function() {
330 setTimeout(function(){
331 grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
332 if(onCancel) onCancel();
336 if (typeof this.editPaneOnSubmit == "function")
337 pane.onSubmit = this.editPaneOnSubmit;
338 pane.fieldOrder = this.fieldOrder;
339 pane.mode = 'update';
343 _makeCreatePane : function(onPostSubmit, onCancel) {
345 var pane = new openils.widget.EditPane({
346 fmClass : this.fmClass,
347 overrideWidgets : this.overrideEditWidgets,
348 overrideWidgetClass : this.overrideEditWidgetClass,
349 overrideWidgetArgs : this.overrideWidgetArgs,
350 disableWidgetTest : this.disableWidgetTest,
351 requiredFields : this.requiredFields,
352 suppressFields : this.suppressEditFields,
353 onPostSubmit : function(req, cudResults) {
354 var fmObject = cudResults[0];
355 if(grid.onPostCreate)
356 grid.onPostCreate(fmObject);
358 grid.store.newItem(fmObject.toStoreItem());
359 setTimeout(function(){
361 grid.selection.select(grid.rowCount-1);
362 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
366 onPostSubmit(fmObject);
367 if (grid.onPostSubmit)
370 onCancel : function() {
371 if(onCancel) onCancel();
374 if (typeof this.createPaneOnSubmit == "function")
375 pane.onSubmit = this.createPaneOnSubmit;
376 pane.fieldOrder = this.fieldOrder;
377 pane.mode = 'create';
382 * Creates an EditPane with a copy of the data from the provided store
383 * item for cloning said item
384 * @param {Object} storeItem Dojo data item
385 * @param {Number} rowIndex The Grid row index of the item to be cloned
386 * @param {Function} onPostSubmit Optional callback for post-submit behavior
387 * @param {Function} onCancel Optional callback for clone cancelation
388 * @return {Object} The clone EditPane
390 _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
391 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
392 var origPane = this._makeEditPane(storeItem, rowIndex);
395 dojo.forEach(origPane.fieldList,
397 if(field.widget.widget.attr('disabled')) return;
398 var w = clonePane.fieldList.filter(
399 function(i) { return (i.name == field.name) })[0];
400 w.widget.baseWidgetValue(field.widget.widget.attr('value')); // sync widgets
401 w.widget.onload = function(){w.widget.baseWidgetValue(field.widget.widget.attr('value'))}; // async widgets
409 _drawEditDialog : function(storeItem, rowIndex) {
411 var done = function() { self.hideDialog(); };
412 var pane = this._makeEditPane(storeItem, rowIndex, done, done);
413 this.editDialog = new openils.widget.EditDialog({editPane:pane});
414 this.editDialog.startup();
415 this.editDialog.show();
416 if(this.onEditPane) this.onEditPane(pane);
420 * Generates an EditDialog for object creation and displays it to the user
422 showCreateDialog : function() {
424 var done = function() { self.hideDialog(); };
425 var pane = this._makeCreatePane(done, done);
426 this.editDialog = new openils.widget.EditDialog({editPane:pane});
427 this.editDialog.startup();
428 this.editDialog.show();
431 _drawEditPane : function(storeItem, rowIndex) {
433 var done = function() { self.hidePane(); };
434 dojo.style(this.domNode, 'display', 'none');
435 this.editPane = this._makeEditPane(storeItem, rowIndex, done, done);
436 this.editPane.startup();
437 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
438 if(this.onEditPane) this.onEditPane(this.editPane);
441 showClonePane : function(onPostSubmit) {
443 var done = function() { self.hidePane(); };
446 var row = this.getFirstSelectedRow();
449 var postSubmit = (onPostSubmit) ?
450 function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
453 dojo.style(this.domNode, 'display', 'none');
454 this.editPane = this._makeClonePane(this.getItem(row), row, postSubmit, done);
455 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
456 if(this.onEditPane) this.onEditPane(this.editPane);
459 showCreatePane : function() {
460 if (this._showing_create_pane)
462 this._showing_create_pane = true;
465 var done = function() {
466 self._showing_create_pane = false;
469 dojo.style(this.domNode, 'display', 'none');
470 this.editPane = this._makeCreatePane(done, done);
471 this.editPane.startup();
472 this.domNode.parentNode.insertBefore(this.editPane.domNode, this.domNode);
473 if(this.onEditPane) this.onEditPane(this.editPane);
476 hideDialog : function() {
477 this.editDialog.hide();
478 this.editDialog.destroy();
479 delete this.editDialog;
483 hidePane : function() {
484 this.domNode.parentNode.removeChild(this.editPane.domNode);
485 this.editPane.destroy();
486 delete this.editPane;
487 dojo.style(this.domNode, 'display', 'block');
491 resetStore : function() {
492 this.setStore(this.buildAutoStore());
495 refresh : function(opts, search) {
496 opts = opts || this.cachedQueryOpts;
497 search = search || this.cachedQuerySearch;
502 this._loadAll(opts, search);
505 // called after a sort change occurs within the column picker
506 cpSortHandler : function(fields) {
507 console.log("AutoGrod cpSortHandler(): " + js2JSON(fields));
508 // user-defined sort handler
509 if (this.onSortChange) {
510 this.onSortChange(fields)
512 // default sort handler
514 if (!this.cachedQueryOpts)
515 this.cachedQueryOpts = {};
517 dojo.forEach(fields, function(f) {
518 if (order_by) order_by += ',';
519 order_by += f.field + ' ' + f.direction
521 this.cachedQueryOpts.order_by = {};
522 this.cachedQueryOpts.order_by[this.fmClass] = order_by;
527 loadAll : function(opts, search, filter_triggered) {
530 // first we have to load the column picker to determine the sort fields.
532 if(this.showColumnPicker && !this.columnPicker) {
533 if(!this.columnPersistKey) {
534 console.error("No columnPersistKey defined");
535 this.columnPicker = {};
537 this.columnPicker = new openils.widget.GridColumnPicker(
538 openils.User.authtoken, this.columnPersistKey, this);
539 this.columnPicker.onSortChange = function(fields) {_this.cpSortHandler(fields)};
540 this.columnPicker.onLoad = function(cpOpts) {
541 _this.cachedQueryOpts = opts;
542 _this.cachedQuerySearch = search;
543 _this.cpSortHandler(cpOpts.sortFields); // calls refresh() -> _loadAll()
545 this.columnPicker.load();
550 // column picker not wanted or already loaded
551 this._loadAll(opts, search, filter_triggered);
554 _loadAll : function(opts, search, filter_triggered) {
557 dojo.require('openils.PermaCrud');
558 if(this.loadProgressIndicator)
559 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
561 {limit : this.displayLimit, offset : this.displayOffset},
564 opts = dojo.mixin(opts, {
567 onresponse : function(r) {
568 var item = openils.Util.readResponse(r);
569 if (self.onItemReceived)
570 self.onItemReceived(item);
571 self.store.newItem(item.toStoreItem());
573 oncomplete : function() {
574 if(self.loadProgressIndicator)
575 dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
579 this.cachedQuerySearch = search;
580 this.cachedQueryOpts = opts;
582 // retain the most recent external loadAll
583 if (!filter_triggered || !this.preFilterSearch)
584 this.preFilterSearch = dojo.clone( this.cachedQuerySearch );
587 new openils.PermaCrud().search(this.fmClass, search, opts);
589 new openils.PermaCrud().retrieveAll(this.fmClass, opts);
594 // static ID generater seed
595 openils.widget.AutoGrid.sequence = 0;
596 openils.widget.AutoGrid.gridCache = {};
598 openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
600 openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
602 if(!this.grid.overrideWidgetArgs[this.field])
603 this.grid.overrideWidgetArgs[this.field] = {};
604 var val = this.grid.store.getValue(item, this.field);
605 var autoWidget = new openils.widget.AutoFieldWidget(dojo.mixin({
606 fmClass: this.grid.fmClass,
610 forceSync : true, // prevents many simultaneous requests for the same data
611 suppressLinkedFields : this.grid.suppressLinkedFields
612 },this.grid.overrideWidgetArgs[this.field]));
617 // With proper caching, this should not be necessary to prevent grid render flickering
622 var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
624 node.innerHTML = ww.getDisplayString();
630 return autoWidget.getDisplayString();
633 openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
634 if (!item) return "";
636 var aou_id = this.grid.store.getValue(item, this.field);
638 return fieldmapper.aou.findOrgUnit(aou_id).shortname();