]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
started support for overrideing the label for remote object 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             /*
381             var setLabelFunc = function(linkedObjectStore) {
382                 self.widget.labelFunc = function(val) { return 'FOO'} ;
383                 return;
384                 if(self.labelFormat) {
385                     self.widget.labelFunc = function(val) { 
386
387                         try {
388
389                             // find the linked item in the remote object store
390                             var query = {};
391                             var linkedItem;
392                             query[fieldmapper.IDL.fmclasses[linkClass].pkey] = ''+self.widgetValue;
393
394                             // find the linked object whose pkey == this widget's value
395                             linkedObjectStore.fetch({
396                                 query: query,
397                                 onComplete: function(list) { linkedItem = list[0]; }
398                             });
399                                                                                                                                                                     
400                             // find the values from the linked item in the remote object store
401                             var format = self.labelFormat[0];
402                             var values = [];
403                             for(var i = 1; i< self.labelFormat.length; i++) 
404                                 values.push(linkedObjectStore.getValue(linkedItem, self.labelFormat[i]));
405                             
406                             // format the label string w/ the extracted linked object values
407                             return dojo.string.substitute(format, values);
408
409                         } catch (E) {
410                             throw new Error('AutoFieldWidget: bad label format [' + format + ':' + values + ']  ' + E);
411                         }
412                     };
413                 }
414             }
415             */
416
417             var oncomplete = function(list) {
418
419                 if(self.labelFormat) 
420                     self.widget.labelAttr = '_label';
421
422                 if(list) {
423                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
424
425                     if(self.labelFormat) {
426                         var format = self.labelFormat[0];
427
428                         dojo.forEach(storeData.data.items, 
429                             function(item) {
430                                 var values = [];
431                                 for(var i = 1; i< self.labelFormat.length; i++) 
432                                     values.push(item[self.labelFormat[i]]);
433                                 item._label = dojo.string.substitute(format, values);
434                             }
435                         );
436                         console.log(js2JSON(storeData));
437                     }
438
439                     self.widget.store = new dojo.data.ItemFileReadStore(storeData);
440                     self.cache[self.auth].list[linkClass] = self.widget.store;
441                 } else {
442                     self.widget.store = self.cache[self.auth].list[linkClass];
443                 }
444                 self.widget.startup();
445                 self._widgetLoaded();
446             };
447
448             if(this.cache[self.auth].list[linkClass]) {
449                 oncomplete();
450
451             } else {
452                 var _cb = function(r) {
453                     oncomplete(openils.Util.readResponse(r, false, true));
454                 };
455                 if (this.searchFilter) {
456                     new openils.PermaCrud().search(linkClass, this.searchFilter, {
457                         async : !this.forceSync, oncomplete : _cb
458                     });
459                 } else {
460                     new openils.PermaCrud().retrieveAll(linkClass, {
461                         async : !this.forceSync, oncomplete : _cb
462                     });
463                 }
464             }
465
466             return true;
467         },
468
469         /**
470          * For widgets that run asynchronously, provide a callback for finishing up
471          */
472         _widgetLoaded : function(value) {
473             
474             if(this.readOnly) {
475
476                 /* -------------------------------------------------------------
477                    when using widgets in a grid, the cell may dissapear, which 
478                    kills the underlying DOM node, which causes this to fail.
479                    For now, back out gracefully and let grid getters use
480                    getDisplayString() instead
481                   -------------------------------------------------------------*/
482                 try { 
483                     this.baseWidgetValue(this.getDisplayString());
484                 } catch (E) {};
485
486             } else {
487
488                 this.baseWidgetValue(this.widgetValue);
489                 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
490                     this.widget.attr('disabled', true); 
491                 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
492                     this.widget.attr('disabled', true); 
493             }
494             if(this.onload)
495                 this.onload(this.widget, this);
496         },
497
498         _buildOrgSelector : function() {
499             dojo.require('fieldmapper.OrgUtils');
500             dojo.require('openils.widget.FilteringTreeSelect');
501             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
502             this.widget.searchAttr = 'shortname';
503             this.widget.labelAttr = 'shortname';
504             this.widget.parentField = 'parent_ou';
505             var user = new openils.User();
506
507             if(this.widgetValue == null && this.orgDefaultsToWs) 
508                 this.widgetValue = user.user.ws_ou();
509             
510             // if we have a limit perm, find the relevent orgs (async)
511             if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
512                 this.async = true;
513                 var self = this;
514                 user.getPermOrgList(this.orgLimitPerms, 
515                     function(orgList) {
516                         self.widget.tree = orgList;
517                         self.widget.startup();
518                         self._widgetLoaded();
519                     }
520                 );
521
522             } else {
523                 this.widget.tree = fieldmapper.aou.globalOrgTree;
524                 this.widget.startup();
525             }
526
527             return true;
528         },
529
530         _buildPermGrpSelector : function() {
531             dojo.require('openils.widget.FilteringTreeSelect');
532             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
533             this.widget.searchAttr = 'name';
534
535             if(this.cache.permGrpTree) {
536                 this.widget.tree = this.cache.permGrpTree;
537                 this.widget.startup();
538                 return true;
539             } 
540
541             var self = this;
542             this.async = true;
543             new openils.PermaCrud().retrieveAll('pgt', {
544                 async : !this.forceSync,
545                 oncomplete : function(r) {
546                     var list = openils.Util.readResponse(r, false, true);
547                     if(!list) return;
548                     var map = {};
549                     var root = null;
550                     for(var l in list)
551                         map[list[l].id()] = list[l];
552                     for(var l in list) {
553                         var node = list[l];
554                         var pnode = map[node.parent()];
555                         if(!pnode) {root = node; continue;}
556                         if(!pnode.children()) pnode.children([]);
557                         pnode.children().push(node);
558                     }
559                     self.widget.tree = self.cache.permGrpTree = root;
560                     self.widget.startup();
561                     self._widgetLoaded();
562                 }
563             });
564
565             return true;
566         },
567
568         _buildCopyLocSelector : function() {
569             dojo.require('dijit.form.FilteringSelect');
570             this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
571             this.widget.searchAttr = this.widget.labalAttr = 'name';
572             this.widget.valueAttr = 'id';
573
574             if(this.cache.copyLocStore) {
575                 this.widget.store = this.cache.copyLocStore;
576                 this.widget.startup();
577                 this.async = false;
578                 return true;
579             } 
580
581             // my orgs
582             var ws_ou = openils.User.user.ws_ou();
583             var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
584             orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
585
586             var self = this;
587             new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
588                 async : !this.forceSync,
589                 oncomplete : function(r) {
590                     var list = openils.Util.readResponse(r, false, true);
591                     if(!list) return;
592                     self.widget.store = 
593                         new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
594                     self.cache.copyLocStore = self.widget.store;
595                     self.widget.startup();
596                     self._widgetLoaded();
597                 }
598             });
599
600             return true;
601         }
602     });
603
604     openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
605     openils.widget.AutoFieldWidget.cache = {};
606 }
607