//This is the CalculateApp object, providing a full context for the app
|
|
var CalculateApp = {
|
|
/* UI Elements */
|
|
elements: {
|
|
resultsZoneDiv: undefined,
|
|
calcInputDiv: undefined,
|
|
calcInput: undefined,
|
|
labelInput: undefined,
|
|
modalDiv: undefined
|
|
},
|
|
|
|
/* libraries loaded by require */
|
|
libs: {
|
|
mustache: undefined,
|
|
functionPlot: undefined
|
|
},
|
|
|
|
/* Application data */
|
|
data: {
|
|
buddyColor: {
|
|
stroke: "#1500A7",
|
|
fill: "#ff0000"
|
|
},
|
|
calculations: [],
|
|
windowWidth: undefined,
|
|
windowHeight: undefined,
|
|
isMobile: /iphone|ipod|ipad|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec/i.test(navigator.userAgent.toLowerCase()),
|
|
isIos: (navigator.userAgent.match(/iPad|iPhone|iPod/g) ? true : false ),
|
|
outputBase: 10,
|
|
outputDigits: 6,
|
|
isRadian: false,
|
|
pi: 3.141592653589793
|
|
},
|
|
|
|
/* Return if Chrome WebApp env. Chrome WebApp does not allow eval. */
|
|
isFullMode: function fullModeEval() {
|
|
if (typeof chrome != "undefined" && chrome.app && chrome.app.runtime) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}(),
|
|
|
|
/* Evaluate a calculation without using eval() */
|
|
evalParser: function(input) {
|
|
var result = Parser.parse(input).evaluate();
|
|
return result;
|
|
},
|
|
|
|
/* Evaluate a calculation using eval() - provides advanced features */
|
|
evalMathJS: function(input) {
|
|
var result = mathjs.eval(input);
|
|
return result;
|
|
},
|
|
|
|
/* Auto eval using light or full mode */
|
|
eval: function(input) {
|
|
var result;
|
|
var resultBase10;
|
|
if (CalculateApp.isFullMode) {
|
|
result = CalculateApp.evalMathJS(input);
|
|
resultBase10 = CalculateApp.baseConv(CalculateApp.evalMathJS(input), CalculateApp.data.outputBase, 10);
|
|
} else {
|
|
result = CalculateApp.evalParser(input);
|
|
resultBase10 = CalculateApp.baseConv(CalculateApp.evalParser(input), CalculateApp.data.outputBase, 10);
|
|
}
|
|
return {
|
|
resultBase10: CalculateApp.toFixed(result, CalculateApp.data.outputDigits),
|
|
result: CalculateApp.toFixed(resultBase10, CalculateApp.data.outputDigits)
|
|
};
|
|
},
|
|
|
|
/* Calcul f(x) with x = 0 using light or full mode*/
|
|
evalFunctionAtZero: function(input) {
|
|
var result;
|
|
if (CalculateApp.isFullMode) {
|
|
try {
|
|
result = mathjs.eval(input)(0);
|
|
if (isNaN(result) || !isFinite(result)) {
|
|
return 0;
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
try {
|
|
result = Parser.parse(input).evaluate({
|
|
x: 0
|
|
});
|
|
if (isNaN(result) || !isFinite(result)) {
|
|
return 0;
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
return 0;
|
|
}
|
|
}
|
|
},
|
|
|
|
/* Check if simple calculation or function */
|
|
isGraph: function(input) {
|
|
if (input.indexOf("f(x)") == -1) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
/* Display a graph */
|
|
_graph: function(equation, valueForZero, graphFn) {
|
|
nanoModal(CalculateApp.elements.modalDiv, {
|
|
buttons: [],
|
|
}).onShow(function(m) {
|
|
CalculateApp.elements.modalDiv.style.display = "block";
|
|
CalculateApp.elements.modalDiv.style.marginLeft = "auto";
|
|
CalculateApp.elements.modalDiv.style.marginRight = "auto";
|
|
CalculateApp.elements.modalDiv.style.height = parseInt(CalculateApp.data.windowHeight * 80 / 100) + "px";
|
|
CalculateApp.elements.modalDiv.style.width = parseInt(CalculateApp.data.windowWidth * 80 / 100) + "px";
|
|
try {
|
|
var openedModal = CalculateApp.libs.functionPlot({
|
|
target: '#plot',
|
|
yDomain: [valueForZero - 10, valueForZero + 10],
|
|
xDomain: [-10, 10],
|
|
width: parseInt(CalculateApp.data.windowWidth * 80 / 100),
|
|
height: parseInt(CalculateApp.data.windowHeight * 80 / 100),
|
|
data: [{
|
|
fn: graphFn
|
|
}]
|
|
});
|
|
for (var i = 0; i < 10; i++) {
|
|
window.scroll(0, 0);
|
|
}
|
|
} catch (err) {
|
|
m.hide();
|
|
}
|
|
|
|
}).onHide(function(m) {
|
|
m.remove();
|
|
setTimeout(function() {
|
|
CalculateApp.focus();
|
|
}, 300);
|
|
}).show();
|
|
},
|
|
|
|
/* Display a graph using light mode */
|
|
graphParser: function(equation) {
|
|
var valueForZero = CalculateApp.evalFunctionAtZero(equation);
|
|
var graphFn = function(x) {
|
|
return Parser.parse(equation).evaluate({
|
|
"x": x
|
|
});
|
|
};
|
|
CalculateApp._graph(equation, valueForZero, graphFn);
|
|
},
|
|
|
|
/* Display a graph using full mode */
|
|
graphMathJS: function(equation) {
|
|
var valueForZero = CalculateApp.evalFunctionAtZero(equation);
|
|
var graphFn = mathjs.eval(equation);
|
|
CalculateApp._graph(equation, valueForZero, graphFn);
|
|
},
|
|
|
|
/* Display a graph using light or full mode */
|
|
graph: function(input) {
|
|
input = CalculateApp.tryPatchGraphEquation(input);
|
|
|
|
if (CalculateApp.isFullMode) {
|
|
return CalculateApp.graphMathJS(input);
|
|
} else {
|
|
return CalculateApp.graphParser(input);
|
|
}
|
|
},
|
|
|
|
/* Display a calculation */
|
|
displayCalculation: function(calculation) {
|
|
var node = document.createElement("div");
|
|
if (calculation.error) {
|
|
node.innerHTML = CalculateApp.libs.mustache.render(getErrorTemplate(), calculation);
|
|
} else {
|
|
if (calculation.graph) {
|
|
node.innerHTML = CalculateApp.libs.mustache.render(getGraphTemplate(), calculation);
|
|
var childrens = node.childNodes[0].childNodes;
|
|
var functionGraph = function(input) {
|
|
CalculateApp.graph(input.target.value);
|
|
};
|
|
for (var i = 0; i < childrens.length; i++) {
|
|
if (childrens[i].tagName === "BUTTON") {
|
|
childrens[i].addEventListener('click', functionGraph);
|
|
}
|
|
}
|
|
} else {
|
|
node.innerHTML = CalculateApp.libs.mustache.render(getResultTemplate(), calculation);
|
|
}
|
|
}
|
|
CalculateApp.elements.resultsZoneDiv.insertBefore(node, CalculateApp.elements.resultsZoneDiv.childNodes[0]);
|
|
},
|
|
|
|
/* Add calculation to list */
|
|
storeCalculation: function(calculation) {
|
|
if(CalculateApp.data.calculations === null){
|
|
CalculateApp.data.calculations = [];
|
|
}
|
|
CalculateApp.data.calculations.push(calculation);
|
|
},
|
|
|
|
/* Persist calculation lists */
|
|
persistCalculations: function() {
|
|
var calculationsToSave = [];
|
|
for (var i = CalculateApp.data.calculations.length - 1; i >= 0; i--) {
|
|
if (calculationsToSave.length >= 20) {
|
|
break;
|
|
}
|
|
calculationsToSave.unshift(CalculateApp.data.calculations[i]);
|
|
}
|
|
var json = JSON.stringify(calculationsToSave);
|
|
CalculateApp.libs.activity.getDatastoreObject().setDataAsText(json);
|
|
CalculateApp.libs.activity.getDatastoreObject().save(function() {});
|
|
},
|
|
|
|
/* Base convert */
|
|
baseConv: function(number, to, from) {
|
|
if (to == from) {
|
|
return number;
|
|
}
|
|
return parseInt(number, from || 10).toString(to);
|
|
},
|
|
|
|
/* Handle window resize */
|
|
onResize: function() {
|
|
CalculateApp.data.windowHeight = getWindowHeight();
|
|
CalculateApp.data.windowWidth = getWindowWidth();
|
|
|
|
setTimeout(function() {
|
|
CalculateApp.elements.resultsZoneDiv.style.height = CalculateApp.elements.calcInputDiv.clientHeight + "px";
|
|
CalculateApp.elements.resultsZoneDiv.style.display = "block";
|
|
}, 300);
|
|
},
|
|
|
|
/* Focus only if computer */
|
|
focus: function() {
|
|
if (!CalculateApp.data.isMobile) {
|
|
CalculateApp.elements.calcInput.focus();
|
|
}
|
|
},
|
|
|
|
/* Translation of the Gui */
|
|
transateGui: function() {
|
|
if (CalculateApp.libs.webL10n.get("calcul") !== undefined && CalculateApp.libs.webL10n.get("calcul").length > 0) {
|
|
CalculateApp.elements.calcInput.placeholder = CalculateApp.libs.webL10n.get("calcul");
|
|
}
|
|
if (CalculateApp.libs.webL10n.get("label") !== undefined && CalculateApp.libs.webL10n.get("label").length > 0) {
|
|
CalculateApp.elements.labelInput.placeholder = CalculateApp.libs.webL10n.get("label");
|
|
}
|
|
if (CalculateApp.libs.webL10n.get("clear") !== undefined && CalculateApp.libs.webL10n.get("clear").length > 0) {
|
|
CalculateApp.elements.calcButtonClear.innerHTML = CalculateApp.libs.webL10n.get("clear");
|
|
}
|
|
},
|
|
|
|
/* We clear the result box and display all previous calculations */
|
|
displayAllCalculations: function() {
|
|
while (CalculateApp.elements.resultsZoneDiv.firstChild) {
|
|
CalculateApp.elements.resultsZoneDiv.removeChild(CalculateApp.elements.resultsZoneDiv.firstChild);
|
|
}
|
|
if(CalculateApp.data.calculations !== null){
|
|
for (var i = 0; i < CalculateApp.data.calculations.length; i++) {
|
|
CalculateApp.displayCalculation(CalculateApp.data.calculations[i]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/* We try to patch the equation to handle things like degree to radians conversion */
|
|
tryPatchEquation: function(equation) {
|
|
if (CalculateApp.data.isRadian) {
|
|
return equation;
|
|
}
|
|
var matchingFunctions = [
|
|
"cos",
|
|
"sin",
|
|
"tan",
|
|
"acos",
|
|
"asin",
|
|
"atan",
|
|
"cosh",
|
|
"sinh",
|
|
"tanh"
|
|
];
|
|
|
|
for (var i = 0; i < matchingFunctions.length; i++) {
|
|
var regex = new RegExp(matchingFunctions[i] + "\\((.+?)\\)", "gi");
|
|
var results = equation.match(regex);
|
|
if (results) {
|
|
for (var j = 0; j < results.length; j++) {
|
|
var parenthesisRegex = new RegExp("\\(([^\\)]+)\\)", "gi");
|
|
var arrayToReplace = parenthesisRegex.exec(results[j]);
|
|
if (arrayToReplace && arrayToReplace.length >= 2) {
|
|
equation = equation.replace(arrayToReplace[1], arrayToReplace[1] + " * " + CalculateApp.data.pi + " / 180");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return equation;
|
|
},
|
|
|
|
/* We try to patch the equation to make the graph working on both full mode and light mode */
|
|
tryPatchGraphEquation: function(question) {
|
|
if (!CalculateApp.isFullMode) {
|
|
question = question.replace("f(x)=", "");
|
|
}
|
|
return question;
|
|
},
|
|
|
|
/* Simple toFixed method to format the output with max digits */
|
|
toFixed: function(value, precision) {
|
|
if (isNaN(value) || (Number(value) === value && value % 1 === 0)) {
|
|
return value;
|
|
}
|
|
var power = Math.pow(10, precision || 0);
|
|
return String(Math.round(value * power) / power);
|
|
}
|
|
};
|