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