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 dojo.require('dojo.date.locale');
155 dojo.require('dojo.date.stamp');
156 var date = dojo.date.stamp.fromISOString(value);
157 return dojo.date.locale.format(date, {formatLength:'short'});
159 if(value === null || value === undefined) return '';
160 return fieldmapper.aou.findOrgUnit(value).shortname();
163 if(isNaN(value)) value = 0;
165 if(value === undefined || value === null)
171 build : function(onload) {
173 if(this.widgetValue == null)
174 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
177 // core widget provided for us, attach and move on
178 if(this.parentNode) // may already be in the "right" place
179 this.parentNode.appendChild(this.widget.domNode);
180 if(this.widget.attr('value') == null)
181 this._widgetLoaded();
185 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
186 this.parentNode = dojo.create('div');
188 this.onload = onload;
191 dojo.require('dijit.layout.ContentPane');
192 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
193 if(this.widgetValue !== null)
194 this._tryLinkedDisplayField();
196 } else if(this.widgetClass) {
197 dojo.require(this.widgetClass);
198 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
202 switch(this.idlField.datatype) {
205 dojo.require('dijit.form.TextBox');
206 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
210 this._buildOrgSelector();
214 dojo.require('dijit.form.CurrencyTextBox');
215 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
219 dojo.require('dijit.form.NumberTextBox');
220 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
221 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
225 dojo.require('dijit.form.NumberTextBox');
226 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
230 dojo.require('dijit.form.DateTextBox');
231 dojo.require('dojo.date.stamp');
232 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
233 if(this.widgetValue != null)
234 this.widgetValue = dojo.date.stamp.fromISOString(
235 // Kludge until the ML returning ISO timestamps with a colon in the timezone offset,
236 // which dojo.date.stamp.fromISOString requires
237 this.widgetValue.replace( /^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d-\d\d)(\d\d)$/, '$1:$2')
243 dojo.require('dijit.form.FilteringSelect');
244 var store = new dojo.data.ItemFileReadStore({
246 identifier : 'value',
248 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
249 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
250 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
254 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
255 this.widget.searchAttr = this.widget.labelAttr = 'label';
256 this.widget.valueAttr = 'value';
257 this.widget.store = store;
258 this.widget.startup();
259 this.widgetValue = (this.widgetValue === null) ? 'unset' :
260 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
262 dojo.require('dijit.form.CheckBox');
263 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
264 this.widgetValue = openils.Util.isTrue(this.widgetValue);
269 if(this._buildLinkSelector()) break;
272 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
273 dojo.require('dijit.form.ValidationTextBox');
274 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
276 dojo.require('dijit.form.TextBox');
277 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
282 if(!this.async) this._widgetLoaded();
286 // we want to display the value for our widget. However, instead of displaying
287 // an ID, for exmaple, display the value for the 'selector' field on the object
289 _tryLinkedDisplayField : function(noAsync) {
291 if(this.idlField.datatype == 'org_unit')
292 return false; // we already handle org_units, no need to re-fetch
294 // user opted to bypass fetching this linked data
295 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
298 var linkInfo = this._getLinkSelector();
299 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
301 var lclass = linkInfo.linkClass;
306 // first try the store cache
308 if(this.cache[this.auth].list[lclass]) {
309 var store = this.cache[this.auth].list[lclass];
311 query[linkInfo.vfield.name] = ''+this.widgetValue;
313 store.fetch({query:query, onComplete:
316 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
325 // then try the single object cache
326 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
327 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
331 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
333 // if those fail, fetch the linked object
336 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
337 async : !this.forceSync,
338 oncomplete : function(r) {
339 var item = openils.Util.readResponse(r);
340 var newvalue = item[linkInfo.vfield.selector]();
342 if(!self.cache[self.auth].single[lclass])
343 self.cache[self.auth].single[lclass] = {};
344 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
346 self.widgetValue = newvalue;
347 self.widget.startup();
348 self._widgetLoaded();
353 _getLinkSelector : function() {
354 var linkClass = this.idlField['class'];
355 if(this.idlField.reltype != 'has_a') return false;
356 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
357 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
360 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
362 for(var f in rclassIdl.fields) {
363 if(this.idlField.key == rclassIdl.fields[f].name) {
364 vfield = rclassIdl.fields[f];
370 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
373 linkClass : linkClass,
378 _buildLinkSelector : function() {
380 var selectorInfo = this._getLinkSelector();
381 if(!selectorInfo) return false;
383 var linkClass = selectorInfo.linkClass;
384 var vfield = selectorInfo.vfield;
388 if(linkClass == 'pgt')
389 return this._buildPermGrpSelector();
390 if(linkClass == 'aou')
391 return this._buildOrgSelector();
392 if(linkClass == 'acpl')
393 return this._buildCopyLocSelector();
396 dojo.require('dojo.data.ItemFileReadStore');
397 dojo.require('dijit.form.FilteringSelect');
399 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
400 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
401 this.widget.valueAttr = vfield.name;
403 var oncomplete = function(list) {
406 self.widget.labelAttr = '_label';
408 if(self.searchFormat)
409 self.widget.searchAttr = '_search';
411 function formatString(item, formatList) {
415 // formatList[1..*] are names of fields. Pull the field
416 // values from each object to determine the values for string substitution
418 var format = formatList[0];
419 for(var i = 1; i< formatList.length; i++)
420 values.push(item[formatList[i]]);
422 return dojo.string.substitute(format, values);
426 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
432 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
434 if(self.labelFormat) {
435 dojo.forEach(storeData.data.items,
437 item._label = formatString(item, self.labelFormat);
442 if(self.searchFormat) {
443 dojo.forEach(storeData.data.items,
445 item._search = formatString(item, self.searchFormat);
450 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
451 self.cache[self.auth].list[linkClass] = self.widget.store;
454 self.widget.store = self.cache[self.auth].list[linkClass];
457 self.widget.startup();
458 self._widgetLoaded();
461 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
466 if(this.dataLoader) {
468 // caller provided an external function for retrieving the data
469 this.dataLoader(linkClass, this.searchFilter, oncomplete);
473 var _cb = function(r) {
474 oncomplete(openils.Util.readResponse(r, false, true));
477 if (this.searchFilter) {
478 new openils.PermaCrud().search(linkClass, this.searchFilter, {
479 async : !this.forceSync, oncomplete : _cb
482 new openils.PermaCrud().retrieveAll(linkClass, {
483 async : !this.forceSync, oncomplete : _cb
493 * For widgets that run asynchronously, provide a callback for finishing up
495 _widgetLoaded : function(value) {
499 /* -------------------------------------------------------------
500 when using widgets in a grid, the cell may dissapear, which
501 kills the underlying DOM node, which causes this to fail.
502 For now, back out gracefully and let grid getters use
503 getDisplayString() instead
504 -------------------------------------------------------------*/
506 this.baseWidgetValue(this.getDisplayString());
511 this.baseWidgetValue(this.widgetValue);
512 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
513 this.widget.attr('disabled', true);
514 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
515 this.widget.attr('disabled', true);
518 this.onload(this.widget, this);
520 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
521 // a required dijit is not given any styling to indicate the value
522 // is invalid until the user has focused the widget then left it with
523 // invalid data. This change tells dojo to pretend this focusing has
524 // already happened so we can style required widgets during page render.
525 this.widget._hasBeenBlurred = true;
526 this.widget.validate();
530 _buildOrgSelector : function() {
531 dojo.require('fieldmapper.OrgUtils');
532 dojo.require('openils.widget.FilteringTreeSelect');
533 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
534 this.widget.searchAttr = 'shortname';
535 this.widget.labelAttr = 'shortname';
536 this.widget.parentField = 'parent_ou';
537 var user = new openils.User();
539 if(this.widgetValue == null && this.orgDefaultsToWs)
540 this.widgetValue = user.user.ws_ou();
542 // if we have a limit perm, find the relevent orgs (async)
543 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
546 user.getPermOrgList(this.orgLimitPerms,
548 self.widget.tree = orgList;
549 self.widget.startup();
550 self._widgetLoaded();
555 this.widget.tree = fieldmapper.aou.globalOrgTree;
556 this.widget.startup();
562 _buildPermGrpSelector : function() {
563 dojo.require('openils.widget.FilteringTreeSelect');
564 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
565 this.widget.disableQuery = this.disableQuery;
566 this.widget.searchAttr = 'name';
568 if(this.cache.permGrpTree) {
569 this.widget.tree = this.cache.permGrpTree;
570 this.widget.startup();
571 this._widgetLoaded();
577 new openils.PermaCrud().retrieveAll('pgt', {
578 async : !this.forceSync,
579 oncomplete : function(r) {
580 var list = openils.Util.readResponse(r, false, true);
585 map[list[l].id()] = list[l];
588 var pnode = map[node.parent()];
589 if(!pnode) {root = node; continue;}
590 if(!pnode.children()) pnode.children([]);
591 pnode.children().push(node);
593 self.widget.tree = self.cache.permGrpTree = root;
594 self.widget.startup();
595 self._widgetLoaded();
602 _buildCopyLocSelector : function() {
603 dojo.require('dijit.form.FilteringSelect');
604 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
605 this.widget.searchAttr = this.widget.labalAttr = 'name';
606 this.widget.valueAttr = 'id';
608 if(this.cache.copyLocStore) {
609 this.widget.store = this.cache.copyLocStore;
610 this.widget.startup();
616 var ws_ou = openils.User.user.ws_ou();
617 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
618 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
621 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
622 async : !this.forceSync,
623 oncomplete : function(r) {
624 var list = openils.Util.readResponse(r, false, true);
627 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
628 self.cache.copyLocStore = self.widget.store;
629 self.widget.startup();
630 self._widgetLoaded();
638 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
639 openils.widget.AutoFieldWidget.cache = {};