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