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 throw new Error("IDL class '" + this.fmClass + "' not defined");
68 this.suppressLinkedFields = args.suppressLinkedFields || [];
70 if(this.selfReference) {
71 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
73 // create a mock-up of the idlField object.
76 'class' : this.fmClass,
85 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
86 var fields = this.fmIDL.fields;
88 if(fields[f].name == this.fmField)
89 this.idlField = fields[f];
94 throw new Error("AutoFieldWidget could not determine which " +
95 "field to render. We need more information. fmClass=" +
96 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
98 this.auth = openils.User.authtoken;
99 this.cache = openils.widget.AutoFieldWidget.cache;
100 this.cache[this.auth] = this.cache[this.auth] || {};
101 this.cache[this.auth].single = this.cache[this.auth].single || {};
102 this.cache[this.auth].list = this.cache[this.auth].list || {};
106 * Turn the widget-stored value into a value oils understands
108 getFormattedValue : function() {
109 var value = this.baseWidgetValue();
110 switch(this.idlField.datatype) {
113 case 'true': return 't';
114 case 'on': return 't';
115 case 'false' : return 'f';
116 case 'unset' : return null;
117 case true : return 't';
121 if(!value) return null;
122 return dojo.date.stamp.toISOString(value);
126 if(isNaN(value)) value = null;
128 return (value === '') ? null : value;
132 baseWidgetValue : function(value) {
133 var attr = (this.readOnly) ? 'content' : 'value';
134 if(arguments.length) this.widget.attr(attr, value);
135 return this.widget.attr(attr);
139 * Turn the widget-stored value into something visually suitable
141 getDisplayString : function() {
142 var value = this.widgetValue;
143 switch(this.idlField.datatype) {
148 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
151 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
153 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
154 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
155 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
158 if (!value) return '';
159 return openils.Util.timeStamp(
160 value, {"formatLength": "short"}
163 if(value === null || value === undefined) return '';
164 return fieldmapper.aou.findOrgUnit(value).shortname();
167 if(isNaN(value)) value = 0;
169 if(value === undefined || value === null)
175 build : function(onload) {
177 if(this.widgetValue == null)
178 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
181 // core widget provided for us, attach and move on
182 if(this.parentNode) // may already be in the "right" place
183 this.parentNode.appendChild(this.widget.domNode);
185 if (this.shove.mode == "update")
186 this.widget.attr("value", this.widgetValue);
188 this.widgetValue = this.shove.create;
189 this._widgetLoaded();
190 } else if (this.widget.attr("value") == null) {
191 this._widgetLoaded();
196 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
197 this.parentNode = dojo.create('div');
199 this.onload = onload;
202 dojo.require('dijit.layout.ContentPane');
203 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
204 if(this.widgetValue !== null)
205 this._tryLinkedDisplayField();
207 } else if(this.widgetClass) {
208 dojo.require(this.widgetClass);
209 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
213 switch(this.idlField.datatype) {
216 dojo.require('dijit.form.TextBox');
217 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
221 this._buildOrgSelector();
225 dojo.require('dijit.form.CurrencyTextBox');
226 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
230 dojo.require('dijit.form.NumberTextBox');
231 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
232 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
236 dojo.require('dijit.form.NumberTextBox');
237 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
241 dojo.require('dijit.form.DateTextBox');
242 dojo.require('dojo.date.stamp');
243 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
244 if (this.widgetValue != null) {
245 this.widgetValue = openils.Util.timeStampAsDateObj(
253 dojo.require('dijit.form.FilteringSelect');
254 var store = new dojo.data.ItemFileReadStore({
256 identifier : 'value',
258 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
259 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
260 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
264 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
265 this.widget.searchAttr = this.widget.labelAttr = 'label';
266 this.widget.valueAttr = 'value';
267 this.widget.store = store;
268 this.widget.startup();
269 this.widgetValue = (this.widgetValue === null) ? 'unset' :
270 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
272 dojo.require('dijit.form.CheckBox');
273 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
274 this.widgetValue = openils.Util.isTrue(this.widgetValue);
279 if(this._buildLinkSelector()) break;
282 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
283 dojo.require('dijit.form.ValidationTextBox');
284 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
286 dojo.require('dijit.form.TextBox');
287 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
292 if(!this.async) this._widgetLoaded();
296 // we want to display the value for our widget. However, instead of displaying
297 // an ID, for exmaple, display the value for the 'selector' field on the object
299 _tryLinkedDisplayField : function(noAsync) {
301 if(this.idlField.datatype == 'org_unit')
302 return false; // we already handle org_units, no need to re-fetch
304 // user opted to bypass fetching this linked data
305 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
308 var linkInfo = this._getLinkSelector();
309 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
311 var lclass = linkInfo.linkClass;
316 // first try the store cache
318 if(this.cache[this.auth].list[lclass]) {
319 var store = this.cache[this.auth].list[lclass];
321 query[linkInfo.vfield.name] = ''+this.widgetValue;
323 store.fetch({query:query, onComplete:
326 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
335 // then try the single object cache
336 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
337 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
341 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
343 // if those fail, fetch the linked object
346 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
347 async : !this.forceSync,
348 oncomplete : function(r) {
349 var item = openils.Util.readResponse(r);
350 var newvalue = item[linkInfo.vfield.selector]();
352 if(!self.cache[self.auth].single[lclass])
353 self.cache[self.auth].single[lclass] = {};
354 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
356 self.widgetValue = newvalue;
357 self.widget.startup();
358 self._widgetLoaded();
363 _getLinkSelector : function() {
364 var linkClass = this.idlField['class'];
365 if(this.idlField.reltype != 'has_a') return false;
366 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
367 throw new Error("IDL Class '" + linkClass + "' not defined");
368 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
369 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
372 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
374 for(var f in rclassIdl.fields) {
375 if(this.idlField.key == rclassIdl.fields[f].name) {
376 vfield = rclassIdl.fields[f];
382 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
385 linkClass : linkClass,
390 _buildLinkSelector : function() {
392 var selectorInfo = this._getLinkSelector();
393 if(!selectorInfo) return false;
395 var linkClass = selectorInfo.linkClass;
396 var vfield = selectorInfo.vfield;
400 if(linkClass == 'pgt')
401 return this._buildPermGrpSelector();
402 if(linkClass == 'aou')
403 return this._buildOrgSelector();
404 if(linkClass == 'acpl')
405 return this._buildCopyLocSelector();
408 dojo.require('dojo.data.ItemFileReadStore');
409 dojo.require('dijit.form.FilteringSelect');
411 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
412 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
413 this.widget.valueAttr = vfield.name;
415 var oncomplete = function(list) {
418 self.widget.labelAttr = '_label';
420 if(self.searchFormat)
421 self.widget.searchAttr = '_search';
423 function formatString(item, formatList) {
427 // formatList[1..*] are names of fields. Pull the field
428 // values from each object to determine the values for string substitution
430 var format = formatList[0];
431 for(var i = 1; i< formatList.length; i++)
432 values.push(item[formatList[i]]);
434 return dojo.string.substitute(format, values);
438 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
444 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
446 if(self.labelFormat) {
447 dojo.forEach(storeData.data.items,
449 item._label = formatString(item, self.labelFormat);
454 if(self.searchFormat) {
455 dojo.forEach(storeData.data.items,
457 item._search = formatString(item, self.searchFormat);
462 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
463 self.cache[self.auth].list[linkClass] = self.widget.store;
466 self.widget.store = self.cache[self.auth].list[linkClass];
469 self.widget.startup();
470 self._widgetLoaded();
473 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
478 if(this.dataLoader) {
480 // caller provided an external function for retrieving the data
481 this.dataLoader(linkClass, this.searchFilter, oncomplete);
485 var _cb = function(r) {
486 oncomplete(openils.Util.readResponse(r, false, true));
489 if (this.searchFilter) {
490 new openils.PermaCrud().search(linkClass, this.searchFilter, {
491 async : !this.forceSync, oncomplete : _cb
494 new openils.PermaCrud().retrieveAll(linkClass, {
495 async : !this.forceSync, oncomplete : _cb
505 * For widgets that run asynchronously, provide a callback for finishing up
507 _widgetLoaded : function(value) {
511 /* -------------------------------------------------------------
512 when using widgets in a grid, the cell may dissapear, which
513 kills the underlying DOM node, which causes this to fail.
514 For now, back out gracefully and let grid getters use
515 getDisplayString() instead
516 -------------------------------------------------------------*/
518 this.baseWidgetValue(this.getDisplayString());
523 this.baseWidgetValue(this.widgetValue);
524 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
525 this.widget.attr('disabled', true);
526 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
527 this.widget.attr('disabled', true);
530 this.onload(this.widget, this);
532 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
533 // a required dijit is not given any styling to indicate the value
534 // is invalid until the user has focused the widget then left it with
535 // invalid data. This change tells dojo to pretend this focusing has
536 // already happened so we can style required widgets during page render.
537 this.widget._hasBeenBlurred = true;
538 this.widget.validate();
542 _buildOrgSelector : function() {
543 dojo.require('fieldmapper.OrgUtils');
544 dojo.require('openils.widget.FilteringTreeSelect');
545 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
546 this.widget.searchAttr = 'shortname';
547 this.widget.labelAttr = 'shortname';
548 this.widget.parentField = 'parent_ou';
549 var user = new openils.User();
551 if(this.widgetValue == null && this.orgDefaultsToWs)
552 this.widgetValue = user.user.ws_ou();
554 // if we have a limit perm, find the relevent orgs (async)
555 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
558 user.getPermOrgList(this.orgLimitPerms,
560 self.widget.tree = orgList;
561 self.widget.startup();
562 self._widgetLoaded();
567 this.widget.tree = fieldmapper.aou.globalOrgTree;
568 this.widget.startup();
574 _buildPermGrpSelector : function() {
575 dojo.require('openils.widget.FilteringTreeSelect');
576 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
577 this.widget.disableQuery = this.disableQuery;
578 this.widget.searchAttr = 'name';
580 if(this.cache.permGrpTree) {
581 this.widget.tree = this.cache.permGrpTree;
582 this.widget.startup();
583 this._widgetLoaded();
589 new openils.PermaCrud().retrieveAll('pgt', {
590 async : !this.forceSync,
591 oncomplete : function(r) {
592 var list = openils.Util.readResponse(r, false, true);
597 map[list[l].id()] = list[l];
600 var pnode = map[node.parent()];
601 if(!pnode) {root = node; continue;}
602 if(!pnode.children()) pnode.children([]);
603 pnode.children().push(node);
605 self.widget.tree = self.cache.permGrpTree = root;
606 self.widget.startup();
607 self._widgetLoaded();
614 _buildCopyLocSelector : function() {
615 dojo.require('dijit.form.FilteringSelect');
616 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
617 this.widget.searchAttr = this.widget.labalAttr = 'name';
618 this.widget.valueAttr = 'id';
620 if(this.cache.copyLocStore) {
621 this.widget.store = this.cache.copyLocStore;
622 this.widget.startup();
628 var ws_ou = openils.User.user.ws_ou();
629 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
630 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
633 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
634 async : !this.forceSync,
635 oncomplete : function(r) {
636 var list = openils.Util.readResponse(r, false, true);
639 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
640 self.cache.copyLocStore = self.widget.store;
641 self.widget.startup();
642 self._widgetLoaded();
650 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
651 openils.widget.AutoFieldWidget.cache = {};