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