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;
60 // find the field description in the IDL if not provided
62 this.fmClass = this.fmObject.classname;
63 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
65 if(this.fmClass && !this.fmIDL) {
66 fieldmapper.IDL.load([this.fmClass]);
67 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
70 this.suppressLinkedFields = args.suppressLinkedFields || [];
72 if(this.selfReference) {
73 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
75 // create a mock-up of the idlField object.
78 'class' : this.fmClass,
87 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
88 var fields = this.fmIDL.fields;
90 if(fields[f].name == this.fmField)
91 this.idlField = fields[f];
96 throw new Error("AutoFieldWidget could not determine which " +
97 "field to render. We need more information. fmClass=" +
98 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
100 this.auth = openils.User.authtoken;
101 this.cache = openils.widget.AutoFieldWidget.cache;
102 this.cache[this.auth] = this.cache[this.auth] || {};
103 this.cache[this.auth].single = this.cache[this.auth].single || {};
104 this.cache[this.auth].list = this.cache[this.auth].list || {};
108 * Turn the widget-stored value into a value oils understands
110 getFormattedValue : function() {
111 var value = this.baseWidgetValue();
112 switch(this.idlField.datatype) {
115 case 'true': return 't';
116 case 'on': return 't';
117 case 'false' : return 'f';
118 case 'unset' : return null;
119 case true : return 't';
123 if(!value) return null;
124 return dojo.date.stamp.toISOString(value);
128 if(isNaN(value)) value = null;
130 return (value === '') ? null : value;
134 baseWidgetValue : function(value) {
135 var attr = (this.readOnly) ? 'content' : 'value';
136 if(arguments.length) this.widget.attr(attr, value);
137 return this.widget.attr(attr);
141 * Turn the widget-stored value into something visually suitable
143 getDisplayString : function() {
144 var value = this.widgetValue;
145 switch(this.idlField.datatype) {
150 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
153 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
155 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
156 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
157 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
160 if (!value) return '';
161 return openils.Util.timeStamp(
162 value, {"formatLength": "short"}
165 if(value === null || value === undefined) return '';
166 return fieldmapper.aou.findOrgUnit(value).shortname();
169 if(isNaN(value)) value = 0;
171 if(value === undefined || value === null)
177 build : function(onload) {
179 if(this.widgetValue == null)
180 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
183 // core widget provided for us, attach and move on
184 if(this.parentNode) // may already be in the "right" place
185 this.parentNode.appendChild(this.widget.domNode);
187 if (this.shove.mode == "update")
188 this.widget.attr("value", this.widgetValue);
190 this.widgetValue = this.shove.create;
191 this._widgetLoaded();
192 } else if (this.widget.attr("value") == null) {
193 this._widgetLoaded();
198 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
199 this.parentNode = dojo.create('div');
201 this.onload = onload;
204 dojo.require('dijit.layout.ContentPane');
205 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
206 if(this.widgetValue !== null)
207 this._tryLinkedDisplayField();
209 } else if(this.widgetClass) {
210 dojo.require(this.widgetClass);
211 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
215 switch(this.idlField.datatype) {
218 dojo.require('dijit.form.TextBox');
219 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
223 this._buildOrgSelector();
227 dojo.require('dijit.form.CurrencyTextBox');
228 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
232 dojo.require('dijit.form.NumberTextBox');
233 this.dijitArgs = dojo.mixin({constraints:{places:0}}, this.dijitArgs || {});
234 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
238 dojo.require('dijit.form.NumberTextBox');
239 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
243 dojo.require('dijit.form.DateTextBox');
244 dojo.require('dojo.date.stamp');
245 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
246 if (this.widgetValue != null) {
247 this.widgetValue = openils.Util.timeStampAsDateObj(
255 dojo.require('dijit.form.FilteringSelect');
256 var store = new dojo.data.ItemFileReadStore({
258 identifier : 'value',
260 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
261 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
262 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
266 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
267 this.widget.searchAttr = this.widget.labelAttr = 'label';
268 this.widget.valueAttr = 'value';
269 this.widget.store = store;
270 this.widget.startup();
271 this.widgetValue = (this.widgetValue === null) ? 'unset' :
272 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
274 dojo.require('dijit.form.CheckBox');
275 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
276 this.widgetValue = openils.Util.isTrue(this.widgetValue);
281 if(this._buildLinkSelector()) break;
284 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
285 dojo.require('dijit.form.ValidationTextBox');
286 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
288 dojo.require('dijit.form.TextBox');
289 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
294 if(!this.async) this._widgetLoaded();
298 // we want to display the value for our widget. However, instead of displaying
299 // an ID, for exmaple, display the value for the 'selector' field on the object
301 _tryLinkedDisplayField : function(noAsync) {
303 if(this.idlField.datatype == 'org_unit')
304 return false; // we already handle org_units, no need to re-fetch
306 // user opted to bypass fetching this linked data
307 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
310 var linkInfo = this._getLinkSelector();
311 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
313 var lclass = linkInfo.linkClass;
318 // first try the store cache
320 if(this.cache[this.auth].list[lclass]) {
321 var store = this.cache[this.auth].list[lclass];
323 query[linkInfo.vfield.name] = ''+this.widgetValue;
325 store.fetch({query:query, onComplete:
328 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
337 // then try the single object cache
338 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
339 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
343 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
345 // if those fail, fetch the linked object
348 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
349 async : !this.forceSync,
350 oncomplete : function(r) {
351 var item = openils.Util.readResponse(r);
352 var newvalue = item[linkInfo.vfield.selector]();
354 if(!self.cache[self.auth].single[lclass])
355 self.cache[self.auth].single[lclass] = {};
356 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
358 self.widgetValue = newvalue;
359 self.widget.startup();
360 self._widgetLoaded();
365 _getLinkSelector : function() {
366 var linkClass = this.idlField['class'];
367 if(this.idlField.reltype != 'has_a') return false;
368 if(!fieldmapper.IDL.fmclasses[linkClass]) // class neglected by AutoIDL
369 fieldmapper.IDL.load([linkClass]);
370 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
371 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
374 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
376 for(var f in rclassIdl.fields) {
377 if(this.idlField.key == rclassIdl.fields[f].name) {
378 vfield = rclassIdl.fields[f];
384 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
387 linkClass : linkClass,
392 _buildLinkSelector : function() {
394 var selectorInfo = this._getLinkSelector();
395 if(!selectorInfo) return false;
397 var linkClass = selectorInfo.linkClass;
398 var vfield = selectorInfo.vfield;
402 if(linkClass == 'pgt')
403 return this._buildPermGrpSelector();
404 if(linkClass == 'aou')
405 return this._buildOrgSelector();
406 if(linkClass == 'acpl')
407 return this._buildCopyLocSelector();
410 dojo.require('dojo.data.ItemFileReadStore');
411 dojo.require('dijit.form.FilteringSelect');
413 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
414 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
415 this.widget.valueAttr = vfield.name;
416 this.widget.attr('disabled', true);
418 var oncomplete = function(list) {
419 self.widget.attr('disabled', false);
422 self.widget.labelAttr = '_label';
424 if(self.searchFormat)
425 self.widget.searchAttr = '_search';
427 function formatString(item, formatList) {
431 // formatList[1..*] are names of fields. Pull the field
432 // values from each object to determine the values for string substitution
434 var format = formatList[0];
435 for(var i = 1; i< formatList.length; i++)
436 values.push(item[formatList[i]]);
438 return dojo.string.substitute(format, values);
442 "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
448 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
450 if(self.labelFormat) {
451 dojo.forEach(storeData.data.items,
453 item._label = formatString(item, self.labelFormat);
458 if(self.searchFormat) {
459 dojo.forEach(storeData.data.items,
461 item._search = formatString(item, self.searchFormat);
466 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
467 self.cache[self.auth].list[linkClass] = self.widget.store;
470 self.widget.store = self.cache[self.auth].list[linkClass];
473 self.widget.startup();
474 self._widgetLoaded();
477 if(!this.noCache && this.cache[self.auth].list[linkClass]) {
482 if(!this.dataLoader && openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass])
483 this.dataLoader = openils.widget.AutoFieldWidget.defaultLinkedDataLoader[linkClass];
485 if(this.dataLoader) {
487 // caller provided an external function for retrieving the data
488 this.dataLoader(linkClass, this.searchFilter, oncomplete);
492 var _cb = function(r) {
493 oncomplete(openils.Util.readResponse(r, false, true));
496 if (this.searchFilter) {
497 new openils.PermaCrud().search(linkClass, this.searchFilter, {
498 async : !this.forceSync, oncomplete : _cb
501 new openils.PermaCrud().retrieveAll(linkClass, {
502 async : !this.forceSync, oncomplete : _cb
512 * For widgets that run asynchronously, provide a callback for finishing up
514 _widgetLoaded : function(value) {
518 /* -------------------------------------------------------------
519 when using widgets in a grid, the cell may dissapear, which
520 kills the underlying DOM node, which causes this to fail.
521 For now, back out gracefully and let grid getters use
522 getDisplayString() instead
523 -------------------------------------------------------------*/
525 this.baseWidgetValue(this.getDisplayString());
530 this.baseWidgetValue(this.widgetValue);
531 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && (!this.selfReference && !this.noDisablePkey))
532 this.widget.attr('disabled', true);
533 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
534 this.widget.attr('disabled', true);
537 this.onload(this.widget, this);
539 if(!this.readOnly && (this.idlField.required || (this.dijitArgs && this.dijitArgs.required))) {
540 // a required dijit is not given any styling to indicate the value
541 // is invalid until the user has focused the widget then left it with
542 // invalid data. This change tells dojo to pretend this focusing has
543 // already happened so we can style required widgets during page render.
544 this.widget._hasBeenBlurred = true;
545 this.widget.validate();
549 _buildOrgSelector : function() {
550 dojo.require('fieldmapper.OrgUtils');
551 dojo.require('openils.widget.FilteringTreeSelect');
552 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
553 this.widget.searchAttr = 'shortname';
554 this.widget.labelAttr = 'shortname';
555 this.widget.parentField = 'parent_ou';
556 var user = new openils.User();
558 if(this.widgetValue == null && this.orgDefaultsToWs)
559 this.widgetValue = user.user.ws_ou();
561 // if we have a limit perm, find the relevent orgs (async)
562 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
565 user.getPermOrgList(this.orgLimitPerms,
567 self.widget.tree = orgList;
568 self.widget.startup();
569 self._widgetLoaded();
574 this.widget.tree = fieldmapper.aou.globalOrgTree;
575 this.widget.startup();
581 _buildPermGrpSelector : function() {
582 dojo.require('openils.widget.FilteringTreeSelect');
583 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
584 this.widget.disableQuery = this.disableQuery;
585 this.widget.searchAttr = 'name';
587 if(this.cache.permGrpTree) {
588 this.widget.tree = this.cache.permGrpTree;
589 this.widget.startup();
590 this._widgetLoaded();
596 new openils.PermaCrud().retrieveAll('pgt', {
597 async : !this.forceSync,
598 oncomplete : function(r) {
599 var list = openils.Util.readResponse(r, false, true);
604 map[list[l].id()] = list[l];
607 var pnode = map[node.parent()];
608 if(!pnode) {root = node; continue;}
609 if(!pnode.children()) pnode.children([]);
610 pnode.children().push(node);
612 self.widget.tree = self.cache.permGrpTree = root;
613 self.widget.startup();
614 self._widgetLoaded();
621 _buildCopyLocSelector : function() {
622 dojo.require('dijit.form.FilteringSelect');
623 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
624 this.widget.searchAttr = this.widget.labalAttr = 'name';
625 this.widget.valueAttr = 'id';
627 if(this.cache.copyLocStore) {
628 this.widget.store = this.cache.copyLocStore;
629 this.widget.startup();
635 var ws_ou = openils.User.user.ws_ou();
636 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
637 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
640 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
641 async : !this.forceSync,
642 oncomplete : function(r) {
643 var list = openils.Util.readResponse(r, false, true);
646 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
647 self.cache.copyLocStore = self.widget.store;
648 self.widget.startup();
649 self._widgetLoaded();
657 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
658 openils.widget.AutoFieldWidget.cache = {};
659 openils.widget.AutoFieldWidget.defaultLinkedDataLoader = {};
661 /* Custom provider-as-link-class fetcher. Fitler is ignored.
662 * All viewable providers are retrieved.
664 openils.widget.AutoFieldWidget.defaultLinkedDataLoader.acqpro =
665 function(linkClass, fitler, oncomplete) {
667 fieldmapper.standardRequest(
668 ['open-ils.acq', 'open-ils.acq.provider.org.retrieve'],
671 params : [openils.User.authtoken],
672 oncomplete : function(r) {
675 while(resp = r.recv()) {
676 var pro = resp.content();
677 if(pro) list.push(pro);