Calculator - Using Enact Framework

February 18, 2022

Overview

This tutorial demonstrates how to use the Enact framework to create a typical calculator app for webOS OSE. Enact is a React-based JavaScript framework optimized for developing web apps for webOS OSE.

The calculator app makes use of the Moonstone theme to compose the user interface (UI) of the app.

The calculator app provides the following features:

  • Basic arithmetic functions
  • Basic math functions
  • Logarithmic functions
  • Trigonometric functions
  • Memory storage functions

You can use this calculator app in the following ways:

  • Install the app as-is on a webOS OSE target device.
  • Update the source code as required and then deploy on a webOS OSE target device.
  • Analyze the source code to understand the usage of different Enact components.

Prerequisites

You must install Node.js version 10 or later. Download and install Node.js from the Node.js website.

Install the Enact command-line interface (CLI) globally by using the npm install command:

$ npm install -g @enact/cli

Also install the webOS OSE CLI as follows:

$ npm install -g @webosose/ares-cli

Source Code

The source code is available at the samples repository. Clone this repository to download the source code on your local development system, and find the com.reference.app.calculator directory under the ref-apps repository.

Analyze the source code to get an understanding of the functionalities implemented in the calculator app. Refer to the snippets provided in this section.

Using Enact UI Components

The below code snippet uses the Moonstone theme on the calculator app.

  • Panels provides a way to manage the different screens of the app. Here, header and calculator components are managed by Panels.
  • Button and Label are mainly used for creating UI components. The calculator app is provided as a sample; it only makes use of such simple UI components.
import { Panel } from "@enact/moonstone/Panels";
...	
    <Panel {...props} style={{ backgroundColor: "black" }}>
      <Header onClose={handleClose}></Header>
      <Calculator />
    </Panel>
...
import Label from "@enact/moonstone/LabeledItem";
import Button from "@enact/moonstone/Button";
...
 <label className={css.result}> {result}</label>
 <Label className={css.history}> {history}</Label>
	...
 <Button onClick={() => onClickHandler(Numbers.ONE)}>{Numbers.ONE}</Button>
 <Button onClick={() => onClickHandler(Numbers.TWO)}>{Numbers.TWO}</Button>
 <Button onClick={() => onClickHandler(Numbers.THREE)}>{Numbers.THREE}</Button>
...

Defining CSS Styles

The following CSS style defines the grid container for the keypad layout:

...	
.keypad {
  display: grid;
  background-color: rgb(172, 172, 172);
  font-family: "digital-clock-font";
  grid-template-columns: repeat(7, 6fr);
  grid-template-rows: repeat(9, 6fr);
  grid-column-gap: 5px;
  grid-row-gap: 10px;
  border: 5px solid #f93881;
  border-radius: 12px;
  padding: 20px;
  zoom: 110%;
}
...

The following CSS style decorates the result and history components for the best appearance of the calculator:

...	
.history {
  .common;
  font-size: 2rem;
  background-color: rgb(153, 153, 153);
  color: whitesmoke;
  font-weight: normal;
  direction: rtl;
}
.result {
  .common;
  font-size: 2.5rem;
  background-color: seashell;
  color: black;
}
...

Using Math.js library

The calculator app uses the Math.js library for parsing and evaluating mathematical expressions used in the calculator app.

import * as mathjs from "mathjs";
...	
const doEval = (expression) => {
  let answer = DISPLAY.ERROR;
  try {
    answer = mathjs.evaluate(expression);
  } catch (err) {
    answer = DISPLAY.ERROR;
  }
  return answer;
};
...

Validating Syntax

The following shows a simple parsing engine that checks whether parentheses on user input are valid. The parser takes the user input and returns an error if the input is invalid.

...
const validateSyntax = (history) => {
  let leftParen = history.indexOf("(");
  let rightParen = history.indexOf(")");
  if (
    (leftParen === -1 && rightParen >= 0) ||
    (rightParen === -1 && leftParen >= 0)
  ) {
    console.log("Parenthesss missing -1 !!!!");
    return -1;
  } else {
    let counter = 0;
    let splitArr = history.split("");
    for (let i = 0; i < splitArr.length; i++) {
      if (splitArr[i] === "(") {
        counter++;
      } else if (splitArr[i] === ")") {
        counter--;
      }

      if (counter === -1) {
        console.log("Parenthesss missing -2 !!!!");
        return -1;
      }
    }
    if (counter < 0) {
      console.log("Parenthesss missing -3 !!!!");
      return -1;
    }
  }
  return 1;
};
...
const doEvalUtil = (history) => {
  if (validateSyntax(history) === -1) {
    return DISPLAY.INVALID;
  } else if (history !== undefined && history.length > 0) {
    if (history.includes("--")) {
      history = history.replace("--", "+");
    }
    if (history.includes("%")) {
      history = history.replace("%", "/100");
    }
    return doEval(history);
  }
};
...

Installing the App on the Target Device

Go to the app directory of the downloaded source code and execute the following commands:

  1. Package the enact source code.

    $ enact pack  
    

    A directory named dist is created.

  2. Package the app to create an IPK.

    $ ares-package dist
    

    An IPK named com.lgsi.app.calculator_1.0.1_all.ipk is created.

  3. Install the IPK on the target device.

    $ ares-install --device <TARGET_DEVICE> com.lgsi.app.calculator_1.0.1_all.ipk
    

Contents