Rapid Prototyping with Flask, Bootstrap and Secutio

mrhdias

Henrique Dias

Posted on January 30, 2024

Rapid Prototyping with Flask, Bootstrap and Secutio

Introduction

Secutio is a project in the early stages of development with the goal of simplifying the creation of dynamic content in a straightforward manner. The vision is that anyone should be able to build a web application using only HTML, stylesheets, and a JSON structure where "tasks" are specified.

It easy update parts of a page and create a Single-page Application (SPA):


Try opening the network tab in the browser's dev tools. When the button is clicked, a GET request is sent to the https://v2.jokeapi.dev/joke/Any?format=txt&safe-mode endpoint. The response is then appended to the HTML element with the "id" of "output".

Demo Description

The example outlined in this article mimics the "click to edit" functionality found in the htmx framework. Similar to htmx, this framework offers a method to enable inline editing of all or part of a record without the need for a page refresh.

Click To Edit Demo

Secutio enables each HTML element to have one or more unique actions associated with it, referred to here as tasks. Let's expand the idea of actions, similar to those found in HTML forms, to include any HTML element. When an event is triggered on an HTML element with an associated task, it executes the defined action, enabling the creation of localized dynamic content without the need to reload the entire page.

Key features include:

  • Tasks associated with any HTML element;
  • Multiple tasks can be defined per element, as long as they are associated with different events;
  • Tasks specified in a file, similar to how styles are specified in a CSS file;
  • Inline task definitions, similar to inline styles;
  • Templates (embedded or loaded) using JavaScript Template Literals;
  • Client-side rendering of data via JSON content or static JSON files;
  • Support for server-side templates, such as the Jinja template library;
  • Definition of transformations and targets on the frontend or backend through an HTTP custom header.

And there's much more to come...

Setup the Project

The "click to edit" example is a page that enables the editing of an online contact without requiring a page refresh, in contrast to the process when using only HTML and submitting a form.

To make the demo more interesting, we will use the Bootstrap framework and Flask as the backend.

A simple project structure:

├── click-to-edit/
│   ├── public/
│   │   ├── css/
│   │   │   ├── styles.css
│   │   ├── index.html
│   │   ├── tasks.json
└── server.py
Enter fullscreen mode Exit fullscreen mode

The example consists of a single HTML page (index.html) with two embedded templates, "details-contact-tpl" and "edit-contact-tpl". The HTML content to render must be within the "script" tag with type "text/template". Within the "head" element, there is an HTML "script" element with the "data-tasktable" attribute and a "src" attribute specifying the file path for the "tasks" file. The JavaScript library, 'secutio.js,' responsible for processing tasks and replacing variables in templates, is then added at the end.

<!doctype html>
<html lang="en">

<head>
    <title>Click To Edit</title>
    <link rel="icon" type="image/svg+xml" href="img/favicon.svg">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="Cache-control" content="no-cache">
    <meta name="author" content="Henrique Dias">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
        integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    <link rel="stylesheet" href="css/styles.css?2024011401">
    <script data-tasktable type="application/json" src="tasks.json"></script>
</head>

<body>

    <div id="details-contact" data-tasks="show-contact"></div>

    <!-- embedded templates -->

    <script id="details-contact-tpl" type="text/template">
        <div id="details-contact">
            <div class="mb-3"><label>First Name</label>: ${data.firstname}</div>
            <div class="mb-3"><label>Last Name</label>: ${data.lastname}</div>
            <div class="mb-3"><label>Email</label>: ${data.email}</div>
            <button data-tasks="edit-contact" data-task-action="/contact/${data.id}/edit"
                class="btn btn-outline-primary btn-sm">
                Click To Edit
            </button>
        </div>
        <script type="text/javascript">
            console.log('Yes work!');
        </script>
    </script>

    <script id="edit-contact-tpl" type="text/template">
        <form id="details-contact" data-tasks="save-contact" data-task-action="/contact/${data.id}/save">
            <input type="hidden" name="id" value="${data.id}">
            <div class="mb-3">
                <label for="firstname" class="form-label">First Name</label>
                <input class="form-control" id="firstname" type="text" name="firstname" value="${data.firstname}"
                    placeholder="First Name" required>
            </div>
            <div class="mb-3">
                <label for="lastname" class="form-label">Last Name</label>
                <input class="form-control" id="lastname" type="text" name="lastname" value="${data.lastname}"
                    placeholder="Last Name" required>
            </div>
            <div class="mb-3">
                <label for="email" class="form-label">Email Address</label>
                <input class="form-control" id="email" type="email" name="email" value="${data.email}"
                    placeholder="name@example.com" required>
            </div>
            <div>
                <button class="btn btn-outline-primary btn-sm">Submit</button>
                <button class="btn btn-outline-primary btn-sm" data-tasks="show-contact"
                    data-task-trigger="click">Cancel</button>
            </div>
        </form>
    </script>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
        crossorigin="anonymous"></script>

    <script type="text/javascript"
        src="https://cdn.jsdelivr.net/gh/mrhdias/secutio@master/dist/js/secutio.min.js"></script>
    <script>
        const app = new Secutio();
        app.init();
    </script>

