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) {
389 // set the label for each value in the store based on the provide label format
390 var format = self.labelFormat[0];
392 dojo.forEach(storeData.data.items,
395 for(var i = 1; i< self.labelFormat.length; i++)
396 values.push(item[self.labelFormat[i]]);
397 item._label = dojo.string.substitute(format, values);
402 self.widget.store = new dojo.data.ItemFileReadStore(storeData);
403 self.cache[self.auth].list[linkClass] = self.widget.store;
405 self.widget.store = self.cache[self.auth].list[linkClass];
408 self.widget.startup();
409 self._widgetLoaded();
412 if(this.cache[self.auth].list[linkClass]) {
416 var _cb = function(r) {
417 oncomplete(openils.Util.readResponse(r, false, true));
419 if (this.searchFilter) {
420 new openils.PermaCrud().search(linkClass, this.searchFilter, {
421 async : !this.forceSync, oncomplete : _cb
424 new openils.PermaCrud().retrieveAll(linkClass, {
425 async : !this.forceSync, oncomplete : _cb
434 * For widgets that run asynchronously, provide a callback for finishing up
436 _widgetLoaded : function(value) {
440 /* -------------------------------------------------------------
441 when using widgets in a grid, the cell may dissapear, which
442 kills the underlying DOM node, which causes this to fail.
443 For now, back out gracefully and let grid getters use
444 getDisplayString() instead
445 -------------------------------------------------------------*/
447 this.baseWidgetValue(this.getDisplayString());
452 this.baseWidgetValue(this.widgetValue);
453 if(this.idlField.name == this.fmIDL.pkey && this.fmIDL.pkey_sequence && !this.selfReference)
454 this.widget.attr('disabled', true);
455 if(this.disableWidgetTest && this.disableWidgetTest(this.idlField.name, this.fmObject))
456 this.widget.attr('disabled', true);
459 this.onload(this.widget, this);
462 _buildOrgSelector : function() {
463 dojo.require('fieldmapper.OrgUtils');
464 dojo.require('openils.widget.FilteringTreeSelect');
465 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
466 this.widget.searchAttr = 'shortname';
467 this.widget.labelAttr = 'shortname';
468 this.widget.parentField = 'parent_ou';
469 var user = new openils.User();
471 if(this.widgetValue == null && this.orgDefaultsToWs)
472 this.widgetValue = user.user.ws_ou();
474 // if we have a limit perm, find the relevent orgs (async)
475 if(this.orgLimitPerms && this.orgLimitPerms.length > 0) {
478 user.getPermOrgList(this.orgLimitPerms,
480 self.widget.tree = orgList;
481 self.widget.startup();
482 self._widgetLoaded();
487 this.widget.tree = fieldmapper.aou.globalOrgTree;
488 this.widget.startup();
494 _buildPermGrpSelector : function() {
495 dojo.require('openils.widget.FilteringTreeSelect');
496 this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
497 this.widget.searchAttr = 'name';
499 if(this.cache.permGrpTree) {
500 this.widget.tree = this.cache.permGrpTree;
501 this.widget.startup();
507 new openils.PermaCrud().retrieveAll('pgt', {
508 async : !this.forceSync,
509 oncomplete : function(r) {
510 var list = openils.Util.readResponse(r, false, true);
515 map[list[l].id()] = list[l];
518 var pnode = map[node.parent()];
519 if(!pnode) {root = node; continue;}
520 if(!pnode.children()) pnode.children([]);
521 pnode.children().push(node);
523 self.widget.tree = self.cache.permGrpTree = root;
524 self.widget.startup();
525 self._widgetLoaded();
532 _buildCopyLocSelector : function() {
533 dojo.require('dijit.form.FilteringSelect');
534 this.widget = new dijit.form.FilteringSelect(this.dijitArgs, this.parentNode);
535 this.widget.searchAttr = this.widget.labalAttr = 'name';
536 this.widget.valueAttr = 'id';
538 if(this.cache.copyLocStore) {
539 this.widget.store = this.cache.copyLocStore;
540 this.widget.startup();
546 var ws_ou = openils.User.user.ws_ou();
547 var orgs = fieldmapper.aou.findOrgUnit(ws_ou).orgNodeTrail().map(function (i) { return i.id() });
548 orgs = orgs.concat(fieldmapper.aou.descendantNodeList(ws_ou).map(function (i) { return i.id() }));
551 new openils.PermaCrud().search('acpl', {owning_lib : orgs}, {
552 async : !this.forceSync,
553 oncomplete : function(r) {
554 var list = openils.Util.readResponse(r, false, true);
557 new dojo.data.ItemFileReadStore({data:fieldmapper.acpl.toStoreData(list)});
558 self.cache.copyLocStore = self.widget.store;
559 self.widget.startup();
560 self._widgetLoaded();
568 openils.widget.AutoFieldWidget.localeStrings = dojo.i18n.getLocalization("openils.widget", "AutoFieldWidget");
569 openils.widget.AutoFieldWidget.cache = {};