blob: 5e98452a188f78df8dcb02316948746091ec3724 [file] [log] [blame]
Kulanthaivel Palanichamy8ccd6722014-02-19 15:48:02 -08001/*
2 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above
10 * copyright notice, this list of conditions and the following
11 * disclaimer in the documentation and/or other materials provided
12 * with the distribution.
13 * * Neither the name of The Linux Foundation nor the names of its
14 * contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*/
29
30var cardElements;
31var currentActiveCard = null;
32var defaultActiveCard = null;
33// Forward navigation >= 0, backward navigation == -1, unknown navigation == -2
34var currentNavigationType = -2;
35var blankRegEx = /^\s*$/;
36
37var WMLBrowser = {
38 name: "WMLBrowserContext",
39 variables: new Object(),
40 setVar: function (key, value) {
41 this.variables[key] = value;
42 },
43 getVar: function (key) {
44 var value = this.variables[key];
45 if (value == null)
46 value = '';
47 return value;
48 },
49 newContext: function () {
50 this.variables = new Object();
51 },
52}
53
54function isBlank(str) {
55 return (!str || blankRegEx.test(str));
56}
57
58function isURLHash(str) {
59 return (!isBlank(str) && str.length > 1 && str.indexOf("#") == 0);
60}
61
62function getIdFromURLHash(hash) {
63 return hash.substr(1);
64}
65
66window.onhashchange = function()
67{
68 console.log("window.onhashchange currentNavigationType = " + currentNavigationType);
69 var newHash = window.location.hash;
70 if (!isURLHash(newHash)) {
71 currentActiveCard.style.display = "none";
72 defaultActiveCard.style.display = "initial";
73 currentActiveCard = defaultActiveCard;
74 } else {
75 showCardById(newHash.substr(1));
76 }
77 updateWMLVariables();
78 scheduleTimerTaskIfNeeded(currentActiveCard);
79 handleOnNavigationIntrinsicEvent();
80};
81
82window.onload = function()
83{
84 // Consider all the load/reload on this deck as forward navigation.
85 currentNavigationType = 1;
86 var cardHash = window.location.hash;
87 console.log("window.onload card = " + cardHash);
88 cardElements = document.getElementsByClassName('wml_card');
89 defaultActiveCard = cardElements[0];
90 // All the cards are hidden by default.
91 // Show the active card onload.
92 if (isURLHash(cardHash)) {
93 var cardId = cardHash.substr(1);
94 for(var i=0, l=cardElements.length; i<l; i++) {
95 var card = cardElements[i];
96 if (card.getAttribute("id") == cardId) {
97 currentActiveCard = card;
98 currentActiveCard.style.display = "initial";
99 break;
100 }
101 }
102 }
103 if (!currentActiveCard) {
104 currentActiveCard = defaultActiveCard;
105 currentActiveCard.style.display = "initial";
106 }
107 replaceVariablesInTextContentBySpan();
108 fixTextContentInAnchorTasks();
109 initializeSelectElements();
110 scheduleTimerTaskIfNeeded(currentActiveCard);
111 handleOnNavigationIntrinsicEvent();
112};
113
114function showCardById(cardId, onload)
115{
116 if (currentActiveCard && currentActiveCard.getAttribute("id") == cardId) {
117 // We have nothing to do.
118 return false;
119 }
120 for(var i=0, l=cardElements.length; i<l; i++) {
121 var card = cardElements[i];
122 if (card.getAttribute("id") == cardId) {
123 currentActiveCard.style.display = "none";
124 currentActiveCard = card;
125 currentActiveCard.style.display = "initial";
126 return true;
127 }
128 }
129 return false;
130}
131
132function handleOnNavigationIntrinsicEvent() {
133 var navigationType = currentNavigationType;
134 currentNavigationType = -2;
135
136 if (navigationType >= 0) {
137 executeOnenterforwardTask();
138 } else if (navigationType == -1) {
139 executeOnenterbackwardTask();
140 } else {
141 console.log("WARNING: Cannot determine the navigation event type on this card!");
142 }
143}
144
145////////////////////////// WML Variables ////////////////////////////////////////
146function replaceVariablesInTextContentBySpan()
147{
148 var pattern1 = /(\$\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
149 var pattern2 = /(\$([_a-zA-z]{1}[_a-zA-Z0-9]*))/g;
150 var whitespace = /^\s*$/g;
151 var replacer = function () {
152 var name = arguments[2];
153 var escape = "";
154 if (arguments[12])
155 escape = arguments[12];
156 else if (arguments[9])
157 escape = arguments[9];
158 else if (arguments[6])
159 escape = arguments[6];
160 var wml_variable_span = "\<span\ data\-wml\_name\=\"" + name + "\"\ class\=\"wml_variable\" data\-wml\_escape\=\"" + escape + "\"\>\<\/span\>";
161 console.log("replaceVariablesInTextContentBySpan() Found variable " + arguments[0]);
162 return wml_variable_span;
163 };
164
165 var textNodes = [];
166 var treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
167 while(treeWalker.nextNode()) {
168 textNodes.push(treeWalker.currentNode);
169 }
170
171 for(var i=0, l=textNodes.length; i<l; i++) {
172 var curNode = textNodes[i];
173 var text = curNode.nodeValue;
174 if (!(whitespace.test(text))) {
175 var replaced = false;
176 if (pattern1.test(text)) {
177 text = text.replace(pattern1, replacer);
178 replaced = true;
179 }
180 if (pattern2.test(text)) {
181 text = text.replace(pattern2, replacer);
182 replaced = true;
183 }
184 if (replaced) {
185 var mySpan = document.createElement("span");
186 mySpan.innerHTML = text;
187 curNode.parentNode.replaceChild(mySpan, curNode);
188 }
189 }
190 }
191}
192
193////////////////////////// WML Anchor Tasks ////////////////////////////////////////
194// TODO: Optimize this brutal code
195function fixTextContentInAnchorTasks()
196{
197 var anchorTasks = document.getElementsByClassName('wml_anchor_task');
198 for(var i=0, l=anchorTasks.length; i<l; i++) {
199 var container;
200 var taskParent;
201 var task = anchorTasks[i];
202 if (task.dataset.wml_task_type == 'wml_task_go') {
203 taskParent = task.parentNode;
204 container = taskParent.parentNode;
205 } else {
206 taskParent = task;
207 container = task.parentNode;
208 }
209 container.removeChild(taskParent);
210 var anchorText = container.innerHTML;
211 while (container.firstChild) {
212 container.removeChild(container.firstChild);
213 }
214 container.appendChild(taskParent);
215 console.log("fixTextContentInAnchorTasks: anchorText = " + anchorText);
216 task.innerHTML = anchorText + task.innerHTML;
217 }
218}
219
220////////////////////////// WML Timer ////////////////////////////////////////
221var activeScheduledTimer = null;
222function scheduleTimerTaskIfNeeded(card)
223{
224 var timerElements = card.getElementsByClassName('wml_timer');
225 if (timerElements.length > 0) {
226 var timeout = timerElements[0].dataset.wml_value;
227 activeScheduledTimer = window.setTimeout(executeTimerTask, timeout * 100);
228 console.log("Starting WML timer timeout = " + timeout);
229 }
230}
231
232function cancelScheduledTimerTask()
233{
234 if (activeScheduledTimer != null) {
235 window.clearTimeout(clearTimeout);
236 activeScheduledTimer = null;
237 }
238}
239
240////////////////////////////////// <select> & <option> ////////////////////////
241var multiSelectSeparator = ";";
242var selectElementsMap = new Object();
243var WMLSelectElements = {
244 name: "WMLSelectElementsList",
245 options: new Object(),
246 setSelectedOptions: function (key, options) {
247 this.options[key] = options;
248 },
249 getSelectedOptions: function (key) {
250 return this.options[key];
251 },
252};
253
254function initializeSelectElements()
255{
256 var selectElements = document.getElementsByClassName('wml_select');
257 for(var i=0, l=selectElements.length; i<l; i++) {
258 var select = selectElements[i];
259 var iname = select.dataset.wml_iname;
260 // Preselect options based on 'ivalue'
261 var ivalue = select.dataset.wml_ivalue;
262 if (!isBlank(ivalue)) {
263 var options = select.options;
264 var optionsCount = options.length;
265 var ivalueList = ivalue.split(multiSelectSeparator);
266 for(var j=0, ll=ivalueList.length; j<ll; j++) {
267 var index = parseInt(ivalueList[j], 10);
268 if (index > 0 && index <= optionsCount) {
269 options[index-1].selected = 'selected';
270 }
271 }
272 }
273
274 // Keep a copy of selected options to process 'onpick' events
275 var options = select.selectedOptions;
276 var optionsCount = options.length;
277 var optionsList = [];
278 for(var j=0; j<optionsCount; j++) {
279 optionsList.push(options[j]);
280 }
281 var uniqueId = "wml_select_" + i;
282 select.dataset.wml_unique_id = uniqueId;
283 WMLSelectElements.setSelectedOptions(uniqueId, optionsList);
284 }
285}
286
287function handleSelectOnchangeEvent(select)
288{
289 refreshVariableInSelectElement(select);
290 var options = select.selectedOptions;
291 var optionsCount = options.length;
292 var uniqueId = select.dataset.wml_unique_id;
293 var oldSelectedOptions = WMLSelectElements.getSelectedOptions(uniqueId);
294
295 // Update the copy of selected options before we do anything
296 var optionsList = [];
297 for(var j=0; j<optionsCount; j++) {
298 optionsList.push(options[j]);
299 }
300 WMLSelectElements.setSelectedOptions(uniqueId, optionsList);
301
302 // process 'onpick' events if needed
303 for(var i=0; i<optionsCount; i++) {
304 var option = options[i];
305 var onpick = option.dataset.wml_onpick;
306 if (!isBlank(onpick)) {
307 var selectedNewly = true;
308 for(var j=0; j<oldSelectedOptions.length; j++) {
309 if (option.isSameNode(oldSelectedOptions[j])) {
310 selectedNewly = false;
311 break;
312 }
313 }
314 if (selectedNewly) {
315 internal_executeOnpickTask(option);
316 return;
317 }
318 }
319 }
320}
321
322function handleAOnClick(event, node)
323{
324 var href = node.href;
325 var search = node.search;
326 console.log("handleAOnClick <a> href = " + href);
327 refreshVariablesInControlElements();
328 if (!isBlank(search)) {
329 node.search = substituteVariablesInURL(search);
330 } else {
331 node.href = href.split("?")[0];
332 }
333 event.preventDefault();
334 navigateToURL(node.href);
335 return false;
336}
337
338//////////////////////// Variables /////////////////////////
339function substituteVariablesInURL(url)
340{
341 var pattern = /(((\%24)|(\$))\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
342 var replacer = function () {
343 var name = arguments[2];
344 // TODO: Do the URL escaping here
345 console.log("substituteVariablesInURL() found variable : " + arguments[0]);
346 return WMLBrowser.getVar(name);
347 };
348 return url.replace(pattern, replacer);
349}
350
351function substituteVariablesInPostfield(value)
352{
353 var pattern = /(\$\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
354 var replacer = function () {
355 var name = arguments[2];
356 // TODO: Do the URL escaping here
357 console.log("substituteVariablesInPostfield() found variable : " + arguments[0]);
358 return WMLBrowser.getVar(name);
359 };
360 return value.replace(pattern, replacer);
361}
362
363function refreshVariableInSelectElement(select)
364{
365 var options = select.selectedOptions;
366 var value = "";
367 var ivalue = "";
368 var optionsCount = options.length;
369 if (optionsCount > 0) {
370 var op = options[0];
371 value = op.value;
372 ivalue = (op.index + 1);
373 for(var i=1; i<optionsCount; i++) {
374 op = options[i];
375 value += multiSelectSeparator + op.value;
376 ivalue += multiSelectSeparator + (op.index + 1);
377 }
378 }
379 var name = select.name;
380 if (!isBlank(name)) {
381 WMLBrowser.setVar(name, value);
382 console.log("refreshVariableInSelectElement name = " + name + ", value = " + value);
383 }
384 var iname = select.dataset.wml_iname;
385 if (!isBlank(iname)) {
386 if (isBlank(ivalue)) {
387 // An index of zero indicates that no option is selected.
388 ivalue = "0";
389 }
390 WMLBrowser.setVar(iname, ivalue);
391 console.log("refreshVariableInSelectElement iname = " + iname + ", ivalue = " + ivalue);
392 }
393}
394
395function refreshVariablesInControlElements()
396{
397 var inputElements = currentActiveCard.getElementsByClassName('wml_input');
398 for(var i=0, l=inputElements.length; i<l; i++) {
399 var input = inputElements[i];
400 WMLBrowser.setVar(input.name, input.value);
401 console.log("refreshVariablesInControlElements <input> name = " + input.name + ", value = " + input.value);
402 }
403
404 var selectElements = currentActiveCard.getElementsByClassName('wml_select');
405 for(var i=0, l=selectElements.length; i<l; i++) {
406 var select = selectElements[i];
407 refreshVariableInSelectElement(selectElements[i]);
408 }
409}
410
411function updateVariableInPostfields(form)
412{
413 var postfields = currentActiveCard.getElementsByClassName('wml_postfield');
414 for(var i=0, l=postfields.length; i<l; i++) {
415 var input = postfields[i];
416 input.value = substituteVariablesInPostfield(input.value);
417 console.log("updateVariableInPostfields <postfield> name = " + input.name + ", value = " + input.value);
418 }
419}
420
421/////////////// Navigation ////////////////////
422function navigateToURL(url)
423{
424 console.log("navigateToURL: url = " + url);
425 cancelScheduledTimerTask();
426 currentNavigationType = 1;
427 window.location = url;
428}
429
430function navigateToCard(card)
431{
432 console.log("navigateToCard: card = " + card);
433 cancelScheduledTimerTask();
434 currentNavigationType = 1;
435 window.location.hash = card;
436}
437
438function navigateBack()
439{
440 console.log("navigateBack: currentState = ");
441 console.log(window.history.state);
442 cancelScheduledTimerTask();
443 currentNavigationType = -1;
444 window.history.back();
445}
446
447////////////// WML Tasks //////////////////////
448//<refresh>
449
450function updateWMLVariables()
451{
452 var wmlVariables = currentActiveCard.getElementsByClassName('wml_variable');
453 for(var i=0, l=wmlVariables.length; i<l; i++) {
454 var varElement = wmlVariables[i];
455 // Handle the variable escaping option here
456 var value = WMLBrowser.getVar(varElement.dataset.wml_name);
457 // TODO: Handle nested variable substitution on 'value'
458 var conversion = varElement.dataset.wml_escape;
459 if (!isBlank(value) && !isBlank(conversion)) {
460 if (conversion == "e" || conversion == "E") {
461 value = encodeURIComponent(value);
462 } else if (conversion == "u" || conversion == "U") {
463 value = decodeURIComponent(value);
464 }
465 }
466 varElement.innerHTML = value;
467 }
468}
469
470function internal_executeRefreshTask(root)
471{
472 console.log("internal_executeRefreshTask");
473 var setvarElements = root.getElementsByClassName('wml_setvar');
474 for(var i=0, l=setvarElements.length; i<l; i++) {
475 var setvar = setvarElements[i];
476 WMLBrowser.setVar(setvar.dataset.wml_name, setvar.dataset.wml_value);
477 console.log("<setvar> " + setvar.dataset.wml_name + " = " + setvar.dataset.wml_value);
478 }
479 updateWMLVariables();
480}
481
482function executeRefreshTask(event, node)
483{
484 event.preventDefault();
485 internal_executeRefreshTask(node.parentNode);
486 return false;
487}
488
489// <prev>
490function internal_executePrevTask(node)
491{
492 console.log("internal_executePrevTask");
493 internal_executeRefreshTask(node.parentNode);
494 navigateBack();
495}
496
497function executePrevTask(event, node)
498{
499 event.preventDefault();
500 internal_executePrevTask(node);
501 return false;
502}
503// <option onpick="...">
504function internal_executeOnpickTask(option)
505{
506 var href = option.dataset.wml_onpick;
507 console.log("internal_executeOnpickTask href = " + href);
508 //internal_executeRefreshTask(option.parentNode);
509 //refreshVariablesInControlElements();
510 if (isURLHash(href)) {
511 navigateToCard(href);
512 return true;
513 }
514 navigateToURL(href);
515 return true;
516}
517
518//<go>
Tom Zakrajsekbb6f8942014-04-17 22:05:50 -0700519function addQueryStringKeyValuePairsToForm(form)
520{
521 var href = form.dataset.wml_href;
522 var query;
523
524 // Seperate the query string from the href
525 var queryFragments = href.split("?");
526 if (queryFragments.length === 2) {
527 query = queryFragments[1];
528 } else {
529 queryFragments.shift();
530 query = queryFragments.join("?");
531 }
532
533 // Parse the query string for key/value pairs. Add them to the form
534 // as hidden input elements.
535 // E.g., http://myserver/test.wml?p1=foo&p2=bar
536 // would add the following to the form:
537 // <input type="hidden" name="p1" value="foo">
538 // <input type="hidden" name="p2" value="bar">
539 query.replace(
540 new RegExp( "([^?=&]+)(?:=([^&]*))?", "g" ),
541 function(match, name, value) {
542 var param = document.createElement("input");
543 param.setAttribute("type","hidden")
544 param.setAttribute("name", name)
545 param.setAttribute("value",value)
546 form.appendChild(param)
547 }
548 );
549 return true;
550}
551
Kulanthaivel Palanichamy8ccd6722014-02-19 15:48:02 -0800552function internal_executeGoTask(form)
553{
554 var href = form.dataset.wml_href;
555 console.log("internal_executeGoTask href = " + href);
556 internal_executeRefreshTask(form.parentNode);
557 refreshVariablesInControlElements();
558 if (isURLHash(href)) {
559 navigateToCard(href);
560 return true;
561 }
562 // Substitute variables in <postfield> 'value' attributes before form submission.
563 updateVariableInPostfields();
Tom Zakrajsekbb6f8942014-04-17 22:05:50 -0700564 addQueryStringKeyValuePairsToForm(form);
Kulanthaivel Palanichamy8ccd6722014-02-19 15:48:02 -0800565 form.submit();
566 return false;
567}
568
569function executeGoTask(event, form)
570{
571 event.preventDefault();
572 internal_executeGoTask(form);
573 return false;
574}
575
576//<onevent>
577function executeTimerTask()
578{
579 console.log("executeTimerTask()");
580 activeScheduledTimer = null;
581 // Handle <card ontimer="..."> event first
582 var ontimer = currentActiveCard.dataset.wml_ontimer;
583 if (!isBlank(ontimer))
584 {
585 navigateToURL(ontimer);
586 return;
587 }
588 // Handle <onevent type="timer">... here
589 var tasks = currentActiveCard.getElementsByClassName('wml_onevent_ontimer');
590 if (tasks.length > 0) {
591 var onevent = tasks[0];
592 if (onevent.dataset.wml_task_type == 'wml_task_go')
593 return internal_executeGoTask(onevent);
594 else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
595 return internal_executeRefreshTask(onevent);
596 else if (onevent.dataset.wml_task_type == 'wml_task_prev')
597 return internal_executePrevTask(onevent);
598 else if (onevent.dataset.wml_task_type == 'wml_task_noop')
599 return;
600 }
601 // Handle <template timer="..."> event at the end
602 var templates = currentActiveCard.getElementsByClassName('wml_template');
603 if (templates.length > 0) {
604 var ontimer = templates[0].dataset.wml_ontimer;
605 if (!isBlank(ontimer))
606 {
607 navigateToURL(ontimer);
608 return;
609 }
610 }
611}
612
613function executeOnenterforwardTask()
614{
615 console.log("executeOnenterforwardTask()");
616 // Handle <card onenterforward="..."> event first
617 var onef = currentActiveCard.dataset.wml_onenterforward;
618 if (!isBlank(onef))
619 {
620 navigateToURL(onef);
621 return;
622 }
623 // Handle <onevent type="onenterforward">... here
624 var tasks = currentActiveCard.getElementsByClassName('wml_onevent_onenterforward');
625 if (tasks.length > 0) {
626 var onevent = tasks[0];
627 if (onevent.dataset.wml_task_type == 'wml_task_go')
628 return internal_executeGoTask(onevent);
629 else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
630 return internal_executeRefreshTask(onevent);
631 else if (onevent.dataset.wml_task_type == 'wml_task_prev')
632 return internal_executePrevTask(onevent);
633 else if (onevent.dataset.wml_task_type == 'wml_task_noop')
634 return;
635 }
636 // Handle <template onenterforward="..."> event at the end
637 var templates = currentActiveCard.getElementsByClassName('wml_template');
638 if (templates.length > 0) {
639 var onef = templates[0].dataset.wml_onenterforward;
640 if (!isBlank(onef))
641 {
642 navigateToURL(onef);
643 return;
644 }
645 }
646}
647
648function executeOnenterbackwardTask()
649{
650 console.log("executeOnenterbackwardTask()");
651 // Handle <card onenterforward="..."> event first
652 var oneb = currentActiveCard.dataset.wml_onenterbackward;
653 if (!isBlank(oneb))
654 {
655 navigateToURL(oneb);
656 return;
657 }
658 // Handle <onevent type="onenterbackward">... here
659 var tasks = currentActiveCard.getElementsByClassName('wml_onevent_onenterbackward');
660 if (tasks.length > 0) {
661 var onevent = tasks[0];
662 if (onevent.dataset.wml_task_type == 'wml_task_go')
663 return internal_executeGoTask(onevent);
664 else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
665 return internal_executeRefreshTask(onevent);
666 else if (onevent.dataset.wml_task_type == 'wml_task_prev')
667 return internal_executePrevTask(onevent);
668 else if (onevent.dataset.wml_task_type == 'wml_task_noop')
669 return;
670 }
671 // Handle <template onenterbackward="..."> event at the end
672 var templates = currentActiveCard.getElementsByClassName('wml_template');
673 if (templates.length > 0) {
674 var oneb = templates[0].dataset.wml_onenterbackward;
675 if (!isBlank(oneb))
676 {
677 navigateToURL(oneb);
678 return;
679 }
680 }
681}
682
683