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