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