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
48 constructor : function(args) {
52 if (!this.dijitArgs) {
55 this.dijitArgs['scrollOnFocus'] = false;
57 // find the field description in the IDL if not provided
59 this.fmClass = this.fmObject.classname;
60 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
61 this.suppressLinkedFields = args.suppressLinkedFields || [];
63 if(this.selfReference) {
64 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
66 // create a mock-up of the idlField object.
69 'class' : this.fmClass,
78 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
79 var fields = this.fmIDL.fields;
81 if(fields[f].name == this.fmField)
82 this.idlField = fields[f];
87 throw new Error("AutoFieldWidget could not determine which " +
88 "field to render. We need more information. fmClass=" +
89 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
91 this.auth = openils.User.authtoken;
92 this.cache = openils.widget.AutoFieldWidget.cache;
93 this.cache[this.auth] = this.cache[this.auth] || {};
94 this.cache[this.auth].single = this.cache[this.auth].single || {};
95 this.cache[this.auth].list = this.cache[this.auth].list || {};
99 * Turn the widget-stored value into a value oils understands
101 getFormattedValue : function() {
102 var value = this.baseWidgetValue();
103 switch(this.idlField.datatype) {
106 case 'true': return 't';
107 case 'on': return 't';
108 case 'false' : return 'f';
109 case 'unset' : return null;
110 case true : return 't';
114 if(!value) return null;
115 return dojo.date.stamp.toISOString(value);
119 if(isNaN(value)) value = null;
121 return (value === '') ? null : value;
125 baseWidgetValue : function(value) {
126 var attr = (this.readOnly) ? 'content' : 'value';
127 if(arguments.length) this.widget.attr(attr, value);
128 return this.widget.attr(attr);
132 * Turn the widget-stored value into something visually suitable
134 getDisplayString : function() {
135 var value = this.widgetValue;
136 switch(this.idlField.datatype) {
141 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
144 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
146 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
147 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
148 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
151 if (!value) return '';
152 dojo.require('dojo.date.locale');
153 dojo.require('dojo.date.stamp');
154 var date = dojo.date.stamp.fromISOString(value);
155 return dojo.date.locale.format(date, {formatLength:'short'});
157 if(value === null || value === undefined) return '';
158 return fieldmapper.aou.findOrgUnit(value).shortname();
161 if(isNaN(value)) value = 0;
163 if(value === undefined || value === null)
169 build : function(onload) {
171 if(this.widgetValue == null)
172 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
175 // core widget provided for us, attach and move on
176 if(this.parentNode) // may already be in the "right" place
177 this.parentNode.appendChild(this.widget.domNode);
178 if(this.widget.attr('value') == null)
179 this._widgetLoaded();
183 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
184 this.parentNode = dojo.create('div');
186 this.onload = onload;
189 dojo.require('dijit.layout.ContentPane');
190 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
191 if(this.widgetValue !== null)
192 this._tryLinkedDisplayField();
194 } else if(this.widgetClass) {
195 dojo.require(this.widgetClass);
196 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
200 switch(this.idlField.datatype) {
203 dojo.require('dijit.form.TextBox');
204 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
208 this._buildOrgSelector();
212 dojo.require('dijit.form.CurrencyTextBox');
213 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
217 dojo.require('dijit.form.NumberTextBox');
218 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
219 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
223 dojo.require('dijit.form.NumberTextBox');
224 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
228 dojo.require('dijit.form.DateTextBox');
229 dojo.require('dojo.date.stamp');
230 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
231 if(this.widgetValue != null)
232 this.widgetValue = dojo.date.stamp.fromISOString(
233 // Kludge until the ML returning ISO timestamps with a colon in the timezone offset,
234 // which dojo.date.stamp.fromISOString requires
235 this.widgetValue.replace( /^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d-\d\d)(\d\d)$/, '$1:$2')
241 dojo.require('dijit.form.FilteringSelect');
242 var store = new dojo.data.ItemFileReadStore({
244 identifier : 'value',
246 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
247 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
248 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
252 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
253 this.widget.searchAttr = this.widget.labelAttr = 'label';
254 this.widget.valueAttr = 'value';
255 this.widget.store = store;
256 this.widget.startup();
257 this.widgetValue = (this.widgetValue === null) ? 'unset' :
258 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
260 dojo.require('dijit.form.CheckBox');
261 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
262 this.widgetValue = openils.Util.isTrue(this.widgetValue);
267 if(this._buildLinkSelector()) break;
270 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
271 dojo.require('dijit.form.ValidationTextBox');
272 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
274 dojo.require('dijit.form.TextBox');
275 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
280 if(!this.async) this._widgetLoaded();
284 // we want to display the value for our widget. However, instead of displaying
285 // an ID, for exmaple, display the value for the 'selector' field on the object
287 _tryLinkedDisplayField : function(noAsync) {
289 if(this.idlField.datatype == 'org_unit')
290 return false; // we already handle org_units, no need to re-fetch
292 // user opted to bypass fetching this linked data
293 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
296 var linkInfo = this._getLinkSelector();
297 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
299 var lclass = linkInfo.linkClass;
304 // first try the store cache
306 if(this.cache[this.auth].list[lclass]) {
307 var store = this.cache[this.auth].list[lclass];
309 query[linkInfo.vfield.name] = ''+this.widgetValue;
311 store.fetch({query:query, onComplete:
314 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
323 // then try the single object cache
324 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
325 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
329 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
331 // if those fail, fetch the linked object
334 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
335 async : !this.forceSync,
336 oncomplete : function(r) {
337 var item = openils.Util.readResponse(r);
338 var newvalue = item[linkInfo.vfield.selector]();
340 if(!self.cache[self.auth].single[lclass])
341 self.cache[self.auth].single[lclass] = {};
342 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
344 self.widgetValue = newvalue;
345 self.widget.startup();
346 self._widgetLoaded();
351 _getLinkSelector : function() {
352 var linkClass = this.idlField['class'];
353 if(this.idlField.reltype != 'has_a') return false;
354 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
355 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
358 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
360 for(var f in rclassIdl.fields) {
361 if(this.idlField.key == rclassIdl.fields[f].name) {
362 vfield = rclassIdl.fields[f];
368 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
371 linkClass : linkClass,
376 _buildLinkSelector : function() {
378 var selectorInfo = this._getLinkSelector();
379 if(!selectorInfo) return false;
381 var linkClass = selectorInfo.linkClass;
382 var vfield = selectorInfo.vfield;
386 if(linkClass == 'pgt')
387 return this._buildPermGrpSelector();
388 if(linkClass == 'aou')
389 return this._buildOrgSelector();
390 if(linkClass == 'acpl')
391 return this._buildCopyLocSelector();
394 dojo.require('dojo.data.ItemFileReadStore');
395 dojo.require('dijit.form.FilteringSelect');
397 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
398 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
399 this.widget.valueAttr = vfield.name;
401 var oncomplete = function(list) {
404 self.widget.labelAttr = '_label';
406 if(self.searchFormat)
407 self.widget.searchAttr = '_search';
409 function formatString(item, formatList) {
413 // formatList[1..*] are names of fields. Pull the field
414 // values from each object to determine the values for string substitution
416 var format = formatList[0];
417 for(var i = 1; i< formatList.length; i++)
418 values.push(item[formatList[i]]);
420 return dojo.string.substitute(format, values);
424 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
430 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
432 if(self.labelFormat) {
433 dojo.forEach(storeData.data.items,
435 item._label = formatString(item, self.labelFormat);
440 if(self.searchFormat) {
441 dojo.forEach(storeData.data.items,
443 item._search = formatString(item, self.searchFormat);
448 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
449 self.cache[self.auth].list[linkClass] = self.widget.store;
452 self.widget.store = self.cache[self.auth].list[linkClass];
455 self.widget.startup();
456 self._widgetLoaded();
459 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
464 if(this.dataLoader) {
466 // caller provided an external function for retrieving the data
467 this.dataLoader(linkClass, this.searchFilter, oncomplete);
471 var _cb = function(r) {
472 oncomplete(openils.Util.readResponse(r, false, true));
475 if (this.searchFilter) {
476 new openils.PermaCrud().search(linkClass, this.searchFilter, {
477 async : !this.forceSync, oncomplete : _cb
480 new openils.PermaCrud().retrieveAll(linkClass, {
481 async : !this.forceSync, oncomplete : _cb
491 * For widgets that run asynchronously, provide a callback for finishing up
493 _widgetLoaded : function(value) {
497 /* -------------------------------------------------------------
498 when using widgets in a grid, the cell may dissapear, which
499 kills the underlying DOM node, which causes this to fail.
500 For now, back out gracefully and let grid getters use
501 getDisplayString() instead
502 -------------------------------------------------------------*/
504 this.baseWidgetValue(this.getDisplayString());
509 this.baseWidgetValue(this.widgetValue);
510 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
511 this.widget.attr('disabled', true);
512 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
513 this.widget.attr('disabled', true);
516 this.onload(this.widget, this);
518 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
519 // a required dijit is not given any styling to indicate the value
520 // is invalid until the user has focused the widget then left it with
521 // invalid data. This change tells dojo to pretend this focusing has
522 // already happened so we can style required widgets during page render.
523 this.widget._hasBeenBlurred = true;
524 this.widget.validate();
528 _buildOrgSelector : function() {
529 dojo.require('fieldmapper.OrgUtils');
530 dojo.require('openils.widget.FilteringTreeSelect');
531 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
532 this.widget.searchAttr = 'shortname';
533 this.widget.labelAttr = 'shortname';
534 this.widget.parentField = 'parent_ou';
535 var user = new openils.User();
537 if(this.widgetValue == null && this.orgDefaultsToWs)
538 this.widgetValue = user.user.ws_ou();
540 // if we have a limit perm, find the relevent orgs (async)
541 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
544 user.getPermOrgList(this.orgLimitPerms,
546 self.widget.tree = orgList;
547 self.widget.startup();
548 self._widgetLoaded();
553 this.widget.tree = fieldmapper.aou.globalOrgTree;
554 this.widget.startup();
560 _buildPermGrpSelector : function() {
561 dojo.require('openils.widget.FilteringTreeSelect');
562 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
563 this.widget.searchAttr = 'name';
565 if(this.cache.permGrpTree) {
566 this.widget.tree = this.cache.permGrpTree;
567 this.widget.startup();
568 this._widgetLoaded();
574 new openils.PermaCrud().retrieveAll('pgt', {
575 async : !this.forceSync,
576 oncomplete : function(r) {
577 var list = openils.Util.readResponse(r, false, true);
582 map[list[l].id()] = list[l];
585 var pnode = map[node.parent()];
586 if(!pnode) {root = node; continue;}
587 if(!pnode.children()) pnode.children([]);
588 pnode.children().push(node);
590 self.widget.tree = self.cache.permGrpTree = root;
591 self.widget.startup();
592 self._widgetLoaded();
599 _buildCopyLocSelector : function() {
600 dojo.require('dijit.form.FilteringSelect');
601 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
602 this.widget.searchAttr = this.widget.labalAttr = 'name';
603 this.widget.valueAttr = 'id';
605 if(this.cache.copyLocStore) {
606 this.widget.store = this.cache.copyLocStore;
607 this.widget.startup();
613 var ws_ou = openils.User.user.ws_ou();
614 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
615 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
618 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
619 async : !this.forceSync,
620 oncomplete : function(r) {
621 var list = openils.Util.readResponse(r, false, true);
624 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
625 self.cache.copyLocStore = self.widget.store;
626 self.widget.startup();
627 self._widgetLoaded();
635 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
636 openils.widget.AutoFieldWidget.cache = {};