Calculadora

Ayuda

 
  
 

Calculadora con módulos calc.js y calcui.js

Figura
Figura. Calculadora básica

Esta aplicación es una calculadora que tiene por objeto probar el funcionamiento del módulo calc.js, cuya finalidad es ser un motor de cálculo de expresiones. Permite ese módulo calcular expresiones usando una pila con métodos de Array, dado que es la estructura de pila la que utiliza el algoritmo para transformar una expresión infija en notación polaca inversa (RPN).

En el enlace anterior del tema sobre la pila con los métodos push() y pop() de Array, expuse un sencillo ejemplo para implementar una pila con la que convertir y ejecutar RPN. Basado en ese planteamiento inicial decidí ampliar su funcionalidad para convertirlo finalmente en el módulo calc.js.

Una expresión matemática infija es algo como 1 + 2, mientras que su transformación en RPN es 1 2 +. En esta notación el operador le sigue a dos operandos, mientras que en infija el operador está entre los dos operandos. La notacion RPN es, a su vez, más cómoda para calcular en un programa porque se prescinden de los paréntesis. La transformación infijo-RPN y el posterior cálculo del RPN se realizan ambos usando pilas. La transformación infijo a RPN se consigue con el algoritmo de shuting yard inventado por Dijkstra.

Por otra parte el módulo calcui.js se encarga de construir una interfaz para generar y manejar esta aplicación calculadora. Dispone de varias vistas que pueden modificarse desde el botón de configuración :

  • Estándar, con las funcionalidades de una cálculadora básica (Figura).
  • Científica, con funciones matemáticas, siendo esta vista la que hemos usado al iniciar la aplicación.
  • Estadística, con funciones básicas como cálculo de media, media acotada, mediana, moda, números aleatorios y otras.
  • Estructural, con funciones para leer y escribir registros, condicionales de ejecución y otras funciones que modifican estructuras de datos.
  • Texto, con funciones como convertir mayúsculas o minúsculas; capitalizar; ordenar, buscar y filtrar una lista; reemplazar texto.
  • Estilo, para generar código HTML de estilo negrita, itálica, color, etc. Además dispone de la función chart() que permite generar una gráfica lineal con SVG.
  • Fecha, para generar la fecha actual, una hoja de calendario de un mes, diferencia en días entre dos fechas, sumas días a una fecha, obtener el número de semana, etc.
Figura
Figura. Configuración de la calculadora

Hay otras cosas que podemos modificar con el botón de configuración (Figura). El valor num es el número identificativo de la instancia de la interfaz, valor que se establece en el momento de crear dicha instancia y no puede modificarse posteriormente.

Los valores de la configuración se explican a continuación:

  • border (borde): Declarar un borde para la calculadora.
  • view (vista): Cambiar la vista standard, scientific, statistics, structural, text, style, date (estándar, científica, estadística, estructural, texto, estilo, fecha).
  • fontSize (tamaño fuente): Especificando la fuente entre 0.6em y 4em cambiamos el tamaño total de la calculadora.
  • width (ancho): El ancho de la calculadora, puede ser "auto" o un valor como "100%", "400px" o "20em". Con valor "auto" el ancho se fija al número de botones horizontales de cada vista. Con otro valor ajustarán las áreas de texto de fórmula y resultado a ese ancho. Con "100%" se ajustará al ancho del contenedor.
  • modeErrorMessage (modo mensajes error): Los mensajes de error pueden mostrarse en el resultado (con "html") o como un mensaje del navegador (con "alert"). El valor "function" hará que una función externa maneje la presentación, pero si no se declaró esa función cuando se creó la calculadora usará "alert".
  • msgErrorShort (mensaje de error corto): Activado mostrará un mensaje corto, desactivado mostrará también las funciones del módulo donde se produjo el error.
  • maxUndo (máximas acciones a deshacer): Por defecto se establecen 50 acciones.
  • autoInsert (auto insertar): El resultado de los cálculos se muestra en la segunda área de texto de fondo azul. Si activamos esta opción, tras calcular con el botón "=" insertará el resultado en el área de texto donde se escribe la fórmula a calcular. En cualquier caso siempre podemos insertar el resultado con el botón .
  • hideResult (ocultar resultado): Para ocultar el panel de resultado con fondo azul. Si se activa esta opción se activará automáticamente la opción anterior, de tal forma que el resultado de cada operación se insertará en el área de fórmula.
  • accumulate (usar acumulador): Con esta opción el resultado de una operación se va sumando al panel de resultado. Puede también actuarse sobre esta configuración con el botón .
  • keyboard (usar teclado): Puede escribir una expresión usando el teclado en pantalla de la aplicación y al mismo tiempo el teclado del dispositivo escribiendo directamente en el área de texto. Cuando se abre la aplicación en dispositivos de pequeño tamaño como smartphones, es preferible desactivar el teclado del dispositivo pues oculta la aplicación. Puede también actuarse sobre esta configuración con el botón .
  • wrap (ajustar área texto resultado): Ajusta o no el texto al área de texto del resultado. Por defecto se ajusta (valor true).

Ejecutando la función ui() devolverá una representación JSON de la configuración, donde las comillas dobles se sustituyen por simples. Obtendríamos algo como esto, donde los valores num y lang (que define el idioma de la interfaz) se establecen al crear la interfaz y no pueden modificarse posteriormente:

