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