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