{'num': 0,
'lang': 'es',
'config': {
    'view': 'standard',
    'border': 'gray solid 1px',
    'width': 'auto',
    'fontSize': '1em',
    'modeErrorMessage': 'html',
    'msgErrorShort': true,
    'maxUndo': 50,
    'autoInsert': false,
    'hideResult': false,
    'wrap': true
  }
}

Carga e iniciar el módulo calc.js y su vinculación optativa con chart.js

El módulo calc.js es una instancia única en la página y funciona independientemente del otro módulo calcui.js que explicaremos en el siguiente apartado. Sin embargo podemos optativamente vincular a calc.js otro módulo para crear gráficas lineales con SVG. Veámos como cargar calc.js y chart.js:

<script src="/res/inc/chart.js" async></script>
<script src="/res/inc/calc.js" async></script>

Estos módulos tienen una estructura de carga como la siguiente, donde CALC_JS es una envoltura para un cargador de módulos que estoy usando en este sitio. Pero aquí lo expondremos directamente sin usar ese cargador. Wextensible es una variable global a modo de espacio de nombres:

var Wextensible = Wextensible || {};
Wextensible.CALC_JS = function(){
    Wextensible.iniciarCalc = function(chartModule){
        const version = "08/07/2019";
        ...
        function calcular(expresion=""){
            ...
        }
        ...
        return {
            iniciado: true,
            valores,
            constantes,
            calcular,
            generarHtmlSelect,
            obtenerTipo,
            help,
            keysIconos,
            specialLists
        };
    };
};

Primero cargamos todas las envolturas en cualquier orden pues no hay interdepencias entre ellas:

Wextensible.CHART_JS();
Wextensible.CALC_JS();

Y a continuación ejecutamos los iniciadores en un orden específico, pues necesitamos que chart esté cargado e iniciado previamente:

wxL.chart = Wextensible.startChart();
wxL.calc = Wextensible.iniciarCalc(wxL.chart);

wxL es simplemente un acortador para Wextensible.local = {}, un objeto donde guardar cosas locales a la página. Finalmente en wxL.calc tendremos un objeto con las variables y métodos como la función calcular(). Podemos utilizar la función chart(config, numSeries, datos) para crear una gráfica lineal:

let formula = 
`chart("{'svgWidth': 250, 'svgType': 'line', 'xAxisRotation': 0}", 3,
todata("Meses	Serie 1	Serie 2	Serie 3
Enero	40.3	50	25
Febrero	90	65.4	33
Marzo	100	78	22.1"))`;
let dev = wxL.calc.calcular(formula);
let res = dev.error ? dev.error : dev.resultado.replace(/^"(.*)"$/, "$1");
document.getElementById("conten-chart-example").innerHTML = res;

Hemos usado la función todata(texto) que convierte una tabla tabulada en "tsv" (tab separated values) en el formato de datos que el módulo puede manejar. El elemento conten-chart-example siguiente contiene la gráfica en SVG:

Ejemplo: Usando el módulo chart.js con calc.js

La función calcular() del módulo calc devuelve un objeto {error, resultado}. Si hay error tendremos el mensaje de error en esa variable. El resultado se devuelve encerrado entre comillas dobles si es un tipo String, como es el caso del código SVG. Así que le quitamos esas comillas para insertarlo con innerHTML.

Módulo interfaz calcui.js con múltiples instancias

El módulo calcui.js permite construir múltiples instancias en la misma página. La primera instancia que usa la calculadora principal de esta página la hemos creado con esto:

let constructor = Wextensible.iniciarCalcui();
let ui = constructor.createUi({
    location: document.querySelector(`#calc0`),
    lang: "es",
    calcModule: wxL.calc,
    formula: "info()",
    config: {view: "scientific", width: "100%", border: "gray solid 1px"}

});

Pasamos la referencia del elemento con id #calc0 para insertar ahí la interfaz. Pasamos también la referencia al módulo wxL.calc que iniciamos previamente. El idioma a usar en la interfaz es español ("es"). Como fómula inicial ponemos la función info(). La vista inicial es la científica. El ancho 100% hará que se expandan las áreas de texto de fórmula y resultado hasta ocupar el total disponible.

A continuacion crearemos una segunda instancia. Con el siguiente botón puede abrirse una segunda calculadora en una ventana emergente:

Para ello usamos un formulario emergente para crear una ventana. Pasamos la referencia a un elemento en el interior del formulario al que podemos acceder con wxL.calcwin.form("Interior"), elemento donde crearemos la interfaz.

wxL.calcwin = new Wextensible.FormEmerge("wxL.calcwin","Calc2",false,0);
let ui2 = constructor.createUi({
    location: wxL.calcwin.form("Interior"),
    lang: "es",
    calcModule: wxL.calc,
    config: {view: "standard", hideResult: true, fontSize: "0.8em"}
});
function abrirCalc2(event){
    let elemento = event.target;
    let left = elemento.offsetLeft + elemento.offsetWidth;
    let top = elemento.offsetTop;
    wxL.calcwin.abrir("", "", left, top);
}
document.getElementById("botoncalc").addEventListener("click",abrirCalc2);

La función abrirCalc2() se adjudica al botón anterior. Usará el método abrir() de FormEmerge para abrir esta segunda calculadora junto al botón.