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;
59 // find the field description in the IDL if not provided
61 this.fmClass = this.fmObject.classname;
62 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
63 this.suppressLinkedFields = args.suppressLinkedFields || [];
65 if(this.selfReference) {
66 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
68 // create a mock-up of the idlField object.
71 'class' : this.fmClass,
80 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
81 var fields = this.fmIDL.fields;
83 if(fields[f].name == this.fmField)
84 this.idlField = fields[f];
89 throw new Error("AutoFieldWidget could not determine which " +
90 "field to render. We need more information. fmClass=" +
91 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
93 this.auth = openils.User.authtoken;
94 this.cache = openils.widget.AutoFieldWidget.cache;
95 this.cache[this.auth] = this.cache[this.auth] || {};
96 this.cache[this.auth].single = this.cache[this.auth].single || {};
97 this.cache[this.auth].list = this.cache[this.auth].list || {};
101 * Turn the widget-stored value into a value oils understands
103 getFormattedValue : function() {
104 var value = this.baseWidgetValue();
105 switch(this.idlField.datatype) {
108 case 'true': return 't';
109 case 'on': return 't';
110 case 'false' : return 'f';
111 case 'unset' : return null;
112 case true : return 't';
116 if(!value) return null;
117 return dojo.date.stamp.toISOString(value);
121 if(isNaN(value)) value = null;
123 return (value === '') ? null : value;
127 baseWidgetValue : function(value) {
128 var attr = (this.readOnly) ? 'content' : 'value';
129 if(arguments.length) this.widget.attr(attr, value);
130 return this.widget.attr(attr);
134 * Turn the widget-stored value into something visually suitable
136 getDisplayString : function() {
137 var value = this.widgetValue;
138 switch(this.idlField.datatype) {
143 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
146 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
148 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
149 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
150 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
153 if (!value) return '';
154 return openils.Util.timeStamp(
155 value, {"formatLength": "short"}
158 if(value === null || value === undefined) return '';
159 return fieldmapper.aou.findOrgUnit(value).shortname();
162 if(isNaN(value)) value = 0;
164 if(value === undefined || value === null)
170 build : function(onload) {
172 if(this.widgetValue == null)
173 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
176 // core widget provided for us, attach and move on
177 if(this.parentNode) // may already be in the "right" place
178 this.parentNode.appendChild(this.widget.domNode);
180 this.widget.attr("value", this.widgetValue);
181 this._widgetLoaded();
182 } else if (this.widget.attr("value") == null) {
183 this._widgetLoaded();
188 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
189 this.parentNode = dojo.create('div');
191 this.onload = onload;
194 dojo.require('dijit.layout.ContentPane');
195 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
196 if(this.widgetValue !== null)
197 this._tryLinkedDisplayField();
199 } else if(this.widgetClass) {
200 dojo.require(this.widgetClass);
201 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
205 switch(this.idlField.datatype) {
208 dojo.require('dijit.form.TextBox');
209 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
213 this._buildOrgSelector();
217 dojo.require('dijit.form.CurrencyTextBox');
218 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
222 dojo.require('dijit.form.NumberTextBox');
223 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
224 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
228 dojo.require('dijit.form.NumberTextBox');
229 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
233 dojo.require('dijit.form.DateTextBox');
234 dojo.require('dojo.date.stamp');
235 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
236 if (this.widgetValue != null) {
237 this.widgetValue = openils.Util.timeStampAsDateObj(
245 dojo.require('dijit.form.FilteringSelect');
246 var store = new dojo.data.ItemFileReadStore({
248 identifier : 'value',
250 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
251 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
252 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
256 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
257 this.widget.searchAttr = this.widget.labelAttr = 'label';
258 this.widget.valueAttr = 'value';
259 this.widget.store = store;
260 this.widget.startup();
261 this.widgetValue = (this.widgetValue === null) ? 'unset' :
262 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
264 dojo.require('dijit.form.CheckBox');
265 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
266 this.widgetValue = openils.Util.isTrue(this.widgetValue);
271 if(this._buildLinkSelector()) break;
274 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
275 dojo.require('dijit.form.ValidationTextBox');
276 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
278 dojo.require('dijit.form.TextBox');
279 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
284 if(!this.async) this._widgetLoaded();
288 // we want to display the value for our widget. However, instead of displaying
289 // an ID, for exmaple, display the value for the 'selector' field on the object
291 _tryLinkedDisplayField : function(noAsync) {
293 if(this.idlField.datatype == 'org_unit')
294 return false; // we already handle org_units, no need to re-fetch
296 // user opted to bypass fetching this linked data
297 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
300 var linkInfo = this._getLinkSelector();
301 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
303 var lclass = linkInfo.linkClass;
308 // first try the store cache
310 if(this.cache[this.auth].list[lclass]) {
311 var store = this.cache[this.auth].list[lclass];
313 query[linkInfo.vfield.name] = ''+this.widgetValue;
315 store.fetch({query:query, onComplete:
318 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
327 // then try the single object cache
328 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
329 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
333 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
335 // if those fail, fetch the linked object
338 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
339 async : !this.forceSync,
340 oncomplete : function(r) {
341 var item = openils.Util.readResponse(r);
342 var newvalue = item[linkInfo.vfield.selector]();
344 if(!self.cache[self.auth].single[lclass])
345 self.cache[self.auth].single[lclass] = {};
346 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
348 self.widgetValue = newvalue;
349 self.widget.startup();
350 self._widgetLoaded();
355 _getLinkSelector : function() {
356 var linkClass = this.idlField['class'];
357 if(this.idlField.reltype != 'has_a') return false;
358 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
359 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
362 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
364 for(var f in rclassIdl.fields) {
365 if(this.idlField.key == rclassIdl.fields[f].name) {
366 vfield = rclassIdl.fields[f];
372 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
375 linkClass : linkClass,
380 _buildLinkSelector : function() {
382 var selectorInfo = this._getLinkSelector();
383 if(!selectorInfo) return false;
385 var linkClass = selectorInfo.linkClass;
386 var vfield = selectorInfo.vfield;
390 if(linkClass == 'pgt')
391 return this._buildPermGrpSelector();
392 if(linkClass == 'aou')
393 return this._buildOrgSelector();
394 if(linkClass == 'acpl')
395 return this._buildCopyLocSelector();
398 dojo.require('dojo.data.ItemFileReadStore');
399 dojo.require('dijit.form.FilteringSelect');
401 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
402 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
403 this.widget.valueAttr = vfield.name;
405 var oncomplete = function(list) {
408 self.widget.labelAttr = '_label';
410 if(self.searchFormat)
411 self.widget.searchAttr = '_search';
413 function formatString(item, formatList) {
417 // formatList[1..*] are names of fields. Pull the field
418 // values from each object to determine the values for string substitution
420 var format = formatList[0];
421 for(var i = 1; i< formatList.length; i++)
422 values.push(item[formatList[i]]);
424 return dojo.string.substitute(format, values);
428 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
434 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
436 if(self.labelFormat) {
437 dojo.forEach(storeData.data.items,
439 item._label = formatString(item, self.labelFormat);
444 if(self.searchFormat) {
445 dojo.forEach(storeData.data.items,
447 item._search = formatString(item, self.searchFormat);
452 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
453 self.cache[self.auth].list[linkClass] = self.widget.store;
456 self.widget.store = self.cache[self.auth].list[linkClass];
459 self.widget.startup();
460 self._widgetLoaded();
463 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
468 if(this.dataLoader) {
470 // caller provided an external function for retrieving the data
471 this.dataLoader(linkClass, this.searchFilter, oncomplete);
475 var _cb = function(r) {
476 oncomplete(openils.Util.readResponse(r, false, true));
479 if (this.searchFilter) {
480 new openils.PermaCrud().search(linkClass, this.searchFilter, {
481 async : !this.forceSync, oncomplete : _cb
484 new openils.PermaCrud().retrieveAll(linkClass, {
485 async : !this.forceSync, oncomplete : _cb
495 * For widgets that run asynchronously, provide a callback for finishing up
497 _widgetLoaded : function(value) {
501 /* -------------------------------------------------------------
502 when using widgets in a grid, the cell may dissapear, which
503 kills the underlying DOM node, which causes this to fail.
504 For now, back out gracefully and let grid getters use
505 getDisplayString() instead
506 -------------------------------------------------------------*/
508 this.baseWidgetValue(this.getDisplayString());
513 this.baseWidgetValue(this.widgetValue);
514 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
515 this.widget.attr('disabled', true);
516 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
517 this.widget.attr('disabled', true);
520 this.onload(this.widget, this);
522 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
523 // a required dijit is not given any styling to indicate the value
524 // is invalid until the user has focused the widget then left it with
525 // invalid data. This change tells dojo to pretend this focusing has
526 // already happened so we can style required widgets during page render.
527 this.widget._hasBeenBlurred = true;
528 this.widget.validate();
532 _buildOrgSelector : function() {
533 dojo.require('fieldmapper.OrgUtils');
534 dojo.require('openils.widget.FilteringTreeSelect');
535 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
536 this.widget.searchAttr = 'shortname';
537 this.widget.labelAttr = 'shortname';
538 this.widget.parentField = 'parent_ou';
539 var user = new openils.User();
541 if(this.widgetValue == null && this.orgDefaultsToWs)
542 this.widgetValue = user.user.ws_ou();
544 // if we have a limit perm, find the relevent orgs (async)
545 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
548 user.getPermOrgList(this.orgLimitPerms,
550 self.widget.tree = orgList;
551 self.widget.startup();
552 self._widgetLoaded();
557 this.widget.tree = fieldmapper.aou.globalOrgTree;
558 this.widget.startup();
564 _buildPermGrpSelector : function() {
565 dojo.require('openils.widget.FilteringTreeSelect');
566 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
567 this.widget.disableQuery = this.disableQuery;
568 this.widget.searchAttr = 'name';
570 if(this.cache.permGrpTree) {
571 this.widget.tree = this.cache.permGrpTree;
572 this.widget.startup();
573 this._widgetLoaded();
579 new openils.PermaCrud().retrieveAll('pgt', {
580 async : !this.forceSync,
581 oncomplete : function(r) {
582 var list = openils.Util.readResponse(r, false, true);
587 map[list[l].id()] = list[l];
590 var pnode = map[node.parent()];
591 if(!pnode) {root = node; continue;}
592 if(!pnode.children()) pnode.children([]);
593 pnode.children().push(node);
595 self.widget.tree = self.cache.permGrpTree = root;
596 self.widget.startup();
597 self._widgetLoaded();
604 _buildCopyLocSelector : function() {
605 dojo.require('dijit.form.FilteringSelect');
606 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
607 this.widget.searchAttr = this.widget.labalAttr = 'name';
608 this.widget.valueAttr = 'id';
610 if(this.cache.copyLocStore) {
611 this.widget.store = this.cache.copyLocStore;
612 this.widget.startup();
618 var ws_ou = openils.User.user.ws_ou();
619 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
620 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
623 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
624 async : !this.forceSync,
625 oncomplete : function(r) {
626 var list = openils.Util.readResponse(r, false, true);
629 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
630 self.cache.copyLocStore = self.widget.store;
631 self.widget.startup();
632 self._widgetLoaded();
640 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
641 openils.widget.AutoFieldWidget.cache = {};