]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
additional code-level comments
[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          */
40         constructor : function(args) {
41             for(var k in args)
42                 this[k] = args[k];
43
44             // find the field description in the IDL if not provided
45             if(this.fmObject) 
46                 this.fmClass = this.fmObject.classname;
47             this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
48             this.suppressLinkedFields = args.suppressLinkedFields || [];
49
50             if(this.selfReference) {
51                 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
52                 
53                 // create a mock-up of the idlField object.  
54                 this.idlField = {
55                     datatype : 'link',
56                     'class' : this.fmClass,
57                     reltype : 'has_a',
58                     key : this.fmField,
59                     name : this.fmField
60                 };
61
62             } else {
63
64                 if(!this.idlField) {
65                     this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
66                     var fields = this.fmIDL.fields;
67                     for(var f in fields) 
68                         if(fields[f].name == this.fmField)
69                             this.idlField = fields[f];
70                 }
71             }
72
73             if(!this.idlField) 
74                 throw new Error("AutoFieldWidget could not determine which " +
75                     "field to render.  We need more information. fmClass=" + 
76                     this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
77
78             this.auth = openils.User.authtoken;
79             this.cache = openils.widget.AutoFieldWidget.cache;
80             this.cache[this.auth] = this.cache[this.auth] || {};
81             this.cache[this.auth].single = this.cache[this.auth].single || {};
82             this.cache[this.auth].list = this.cache[this.auth].list || {};
83         },
84
85         /**
86          * Turn the widget-stored value into a value oils understands
87          */
88         getFormattedValue : function() {
89             var value = this.baseWidgetValue();
90             switch(this.idlField.datatype) {
91                 case 'bool':
92                     switch(value) {
93                         case 'true': return 't';
94                         case 'on': return 't';
95                         case 'false' : return 'f';
96                         case 'unset' : return null;
97                         case true : return 't';
98                         default: return 'f';
99                     }
100                 case 'timestamp':
101                     if(!value) return null;
102                     return dojo.date.stamp.toISOString(value);
103                 case 'int':
104                 case 'float':
105                 case 'money':
106                     if(isNaN(value)) value = null;
107                 default:
108                     return (value === '') ? null : value;
109             }
110         },
111
112         baseWidgetValue : function(value) {
113             var attr = (this.readOnly) ? 'content' : 'value';
114             if(arguments.length) this.widget.attr(attr, value);
115             return this.widget.attr(attr);
116         },
117         
118         /**
119          * Turn the widget-stored value into something visually suitable
120          */
121         getDisplayString : function() {
122             var value = this.widgetValue;
123             switch(this.idlField.datatype) {
124                 case 'bool':
125                     switch(value) {
126                         case 't': 
127                         case 'true': 
128                             return openils.widget.AutoFieldWidget.localeStrings.TRUE; 
129                         case 'f' : 
130                         case 'false' : 
131                             return openils.widget.AutoFieldWidget.localeStrings.FALSE;
132                         case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
133                         case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE; 
134                         default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
135                     }
136                 case 'timestamp':
137                     if (!value) return '';
138                     dojo.require('dojo.date.locale');
139                     dojo.require('dojo.date.stamp');
140                     var date = dojo.date.stamp.fromISOString(value);
141                     return dojo.date.locale.format(date, {formatLength:'short'});
142                 case 'org_unit':
143                     if(value === null || value === undefined) return '';
144                     return fieldmapper.aou.findOrgUnit(value).shortname();
145                 case 'int':
146                 case 'float':
147                     if(isNaN(value)) value = 0;
148                 default:
149                     if(value === undefined || value === null)
150                         value = '';
151                     return value+'';
152             }
153         },
154
155         build : function(onload) {
156
157             if(this.widgetValue == null)
158                 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
159
160             if(this.widget) {
161                 // core widget provided for us, attach and move on
162                 if(this.parentNode) // may already be in the "right" place
163                     this.parentNode.appendChild(this.widget.domNode);
164                 if(this.widget.attr('value') == null)
165                     this._widgetLoaded();
166                 return;
167             }
168             
169             if(!this.parentNode) // give it somewhere to live so that dojo won't complain
170                 this.parentNode = dojo.create('div');
171
172             this.onload = onload;
173
174             if(this.readOnly) {
175                 dojo.require('dijit.layout.ContentPane');
176                 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
177                 if(this.widgetValue !== null)
178                     this._tryLinkedDisplayField();
179
180             } else if(this.widgetClass) {
181                 dojo.require(this.widgetClass);
182                 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
183
184             } else {
185
186                 switch(this.idlField.datatype) {
187                     
188                     case 'id':
189                         dojo.require('dijit.form.TextBox');
190                         this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
191                         break;
192
193                     case 'org_unit':
194                         this._buildOrgSelector();
195                         break;
196
197                     case 'money':
198                         dojo.require('dijit.form.CurrencyTextBox');
199                         this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
200                         break;
201
202                     case 'int':
203                         dojo.require('dijit.form.NumberTextBox');
204                         this.dijitArgs = dojo.mixin(this.dijitArgs || {}, {constraints:{places:0}});
205                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
206                         break;
207
208                     case 'float':
209                         dojo.require('dijit.form.NumberTextBox');
210                         this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
211                         break;
212
213                     case 'timestamp':
214                         dojo.require('dijit.form.DateTextBox');
215                         dojo.require('dojo.date.stamp');
216                         this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
217                         if(this.widgetValue != null) 
218                             this.widgetValue = dojo.date.stamp.fromISOString(this.widgetValue);
219                         break;
220
221                     case 'bool':
222                         if(this.ternary) {
223                             dojo.require('dijit.form.FilteringSelect');
224                             var store = new dojo.data.ItemFileReadStore({
225                                 data:{
226                                     identifier : 'value',
227                                     items:[
228                                         {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
229                                         {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
230                                         {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
231                                     ]
232                                 }
233                             });
234                             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
235                             this.widget.searchAttr = this.widget.labelAttr = 'label';
236                             this.widget.valueAttr = 'value';
237                             this.widget.store = store;
238                             this.widget.startup();
239                             this.widgetValue = (this.widgetValue === null) ? 'unset' : 
240                                 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
241                         } else {
242                             dojo.require('dijit.form.CheckBox');
243                             this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
244                             this.widgetValue = openils.Util.isTrue(this.widgetValue);
245                         }
246                         break;
247
248                     case 'link':
249                         if(this._buildLinkSelector()) break;
250
251                     default:
252                         if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
253                             dojo.require('dijit.form.ValidationTextBox');
254                             this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
255                         } else {
256                             dojo.require('dijit.form.TextBox');
257                             this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
258                         }
259                 }
260             }
261
262             if(!this.async) this._widgetLoaded();
263             return this.widget;
264         },
265
266         // we want to display the value for our widget.  However, instead of displaying
267         // an ID, for exmaple, display the value for the 'selector' field on the object
268         // the ID points to
269         _tryLinkedDisplayField : function(noAsync) {
270
271             if(this.idlField.datatype == 'org_unit')
272                 return false; // we already handle org_units, no need to re-fetch
273
274             // user opted to bypass fetching this linked data
275             if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
276                 return false;
277
278             var linkInfo = this._getLinkSelector();
279             if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector)) 
280                 return false;
281             var lclass = linkInfo.linkClass;
282
283             if(lclass == 'aou') {
284                 this.widgetValue = fieldmapper.aou.findOrgUnit(this.widgetValue).shortname();
285                 return;
286             }
287
288             // first try the store cache
289             var self = this;
290             if(this.cache[this.auth].list[lclass]) {
291                 var store = this.cache[this.auth].list[lclass];
292                 var query = {};
293                 query[linkInfo.vfield.name] = ''+this.widgetValue;
294                 store.fetch({query:query, onComplete:
295                     function(list) {
296                         self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
297                     }
298                 });
299                 return;
300             }
301
302             // then try the single object cache
303             if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
304                 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
305                 return;
306             }
307
308             console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
309
310             // if those fail, fetch the linked object
311             this.async = true;
312             var self = this;
313             new openils.PermaCrud().retrieve(lclass, this.widgetValue, {   
314                 async : !this.forceSync,
315                 oncomplete : function(r) {
316                     var item = openils.Util.readResponse(r);
317                     var newvalue = item[linkInfo.vfield.selector]();
318
319                     if(!self.cache[self.auth].single[lclass])
320                         self.cache[self.auth].single[lclass] = {};
321                     self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
322
323                     self.widgetValue = newvalue;
324                     self.widget.startup();
325                     self._widgetLoaded();
326                 }
327             });
328         },
329
330         _getLinkSelector : function() {
331             var linkClass = this.idlField['class'];
332             if(this.idlField.reltype != 'has_a')  return false;
333             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
334             if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
335
336             var vfield;
337             var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
338
339             for(var f in rclassIdl.fields) {
340                 if(this.idlField.key == rclassIdl.fields[f].name) {
341                     vfield = rclassIdl.fields[f];
342                     break;
343                 }
344             }
345
346             if(!vfield) 
347                 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
348
349             return {
350                 linkClass : linkClass,
351                 vfield : vfield
352             };
353         },
354
355         _buildLinkSelector : function() {
356             var self = this;
357             var selectorInfo = this._getLinkSelector();
358             if(!selectorInfo) return false;
359
360             var linkClass = selectorInfo.linkClass;
361             var vfield = selectorInfo.vfield;
362
363             this.async = true;
364
365             if(linkClass == 'pgt')
366                 return this._buildPermGrpSelector();
367             if(linkClass == 'aou')
368                 return this._buildOrgSelector();
369             if(linkClass == 'acpl')
370                 return this._buildCopyLocSelector();
371
372
373             dojo.require('dojo.data.ItemFileReadStore');
374             dojo.require('dijit.form.FilteringSelect');
375
376             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
377             this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
378             this.widget.valueAttr = vfield.name;
379
380             var oncomplete = function(list) {
381
382                 if(self.labelFormat) 
383                     self.widget.labelAttr = self.widget.searchAttr = '_label';
384
385                 if(list) {
386                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
387
388                     if(self.labelFormat) {
389                         
390                         // set the label for each value in the store based on the provided label format.
391
392                         var format = self.labelFormat[0];
393                         dojo.forEach(storeData.data.items, 
394
395                             function(item) {
396                                 var values = [];
397
398                                 // self.labelFormat[1..*] are names of fields.  Pull the field
399                                 // values from each object to determine the values for string substitution
400                                for(var i = 1; i< self.labelFormat.length; i++) 
401                                     values.push(item[self.labelFormat[i]]);
402
403                                 item._label = dojo.string.substitute(format, values);
404                             }
405                         );
406                     }
407
408                     self.widget.store = new dojo.data.ItemFileReadStore(storeData);
409                     self.cache[self.auth].list[linkClass] = self.widget.store;
410
411                 } else {
412                     self.widget.store = self.cache[self.auth].list[linkClass];
413                 }
414
415                 self.widget.startup();
416                 self._widgetLoaded();
417             };
418
419             if(this.cache[self.auth].list[linkClass]) {
420                 oncomplete();
421
422             } else {
423                 var _cb = function(r) {
424                     oncomplete(openils.Util.readResponse(r, false, true));
425                 };
426                 if (this.searchFilter) {
427                     new openils.PermaCrud().search(linkClass, this.searchFilter, {
428                         async : !this.forceSync, oncomplete : _cb
429                     });
430                 } else {
431                     new openils.PermaCrud().retrieveAll(linkClass, {
432                         async : !this.forceSync, oncomplete : _cb
433                     });
434                 }
435             }
436
437             return true;
438         },
439
440         /**
441          * For widgets that run asynchronously, provide a callback for finishing up
442          */
443         _widgetLoaded : function(value) {
444             
445             if(this.readOnly) {
446
447                 /* -------------------------------------------------------------
448                    when using widgets in a grid, the cell may dissapear, which 
449                    kills the underlying DOM node, which causes this to fail.
450                    For now, back out gracefully and let grid getters use
451                    getDisplayString() instead
452                   -------------------------------------------------------------*/
453                 try { 
454                     this.baseWidgetValue(this.getDisplayString());
455                 } catch (E) {};
456
457             } else {
458
459                 this.baseWidgetValue(this.widgetValue);
460                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
461                     this.widget.attr('disabled', true); 
462                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
463                     this.widget.attr('disabled', true); 
464             }
465             if(this.onload)
466                 this.onload(this.widget, this);
467         },
468
469         _buildOrgSelector : function() {
470             dojo.require('fieldmapper.OrgUtils');
471             dojo.require('openils.widget.FilteringTreeSelect');
472             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
473             this.widget.searchAttr = 'shortname';
474             this.widget.labelAttr = 'shortname';
475             this.widget.parentField = 'parent_ou';
476             var user = new openils.User();
477
478             if(this.widgetValue == null && this.orgDefaultsToWs) 
479                 this.widgetValue = user.user.ws_ou();
480             
481             // if we have a limit perm, find the relevent orgs (async)
482             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
483                 this.async = true;
484                 var self = this;
485                 user.getPermOrgList(this.orgLimitPerms, 
486                     function(orgList) {
487                         self.widget.tree = orgList;
488                         self.widget.startup();
489                         self._widgetLoaded();
490                     }
491                 );
492
493             } else {
494                 this.widget.tree = fieldmapper.aou.globalOrgTree;
495                 this.widget.startup();
496             }
497
498             return true;
499         },
500
501         _buildPermGrpSelector : function() {
502             dojo.require('openils.widget.FilteringTreeSelect');
503             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
504             this.widget.searchAttr = 'name';
505
506             if(this.cache.permGrpTree) {
507                 this.widget.tree = this.cache.permGrpTree;
508                 this.widget.startup();
509                 return true;
510             } 
511
512             var self = this;
513             this.async = true;
514             new openils.PermaCrud().retrieveAll('pgt', {
515                 async : !this.forceSync,
516                 oncomplete : function(r) {
517                     var list = openils.Util.readResponse(r, false, true);
518                     if(!list) return;
519                     var map = {};
520                     var root = null;
521                     for(var l in list)
522                         map[list[l].id()] = list[l];
523                     for(var l in list) {
524                         var node = list[l];
525                         var pnode = map[node.parent()];
526                         if(!pnode) {root = node; continue;}
527                         if(!pnode.children()) pnode.children([]);
528                         pnode.children().push(node);
529                     }
530                     self.widget.tree = self.cache.permGrpTree = root;
531                     self.widget.startup();
532                     self._widgetLoaded();
533                 }
534             });
535
536             return true;
537         },
538
539         _buildCopyLocSelector : function() {
540             dojo.require('dijit.form.FilteringSelect');
541             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
542             this.widget.searchAttr = this.widget.labalAttr = 'name';
543             this.widget.valueAttr = 'id';
544
545             if(this.cache.copyLocStore) {
546                 this.widget.store = this.cache.copyLocStore;
547                 this.widget.startup();
548                 this.async = false;
549                 return true;
550             } 
551
552             // my orgs
553             var ws_ou = openils.User.user.ws_ou();
554             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
555             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
556
557             var self = this;
558             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
559                 async : !this.forceSync,
560                 oncomplete : function(r) {
561                     var list = openils.Util.readResponse(r, false, true);
562                     if(!list) return;
563                     self.widget.store = 
564                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
565                     self.cache.copyLocStore = self.widget.store;
566                     self.widget.startup();
567                     self._widgetLoaded();
568                 }
569             });
570
571             return true;
572         }
573     });
574
575     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
576     openils.widget.AutoFieldWidget.cache = {};
577 }
578