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.
49 * useWriteStore : tells AFW to use a dojo.data.ItemFileWriteStore instead of a ReadStore for
50 * data stores created with dynamic data. This allows the caller to add/remove items from
53 constructor : function(args) {
57 if (!this.dijitArgs) {
60 this.dijitArgs['scrollOnFocus'] = false;
63 // find the field description in the IDL if not provided
65 this.fmClass = this.fmObject.classname;
66 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
68 if(this.fmClass && !this.fmIDL) {
69 fieldmapper.IDL.load([this.fmClass]);
70 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
73 this.suppressLinkedFields = args.suppressLinkedFields || [];
75 if(this.selfReference) {
76 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
78 // create a mock-up of the idlField object.
81 'class' : this.fmClass,
90 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
91 var fields = this.fmIDL.fields;
93 if(fields[f].name == this.fmField)
94 this.idlField = fields[f];
99 throw new Error("AutoFieldWidget could not determine which " +
100 "field to render. We need more information. fmClass=" +
101 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
103 this.auth = openils.User.authtoken;
104 this.cache = openils.widget.AutoFieldWidget.cache;
105 this.cache[this.auth] = this.cache[this.auth] || {};
106 this.cache[this.auth].single = this.cache[this.auth].single || {};
107 this.cache[this.auth].list = this.cache[this.auth].list || {};
109 if (this.useWriteStore) {
110 dojo.require('dojo.data.ItemFileWriteStore');
111 this.storeConstructor = dojo.data.ItemFileWriteStore;
113 this.storeConstructor = dojo.data.ItemFileReadStore;
118 * Turn the widget-stored value into a value oils understands
120 getFormattedValue : function() {
121 var value = this.baseWidgetValue();
122 switch(this.idlField.datatype) {
125 case 'true': return 't';
126 case 'on': return 't';
127 case 'false' : return 'f';
128 case 'unset' : return null;
129 case true : return 't';
133 if(!value) return null;
134 return dojo.date.stamp.toISOString(value);
138 if(isNaN(value)) value = null;
140 return (value === '') ? null : value;
144 baseWidgetValue : function(value) {
145 var attr = (this.readOnly) ? 'content' : 'value';
146 if(arguments.length) this.widget.attr(attr, value);
147 return this.widget.attr(attr);
151 * Turn the widget-stored value into something visually suitable
153 getDisplayString : function() {
154 var value = this.widgetValue;
160 return openils.widget.AutoFieldWidget.localeStrings.INHERITED;
163 switch(this.idlField.datatype) {
168 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
171 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
173 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
174 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
175 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
178 if (!value) return '';
179 return openils.Util.timeStamp(
180 value, {"formatLength": "short"}
183 if(value === null || value === undefined) return '';
184 return fieldmapper.aou.findOrgUnit(value).shortname();
187 if(isNaN(value)) value = 0;
189 if(value === undefined || value === null)
195 isRequired : function() {
198 this.idlField.required || (
200 this.dijitArgs.required || this.dijitArgs.regExp
207 build : function(onload) {
209 if(this.widgetValue == null)
210 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
213 // core widget provided for us, attach and move on
214 if(this.parentNode) // may already be in the "right" place
215 this.parentNode.appendChild(this.widget.domNode);
217 if (this.shove.mode == "update") {
218 if (this.idlField.datatype == "timestamp")
219 this.widgetValue = openils.Util.timeStampAsDateObj(
223 this.widgetValue = this.shove.create;
225 this._widgetLoaded();
226 } else if (this.widget.attr("value") == null) {
227 this._widgetLoaded();
232 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
233 this.parentNode = dojo.create('div');
235 this.onload = onload;
238 dojo.require('dijit.layout.ContentPane');
239 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
240 if(this.widgetValue !== null)
241 this._tryLinkedDisplayField();
243 } else if(this.widgetClass) {
244 dojo.require(this.widgetClass);
245 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
249 switch(this.idlField.datatype) {
252 dojo.require('dijit.form.TextBox');
253 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
257 this._buildOrgSelector();
261 // dojo.require('dijit.form.CurrencyTextBox');
262 // the CurrencyTextBox dijit is broken in Dojo 1.3; upon upgrading
263 // to Dojo 1.5 or later, should re-evaluate work-around use of dijit.form.NumberTextBox.
264 // See https://bugs.launchpad.net/evergreen/+bug/702117
265 dojo.require('dijit.form.NumberTextBox');
266 this.dijitArgs = dojo.mixin({constraints:{places:'0,2'}}, this.dijitArgs || {});
267 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
271 dojo.require('dijit.form.NumberTextBox');
272 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
273 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
277 dojo.require('dijit.form.NumberTextBox');
278 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
282 dojo.require('dijit.form.DateTextBox');
283 dojo.require('dojo.date.stamp');
284 if(!this.dijitArgs.constraints) {
285 this.dijitArgs.constraints = {};
287 if(!this.dijitArgs.constraints.datePattern) {
288 var user = new openils.User().user;
290 var datePattern = fieldmapper.aou.fetchOrgSettingDefault(user.ws_ou(), 'format.date');
291 if(datePattern) this.dijitArgs.constraints.datePattern = datePattern.value;
294 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
295 if (this.widgetValue != null) {
296 this.widgetValue = openils.Util.timeStampAsDateObj(
303 if(this.ternary || this.inherits) {
304 dojo.require('dijit.form.FilteringSelect');
305 var store = new dojo.data.ItemFileReadStore({
307 identifier : 'value',
309 {label : (this.inherits ? openils.widget.AutoFieldWidget.localeStrings.INHERITED : openils.widget.AutoFieldWidget.localeStrings.UNSET), value : 'unset'},
310 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
311 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
315 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
316 this.widget.searchAttr = this.widget.labelAttr = 'label';
317 this.widget.valueAttr = 'value';
318 this.widget.store = store;
319 this.widget.startup();
320 this.widgetValue = (this.widgetValue === null) ? 'unset' :
321 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
323 dojo.require('dijit.form.CheckBox');
324 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
325 this.widgetValue = openils.Util.isTrue(this.widgetValue);
330 if(this._buildLinkSelector()) break;
333 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
334 dojo.require('dijit.form.ValidationTextBox');
335 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
337 dojo.require('dijit.form.TextBox');
338 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
343 if(!this.async) this._widgetLoaded();
347 // we want to display the value for our widget. However, instead of displaying
348 // an ID, for exmaple, display the value for the 'selector' field on the object
350 _tryLinkedDisplayField : function(noAsync) {
352 if(this.idlField.datatype == 'org_unit')
353 return false; // we already handle org_units, no need to re-fetch
355 // user opted to bypass fetching this linked data
356 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
359 var linkInfo = this._getLinkSelector();
360 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
362 var lclass = linkInfo.linkClass;
367 // first try the store cache
369 if(this.cache[this.auth].list[lclass]) {
370 var store = this.cache[this.auth].list[lclass];
372 query[linkInfo.vfield.name] = ''+this.widgetValue;
374 store.fetch({query:query, onComplete:
378 if(self.labelFormat) {
379 self.widgetValue = self._applyLabelFormat(item, self.labelFormat);
381 self.widgetValue = store.getValue(item, linkInfo.vfield.selector);
391 // then try the single object cache
392 if(this.cache[this.auth].single[lclass] &&
393 this.cache[this.auth].single[lclass][this.widgetValue] &&
394 this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || '']) {
395 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || ''];
399 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
401 // if those fail, fetch the linked object
404 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
405 async : !this.forceSync,
406 oncomplete : function(r) {
407 var item = openils.Util.readResponse(r);
409 var newvalue = item[linkInfo.vfield.selector]();
411 var labelCacheKey = '';
413 if(self.labelFormat) {
414 labelCacheKey = self.labelFormat;
415 self.widgetValue = self._applyLabelFormat(item.toStoreItem(), self.labelFormat);
417 self.widgetValue = newvalue;
420 if(!self.cache[self.auth].single[lclass])
421 self.cache[self.auth].single[lclass] = {};
422 if(!self.cache[self.auth].single[lclass][self.widgetValue])
423 self.cache[self.auth].single[lclass][self.widgetValue] = {};
424 self.cache[self.auth].single[lclass][self.widgetValue][labelCacheKey] = newvalue;
426 self.widget.startup();
427 self._widgetLoaded();
432 _getLinkSelector : function() {
433 var linkClass = this.idlField['class'];
434 if(this.idlField.reltype != 'has_a') return false;
435 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
436 fieldmapper.IDL.load([linkClass]);
437 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
438 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
441 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
443 for(var f in rclassIdl.fields) {
444 if(this.idlField.key == rclassIdl.fields[f].name) {
445 vfield = rclassIdl.fields[f];
451 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
454 linkClass : linkClass,
459 _applyLabelFormat : function (item, formatList) {
463 // formatList[1..*] are names of fields. Pull the field
464 // values from each object to determine the values for string substitution
466 var format = formatList[0];
467 for(var i = 1; i< formatList.length; i++)
468 values.push(item[formatList[i]]);
470 return dojo.string.substitute(format, values);
474 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
478 _buildLinkSelector : function() {
480 var selectorInfo = this._getLinkSelector();
481 if(!selectorInfo) return false;
483 var linkClass = selectorInfo.linkClass;
484 var vfield = selectorInfo.vfield;
488 if(linkClass == 'pgt')
489 return this._buildPermGrpSelector();
490 if(linkClass == 'aou')
491 return this._buildOrgSelector();
492 if(linkClass == 'acpl')
493 return this._buildCopyLocSelector();
494 if(linkClass == 'acqpro')
495 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
498 dojo.require('dojo.data.ItemFileReadStore');
499 dojo.require('dijit.form.FilteringSelect');
501 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
502 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
503 this.widget.valueAttr = vfield.name;
504 this.widget.attr('disabled', true);
506 var oncomplete = function(list) {
507 self.widget.attr('disabled', false);
510 self.widget.labelAttr = '_label';
512 if(self.searchFormat)
513 self.widget.searchAttr = '_search';
516 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
518 if(self.labelFormat) {
519 dojo.forEach(storeData.data.items,
521 item._label = self._applyLabelFormat(item, self.labelFormat);
526 if(self.searchFormat) {
527 dojo.forEach(storeData.data.items,
529 item._search = self._applyLabelFormat(item, self.searchFormat);
534 self.widget.store = new self.storeConstructor(storeData);
535 self.cache[self.auth].list[linkClass] = self.widget.store;
538 self.widget.store = self.cache[self.auth].list[linkClass];
541 self.widget.startup();
542 self._widgetLoaded();
545 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
550 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
551 this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
553 if(this.dataLoader) {
555 // caller provided an external function for retrieving the data
556 this.dataLoader(linkClass, this.searchFilter, oncomplete);
560 var _cb = function(r) {
561 oncomplete(openils.Util.readResponse(r, false, true));
564 this.searchOptions = dojo.mixin(
565 {async : !this.forceSync, oncomplete : _cb},
569 if (this.searchFilter) {
570 new openils.PermaCrud().search(linkClass, this.searchFilter, this.searchOptions);
572 new openils.PermaCrud().retrieveAll(linkClass, this.searchOptions);
581 * For widgets that run asynchronously, provide a callback for finishing up
583 _widgetLoaded : function(value) {
587 /* -------------------------------------------------------------
588 when using widgets in a grid, the cell may dissapear, which
589 kills the underlying DOM node, which causes this to fail.
590 For now, back out gracefully and let grid getters use
591 getDisplayString() instead
592 -------------------------------------------------------------*/
594 this.baseWidgetValue(this.getDisplayString());
599 this.baseWidgetValue(this.widgetValue);
600 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
601 this.widget.attr('disabled', true);
602 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
603 this.widget.attr('disabled', true);
606 this.onload(this.widget, this);
608 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
609 // a required dijit is not given any styling to indicate the value
610 // is invalid until the user has focused the widget then left it with
611 // invalid data. This change tells dojo to pretend this focusing has
612 // already happened so we can style required widgets during page render.
613 this.widget._hasBeenBlurred = true;
614 if(this.widget.validate)
615 this.widget.validate();
619 _buildOrgSelector : function() {
620 dojo.require('fieldmapper.OrgUtils');
621 dojo.require('openils.widget.FilteringTreeSelect');
622 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
623 this.widget.searchAttr = this.searchAttr || 'shortname';
624 this.widget.labelAttr = this.searchAttr || 'shortname';
625 this.widget.parentField = 'parent_ou';
626 var user = new openils.User();
628 if(this.widgetValue == null && this.orgDefaultsToWs)
629 this.widgetValue = user.user.ws_ou();
631 // if we have a limit perm, find the relevent orgs (async)
632 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
635 user.getPermOrgList(this.orgLimitPerms,
637 self.widget.tree = orgList;
638 self.widget.startup();
639 self._widgetLoaded();
644 this.widget.tree = fieldmapper.aou.globalOrgTree;
645 this.widget.startup();
651 _buildPermGrpSelector : function() {
652 dojo.require('openils.widget.FilteringTreeSelect');
653 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
654 this.widget.disableQuery = this.disableQuery;
655 this.widget.searchAttr = 'name';
657 if(this.cache.permGrpTree) {
658 this.widget.tree = this.cache.permGrpTree;
659 this.widget.startup();
660 this._widgetLoaded();
666 new openils.PermaCrud().retrieveAll('pgt', {
667 async : !this.forceSync,
668 oncomplete : function(r) {
669 var list = openils.Util.readResponse(r, false, true);
674 map[list[l].id()] = list[l];
677 var pnode = map[node.parent()];
678 if(!pnode) {root = node; continue;}
679 if(!pnode.children()) pnode.children([]);
680 pnode.children().push(node);
682 self.widget.tree = self.cache.permGrpTree = root;
683 self.widget.startup();
684 self._widgetLoaded();
691 _buildCopyLocSelector : function() {
692 dojo.require('dijit.form.FilteringSelect');
693 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
694 this.widget.searchAttr = this.widget.labalAttr = 'name';
695 this.widget.valueAttr = 'id';
697 if(this.cache.copyLocStore) {
698 this.widget.store = this.cache.copyLocStore;
699 this.widget.startup();
705 var ws_ou = openils.User.user.ws_ou();
706 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
707 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
710 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
711 async : !this.forceSync,
712 oncomplete : function(r) {
713 var list = openils.Util.readResponse(r, false, true);
716 new self.storeConstructor({data:fieldmapper.acpl.toStoreData(list)});
717 self.cache.copyLocStore = self.widget.store;
718 self.widget.startup();
719 self._widgetLoaded();
726 _buildAutoCompleteSelector : function(linkClass, searchAttr) {
727 dojo.require("openils.widget.PCrudAutocompleteBox");
728 dojo.mixin(this.dijitArgs, {
730 searchAttr : searchAttr,
732 this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
733 this._widgetLoaded();
738 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
739 openils.widget.AutoFieldWidget.cache = {};
740 openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};