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