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 // plug in fix for Dojo bug http://bugs.dojotoolkit.org/ticket/9438
229 // to allow entry of negative values; won't be needed after we upgrade
231 dojo.extend(dijit.form.CurrencyTextBox, {
232 regExpGen: function(constraints){
233 return this._focused ?
234 dojo.number.regexp(dojo.mixin(dojo.mixin(this.editOptions, constraints), {type: 'decimal'})) :
235 dojo.currency.regexp(constraints);
238 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
242 dojo.require('dijit.form.NumberTextBox');
243 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
244 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
248 dojo.require('dijit.form.NumberTextBox');
249 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
253 dojo.require('dijit.form.DateTextBox');
254 dojo.require('dojo.date.stamp');
255 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
256 if (this.widgetValue != null) {
257 this.widgetValue = openils.Util.timeStampAsDateObj(
265 dojo.require('dijit.form.FilteringSelect');
266 var store = new dojo.data.ItemFileReadStore({
268 identifier : 'value',
270 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
271 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
272 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
276 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
277 this.widget.searchAttr = this.widget.labelAttr = 'label';
278 this.widget.valueAttr = 'value';
279 this.widget.store = store;
280 this.widget.startup();
281 this.widgetValue = (this.widgetValue === null) ? 'unset' :
282 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
284 dojo.require('dijit.form.CheckBox');
285 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
286 this.widgetValue = openils.Util.isTrue(this.widgetValue);
291 if(this._buildLinkSelector()) break;
294 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
295 dojo.require('dijit.form.ValidationTextBox');
296 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
298 dojo.require('dijit.form.TextBox');
299 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
304 if(!this.async) this._widgetLoaded();
308 // we want to display the value for our widget. However, instead of displaying
309 // an ID, for exmaple, display the value for the 'selector' field on the object
311 _tryLinkedDisplayField : function(noAsync) {
313 if(this.idlField.datatype == 'org_unit')
314 return false; // we already handle org_units, no need to re-fetch
316 // user opted to bypass fetching this linked data
317 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
320 var linkInfo = this._getLinkSelector();
321 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
323 var lclass = linkInfo.linkClass;
328 // first try the store cache
330 if(this.cache[this.auth].list[lclass]) {
331 var store = this.cache[this.auth].list[lclass];
333 query[linkInfo.vfield.name] = ''+this.widgetValue;
335 store.fetch({query:query, onComplete:
338 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
347 // then try the single object cache
348 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
349 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
353 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
355 // if those fail, fetch the linked object
358 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
359 async : !this.forceSync,
360 oncomplete : function(r) {
361 var item = openils.Util.readResponse(r);
362 var newvalue = item[linkInfo.vfield.selector]();
364 if(!self.cache[self.auth].single[lclass])
365 self.cache[self.auth].single[lclass] = {};
366 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
368 self.widgetValue = newvalue;
369 self.widget.startup();
370 self._widgetLoaded();
375 _getLinkSelector : function() {
376 var linkClass = this.idlField['class'];
377 if(this.idlField.reltype != 'has_a') return false;
378 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
379 fieldmapper.IDL.load([linkClass]);
380 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
381 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
384 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
386 for(var f in rclassIdl.fields) {
387 if(this.idlField.key == rclassIdl.fields[f].name) {
388 vfield = rclassIdl.fields[f];
394 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
397 linkClass : linkClass,
402 _buildLinkSelector : function() {
404 var selectorInfo = this._getLinkSelector();
405 if(!selectorInfo) return false;
407 var linkClass = selectorInfo.linkClass;
408 var vfield = selectorInfo.vfield;
412 if(linkClass == 'pgt')
413 return this._buildPermGrpSelector();
414 if(linkClass == 'aou')
415 return this._buildOrgSelector();
416 if(linkClass == 'acpl')
417 return this._buildCopyLocSelector();
418 if(linkClass == 'acqpro')
419 return this._buildAutoCompleteSelector(linkClass, vfield.selector);
422 dojo.require('dojo.data.ItemFileReadStore');
423 dojo.require('dijit.form.FilteringSelect');
425 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
426 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
427 this.widget.valueAttr = vfield.name;
428 this.widget.attr('disabled', true);
430 var oncomplete = function(list) {
431 self.widget.attr('disabled', false);
434 self.widget.labelAttr = '_label';
436 if(self.searchFormat)
437 self.widget.searchAttr = '_search';
439 function formatString(item, formatList) {
443 // formatList[1..*] are names of fields. Pull the field
444 // values from each object to determine the values for string substitution
446 var format = formatList[0];
447 for(var i = 1; i< formatList.length; i++)
448 values.push(item[formatList[i]]);
450 return dojo.string.substitute(format, values);
454 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
460 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
462 if(self.labelFormat) {
463 dojo.forEach(storeData.data.items,
465 item._label = formatString(item, self.labelFormat);
470 if(self.searchFormat) {
471 dojo.forEach(storeData.data.items,
473 item._search = formatString(item, self.searchFormat);
478 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
479 self.cache[self.auth].list[linkClass] = self.widget.store;
482 self.widget.store = self.cache[self.auth].list[linkClass];
485 self.widget.startup();
486 self._widgetLoaded();
489 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
494 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
495 this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
497 if(this.dataLoader) {
499 // caller provided an external function for retrieving the data
500 this.dataLoader(linkClass, this.searchFilter, oncomplete);
504 var _cb = function(r) {
505 oncomplete(openils.Util.readResponse(r, false, true));
508 if (this.searchFilter) {
509 new openils.PermaCrud().search(linkClass, this.searchFilter, {
510 async : !this.forceSync, oncomplete : _cb
513 new openils.PermaCrud().retrieveAll(linkClass, {
514 async : !this.forceSync, oncomplete : _cb
524 * For widgets that run asynchronously, provide a callback for finishing up
526 _widgetLoaded : function(value) {
530 /* -------------------------------------------------------------
531 when using widgets in a grid, the cell may dissapear, which
532 kills the underlying DOM node, which causes this to fail.
533 For now, back out gracefully and let grid getters use
534 getDisplayString() instead
535 -------------------------------------------------------------*/
537 this.baseWidgetValue(this.getDisplayString());
542 this.baseWidgetValue(this.widgetValue);
543 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
544 this.widget.attr('disabled', true);
545 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
546 this.widget.attr('disabled', true);
549 this.onload(this.widget, this);
551 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
552 // a required dijit is not given any styling to indicate the value
553 // is invalid until the user has focused the widget then left it with
554 // invalid data. This change tells dojo to pretend this focusing has
555 // already happened so we can style required widgets during page render.
556 this.widget._hasBeenBlurred = true;
557 this.widget.validate();
561 _buildOrgSelector : function() {
562 dojo.require('fieldmapper.OrgUtils');
563 dojo.require('openils.widget.FilteringTreeSelect');
564 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
565 this.widget.searchAttr = 'shortname';
566 this.widget.labelAttr = 'shortname';
567 this.widget.parentField = 'parent_ou';
568 var user = new openils.User();
570 if(this.widgetValue == null && this.orgDefaultsToWs)
571 this.widgetValue = user.user.ws_ou();
573 // if we have a limit perm, find the relevent orgs (async)
574 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
577 user.getPermOrgList(this.orgLimitPerms,
579 self.widget.tree = orgList;
580 self.widget.startup();
581 self._widgetLoaded();
586 this.widget.tree = fieldmapper.aou.globalOrgTree;
587 this.widget.startup();
593 _buildPermGrpSelector : function() {
594 dojo.require('openils.widget.FilteringTreeSelect');
595 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
596 this.widget.disableQuery = this.disableQuery;
597 this.widget.searchAttr = 'name';
599 if(this.cache.permGrpTree) {
600 this.widget.tree = this.cache.permGrpTree;
601 this.widget.startup();
602 this._widgetLoaded();
608 new openils.PermaCrud().retrieveAll('pgt', {
609 async : !this.forceSync,
610 oncomplete : function(r) {
611 var list = openils.Util.readResponse(r, false, true);
616 map[list[l].id()] = list[l];
619 var pnode = map[node.parent()];
620 if(!pnode) {root = node; continue;}
621 if(!pnode.children()) pnode.children([]);
622 pnode.children().push(node);
624 self.widget.tree = self.cache.permGrpTree = root;
625 self.widget.startup();
626 self._widgetLoaded();
633 _buildCopyLocSelector : function() {
634 dojo.require('dijit.form.FilteringSelect');
635 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
636 this.widget.searchAttr = this.widget.labalAttr = 'name';
637 this.widget.valueAttr = 'id';
639 if(this.cache.copyLocStore) {
640 this.widget.store = this.cache.copyLocStore;
641 this.widget.startup();
647 var ws_ou = openils.User.user.ws_ou();
648 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
649 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
652 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
653 async : !this.forceSync,
654 oncomplete : function(r) {
655 var list = openils.Util.readResponse(r, false, true);
658 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
659 self.cache.copyLocStore = self.widget.store;
660 self.widget.startup();
661 self._widgetLoaded();
668 _buildAutoCompleteSelector : function(linkClass, searchAttr) {
669 dojo.require("openils.widget.PCrudAutocompleteBox");
670 dojo.mixin(this.dijitArgs, {
672 searchAttr : searchAttr,
674 this.widget = new openils.widget.PCrudAutocompleteBox(this.dijitArgs, this.parentNode);
675 this._widgetLoaded();
680 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
681 openils.widget.AutoFieldWidget.cache = {};
682 openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};