]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
Simplified Hold Pull List: Fix broken filters
[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                     /* XXX LFW: I want to uncomment the following three lines that refer to ob, but haven't had the time to properly test. */
561
562                     //var ob = {};
563                     //ob[linkClass] = vfield.selector || vfield.name;
564
565                     this.searchOptions = dojo.mixin(
566                         {
567                             async : !this.forceSync,
568                             oncomplete : _cb
569                             //order_by : ob
570                         }, this.searchOptions
571                     );
572
573                     if (this.searchFilter) {
574                         new openils.PermaCrud().search(linkClass, this.searchFilter, this.searchOptions);
575                     } else {
576                         new openils.PermaCrud().retrieveAll(linkClass, this.searchOptions);
577                     }
578                 }
579             }
580
581             return true;
582         },
583
584         /**
585          * For widgets that run asynchronously, provide a callback for finishing up
586          */
587         _widgetLoaded : function(value) {
588             
589             if(this.readOnly) {
590
591                 /* -------------------------------------------------------------
592                    when using widgets in a grid, the cell may dissapear, which 
593                    kills the underlying DOM node, which causes this to fail.
594                    For now, back out gracefully and let grid getters use
595                    getDisplayString() instead
596                   -------------------------------------------------------------*/
597                 try { 
598                     this.baseWidgetValue(this.getDisplayString());
599                 } catch (E) {};
600
601             } else {
602
603                 this.baseWidgetValue(this.widgetValue);
604                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
605                     this.widget.attr('disabled', true); 
606                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
607                     this.widget.attr('disabled', true); 
608             }
609             if(this.onload)
610                 this.onload(this.widget, this);
611
612             if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
613                 // a required dijit is not given any styling to indicate the value
614                 // is invalid until the user has focused the widget then left it with
615                 // invalid data.  This change tells dojo to pretend this focusing has 
616                 // already happened so we can style required widgets during page render.
617                 this.widget._hasBeenBlurred = true;
618                 if(this.widget.validate)
619                     this.widget.validate();
620             }
621         },
622
623         _buildOrgSelector : function() {
624             dojo.require('fieldmapper.OrgUtils');
625             dojo.require('openils.widget.FilteringTreeSelect');
626             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
627             this.widget.searchAttr = this.searchAttr || 'shortname';
628             this.widget.labelAttr = this.searchAttr || 'shortname';
629             this.widget.parentField = 'parent_ou';
630             var user = new openils.User();
631
632             if(this.widgetValue == null && this.orgDefaultsToWs) 
633                 this.widgetValue = user.user.ws_ou();
634             
635             // if we have a limit perm, find the relevent orgs (async)
636             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
637                 this.async = true;
638                 var self = this;
639                 user.getPermOrgList(this.orgLimitPerms, 
640                     function(orgList) {
641                         self.widget.tree = orgList;
642                         self.widget.startup();
643                         self._widgetLoaded();
644                     }
645                 );
646
647             } else {
648                 this.widget.tree = fieldmapper.aou.globalOrgTree;
649                 this.widget.startup();
650             }
651
652             return true;
653         },
654
655         _buildPermGrpSelector : function() {
656             dojo.require('openils.widget.FilteringTreeSelect');
657             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
658             this.widget.disableQuery = this.disableQuery;
659             this.widget.searchAttr = 'name';
660
661             if(this.cache.permGrpTree) {
662                 this.widget.tree = this.cache.permGrpTree;
663                 this.widget.startup();
664                 this._widgetLoaded();
665                 return true;
666             } 
667
668             var self = this;
669             this.async = true;
670             new openils.PermaCrud().retrieveAll('pgt', {
671                 async : !this.forceSync,
672                 oncomplete : function(r) {
673                     var list = openils.Util.readResponse(r, false, true);
674                     if(!list) return;
675                     var map = {};
676                     var root = null;
677                     for(var l in list)
678                         map[list[l].id()] = list[l];
679                     for(var l in list) {
680                         var node = list[l];
681                         var pnode = map[node.parent()];
682                         if(!pnode) {root = node; continue;}
683                         if(!pnode.children()) pnode.children([]);
684                         pnode.children().push(node);
685                     }
686                     self.widget.tree = self.cache.permGrpTree = root;
687                     self.widget.startup();
688                     self._widgetLoaded();
689                 }
690             });
691
692             return true;
693         },
694
695         _buildCopyLocSelector : function() {
696             dojo.require('dijit.form.FilteringSelect');
697             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
698             this.widget.searchAttr = this.widget.labalAttr = 'name';
699             this.widget.valueAttr = 'id';
700
701             if(this.cache.copyLocStore) {
702                 this.widget.store = this.cache.copyLocStore;
703                 this.widget.startup();
704                 this.async = false;
705                 return true;
706             } 
707
708             // my orgs
709             var ws_ou = openils.User.user.ws_ou();
710             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
711             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
712
713             var self = this;
714             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
715                 async : !this.forceSync,
716                 order_by : {"acpl": "name"},
717                 oncomplete : function(r) {
718                     var list = openils.Util.readResponse(r, false, true);
719                     if(!list) return;
720                     self.widget.store = 
721                         new self.storeConstructor({data:fieldmapper.acpl.toStoreData(list)});
722                     self.cache.copyLocStore = self.widget.store;
723                     self.widget.startup();
724                     self._widgetLoaded();
725                 }
726             });
727
728             return true;
729         },
730
731         _buildAutoCompleteSelector : function(linkClass, searchAttr) {
732             dojo.require("openils.widget.PCrudAutocompleteBox");
733             dojo.mixin(this.dijitArgs, {
734                 fmclass : linkClass,
735                 searchAttr : searchAttr,
736             });
737             this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
738             this._widgetLoaded();
739             return true;
740         }
741     });
742
743     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
744     openils.widget.AutoFieldWidget.cache = {};
745     openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};
746
747 }
748