ypergen

- take a break from javascript

Back to documentation

Form Input elements

Hypergen has support for input, select, textarea and contenteditable elements. They can be used as arguments to callback functions. When the event the callback is attached to happens, the frontend will read their values and send them as arguments to the callback functions.

As an example try to experiment with what this code would send to the server:

inputs = [
    input_(id_="i1", value=1, type_="number"),
    input_(id_="i2", value=2, type_="number", coerce_to=float),
    input_(id_="i3", value=None, type_="number", coerce_to=str),
        ]
i4 = input_(id_="i4", value=4)
i5 = span("Edit me!", contenteditable=True, id_="i5")
button("send to server", onclick=callback(mycallback, inputs, {"myi4": i4}, i5), id_="mycallback-send")

Edit me!

The fact that multiple inputs can be nested into your datastructure of choice and sent to the server allows for very flexible workflows.

class input_(coerce_to=None, js_value_func=None, js_coerce_func=None)

Renders an html5 <input /> tag. select() and textarea() works the same.

coerce_to
Takes the following builtin types: str, int, float.
js_value_func
Se below
js_coerce_func
Se below
Low level value reading and value coercion

Input elements has useful defaults for reading the value of the different input types and to which datatype to coerce the read value. Value reading and value coercion can be overridden by the js_value_func and js_coerce_func kwargs. Their values should exist as a dotted path on the frontend.

These are the default values for js_value_func by element type:

checkbox
hypergen.read.checked
radio
hypergen.read.radio
file
hypergen.read.file
everything else
hypergen.read.value

And similarly for js_coerce_func:

month
hypergen.coerce.month
number
hypergen.coerce.int
range
hypergen.coerce.float
week
hypergen.coerce.week
date
hypergen.coerce.date
time
hypergen.coerce.time
datetime-local
hypergen.coerce.datetime

Check hypergen.js to see their definitions. Should you wish to implement a custom html tag you would probably change these. Otherwise the default settings should be sufficient.

Examples for all HTML5 input types.

Interact with the form elements below to see which data is send to the server.

Input typeCodeRenderedServer callback value
This is the __repr__ representations of the hypergen input elements, which coincidentally looks a lot like python code :)The data received on the server when the relevant onXXX events are triggered.
checkbox
input_(id_="element-0",
       type_="checkbox",
       oninput=callback('/inputs/submit/', THIS, "server-value-0"),
       checked=True)
color
input_(id_="element-1",
       type_="color",
       oninput=callback('/inputs/submit/', THIS, "server-value-1"),
       value="#bb7777")
date
input_(id_="element-2",
       type_="date",
       oninput=callback('/inputs/submit/', THIS, "server-value-2"),
       value=datetime.date(2021, 4, 16))

Takes a datetime. Date, datetime-local, month, time and week fields also accepts the string representation as input value. They will return the python object on the server though.

datetime-local
input_(id_="element-3",
       type_="datetime-local",
       oninput=callback('/inputs/submit/', THIS, "server-value-3"),
       value=datetime.datetime(1941, 5, 5, 5, 23))

Takes a datetime.

email
input_(id_="element-4",
       type_="email",
       oninput=callback('/inputs/submit/', THIS, "server-value-4"),
       value="foo@example.com")
file
input_(id_="element-5",
       type_="file",
       oninput=callback('/inputs/submit/', THIS, "server-value-5"))
hidden
input_(id_="element-6",
       type_="hidden",
       oninput=callback('/inputs/submit/', THIS, "server-value-6"),
       value="hidden")
month
input_(id_="element-7",
       type_="month",
       oninput=callback('/inputs/submit/', THIS, "server-value-7"),
       value={
           'year': 2099,
           'month': 9
       })

Takes a dict with year and month.

number (int)
input_(id_="element-8",
       type_="number",
       oninput=callback('/inputs/submit/', THIS, "server-value-8"),
       value=99)
