blob: 92fa6ee46eed47ac0a0e3857a583ad3fe5802eef [file] [log] [blame]
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var cardElements;
var currentActiveCard = null;
var defaultActiveCard = null;
// Forward navigation >= 0, backward navigation == -1, unknown navigation == -2
var currentNavigationType = -2;
var blankRegEx = /^\s*$/;
var WMLBrowser = {
name: "WMLBrowserContext",
variables: new Object(),
setVar: function (key, value) {
this.variables[key] = value;
},
getVar: function (key) {
var value = this.variables[key];
if (value == null)
value = '';
return value;
},
newContext: function () {
this.variables = new Object();
},
}
function isBlank(str) {
return (!str || blankRegEx.test(str));
}
function isURLHash(str) {
return (!isBlank(str) && str.length > 1 && str.indexOf("#") == 0);
}
function getIdFromURLHash(hash) {
return hash.substr(1);
}
window.onhashchange = function()
{
console.log("window.onhashchange currentNavigationType = " + currentNavigationType);
var newHash = window.location.hash;
if (!isURLHash(newHash)) {
currentActiveCard.style.display = "none";
defaultActiveCard.style.display = "initial";
currentActiveCard = defaultActiveCard;
} else {
showCardById(newHash.substr(1));
}
updateWMLVariables();
scheduleTimerTaskIfNeeded(currentActiveCard);
handleOnNavigationIntrinsicEvent();
};
window.onload = function()
{
// Consider all the load/reload on this deck as forward navigation.
currentNavigationType = 1;
var cardHash = window.location.hash;
console.log("window.onload card = " + cardHash);
cardElements = document.getElementsByClassName('wml_card');
defaultActiveCard = cardElements[0];
// All the cards are hidden by default.
// Show the active card onload.
if (isURLHash(cardHash)) {
var cardId = cardHash.substr(1);
for(var i=0, l=cardElements.length; i<l; i++) {
var card = cardElements[i];
if (card.getAttribute("id") == cardId) {
currentActiveCard = card;
currentActiveCard.style.display = "initial";
break;
}
}
}
if (!currentActiveCard) {
currentActiveCard = defaultActiveCard;
currentActiveCard.style.display = "initial";
}
replaceVariablesInTextContentBySpan();
fixTextContentInAnchorTasks();
initializeSelectElements();
scheduleTimerTaskIfNeeded(currentActiveCard);
handleOnNavigationIntrinsicEvent();
};
function showCardById(cardId, onload)
{
if (currentActiveCard && currentActiveCard.getAttribute("id") == cardId) {
// We have nothing to do.
return false;
}
for(var i=0, l=cardElements.length; i<l; i++) {
var card = cardElements[i];
if (card.getAttribute("id") == cardId) {
currentActiveCard.style.display = "none";
currentActiveCard = card;
currentActiveCard.style.display = "initial";
return true;
}
}
return false;
}
function handleOnNavigationIntrinsicEvent() {
var navigationType = currentNavigationType;
currentNavigationType = -2;
if (navigationType >= 0) {
executeOnenterforwardTask();
} else if (navigationType == -1) {
executeOnenterbackwardTask();
} else {
console.log("WARNING: Cannot determine the navigation event type on this card!");
}
}
////////////////////////// WML Variables ////////////////////////////////////////
function replaceVariablesInTextContentBySpan()
{
var pattern1 = /(\$\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
var pattern2 = /(\$([_a-zA-z]{1}[_a-zA-Z0-9]*))/g;
var whitespace = /^\s*$/g;
var replacer = function () {
var name = arguments[2];
var escape = "";
if (arguments[12])
escape = arguments[12];
else if (arguments[9])
escape = arguments[9];
else if (arguments[6])
escape = arguments[6];
var wml_variable_span = "\<span\ data\-wml\_name\=\"" + name + "\"\ class\=\"wml_variable\" data\-wml\_escape\=\"" + escape + "\"\>\<\/span\>";
console.log("replaceVariablesInTextContentBySpan() Found variable " + arguments[0]);
return wml_variable_span;
};
var textNodes = [];
var treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
while(treeWalker.nextNode()) {
textNodes.push(treeWalker.currentNode);
}
for(var i=0, l=textNodes.length; i<l; i++) {
var curNode = textNodes[i];
var text = curNode.nodeValue;
if (!(whitespace.test(text))) {
var replaced = false;
if (pattern1.test(text)) {
text = text.replace(pattern1, replacer);
replaced = true;
}
if (pattern2.test(text)) {
text = text.replace(pattern2, replacer);
replaced = true;
}
if (replaced) {
var mySpan = document.createElement("span");
mySpan.innerHTML = text;
curNode.parentNode.replaceChild(mySpan, curNode);
}
}
}
}
////////////////////////// WML Anchor Tasks ////////////////////////////////////////
// TODO: Optimize this brutal code
function fixTextContentInAnchorTasks()
{
var anchorTasks = document.getElementsByClassName('wml_anchor_task');
for(var i=0, l=anchorTasks.length; i<l; i++) {
var container;
var taskParent;
var task = anchorTasks[i];
if (task.dataset.wml_task_type == 'wml_task_go') {
taskParent = task.parentNode;
container = taskParent.parentNode;
} else {
taskParent = task;
container = task.parentNode;
}
container.removeChild(taskParent);
var anchorText = container.innerHTML;
while (container.firstChild) {
container.removeChild(container.firstChild);
}
container.appendChild(taskParent);
console.log("fixTextContentInAnchorTasks: anchorText = " + anchorText);
task.innerHTML = anchorText + task.innerHTML;
}
}
////////////////////////// WML Timer ////////////////////////////////////////
var activeScheduledTimer = null;
function scheduleTimerTaskIfNeeded(card)
{
var timerElements = card.getElementsByClassName('wml_timer');
if (timerElements.length > 0) {
var timeout = timerElements[0].dataset.wml_value;
activeScheduledTimer = window.setTimeout(executeTimerTask, timeout * 100);
console.log("Starting WML timer timeout = " + timeout);
}
}
function cancelScheduledTimerTask()
{
if (activeScheduledTimer != null) {
window.clearTimeout(clearTimeout);
activeScheduledTimer = null;
}
}
////////////////////////////////// <select> & <option> ////////////////////////
var multiSelectSeparator = ";";
var selectElementsMap = new Object();
var WMLSelectElements = {
name: "WMLSelectElementsList",
options: new Object(),
setSelectedOptions: function (key, options) {
this.options[key] = options;
},
getSelectedOptions: function (key) {
return this.options[key];
},
};
function initializeSelectElements()
{
var selectElements = document.getElementsByClassName('wml_select');
for(var i=0, l=selectElements.length; i<l; i++) {
var select = selectElements[i];
var iname = select.dataset.wml_iname;
// Preselect options based on 'ivalue'
var ivalue = select.dataset.wml_ivalue;
if (!isBlank(ivalue)) {
var options = select.options;
var optionsCount = options.length;
var ivalueList = ivalue.split(multiSelectSeparator);
for(var j=0, ll=ivalueList.length; j<ll; j++) {
var index = parseInt(ivalueList[j], 10);
if (index > 0 && index <= optionsCount) {
options[index-1].selected = 'selected';
}
}
}
// Keep a copy of selected options to process 'onpick' events
var options = select.selectedOptions;
var optionsCount = options.length;
var optionsList = [];
for(var j=0; j<optionsCount; j++) {
optionsList.push(options[j]);
}
var uniqueId = "wml_select_" + i;
select.dataset.wml_unique_id = uniqueId;
WMLSelectElements.setSelectedOptions(uniqueId, optionsList);
}
}
function handleSelectOnchangeEvent(select)
{
refreshVariableInSelectElement(select);
var options = select.selectedOptions;
var optionsCount = options.length;
var uniqueId = select.dataset.wml_unique_id;
var oldSelectedOptions = WMLSelectElements.getSelectedOptions(uniqueId);
// Update the copy of selected options before we do anything
var optionsList = [];
for(var j=0; j<optionsCount; j++) {
optionsList.push(options[j]);
}
WMLSelectElements.setSelectedOptions(uniqueId, optionsList);
// process 'onpick' events if needed
for(var i=0; i<optionsCount; i++) {
var option = options[i];
var onpick = option.dataset.wml_onpick;
if (!isBlank(onpick)) {
var selectedNewly = true;
for(var j=0; j<oldSelectedOptions.length; j++) {
if (option.isSameNode(oldSelectedOptions[j])) {
selectedNewly = false;
break;
}
}
if (selectedNewly) {
internal_executeOnpickTask(option);
return;
}
}
}
}
function handleAOnClick(event, node)
{
var href = node.href;
var search = node.search;
console.log("handleAOnClick <a> href = " + href);
refreshVariablesInControlElements();
if (!isBlank(search)) {
node.search = substituteVariablesInURL(search);
} else {
node.href = href.split("?")[0];
}
event.preventDefault();
navigateToURL(node.href);
return false;
}
//////////////////////// Variables /////////////////////////
function substituteVariablesInURL(url)
{
var pattern = /(((\%24)|(\$))\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
var replacer = function () {
var name = arguments[2];
// TODO: Do the URL escaping here
console.log("substituteVariablesInURL() found variable : " + arguments[0]);
return WMLBrowser.getVar(name);
};
return url.replace(pattern, replacer);
}
function substituteVariablesInPostfield(value)
{
var pattern = /(\$\(([_a-z]{1}[_a-z0-9]*)([:]{1}((([e]{1})(scape)?)|(([n]{1})(oesc)?)|(([u]{1})(nesc)?)))?\))/gi;
var replacer = function () {
var name = arguments[2];
// TODO: Do the URL escaping here
console.log("substituteVariablesInPostfield() found variable : " + arguments[0]);
return WMLBrowser.getVar(name);
};
return value.replace(pattern, replacer);
}
function refreshVariableInSelectElement(select)
{
var options = select.selectedOptions;
var value = "";
var ivalue = "";
var optionsCount = options.length;
if (optionsCount > 0) {
var op = options[0];
value = op.value;
ivalue = (op.index + 1);
for(var i=1; i<optionsCount; i++) {
op = options[i];
value += multiSelectSeparator + op.value;
ivalue += multiSelectSeparator + (op.index + 1);
}
}
var name = select.name;
if (!isBlank(name)) {
WMLBrowser.setVar(name, value);
console.log("refreshVariableInSelectElement name = " + name + ", value = " + value);
}
var iname = select.dataset.wml_iname;
if (!isBlank(iname)) {
if (isBlank(ivalue)) {
// An index of zero indicates that no option is selected.
ivalue = "0";
}
WMLBrowser.setVar(iname, ivalue);
console.log("refreshVariableInSelectElement iname = " + iname + ", ivalue = " + ivalue);
}
}
function refreshVariablesInControlElements()
{
var inputElements = currentActiveCard.getElementsByClassName('wml_input');
for(var i=0, l=inputElements.length; i<l; i++) {
var input = inputElements[i];
WMLBrowser.setVar(input.name, input.value);
console.log("refreshVariablesInControlElements <input> name = " + input.name + ", value = " + input.value);
}
var selectElements = currentActiveCard.getElementsByClassName('wml_select');
for(var i=0, l=selectElements.length; i<l; i++) {
var select = selectElements[i];
refreshVariableInSelectElement(selectElements[i]);
}
}
function updateVariableInPostfields(form)
{
var postfields = currentActiveCard.getElementsByClassName('wml_postfield');
for(var i=0, l=postfields.length; i<l; i++) {
var input = postfields[i];
input.value = substituteVariablesInPostfield(input.value);
console.log("updateVariableInPostfields <postfield> name = " + input.name + ", value = " + input.value);
}
}
/////////////// Navigation ////////////////////
function navigateToURL(url)
{
console.log("navigateToURL: url = " + url);
cancelScheduledTimerTask();
currentNavigationType = 1;
window.location = url;
}
function navigateToCard(card)
{
console.log("navigateToCard: card = " + card);
cancelScheduledTimerTask();
currentNavigationType = 1;
window.location.hash = card;
}
function navigateBack()
{
console.log("navigateBack: currentState = ");
console.log(window.history.state);
cancelScheduledTimerTask();
currentNavigationType = -1;
window.history.back();
}
////////////// WML Tasks //////////////////////
//<refresh>
function updateWMLVariables()
{
var wmlVariables = currentActiveCard.getElementsByClassName('wml_variable');
for(var i=0, l=wmlVariables.length; i<l; i++) {
var varElement = wmlVariables[i];
// Handle the variable escaping option here
var value = WMLBrowser.getVar(varElement.dataset.wml_name);
// TODO: Handle nested variable substitution on 'value'
var conversion = varElement.dataset.wml_escape;
if (!isBlank(value) && !isBlank(conversion)) {
if (conversion == "e" || conversion == "E") {
value = encodeURIComponent(value);
} else if (conversion == "u" || conversion == "U") {
value = decodeURIComponent(value);
}
}
varElement.innerHTML = value;
}
}
function internal_executeRefreshTask(root)
{
console.log("internal_executeRefreshTask");
var setvarElements = root.getElementsByClassName('wml_setvar');
for(var i=0, l=setvarElements.length; i<l; i++) {
var setvar = setvarElements[i];
WMLBrowser.setVar(setvar.dataset.wml_name, setvar.dataset.wml_value);
console.log("<setvar> " + setvar.dataset.wml_name + " = " + setvar.dataset.wml_value);
}
updateWMLVariables();
}
function executeRefreshTask(event, node)
{
event.preventDefault();
internal_executeRefreshTask(node.parentNode);
return false;
}
// <prev>
function internal_executePrevTask(node)
{
console.log("internal_executePrevTask");
internal_executeRefreshTask(node.parentNode);
navigateBack();
}
function executePrevTask(event, node)
{
event.preventDefault();
internal_executePrevTask(node);
return false;
}
// <option onpick="...">
function internal_executeOnpickTask(option)
{
var href = option.dataset.wml_onpick;
console.log("internal_executeOnpickTask href = " + href);
//internal_executeRefreshTask(option.parentNode);
//refreshVariablesInControlElements();
if (isURLHash(href)) {
navigateToCard(href);
return true;
}
navigateToURL(href);
return true;
}
//<go>
function internal_executeGoTask(form)
{
var href = form.dataset.wml_href;
console.log("internal_executeGoTask href = " + href);
internal_executeRefreshTask(form.parentNode);
refreshVariablesInControlElements();
if (isURLHash(href)) {
navigateToCard(href);
return true;
}
// Substitute variables in <postfield> 'value' attributes before form submission.
updateVariableInPostfields();
form.submit();
return false;
}
function executeGoTask(event, form)
{
event.preventDefault();
internal_executeGoTask(form);
return false;
}
//<onevent>
function executeTimerTask()
{
console.log("executeTimerTask()");
activeScheduledTimer = null;
// Handle <card ontimer="..."> event first
var ontimer = currentActiveCard.dataset.wml_ontimer;
if (!isBlank(ontimer))
{
navigateToURL(ontimer);
return;
}
// Handle <onevent type="timer">... here
var tasks = currentActiveCard.getElementsByClassName('wml_onevent_ontimer');
if (tasks.length > 0) {
var onevent = tasks[0];
if (onevent.dataset.wml_task_type == 'wml_task_go')
return internal_executeGoTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
return internal_executeRefreshTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_prev')
return internal_executePrevTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_noop')
return;
}
// Handle <template timer="..."> event at the end
var templates = currentActiveCard.getElementsByClassName('wml_template');
if (templates.length > 0) {
var ontimer = templates[0].dataset.wml_ontimer;
if (!isBlank(ontimer))
{
navigateToURL(ontimer);
return;
}
}
}
function executeOnenterforwardTask()
{
console.log("executeOnenterforwardTask()");
// Handle <card onenterforward="..."> event first
var onef = currentActiveCard.dataset.wml_onenterforward;
if (!isBlank(onef))
{
navigateToURL(onef);
return;
}
// Handle <onevent type="onenterforward">... here
var tasks = currentActiveCard.getElementsByClassName('wml_onevent_onenterforward');
if (tasks.length > 0) {
var onevent = tasks[0];
if (onevent.dataset.wml_task_type == 'wml_task_go')
return internal_executeGoTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
return internal_executeRefreshTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_prev')
return internal_executePrevTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_noop')
return;
}
// Handle <template onenterforward="..."> event at the end
var templates = currentActiveCard.getElementsByClassName('wml_template');
if (templates.length > 0) {
var onef = templates[0].dataset.wml_onenterforward;
if (!isBlank(onef))
{
navigateToURL(onef);
return;
}
}
}
function executeOnenterbackwardTask()
{
console.log("executeOnenterbackwardTask()");
// Handle <card onenterforward="..."> event first
var oneb = currentActiveCard.dataset.wml_onenterbackward;
if (!isBlank(oneb))
{
navigateToURL(oneb);
return;
}
// Handle <onevent type="onenterbackward">... here
var tasks = currentActiveCard.getElementsByClassName('wml_onevent_onenterbackward');
if (tasks.length > 0) {
var onevent = tasks[0];
if (onevent.dataset.wml_task_type == 'wml_task_go')
return internal_executeGoTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_refresh')
return internal_executeRefreshTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_prev')
return internal_executePrevTask(onevent);
else if (onevent.dataset.wml_task_type == 'wml_task_noop')
return;
}
// Handle <template onenterbackward="..."> event at the end
var templates = currentActiveCard.getElementsByClassName('wml_template');
if (templates.length > 0) {
var oneb = templates[0].dataset.wml_onenterbackward;
if (!isBlank(oneb))
{
navigateToURL(oneb);
return;
}
}
}