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 this.widget.attr("value", this.widgetValue);
190 this.widgetValue = this.shove.create;
191 this._widgetLoaded();
192 } else if (this.widget.attr("value") == null) {
193 this._widgetLoaded();
198 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
199 this.parentNode = dojo.create('div');
201 this.onload = onload;
204 dojo.require('dijit.layout.ContentPane');
205 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
206 if(this.widgetValue !== null)
207 this._tryLinkedDisplayField();
209 } else if(this.widgetClass) {
210 dojo.require(this.widgetClass);
211 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
215 switch(this.idlField.datatype) {
218 dojo.require('dijit.form.TextBox');
219 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
223 this._buildOrgSelector();
227 // dojo.require('dijit.form.CurrencyTextBox');
228 // the CurrencyTextBox dijit is broken in Dojo 1.3; upon upgrading
229 // to Dojo 1.5 or later, should re-evaluate work-around use of dijit.form.NumberTextBox.
230 // See https://bugs.launchpad.net/evergreen/+bug/702117
231 dojo.require('dijit.form.NumberTextBox');
232 this.dijitArgs = dojo.mixin({constraints:{places:'0,2'}}, this.dijitArgs || {});
233 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
237 dojo.require('dijit.form.NumberTextBox');
238 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
239 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
243 dojo.require('dijit.form.NumberTextBox');
244 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
248 dojo.require('dijit.form.DateTextBox');
249 dojo.require('dojo.date.stamp');
250 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
251 if (this.widgetValue != null) {
252 this.widgetValue = openils.Util.timeStampAsDateObj(
260 dojo.require('dijit.form.FilteringSelect');
261 var store = new dojo.data.ItemFileReadStore({
263 identifier : 'value',
265 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
266 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
267 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
271 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
272 this.widget.searchAttr = this.widget.labelAttr = 'label';
273 this.widget.valueAttr = 'value';
274 this.widget.store = store;
275 this.widget.startup();
276 this.widgetValue = (this.widgetValue === null) ? 'unset' :
277 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
279 dojo.require('dijit.form.CheckBox');
280 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
281 this.widgetValue = openils.Util.isTrue(this.widgetValue);
286 if(this._buildLinkSelector()) break;
289 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
290 dojo.require('dijit.form.ValidationTextBox');
291 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
293 dojo.require('dijit.form.TextBox');
294 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
299 if(!this.async) this._widgetLoaded();
303 // we want to display the value for our widget. However, instead of displaying
304 // an ID, for exmaple, display the value for the 'selector' field on the object
306 _tryLinkedDisplayField : function(noAsync) {
308 if(this.idlField.datatype == 'org_unit')
309 return false; // we already handle org_units, no need to re-fetch
311 // user opted to bypass fetching this linked data
312 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
315 var linkInfo = this._getLinkSelector();
316 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
318 var lclass = linkInfo.linkClass;
323 // first try the store cache
325 if(this.cache[this.auth].list[lclass]) {
326 var store = this.cache[this.auth].list[lclass];
328 query[linkInfo.vfield.name] = ''+this.widgetValue;
330 store.fetch({query:query, onComplete:
333 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
342 // then try the single object cache
343 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
344 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
348 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
350 // if those fail, fetch the linked object
353 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
354 async : !this.forceSync,
355 oncomplete : function(r) {
356 var item = openils.Util.readResponse(r);
357 var newvalue = item[linkInfo.vfield.selector]();
359 if(!self.cache[self.auth].single[lclass])
360 self.cache[self.auth].single[lclass] = {};
361 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
363 self.widgetValue = newvalue;
364 self.widget.startup();
365 self._widgetLoaded();
370 _getLinkSelector : function() {
371 var linkClass = this.idlField['class'];
372 if(this.idlField.reltype != 'has_a') return false;
373 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
374 fieldmapper.IDL.load([linkClass]);
375 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
376 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
379 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
381 for(var f in rclassIdl.fields) {
382 if(this.idlField.key == rclassIdl.fields[f].name) {
383 vfield = rclassIdl.fields[f];
389 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
392 linkClass : linkClass,
397 _buildLinkSelector : function() {
399 var selectorInfo = this._getLinkSelector();
400 if(!selectorInfo) return false;
402 var linkClass = selectorInfo.linkClass;
403 var vfield = selectorInfo.vfield;
407 if(linkClass == 'pgt')
408 return this._buildPermGrpSelector();
409 if(linkClass == 'aou')
410 return this._buildOrgSelector();
411 if(linkClass == 'acpl')
412 return this._buildCopyLocSelector();
413 if(linkClass == 'acqpro')
414 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
417 dojo.require('dojo.data.ItemFileReadStore');
418 dojo.require('dijit.form.FilteringSelect');
420 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
421 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
422 this.widget.valueAttr = vfield.name;
423 this.widget.attr('disabled', true);
425 var oncomplete = function(list) {
426 self.widget.attr('disabled', false);
429 self.widget.labelAttr = '_label';
431 if(self.searchFormat)
432 self.widget.searchAttr = '_search';
434 function formatString(item, formatList) {
438 // formatList[1..*] are names of fields. Pull the field
439 // values from each object to determine the values for string substitution
441 var format = formatList[0];
442 for(var i = 1; i< formatList.length; i++)
443 values.push(item[formatList[i]]);
445 return dojo.string.substitute(format, values);
449 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
455 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
457 if(self.labelFormat) {
458 dojo.forEach(storeData.data.items,
460 item._label = formatString(item, self.labelFormat);
465 if(self.searchFormat) {
466 dojo.forEach(storeData.data.items,
468 item._search = formatString(item, self.searchFormat);
473 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
474 self.cache[self.auth].list[linkClass] = self.widget.store;
477 self.widget.store = self.cache[self.auth].list[linkClass];
480 self.widget.startup();
481 self._widgetLoaded();
484 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
489 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
490 this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
492 if(this.dataLoader) {
494 // caller provided an external function for retrieving the data
495 this.dataLoader(linkClass, this.searchFilter, oncomplete);
499 var _cb = function(r) {
500 oncomplete(openils.Util.readResponse(r, false, true));
503 if (this.searchFilter) {
504 new openils.PermaCrud().search(linkClass, this.searchFilter, {
505 async : !this.forceSync, oncomplete : _cb
508 new openils.PermaCrud().retrieveAll(linkClass, {
509 async : !this.forceSync, oncomplete : _cb
519 * For widgets that run asynchronously, provide a callback for finishing up
521 _widgetLoaded : function(value) {
525 /* -------------------------------------------------------------
526 when using widgets in a grid, the cell may dissapear, which
527 kills the underlying DOM node, which causes this to fail.
528 For now, back out gracefully and let grid getters use
529 getDisplayString() instead
530 -------------------------------------------------------------*/
532 this.baseWidgetValue(this.getDisplayString());
537 this.baseWidgetValue(this.widgetValue);
538 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
539 this.widget.attr('disabled', true);
540 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
541 this.widget.attr('disabled', true);
544 this.onload(this.widget, this);
546 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
547 // a required dijit is not given any styling to indicate the value
548 // is invalid until the user has focused the widget then left it with
549 // invalid data. This change tells dojo to pretend this focusing has
550 // already happened so we can style required widgets during page render.
551 this.widget._hasBeenBlurred = true;
552 this.widget.validate();
556 _buildOrgSelector : function() {
557 dojo.require('fieldmapper.OrgUtils');
558 dojo.require('openils.widget.FilteringTreeSelect');
559 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
560 this.widget.searchAttr = 'shortname';
561 this.widget.labelAttr = 'shortname';
562 this.widget.parentField = 'parent_ou';
563 var user = new openils.User();
565 if(this.widgetValue == null && this.orgDefaultsToWs)
566 this.widgetValue = user.user.ws_ou();
568 // if we have a limit perm, find the relevent orgs (async)
569 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
572 user.getPermOrgList(this.orgLimitPerms,
574 self.widget.tree = orgList;
575 self.widget.startup();
576 self._widgetLoaded();
581 this.widget.tree = fieldmapper.aou.globalOrgTree;
582 this.widget.startup();
588 _buildPermGrpSelector : function() {
589 dojo.require('openils.widget.FilteringTreeSelect');
590 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
591 this.widget.disableQuery = this.disableQuery;
592 this.widget.searchAttr = 'name';
594 if(this.cache.permGrpTree) {
595 this.widget.tree = this.cache.permGrpTree;
596 this.widget.startup();
597 this._widgetLoaded();
603 new openils.PermaCrud().retrieveAll('pgt', {
604 async : !this.forceSync,
605 oncomplete : function(r) {
606 var list = openils.Util.readResponse(r, false, true);
611 map[list[l].id()] = list[l];
614 var pnode = map[node.parent()];
615 if(!pnode) {root = node; continue;}
616 if(!pnode.children()) pnode.children([]);
617 pnode.children().push(node);
619 self.widget.tree = self.cache.permGrpTree = root;
620 self.widget.startup();
621 self._widgetLoaded();
628 _buildCopyLocSelector : function() {
629 dojo.require('dijit.form.FilteringSelect');
630 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
631 this.widget.searchAttr = this.widget.labalAttr = 'name';
632 this.widget.valueAttr = 'id';
634 if(this.cache.copyLocStore) {
635 this.widget.store = this.cache.copyLocStore;
636 this.widget.startup();
642 var ws_ou = openils.User.user.ws_ou();
643 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
644 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
647 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
648 async : !this.forceSync,
649 oncomplete : function(r) {
650 var list = openils.Util.readResponse(r, false, true);
653 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
654 self.cache.copyLocStore = self.widget.store;
655 self.widget.startup();
656 self._widgetLoaded();
663 _buildAutoCompleteSelector : function(linkClass, searchAttr) {
664 dojo.require("openils.widget.PCrudAutocompleteBox");
665 dojo.mixin(this.dijitArgs, {
667 searchAttr : searchAttr,
669 this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
670 this._widgetLoaded();
675 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
676 openils.widget.AutoFieldWidget.cache = {};
677 openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};