]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
allow data entry of negative currency amounts
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / widget / AutoFieldWidget.js
1 if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
2     dojo.provide('openils.widget.AutoFieldWidget');
3     dojo.require('openils.Util');
4     dojo.require('openils.User');
5     dojo.require('fieldmapper.IDL');
6     dojo.require('openils.PermaCrud');
7         dojo.requireLocalization("openils.widget", "AutoFieldWidget");
8
9     dojo.declare('openils.widget.AutoFieldWidget', null, {
10
11         async : false,
12
13         /**
14          * args:
15          *  idlField -- Field description object from fieldmapper.IDL.fmclasses
16          *  fmObject -- If available, the object being edited.  This will be used 
17          *      to set the value of the widget.
18          *  fmClass -- Class name (not required if idlField or fmObject is set)
19          *  fmField -- Field name (not required if idlField)
20          *  parentNode -- If defined, the widget will be appended to this DOM node
21          *  dijitArgs -- Optional parameters object, passed directly to the dojo widget
22          *  orgLimitPerms -- If this field defines a set of org units and an orgLimitPerms 
23          *      is defined, the code will limit the org units in the set to those
24          *      allowed by the permission
25          *  orgDefaultsToWs -- If this is an org unit field and the widget has no value,
26          *      set the value equal to the users's workstation org unit.  Othwerwise, leave it null
27          *  selfReference -- The primary purpose of an AutoFieldWidget is to render the value
28          *      or widget for a field on an object (that may or may not link to another object).
29          *      selfReference allows you to sidestep the indirection and create a selector widget
30          *      based purely on an fmClass.  To get a dropdown of all of the 'abc'
31          *      objects, pass in {selfReference : true, fmClass : 'abc'}.  
32          *  labelFormat -- For widgets that are displayed as remote object filtering selects,
33          *      this provides a mechanism for overriding the label format in the filtering select.
34          *      It must be an array, whose first value is a format string, compliant with
35          *      dojo.string.substitute.  The remaining array items are the arguments to the format
36          *      represented as field names on the remote linked object.
37          *      E.g.
38          *      labelFormat : [ '${0} (${1})', 'obj_field_1', 'obj_field_2' ]
39          *      Note: this does not control the final display value.  Only values in the drop-down.
40          *      See searchFormat for controlling the display value
41          *  searchFormat -- This format controls the structure of the search attribute which
42          *      controls the text used during type-ahead searching and the displayed value in 
43          *      the filtering select.  See labelFormat for the structure.  
44          *  dataLoader : Bypass the default PermaCrud linked data fetcher and use this function instead.
45          *      Function arguments are (link class name, search filter, callback)
46          *      The fetched objects should be passed to the callback as an array
47          *  disableQuery : dojo.data query passed to FilteringTreeSelect-based widgets to disable
48          *      (but leave visible) certain options.  
49          */
50         constructor : function(args) {
51             for(var k in args)
52                 this[k] = args[k];
53
54             if (!this.dijitArgs) {
55                 this.dijitArgs = {};
56             }
57             this.dijitArgs['scrollOnFocus'] = false;
58
59
60             // find the field description in the IDL if not provided
61             if(this.fmObject) 
62                 this.fmClass = this.fmObject.classname;
63             this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
64
65             if(this.fmClass && !this.fmIDL) {
66                 fieldmapper.IDL.load([this.fmClass]);
67                 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
68             }
69
70             this.suppressLinkedFields = args.suppressLinkedFields || [];
71
72             if(this.selfReference) {
73                 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
74                 
75                 // create a mock-up of the idlField object.  
76                 this.idlField = {
77                     datatype : 'link',
78                     'class' : this.fmClass,
79                     reltype : 'has_a',
80                     key : this.fmField,
81                     name : this.fmField
82                 };
83
84             } else {
85
86                 if(!this.idlField) {
87                     this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
88                     var fields = this.fmIDL.fields;
89                     for(var f in fields) 
90                         if(fields[f].name == this.fmField)
91                             this.idlField = fields[f];
92                 }
93             }
94
95             if(!this.idlField) 
96                 throw new Error("AutoFieldWidget could not determine which " +
97                     "field to render.  We need more information. fmClass=" + 
98                     this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
99
100             this.auth = openils.User.authtoken;
101             this.cache = openils.widget.AutoFieldWidget.cache;
102             this.cache[this.auth] = this.cache[this.auth] || {};
103             this.cache[this.auth].single = this.cache[this.auth].single || {};
104             this.cache[this.auth].list = this.cache[this.auth].list || {};
105         },
106
107         /**
108          * Turn the widget-stored value into a value oils understands
109          */
110         getFormattedValue : function() {
111             var value = this.baseWidgetValue();
112             switch(this.idlField.datatype) {
113                 case 'bool':
114                     switch(value) {
115                         case 'true': return 't';
116                         case 'on': return 't';
117                         case 'false' : return 'f';
118                         case 'unset' : return null;
119                         case true : return 't';
120                         default: return 'f';
121                     }
122                 case 'timestamp':
123                     if(!value) return null;
124                     return dojo.date.stamp.toISOString(value);
125                 case 'int':
126                 case 'float':
127                 case 'money':
128                     if(isNaN(value)) value = null;
129                 default:
130                     return (value === '') ? null : value;
131             }
132         },
133
134         baseWidgetValue : function(value) {
135             var attr = (this.readOnly) ? 'content' : 'value';
136             if(arguments.length) this.widget.attr(attr, value);
137             return this.widget.attr(attr);
138         },
139         
140         /**
141          * Turn the widget-stored value into something visually suitable
142          */
143         getDisplayString : function() {
144             var value = this.widgetValue;
145             switch(this.idlField.datatype) {
146                 case 'bool':
147                     switch(value) {
148                         case 't': 
149                         case 'true': 
150                             return openils.widget.AutoFieldWidget.localeStrings.TRUE; 
151                         case 'f' : 
152                         case 'false' : 
153                             return openils.widget.AutoFieldWidget.localeStrings.FALSE;
154                         case  null :
155                         case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
156                         case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE; 
157                         default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
158                     }
159                 case 'timestamp':
160                     if (!value) return '';
161                     return openils.Util.timeStamp(
162                         value, {"formatLength": "short"}
163                     );
164                 case 'org_unit':
165                     if(value === null || value === undefined) return '';
166                     return fieldmapper.aou.findOrgUnit(value).shortname();
167                 case 'int':
168                 case 'float':
169                     if(isNaN(value)) value = 0;
170                 default:
171                     if(value === undefined || value === null)
172                         value = '';
173                     return value+'';
174             }
175         },
176
177         build : function(onload) {
178
179             if(this.widgetValue == null)
180                 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
181
182             if(this.widget) {
183                 // core widget provided for us, attach and move on
184                 if(this.parentNode) // may already be in the "right" place
185                     this.parentNode.appendChild(this.widget.domNode);
186                 if (this.shove) {
187                     if (this.shove.mode == "update")
188                         this.widget.attr("value", this.widgetValue);
189                     else
190                         this.widgetValue = this.shove.create;
191                     this._widgetLoaded();
192                 } else if (this.widget.attr("value") == null) {
193                     this._widgetLoaded();
194                 }
195                 return;
196             }
197             
198             if(!this.parentNode) // give it somewhere to live so that dojo won't complain
199                 this.parentNode = dojo.create('div');
200
201             this.onload = onload;
202
203             if(this.readOnly) {
204                 dojo.require('dijit.layout.ContentPane');
205                 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
206                 if(this.widgetValue !== null)
207                     this._tryLinkedDisplayField();
208
209             } else if(this.widgetClass) {
210                 dojo.require(this.widgetClass);
211                 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
212
213             } else {
214
215                 switch(this.idlField.datatype) {
216                     
217                     case 'id':
218                         dojo.require('dijit.form.TextBox');
219                         this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
220                         break;
221
222                     case 'org_unit':
223                         this._buildOrgSelector();
224                         break;
225
226                     case 'money':
227                         dojo.require('dijit.form.CurrencyTextBox');
228                         // plug in fix for Dojo bug http://bugs.dojotoolkit.org/ticket/9438
229                         // to allow entry of negative values; won't be needed after we upgrade
230                         // to Dojo 1.5
231                         dojo.extend(dijit.form.CurrencyTextBox, {
232                             regExpGen: function(constraints){
233                                 return this._focused ?
234                                     dojo.number.regexp(dojo.mixin(dojo.mixin(this.editOptions, constraints), {type: 'decimal'})) :
235                                     dojo.currency.regexp(constraints);
236                             }
237                         });
238                         this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
239                         break;
240
241                     case 'int':
242                         dojo.require('dijit.form.NumberTextBox');
243                         this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
244                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
245                         break;
246
247                     case 'float':
248                         dojo.require('dijit.form.NumberTextBox');
249                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
250                         break;
251
252                     case 'timestamp':
253                         dojo.require('dijit.form.DateTextBox');
254                         dojo.require('dojo.date.stamp');
255                         this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
256                         if (this.widgetValue != null) {
257                             this.widgetValue = openils.Util.timeStampAsDateObj(
258                                 this.widgetValue
259                             );
260                         }
261                         break;
262
263                     case 'bool':
264                         if(this.ternary) {
265                             dojo.require('dijit.form.FilteringSelect');
266                             var store = new dojo.data.ItemFileReadStore({
267                                 data:{
268                                     identifier : 'value',
269                                     items:[
270                                         {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
271                                         {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
272                                         {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
273                                     ]
274                                 }
275                             });
276                             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
277                             this.widget.searchAttr = this.widget.labelAttr = 'label';
278                             this.widget.valueAttr = 'value';
279                             this.widget.store = store;
280                             this.widget.startup();
281                             this.widgetValue = (this.widgetValue === null) ? 'unset' : 
282                                 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
283                         } else {
284                             dojo.require('dijit.form.CheckBox');
285                             this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
286                             this.widgetValue = openils.Util.isTrue(this.widgetValue);
287                         }
288                         break;
289
290                     case 'link':
291                         if(this._buildLinkSelector()) break;
292
293                     default:
294                         if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
295                             dojo.require('dijit.form.ValidationTextBox');
296                             this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
297                         } else {
298                             dojo.require('dijit.form.TextBox');
299                             this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
300                         }
301                 }
302             }
303
304             if(!this.async) this._widgetLoaded();
305             return this.widget;
306         },
307
308         // we want to display the value for our widget.  However, instead of displaying
309         // an ID, for exmaple, display the value for the 'selector' field on the object
310         // the ID points to
311         _tryLinkedDisplayField : function(noAsync) {
312
313             if(this.idlField.datatype == 'org_unit')
314                 return false; // we already handle org_units, no need to re-fetch
315
316             // user opted to bypass fetching this linked data
317             if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
318                 return false;
319
320             var linkInfo = this._getLinkSelector();
321             if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector)) 
322                 return false;
323             var lclass = linkInfo.linkClass;
324
325             if(lclass == 'aou') 
326                 return false;
327
328             // first try the store cache
329             var self = this;
330             if(this.cache[this.auth].list[lclass]) {
331                 var store = this.cache[this.auth].list[lclass];
332                 var query = {};
333                 query[linkInfo.vfield.name] = ''+this.widgetValue;
334                 var found = false;
335                 store.fetch({query:query, onComplete:
336                     function(list) {
337                         if(list[0]) {
338                             self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
339                             found = true;
340                         }
341                     }
342                 });
343
344                 if(found) return;
345             }
346
347             // then try the single object cache
348             if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
349                 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
350                 return;
351             }
352
353             console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
354
355             // if those fail, fetch the linked object
356             this.async = true;
357             var self = this;
358             new openils.PermaCrud().retrieve(lclass, this.widgetValue, {   
359                 async : !this.forceSync,
360                 oncomplete : function(r) {
361                     var item = openils.Util.readResponse(r);
362                     var newvalue = item[linkInfo.vfield.selector]();
363
364                     if(!self.cache[self.auth].single[lclass])
365                         self.cache[self.auth].single[lclass] = {};
366                     self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
367
368                     self.widgetValue = newvalue;
369                     self.widget.startup();
370                     self._widgetLoaded();
371                 }
372             });
373         },
374
375         _getLinkSelector : function() {
376             var linkClass = this.idlField['class'];
377             if(this.idlField.reltype != 'has_a')  return false;
378             if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
379                 fieldmapper.IDL.load([linkClass]);
380             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
381             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
382
383             var vfield;
384             var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
385
386             for(var f in rclassIdl.fields) {
387                 if(this.idlField.key == rclassIdl.fields[f].name) {
388                     vfield = rclassIdl.fields[f];
389                     break;
390                 }
391             }
392
393             if(!vfield) 
394                 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
395
396             return {
397                 linkClass : linkClass,
398                 vfield : vfield
399             };
400         },
401
402         _buildLinkSelector : function() {
403             var self = this;
404             var selectorInfo = this._getLinkSelector();
405             if(!selectorInfo) return false;
406
407             var linkClass = selectorInfo.linkClass;
408             var vfield = selectorInfo.vfield;
409
410             this.async = true;
411
412             if(linkClass == 'pgt')
413                 return this._buildPermGrpSelector();
414             if(linkClass == 'aou')
415                 return this._buildOrgSelector();
416             if(linkClass == 'acpl')
417                 return this._buildCopyLocSelector();
418             if(linkClass == 'acqpro')
419                 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
420
421
422             dojo.require('dojo.data.ItemFileReadStore');
423             dojo.require('dijit.form.FilteringSelect');
424
425             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
426             this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
427             this.widget.valueAttr = vfield.name;
428             this.widget.attr('disabled', true);
429
430             var oncomplete = function(list) {
431                 self.widget.attr('disabled', false);
432
433                 if(self.labelFormat) 
434                     self.widget.labelAttr = '_label';
435
436                 if(self.searchFormat)
437                     self.widget.searchAttr = '_search';
438
439                 function formatString(item, formatList) {
440
441                     try {
442
443                         // formatList[1..*] are names of fields.  Pull the field
444                         // values from each object to determine the values for string substitution
445                         var values = [];
446                         var format = formatList[0];
447                         for(var i = 1; i< formatList.length; i++) 
448                             values.push(item[formatList[i]]);
449
450                         return dojo.string.substitute(format, values);
451
452                     } catch(E) {
453                         throw new Error(
454                             "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
455                     }
456
457                 }
458
459                 if(list) {
460                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
461
462                     if(self.labelFormat) {
463                         dojo.forEach(storeData.data.items, 
464                             function(item) {
465                                 item._label = formatString(item, self.labelFormat);
466                             }
467                         );
468                     }
469
470                     if(self.searchFormat) {
471                         dojo.forEach(storeData.data.items, 
472                             function(item) {
473                                 item._search = formatString(item, self.searchFormat);
474                             }
475                         );
476                     }
477
478                     self.widget.store = new dojo.data.ItemFileReadStore(storeData);
479                     self.cache[self.auth].list[linkClass] = self.widget.store;
480
481                 } else {
482                     self.widget.store = self.cache[self.auth].list[linkClass];
483                 }
484
485                 self.widget.startup();
486                 self._widgetLoaded();
487             };
488
489             if(!this.noCache && this.cache[self.auth].list[linkClass]) {
490                 oncomplete();
491
492             } else {
493
494                 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
495                     this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
496
497                 if(this.dataLoader) {
498
499                     // caller provided an external function for retrieving the data
500                     this.dataLoader(linkClass, this.searchFilter, oncomplete);
501
502                 } else {
503
504                     var _cb = function(r) {
505                         oncomplete(openils.Util.readResponse(r, false, true));
506                     };
507
508                     if (this.searchFilter) {
509                         new openils.PermaCrud().search(linkClass, this.searchFilter, {
510                             async : !this.forceSync, oncomplete : _cb
511                         });
512                     } else {
513                         new openils.PermaCrud().retrieveAll(linkClass, {
514                             async : !this.forceSync, oncomplete : _cb
515                         });
516                     }
517                 }
518             }
519
520             return true;
521         },
522
523         /**
524          * For widgets that run asynchronously, provide a callback for finishing up
525          */
526         _widgetLoaded : function(value) {
527             
528             if(this.readOnly) {
529
530                 /* -------------------------------------------------------------
531                    when using widgets in a grid, the cell may dissapear, which 
532                    kills the underlying DOM node, which causes this to fail.
533                    For now, back out gracefully and let grid getters use
534                    getDisplayString() instead
535                   -------------------------------------------------------------*/
536                 try { 
537                     this.baseWidgetValue(this.getDisplayString());
538                 } catch (E) {};
539
540             } else {
541
542                 this.baseWidgetValue(this.widgetValue);
543                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
544                     this.widget.attr('disabled', true); 
545                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
546                     this.widget.attr('disabled', true); 
547             }
548             if(this.onload)
549                 this.onload(this.widget, this);
550
551             if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
552                 // a required dijit is not given any styling to indicate the value
553                 // is invalid until the user has focused the widget then left it with
554                 // invalid data.  This change tells dojo to pretend this focusing has 
555                 // already happened so we can style required widgets during page render.
556                 this.widget._hasBeenBlurred = true;
557                 this.widget.validate();
558             }
559         },
560
561         _buildOrgSelector : function() {
562             dojo.require('fieldmapper.OrgUtils');
563             dojo.require('openils.widget.FilteringTreeSelect');
564             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
565             this.widget.searchAttr = 'shortname';
566             this.widget.labelAttr = 'shortname';
567             this.widget.parentField = 'parent_ou';
568             var user = new openils.User();
569
570             if(this.widgetValue == null && this.orgDefaultsToWs) 
571                 this.widgetValue = user.user.ws_ou();
572             
573             // if we have a limit perm, find the relevent orgs (async)
574             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
575                 this.async = true;
576                 var self = this;
577                 user.getPermOrgList(this.orgLimitPerms, 
578                     function(orgList) {
579                         self.widget.tree = orgList;
580                         self.widget.startup();
581                         self._widgetLoaded();
582                     }
583                 );
584
585             } else {
586                 this.widget.tree = fieldmapper.aou.globalOrgTree;
587                 this.widget.startup();
588             }
589
590             return true;
591         },
592
593         _buildPermGrpSelector : function() {
594             dojo.require('openils.widget.FilteringTreeSelect');
595             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
596             this.widget.disableQuery = this.disableQuery;
597             this.widget.searchAttr = 'name';
598
599             if(this.cache.permGrpTree) {
600                 this.widget.tree = this.cache.permGrpTree;
601                 this.widget.startup();
602                 this._widgetLoaded();
603                 return true;
604             } 
605
606             var self = this;
607             this.async = true;
608             new openils.PermaCrud().retrieveAll('pgt', {
609                 async : !this.forceSync,
610                 oncomplete : function(r) {
611                     var list = openils.Util.readResponse(r, false, true);
612                     if(!list) return;
613                     var map = {};
614                     var root = null;
615                     for(var l in list)
616                         map[list[l].id()] = list[l];
617                     for(var l in list) {
618                         var node = list[l];
619                         var pnode = map[node.parent()];
620                         if(!pnode) {root = node; continue;}
621                         if(!pnode.children()) pnode.children([]);
622                         pnode.children().push(node);
623                     }
624                     self.widget.tree = self.cache.permGrpTree = root;
625                     self.widget.startup();
626                     self._widgetLoaded();
627                 }
628             });
629
630             return true;
631         },
632
633         _buildCopyLocSelector : function() {
634             dojo.require('dijit.form.FilteringSelect');
635             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
636             this.widget.searchAttr = this.widget.labalAttr = 'name';
637             this.widget.valueAttr = 'id';
638
639             if(this.cache.copyLocStore) {
640                 this.widget.store = this.cache.copyLocStore;
641                 this.widget.startup();
642                 this.async = false;
643                 return true;
644             } 
645
646             // my orgs
647             var ws_ou = openils.User.user.ws_ou();
648             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
649             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
650
651             var self = this;
652             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
653                 async : !this.forceSync,
654                 oncomplete : function(r) {
655                     var list = openils.Util.readResponse(r, false, true);
656                     if(!list) return;
657                     self.widget.store = 
658                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
659                     self.cache.copyLocStore = self.widget.store;
660                     self.widget.startup();
661                     self._widgetLoaded();
662                 }
663             });
664
665             return true;
666         },
667
668         _buildAutoCompleteSelector : function(linkClass, searchAttr) {
669             dojo.require("openils.widget.PCrudAutocompleteBox");
670             dojo.mixin(this.dijitArgs, {
671                 fmclass : linkClass,
672                 searchAttr : searchAttr,
673             });
674             this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
675             this._widgetLoaded();
676             return true;
677         }
678     });
679
680     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
681     openils.widget.AutoFieldWidget.cache = {};
682     openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};
683
684 }
685