📄 conditional_usage.js.svn-base
字号:
// simple event stacking.// i don't like Mochikit's one.function getBindTarget(fieldset) { var possibles = getElementsByTagAndClassName('DIV','conditional_target', fieldset); return possibles[0];}function attachToElementEvent(elem, event_name, func) { // catch IE (grumble) if (elem.attachEvent) { elem.attachEvent('on'+event_name, func); } else { elem.addEventListener(event_name, func, false); }}function removeFromElementEvent(elem, event_name, func) { // catch IE (grumble) if (elem.detachEvent) { elem.detachEvent('on'+event_name, func); } else { elem.removeEventListener(event_name, func, false); }}// quick and dirty helper - find the nearest parent item matching tagName. // FIXME steal the klass or tagName logic from MochiK.// FIXME add to a core js-lib, and add some unit-tests.function breadcrumbFind(elem, tagName) { var stopTag = 'BODY'; var currentTag = elem.tagName; var currentElem = elem; while ((currentTag != stopTag) && (currentTag != tagName)) { currentElem = currentElem.parentNode; currentTag = currentElem.tagName; } if (currentTag == tagName) { return currentElem; } else { return null; }}/* Conditional Metadata Usage * * Allows the system to respond to conditional metadata events. *//**== Basic process around Conditional Metadata JS/HTML interaction ==The system works based on 3 concepts: 1. on the appropriate "activation" command, the entire "field" is serialised and replaced with a hidden input var, and a "user friendly" label. 2. a undo stack needs to be kept, which provides the user with a way to "un-fix" items. 3. When an item is activated, the system: (i) polls the page for fixed input-vars: this _includes_ (for example) fieldset_id, as well as later items. (ii) submits these to a targeturl (set as a "global var" - currently in _this_ file.) // FIXME: this needs to be programmatically settable. // TODO make this operate on a particular subset of the page, and be _instantiable_. (use fieldset as the "controlling component". // TODO lazy bind all activation handlers to ensure that the above problem is solveable. // TODO ensure that this functions across the required browser sets. // TODO verify that the entire set of "lookup" values works here: select and input seem to work.*/var conditional_usage_undostack = new Array();var conditional_usage_keys = new Array();// sorry mom. function checkStackForFieldset(fieldset) { for (var i=0; i<conditional_usage_keys.length; i++) { if (conditional_usage_keys[i] == fieldset) { simpleLog('DEBUG','found undostack at keyindex '+i); return true; } } return false;}// grow and go.function getStackForFieldset(fieldset) { for (var i=0; i<conditional_usage_keys.length; i++) { if (conditional_usage_keys[i] == fieldset) { simpleLog('DEBUG','found undostack at keyindex '+i); return conditional_usage_undostack[i]; } } // we would have returned by now. onward, and upward. // i == conditional_usage_keys.length == conditional_usage_undostack.length conditional_usage_undostack.push(Array()); conditional_usage_keys.push(fieldset); simpleLog('DEBUG','created undostack at keyindex '+i+' for fieldset '+fieldset); simpleLog('DEBUG','undoStack: '+conditional_usage_undostack+'\nundoKeyStack: '+conditional_usage_keys) return conditional_usage_undostack[i]; // must be the "new" element, which is 1 past the old size.}// Stack implementationfunction pushStack(fieldset, subtree) { // FIXME how do I bind this to a particular fieldset object. // 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 ... simpleLog('DEBUG','pushStack received: '+fieldset); var undostack = getStackForFieldset(fieldset); undostack.push(subtree); // onto the end, so it can be popped. simpleLog('ERROR','added item to undo stack..');}function popStack(fieldset) { var undostack = getStackForFieldset(fieldset); if (undostack.length == 0) { return ; } var last_item = undostack.pop(); simpleLog('DEBUG','popping item\n'+toHTML(last_item)); last_item.parentNode.removeChild(last_item); updateFieldset(fieldset);}/** - creates a replacement widget, - adds the _old_ widget to the correct stack.*/function createFixedWidget(fieldset, widget, i_name, i_value, i_label) { // bad, but there's nothing else we can do in the current design. // we need to walk the TR for the TH (widget.tagName == TR) if (widget.tagName != 'DIV') { // alert('Invalid widget in conditional.'+widget); simpleLog('ERROR','invalid widget in conditional.'); return false; } simpleLog('DEBUG','creating fixed widget'); var header = widget.getElementsByTagName('LABEL')[0]; // FIXME _could_ fail if pathalogical. simpleLog('DEBUG','got label'); // check for "requird" and edit. var headlist = header.getElementsByTagName('SPAN'); simpleLog('DEBUG','got headlist - ' + headlist.length); if (headlist.length != 0) { header.removeChild(headlist[0]); } simpleLog('DEBUG','getting name'); var i_friendly_name = scrapeText(header); var newWidget = DIV({'class':'field fixed'}, createDOM('LABEL',null, i_friendly_name), DIV(null, INPUT({'type':'hidden','name':i_name, 'value':i_value,'class':'fixed'}), SPAN(null, i_label) ) ); swapDOM(widget, newWidget); pushStack(fieldset, newWidget); simpleLog('ERROR','conditional_usage passed in fieldset '+fieldset+' and widget '+newWidget); }/** handles the "update" event. needs to: - "replace" the contents of the widget with a "fixed" input. - trigger the "updateFieldset"*/function handleSelectChange(fieldset, widget, select_object) { simpleLog('DEBUG','handleSelectChange on select with name "'+select_object.name+'"'); var i_name = select_object.name; var i_value = select_object.value; var i_label = scrapeText(select_object.options[select_object.selectedIndex]); simpleLog('DEBUG','handleSelectChange creating'); createFixedWidget(fieldset, widget, i_name, i_value, i_label); simpleLog('DEBUG','handleSelectChange updating'); updateFieldset(fieldset);}function handleRadioChange(fieldset, widget, radio_object) { simpleLog('ERROR','call to stub: handleRadioChange on radio with name "'+radio_object.name+'"'); var i_name = radio_object.name; var i_value = radio_object.value; var oLabel = breadcrumbFind(radio_object, 'LABEL'); if (oLabel == null) { simpleLog('ERROR','radiobutton ('+radio_object.name+':'+radio_object.value+') has no associated label. failing.'); return false; } else { var i_label = scrapeText(oLabel); } createFixedWidget(fieldset, widget, i_name, i_value, i_label); updateFieldset(fieldset);}/** extract all the appropriate input-vars from a given fieldset, so that it can be passed into a backed. Returns an array ("formKeys" => array(), "formValues" => array()) that can be passed to be backend. // actually, this is ONLY and issue for the "fieldset_id" form-field: // 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 // 'fieldset_id[]' since the backend can then extract which fieldsets have been called. other vars will get converted // from <input type="radio" ... name="xxxx"> and <select ... name="xxxx"> to <input type="hidden" class="fixed"> // */function parseFieldsetToForm(fieldset) { simpleLog('ERROR','call to untested fn: parseFieldsetToForm. '); var formContent = new Array(); var input_vars = getElementsByTagAndClassName('input','fixed',fieldset); formContent["formKeys"] = new Array(); formContent["formValues"] = new Array(); for (var i=0; i<input_vars.length; i++) { var input_object = input_vars[i]; // don't delete the undo button. if (input_object.type != 'button') { formContent["formKeys"].push(input_object.name); formContent["formValues"].push(input_object.value); } } return formContent;}/** bind a "widget" to a particular fieldset, and populate the appropriate event-handlers - find the various types of input objects and hook in appropriately: make sure that the function binds: - fieldset - pseudo-widget (the div that surrounds each group of options.) -handler. // FIXME: this assumes that inputs are either "select" or "<input type='radio'>" // FIXME: is that a valid assumption?*/function bindToConditionalFieldset(fieldset, widget) { // handleChange needs to be bound to each input widget. // for <input type != "hidden"> type variables this means binding to onclick // for <select> this means binding to onchange var select_fields = widget.getElementsByTagName('SELECT'); var input_fields = widget.getElementsByTagName('INPUT'); // needs to be filtered - no "hidden" vars. for (var i=0; i<select_fields.length; i++) { var select_object = select_fields[i]; var handler = partial(handleSelectChange, fieldset, widget, select_object); attachToElementEvent(select_object, 'change', handler); } for (var i=0; i<input_fields.length; i++) { var input_object = input_fields[i]; var handler = partial(handleRadioChange, fieldset, widget, input_object); if (input_object.type == 'radio') { attachToElementEvent(input_object, 'click', handler); } else if (input_object.type == 'hidden') { ; // this is OK, and expected. } else { simpleLog('ERROR','bindToConditionalFieldset found a non-hidden input field of type: '+input_object.type); } } simpleLog('DEBUG','bindToConditionalFieldset complete');} function clearUnfixedWidgets(fieldset) { var widgets = getElementsByTagAndClassName('DIV', 'field', fieldset); for (var i=0; i<widgets.length; i++) { var w = widgets[i]; if (hasElementClass(w, 'fixed')) { simpleLog('DEBUG','Not deleting widget with class '+w.getAttribute('class')); } else { w.parentNode.removeChild(w); simpleLog('DEBUG','Deleting widget with class '+w.getAttribute('class')); } }}/* XMLHttpRequest functions * */function updateFieldset(fieldset) { var baseurl = getElement('kt-core-baseurl').value; var targeturl = baseurl + '/presentation/lookAndFeel/knowledgeTree/ajaxConditional.php'; // test_metadata_update.txt'; simpleLog('DEBUG','AJAX function called: updateFieldset'); var formdata = parseFieldsetToForm(fieldset); formdata.formKeys.push('action'); formdata.formValues.push('updateFieldset'); var POSTval = queryString(formdata.formKeys, formdata.formValues); var req = getXMLHttpRequest(); req.open('POST',targeturl, true); // MUST be async. //simpleLog('DEBUG','form submission from updateFieldset: '+logFormSubmission(formdata)); simpleLog('DEBUG','form submission from updateFieldset: '+(formdata)); req.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); var deferred = sendXMLHttpRequest(req, POSTval); deferred.addErrback(partial(do_handleError, 'updateFieldset')); deferred.addCallback(partial(do_updateFieldset, fieldset));}function do_handleError(function_name, err) { simpleLog('ERROR','AJAX request from function '+function_name+' failed on exception: '+err);}function do_updateFieldset(fieldset, req) { simpleLog('DEBUG','AJAX function do_updateFieldset received: \n'+req.responseText); // clear unfixed widgets before we start. clearUnfixedWidgets(fieldset); // create an unparented div for HTML insertion. var hold = DIV(null); hold.innerHTML = req.responseText; var new_widgets = getElementsByTagAndClassName('DIV','field', hold); simpleLog('DEBUG','new_widgets.length: '+new_widgets.length); var target = getBindTarget(fieldset); simpleLog('DEBUG','new_widgets.length: '+new_widgets.length); for (var i=0; i<new_widgets.length; i++) { var w = new_widgets[i]; simpleLog('DEBUG','binding: '+toHTML(w)); target.appendChild(w); bindToConditionalFieldset(fieldset, w); } simpleLog('DEBUG','fieldset ends as: \n'+toHTML(fieldset)); delete t; // clean this up.}/* HTML callbacks - functions called on-event. * */ function reviseConditional(buttonsource) { var fieldset = breadcrumbFind(buttonsource, 'FIELDSET'); setElementClass(fieldset, 'conditional_metadata'); if (!checkStackForFieldset(fieldset)) { var undo_button = INPUT({'type':'button','value':_('Undo')},null); attachToElementEvent(undo_button,'click',partial(popStack, fieldset)); fieldset.appendChild(undo_button); // initialise the stack. getStackForFieldset(fieldset); updateFieldset(fieldset); buttonsource.parentNode.removeChild(buttonsource); } }/* Fieldset creation and update. * */function initialiseConditionalFieldsets() { var fieldsets = getElementsByTagAndClassName('FIELDSET','conditional_metadata'); simpleLog('DEBUG','found fieldsets: '+fieldsets.length); // triggers initial update - since this contains no "fixed" vars, it'll remove "unfixed" widgets // and insert the initial (master) field. for (var i=0; i<fieldsets.length; i++) { if (!checkStackForFieldset(fieldsets[i])) { var undo_button = INPUT({'type':'button','value':_('Undo')},null); attachToElementEvent(undo_button,'click',partial(popStack, fieldsets[i])); fieldsets[i].appendChild(undo_button); // initialise the stack. getStackForFieldset(fieldsets[i]); updateFieldset(fieldsets[i]); } }}addLoadEvent(initialiseConditionalFieldsets);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -