1 if (!dojo._hasResource["openils.widget.PhysCharWizard"]) {
2 dojo._hasResource["openils.widget.PhysCharWizard"] = true;
4 dojo.provide("openils.widget.PhysCharWizard");
5 dojo.require("dojo.string");
6 dojo.require("openils.User");
7 dojo.require("openils.Util");
8 dojo.require("openils.PermaCrud");
9 dojo.requireLocalization("openils.widget", "PhysCharWizard");
11 (function() { /* Namespace protection so we can still make little helpers
12 within our own bubble */
14 var _xhtml_ns = "http://www.w3.org/1999/xhtml";
16 function _show_button(n, yes) { /* yet another hide/reveal thing */
17 /* This is a re-invented wheel, but I was having trouble
18 * getting my <button>s to react to the disabled property, and
19 * decided to hide/reveal them instead of disable/enable then.
20 * Then I had this need to do it in a consistent way. */
21 n.setAttribute("style", "visibility: " + (yes? "visible":"hidden"));
24 function _get_xul_combobox_value(menulist) {
25 /* XUL comboboxes (<menulist editable="true">) are funny. */
27 return menulist.selectedItem ?
28 menulist.selectedItem.value :
29 menulist.label; /* sic! Even .getAttribute('label') is
30 wrong, and anything to do with 'value'
34 /* Within the openils.widget.PhysCharWizard class, methods and
35 * properties that start "_" should be considered private, and others
38 * The methods whose names end in "_XUL" could be replaced with HTML
39 * versions to make this wizard work as an HTML thing instead of as
43 "openils.widget.PhysCharWizard", [], {
45 "constructor": function(args) {
46 this._ = openils.widget.PhysCharWizard.localeStrings;
47 this._cache = openils.widget.PhysCharWizard.cache;
49 /* Reserve a little of the window namespace that we'll need
50 * (under XUL anyway) */
51 window._owPCWinstances = window._owPCWinstances || 0;
52 window._owPCW = window._owPCW || {};
53 this.instance_num = window._owPCWinstances++;
54 window._owPCW[this.instance_num] = this;
56 "window._owPCW[" + this.instance_num + "]";
58 /* Initialize and save misc values, and call build() to
59 * make and place widgets. */
60 this.onapply = args.onapply;
63 this.more_back = false;
64 this.more_forward = true;
65 this.value = this.original_value = args.node.value;
67 this.pcrud = new openils.PermaCrud(
68 {"authtoken": ses ? ses() : openils.User.authtoken}
71 this.build(args.node);
73 this._load_all_types( /* and then: */
74 dojo.hitch(this, function() { this.move(0); })
77 "build": function(where) {
78 this.original_node = where;
79 var p = this.container_node = where.parentNode;
84 "update_question": function(label, values) {
85 this._update_question_XUL(label, values);
87 "update_value_label": function() {
88 this._update_value_label_XUL(
89 this._get_step_slot(), this.value
92 "update_pagers": function() {
93 _show_button(this.back_button, this.more_back);
94 _show_button(this.forward_button, this.more_forward);
96 "apply": function(callback) {
100 0, dojo.hitch(this, function() {
101 this.onapply(this.value);
102 if (typeof callback == "function")
107 "cancel": function() {
109 this.container_node.removeChild(this.wizard_root_node);
110 this.container_node.appendChild(this.original_node);
112 "_default_after_00": function() {
113 /* This method assumes that the things it looks for in
114 * the cache are freshly put there. */
115 var working_ptype = this.value.substr(0, 1);
116 var sf_list = this._cache.subfields[working_ptype];
118 throw new Error(this._.BAD_WORKING_PTYPE);
120 this.value = working_ptype;
121 for (var i = 0; i < sf_list.length; i++) {
123 var gap = s.start_pos() - this.value.length;
125 for (var j = 0; j < gap; j++)
126 this.value += " "; /* XXX or '#' ? */
127 } else if (gap < 0) {
129 dojo.string.substitute(
130 this._.BACKWARDS_SUBFIELD_PROGRESSION,
136 for (var j = 0; j < s.length(); j++)
140 "move": function(offset, callback) {
141 /* When we move the wizard, we need to accomplish five
143 * 1) Disable both pager buttons - sic
144 * 2) Update the appopriate _slot of the working _value_
145 * with the value from the user input control.
146 * ---- sync above here ^ --------- async below here v ----
147 * 3) Determine what the next _step_ will be and set it
148 * 4) Replace the question and the dropdown with appro-
149 * priate data from the new _step_
150 * 5) Reenable appropriate pager buttons
151 * 6) (optional) fire any callback
155 _show_button(this.back_button, false);
156 _show_button(this.forward_button, false);
158 /* Step 2. No sweat so far. Skip if there is no
159 * user control yet (initializing whole wizard still). */
160 var a_changed = false;
161 if (this.step_user_control) {
162 a_changed = this.update_value_slot(
163 this._get_step_slot(),
164 this.get_step_value_from_control()
165 ) && this.step == 'a';
168 /* Step 3 depends on knowing a) our working_ptype, which
169 * may have just changed if step was 'a' and b) all the
170 * subfields for that ptype, which we may have to
171 * retrieve asynchronously. */
172 this._get_subfields_for_type(
173 this.value.substr(0, 1), /* working_ptype */
174 /* and then: */ dojo.hitch(this, function() {
176 /* Step 2.9 (small) */
177 if (a_changed) this._default_after_00();
180 this._move_step(offset);
182 /* Step 4: For the call to update_question, we had
183 * better have values loaded for our current step.
185 this._get_values_for_step(
187 /* and then: */ dojo.hitch(this, function(l, v){
189 this.update_value_label();
190 this.update_question(l, v);
193 this.update_pagers();
195 if (typeof callback == "function") {
203 "get_step_value_from_control": function() {
204 return _get_xul_combobox_value(this.step_user_control);
206 "get_step_value": function() {
207 return String.prototype.substr.apply(
208 this.value, this._get_step_slot()
211 "update_value_slot": function(slot, value) {
212 /* Return true if this.value changes */
215 /* Prevent erasing positions when backing up. */
216 for (var i = 0; i < slot[1]; i++)
220 var old_value = this.value;
221 var before = this.value.substr(0, slot[0]);
222 var after = this.value.substr(slot[0] + slot[1]);
224 this.value = before + value.substr(0, slot[1]) + after;
225 return (this.value != old_value);
227 "_load_all_types": function(callback) {
228 /* It's easiest to have these always ready, and it's not
229 * a large dataset. */
231 if (this._cache.types.length) /* maybe we already do */
234 this.pcrud.retrieveAll(
236 "oncomplete": dojo.hitch(this, function(r) {
237 if (r = openils.Util.readResponse(r)) {
238 this._cache.types = r.map(
240 return [o.ptype_key(), o.label()];
245 throw new Error(this._.DATA_ERROR_007);
251 "_get_subfields_for_type": function(working_ptype, callback) {
252 if (this._cache.subfields[working_ptype]) {
253 callback(this._cache.subfields[working_ptype]);
256 "cmpcsm", {"ptype_key": working_ptype}, {
257 "order_by": {"cmpcsm": "subfield"},
258 "oncomplete": dojo.hitch(this, function(r) {
259 if (r = openils.Util.readResponse(r)) {
260 this._cache.subfields[working_ptype]= r;
263 throw new Error(this._.DATA_ERROR_007);
270 "_get_values_for_step": function(step, callback) {
271 /* Values are cached by subfield ID, so we find the
272 * current subfield ID using the step and the
275 if (this.step == 'a') {
276 callback(this._.A_LABEL, this._cache.types);
280 var step = this.step; /* for use w/in closure */
281 var working_ptype = this.value.substr(0, 1);
283 this._cache.subfields[working_ptype].filter(
284 function(s) { return s.subfield() == step; }
287 if (subfields.length != 1) {
288 throw new Error(this._.BAD_SUBFIELD_DATA);
292 var subfield = subfields[0];
293 if (this._cache.values[subfield.id()]) {
296 this._cache.values[subfield.id()]
300 "cmpcvm", {"ptype_subfield": subfield.id()}, {
301 "order_by": {"cmpcvm": "value"},
302 "onresponse": dojo.hitch(this, function(r) {
303 if (r = openils.Util.readResponse(r)) {
304 this._cache.values[subfield.id()] =
307 return [v.value(),v.label()]
310 callback(subfield.label(), r);
312 throw new Error(this._.DATA_ERROR_007);
319 "_get_step_slot": function() {
320 /* We should never need to know the slot for our step
321 * until *after* we have the subfields for that step
322 * loaded. That allows us to keep this function sync
323 * (i.e., it returns instead of using a callback). */
325 if (this.step == 'a') {
328 var step = this.step; /* to use w/in closure */
329 var working_ptype = this.value.substr(0, 1);
331 this._cache.subfields[working_ptype].filter(
332 function(s) { return s.subfield() == step; }
335 if (matches.length == 1)
336 return [matches[0].start_pos(),matches[0].length()];
338 throw new Error(this._.BAD_SUBFIELD_DATA);
341 "_move_step": function(offset) {
342 /* This method is/should only be called when we know we
343 * have the list of subfields for our working_ptype cached.
345 * We have two jobs in this method:
346 * 1) Set this.step to something new.
347 * 2) Update this.more_forward and this.more_back (bools)
349 var working_ptype = this.value.substr(0, 1);
351 var sf_list = this._cache.subfields[working_ptype];
353 for (var i = 0; i < sf_list.length; i++) {
354 if (sf_list[i].subfield() == this.step) {
360 var idx = found + offset;
362 this.step = sf_list[idx].subfield();
363 this.more_forward = Boolean(sf_list[idx + 1]);
364 this.more_back = Boolean(idx >= 0);
365 } else if (idx == -1) { /* 'a' */
367 this.more_back = false;
368 this.more_forward = true; /* or something's broke */
370 throw new Error(this._.FELL_OFF_STEPS);
373 "_update_question_XUL": function(step_label, value_list) {
374 var qh = this.question_holder;
376 while (qh.firstChild) qh.removeChild(qh.firstChild);
378 /* Add question label */
379 var label = document.createElement("label");
380 label.setAttribute("value", step_label + "?");
381 label.setAttribute("style", "min-width: 16em;");
382 qh.appendChild(label);
384 /* Create combobox (in XUL this a <menulist editable="true">
385 * with <menupopup> underneath and several <menuitem>s under
387 var ml = this.step_user_control =
388 document.createElement("menulist");
389 ml.setAttribute("editable", "true");
390 var mp = document.createElement("menupopup");
393 var starting_value = this.get_step_value();
394 var found_starting_value = false;
398 var mi = document.createElement("menuitem");
399 mi.setAttribute("label", v[0] + ": " + v[1]);
400 mi.setAttribute("value", v[0]);
402 if (v[0] == starting_value) {
403 mi.setAttribute("selected", "true");
404 found_starting_value = true;
411 if (!found_starting_value) {
412 /* Starting value wasn't one of the menuitems, but
413 * we can force it: */
414 ml.setAttribute("label", starting_value);
418 "_update_value_label_XUL": function(step_win, value) {
419 var before = value.substr(0, step_win[0]);
420 var within = value.substr(step_win[0], step_win[1]);
421 var after = value.substr(step_win[0] + step_win[1]);
423 var div = this.value_label;
424 while (div.firstChild)
425 div.removeChild(div.firstChild);
427 div.appendChild(document.createTextNode(before));
429 var el = document.createElementNS(_xhtml_ns,"xhtml:strong");
430 el.appendChild(document.createTextNode(within));
433 div.appendChild(document.createTextNode(after));
435 "_gen_XUL_oncommand": function(methstr) {
436 return "try { " + this.outside_ref +
437 "." + methstr + " } catch (E) { alert('" +
438 this.outside_ref + ": ' + E) }";
440 "_build_XUL": function() {
441 var vbox = this.container_node.appendChild(
442 document.createElement("vbox")
446 vbox.appendChild(document.createElement("hbox"));
448 this.question_holder =
449 vbox.appendChild(document.createElement("hbox"));
450 this.question_holder.setAttribute("align", "center");
453 vbox.appendChild(document.createElement("hbox"));
455 this.value_label = top_hbox.appendChild(
456 document.createElementNS(_xhtml_ns, "xhtml:div")
459 /* These em's must be measured in terms of the body
460 * font-size, not the font-size local to these elements?
461 * Or is that how em's always work? */
462 this.value_label.setAttribute(
463 "style", "min-width: 16em; white-space: pre;"
466 /* From here to the end of the method we're just building
467 * and placing the wizard's four buttons. */
470 button = document.createElement("button");
471 button.setAttribute("label", this._.OK);
472 button.setAttribute("icon", "apply");
474 "oncommand", this._gen_XUL_oncommand("apply()")
476 top_hbox.appendChild(button);
478 button = document.createElement("button");
479 button.setAttribute("label", this._.CANCEL);
480 button.setAttribute("icon", "cancel");
482 "oncommand", this._gen_XUL_oncommand("cancel()")
484 top_hbox.appendChild(button);
486 this.back_button = button =
487 document.createElement("button");
488 button.setAttribute("label", this._.BACK);
489 button.setAttribute("icon", "go-back");
491 "oncommand", this._gen_XUL_oncommand("move(-1)")
493 button.disabled = true;
494 bottom_hbox.appendChild(button);
496 this.forward_button = button =
497 document.createElement("button");
498 button.setAttribute("label", this._.FORWARD);
499 button.setAttribute("icon", "go-forward");
501 "oncommand", this._gen_XUL_oncommand("move(1)")
503 button.disabled = true;
504 bottom_hbox.appendChild(button);
506 /* Save reference to root node of wizard for easy
507 * removal when finished. */
508 this.wizard_root_node = vbox;
514 /* Class-wide cache; all instance objects share this */
515 openils.widget.PhysCharWizard.cache = {
516 "subfields": {}, /* by type */
517 "values": {}, /* by subfield ID */
521 openils.widget.PhysCharWizard.localeStrings =
522 dojo.i18n.getLocalization("openils.widget", "PhysCharWizard");