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