]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
ba05bc1b972a0caa47856123b7aceaee26bf1594
[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         isRequired : function() {
178             return (
179                 !this.readOnly && (
180                     this.idlField.required || (
181                         this.dijitArgs && (
182                             this.dijitArgs.required || this.dijitArgs.regExp
183                         )
184                     )
185                 )
186             );
187         },
188
189         build : function(onload) {
190
191             if(this.widgetValue == null)
192                 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
193
194             if(this.widget) {
195                 // core widget provided for us, attach and move on
196                 if(this.parentNode) // may already be in the "right" place
197                     this.parentNode.appendChild(this.widget.domNode);
198                 if (this.shove) {
199                     if (this.shove.mode == "update") {
200                         if (this.idlField.datatype == "timestamp")
201                             this.widgetValue = openils.Util.timeStampAsDateObj(
202                                 this.widgetValue
203                             );
204                     } else {
205                         this.widgetValue = this.shove.create;
206                     }
207                     this._widgetLoaded();
208                 } else if (this.widget.attr("value") == null) {
209                     this._widgetLoaded();
210                 }
211                 return;
212             }
213             
214             if(!this.parentNode) // give it somewhere to live so that dojo won't complain
215                 this.parentNode = dojo.create('div');
216
217             this.onload = onload;
218
219             if(this.readOnly) {
220                 dojo.require('dijit.layout.ContentPane');
221                 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
222                 if(this.widgetValue !== null)
223                     this._tryLinkedDisplayField();
224
225             } else if(this.widgetClass) {
226                 dojo.require(this.widgetClass);
227                 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
228
229             } else {
230
231                 switch(this.idlField.datatype) {
232                     
233                     case 'id':
234                         dojo.require('dijit.form.TextBox');
235                         this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
236                         break;
237
238                     case 'org_unit':
239                         this._buildOrgSelector();
240                         break;
241
242                     case 'money':
243                         // dojo.require('dijit.form.CurrencyTextBox');
244                         // the CurrencyTextBox dijit is broken in Dojo 1.3; upon upgrading
245                         // to Dojo 1.5 or later, should re-evaluate work-around use of dijit.form.NumberTextBox.
246                         // See https://bugs.launchpad.net/evergreen/+bug/702117
247                         dojo.require('dijit.form.NumberTextBox');
248                         this.dijitArgs = dojo.mixin({constraints:{places:'0,2'}}, this.dijitArgs || {});
249                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
250                         break;
251
252                     case 'int':
253                         dojo.require('dijit.form.NumberTextBox');
254                         this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
255                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
256                         break;
257
258                     case 'float':
259                         dojo.require('dijit.form.NumberTextBox');
260                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
261                         break;
262
263                     case 'timestamp':
264                         dojo.require('dijit.form.DateTextBox');
265                         dojo.require('dojo.date.stamp');
266                         this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
267                         if (this.widgetValue != null) {
268                             this.widgetValue = openils.Util.timeStampAsDateObj(
269                                 this.widgetValue
270                             );
271                         }
272                         break;
273
274                     case 'bool':
275                         if(this.ternary) {
276                             dojo.require('dijit.form.FilteringSelect');
277                             var store = new dojo.data.ItemFileReadStore({
278                                 data:{
279                                     identifier : 'value',
280                                     items:[
281                                         {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
282                                         {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
283                                         {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
284                                     ]
285                                 }
286                             });
287                             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
288                             this.widget.searchAttr = this.widget.labelAttr = 'label';
289                             this.widget.valueAttr = 'value';
290                             this.widget.store = store;
291                             this.widget.startup();
292                             this.widgetValue = (this.widgetValue === null) ? 'unset' : 
293                                 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
294                         } else {
295                             dojo.require('dijit.form.CheckBox');
296                             this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
297                             this.widgetValue = openils.Util.isTrue(this.widgetValue);
298                         }
299                         break;
300
301                     case 'link':
302                         if(this._buildLinkSelector()) break;
303
304                     default:
305                         if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
306                             dojo.require('dijit.form.ValidationTextBox');
307                             this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
308                         } else {
309                             dojo.require('dijit.form.TextBox');
310                             this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
311                         }
312                 }
313             }
314
315             if(!this.async) this._widgetLoaded();
316             return this.widget;
317         },
318
319         // we want to display the value for our widget.  However, instead of displaying
320         // an ID, for exmaple, display the value for the 'selector' field on the object
321         // the ID points to
322         _tryLinkedDisplayField : function(noAsync) {
323
324             if(this.idlField.datatype == 'org_unit')
325                 return false; // we already handle org_units, no need to re-fetch
326
327             // user opted to bypass fetching this linked data
328             if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
329                 return false;
330
331             var linkInfo = this._getLinkSelector();
332             if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector)) 
333                 return false;
334             var lclass = linkInfo.linkClass;
335
336             if(lclass == 'aou') 
337                 return false;
338
339             // first try the store cache
340             var self = this;
341             if(this.cache[this.auth].list[lclass]) {
342                 var store = this.cache[this.auth].list[lclass];
343                 var query = {};
344                 query[linkInfo.vfield.name] = ''+this.widgetValue;
345                 var found = false;
346                 store.fetch({query:query, onComplete:
347                     function(list) {
348                         if(list[0]) {
349                             self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
350                             found = true;
351                         }
352                     }
353                 });
354
355                 if(found) return;
356             }
357
358             // then try the single object cache
359             if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
360                 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
361                 return;
362             }
363
364             console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
365
366             // if those fail, fetch the linked object
367             this.async = true;
368             var self = this;
369             new openils.PermaCrud().retrieve(lclass, this.widgetValue, {   
370                 async : !this.forceSync,
371                 oncomplete : function(r) {
372                     var item = openils.Util.readResponse(r);
373                     var newvalue = item[linkInfo.vfield.selector]();
374
375                     if(!self.cache[self.auth].single[lclass])
376                         self.cache[self.auth].single[lclass] = {};
377                     self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
378
379                     self.widgetValue = newvalue;
380                     self.widget.startup();
381                     self._widgetLoaded();
382                 }
383             });
384         },
385
386         _getLinkSelector : function() {
387             var linkClass = this.idlField['class'];
388             if(this.idlField.reltype != 'has_a')  return false;
389             if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
390                 fieldmapper.IDL.load([linkClass]);
391             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
392             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
393
394             var vfield;
395             var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
396
397             for(var f in rclassIdl.fields) {
398                 if(this.idlField.key == rclassIdl.fields[f].name) {
399                     vfield = rclassIdl.fields[f];
400                     break;
401                 }
402             }
403
404             if(!vfield) 
405                 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
406
407             return {
408                 linkClass : linkClass,
409                 vfield : vfield
410             };
411         },
412
413         _buildLinkSelector : function() {
414             var self = this;
415             var selectorInfo = this._getLinkSelector();
416             if(!selectorInfo) return false;
417
418             var linkClass = selectorInfo.linkClass;
419             var vfield = selectorInfo.vfield;
420
421             this.async = true;
422
423             if(linkClass == 'pgt')
424                 return this._buildPermGrpSelector();
425             if(linkClass == 'aou')
426                 return this._buildOrgSelector();
427             if(linkClass == 'acpl')
428                 return this._buildCopyLocSelector();
429             if(linkClass == 'acqpro')
430                 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
431
432
433             dojo.require('dojo.data.ItemFileReadStore');
434             dojo.require('dijit.form.FilteringSelect');
435
436             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
437             this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
438             this.widget.valueAttr = vfield.name;
439             this.widget.attr('disabled', true);
440
441             var oncomplete = function(list) {
442                 self.widget.attr('disabled', false);
443
444                 if(self.labelFormat) 
445                     self.widget.labelAttr = '_label';
446
447                 if(self.searchFormat)
448                     self.widget.searchAttr = '_search';
449
450                 function formatString(item, formatList) {
451
452                     try {
453
454                         // formatList[1..*] are names of fields.  Pull the field
455                         // values from each object to determine the values for string substitution
456                         var values = [];
457                         var format = formatList[0];
458                         for(var i = 1; i< formatList.length; i++) 
459                             values.push(item[formatList[i]]);
460
461                         return dojo.string.substitute(format, values);
462
463                     } catch(E) {
464                         throw new Error(
465                             "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
466                     }
467
468                 }
469
470                 if(list) {
471                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
472
473                     if(self.labelFormat) {
474                         dojo.forEach(storeData.data.items, 
475                             function(item) {
476                                 item._label = formatString(item, self.labelFormat);
477                             }
478                         );
479                     }
480
481                     if(self.searchFormat) {
482                         dojo.forEach(storeData.data.items, 
483                             function(item) {
484                                 item._search = formatString(item, self.searchFormat);
485                             }
486                         );
487                     }
488
489                     self.widget.store = new dojo.data.ItemFileReadStore(storeData);
490                     self.cache[self.auth].list[linkClass] = self.widget.store;
491
492                 } else {
493                     self.widget.store = self.cache[self.auth].list[linkClass];
494                 }
495
496                 self.widget.startup();
497                 self._widgetLoaded();
498             };
499
500             if(!this.noCache && this.cache[self.auth].list[linkClass]) {
501                 oncomplete();
502
503             } else {
504
505                 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
506                     this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
507
508                 if(this.dataLoader) {
509
510                     // caller provided an external function for retrieving the data
511                     this.dataLoader(linkClass, this.searchFilter, oncomplete);
512
513                 } else {
514
515                     var _cb = function(r) {
516                         oncomplete(openils.Util.readResponse(r, false, true));
517                     };
518
519                     if (this.searchFilter) {
520                         new openils.PermaCrud().search(linkClass, this.searchFilter, {
521                             async : !this.forceSync, oncomplete : _cb
522                         });
523                     } else {
524                         new openils.PermaCrud().retrieveAll(linkClass, {
525                             async : !this.forceSync, oncomplete : _cb
526                         });
527                     }
528                 }
529             }
530
531             return true;
532         },
533
534         /**
535          * For widgets that run asynchronously, provide a callback for finishing up
536          */
537         _widgetLoaded : function(value) {
538             
539             if(this.readOnly) {
540
541                 /* -------------------------------------------------------------
542                    when using widgets in a grid, the cell may dissapear, which 
543                    kills the underlying DOM node, which causes this to fail.
544                    For now, back out gracefully and let grid getters use
545                    getDisplayString() instead
546                   -------------------------------------------------------------*/
547                 try { 
548                     this.baseWidgetValue(this.getDisplayString());
549                 } catch (E) {};
550
551             } else {
552
553                 this.baseWidgetValue(this.widgetValue);
554                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
555                     this.widget.attr('disabled', true); 
556                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
557                     this.widget.attr('disabled', true); 
558             }
559             if(this.onload)
560                 this.onload(this.widget, this);
561
562             if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
563                 // a required dijit is not given any styling to indicate the value
564                 // is invalid until the user has focused the widget then left it with
565                 // invalid data.  This change tells dojo to pretend this focusing has 
566                 // already happened so we can style required widgets during page render.
567                 this.widget._hasBeenBlurred = true;
568                 this.widget.validate();
569             }
570         },
571
572         _buildOrgSelector : function() {
573             dojo.require('fieldmapper.OrgUtils');
574             dojo.require('openils.widget.FilteringTreeSelect');
575             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
576             this.widget.searchAttr = 'shortname';
577             this.widget.labelAttr = 'shortname';
578             this.widget.parentField = 'parent_ou';
579             var user = new openils.User();
580
581             if(this.widgetValue == null && this.orgDefaultsToWs) 
582                 this.widgetValue = user.user.ws_ou();
583             
584             // if we have a limit perm, find the relevent orgs (async)
585             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
586                 this.async = true;
587                 var self = this;
588                 user.getPermOrgList(this.orgLimitPerms, 
589                     function(orgList) {
590                         self.widget.tree = orgList;
591                         self.widget.startup();
592                         self._widgetLoaded();
593                     }
594                 );
595
596             } else {
597                 this.widget.tree = fieldmapper.aou.globalOrgTree;
598                 this.widget.startup();
599             }
600
601             return true;
602         },
603
604         _buildPermGrpSelector : function() {
605             dojo.require('openils.widget.FilteringTreeSelect');
606             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
607             this.widget.disableQuery = this.disableQuery;
608             this.widget.searchAttr = 'name';
609
610             if(this.cache.permGrpTree) {
611                 this.widget.tree = this.cache.permGrpTree;
612                 this.widget.startup();
613                 this._widgetLoaded();
614                 return true;
615             } 
616
617             var self = this;
618             this.async = true;
619             new openils.PermaCrud().retrieveAll('pgt', {
620                 async : !this.forceSync,
621                 oncomplete : function(r) {
622                     var list = openils.Util.readResponse(r, false, true);
623                     if(!list) return;
624                     var map = {};
625                     var root = null;
626                     for(var l in list)
627                         map[list[l].id()] = list[l];
628                     for(var l in list) {
629                         var node = list[l];
630                         var pnode = map[node.parent()];
631                         if(!pnode) {root = node; continue;}
632                         if(!pnode.children()) pnode.children([]);
633                         pnode.children().push(node);
634                     }
635                     self.widget.tree = self.cache.permGrpTree = root;
636                     self.widget.startup();
637                     self._widgetLoaded();
638                 }
639             });
640
641             return true;
642         },
643
644         _buildCopyLocSelector : function() {
645             dojo.require('dijit.form.FilteringSelect');
646             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
647             this.widget.searchAttr = this.widget.labalAttr = 'name';
648             this.widget.valueAttr = 'id';
649
650             if(this.cache.copyLocStore) {
651                 this.widget.store = this.cache.copyLocStore;
652                 this.widget.startup();
653                 this.async = false;
654                 return true;
655             } 
656
657             // my orgs
658             var ws_ou = openils.User.user.ws_ou();
659             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
660             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
661
662             var self = this;
663             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
664                 async : !this.forceSync,
665                 oncomplete : function(r) {
666                     var list = openils.Util.readResponse(r, false, true);
667                     if(!list) return;
668                     self.widget.store = 
669                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
670                     self.cache.copyLocStore = self.widget.store;
671                     self.widget.startup();
672                     self._widgetLoaded();
673                 }
674             });
675
676             return true;
677         },
678
679         _buildAutoCompleteSelector : function(linkClass, searchAttr) {
680             dojo.require("openils.widget.PCrudAutocompleteBox");
681             dojo.mixin(this.dijitArgs, {
682                 fmclass : linkClass,
683                 searchAttr : searchAttr,
684             });
685             this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
686             this._widgetLoaded();
687             return true;
688         }
689     });
690
691     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
692     openils.widget.AutoFieldWidget.cache = {};
693     openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};
694
695 }
696