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