]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoGrid.js
8dac610f65826f33f4c15fc94edf4eab08b01dc1
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / widget / AutoGrid.js
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');
15
16     dojo.declare(
17         'openils.widget.AutoGrid',
18         [dojox.grid.DataGrid, openils.widget.AutoWidget, openils.widget._GridHelperColumns],
19         {
20
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,
25             editOnEnter : false, 
26             defaultCellWidth : null,
27             editStyle : 'dialog',
28             editReadOnly : false,
29             suppressFields : null,
30             suppressEditFields : null,
31             suppressFilterFields : null,
32             showColumnPicker : false,
33             columnPersistKey : null,
34             displayLimit : 15,
35             displayOffset : 0,
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,
42
43             /* by default, don't show auto-generated (sequence) fields */
44             showSequenceFields : false, 
45
46             startup : function() {
47                 var _this = this;
48                 this.selectionMode = 'single';
49                 this.sequence = openils.widget.AutoGrid.sequence++;
50                 openils.widget.AutoGrid.gridCache[this.sequence] = this;
51                 this.inherited(arguments);
52                 this.initAutoEnv();
53
54                 this.setStructure(this._compileStructure());
55                 this._startupGridHelperColumns();
56                 this.setStructure(this.structure); // required after _startupGridHelper()
57
58                 this.setStore(this.buildAutoStore());
59                 this.cachedQueryOpts = {};
60                 this._showing_create_pane = false;
61                 this.overrideEditWidgets = {};
62                 this.overrideEditWidgetClass = {};
63                 this.overrideWidgetArgs = {};
64
65                 this.nls = dojo.i18n.getLocalization('openils.widget', 'AutoFieldWidget');
66
67                 if(this.editOnEnter) 
68                     this._applyEditOnEnter();
69                 else if(this.singleEditStyle) 
70                     this._applySingleEditStyle();
71
72                 if(!this.hidePaginator) {
73                     var self = this;
74                     this.paginator = new dijit.layout.ContentPane();
75
76
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;
85                             self.refresh();
86                         }
87                     });
88
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;
96                             self.refresh();
97                         }
98                     });
99
100                     dojo.place(this.paginator.domNode, this.domNode, 'before');
101                     dojo.place(back, this.paginator.domNode);
102                     dojo.place(forw, this.paginator.domNode);
103
104                     if(this.showLoadFilter) {
105                         dojo.require('openils.widget.PCrudFilterDialog');
106                         dojo.place(
107                             dojo.create('a', {
108                                 innerHTML : self.nls.FILTER,
109                                 style : 'padding-right:6px;',
110                                 href : 'javascript:void(0);', 
111                                 onclick : function() { 
112                                     if (!self.filterDialog) {
113
114                                         self.filterDialog = new openils.widget.PCrudFilterDialog(
115                                             {fmClass:self.fmClass, suppressFilterFields:self.suppressFilterFields})
116
117                                         self.filterDialog.onApply = function(filter) {
118                                             if (self.urlBasedFilterPaging) { // TODO: grid config
119                                                 self.applyFilterByPage(0, filter);
120
121                                             } else {
122                                                 self.cachedQuerySearch = filter;
123                                                 self.resetStore();
124                                                 self.loadAll(
125                                                     dojo.mixin( { offset : 0 }, self.cachedQueryOpts ),
126                                                     self.cachedQuerySearch,
127                                                     true
128                                                 );
129                                             }
130                                         };
131
132                                         self.filterDialog.startup();
133                                     }
134                                     self.filterDialog.show();
135                                 }
136                             }),
137                             this.paginator.domNode
138                         );
139                     }
140
141                     // progress image
142                     this.loadProgressIndicator = dojo.create('img', {
143                         src:'/opac/images/progressbar_green.gif', // TODO configured path
144                         style:'height:16px;width:16px;'
145                     });
146                     dojo.place(this.loadProgressIndicator, this.paginator.domNode);
147                 }
148             },
149
150             applyFilterByPage : function(offset, filter) {
151                 var ops = {
152                     filter : filter, // TODO: load from query cache on offset-only change
153                     offset : offset || 0
154                 }
155
156                 var encoded = dojox.encoding.base64.encode(
157                     js2JSON(ops)
158                     .split("")
159                     .map(function(c) { return c.charCodeAt(0); })
160                 );
161
162                 var cgi = new openils.CGI();
163                 cgi.param('djgridops', encoded);
164                 location.href = cgi.url();
165             },
166
167             extractFilterByPage : function() {
168                 // these should only be extracted once per page
169                 if (this.urlFiltersExtracted) return;
170                 this.urlFiltersExtracted = true;
171
172                 var ops = new openils.CGI().param('djgridops');
173                 if (!ops) return;
174
175                 return JSON2js(
176                     dojox.encoding.base64.decode(ops)
177                     .map(function(b) {return String.fromCharCode(b)})
178                     .join("")
179                 );
180             },
181
182             hideLoadProgressIndicator : function() {
183                 dojo.style(this.loadProgressIndicator, 'visibility', 'hidden');
184             },
185
186             showLoadProgressIndicator : function() {
187                 dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
188             },
189
190             _compileStructure : function() {
191                 var existing = (this.structure && this.structure[0].cells[0]) ? 
192                     this.structure[0].cells[0] : [];
193                 var fields = [];
194
195                 var self = this;
196                 function pushEntry(entry) {
197                     if(self.suppressFields) {
198                         if(dojo.indexOf(self.suppressFields, entry.field) != -1)
199                             return;
200                     }
201                     if(!entry.get) 
202                         entry.get = openils.widget.AutoGrid.defaultGetter
203                     if(!entry.width && self.defaultCellWidth)
204                         entry.width = self.defaultCellWidth;
205                     fields.push(entry);
206                 }
207
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;
215                         pushEntry(entry);
216                     }
217                 }
218
219                 for(var f in this.sortedFieldList) {
220                     var field = this.sortedFieldList[f];
221                     if(!field || field.virtual) continue;
222                     
223                     // field was already added above
224                     if(fields.filter(function(i){return (i.field == field.name)})[0]) 
225                         continue;
226
227                     var entry = existing.filter(function(i){return (i.field == field.name)})[0];
228                     if(entry) {
229                         entry.name = entry.name || field.label;
230                     } else {
231                         // unless specifically requested, hide sequence fields
232                         if(!this.showSequenceFields && field.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence)
233                             continue; 
234
235                         entry = {field:field.name, name:field.label};
236                     }
237                     pushEntry(entry);
238                 }
239
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
247                         pushEntry(entry);
248                     }
249                 }
250
251                 return [{cells: [fields]}];
252             },
253
254             /**
255              * @return {Array} List of every fieldmapper object in the data store
256              */
257             getAllObjects : function() {
258                 var objs = [];
259                 var self = this;
260                 this.store.fetch({
261                     onComplete : function(list) {
262                         dojo.forEach(list, 
263                             function(item) {
264                                 objs.push(new fieldmapper[self.fmClass]().fromStoreItem(item));
265                             }
266                         )
267                     }
268                 });
269                 return objs;
270             },
271
272             /**
273              * Deletes the underlying object for all selected rows
274              */
275             deleteSelected : function() {
276                 var items = this.getSelectedItems();
277                 var total = items.length;
278                 var self = this;
279                 dojo.require('openils.PermaCrud');
280                 dojo.forEach(items,
281                     function(item) {
282                         var fmObject = new fieldmapper[self.fmClass]().fromStoreItem(item);
283                         new openils.PermaCrud()['eliminate'](
284                             fmObject, {
285                                 oncomplete : function(r) {
286                                     self.store.deleteItem(item);
287                                     if (--total < 1 && self.onPostSubmit) {
288                                         self.onPostSubmit();
289                                     }
290                                 }
291                             }
292                         );
293                     }
294                 );
295             },
296
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);
303                 };
304             },
305
306             /* capture keydown and launch edit dialog on enter */
307             _applyEditOnEnter : function() {
308                 this._applySingleEditStyle();
309
310                 dojo.connect(this, 'onRowDblClick',
311                     function(e) {
312                         if(this.editStyle == 'pane')
313                             this._drawEditPane(this.selection.getFirstSelected(), this.focus.rowIndex);
314                         else
315                             this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
316                     }
317                 );
318
319                 dojo.connect(this, 'onKeyDown',
320                     function(e) {
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);
326                             else
327                                 this._drawEditDialog(this.selection.getFirstSelected(), this.focus.rowIndex);
328                         }
329                     }
330                 );
331             },
332
333             _makeEditPane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
334                 var grid = this;
335                 var fmObject = new fieldmapper[this.fmClass]().fromStoreItem(storeItem);
336                 var idents = grid.store.getIdentityAttributes();
337
338                 var pane = new openils.widget.EditPane({
339                     fmObject:fmObject,
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]());
354                         }
355                         if(grid.onPostUpdate)
356                             grid.onPostUpdate(storeItem, rowIndex);
357                         setTimeout(
358                             function(){
359                                 try { 
360                                     grid.views.views[0].getCellNode(rowIndex, 0).focus(); 
361                                 } catch (E) {}
362                             },200
363                         );
364                         if(onPostSubmit) 
365                             onPostSubmit();
366                         if (grid.onPostSubmit)
367                             grid.onPostSubmit();
368
369                     },
370                     onCancel : function() {
371                         setTimeout(function(){
372                             grid.views.views[0].getCellNode(rowIndex, 0).focus();},200);
373                         if(onCancel) onCancel();
374                     }
375                 });
376
377                 if (typeof this.editPaneOnSubmit == "function")
378                     pane.onSubmit = this.editPaneOnSubmit;
379                 pane.fieldOrder = this.fieldOrder;
380                 pane.mode = 'update';
381                 return pane;
382             },
383
384             _makeCreatePane : function(onPostSubmit, onCancel) {
385                 var grid = this;
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);
398                         if(fmObject) 
399                             grid.store.newItem(fmObject.toStoreItem());
400                         setTimeout(function(){
401                             try {
402                                 grid.selection.select(grid.rowCount-1);
403                                 grid.views.views[0].getCellNode(grid.rowCount-1, 1).focus();
404                             } catch (E) {}
405                         },200);
406                         if(onPostSubmit)
407                             onPostSubmit(fmObject);
408                         if (grid.onPostSubmit)
409                             grid.onPostSubmit();
410                     },
411                     onCancel : function() {
412                         if(onCancel) onCancel();
413                     }
414                 });
415                 if (typeof this.createPaneOnSubmit == "function")
416                     pane.onSubmit = this.createPaneOnSubmit;
417                 pane.fieldOrder = this.fieldOrder;
418                 pane.mode = 'create';
419                 return pane;
420             },
421
422             /**
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
430              */
431             _makeClonePane : function(storeItem, rowIndex, onPostSubmit, onCancel) {
432                 var clonePane = this._makeCreatePane(onPostSubmit, onCancel);
433                 var origPane = this._makeEditPane(storeItem, rowIndex);
434                 clonePane.startup();
435                 origPane.startup();
436                 dojo.forEach(origPane.fieldList,
437                     function(field) {
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
443                     }
444                 );
445                 origPane.destroy();
446                 return clonePane;
447             },
448
449
450             _drawEditDialog : function(storeItem, rowIndex) {
451                 var self = this;
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);
458             },
459
460             /**
461              * Generates an EditDialog for object creation and displays it to the user
462              */
463             showCreateDialog : function() {
464                 var self = this;
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();
470             },
471
472             _drawEditPane : function(storeItem, rowIndex) {
473                 var self = this;
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);
480             },
481
482             showClonePane : function(onPostSubmit) {
483                 var self = this;
484                 var done = function() { self.hidePane(); };
485
486                                     
487                 var row = this.getFirstSelectedRow();
488                 if(!row) return;
489
490                 var postSubmit = (onPostSubmit) ? 
491                     function(result) { onPostSubmit(self.getItem(row), result); self.hidePane(); } :
492                     done;
493
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);
498             },
499
500             showCreatePane : function() {
501                 if (this._showing_create_pane)
502                     return;
503                 this._showing_create_pane = true;
504
505                 var self = this;
506                 var done = function() {
507                     self._showing_create_pane = false;
508                     self.hidePane();
509                 };
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);
515             },
516
517             hideDialog : function() {
518                 this.editDialog.hide(); 
519                 this.editDialog.destroy(); 
520                 delete this.editDialog;
521                 this.update();
522             },
523
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');
529                 this.update();
530             },
531             
532             resetStore : function() {
533                 this.setStore(this.buildAutoStore());
534             },
535
536             refresh : function(opts, search) {
537                 opts = opts || this.cachedQueryOpts;
538                 search = search || this.cachedQuerySearch;
539                 this.resetStore();
540                 if (this.dataLoader)
541                     this.dataLoader()
542                 else
543                     this._loadAll(opts, search);
544             },
545
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)
552
553                 // default sort handler
554                 } else { 
555                     if (!this.cachedQueryOpts) 
556                         this.cachedQueryOpts = {};
557                     var order_by = '';
558                     dojo.forEach(fields, function(f) {
559                         if (order_by) order_by += ',';
560                         order_by += f.field + ' ' + f.direction
561                     });
562                     this.cachedQueryOpts.order_by = {};
563                     this.cachedQueryOpts.order_by[this.fmClass] = order_by;
564                     this.refresh();
565                 }
566             },
567
568             loadAll : function(opts, search, filter_triggered) {
569                 var _this = this;
570
571                 // first we have to load the column picker to determine the sort fields.
572                
573                 if(this.showColumnPicker && !this.columnPicker) {
574                     if(!this.columnPersistKey) {
575                         console.error("No columnPersistKey defined");
576                         this.columnPicker = {};
577                     } else {
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()
585                         };
586                         this.columnPicker.load();
587                         return;
588                     }
589                 }
590
591                 // column picker not wanted or already loaded
592                 this._loadAll(opts, search, filter_triggered);
593             },
594
595             _loadAll : function(opts, search, filter_triggered) {
596                 var self = this;
597
598                 dojo.require('openils.PermaCrud');
599                 if(this.loadProgressIndicator)
600                     dojo.style(this.loadProgressIndicator, 'visibility', 'visible');
601                 opts = dojo.mixin(
602                     {limit : this.displayLimit, offset : this.displayOffset}, 
603                     opts || {}
604                 );
605                 opts = dojo.mixin(opts, {
606                     async : true,
607                     streaming : true,
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());
613                     },
614                     oncomplete : function() {
615                         if(self.loadProgressIndicator) 
616                             dojo.style(self.loadProgressIndicator, 'visibility', 'hidden');
617                     }
618                 });
619
620                 this.cachedQuerySearch = search;
621                 this.cachedQueryOpts = opts;
622
623                 // retain the most recent external loadAll 
624                 if (!filter_triggered || !this.preFilterSearch)
625                     this.preFilterSearch = dojo.clone( this.cachedQuerySearch );
626
627                 var url_ops = this.extractFilterByPage();
628                 if (url_ops) {
629                     opts.offset = url_ops.offset;
630                     search = dojo.mixin(search || {}, url_ops.filter);
631                 }
632
633                 // TODO: remove these debug lines
634                 console.log('search = ' + js2JSON(search));
635                 console.log('opts = ' + js2JSON(opts));
636
637                 if(search)
638                     new openils.PermaCrud().search(this.fmClass, search, opts);
639                 else
640                     new openils.PermaCrud().retrieveAll(this.fmClass, opts);
641             }
642         } 
643     );
644
645     // static ID generater seed
646     openils.widget.AutoGrid.sequence = 0;
647     openils.widget.AutoGrid.gridCache = {};
648
649     openils.widget.AutoGrid.markupFactory = dojox.grid.DataGrid.markupFactory;
650
651     openils.widget.AutoGrid.defaultGetter = function(rowIndex, item) {
652         if(!item) return '';
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,
658             fmField: this.field,
659             widgetValue : val,
660             readOnly : true,
661             forceSync : true, // prevents many simultaneous requests for the same data
662             suppressLinkedFields : this.grid.suppressLinkedFields
663         },this.grid.overrideWidgetArgs[this.field]));
664
665         autoWidget.build();
666
667         /*
668         // With proper caching, this should not be necessary to prevent grid render flickering
669         var _this = this;
670         autoWidget.build(
671             function(w, ww) {
672                 try {
673                     var node = _this.grid.getCell(_this.index).view.getCellNode(rowIndex, _this.index);
674                     if(node) 
675                         node.innerHTML = ww.getDisplayString();
676                 } catch(E) {}
677             }
678         );
679         */
680
681         return autoWidget.getDisplayString();
682     };
683
684     openils.widget.AutoGrid.orgUnitGetter = function(rowIndex, item) {
685         if (!item) return "";
686
687         var aou_id = this.grid.store.getValue(item, this.field);
688         if (aou_id)
689             return fieldmapper.aou.findOrgUnit(aou_id).shortname();
690         else
691             return "";
692     };
693 }
694