This repository has been archived on 2024-11-28. You can view files and clone it, but cannot push or open issues or pull requests.
Incam_SGD/resources/js/conditional_usage.js

378 lines
14 KiB
JavaScript
Raw Normal View History

// 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 implementation
function 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);