</body>

</html>
Enter fullscreen mode Exit fullscreen mode

On the HTML page, we can observe several attributes defining tasks: data-tasks="show-contact", data-tasks="edit-contact", and data-tasks="save-contact". The properties of these tasks are specified in the JSON file named "tasks.json", which is shown below.

The "details-contact-tpl" template displays the details of a contact. The associated div contains a button that, when pressed, triggers the "edit-contact" task to retrieve the editing form ("edit-contact-tpl" template) UI for the contact. The form has a "save-contact" task that issues a PUT request to /contact/1, following the usual RESTful pattern.

{
    "show-contact": {
        "action": "/contact/1",
        "method": "get",
        "attribute-trigger": "data-task-trigger",
        "trigger": "init",
        "target": "#details-contact",
        "template": "#details-contact-tpl",
        "swap": "outer"
    },
    "edit-contact": {
        "attribute-action": "data-task-action",
        "method": "get",
        "trigger": "click",
        "target": "#details-contact",
        "template": "#edit-contact-tpl",
        "swap": "outer",
        "before": "remove-script"
    },
    "save-contact": {
        "attribute-action": "data-task-action",
        "method": "put",
        "trigger": "submit",
        "target": "#details-contact",
        "template": "#details-contact-tpl",
        "swap": "outer"
    },
    "remove-script": {
        "selector": "#details-contact + script",
        "remove": {}
    }
}
Enter fullscreen mode Exit fullscreen mode

The explanations for each attribute and task property can be found in the project documentation (which is still incomplete).

The Flask framework (Python) was employed to create the HTTP server that serves JSON content, rendering the contact on the client side in accordance with the RESTful pattern.

#!/usr/bin/python
#
# https://flask.palletsprojects.com/en/3.0.x/installation/
#

from flask import Flask, jsonify, request

contacts = [
    {
        "id":        "1",
        "firstname": "Lorem",
        "lastname":  "Ipsum",
        "email":     "lorem.ipsum@example.com",
    },
    {
        "id":        "2",
        "firstname": "Mauris",
        "lastname":  "Quis",
        "email":     "mauris.quis@example.com",
    },
    {
        "id":        "3",
        "firstname": "Donec Purus",
        "lastname":  "Purus",
        "email":     "donec.purus@example.com",
    }
]

app = Flask(__name__,
            static_url_path='',
            static_folder='public',)

@app.route("/contact/<int:id>/save", methods=["PUT"])
def save_contact(id):
    data = request.json
    contacts[id - 1] = data

    return jsonify(contacts[id - 1])

@app.route("/contact/<int:id>", methods=["GET"])
@app.route("/contact/<int:id>/edit", methods=["GET"])
def get_contact(id):
    return jsonify(contacts[id - 1])

@app.route('/')
def root():
    return app.send_static_file('index.html')

if __name__ == '__main__':
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

Conclusion

The concept of "styelization" of "tasks", akin to CSS styles, enables the dynamic creation of content without cluttering HTML elements with excessive attributes. It also facilitates the creation of a cascade of tasks that can be applied before and after content is swapped in the target HTML element, in a straightforward and practical manner.


The source code for this demo is available here: click to edit

Thank you for reading!

💖 💪 🙅 🚩
mrhdias
Henrique Dias

Posted on January 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related