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(
228 // Kludge until the ML returning ISO timestamps with a colon in the timezone offset,
229 // which dojo.date.stamp.fromISOString requires
230 this.widgetValue.replace( /^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d-\d\d)(\d\d)$/, '$1:$2')
236 dojo.require('dijit.form.FilteringSelect');
237 var store = new dojo.data.ItemFileReadStore({
239 identifier : 'value',
241 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
242 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
243 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
247 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
248 this.widget.searchAttr = this.widget.labelAttr = 'label';
249 this.widget.valueAttr = 'value';
250 this.widget.store = store;
251 this.widget.startup();
252 this.widgetValue = (this.widgetValue === null) ? 'unset' :
253 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
255 dojo.require('dijit.form.CheckBox');
256 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
257 this.widgetValue = openils.Util.isTrue(this.widgetValue);
262 if(this._buildLinkSelector()) break;
265 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
266 dojo.require('dijit.form.ValidationTextBox');
267 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
269 dojo.require('dijit.form.TextBox');
270 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
275 if(!this.async) this._widgetLoaded();
279 // we want to display the value for our widget. However, instead of displaying
280 // an ID, for exmaple, display the value for the 'selector' field on the object
282 _tryLinkedDisplayField : function(noAsync) {
284 if(this.idlField.datatype == 'org_unit')
285 return false; // we already handle org_units, no need to re-fetch
287 // user opted to bypass fetching this linked data
288 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
291 var linkInfo = this._getLinkSelector();
292 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
294 var lclass = linkInfo.linkClass;
299 // first try the store cache
301 if(this.cache[this.auth].list[lclass]) {
302 var store = this.cache[this.auth].list[lclass];
304 query[linkInfo.vfield.name] = ''+this.widgetValue;
306 store.fetch({query:query, onComplete:
309 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
318 // then try the single object cache
319 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
320 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
324 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
326 // if those fail, fetch the linked object
329 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
330 async : !this.forceSync,
331 oncomplete : function(r) {
332 var item = openils.Util.readResponse(r);
333 var newvalue = item[linkInfo.vfield.selector]();
335 if(!self.cache[self.auth].single[lclass])
336 self.cache[self.auth].single[lclass] = {};
337 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
339 self.widgetValue = newvalue;
340 self.widget.startup();
341 self._widgetLoaded();
346 _getLinkSelector : function() {
347 var linkClass = this.idlField['class'];
348 if(this.idlField.reltype != 'has_a') return false;
349 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
350 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
353 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
355 for(var f in rclassIdl.fields) {
356 if(this.idlField.key == rclassIdl.fields[f].name) {
357 vfield = rclassIdl.fields[f];
363 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
366 linkClass : linkClass,
371 _buildLinkSelector : function() {
373 var selectorInfo = this._getLinkSelector();
374 if(!selectorInfo) return false;
376 var linkClass = selectorInfo.linkClass;
377 var vfield = selectorInfo.vfield;
381 if(linkClass == 'pgt')
382 return this._buildPermGrpSelector();
383 if(linkClass == 'aou')
384 return this._buildOrgSelector();
385 if(linkClass == 'acpl')
386 return this._buildCopyLocSelector();
389 dojo.require('dojo.data.ItemFileReadStore');
390 dojo.require('dijit.form.FilteringSelect');
392 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
393 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
394 this.widget.valueAttr = vfield.name;
396 var oncomplete = function(list) {
399 self.widget.labelAttr = '_label';
401 if(self.searchFormat)
402 self.widget.searchAttr = '_search';
404 function formatString(item, formatList) {
408 // formatList[1..*] are names of fields. Pull the field
409 // values from each object to determine the values for string substitution
411 var format = formatList[0];
412 for(var i = 1; i< formatList.length; i++)
413 values.push(item[formatList[i]]);
415 return dojo.string.substitute(format, values);
419 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
425 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
427 if(self.labelFormat) {
428 dojo.forEach(storeData.data.items,
430 item._label = formatString(item, self.labelFormat);
435 if(self.searchFormat) {
436 dojo.forEach(storeData.data.items,
438 item._search = formatString(item, self.searchFormat);
443 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
444 self.cache[self.auth].list[linkClass] = self.widget.store;
447 self.widget.store = self.cache[self.auth].list[linkClass];
450 self.widget.startup();
451 self._widgetLoaded();
454 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
459 if(this.dataLoader) {
461 // caller provided an external function for retrieving the data
462 this.dataLoader(linkClass, this.searchFilter, oncomplete);
466 var _cb = function(r) {
467 oncomplete(openils.Util.readResponse(r, false, true));
470 if (this.searchFilter) {
471 new openils.PermaCrud().search(linkClass, this.searchFilter, {
472 async : !this.forceSync, oncomplete : _cb
475 new openils.PermaCrud().retrieveAll(linkClass, {
476 async : !this.forceSync, oncomplete : _cb
486 * For widgets that run asynchronously, provide a callback for finishing up
488 _widgetLoaded : function(value) {
492 /* -------------------------------------------------------------
493 when using widgets in a grid, the cell may dissapear, which
494 kills the underlying DOM node, which causes this to fail.
495 For now, back out gracefully and let grid getters use
496 getDisplayString() instead
497 -------------------------------------------------------------*/
499 this.baseWidgetValue(this.getDisplayString());
504 this.baseWidgetValue(this.widgetValue);
505 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
506 this.widget.attr('disabled', true);
507 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
508 this.widget.attr('disabled', true);
511 this.onload(this.widget, this);
513 if(!this.readOnly && this.dijitArgs && this.dijitArgs.required) {
514 // a required dijit is not given any styling to indicate the value
515 // is invalid until the user has focused the widget then left it with
516 // invalid data. This change tells dojo to pretend this focusing has
517 // already happened so we can style required widgets during page render.
518 this.widget._hasBeenBlurred = true;
519 this.widget.validate();
523 _buildOrgSelector : function() {
524 dojo.require('fieldmapper.OrgUtils');
525 dojo.require('openils.widget.FilteringTreeSelect');
526 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
527 this.widget.searchAttr = 'shortname';
528 this.widget.labelAttr = 'shortname';
529 this.widget.parentField = 'parent_ou';
530 var user = new openils.User();
532 if(this.widgetValue == null && this.orgDefaultsToWs)
533 this.widgetValue = user.user.ws_ou();
535 // if we have a limit perm, find the relevent orgs (async)
536 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
539 user.getPermOrgList(this.orgLimitPerms,
541 self.widget.tree = orgList;
542 self.widget.startup();
543 self._widgetLoaded();
548 this.widget.tree = fieldmapper.aou.globalOrgTree;
549 this.widget.startup();
555 _buildPermGrpSelector : function() {
556 dojo.require('openils.widget.FilteringTreeSelect');
557 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
558 this.widget.searchAttr = 'name';
560 if(this.cache.permGrpTree) {
561 this.widget.tree = this.cache.permGrpTree;
562 this.widget.startup();
568 new openils.PermaCrud().retrieveAll('pgt', {
569 async : !this.forceSync,
570 oncomplete : function(r) {
571 var list = openils.Util.readResponse(r, false, true);
576 map[list[l].id()] = list[l];
579 var pnode = map[node.parent()];
580 if(!pnode) {root = node; continue;}
581 if(!pnode.children()) pnode.children([]);
582 pnode.children().push(node);
584 self.widget.tree = self.cache.permGrpTree = root;
585 self.widget.startup();
586 self._widgetLoaded();
593 _buildCopyLocSelector : function() {
594 dojo.require('dijit.form.FilteringSelect');
595 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
596 this.widget.searchAttr = this.widget.labalAttr = 'name';
597 this.widget.valueAttr = 'id';
599 if(this.cache.copyLocStore) {
600 this.widget.store = this.cache.copyLocStore;
601 this.widget.startup();
607 var ws_ou = openils.User.user.ws_ou();
608 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
609 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
612 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
613 async : !this.forceSync,
614 oncomplete : function(r) {
615 var list = openils.Util.readResponse(r, false, true);
618 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
619 self.cache.copyLocStore = self.widget.store;
620 self.widget.startup();
621 self._widgetLoaded();
629 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
630 openils.widget.AutoFieldWidget.cache = {};