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' ]
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.
50 constructor : function(args) {
54 if (!this.dijitArgs) {
57 this.dijitArgs['scrollOnFocus'] = false;
60 // find the field description in the IDL if not provided
62 this.fmClass = this.fmObject.classname;
63 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
65 if(this.fmClass && !this.fmIDL) {
66 fieldmapper.IDL.load([this.fmClass]);
67 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
70 this.suppressLinkedFields = args.suppressLinkedFields || [];
72 if(this.selfReference) {
73 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
75 // create a mock-up of the idlField object.
78 'class' : this.fmClass,
87 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
88 var fields = this.fmIDL.fields;
90 if(fields[f].name == this.fmField)
91 this.idlField = fields[f];
96 throw new Error("AutoFieldWidget could not determine which " +
97 "field to render. We need more information. fmClass=" +
98 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
100 this.auth = openils.User.authtoken;
101 this.cache = openils.widget.AutoFieldWidget.cache;
102 this.cache[this.auth] = this.cache[this.auth] || {};
103 this.cache[this.auth].single = this.cache[this.auth].single || {};
104 this.cache[this.auth].list = this.cache[this.auth].list || {};
108 * Turn the widget-stored value into a value oils understands
110 getFormattedValue : function() {
111 var value = this.baseWidgetValue();
112 switch(this.idlField.datatype) {
115 case 'true': return 't';
116 case 'on': return 't';
117 case 'false' : return 'f';
118 case 'unset' : return null;
119 case true : return 't';
123 if(!value) return null;
124 return dojo.date.stamp.toISOString(value);
128 if(isNaN(value)) value = null;
130 return (value === '') ? null : value;
134 baseWidgetValue : function(value) {
135 var attr = (this.readOnly) ? 'content' : 'value';
136 if(arguments.length) this.widget.attr(attr, value);
137 return this.widget.attr(attr);
141 * Turn the widget-stored value into something visually suitable
143 getDisplayString : function() {
144 var value = this.widgetValue;
145 switch(this.idlField.datatype) {
150 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
153 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
155 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
156 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
157 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
160 if (!value) return '';
161 return openils.Util.timeStamp(
162 value, {"formatLength": "short"}
165 if(value === null || value === undefined) return '';
166 return fieldmapper.aou.findOrgUnit(value).shortname();
169 if(isNaN(value)) value = 0;
171 if(value === undefined || value === null)
177 build : function(onload) {
179 if(this.widgetValue == null)
180 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
183 // core widget provided for us, attach and move on
184 if(this.parentNode) // may already be in the "right" place
185 this.parentNode.appendChild(this.widget.domNode);
187 if (this.shove.mode == "update") {
188 if (this.idlField.datatype == "timestamp")
189 this.widgetValue = openils.Util.timeStampAsDateObj(
193 this.widgetValue = this.shove.create;
195 this._widgetLoaded();
196 } else if (this.widget.attr("value") == null) {
197 this._widgetLoaded();
202 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
203 this.parentNode = dojo.create('div');
205 this.onload = onload;
208 dojo.require('dijit.layout.ContentPane');
209 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
210 if(this.widgetValue !== null)
211 this._tryLinkedDisplayField();
213 } else if(this.widgetClass) {
214 dojo.require(this.widgetClass);
215 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
219 switch(this.idlField.datatype) {
222 dojo.require('dijit.form.TextBox');
223 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
227 this._buildOrgSelector();
231 // dojo.require('dijit.form.CurrencyTextBox');
232 // the CurrencyTextBox dijit is broken in Dojo 1.3; upon upgrading
233 // to Dojo 1.5 or later, should re-evaluate work-around use of dijit.form.NumberTextBox.
234 // See https://bugs.launchpad.net/evergreen/+bug/702117
235 dojo.require('dijit.form.NumberTextBox');
236 this.dijitArgs = dojo.mixin({constraints:{places:'0,2'}}, this.dijitArgs || {});
237 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
241 dojo.require('dijit.form.NumberTextBox');
242 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
243 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
247 dojo.require('dijit.form.NumberTextBox');
248 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
252 dojo.require('dijit.form.DateTextBox');
253 dojo.require('dojo.date.stamp');
254 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
255 if (this.widgetValue != null) {
256 this.widgetValue = openils.Util.timeStampAsDateObj(
264 dojo.require('dijit.form.FilteringSelect');
265 var store = new dojo.data.ItemFileReadStore({
267 identifier : 'value',
269 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
270 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
271 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
275 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
276 this.widget.searchAttr = this.widget.labelAttr = 'label';
277 this.widget.valueAttr = 'value';
278 this.widget.store = store;
279 this.widget.startup();
280 this.widgetValue = (this.widgetValue === null) ? 'unset' :
281 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
283 dojo.require('dijit.form.CheckBox');
284 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
285 this.widgetValue = openils.Util.isTrue(this.widgetValue);
290 if(this._buildLinkSelector()) break;
293 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
294 dojo.require('dijit.form.ValidationTextBox');
295 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
297 dojo.require('dijit.form.TextBox');
298 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
303 if(!this.async) this._widgetLoaded();
307 // we want to display the value for our widget. However, instead of displaying
308 // an ID, for exmaple, display the value for the 'selector' field on the object
310 _tryLinkedDisplayField : function(noAsync) {
312 if(this.idlField.datatype == 'org_unit')
313 return false; // we already handle org_units, no need to re-fetch
315 // user opted to bypass fetching this linked data
316 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
319 var linkInfo = this._getLinkSelector();
320 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
322 var lclass = linkInfo.linkClass;
327 // first try the store cache
329 if(this.cache[this.auth].list[lclass]) {
330 var store = this.cache[this.auth].list[lclass];
332 query[linkInfo.vfield.name] = ''+this.widgetValue;
334 store.fetch({query:query, onComplete:
337 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
346 // then try the single object cache
347 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
348 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
352 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
354 // if those fail, fetch the linked object
357 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
358 async : !this.forceSync,
359 oncomplete : function(r) {
360 var item = openils.Util.readResponse(r);
361 var newvalue = item[linkInfo.vfield.selector]();
363 if(!self.cache[self.auth].single[lclass])
364 self.cache[self.auth].single[lclass] = {};
365 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
367 self.widgetValue = newvalue;
368 self.widget.startup();
369 self._widgetLoaded();
374 _getLinkSelector : function() {
375 var linkClass = this.idlField['class'];
376 if(this.idlField.reltype != 'has_a') return false;
377 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
378 fieldmapper.IDL.load([linkClass]);
379 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
380 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
383 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
385 for(var f in rclassIdl.fields) {
386 if(this.idlField.key == rclassIdl.fields[f].name) {
387 vfield = rclassIdl.fields[f];
393 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
396 linkClass : linkClass,
401 _buildLinkSelector : function() {
403 var selectorInfo = this._getLinkSelector();
404 if(!selectorInfo) return false;
406 var linkClass = selectorInfo.linkClass;
407 var vfield = selectorInfo.vfield;
411 if(linkClass == 'pgt')
412 return this._buildPermGrpSelector();
413 if(linkClass == 'aou')
414 return this._buildOrgSelector();
415 if(linkClass == 'acpl')
416 return this._buildCopyLocSelector();
417 if(linkClass == 'acqpro')
418 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
421 dojo.require('dojo.data.ItemFileReadStore');
422 dojo.require('dijit.form.FilteringSelect');
424 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
425 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
426 this.widget.valueAttr = vfield.name;
427 this.widget.attr('disabled', true);
429 var oncomplete = function(list) {
430 self.widget.attr('disabled', false);
433 self.widget.labelAttr = '_label';
435 if(self.searchFormat)
436 self.widget.searchAttr = '_search';
438 function formatString(item, formatList) {
442 // formatList[1..*] are names of fields. Pull the field
443 // values from each object to determine the values for string substitution
445 var format = formatList[0];
446 for(var i = 1; i< formatList.length; i++)
447 values.push(item[formatList[i]]);
449 return dojo.string.substitute(format, values);
453 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
459 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
461 if(self.labelFormat) {
462 dojo.forEach(storeData.data.items,
464 item._label = formatString(item, self.labelFormat);
469 if(self.searchFormat) {
470 dojo.forEach(storeData.data.items,
472 item._search = formatString(item, self.searchFormat);
477 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
478 self.cache[self.auth].list[linkClass] = self.widget.store;
481 self.widget.store = self.cache[self.auth].list[linkClass];
484 self.widget.startup();
485 self._widgetLoaded();
488 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
493 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
494 this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
496 if(this.dataLoader) {
498 // caller provided an external function for retrieving the data
499 this.dataLoader(linkClass, this.searchFilter, oncomplete);
503 var _cb = function(r) {
504 oncomplete(openils.Util.readResponse(r, false, true));
507 if (this.searchFilter) {
508 new openils.PermaCrud().search(linkClass, this.searchFilter, {
509 async : !this.forceSync, oncomplete : _cb
512 new openils.PermaCrud().retrieveAll(linkClass, {
513 async : !this.forceSync, oncomplete : _cb
523 * For widgets that run asynchronously, provide a callback for finishing up
525 _widgetLoaded : function(value) {
529 /* -------------------------------------------------------------
530 when using widgets in a grid, the cell may dissapear, which
531 kills the underlying DOM node, which causes this to fail.
532 For now, back out gracefully and let grid getters use
533 getDisplayString() instead
534 -------------------------------------------------------------*/
536 this.baseWidgetValue(this.getDisplayString());
541 this.baseWidgetValue(this.widgetValue);
542 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
543 this.widget.attr('disabled', true);
544 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
545 this.widget.attr('disabled', true);
548 this.onload(this.widget, this);
550 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
551 // a required dijit is not given any styling to indicate the value
552 // is invalid until the user has focused the widget then left it with
553 // invalid data. This change tells dojo to pretend this focusing has
554 // already happened so we can style required widgets during page render.
555 this.widget._hasBeenBlurred = true;
556 this.widget.validate();
560 _buildOrgSelector : function() {
561 dojo.require('fieldmapper.OrgUtils');
562 dojo.require('openils.widget.FilteringTreeSelect');
563 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
564 this.widget.searchAttr = 'shortname';
565 this.widget.labelAttr = 'shortname';
566 this.widget.parentField = 'parent_ou';
567 var user = new openils.User();
569 if(this.widgetValue == null && this.orgDefaultsToWs)
570 this.widgetValue = user.user.ws_ou();
572 // if we have a limit perm, find the relevent orgs (async)
573 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
576 user.getPermOrgList(this.orgLimitPerms,
578 self.widget.tree = orgList;
579 self.widget.startup();
580 self._widgetLoaded();
585 this.widget.tree = fieldmapper.aou.globalOrgTree;
586 this.widget.startup();
592 _buildPermGrpSelector : function() {
593 dojo.require('openils.widget.FilteringTreeSelect');
594 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
595 this.widget.disableQuery = this.disableQuery;
596 this.widget.searchAttr = 'name';
598 if(this.cache.permGrpTree) {
599 this.widget.tree = this.cache.permGrpTree;
600 this.widget.startup();
601 this._widgetLoaded();
607 new openils.PermaCrud().retrieveAll('pgt', {
608 async : !this.forceSync,
609 oncomplete : function(r) {
610 var list = openils.Util.readResponse(r, false, true);
615 map[list[l].id()] = list[l];
618 var pnode = map[node.parent()];
619 if(!pnode) {root = node; continue;}
620 if(!pnode.children()) pnode.children([]);
621 pnode.children().push(node);
623 self.widget.tree = self.cache.permGrpTree = root;
624 self.widget.startup();
625 self._widgetLoaded();
632 _buildCopyLocSelector : function() {
633 dojo.require('dijit.form.FilteringSelect');
634 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
635 this.widget.searchAttr = this.widget.labalAttr = 'name';
636 this.widget.valueAttr = 'id';
638 if(this.cache.copyLocStore) {
639 this.widget.store = this.cache.copyLocStore;
640 this.widget.startup();
646 var ws_ou = openils.User.user.ws_ou();
647 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
648 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
651 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
652 async : !this.forceSync,
653 oncomplete : function(r) {
654 var list = openils.Util.readResponse(r, false, true);
657 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
658 self.cache.copyLocStore = self.widget.store;
659 self.widget.startup();
660 self._widgetLoaded();
667 _buildAutoCompleteSelector : function(linkClass, searchAttr) {
668 dojo.require("openils.widget.PCrudAutocompleteBox");
669 dojo.mixin(this.dijitArgs, {
671 searchAttr : searchAttr,
673 this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
674 this._widgetLoaded();
679 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
680 openils.widget.AutoFieldWidget.cache = {};
681 openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};