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 if (this.shove.mode == "update")
181 this.widget.attr("value", this.widgetValue);
183 this.widgetValue = this.shove.create;
184 this._widgetLoaded();
185 } else if (this.widget.attr("value") == null) {
186 this._widgetLoaded();
191 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
192 this.parentNode = dojo.create('div');
194 this.onload = onload;
197 dojo.require('dijit.layout.ContentPane');
198 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
199 if(this.widgetValue !== null)
200 this._tryLinkedDisplayField();
202 } else if(this.widgetClass) {
203 dojo.require(this.widgetClass);
204 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
208 switch(this.idlField.datatype) {
211 dojo.require('dijit.form.TextBox');
212 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
216 this._buildOrgSelector();
220 dojo.require('dijit.form.CurrencyTextBox');
221 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
225 dojo.require('dijit.form.NumberTextBox');
226 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
227 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
231 dojo.require('dijit.form.NumberTextBox');
232 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
236 dojo.require('dijit.form.DateTextBox');
237 dojo.require('dojo.date.stamp');
238 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
239 if (this.widgetValue != null) {
240 this.widgetValue = openils.Util.timeStampAsDateObj(
248 dojo.require('dijit.form.FilteringSelect');
249 var store = new dojo.data.ItemFileReadStore({
251 identifier : 'value',
253 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
254 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
255 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
259 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
260 this.widget.searchAttr = this.widget.labelAttr = 'label';
261 this.widget.valueAttr = 'value';
262 this.widget.store = store;
263 this.widget.startup();
264 this.widgetValue = (this.widgetValue === null) ? 'unset' :
265 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
267 dojo.require('dijit.form.CheckBox');
268 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
269 this.widgetValue = openils.Util.isTrue(this.widgetValue);
274 if(this._buildLinkSelector()) break;
277 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
278 dojo.require('dijit.form.ValidationTextBox');
279 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
281 dojo.require('dijit.form.TextBox');
282 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
287 if(!this.async) this._widgetLoaded();
291 // we want to display the value for our widget. However, instead of displaying
292 // an ID, for exmaple, display the value for the 'selector' field on the object
294 _tryLinkedDisplayField : function(noAsync) {
296 if(this.idlField.datatype == 'org_unit')
297 return false; // we already handle org_units, no need to re-fetch
299 // user opted to bypass fetching this linked data
300 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
303 var linkInfo = this._getLinkSelector();
304 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
306 var lclass = linkInfo.linkClass;
311 // first try the store cache
313 if(this.cache[this.auth].list[lclass]) {
314 var store = this.cache[this.auth].list[lclass];
316 query[linkInfo.vfield.name] = ''+this.widgetValue;
318 store.fetch({query:query, onComplete:
321 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
330 // then try the single object cache
331 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
332 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
336 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
338 // if those fail, fetch the linked object
341 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
342 async : !this.forceSync,
343 oncomplete : function(r) {
344 var item = openils.Util.readResponse(r);
345 var newvalue = item[linkInfo.vfield.selector]();
347 if(!self.cache[self.auth].single[lclass])
348 self.cache[self.auth].single[lclass] = {};
349 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
351 self.widgetValue = newvalue;
352 self.widget.startup();
353 self._widgetLoaded();
358 _getLinkSelector : function() {
359 var linkClass = this.idlField['class'];
360 if(this.idlField.reltype != 'has_a') return false;
361 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
362 throw new Error("IDL Class '" + linkClass + "' not defined");
363 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
364 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
367 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
369 for(var f in rclassIdl.fields) {
370 if(this.idlField.key == rclassIdl.fields[f].name) {
371 vfield = rclassIdl.fields[f];
377 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
380 linkClass : linkClass,
385 _buildLinkSelector : function() {
387 var selectorInfo = this._getLinkSelector();
388 if(!selectorInfo) return false;
390 var linkClass = selectorInfo.linkClass;
391 var vfield = selectorInfo.vfield;
395 if(linkClass == 'pgt')
396 return this._buildPermGrpSelector();
397 if(linkClass == 'aou')
398 return this._buildOrgSelector();
399 if(linkClass == 'acpl')
400 return this._buildCopyLocSelector();
403 dojo.require('dojo.data.ItemFileReadStore');
404 dojo.require('dijit.form.FilteringSelect');
406 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
407 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
408 this.widget.valueAttr = vfield.name;
410 var oncomplete = function(list) {
413 self.widget.labelAttr = '_label';
415 if(self.searchFormat)
416 self.widget.searchAttr = '_search';
418 function formatString(item, formatList) {
422 // formatList[1..*] are names of fields. Pull the field
423 // values from each object to determine the values for string substitution
425 var format = formatList[0];
426 for(var i = 1; i< formatList.length; i++)
427 values.push(item[formatList[i]]);
429 return dojo.string.substitute(format, values);
433 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
439 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
441 if(self.labelFormat) {
442 dojo.forEach(storeData.data.items,
444 item._label = formatString(item, self.labelFormat);
449 if(self.searchFormat) {
450 dojo.forEach(storeData.data.items,
452 item._search = formatString(item, self.searchFormat);
457 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
458 self.cache[self.auth].list[linkClass] = self.widget.store;
461 self.widget.store = self.cache[self.auth].list[linkClass];
464 self.widget.startup();
465 self._widgetLoaded();
468 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
473 if(this.dataLoader) {
475 // caller provided an external function for retrieving the data
476 this.dataLoader(linkClass, this.searchFilter, oncomplete);
480 var _cb = function(r) {
481 oncomplete(openils.Util.readResponse(r, false, true));
484 if (this.searchFilter) {
485 new openils.PermaCrud().search(linkClass, this.searchFilter, {
486 async : !this.forceSync, oncomplete : _cb
489 new openils.PermaCrud().retrieveAll(linkClass, {
490 async : !this.forceSync, oncomplete : _cb
500 * For widgets that run asynchronously, provide a callback for finishing up
502 _widgetLoaded : function(value) {
506 /* -------------------------------------------------------------
507 when using widgets in a grid, the cell may dissapear, which
508 kills the underlying DOM node, which causes this to fail.
509 For now, back out gracefully and let grid getters use
510 getDisplayString() instead
511 -------------------------------------------------------------*/
513 this.baseWidgetValue(this.getDisplayString());
518 this.baseWidgetValue(this.widgetValue);
519 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
520 this.widget.attr('disabled', true);
521 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
522 this.widget.attr('disabled', true);
525 this.onload(this.widget, this);
527 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
528 // a required dijit is not given any styling to indicate the value
529 // is invalid until the user has focused the widget then left it with
530 // invalid data. This change tells dojo to pretend this focusing has
531 // already happened so we can style required widgets during page render.
532 this.widget._hasBeenBlurred = true;
533 this.widget.validate();
537 _buildOrgSelector : function() {
538 dojo.require('fieldmapper.OrgUtils');
539 dojo.require('openils.widget.FilteringTreeSelect');
540 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
541 this.widget.searchAttr = 'shortname';
542 this.widget.labelAttr = 'shortname';
543 this.widget.parentField = 'parent_ou';
544 var user = new openils.User();
546 if(this.widgetValue == null && this.orgDefaultsToWs)
547 this.widgetValue = user.user.ws_ou();
549 // if we have a limit perm, find the relevent orgs (async)
550 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
553 user.getPermOrgList(this.orgLimitPerms,
555 self.widget.tree = orgList;
556 self.widget.startup();
557 self._widgetLoaded();
562 this.widget.tree = fieldmapper.aou.globalOrgTree;
563 this.widget.startup();
569 _buildPermGrpSelector : function() {
570 dojo.require('openils.widget.FilteringTreeSelect');
571 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
572 this.widget.disableQuery = this.disableQuery;
573 this.widget.searchAttr = 'name';
575 if(this.cache.permGrpTree) {
576 this.widget.tree = this.cache.permGrpTree;
577 this.widget.startup();
578 this._widgetLoaded();
584 new openils.PermaCrud().retrieveAll('pgt', {
585 async : !this.forceSync,
586 oncomplete : function(r) {
587 var list = openils.Util.readResponse(r, false, true);
592 map[list[l].id()] = list[l];
595 var pnode = map[node.parent()];
596 if(!pnode) {root = node; continue;}
597 if(!pnode.children()) pnode.children([]);
598 pnode.children().push(node);
600 self.widget.tree = self.cache.permGrpTree = root;
601 self.widget.startup();
602 self._widgetLoaded();
609 _buildCopyLocSelector : function() {
610 dojo.require('dijit.form.FilteringSelect');
611 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
612 this.widget.searchAttr = this.widget.labalAttr = 'name';
613 this.widget.valueAttr = 'id';
615 if(this.cache.copyLocStore) {
616 this.widget.store = this.cache.copyLocStore;
617 this.widget.startup();
623 var ws_ou = openils.User.user.ws_ou();
624 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
625 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
628 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
629 async : !this.forceSync,
630 oncomplete : function(r) {
631 var list = openils.Util.readResponse(r, false, true);
634 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
635 self.cache.copyLocStore = self.widget.store;
636 self.widget.startup();
637 self._widgetLoaded();
645 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
646 openils.widget.AutoFieldWidget.cache = {};