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");
9 dojo.declare('openils.widget.AutoFieldWidget', null, {
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.
38 * labelFormat : [ '${0} (${1})', 'obj_field_1', 'obj_field_2' ]
40 constructor : function(args) {
44 // find the field description in the IDL if not provided
46 this.fmClass = this.fmObject.classname;
47 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
48 this.suppressLinkedFields = args.suppressLinkedFields || [];
50 if(this.selfReference) {
51 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
53 // create a mock-up of the idlField object.
56 'class' : this.fmClass,
65 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
66 var fields = this.fmIDL.fields;
68 if(fields[f].name == this.fmField)
69 this.idlField = fields[f];
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));
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 || {};
86 * Turn the widget-stored value into a value oils understands
88 getFormattedValue : function() {
89 var value = this.baseWidgetValue();
90 switch(this.idlField.datatype) {
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';
101 if(!value) return null;
102 return dojo.date.stamp.toISOString(value);
106 if(isNaN(value)) value = null;
108 return (value === '') ? null : value;
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);
119 * Turn the widget-stored value into something visually suitable
121 getDisplayString : function() {
122 var value = this.widgetValue;
123 switch(this.idlField.datatype) {
128 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
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;
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'});
143 if(value === null || value === undefined) return '';
144 return fieldmapper.aou.findOrgUnit(value).shortname();
147 if(isNaN(value)) value = 0;
149 if(value === undefined || value === null)
155 build : function(onload) {
157 if(this.widgetValue == null)
158 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
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();
169 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
170 this.parentNode = dojo.create('div');
172 this.onload = onload;
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();
180 } else if(this.widgetClass) {
181 dojo.require(this.widgetClass);
182 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
186 switch(this.idlField.datatype) {
189 dojo.require('dijit.form.TextBox');
190 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
194 this._buildOrgSelector();
198 dojo.require('dijit.form.CurrencyTextBox');
199 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
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);
209 dojo.require('dijit.form.NumberTextBox');
210 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
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);
223 dojo.require('dijit.form.FilteringSelect');
224 var store = new dojo.data.ItemFileReadStore({
226 identifier : 'value',
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'}
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';
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);
249 if(this._buildLinkSelector()) break;
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);
256 dojo.require('dijit.form.TextBox');
257 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
262 if(!this.async) this._widgetLoaded();
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
269 _tryLinkedDisplayField : function(noAsync) {
271 if(this.idlField.datatype == 'org_unit')
272 return false; // we already handle org_units, no need to re-fetch
274 // user opted to bypass fetching this linked data
275 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
278 var linkInfo = this._getLinkSelector();
279 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
281 var lclass = linkInfo.linkClass;
283 if(lclass == 'aou') {
284 this.widgetValue = fieldmapper.aou.findOrgUnit(this.widgetValue).shortname();
288 // first try the store cache
290 if(this.cache[this.auth].list[lclass]) {
291 var store = this.cache[this.auth].list[lclass];
293 query[linkInfo.vfield.name] = ''+this.widgetValue;
294 store.fetch({query:query, onComplete:
296 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
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];
308 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
310 // if those fail, fetch the linked object
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]();
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;
323 self.widgetValue = newvalue;
324 self.widget.startup();
325 self._widgetLoaded();
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;
337 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
339 for(var f in rclassIdl.fields) {
340 if(this.idlField.key == rclassIdl.fields[f].name) {
341 vfield = rclassIdl.fields[f];
347 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
350 linkClass : linkClass,
355 _buildLinkSelector : function() {
357 var selectorInfo = this._getLinkSelector();
358 if(!selectorInfo) return false;
360 var linkClass = selectorInfo.linkClass;
361 var vfield = selectorInfo.vfield;
365 if(linkClass == 'pgt')
366 return this._buildPermGrpSelector();
367 if(linkClass == 'aou')
368 return this._buildOrgSelector();
369 if(linkClass == 'acpl')
370 return this._buildCopyLocSelector();
373 dojo.require('dojo.data.ItemFileReadStore');
374 dojo.require('dijit.form.FilteringSelect');
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;
381 var setLabelFunc = function(linkedObjectStore) {
382 self.widget.labelFunc = function(val) { return 'FOO'} ;
384 if(self.labelFormat) {
385 self.widget.labelFunc = function(val) {
389 // find the linked item in the remote object store
392 query[fieldmapper.IDL.fmclasses[linkClass].pkey] = ''+self.widgetValue;
394 // find the linked object whose pkey == this widget's value
395 linkedObjectStore.fetch({
397 onComplete: function(list) { linkedItem = list[0]; }
400 // find the values from the linked item in the remote object store
401 var format = self.labelFormat[0];
403 for(var i = 1; i< self.labelFormat.length; i++)
404 values.push(linkedObjectStore.getValue(linkedItem, self.labelFormat[i]));
406 // format the label string w/ the extracted linked object values
407 return dojo.string.substitute(format, values);
410 throw new Error('AutoFieldWidget: bad label format [' + format + ':' + values + '] ' + E);
417 var oncomplete = function(list) {
420 self.widget.labelAttr = '_label';
423 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
425 if(self.labelFormat) {
426 var format = self.labelFormat[0];
428 dojo.forEach(storeData.data.items,
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);
436 console.log(js2JSON(storeData));
439 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
440 self.cache[self.auth].list[linkClass] = self.widget.store;
442 self.widget.store = self.cache[self.auth].list[linkClass];
444 self.widget.startup();
445 self._widgetLoaded();
448 if(this.cache[self.auth].list[linkClass]) {
452 var _cb = function(r) {
453 oncomplete(openils.Util.readResponse(r, false, true));
455 if (this.searchFilter) {
456 new openils.PermaCrud().search(linkClass, this.searchFilter, {
457 async : !this.forceSync, oncomplete : _cb
460 new openils.PermaCrud().retrieveAll(linkClass, {
461 async : !this.forceSync, oncomplete : _cb
470 * For widgets that run asynchronously, provide a callback for finishing up
472 _widgetLoaded : function(value) {
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 -------------------------------------------------------------*/
483 this.baseWidgetValue(this.getDisplayString());
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);
495 this.onload(this.widget, this);
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();
507 if(this.widgetValue == null && this.orgDefaultsToWs)
508 this.widgetValue = user.user.ws_ou();
510 // if we have a limit perm, find the relevent orgs (async)
511 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
514 user.getPermOrgList(this.orgLimitPerms,
516 self.widget.tree = orgList;
517 self.widget.startup();
518 self._widgetLoaded();
523 this.widget.tree = fieldmapper.aou.globalOrgTree;
524 this.widget.startup();
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';
535 if(this.cache.permGrpTree) {
536 this.widget.tree = this.cache.permGrpTree;
537 this.widget.startup();
543 new openils.PermaCrud().retrieveAll('pgt', {
544 async : !this.forceSync,
545 oncomplete : function(r) {
546 var list = openils.Util.readResponse(r, false, true);
551 map[list[l].id()] = 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);
559 self.widget.tree = self.cache.permGrpTree = root;
560 self.widget.startup();
561 self._widgetLoaded();
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';
574 if(this.cache.copyLocStore) {
575 this.widget.store = this.cache.copyLocStore;
576 this.widget.startup();
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() }));
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);
593 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
594 self.cache.copyLocStore = self.widget.store;
595 self.widget.startup();
596 self._widgetLoaded();
604 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
605 openils.widget.AutoFieldWidget.cache = {};