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' ]
40 constructor : function(args) {
44 // find the field description in the IDL if not provided
46 this.fmClass = this.fmObject.classname;
47 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
48 this.suppressLinkedFields = args.suppressLinkedFields || [];
50 if(this.selfReference) {
51 this.fmField = fieldmapper.IDL.fmclasses[this.fmClass].pkey;
53 // create a mock-up of the idlField object.
56 'class' : this.fmClass,
65 this.fmIDL = fieldmapper.IDL.fmclasses[this.fmClass];
66 var fields = this.fmIDL.fields;
68 if(fields[f].name == this.fmField)
69 this.idlField = fields[f];
74 throw new Error("AutoFieldWidget could not determine which " +
75 "field to render. We need more information. fmClass=" +
76 this.fmClass + ' fmField=' + this.fmField + ' fmObject=' + js2JSON(this.fmObject));
78 this.auth = openils.User.authtoken;
79 this.cache = openils.widget.AutoFieldWidget.cache;
80 this.cache[this.auth] = this.cache[this.auth] || {};
81 this.cache[this.auth].single = this.cache[this.auth].single || {};
82 this.cache[this.auth].list = this.cache[this.auth].list || {};
86 * Turn the widget-stored value into a value oils understands
88 getFormattedValue : function() {
89 var value = this.baseWidgetValue();
90 switch(this.idlField.datatype) {
93 case 'true': return 't';
94 case 'on': return 't';
95 case 'false' : return 'f';
96 case 'unset' : return null;
97 case true : return 't';
101 if(!value) return null;
102 return dojo.date.stamp.toISOString(value);
106 if(isNaN(value)) value = null;
108 return (value === '') ? null : value;
112 baseWidgetValue : function(value) {
113 var attr = (this.readOnly) ? 'content' : 'value';
114 if(arguments.length) this.widget.attr(attr, value);
115 return this.widget.attr(attr);
119 * Turn the widget-stored value into something visually suitable
121 getDisplayString : function() {
122 var value = this.widgetValue;
123 switch(this.idlField.datatype) {
128 return openils.widget.AutoFieldWidget.localeStrings.TRUE;
131 return openils.widget.AutoFieldWidget.localeStrings.FALSE;
132 case 'unset' : return openils.widget.AutoFieldWidget.localeStrings.UNSET;
133 case true : return openils.widget.AutoFieldWidget.localeStrings.TRUE;
134 default: return openils.widget.AutoFieldWidget.localeStrings.FALSE;
137 if (!value) return '';
138 dojo.require('dojo.date.locale');
139 dojo.require('dojo.date.stamp');
140 var date = dojo.date.stamp.fromISOString(value);
141 return dojo.date.locale.format(date, {formatLength:'short'});
143 if(value === null || value === undefined) return '';
144 return fieldmapper.aou.findOrgUnit(value).shortname();
147 if(isNaN(value)) value = 0;
149 if(value === undefined || value === null)
155 build : function(onload) {
157 if(this.widgetValue == null)
158 this.widgetValue = (this.fmObject) ? this.fmObject[this.idlField.name]() : null;
161 // core widget provided for us, attach and move on
162 if(this.parentNode) // may already be in the "right" place
163 this.parentNode.appendChild(this.widget.domNode);
164 if(this.widget.attr('value') == null)
165 this._widgetLoaded();
169 if(!this.parentNode) // give it somewhere to live so that dojo won't complain
170 this.parentNode = dojo.create('div');
172 this.onload = onload;
175 dojo.require('dijit.layout.ContentPane');
176 this.widget = new dijit.layout.ContentPane(this.dijitArgs, this.parentNode);
177 if(this.widgetValue !== null)
178 this._tryLinkedDisplayField();
180 } else if(this.widgetClass) {
181 dojo.require(this.widgetClass);
182 eval('this.widget = new ' + this.widgetClass + '(this.dijitArgs, this.parentNode);');
186 switch(this.idlField.datatype) {
189 dojo.require('dijit.form.TextBox');
190 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
194 this._buildOrgSelector();
198 dojo.require('dijit.form.CurrencyTextBox');
199 this.widget = new dijit.form.CurrencyTextBox(this.dijitArgs, this.parentNode);
203 dojo.require('dijit.form.NumberTextBox');
204 this.dijitArgs = dojo.mixin(this.dijitArgs || {}, {constraints:{places:0}});
205 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
209 dojo.require('dijit.form.NumberTextBox');
210 this.widget = new dijit.form.NumberTextBox(this.dijitArgs, this.parentNode);
214 dojo.require('dijit.form.DateTextBox');
215 dojo.require('dojo.date.stamp');
216 this.widget = new dijit.form.DateTextBox(this.dijitArgs, this.parentNode);
217 if(this.widgetValue != null)
218 this.widgetValue = dojo.date.stamp.fromISOString(this.widgetValue);
223 dojo.require('dijit.form.FilteringSelect');
224 var store = new dojo.data.ItemFileReadStore({
226 identifier : 'value',
228 {label : openils.widget.AutoFieldWidget.localeStrings.UNSET, value : 'unset'},
229 {label : openils.widget.AutoFieldWidget.localeStrings.TRUE, value : 'true'},
230 {label : openils.widget.AutoFieldWidget.localeStrings.FALSE, value : 'false'}
234 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
235 this.widget.searchAttr = this.widget.labelAttr = 'label';
236 this.widget.valueAttr = 'value';
237 this.widget.store = store;
238 this.widget.startup();
239 this.widgetValue = (this.widgetValue === null) ? 'unset' :
240 (openils.Util.isTrue(this.widgetValue)) ? 'true' : 'false';
242 dojo.require('dijit.form.CheckBox');
243 this.widget = new dijit.form.CheckBox(this.dijitArgs, this.parentNode);
244 this.widgetValue = openils.Util.isTrue(this.widgetValue);
249 if(this._buildLinkSelector()) break;
252 if(this.dijitArgs && (this.dijitArgs.required || this.dijitArgs.regExp)) {
253 dojo.require('dijit.form.ValidationTextBox');
254 this.widget = new dijit.form.ValidationTextBox(this.dijitArgs, this.parentNode);
256 dojo.require('dijit.form.TextBox');
257 this.widget = new dijit.form.TextBox(this.dijitArgs, this.parentNode);
262 if(!this.async) this._widgetLoaded();
266 // we want to display the value for our widget. However, instead of displaying
267 // an ID, for exmaple, display the value for the 'selector' field on the object
269 _tryLinkedDisplayField : function(noAsync) {
271 if(this.idlField.datatype == 'org_unit')
272 return false; // we already handle org_units, no need to re-fetch
274 // user opted to bypass fetching this linked data
275 if(this.suppressLinkedFields.indexOf(this.idlField.name) > -1)
278 var linkInfo = this._getLinkSelector();
279 if(!(linkInfo && linkInfo.vfield && linkInfo.vfield.selector))
281 var lclass = linkInfo.linkClass;
283 if(lclass == 'aou') {
284 this.widgetValue = fieldmapper.aou.findOrgUnit(this.widgetValue).shortname();
288 // first try the store cache
290 if(this.cache[this.auth].list[lclass]) {
291 var store = this.cache[this.auth].list[lclass];
293 query[linkInfo.vfield.name] = ''+this.widgetValue;
294 store.fetch({query:query, onComplete:
296 self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
302 // then try the single object cache
303 if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
304 this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
308 console.log("Fetching sync object " + lclass + " : " + this.widgetValue);
310 // if those fail, fetch the linked object
313 new openils.PermaCrud().retrieve(lclass, this.widgetValue, {
314 async : !this.forceSync,
315 oncomplete : function(r) {
316 var item = openils.Util.readResponse(r);
317 var newvalue = item[linkInfo.vfield.selector]();
319 if(!self.cache[self.auth].single[lclass])
320 self.cache[self.auth].single[lclass] = {};
321 self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
323 self.widgetValue = newvalue;
324 self.widget.startup();
325 self._widgetLoaded();
330 _getLinkSelector : function() {
331 var linkClass = this.idlField['class'];
332 if(this.idlField.reltype != 'has_a') return false;
333 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud) return false;
334 if(!fieldmapper.IDL.fmclasses[linkClass].permacrud.retrieve) return false;
337 var rclassIdl = fieldmapper.IDL.fmclasses[linkClass];
339 for(var f in rclassIdl.fields) {
340 if(this.idlField.key == rclassIdl.fields[f].name) {
341 vfield = rclassIdl.fields[f];
347 throw new Error("'" + linkClass + "' has no '" + this.idlField.key + "' field!");
350 linkClass : linkClass,
355 _buildLinkSelector : function() {
357 var selectorInfo = this._getLinkSelector();
358 if(!selectorInfo) return false;
360 var linkClass = selectorInfo.linkClass;
361 var vfield = selectorInfo.vfield;
365 if(linkClass == 'pgt')
366 return this._buildPermGrpSelector();
367 if(linkClass == 'aou')
368 return this._buildOrgSelector();
369 if(linkClass == 'acpl')
370 return this._buildCopyLocSelector();
373 dojo.require('dojo.data.ItemFileReadStore');
374 dojo.require('dijit.form.FilteringSelect');
376 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
377 this.widget.searchAttr = this.widget.labelAttr = vfield.selector || vfield.name;
378 this.widget.valueAttr = vfield.name;
380 var oncomplete = function(list) {
383 self.widget.labelAttr = self.widget.searchAttr = '_label';
386 var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
388 if(self.labelFormat) {
390 // set the label for each value in the store based on the provided label format.
392 var format = self.labelFormat[0];
393 dojo.forEach(storeData.data.items,
400 // self.labelFormat[1..*] are names of fields. Pull the field
401 // values from each object to determine the values for string substitution
402 for(var i = 1; i< self.labelFormat.length; i++)
403 values.push(item[self.labelFormat[i]]);
405 item._label = dojo.string.substitute(format, values);
408 throw new Error("openils.widget.AutoFieldWidget: Invalid labelFormat [" +
409 self.labelFormat + "] : " + E);
415 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
416 self.cache[self.auth].list[linkClass] = self.widget.store;
419 self.widget.store = self.cache[self.auth].list[linkClass];
422 self.widget.startup();
423 self._widgetLoaded();
426 if(this.cache[self.auth].list[linkClass]) {
430 var _cb = function(r) {
431 oncomplete(openils.Util.readResponse(r, false, true));
433 if (this.searchFilter) {
434 new openils.PermaCrud().search(linkClass, this.searchFilter, {
435 async : !this.forceSync, oncomplete : _cb
438 new openils.PermaCrud().retrieveAll(linkClass, {
439 async : !this.forceSync, oncomplete : _cb
448 * For widgets that run asynchronously, provide a callback for finishing up
450 _widgetLoaded : function(value) {
454 /* -------------------------------------------------------------
455 when using widgets in a grid, the cell may dissapear, which
456 kills the underlying DOM node, which causes this to fail.
457 For now, back out gracefully and let grid getters use
458 getDisplayString() instead
459 -------------------------------------------------------------*/
461 this.baseWidgetValue(this.getDisplayString());
466 this.baseWidgetValue(this.widgetValue);
467 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
468 this.widget.attr('disabled', true);
469 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
470 this.widget.attr('disabled', true);
473 this.onload(this.widget, this);
476 _buildOrgSelector : function() {
477 dojo.require('fieldmapper.OrgUtils');
478 dojo.require('openils.widget.FilteringTreeSelect');
479 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
480 this.widget.searchAttr = 'shortname';
481 this.widget.labelAttr = 'shortname';
482 this.widget.parentField = 'parent_ou';
483 var user = new openils.User();
485 if(this.widgetValue == null && this.orgDefaultsToWs)
486 this.widgetValue = user.user.ws_ou();
488 // if we have a limit perm, find the relevent orgs (async)
489 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
492 user.getPermOrgList(this.orgLimitPerms,
494 self.widget.tree = orgList;
495 self.widget.startup();
496 self._widgetLoaded();
501 this.widget.tree = fieldmapper.aou.globalOrgTree;
502 this.widget.startup();
508 _buildPermGrpSelector : function() {
509 dojo.require('openils.widget.FilteringTreeSelect');
510 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
511 this.widget.searchAttr = 'name';
513 if(this.cache.permGrpTree) {
514 this.widget.tree = this.cache.permGrpTree;
515 this.widget.startup();
521 new openils.PermaCrud().retrieveAll('pgt', {
522 async : !this.forceSync,
523 oncomplete : function(r) {
524 var list = openils.Util.readResponse(r, false, true);
529 map[list[l].id()] = list[l];
532 var pnode = map[node.parent()];
533 if(!pnode) {root = node; continue;}
534 if(!pnode.children()) pnode.children([]);
535 pnode.children().push(node);
537 self.widget.tree = self.cache.permGrpTree = root;
538 self.widget.startup();
539 self._widgetLoaded();
546 _buildCopyLocSelector : function() {
547 dojo.require('dijit.form.FilteringSelect');
548 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
549 this.widget.searchAttr = this.widget.labalAttr = 'name';
550 this.widget.valueAttr = 'id';
552 if(this.cache.copyLocStore) {
553 this.widget.store = this.cache.copyLocStore;
554 this.widget.startup();
560 var ws_ou = openils.User.user.ws_ou();
561 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
562 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
565 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
566 async : !this.forceSync,
567 oncomplete : function(r) {
568 var list = openils.Util.readResponse(r, false, true);
571 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
572 self.cache.copyLocStore = self.widget.store;
573 self.widget.startup();
574 self._widgetLoaded();
582 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
583 openils.widget.AutoFieldWidget.cache = {};