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 // find the field description in the IDL if not provided
54 this.fmClass = this.fmObject.classname;
55 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
56 this.suppressLinkedFields = args.suppressLinkedFields || [];
58 if(this.selfReference) {
59 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
61 // create a mock-up of the idlField object.
64 'class' : this.fmClass,
73 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
74 var fields = this.fmIDL.fields;
76 if(fields[f].name == this.fmField)
77 this.idlField = fields[f];
82 throw new Error("AutoFieldWidget could not determine which " +
83 "field to render. We need more information. fmClass=" +
84 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
86 this.auth = openils.User.authtoken;
87 this.cache = openils.widget.AutoFieldWidget.cache;
88 this.cache[this.auth] = this.cache[this.auth] || {};
89 this.cache[this.auth].single = this.cache[this.auth].single || {};
90 this.cache[this.auth].list = this.cache[this.auth].list || {};
94 * Turn the widget-stored value into a value oils understands
96 getFormattedValue : function() {
97 var value = this.baseWidgetValue();
98 switch(this.idlField.datatype) {
101 case 'true': return 't';
102 case 'on': return 't';
103 case 'false' : return 'f';
104 case 'unset' : return null;
105 case true : return 't';
109 if(!value) return null;
110 return dojo.date.stamp.toISOString(value);
114 if(isNaN(value)) value = null;
116 return (value === '') ? null : value;
120 baseWidgetValue : function(value) {
121 var attr = (this.readOnly) ? 'content' : 'value';
122 if(arguments.length) this.widget.attr(attr, value);
123 return this.widget.attr(attr);
127 * Turn the widget-stored value into something visually suitable
129 getDisplayString : function() {
130 var value = this.widgetValue;
131 switch(this.idlField.datatype) {
136 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
139 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
141 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
142 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
143 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
146 if (!value) return '';
147 dojo.require('dojo.date.locale');
148 dojo.require('dojo.date.stamp');
149 var date = dojo.date.stamp.fromISOString(value);
150 return dojo.date.locale.format(date, {formatLength:'short'});
152 if(value === null || value === undefined) return '';
153 return fieldmapper.aou.findOrgUnit(value).shortname();
156 if(isNaN(value)) value = 0;
158 if(value === undefined || value === null)
164 build : function(onload) {
166 if(this.widgetValue == null)
167 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
170 // core widget provided for us, attach and move on
171 if(this.parentNode) // may already be in the "right" place
172 this.parentNode.appendChild(this.widget.domNode);
173 if(this.widget.attr('value') == null)
174 this._widgetLoaded();
178 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
179 this.parentNode = dojo.create('div');
181 this.onload = onload;
184 dojo.require('dijit.layout.ContentPane');
185 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
186 if(this.widgetValue !== null)
187 this._tryLinkedDisplayField();
189 } else if(this.widgetClass) {
190 dojo.require(this.widgetClass);
191 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
195 switch(this.idlField.datatype) {
198 dojo.require('dijit.form.TextBox');
199 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
203 this._buildOrgSelector();
207 dojo.require('dijit.form.CurrencyTextBox');
208 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
212 dojo.require('dijit.form.NumberTextBox');
213 this.dijitArgs = dojo.mixin(this.dijitArgs || {}, {constraints:{places:0}});
214 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
218 dojo.require('dijit.form.NumberTextBox');
219 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
223 dojo.require('dijit.form.DateTextBox');
224 dojo.require('dojo.date.stamp');
225 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
226 if(this.widgetValue != null)
227 this.widgetValue = dojo.date.stamp.fromISOString(this.widgetValue);
232 dojo.require('dijit.form.FilteringSelect');
233 var store = new dojo.data.ItemFileReadStore({
235 identifier : 'value',
237 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
238 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
239 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
243 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
244 this.widget.searchAttr = this.widget.labelAttr = 'label';
245 this.widget.valueAttr = 'value';
246 this.widget.store = store;
247 this.widget.startup();
248 this.widgetValue = (this.widgetValue === null) ? 'unset' :
249 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
251 dojo.require('dijit.form.CheckBox');
252 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
253 this.widgetValue = openils.Util.isTrue(this.widgetValue);
258 if(this._buildLinkSelector()) break;
261 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
262 dojo.require('dijit.form.ValidationTextBox');
263 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
265 dojo.require('dijit.form.TextBox');
266 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
271 if(!this.async) this._widgetLoaded();
275 // we want to display the value for our widget. However, instead of displaying
276 // an ID, for exmaple, display the value for the 'selector' field on the object
278 _tryLinkedDisplayField : function(noAsync) {
280 if(this.idlField.datatype == 'org_unit')
281 return false; // we already handle org_units, no need to re-fetch
283 // user opted to bypass fetching this linked data
284 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
287 var linkInfo = this._getLinkSelector();
288 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
290 var lclass = linkInfo.linkClass;
295 // first try the store cache
297 if(this.cache[this.auth].list[lclass]) {
298 var store = this.cache[this.auth].list[lclass];
300 query[linkInfo.vfield.name] = ''+this.widgetValue;
302 store.fetch({query:query, onComplete:
305 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
314 // then try the single object cache
315 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
316 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
320 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
322 // if those fail, fetch the linked object
325 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
326 async : !this.forceSync,
327 oncomplete : function(r) {
328 var item = openils.Util.readResponse(r);
329 var newvalue = item[linkInfo.vfield.selector]();
331 if(!self.cache[self.auth].single[lclass])
332 self.cache[self.auth].single[lclass] = {};
333 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
335 self.widgetValue = newvalue;
336 self.widget.startup();
337 self._widgetLoaded();
342 _getLinkSelector : function() {
343 var linkClass = this.idlField['class'];
344 if(this.idlField.reltype != 'has_a') return false;
345 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
346 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
349 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
351 for(var f in rclassIdl.fields) {
352 if(this.idlField.key == rclassIdl.fields[f].name) {
353 vfield = rclassIdl.fields[f];
359 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
362 linkClass : linkClass,
367 _buildLinkSelector : function() {
369 var selectorInfo = this._getLinkSelector();
370 if(!selectorInfo) return false;
372 var linkClass = selectorInfo.linkClass;
373 var vfield = selectorInfo.vfield;
377 if(linkClass == 'pgt')
378 return this._buildPermGrpSelector();
379 if(linkClass == 'aou')
380 return this._buildOrgSelector();
381 if(linkClass == 'acpl')
382 return this._buildCopyLocSelector();
385 dojo.require('dojo.data.ItemFileReadStore');
386 dojo.require('dijit.form.FilteringSelect');
388 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
389 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
390 this.widget.valueAttr = vfield.name;
392 var oncomplete = function(list) {
395 self.widget.labelAttr = '_label';
397 if(self.searchFormat)
398 self.widget.searchAttr = '_search';
400 function formatString(item, formatList) {
404 // formatList[1..*] are names of fields. Pull the field
405 // values from each object to determine the values for string substitution
407 var format = formatList[0];
408 for(var i = 1; i< formatList.length; i++)
409 values.push(item[formatList[i]]);
411 return dojo.string.substitute(format, values);
415 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
421 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
423 if(self.labelFormat) {
424 dojo.forEach(storeData.data.items,
426 item._label = formatString(item, self.labelFormat);
431 if(self.searchFormat) {
432 dojo.forEach(storeData.data.items,
434 item._search = formatString(item, self.searchFormat);
439 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
440 self.cache[self.auth].list[linkClass] = self.widget.store;
443 self.widget.store = self.cache[self.auth].list[linkClass];
446 self.widget.startup();
447 self._widgetLoaded();
450 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
455 if(this.dataLoader) {
457 // caller provided an external function for retrieving the data
458 this.dataLoader(linkClass, this.searchFilter, oncomplete);
462 var _cb = function(r) {
463 oncomplete(openils.Util.readResponse(r, false, true));
466 if (this.searchFilter) {
467 new openils.PermaCrud().search(linkClass, this.searchFilter, {
468 async : !this.forceSync, oncomplete : _cb
471 new openils.PermaCrud().retrieveAll(linkClass, {
472 async : !this.forceSync, oncomplete : _cb
482 * For widgets that run asynchronously, provide a callback for finishing up
484 _widgetLoaded : function(value) {
488 /* -------------------------------------------------------------
489 when using widgets in a grid, the cell may dissapear, which
490 kills the underlying DOM node, which causes this to fail.
491 For now, back out gracefully and let grid getters use
492 getDisplayString() instead
493 -------------------------------------------------------------*/
495 this.baseWidgetValue(this.getDisplayString());
500 this.baseWidgetValue(this.widgetValue);
501 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
502 this.widget.attr('disabled', true);
503 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
504 this.widget.attr('disabled', true);
507 this.onload(this.widget, this);
509 if(!this.readOnly && this.dijitArgs && this.dijitArgs.required) {
510 // a required dijit is not given any styling to indicate the value
511 // is invalid until the user has focused the widget then left it with
512 // invalid data. This change tells dojo to pretend this focusing has
513 // already happened so we can style required widgets during page render.
514 this.widget._hasBeenBlurred = true;
515 this.widget.validate();
519 _buildOrgSelector : function() {
520 dojo.require('fieldmapper.OrgUtils');
521 dojo.require('openils.widget.FilteringTreeSelect');
522 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
523 this.widget.searchAttr = 'shortname';
524 this.widget.labelAttr = 'shortname';
525 this.widget.parentField = 'parent_ou';
526 var user = new openils.User();
528 if(this.widgetValue == null && this.orgDefaultsToWs)
529 this.widgetValue = user.user.ws_ou();
531 // if we have a limit perm, find the relevent orgs (async)
532 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
535 user.getPermOrgList(this.orgLimitPerms,
537 self.widget.tree = orgList;
538 self.widget.startup();
539 self._widgetLoaded();
544 this.widget.tree = fieldmapper.aou.globalOrgTree;
545 this.widget.startup();
551 _buildPermGrpSelector : function() {
552 dojo.require('openils.widget.FilteringTreeSelect');
553 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
554 this.widget.searchAttr = 'name';
556 if(this.cache.permGrpTree) {
557 this.widget.tree = this.cache.permGrpTree;
558 this.widget.startup();
564 new openils.PermaCrud().retrieveAll('pgt', {
565 async : !this.forceSync,
566 oncomplete : function(r) {
567 var list = openils.Util.readResponse(r, false, true);
572 map[list[l].id()] = list[l];
575 var pnode = map[node.parent()];
576 if(!pnode) {root = node; continue;}
577 if(!pnode.children()) pnode.children([]);
578 pnode.children().push(node);
580 self.widget.tree = self.cache.permGrpTree = root;
581 self.widget.startup();
582 self._widgetLoaded();
589 _buildCopyLocSelector : function() {
590 dojo.require('dijit.form.FilteringSelect');
591 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
592 this.widget.searchAttr = this.widget.labalAttr = 'name';
593 this.widget.valueAttr = 'id';
595 if(this.cache.copyLocStore) {
596 this.widget.store = this.cache.copyLocStore;
597 this.widget.startup();
603 var ws_ou = openils.User.user.ws_ou();
604 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
605 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
608 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
609 async : !this.forceSync,
610 oncomplete : function(r) {
611 var list = openils.Util.readResponse(r, false, true);
614 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
615 self.cache.copyLocStore = self.widget.store;
616 self.widget.startup();
617 self._widgetLoaded();
625 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
626 openils.widget.AutoFieldWidget.cache = {};