Commit 63a9bd9d849d20e6ca0c73e9c82f29fd51bdcf8b
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
Showing
1 changed file
with
306 additions
and
0 deletions
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); | ... | ... |