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