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