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 * dataLoader : Bypass the default PermaCrud linked data fetcher and use this function instead.
40 * Function arguments are (link class name, search filter, callback)
41 * The fetched objects should be passed to the callback as an array
43 constructor : function(args) {
47 // find the field description in the IDL if not provided
49 this.fmClass = this.fmObject.classname;
50 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
51 this.suppressLinkedFields = args.suppressLinkedFields || [];
53 if(this.selfReference) {
54 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
56 // create a mock-up of the idlField object.
59 'class' : this.fmClass,
68 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
69 var fields = this.fmIDL.fields;
71 if(fields[f].name == this.fmField)
72 this.idlField = fields[f];
77 throw new Error("AutoFieldWidget could not determine which " +
78 "field to render. We need more information. fmClass=" +
79 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
81 this.auth = openils.User.authtoken;
82 this.cache = openils.widget.AutoFieldWidget.cache;
83 this.cache[this.auth] = this.cache[this.auth] || {};
84 this.cache[this.auth].single = this.cache[this.auth].single || {};
85 this.cache[this.auth].list = this.cache[this.auth].list || {};
89 * Turn the widget-stored value into a value oils understands
91 getFormattedValue : function() {
92 var value = this.baseWidgetValue();
93 switch(this.idlField.datatype) {
96 case 'true': return 't';
97 case 'on': return 't';
98 case 'false' : return 'f';
99 case 'unset' : return null;
100 case true : return 't';
104 if(!value) return null;
105 return dojo.date.stamp.toISOString(value);
109 if(isNaN(value)) value = null;
111 return (value === '') ? null : value;
115 baseWidgetValue : function(value) {
116 var attr = (this.readOnly) ? 'content' : 'value';
117 if(arguments.length) this.widget.attr(attr, value);
118 return this.widget.attr(attr);
122 * Turn the widget-stored value into something visually suitable
124 getDisplayString : function() {
125 var value = this.widgetValue;
126 switch(this.idlField.datatype) {
131 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
134 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
135 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
136 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
137 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
140 if (!value) return '';
141 dojo.require('dojo.date.locale');
142 dojo.require('dojo.date.stamp');
143 var date = dojo.date.stamp.fromISOString(value);
144 return dojo.date.locale.format(date, {formatLength:'short'});
146 if(value === null || value === undefined) return '';
147 return fieldmapper.aou.findOrgUnit(value).shortname();
150 if(isNaN(value)) value = 0;
152 if(value === undefined || value === null)
158 build : function(onload) {
160 if(this.widgetValue == null)
161 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
164 // core widget provided for us, attach and move on
165 if(this.parentNode) // may already be in the "right" place
166 this.parentNode.appendChild(this.widget.domNode);
167 if(this.widget.attr('value') == null)
168 this._widgetLoaded();
172 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
173 this.parentNode = dojo.create('div');
175 this.onload = onload;
178 dojo.require('dijit.layout.ContentPane');
179 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
180 if(this.widgetValue !== null)
181 this._tryLinkedDisplayField();
183 } else if(this.widgetClass) {
184 dojo.require(this.widgetClass);
185 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
189 switch(this.idlField.datatype) {
192 dojo.require('dijit.form.TextBox');
193 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
197 this._buildOrgSelector();
201 dojo.require('dijit.form.CurrencyTextBox');
202 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
206 dojo.require('dijit.form.NumberTextBox');
207 this.dijitArgs = dojo.mixin(this.dijitArgs || {}, {constraints:{places:0}});
208 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
212 dojo.require('dijit.form.NumberTextBox');
213 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
217 dojo.require('dijit.form.DateTextBox');
218 dojo.require('dojo.date.stamp');
219 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
220 if(this.widgetValue != null)
221 this.widgetValue = dojo.date.stamp.fromISOString(this.widgetValue);
226 dojo.require('dijit.form.FilteringSelect');
227 var store = new dojo.data.ItemFileReadStore({
229 identifier : 'value',
231 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
232 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
233 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
237 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
238 this.widget.searchAttr = this.widget.labelAttr = 'label';
239 this.widget.valueAttr = 'value';
240 this.widget.store = store;
241 this.widget.startup();
242 this.widgetValue = (this.widgetValue === null) ? 'unset' :
243 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
245 dojo.require('dijit.form.CheckBox');
246 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
247 this.widgetValue = openils.Util.isTrue(this.widgetValue);
252 if(this._buildLinkSelector()) break;
255 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
256 dojo.require('dijit.form.ValidationTextBox');
257 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
259 dojo.require('dijit.form.TextBox');
260 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
265 if(!this.async) this._widgetLoaded();
269 // we want to display the value for our widget. However, instead of displaying
270 // an ID, for exmaple, display the value for the 'selector' field on the object
272 _tryLinkedDisplayField : function(noAsync) {
274 if(this.idlField.datatype == 'org_unit')
275 return false; // we already handle org_units, no need to re-fetch
277 // user opted to bypass fetching this linked data
278 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
281 var linkInfo = this._getLinkSelector();
282 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
284 var lclass = linkInfo.linkClass;
286 if(lclass == 'aou') {
287 this.widgetValue = fieldmapper.aou.findOrgUnit(this.widgetValue).shortname();
291 // first try the store cache
293 if(this.cache[this.auth].list[lclass]) {
294 var store = this.cache[this.auth].list[lclass];
296 query[linkInfo.vfield.name] = ''+this.widgetValue;
297 store.fetch({query:query, onComplete:
299 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
305 // then try the single object cache
306 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
307 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
311 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
313 // if those fail, fetch the linked object
316 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
317 async : !this.forceSync,
318 oncomplete : function(r) {
319 var item = openils.Util.readResponse(r);
320 var newvalue = item[linkInfo.vfield.selector]();
322 if(!self.cache[self.auth].single[lclass])
323 self.cache[self.auth].single[lclass] = {};
324 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
326 self.widgetValue = newvalue;
327 self.widget.startup();
328 self._widgetLoaded();
333 _getLinkSelector : function() {
334 var linkClass = this.idlField['class'];
335 if(this.idlField.reltype != 'has_a') return false;
336 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
337 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
340 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
342 for(var f in rclassIdl.fields) {
343 if(this.idlField.key == rclassIdl.fields[f].name) {
344 vfield = rclassIdl.fields[f];
350 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
353 linkClass : linkClass,
358 _buildLinkSelector : function() {
360 var selectorInfo = this._getLinkSelector();
361 if(!selectorInfo) return false;
363 var linkClass = selectorInfo.linkClass;
364 var vfield = selectorInfo.vfield;
368 if(linkClass == 'pgt')
369 return this._buildPermGrpSelector();
370 if(linkClass == 'aou')
371 return this._buildOrgSelector();
372 if(linkClass == 'acpl')
373 return this._buildCopyLocSelector();
376 dojo.require('dojo.data.ItemFileReadStore');
377 dojo.require('dijit.form.FilteringSelect');
379 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
380 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
381 this.widget.valueAttr = vfield.name;
383 var oncomplete = function(list) {
386 self.widget.labelAttr = self.widget.searchAttr = '_label';
389 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
391 if(self.labelFormat) {
393 // set the label for each value in the store based on the provided label format.
395 var format = self.labelFormat[0];
396 dojo.forEach(storeData.data.items,
403 // self.labelFormat[1..*] are names of fields. Pull the field
404 // values from each object to determine the values for string substitution
405 for(var i = 1; i< self.labelFormat.length; i++)
406 values.push(item[self.labelFormat[i]]);
408 item._label = dojo.string.substitute(format, values);
411 throw new Error("openils.widget.AutoFieldWidget: Invalid labelFormat [" +
412 self.labelFormat + "] : " + E);
418 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
419 self.cache[self.auth].list[linkClass] = self.widget.store;
422 self.widget.store = self.cache[self.auth].list[linkClass];
425 self.widget.startup();
426 self._widgetLoaded();
429 if(this.cache[self.auth].list[linkClass]) {
434 if(this.dataLoader) {
436 // caller provided an external function for retrieving the data
437 this.dataLoader(linkClass, this.searchFilter, oncomplete);
441 var _cb = function(r) {
442 oncomplete(openils.Util.readResponse(r, false, true));
445 if (this.searchFilter) {
446 new openils.PermaCrud().search(linkClass, this.searchFilter, {
447 async : !this.forceSync, oncomplete : _cb
450 new openils.PermaCrud().retrieveAll(linkClass, {
451 async : !this.forceSync, oncomplete : _cb
461 * For widgets that run asynchronously, provide a callback for finishing up
463 _widgetLoaded : function(value) {
467 /* -------------------------------------------------------------
468 when using widgets in a grid, the cell may dissapear, which
469 kills the underlying DOM node, which causes this to fail.
470 For now, back out gracefully and let grid getters use
471 getDisplayString() instead
472 -------------------------------------------------------------*/
474 this.baseWidgetValue(this.getDisplayString());
479 this.baseWidgetValue(this.widgetValue);
480 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
481 this.widget.attr('disabled', true);
482 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
483 this.widget.attr('disabled', true);
486 this.onload(this.widget, this);
489 _buildOrgSelector : function() {
490 dojo.require('fieldmapper.OrgUtils');
491 dojo.require('openils.widget.FilteringTreeSelect');
492 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
493 this.widget.searchAttr = 'shortname';
494 this.widget.labelAttr = 'shortname';
495 this.widget.parentField = 'parent_ou';
496 var user = new openils.User();
498 if(this.widgetValue == null && this.orgDefaultsToWs)
499 this.widgetValue = user.user.ws_ou();
501 // if we have a limit perm, find the relevent orgs (async)
502 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
505 user.getPermOrgList(this.orgLimitPerms,
507 self.widget.tree = orgList;
508 self.widget.startup();
509 self._widgetLoaded();
514 this.widget.tree = fieldmapper.aou.globalOrgTree;
515 this.widget.startup();
521 _buildPermGrpSelector : function() {
522 dojo.require('openils.widget.FilteringTreeSelect');
523 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
524 this.widget.searchAttr = 'name';
526 if(this.cache.permGrpTree) {
527 this.widget.tree = this.cache.permGrpTree;
528 this.widget.startup();
534 new openils.PermaCrud().retrieveAll('pgt', {
535 async : !this.forceSync,
536 oncomplete : function(r) {
537 var list = openils.Util.readResponse(r, false, true);
542 map[list[l].id()] = list[l];
545 var pnode = map[node.parent()];
546 if(!pnode) {root = node; continue;}
547 if(!pnode.children()) pnode.children([]);
548 pnode.children().push(node);
550 self.widget.tree = self.cache.permGrpTree = root;
551 self.widget.startup();
552 self._widgetLoaded();
559 _buildCopyLocSelector : function() {
560 dojo.require('dijit.form.FilteringSelect');
561 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
562 this.widget.searchAttr = this.widget.labalAttr = 'name';
563 this.widget.valueAttr = 'id';
565 if(this.cache.copyLocStore) {
566 this.widget.store = this.cache.copyLocStore;
567 this.widget.startup();
573 var ws_ou = openils.User.user.ws_ou();
574 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
575 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
578 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
579 async : !this.forceSync,
580 oncomplete : function(r) {
581 var list = openils.Util.readResponse(r, false, true);
584 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
585 self.cache.copyLocStore = self.widget.store;
586 self.widget.startup();
587 self._widgetLoaded();
595 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
596 openils.widget.AutoFieldWidget.cache = {};