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