conditional_usage.js
14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// 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);