Commit 63a9bd9d849d20e6ca0c73e9c82f29fd51bdcf8b

Authored by nbm
1 parent c127b81d

Javascript necessary for rich selection of a conditional fieldset


git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@3760 c91229c3-7414-0410-bfa2-8a42b809f60b
presentation/lookAndFeel/knowledgeTree/js/conditional_usage.js 0 → 100644
  1 +// simple event stacking.
  2 +// i don't like Mochikit's one.
  3 +
  4 +function attachToElementEvent(elem, event_name, func) {
  5 + // catch IE (grumble)
  6 + if (elem.attachEvent) {
  7 + elem.attachEvent('on'+event_name, func);
  8 + } else {
  9 + elem.addEventListener(event_name, func, false);
  10 + }
  11 +}
  12 +
  13 +function removeFromElementEvent(elem, event_name, func) {
  14 + // catch IE (grumble)
  15 + if (elem.detachEvent) {
  16 + elem.detachEvent('on'+event_name, func);
  17 + } else {
  18 + elem.removeEventListener(event_name, func, false);
  19 + }
  20 +}
  21 +
  22 +// quick and dirty helper - find the nearest parent item matching tagName.
  23 +// FIXME steal the klass or tagName logic from MochiK.
  24 +// FIXME add to a core js-lib, and add some unit-tests.
  25 +function breadcrumbFind(elem, tagName) {
  26 + var stopTag = 'BODY';
  27 + var currentTag = elem.tagName;
  28 + var currentElem = elem;
  29 + while ((currentTag != stopTag) && (currentTag != tagName)) {
  30 + currentElem = currentElem.parentNode;
  31 + currentTag = currentElem.tagName;
  32 + }
  33 + if (currentTag == tagName) {
  34 + return currentElem;
  35 + } else {
  36 + return null;
  37 + }
  38 +}
  39 +
  40 +/* Conditional Metadata Usage
  41 + *
  42 + * Allows the system to respond to conditional metadata events.
  43 + */
  44 +
  45 +/**
  46 +
  47 +== Basic process around Conditional Metadata JS/HTML interaction ==
  48 +
  49 +The system works based on 3 concepts:
  50 +
  51 + 1. on the appropriate "activation" command, the entire "field" is serialised and replaced with a hidden
  52 + input var, and a "user friendly" label.
  53 + 2. a undo stack needs to be kept, which provides the user with a way to "un-fix" items.
  54 + 3. When an item is activated, the system:
  55 + (i) polls the page for fixed input-vars: this _includes_ (for example) fieldset_id, as well as later items.
  56 + (ii) submits these to a targeturl (set as a "global var" - currently in _this_ file.) // FIXME: this needs to be programmatically settable.
  57 +
  58 +
  59 + // TODO make this operate on a particular subset of the page, and be _instantiable_. (use fieldset as the "controlling component".
  60 + // TODO lazy bind all activation handlers to ensure that the above problem is solveable.
  61 + // TODO ensure that this functions across the required browser sets.
  62 + // TODO verify that the entire set of "lookup" values works here: select and input seem to work.
  63 +
  64 +*/
  65 +
  66 +var conditional_usage_undostack = new Array();
  67 +var conditional_usage_keys = new Array();
  68 +
  69 +// grow and go.
  70 +function getStackForFieldset(fieldset) {
  71 + for (var i=0; i<conditional_usage_keys.length; i++) {
  72 + if (conditional_usage_keys[i] == fieldset) {
  73 + simpleLog('DEBUG','found undostack at keyindex '+i);
  74 + return conditional_usage_undostack[i];
  75 + }
  76 + }
  77 + // we would have returned by now. onward, and upward.
  78 + // i == conditional_usage_keys.length == conditional_usage_undostack.length
  79 + conditional_usage_undostack.push(Array());
  80 + conditional_usage_keys.push(fieldset);
  81 + simpleLog('DEBUG','created undostack at keyindex '+i+' for fieldset '+fieldset);
  82 + simpleLog('DEBUG','undoStack: '+conditional_usage_undostack+'\nundoKeyStack: '+conditional_usage_keys)
  83 + return conditional_usage_undostack[i]; // must be the "new" element, which is 1 past the old size.
  84 +}
  85 +
  86 +// Stack implementation
  87 +function pushStack(fieldset, subtree) {
  88 + // FIXME how do I bind this to a particular fieldset object.
  89 + // FIXME at worst, we need to use the HTMLFieldSet object as a "key" of sorts into a stack it's O(n) initially, unless we can do some other magic ...
  90 + simpleLog('DEBUG','pushStack received: '+fieldset);
  91 + var undostack = getStackForFieldset(fieldset);
  92 + undostack.push(subtree); // onto the end, so it can be popped.
  93 + simpleLog('ERROR','added item to undo stack..');
  94 +}
  95 +
  96 +function popStack(fieldset) {
  97 + var undostack = getStackForFieldset(fieldset);
  98 + if (undostack.length == 0) {
  99 + return ;
  100 + }
  101 + var last_item = undostack.pop();
  102 + last_item.parentNode.removeChild(last_item);
  103 +}
  104 +
  105 +/**
  106 + - creates a replacement widget,
  107 + - adds the _old_ widget to the correct stack.
  108 +*/
  109 +
  110 +function createFixedWidget(fieldset, widget, i_name, i_value, i_label) {
  111 + var newWidget = DIV({'class':'widget fixed'},
  112 + INPUT({'type':'hidden','name':i_name, 'value':i_value,'class':'fixed'}),
  113 + SPAN(null, 'The value for field '+i_name+' is '+i_label+' ('+i_value+')')
  114 + );
  115 + swapDOM(widget, newWidget);
  116 + pushStack(fieldset, newWidget);
  117 + simpleLog('ERROR','conditional_usage passed in fieldset '+fieldset+' and widget '+newWidget);
  118 +}
  119 +
  120 +/** handles the "update" event.
  121 +
  122 + needs to:
  123 + - "replace" the contents of the widget with a "fixed" input.
  124 + - trigger the "updateFieldset"
  125 +*/
  126 +
  127 +function handleSelectChange(fieldset, widget, select_object) {
  128 + simpleLog('ERROR','call to stub: handleSelectChange on select with name "'+select_object.name+'"');
  129 + var i_name = select_object.name;
  130 + var i_value = select_object.value;
  131 + var i_label = scrapeText(select_object.options[select_object.selectedIndex]);
  132 +
  133 + createFixedWidget(fieldset, widget, i_name, i_value, i_label);
  134 + updateFieldset(fieldset);
  135 +}
  136 +
  137 +function handleRadioChange(fieldset, widget, radio_object) {
  138 + simpleLog('ERROR','call to stub: handleRadioChange on radio with name "'+radio_object.name+'"');
  139 + var i_name = radio_object.name;
  140 + var i_value = radio_object.value;
  141 +
  142 +
  143 + var oLabel = breadcrumbFind(radio_object, 'LABEL');
  144 + if (oLabel == null) {
  145 + simpleLog('ERROR','radiobutton ('+radio_object.name+':'+radio_object.value+') has no associated label. failing.');
  146 + return false;
  147 + } else {
  148 + var i_label = scrapeText(oLabel);
  149 + }
  150 +
  151 + createFixedWidget(fieldset, widget, i_name, i_value, i_label);
  152 + updateFieldset(fieldset);
  153 +}
  154 +
  155 +/** extract all the appropriate input-vars from a given fieldset, so that it can
  156 + be passed into a backed. Returns an array ("formKeys" => array(), "formValues" => array())
  157 + that can be passed to be backend.
  158 +
  159 + // actually, this is ONLY and issue for the "fieldset_id" form-field:
  160 + // for the rest of them, the backend should handle this sanely (e.g. in what it sends _us_). Suspect the "best" option is to call this
  161 + // 'fieldset_id[]' since the backend can then extract which fieldsets have been called. other vars will get converted
  162 + // from <input type="radio" ... name="xxxx"> and <select ... name="xxxx"> to <input type="hidden" class="fixed">
  163 + //
  164 +*/
  165 +function parseFieldsetToForm(fieldset) {
  166 + simpleLog('ERROR','call to untested fn: parseFieldsetToForm. ');
  167 + var formContent = new Array();
  168 +
  169 + var input_vars = getElementsByTagAndClassName('input','fixed',fieldset);
  170 + formContent["formKeys"] = new Array();
  171 + formContent["formValues"] = new Array();
  172 +
  173 + for (var i=0; i<input_vars.length; i++) {
  174 + var input_object = input_vars[i];
  175 + // don't delete the undo button.
  176 + if (input_object.type != 'button') {
  177 + formContent["formKeys"].push(input_object.name);
  178 + formContent["formValues"].push(input_object.value);
  179 + }
  180 + }
  181 +
  182 + return formContent;
  183 +}
  184 +
  185 +/** bind a "widget" to a particular fieldset, and populate the appropriate event-handlers
  186 + - find the various types of input objects and hook in appropriately:
  187 + make sure that the function binds:
  188 + - fieldset
  189 + - pseudo-widget (the div that surrounds each group of options.)
  190 + -handler.
  191 + // FIXME: this assumes that inputs are either "select" or "<input type='radio'>"
  192 + // FIXME: is that a valid assumption?
  193 +*/
  194 +function bindToConditionalFieldset(fieldset, widget) {
  195 +
  196 + // handleChange needs to be bound to each input widget.
  197 + // for <input type != "hidden"> type variables this means binding to onclick
  198 + // for <select> this means binding to onchange
  199 +
  200 + var select_fields = widget.getElementsByTagName('SELECT');
  201 + var input_fields = widget.getElementsByTagName('INPUT'); // needs to be filtered - no "hidden" vars.
  202 +
  203 + for (var i=0; i<select_fields.length; i++) {
  204 + var select_object = select_fields[i];
  205 + var handler = partial(handleSelectChange, fieldset, widget, select_object);
  206 + attachToElementEvent(select_object, 'change', handler);
  207 + }
  208 +
  209 + for (var i=0; i<input_fields.length; i++) {
  210 + var input_object = input_fields[i];
  211 + var handler = partial(handleRadioChange, fieldset, widget, input_object);
  212 + if (input_object.type == 'radio') {
  213 + attachToElementEvent(input_object, 'click', handler);
  214 + } else if (input_object.type == 'hidden') {
  215 + ; // this is OK, and expected.
  216 + } else {
  217 + simpleLog('ERROR','bindToConditionalFieldset found a non-hidden input field of type: '+input_object.type);
  218 + }
  219 + }
  220 + simpleLog('DEBUG','bindToConditionalFieldset complete');
  221 +}
  222 +
  223 +function clearUnfixedWidgets(fieldset) {
  224 + var widgets = getElementsByTagAndClassName('DIV', 'widget', fieldset);
  225 + for (var i=0; i<widgets.length; i++) {
  226 + var w = widgets[i];
  227 + if (hasElementClass(w, 'fixed')) {
  228 + simpleLog('DEBUG','Not deleting widget with class '+w.getAttribute('class'));
  229 + } else {
  230 + w.parentNode.removeChild(w);
  231 + simpleLog('DEBUG','Deleting widget with class '+w.getAttribute('class'));
  232 + }
  233 + }
  234 +}
  235 +
  236 +/* XMLHttpRequest functions
  237 + *
  238 + */
  239 +
  240 +function updateFieldset(fieldset) {
  241 + var targeturl = '/presentation/lookAndFeel/knowledgeTree/ajaxConditional.php'; // test_metadata_update.txt';
  242 + simpleLog('DEBUG','AJAX function called: updateFieldset');
  243 +
  244 + var formdata = parseFieldsetToForm(fieldset);
  245 + formdata.formKeys.push('action');
  246 + formdata.formValues.push('updateFieldset');
  247 + var POSTval = queryString(formdata.formKeys, formdata.formValues);
  248 +
  249 + var req = getXMLHttpRequest();
  250 + req.open('POST',targeturl, true); // MUST be async.
  251 + //simpleLog('DEBUG','form submission from updateFieldset: '+logFormSubmission(formdata));
  252 + simpleLog('DEBUG','form submission from updateFieldset: '+(formdata));
  253 + req.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
  254 + var deferred = sendXMLHttpRequest(req, POSTval);
  255 +
  256 + deferred.addErrback(partial(do_handleError, 'updateFieldset'));
  257 + deferred.addCallback(partial(do_updateFieldset, fieldset));
  258 +}
  259 +
  260 +function do_handleError(function_name, err) {
  261 + simpleLog('ERROR','AJAX request from function '+function_name+' failed on exception: '+err);
  262 +}
  263 +
  264 +function do_updateFieldset(fieldset, req) {
  265 + simpleLog('DEBUG','AJAX function do_updateFieldset received: \n'+req.responseText);
  266 + // clear unfixed widgets before we start.
  267 + clearUnfixedWidgets(fieldset);
  268 + // create an unparented div for HTML insertion.
  269 + var t = DIV(null);
  270 + t.innerHTML = req.responseText;
  271 + var new_widgets = getElementsByTagAndClassName('div','widget', t);
  272 + for (var i=0; i<new_widgets.length; i++) {
  273 + var w = new_widgets[i];
  274 + fieldset.appendChild(w);
  275 + bindToConditionalFieldset(fieldset, w);
  276 + }
  277 + delete t; // clean this up.
  278 +}
  279 +
  280 +/* HTML callbacks - functions called on-event.
  281 + *
  282 + */
  283 +
  284 +
  285 +
  286 +/* Fieldset creation and update.
  287 + *
  288 + */
  289 +
  290 +function initialiseConditionalFieldsets() {
  291 + simpleLog('ERROR','incomplete function called: initialiseFieldsets.');
  292 + var fieldsets = getElementsByTagAndClassName('FIELDSET','conditional_metadata');
  293 + simpleLog('DEBUG','found fieldsets: '+fieldsets.length);
  294 + // triggers initial update - since this contains no "fixed" vars, it'll remove "unfixed" widgets
  295 + // and insert the initial (master) field.
  296 + for (var i=0; i<fieldsets.length; i++) {
  297 + var undo_button = INPUT({'type':'button','value':'undo'},null);
  298 + attachToElementEvent(undo_button,'click',partial(popStack, fieldsets[i]));
  299 + fieldsets[i].appendChild(undo_button);
  300 + // initialise the stack.
  301 + getStackForFieldset(fieldsets[i]);
  302 + updateFieldset(fieldsets[i]);
  303 + }
  304 +}
  305 +
  306 +addLoadEvent(initialiseConditionalFieldsets);
... ...