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