number (float)
input_(id_="element-9",
       type_="number",
       oninput=callback('/inputs/submit/', THIS, "server-value-9"),
       value=12.12)

Coercing to float here.

password
input_(id_="element-10",
       type_="password",
       oninput=callback('/inputs/submit/', THIS, "server-value-10"),
       value="1234")
radio
input_(id_="element-11",
       type_="radio",
       oninput=callback('/inputs/submit/', THIS, "server-value-11"),
       name="myradio",
       value=20,
       checked=True)

Set the same name for radio groups. Notice the coercion to int.

radio
input_(id_="element-12",
       type_="radio",
       oninput=callback('/inputs/submit/', THIS, "server-value-12"),
       name="myradio",
       value=21)
range
input_(id_="element-13",
       type_="range",
       oninput=callback('/inputs/submit/', THIS, "server-value-13"))
search
input_(id_="element-14",
       type_="search",
       oninput=callback('/inputs/submit/', THIS, "server-value-14"),
       value="Who is Rune Kaagaard?")
tel
input_(id_="element-15",
       type_="tel",
       oninput=callback('/inputs/submit/', THIS, "server-value-15"),
       value="12345678")
text
input_(id_="element-16",
       type_="text",
       oninput=callback('/inputs/submit/', THIS, "server-value-16"),
       value="This is text!")
time
input_(id_="element-17",
       type_="time",
       oninput=callback('/inputs/submit/', THIS, "server-value-17"),
       value=datetime.time(7, 42))

Takes a datetime.time value.

url
input_(id_="element-18",
       type_="url",
       oninput=callback('/inputs/submit/', THIS, "server-value-18"),
       value="https://github.com/runekaagaard/django-hypergen/")
week
input_(id_="element-19",
       type_="week",
       oninput=callback('/inputs/submit/', THIS, "server-value-19"),
       value={
           'year': 1999,
           'week': 42
       })

Takes a dict with year and week.

button
input_(id_="element-20",
       type_="button",
       onclick=callback('/inputs/submit/', THIS, "server-value-20"),
       value="clicked")
image
input_(id_="element-21",
       type_="image",
       onclick=callback('/inputs/submit/', THIS, "server-value-21"),
       src="https://picsum.photos/80/40",
       value="clicked")
reset
input_(id_="element-22",
       type_="reset",
       onclick=callback('/inputs/submit/', THIS, "server-value-22"),
       value="clicked")
submit
input_(id_="element-23",
       type_="submit",
       onclick=callback('/inputs/submit/', THIS, "server-value-23"),
       value="clicked")
textarea
textarea("Who is Jeppe Tuxen?",
         id_="element-24",
         oninput=callback('/inputs/submit/', THIS, "server-value-24"))
select
select([
    option(0, value=0, selected=False),
    option(1, value=1, selected=False),
    option(2, value=2, selected=False),
    option(3, value=3, selected=True),
    option(4, value=4, selected=False)
],
       id_="element-25",
       onclick=callback('/inputs/submit/', THIS, "server-value-25"),
       oninput=callback('/inputs/submit/', THIS, "server-value-25"))
contenteditable
div("Click to edit",
    id_="element-26",
    onkeyup=callback('/inputs/submit/', THIS, "server-value-26"),
    contenteditable=True)
Click to edit

Show sourcesviews.py
# coding = utf-8
# pylint: disable=no-value-for-parameter
d = dict

from hypergen.imports import *
from hypergen.context import context as c
from hypergen.liveview import COERCE, JS_COERCE_FUNCS, JS_VALUE_FUNCS

import datetime
from collections import namedtuple
from django.urls.base import reverse

from yapf.yapflib.yapf_api import FormatCode

from website.templates2 import base_template, show_sources

E = namedtuple("E", 'type,attrs,doc')
E.__new__.__defaults__ = (None,) * 3  # default keyword not in py3.6

