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,
398 // self.labelFormat[1..*] are names of fields. Pull the field
399 // values from each object to determine the values for string substitution
400 for(var i = 1; i< self.labelFormat.length; i++)
401 values.push(item[self.labelFormat[i]]);
403 item._label = dojo.string.substitute(format, values);
408 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
409 self.cache[self.auth].list[linkClass] = self.widget.store;
412 self.widget.store = self.cache[self.auth].list[linkClass];
415 self.widget.startup();
416 self._widgetLoaded();
419 if(this.cache[self.auth].list[linkClass]) {
423 var _cb = function(r) {
424 oncomplete(openils.Util.readResponse(r, false, true));
426 if (this.searchFilter) {
427 new openils.PermaCrud().search(linkClass, this.searchFilter, {
428 async : !this.forceSync, oncomplete : _cb
431 new openils.PermaCrud().retrieveAll(linkClass, {
432 async : !this.forceSync, oncomplete : _cb
441 * For widgets that run asynchronously, provide a callback for finishing up
443 _widgetLoaded : function(value) {
447 /* -------------------------------------------------------------
448 when using widgets in a grid, the cell may dissapear, which
449 kills the underlying DOM node, which causes this to fail.
450 For now, back out gracefully and let grid getters use
451 getDisplayString() instead
452 -------------------------------------------------------------*/
454 this.baseWidgetValue(this.getDisplayString());
459 this.baseWidgetValue(this.widgetValue);
460 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
461 this.widget.attr('disabled', true);
462 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
463 this.widget.attr('disabled', true);
466 this.onload(this.widget, this);
469 _buildOrgSelector : function() {
470 dojo.require('fieldmapper.OrgUtils');
471 dojo.require('openils.widget.FilteringTreeSelect');
472 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
473 this.widget.searchAttr = 'shortname';
474 this.widget.labelAttr = 'shortname';
475 this.widget.parentField = 'parent_ou';
476 var user = new openils.User();
478 if(this.widgetValue == null && this.orgDefaultsToWs)
479 this.widgetValue = user.user.ws_ou();
481 // if we have a limit perm, find the relevent orgs (async)
482 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
485 user.getPermOrgList(this.orgLimitPerms,
487 self.widget.tree = orgList;
488 self.widget.startup();
489 self._widgetLoaded();
494 this.widget.tree = fieldmapper.aou.globalOrgTree;
495 this.widget.startup();
501 _buildPermGrpSelector : function() {
502 dojo.require('openils.widget.FilteringTreeSelect');
503 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
504 this.widget.searchAttr = 'name';
506 if(this.cache.permGrpTree) {
507 this.widget.tree = this.cache.permGrpTree;
508 this.widget.startup();
514 new openils.PermaCrud().retrieveAll('pgt', {
515 async : !this.forceSync,
516 oncomplete : function(r) {
517 var list = openils.Util.readResponse(r, false, true);
522 map[list[l].id()] = list[l];
525 var pnode = map[node.parent()];
526 if(!pnode) {root = node; continue;}
527 if(!pnode.children()) pnode.children([]);
528 pnode.children().push(node);
530 self.widget.tree = self.cache.permGrpTree = root;
531 self.widget.startup();
532 self._widgetLoaded();
539 _buildCopyLocSelector : function() {
540 dojo.require('dijit.form.FilteringSelect');
541 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
542 this.widget.searchAttr = this.widget.labalAttr = 'name';
543 this.widget.valueAttr = 'id';
545 if(this.cache.copyLocStore) {
546 this.widget.store = this.cache.copyLocStore;
547 this.widget.startup();
553 var ws_ou = openils.User.user.ws_ou();
554 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
555 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
558 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
559 async : !this.forceSync,
560 oncomplete : function(r) {
561 var list = openils.Util.readResponse(r, false, true);
564 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
565 self.cache.copyLocStore = self.widget.store;
566 self.widget.startup();
567 self._widgetLoaded();
575 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
576 openils.widget.AutoFieldWidget.cache = {};