]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
Merge branch 'master' of git.evergreen-ils.org:Evergreen into template-toolkit-opac
[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                             var item = list[0];
368                             if(self.labelFormat) {
369                                 self.widgetValue = self._applyLabelFormat(item, self.labelFormat);
370                             } else {
371                                 self.widgetValue = store.getValue(item, linkInfo.vfield.selector);
372                             }
373                             found = true;
374                         }
375                     }
376                 });
377
378                 if(found) return;
379             }
380
381             // then try the single object cache
382             if(this.cache[this.auth].single[lclass] && 
383                     this.cache[this.auth].single[lclass][this.widgetValue] &&
384                     this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || '']) {
385                 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || ''];
386                 return;
387             }
388
389             console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
390
391             // if those fail, fetch the linked object
392             this.async = true;
393             var self = this;
394             new openils.PermaCrud().retrieve(lclass, this.widgetValue, {   
395                 async : !this.forceSync,
396                 oncomplete : function(r) {
397                     var item = openils.Util.readResponse(r);
398
399                     var newvalue = item[linkInfo.vfield.selector]();
400
401                     var labelCacheKey = ''; 
402
403                     if(self.labelFormat) {
404                         labelCacheKey = self.labelFormat;
405                         self.widgetValue = self._applyLabelFormat(item.toStoreItem(), self.labelFormat);
406                     } else {
407                         self.widgetValue = newvalue;
408                     }
409
410                     if(!self.cache[self.auth].single[lclass])
411                         self.cache[self.auth].single[lclass] = {};
412                     if(!self.cache[self.auth].single[lclass][self.widgetValue])
413                         self.cache[self.auth].single[lclass][self.widgetValue] = {};
414                     self.cache[self.auth].single[lclass][self.widgetValue][labelCacheKey] = newvalue;
415
416                     self.widget.startup();
417                     self._widgetLoaded();
418                 }
419             });
420         },
421
422         _getLinkSelector : function() {
423             var linkClass = this.idlField['class'];
424             if(this.idlField.reltype != 'has_a')  return false;
425             if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
426                 fieldmapper.IDL.load([linkClass]);
427             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
428             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
429
430             var vfield;
431             var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
432
433             for(var f in rclassIdl.fields) {
434                 if(this.idlField.key == rclassIdl.fields[f].name) {
435                     vfield = rclassIdl.fields[f];
436                     break;
437                 }
438             }
439
440             if(!vfield) 
441                 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
442
443             return {
444                 linkClass : linkClass,
445                 vfield : vfield
446             };
447         },
448
449         _applyLabelFormat : function (item, formatList) {
450
451             try {
452
453                 // formatList[1..*] are names of fields.  Pull the field
454                 // values from each object to determine the values for string substitution
455                 var values = [];
456                 var format = formatList[0];
457                 for(var i = 1; i< formatList.length; i++) 
458                     values.push(item[formatList[i]]);
459
460                 return dojo.string.substitute(format, values);
461
462             } catch(E) {
463                 throw new Error(
464                     "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
465             }
466         },
467
468         _buildLinkSelector : function() {
469             var self = this;
470             var selectorInfo = this._getLinkSelector();
471             if(!selectorInfo) return false;
472
473             var linkClass = selectorInfo.linkClass;
474             var vfield = selectorInfo.vfield;
475
476             this.async = true;
477
478             if(linkClass == 'pgt')
479                 return this._buildPermGrpSelector();
480             if(linkClass == 'aou')
481                 return this._buildOrgSelector();
482             if(linkClass == 'acpl')
483                 return this._buildCopyLocSelector();
484             if(linkClass == 'acqpro')
485                 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
486
487
488             dojo.require('dojo.data.ItemFileReadStore');
489             dojo.require('dijit.form.FilteringSelect');
490
491             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
492             this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
493             this.widget.valueAttr = vfield.name;
494             this.widget.attr('disabled', true);
495
496             var oncomplete = function(list) {
497                 self.widget.attr('disabled', false);
498
499                 if(self.labelFormat) 
500                     self.widget.labelAttr = '_label';
501
502                 if(self.searchFormat)
503                     self.widget.searchAttr = '_search';
504
505                 if(list) {
506                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
507
508                     if(self.labelFormat) {
509                         dojo.forEach(storeData.data.items, 
510                             function(item) {
511                                 item._label = self._applyLabelFormat(item, self.labelFormat);
512                             }
513                         );
514                     }
515
516                     if(self.searchFormat) {
517                         dojo.forEach(storeData.data.items, 
518                             function(item) {
519                                 item._search = self._applyLabelFormat(item, self.searchFormat);
520                             }
521                         );
522                     }
523
524                     self.widget.store = new dojo.data.ItemFileReadStore(storeData);
525                     self.cache[self.auth].list[linkClass] = self.widget.store;
526
527                 } else {
528                     self.widget.store = self.cache[self.auth].list[linkClass];
529                 }
530
531                 self.widget.startup();
532                 self._widgetLoaded();
533             };
534
535             if(!this.noCache && this.cache[self.auth].list[linkClass]) {
536                 oncomplete();
537
538             } else {
539
540                 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
541                     this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
542
543                 if(this.dataLoader) {
544
545                     // caller provided an external function for retrieving the data
546                     this.dataLoader(linkClass, this.searchFilter, oncomplete);
547
548                 } else {
549
550                     var _cb = function(r) {
551                         oncomplete(openils.Util.readResponse(r, false, true));
552                     };
553
554                     this.searchOptions = dojo.mixin(
555                         {async : !this.forceSync, oncomplete : _cb},
556                         this.searchOptions
557                     );
558
559                     if (this.searchFilter) {
560                         new openils.PermaCrud().search(linkClass, this.searchFilter, this.searchOptions);
561                     } else {
562                         new openils.PermaCrud().retrieveAll(linkClass, this.searchOptions);
563                     }
564                 }
565             }
566
567             return true;
568         },
569
570         /**
571          * For widgets that run asynchronously, provide a callback for finishing up
572          */
573         _widgetLoaded : function(value) {
574             
575             if(this.readOnly) {
576
577                 /* -------------------------------------------------------------
578                    when using widgets in a grid, the cell may dissapear, which 
579                    kills the underlying DOM node, which causes this to fail.
580                    For now, back out gracefully and let grid getters use
581                    getDisplayString() instead
582                   -------------------------------------------------------------*/
583                 try { 
584                     this.baseWidgetValue(this.getDisplayString());
585                 } catch (E) {};
586
587             } else {
588
589                 this.baseWidgetValue(this.widgetValue);
590                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
591                     this.widget.attr('disabled', true); 
592                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
593                     this.widget.attr('disabled', true); 
594             }
595             if(this.onload)
596                 this.onload(this.widget, this);
597
598             if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
599                 // a required dijit is not given any styling to indicate the value
600                 // is invalid until the user has focused the widget then left it with
601                 // invalid data.  This change tells dojo to pretend this focusing has 
602                 // already happened so we can style required widgets during page render.
603                 this.widget._hasBeenBlurred = true;
604                 if(this.widget.validate)
605                     this.widget.validate();
606             }
607         },
608
609         _buildOrgSelector : function() {
610             dojo.require('fieldmapper.OrgUtils');
611             dojo.require('openils.widget.FilteringTreeSelect');
612             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
613             this.widget.searchAttr = this.searchAttr || 'shortname';
614             this.widget.labelAttr = this.searchAttr || 'shortname';
615             this.widget.parentField = 'parent_ou';
616             var user = new openils.User();
617
618             if(this.widgetValue == null && this.orgDefaultsToWs) 
619                 this.widgetValue = user.user.ws_ou();
620             
621             // if we have a limit perm, find the relevent orgs (async)
622             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
623                 this.async = true;
624                 var self = this;
625                 user.getPermOrgList(this.orgLimitPerms, 
626                     function(orgList) {
627                         self.widget.tree = orgList;
628                         self.widget.startup();
629                         self._widgetLoaded();
630                     }
631                 );
632
633             } else {
634                 this.widget.tree = fieldmapper.aou.globalOrgTree;
635                 this.widget.startup();
636             }
637
638             return true;
639         },
640
641         _buildPermGrpSelector : function() {
642             dojo.require('openils.widget.FilteringTreeSelect');
643             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
644             this.widget.disableQuery = this.disableQuery;
645             this.widget.searchAttr = 'name';
646
647             if(this.cache.permGrpTree) {
648                 this.widget.tree = this.cache.permGrpTree;
649                 this.widget.startup();
650                 this._widgetLoaded();
651                 return true;
652             } 
653
654             var self = this;
655             this.async = true;
656             new openils.PermaCrud().retrieveAll('pgt', {
657                 async : !this.forceSync,
658                 oncomplete : function(r) {
659                     var list = openils.Util.readResponse(r, false, true);
660                     if(!list) return;
661                     var map = {};
662                     var root = null;
663                     for(var l in list)
664                         map[list[l].id()] = list[l];
665                     for(var l in list) {
666                         var node = list[l];
667                         var pnode = map[node.parent()];
668                         if(!pnode) {root = node; continue;}
669                         if(!pnode.children()) pnode.children([]);
670                         pnode.children().push(node);
671                     }
672                     self.widget.tree = self.cache.permGrpTree = root;
673                     self.widget.startup();
674                     self._widgetLoaded();
675                 }
676             });
677
678             return true;
679         },
680
681         _buildCopyLocSelector : function() {
682             dojo.require('dijit.form.FilteringSelect');
683             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
684             this.widget.searchAttr = this.widget.labalAttr = 'name';
685             this.widget.valueAttr = 'id';
686
687             if(this.cache.copyLocStore) {
688                 this.widget.store = this.cache.copyLocStore;
689                 this.widget.startup();
690                 this.async = false;
691                 return true;
692             } 
693
694             // my orgs
695             var ws_ou = openils.User.user.ws_ou();
696             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
697             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
698
699             var self = this;
700             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
701                 async : !this.forceSync,
702                 oncomplete : function(r) {
703                     var list = openils.Util.readResponse(r, false, true);
704                     if(!list) return;
705                     self.widget.store = 
706                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
707                     self.cache.copyLocStore = self.widget.store;
708                     self.widget.startup();
709                     self._widgetLoaded();
710                 }
711             });
712
713             return true;
714         },
715
716         _buildAutoCompleteSelector : function(linkClass, searchAttr) {
717             dojo.require("openils.widget.PCrudAutocompleteBox");
718             dojo.mixin(this.dijitArgs, {
719                 fmclass : linkClass,
720                 searchAttr : searchAttr,
721             });
722             this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
723             this._widgetLoaded();
724             return true;
725         }
726     });
727
728     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
729     openils.widget.AutoFieldWidget.cache = {};
730     openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};
731
732 }
733