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;
140 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
141 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
142 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
145 if (!value) return '';
146 dojo.require('dojo.date.locale');
147 dojo.require('dojo.date.stamp');
148 var date = dojo.date.stamp.fromISOString(value);
149 return dojo.date.locale.format(date, {formatLength:'short'});
151 if(value === null || value === undefined) return '';
152 return fieldmapper.aou.findOrgUnit(value).shortname();
155 if(isNaN(value)) value = 0;
157 if(value === undefined || value === null)
163 build : function(onload) {
165 if(this.widgetValue == null)
166 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
169 // core widget provided for us, attach and move on
170 if(this.parentNode) // may already be in the "right" place
171 this.parentNode.appendChild(this.widget.domNode);
172 if(this.widget.attr('value') == null)
173 this._widgetLoaded();
177 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
178 this.parentNode = dojo.create('div');
180 this.onload = onload;
183 dojo.require('dijit.layout.ContentPane');
184 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
185 if(this.widgetValue !== null)
186 this._tryLinkedDisplayField();
188 } else if(this.widgetClass) {
189 dojo.require(this.widgetClass);
190 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
194 switch(this.idlField.datatype) {
197 dojo.require('dijit.form.TextBox');
198 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
202 this._buildOrgSelector();
206 dojo.require('dijit.form.CurrencyTextBox');
207 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
211 dojo.require('dijit.form.NumberTextBox');
212 this.dijitArgs = dojo.mixin(this.dijitArgs || {}, {constraints:{places:0}});
213 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
217 dojo.require('dijit.form.NumberTextBox');
218 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
222 dojo.require('dijit.form.DateTextBox');
223 dojo.require('dojo.date.stamp');
224 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
225 if(this.widgetValue != null)
226 this.widgetValue = dojo.date.stamp.fromISOString(this.widgetValue);
231 dojo.require('dijit.form.FilteringSelect');
232 var store = new dojo.data.ItemFileReadStore({
234 identifier : 'value',
236 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
237 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
238 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
242 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
243 this.widget.searchAttr = this.widget.labelAttr = 'label';
244 this.widget.valueAttr = 'value';
245 this.widget.store = store;
246 this.widget.startup();
247 this.widgetValue = (this.widgetValue === null) ? 'unset' :
248 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
250 dojo.require('dijit.form.CheckBox');
251 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
252 this.widgetValue = openils.Util.isTrue(this.widgetValue);
257 if(this._buildLinkSelector()) break;
260 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
261 dojo.require('dijit.form.ValidationTextBox');
262 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
264 dojo.require('dijit.form.TextBox');
265 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
270 if(!this.async) this._widgetLoaded();
274 // we want to display the value for our widget. However, instead of displaying
275 // an ID, for exmaple, display the value for the 'selector' field on the object
277 _tryLinkedDisplayField : function(noAsync) {
279 if(this.idlField.datatype == 'org_unit')
280 return false; // we already handle org_units, no need to re-fetch
282 // user opted to bypass fetching this linked data
283 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
286 var linkInfo = this._getLinkSelector();
287 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
289 var lclass = linkInfo.linkClass;
294 // first try the store cache
296 if(this.cache[this.auth].list[lclass]) {
297 var store = this.cache[this.auth].list[lclass];
299 query[linkInfo.vfield.name] = ''+this.widgetValue;
301 store.fetch({query:query, onComplete:
304 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
313 // then try the single object cache
314 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
315 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
319 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
321 // if those fail, fetch the linked object
324 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
325 async : !this.forceSync,
326 oncomplete : function(r) {
327 var item = openils.Util.readResponse(r);
328 var newvalue = item[linkInfo.vfield.selector]();
330 if(!self.cache[self.auth].single[lclass])
331 self.cache[self.auth].single[lclass] = {};
332 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
334 self.widgetValue = newvalue;
335 self.widget.startup();
336 self._widgetLoaded();
341 _getLinkSelector : function() {
342 var linkClass = this.idlField['class'];
343 if(this.idlField.reltype != 'has_a') return false;
344 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
345 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
348 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
350 for(var f in rclassIdl.fields) {
351 if(this.idlField.key == rclassIdl.fields[f].name) {
352 vfield = rclassIdl.fields[f];
358 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
361 linkClass : linkClass,
366 _buildLinkSelector : function() {
368 var selectorInfo = this._getLinkSelector();
369 if(!selectorInfo) return false;
371 var linkClass = selectorInfo.linkClass;
372 var vfield = selectorInfo.vfield;
376 if(linkClass == 'pgt')
377 return this._buildPermGrpSelector();
378 if(linkClass == 'aou')
379 return this._buildOrgSelector();
380 if(linkClass == 'acpl')
381 return this._buildCopyLocSelector();
384 dojo.require('dojo.data.ItemFileReadStore');
385 dojo.require('dijit.form.FilteringSelect');
387 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
388 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
389 this.widget.valueAttr = vfield.name;
391 var oncomplete = function(list) {
394 self.widget.labelAttr = '_label';
396 if(self.searchFormat)
397 self.widget.searchAttr = '_search';
399 function formatString(item, formatList) {
403 // formatList[1..*] are names of fields. Pull the field
404 // values from each object to determine the values for string substitution
406 var format = formatList[0];
407 for(var i = 1; i< formatList.length; i++)
408 values.push(item[formatList[i]]);
410 return dojo.string.substitute(format, values);
414 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
420 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
422 if(self.labelFormat) {
423 dojo.forEach(storeData.data.items,
425 item._label = formatString(item, self.labelFormat);
430 if(self.searchFormat) {
431 dojo.forEach(storeData.data.items,
433 item._search = formatString(item, self.searchFormat);
438 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
439 self.cache[self.auth].list[linkClass] = self.widget.store;
442 self.widget.store = self.cache[self.auth].list[linkClass];
445 self.widget.startup();
446 self._widgetLoaded();
449 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
454 if(this.dataLoader) {
456 // caller provided an external function for retrieving the data
457 this.dataLoader(linkClass, this.searchFilter, oncomplete);
461 var _cb = function(r) {
462 oncomplete(openils.Util.readResponse(r, false, true));
465 if (this.searchFilter) {
466 new openils.PermaCrud().search(linkClass, this.searchFilter, {
467 async : !this.forceSync, oncomplete : _cb
470 new openils.PermaCrud().retrieveAll(linkClass, {
471 async : !this.forceSync, oncomplete : _cb
481 * For widgets that run asynchronously, provide a callback for finishing up
483 _widgetLoaded : function(value) {
487 /* -------------------------------------------------------------
488 when using widgets in a grid, the cell may dissapear, which
489 kills the underlying DOM node, which causes this to fail.
490 For now, back out gracefully and let grid getters use
491 getDisplayString() instead
492 -------------------------------------------------------------*/
494 this.baseWidgetValue(this.getDisplayString());
499 this.baseWidgetValue(this.widgetValue);
500 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
501 this.widget.attr('disabled', true);
502 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
503 this.widget.attr('disabled', true);
506 this.onload(this.widget, this);
508 if(!this.readOnly && this.dijitArgs && this.dijitArgs.required) {
509 // a required dijit is not given any styling to indicate the value
510 // is invalid until the user has focused the widget then left it with
511 // invalid data. This change tells dojo to pretend this focusing has
512 // already happened so we can style required widgets during page render.
513 this.widget._hasBeenBlurred = true;
514 this.widget.validate();
518 _buildOrgSelector : function() {
519 dojo.require('fieldmapper.OrgUtils');
520 dojo.require('openils.widget.FilteringTreeSelect');
521 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
522 this.widget.searchAttr = 'shortname';
523 this.widget.labelAttr = 'shortname';
524 this.widget.parentField = 'parent_ou';
525 var user = new openils.User();
527 if(this.widgetValue == null && this.orgDefaultsToWs)
528 this.widgetValue = user.user.ws_ou();
530 // if we have a limit perm, find the relevent orgs (async)
531 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
534 user.getPermOrgList(this.orgLimitPerms,
536 self.widget.tree = orgList;
537 self.widget.startup();
538 self._widgetLoaded();
543 this.widget.tree = fieldmapper.aou.globalOrgTree;
544 this.widget.startup();
550 _buildPermGrpSelector : function() {
551 dojo.require('openils.widget.FilteringTreeSelect');
552 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
553 this.widget.searchAttr = 'name';
555 if(this.cache.permGrpTree) {
556 this.widget.tree = this.cache.permGrpTree;
557 this.widget.startup();
563 new openils.PermaCrud().retrieveAll('pgt', {
564 async : !this.forceSync,
565 oncomplete : function(r) {
566 var list = openils.Util.readResponse(r, false, true);
571 map[list[l].id()] = list[l];
574 var pnode = map[node.parent()];
575 if(!pnode) {root = node; continue;}
576 if(!pnode.children()) pnode.children([]);
577 pnode.children().push(node);
579 self.widget.tree = self.cache.permGrpTree = root;
580 self.widget.startup();
581 self._widgetLoaded();
588 _buildCopyLocSelector : function() {
589 dojo.require('dijit.form.FilteringSelect');
590 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
591 this.widget.searchAttr = this.widget.labalAttr = 'name';
592 this.widget.valueAttr = 'id';
594 if(this.cache.copyLocStore) {
595 this.widget.store = this.cache.copyLocStore;
596 this.widget.startup();
602 var ws_ou = openils.User.user.ws_ou();
603 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
604 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
607 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
608 async : !this.forceSync,
609 oncomplete : function(r) {
610 var list = openils.Util.readResponse(r, false, true);
613 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
614 self.cache.copyLocStore = self.widget.store;
615 self.widget.startup();
616 self._widgetLoaded();
624 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
625 openils.widget.AutoFieldWidget.cache = {};