@liveview(re_path="^$", perm=NO_PERM_REQUIRED, base_template=base_template)
def inputs(request):
    INPUT_TYPES = [
        E("checkbox", d(checked=True)),
        E("color", d(value="#bb7777")),
        E(
        "date", d(value=datetime.date(2021, 4, 16)),
        "Takes a datetime. Date, datetime-local, month, time and week fields also accepts the string representation as input value. They will return the python object on the server though."
        ),
        E("datetime-local", d(value=datetime.datetime(1941, 5, 5, 5, 23)), "Takes a datetime."),
        E("email", d(value="foo@example.com")),
        E("file", d()),
        E("hidden", d(value="hidden")),
        E("month", d(value=d(year=2099, month=9)), "Takes a dict with year and month."),
        E("number", d(title="number (int)", value=99)),
        E("number", d(title="number (float)", value=12.12, coerce_to=float), "Coercing to float here."),
        E("password", d(value="1234")),
        E("radio", d(name="myradio", value=20, checked=True, coerce_to=int),
        "Set the same name for radio groups. Notice the coercion to int."),
        E("radio", d(name="myradio", value=21, coerce_to=int)),
        E("range", d()),
        E("search", d(value="Who is Rune Kaagaard?")),
        E("tel", d(value="12345678")),
        E("text", d(value="This is text!")),
        E("time", d(value=datetime.time(7, 42)), "Takes a datetime.time value."),
        E("url", d(value="https://github.com/runekaagaard/django-hypergen/")),
        E("week", d(value=d(year=1999, week=42)), "Takes a dict with year and week."),]

    CLICK_TYPES = [
        ("button", d(value="clicked")),
        ("image", d(src="https://picsum.photos/80/40", value="clicked")),
        ("reset", d(value="clicked")),
        ("submit", d(value="clicked")),]

    with p():
        a("Back to documentation", href=reverse("website:documentation"))

    h2("Form Input elements")
    p(
        "Hypergen has support for input, select, textarea and contenteditable elements. They can be used as arguments to ",
        "callback functions. When the event the callback is attached to happens, the frontend will read their values and send them as arguments to the callback functions."
    )
    p("As an example try to experiment with what this code would send to the server:")
    pre(
        code("""
inputs = [
    input_(id_="i1", value=1, type_="number"),
    input_(id_="i2", value=2, type_="number", coerce_to=float),
    input_(id_="i3", value=None, type_="number", coerce_to=str),
        ]
i4 = input_(id_="i4", value=4)
i5 = span("Edit me!", contenteditable=True, id_="i5")
button("send to server", onclick=callback(mycallback, inputs, {"myi4": i4}, i5), id_="mycallback-send")
    """.strip()))
    with p():
        inputs = [
            input_(id_="i1", value=1, type_="number"),
            input_(id_="i2", value=2, type_="number", coerce_to=float),
            input_(id_="i3", value=None, type_="number", coerce_to=str),]
        i4 = input_(id_="i4", value=4)
        i5 = span("Edit me!", contenteditable=True, id_="i5", style={"margin": "4px;"})
        button("send to server", onclick=callback(mycallback, inputs, {"myi4": i4}, i5), id_="mycallback-send")
        span(id_="mycallback-receive")

    p("The fact that multiple inputs can be nested into your datastructure of choice and sent to the server ",
        "allows for very flexible workflows.")

    dl(
        em("class input_(coerce_to=None, js_value_func=None, js_coerce_func=None)"),
        p("Renders an html5 <input /> tag. select() and textarea() works the same."),
        dt("coerce_to"),
        dd('Takes the following builtin types: ', span([x.__name__ for x in list(COERCE.keys())], sep=", "), "."),
        dt("js_value_func"),
        dd("Se below"),
        dt("js_coerce_func"),
        dd("Se below"),
    )

    with details():
        summary("Low level value reading and value coercion")
        p("Input elements has useful defaults for reading the value",
            "of the different input types and to which datatype to coerce the read value.",
            "Value reading and value coercion can be overridden by the js_value_func and js_coerce_func kwargs.",
            "Their values should exist as a dotted path on the frontend.", sep=" ")
        p("These are the default values for js_value_func by element type: ")
        with dl():
            for k, v in list(JS_VALUE_FUNCS.items()):
                dt(k)
                dd(v)
            dt("everything else")
            dd("hypergen.read.value")

        p("And similarly for js_coerce_func:")
        with dl():
            for k, v in list(JS_COERCE_FUNCS.items()):
                dt(k)
                dd(v)
        p("Check hypergen.js to see their definitions. Should you wish to implement a custom html tag you would ",
            "probably change these. Otherwise the default settings should be sufficient.")

    h2("Examples for all HTML5 input types.")
    p("Interact with the form elements below to see which data is send to the server.")

    with table():
        tr(th(x) for x in ["Input type", "Code", "Rendered", "Server callback value"])
        tr(
            td(x) for x in [
            "", "This is the __repr__ representations of the hypergen input elements, which coincidentally"
            " looks a lot like python code :)", "",
            "The data received on the server when the relevant onXXX events are triggered."])

        for i, pair in enumerate(INPUT_TYPES):
            type_, attrs, doc = pair
            title = attrs.pop("title", type_)
            id_ = "server-value-{}".format(i)

            with tr():
                submit_cb = callback(submit, THIS, id_)
                el = input_(id_=("element", i), type_=type_, oninput=submit_cb, **attrs)
                th(title)
                td(pre(code(FormatCode(repr(el))[0])), p(doc) if doc else "")
                td(el)
                td(id_=id_)

        for i, pair in enumerate(CLICK_TYPES):
            i += len(INPUT_TYPES)
            type_, attrs = pair
            title = attrs.pop("title", type_)
            id_ = "server-value-{}".format(i)

            with tr():
                submit_cb = callback(submit, THIS, id_)
                el = input_(id_=("element", i), type_=type_, onclick=submit_cb, **attrs)
                th(title)
                td(pre(code(FormatCode(repr(el))[0])))
                td(el)
                td(id_=id_)

        with tr():
            i += 1
            id_ = "server-value-{}".format(i)
            attrs = d()
            submit_cb = callback(submit, THIS, id_)
            el = textarea("Who is Jeppe Tuxen?", id_=("element", i), oninput=submit_cb, **attrs)
            th("textarea")
            td(pre(code(FormatCode(repr(el))[0])))
            td(el)
            td(id_=id_)

        with tr():
            i += 1
            id_ = "server-value-{}".format(i)
            attrs = d(coerce_to=int)
            submit_cb = callback(submit, THIS, id_)
            el = select([option(x, value=x, selected=x == 3) for x in range(5)], id_=("element", i),
                onclick=submit_cb, oninput=submit_cb, **attrs)
            th("select")
            td(pre(code(FormatCode(repr(el))[0])))
            td(el)
            td(id_=id_)
        with tr():
            i += 1
            id_ = "server-value-{}".format(i)
            submit_cb = callback(submit, THIS, id_)
            el = div("Click to edit", id_=("element", i), onkeyup=submit_cb, contenteditable=True)
            th("contenteditable")
            td(pre(code(FormatCode(repr(el))[0])))
            td(el)
            td(id_=id_)

    show_sources(__file__)

@action(perm=NO_PERM_REQUIRED)
def submit(request, value, target_id):
    c.hypergen = c.hypergen.set("target_id", target_id)
    with pre(style={"padding": 0}):
        raw(repr(value), " (", type(value).__name__, ")")

@action(perm=NO_PERM_REQUIRED, target_id="mycallback-receive")
def mycallback(request, a1, a2, a3):
    span(" Executed on server: ")
    with code():
        raw("mycallback(request, {}, {}, {})".format(repr(a1), repr(a2), repr(a3)))
urls.py
from hypergen.hypergen import autourls
from inputs import views

app_name = 'inputs'
urlpatterns = autourls(views, app_name)