Skip to content

Calculator Example

theRealProHacker edited this page Feb 25, 2023 · 1 revision

In this example, we want to make our own little calculator app. The user should be able to input simple mathematical equations. The result should then be shown continuously.

The basic structure

For this simple application, we only need a single page, so we put a very basic outline of our app into "index.html" directly beside our script.

<div class="container">
    <h1>Calculator</h1>
    <div id="calculator">
        <input id="input" type="text" placeholder="Input">
        
        <input id="result" type="text" placeholder="Result" disabled>
    </div>
</div>

Note: The disabled attribute is not supported yet, but will hopefully be soon

Importing * from positron will import a Positron Prelude: Things you will probably use and which will unlikely cause collisions.
We use set_cwd(__file__) to make sure that the html file can be found regardless of where the Python script is called from.

from positron import *

set_cwd(__file__)
runSync("index.html")

Screenshot 2023-02-18 233645

The result is pretty ugly, and do you notice the title and the icon; that's definitely not what we want.

Style & Customization

To set the title, we use the <title> HTML tag

<title>Calculator</title>

To set the icon, we use the <link> HTML tag

<!-- Calculator icon created by juicy_fish - Flaticon -->
<!-- "https://www.flaticon.com/free-icons/calculator" -->
<link rel="icon" href="calculator.png">

Always remember to attribute people for their work.

As a reminder, this example with the final code can be found in examples/calculator. But you can also choose your own calculator icon

Also, we want to link our own styling. Normally, you would use another <link> element - which you can still do - but IMHO it is much cleaner to use the <style> tag

<style src="style.css"></style>

We then create the new "style.css" file in the same folder as everything else. All file paths are always relative to the current working directory (cwd). I won't go into much detail but I went for a pretty pink tone which I enjoy a lot. So, feel free to change the colors.

html {
    font-family: 'Times New Roman';
}

h1 {
    color: rgb(231, 116, 116);
    font-weight: bold;
}

body {
    height: 100%;
    background-color:rgb(255, 243, 243);
}

#calculator {
    outline: solid black 1px;
    background-color: rgb(212, 178, 178);
}

#calculator input {
    display: block;
    margin: 30px auto;
    width: 80%;
}

#calculator input:focus {
    outline-color: rgb(238, 140, 140);
}

div.container {
    width: 80%;
    margin: 50px auto;
}

As you might notice, margin collapsing isn't quite there yet. Still, the app looks much prettier.
Now we just need to add the functionality.

Screenshot 2023-02-19 191902

Tip: To get rid of the FPS counter at the top left, set positron.config.DEBUG to False

Routes

To add functionality, we use routes. These work pretty much like Flask routes, but you do both the server part and the frontend part at the same time. The default route is "/" which indicates the root of your project.

To bind routes we use Python decorators which allow us a clean syntax.

In the route we first load the dom from "index.html", then we select the input and the result by their ids. I'm assuming that you know a bit about HTML and CSS selectors but "#input" selects the element with an id of input. Here I used SingleJ which selects only the first element it finds.

Then we can bind event handlers to the elements with the same decorator syntax as with the routes. The rest of the code is pretty simple.

We get the value of the input. If it's empty, we also empty the result, else we try to evaluate the result and write it into the result. But we ignore (or suppress) any SyntaxError.

I directly accessed the _elem (the real Element) attribute of the SingleJs. As you might know, the underscore means that this is a private attribute and should not be accessed from user code. However in the near future, you will be able to use the val attribute directly on SingleJ: SingleJ("#input").val.

To calculate the result, I used a package I made myself: math_evaluator. Just pip install it (pip install math_evaluator) or use your own math evaluator.

from contextlib import suppress

from positron import *
from math_evaluator import calc


@route("/")
async def index():
    load_dom("index.html")

    input = SingleJ("#input")
    result = SingleJ("#result")

    @input.on("input")
    def _():
        if not input._elem.value:
            result._elem.value = ""
            return
        with suppress(SyntaxError):
            result._elem.value = str(calc(input._elem.value))


set_cwd(__file__)
runSync()

The final result

We have put pretty little effort into making this app; it was pretty easy. Does it work? Try it out yourself.

Spoiler: it works

Calculator.2023-02-22.22-51-42_Trim.mp4