]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
Acq: POs can only be created with active providers (vendors)
[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                 var _cb = function(r) {
389                     oncomplete(openils.Util.readResponse(r, false, true));
390                 };
391                 if (this.searchFilter) {
392                     new openils.PermaCrud().search(linkClass, this.searchFilter, {
393                         async : !this.forceSync, oncomplete : _cb
394                     });
395                 } else {
396                     new openils.PermaCrud().retrieveAll(linkClass, {
397                         async : !this.forceSync, oncomplete : _cb
398                     });
399                 }
400             }
401
402             return true;
403         },
404
405         /**
406          * For widgets that run asynchronously, provide a callback for finishing up
407          */
408         _widgetLoaded : function(value) {
409             
410             if(this.readOnly) {
411
412                 /* -------------------------------------------------------------
413                    when using widgets in a grid, the cell may dissapear, which 
414                    kills the underlying DOM node, which causes this to fail.
415                    For now, back out gracefully and let grid getters use
416                    getDisplayString() instead
417                   -------------------------------------------------------------*/
418                 try { 
419                     this.baseWidgetValue(this.getDisplayString());
420                 } catch (E) {};
421
422             } else {
423
424                 this.baseWidgetValue(this.widgetValue);
425                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
426                     this.widget.attr('disabled', true); 
427                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
428                     this.widget.attr('disabled', true); 
429             }
430             if(this.onload)
431                 this.onload(this.widget, this);
432         },
433
434         _buildOrgSelector : function() {
435             dojo.require('fieldmapper.OrgUtils');
436             dojo.require('openils.widget.FilteringTreeSelect');
437             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
438             this.widget.searchAttr = 'shortname';
439             this.widget.labelAttr = 'shortname';
440             this.widget.parentField = 'parent_ou';
441             var user = new openils.User();
442
443             if(this.widgetValue == null && this.orgDefaultsToWs) 
444                 this.widgetValue = user.user.ws_ou();
445             
446             // if we have a limit perm, find the relevent orgs (async)
447             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
448                 this.async = true;
449                 var self = this;
450                 user.getPermOrgList(this.orgLimitPerms, 
451                     function(orgList) {
452                         self.widget.tree = orgList;
453                         self.widget.startup();
454                         self._widgetLoaded();
455                     }
456                 );
457
458             } else {
459                 this.widget.tree = fieldmapper.aou.globalOrgTree;
460                 this.widget.startup();
461             }
462
463             return true;
464         },
465
466         _buildPermGrpSelector : function() {
467             dojo.require('openils.widget.FilteringTreeSelect');
468             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
469             this.widget.searchAttr = 'name';
470
471             if(this.cache.permGrpTree) {
472                 this.widget.tree = this.cache.permGrpTree;
473                 this.widget.startup();
474                 return true;
475             } 
476
477             var self = this;
478             this.async = true;
479             new openils.PermaCrud().retrieveAll('pgt', {
480                 async : !this.forceSync,
481                 oncomplete : function(r) {
482                     var list = openils.Util.readResponse(r, false, true);
483                     if(!list) return;
484                     var map = {};
485                     var root = null;
486                     for(var l in list)
487                         map[list[l].id()] = list[l];
488                     for(var l in list) {
489                         var node = list[l];
490                         var pnode = map[node.parent()];
491                         if(!pnode) {root = node; continue;}
492                         if(!pnode.children()) pnode.children([]);
493                         pnode.children().push(node);
494                     }
495                     self.widget.tree = self.cache.permGrpTree = root;
496                     self.widget.startup();
497                     self._widgetLoaded();
498                 }
499             });
500
501             return true;
502         },
503
504         _buildCopyLocSelector : function() {
505             dojo.require('dijit.form.FilteringSelect');
506             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
507             this.widget.searchAttr = this.widget.labalAttr = 'name';
508             this.widget.valueAttr = 'id';
509
510             if(this.cache.copyLocStore) {
511                 this.widget.store = this.cache.copyLocStore;
512                 this.widget.startup();
513                 this.async = false;
514                 return true;
515             } 
516
517             // my orgs
518             var ws_ou = openils.User.user.ws_ou();
519             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
520             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
521
522             var self = this;
523             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
524                 async : !this.forceSync,
525                 oncomplete : function(r) {
526                     var list = openils.Util.readResponse(r, false, true);
527                     if(!list) return;
528                     self.widget.store = 
529                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
530                     self.cache.copyLocStore = self.widget.store;
531                     self.widget.startup();
532                     self._widgetLoaded();
533                 }
534             });
535
536             return true;
537         }
538     });
539
540     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
541     openils.widget.AutoFieldWidget.cache = {};
542 }
543