Skip to main content

Library for building dynamic forms

Project description

opengui

Library for building dynamic forms, forms whose options and even fields change based on values in other fields.

This doesn't involve rendering dynamic forms, just making creating and altering the structures thereof.

GUI Example

Some Python code in service.py that generate some basic field dynamically

# Create a single multi select field

fields = opengui.Fields(
    values=values,
    fields=[
        {
            "name": "types",
            "options": [
                "textarea",
                "options",
                "fields"
            ],
            "multi": True,
            "trigger": True
        }
    ]
)

# If they select textarea, add it

if "textarea" in (fields["types"].value or []):
    fields.append({
        "name": "people",
        "style": "textarea"
    })
    fields.ready = True

# If they selected option, add a format, then check what format they selected

if "options" in (fields["types"].value or []):
    fields.append({
        "name": "style",
        "options": [
            "radios",
            "select"
        ],
        "default": "radios",
        "trigger": True
    })
    fields.append({
        "name": "stuff",
        "options": [
            "fee",
            "fie",
            "foe",
            "fum"
        ],
        "style": fields["style"].value
    })
    fields.ready = True

# If they add subfields, add two, and make the second optional

if "fields" in (fields["types"].value or []):
    fields.append({
        "name": "things",
        "fields": [
            {
                "name": "yin",
            },
            {
                "name": "yang",
                "optional": True
            }
        ]
    })
    fields.ready = True

# Retrn as a dict

return fields.to_dict(), 200

Universal doTRoute.js form.html to display a dynamic form:

{{?it.message}}
<div class="uk-alert uk-alert-success">
    {{=it.message}}
</div>
{{?}}
{{?it.errors && it.errors.length}}
<div class="uk-alert uk-alert-danger">
    {{~it.errors :error}}
        {{=error}}<br/>
    {{~}}
</div>
{{?}}
<form class="uk-form uk-form-horizontal">
    {{= DRApp.templates.Fields(it) }}
    <br/>
</form>

Universal doTRoute.js fields.html to display all sorts of combinations, including sub fields:

{{~it.fields :field}}
    {{ var prefix = it.prefix || []; }}
    {{ var full_name = prefix.concat(field.name).join('-').replace(/\./g, '-'); }}
    {{ var value = field.value || field.default || (field.multi ? [] : ''); }}
    {{ var readonly = field.readonly || it.readonly; }}
    {{?field.fields || field.name == "yaml"}}
    <div class="uk-form-row"><hr/></div>
    {{?}}
    <div class="uk-form-row">
        <label class="uk-form-label" for="{{=field.name}}"><strong>{{=field.label || field.name}}</strong></label>
        <div class="uk-form-controls">
    {{?field.style == "textarea"}}
        {{?readonly}}
            {{?field.name == "yaml"}}<pre>{{?}}{{=value}}{{?field.name == "yaml"}}</pre>{{?}}
        {{??}}
            <textarea
                rows='7' cols='42'
                id="{{!full_name}}"
                placeholder="{{!field.label || field.name}}"
                {{?field.trigger}}OnInput="DRApp.current.controller.fields_change();"{{?}}
            >{{=value}}</textarea>
        {{?}}
    {{??field.style == "select" && !readonly}}
            <select id="{{!full_name}}" {{?field.trigger}}OnChange="DRApp.current.controller.fields_change();"{{?}}>
        {{?field.optional}}
                <option value=''></option>
        {{?}}
        {{~field.options :option}}
                <option value='{{!option}}' {{?value == option}}selected{{?}}>
                    {{= field.labels ? field.labels[option] : option}}
                </option>
        {{~}}
            </select>
    {{??field.options}}
        {{?readonly}}
            {{?field.multi}}
                {{~value :option}}
            {{= field.labels ? field.labels[option] : option}}<br/>
                {{~}}
            {{??}}
            {{= field.labels ? field.labels[value] : value}}<br/>
            {{?}}
        {{??}}
            {{~field.options :option}}
            <input
                value="{{!option}}"
            {{?field.multi}}
                type="checkbox" name="{{!full_name}}"
                {{?value.indexOf(option) > -1}}checked{{?}}
            {{??}}
                type="radio" name="{{!full_name}}"
                {{?value == option}}checked{{?}}
            {{?}}
                {{?field.trigger}}OnClick="DRApp.current.controller.fields_change();"{{?}}
            />
            {{= field.labels ? field.labels[option] : option}}<br/>
            {{~}}
        {{?}}
    {{??!field.fields}}
        {{?readonly}}
            {{= field.style == "datetime" ? (new Date(value*1000)).toLocaleString() : value}}
        {{??}}
            <input
                id="{{!full_name}}"
                placeholder="{{!field.label || field.name}}"
                value="{{!value}}"
                {{?field.trigger}}OnInput="DRApp.current.controller.fields_change();"{{?}}
                type="text"
            />
        {{?}}
            <br/>
    {{?}}
    {{?field.errors}}
            <span class='uk-form uk-text-danger'>
        {{~field.errors :error}}
                {{=error}}
        {{~}}
            </span>
    {{?}}
    {{?field.description}}
            <dfn>{{=field.description.replace(/\n/g, "<br/>")}}</dfn><br/>
    {{?}}
    {{?field.link}}
        {{ var links = Array.isArray(field.link) ? field.link : [field.link]; }}
        {{~links: link}}
            <a href="{{!link.url || link}}" target="{{!link.target || '_blank'}}">{{=link.name || link.url || link}}</a><br/>
        {{~}}
    {{?}}
        </div>
    </div>
    {{?field.fields}}
        {{= DRApp.templates.Fields({fields: field.fields, readonly: readonly, prefix: prefix.concat(field.name)}) }}
    {{?}}
{{~}}

Which looks like this:

OpenGUI Exmaple

If you're familer with Docker Desktop, Kubernetes, and Tilt make up and hit space.

Navigate to http://localhost:7971/ and give it a whirl.

Cli Example

Just took this from the from the unittests:

@unittest.mock.patch("builtins.print")
@unittest.mock.patch("builtins.input")
def test_ask(self, mock_input, mock_print):

    cli = opengui.Cli(
        fields=[
            {
                "name": "basic",
                "description": "be basic",
                "default": "badass",
                "validation": "^bitch$"
            },
            {
                "name": "single",
                "options": ["yin", "yang"],
                "labels": {
                    "yin": "Yin",
                    "yang": "Yang"
                },
                "default": "yin"
            },
            {
                "name": "multiple",
                "multi": True,
                "options": "{[ fs ]}",
                "default": ["fun", "foe"]
            },
            {
                "name": "yah",
                "bool": True,
                "default": True
            },
            {
                "name": "sure",
                "bool": True
            },
            {
                "name": "nah",
                "bool": True
            }
        ],
        values={
            "fs": ["fee", "fie", "foe", "fum"]
        }
    )

    mock_input.side_effect = [
        "",
        "bitch",
        "fish",
        "0",
        "3",
        "1",
        "fish 0 6",
        "",
        "1 3",
        "",
        "y",
        "n"
    ]


    self.assertEqual(cli.ask(), {
        "basic": "bitch",
        "single": "yin",
        "multiple": ["fee", "foe"],
        "yah": True,
        "sure": True,
        "nah": False,
        "fs": ["fee", "fie", "foe", "fum"]
    })

    mock_print.assert_has_calls([
        unittest.mock.call('  be basic'),
        unittest.mock.call("must match '^bitch$'"),
        unittest.mock.call('[1] Yin'),
        unittest.mock.call('[2] Yang'),
        unittest.mock.call('invalid choice: fish'),
        unittest.mock.call('[1] Yin'),
        unittest.mock.call('[2] Yang'),
        unittest.mock.call('invalid choice: 0'),
        unittest.mock.call('[1] Yin'),
        unittest.mock.call('[2] Yang'),
        unittest.mock.call('invalid choice: 3'),
        unittest.mock.call('[1] Yin'),
        unittest.mock.call('[2] Yang'),
        unittest.mock.call('[1] fee'),
        unittest.mock.call('[2] fie'),
        unittest.mock.call('[3] foe'),
        unittest.mock.call('[4] fum'),
        unittest.mock.call("invalid choices: ['fish', '0', '6']"),
        unittest.mock.call('[1] fee'),
        unittest.mock.call('[2] fie'),
        unittest.mock.call('[3] foe'),
        unittest.mock.call('[4] fum'),
        unittest.mock.call("invalid values ['fun']"),
        unittest.mock.call('[1] fee'),
        unittest.mock.call('[2] fie'),
        unittest.mock.call('[3] foe'),
        unittest.mock.call('[4] fum')
    ])

    mock_input.assert_has_calls([
        unittest.mock.call('basic: '),
        unittest.mock.call('enter index - single: '),
        unittest.mock.call('enter index - single: '),
        unittest.mock.call('enter index - single: '),
        unittest.mock.call('enter index - single: '),
        unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),
        unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),
        unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),
        unittest.mock.call('enter value y/n - yah: '),
        unittest.mock.call('enter value y/n - sure: '),
        unittest.mock.call('enter value y/n - nah: ')
    ])

And fields use yaes.Engine for transformaton each time a question is asked:

fields = opengui.Fields(
    fields=[
        {"name": "a", "label": "{{ lab }}", "stuff": "{[ things ]}"},
        {"name": "b"}
    ],
    errors=['boo'],
    valid=True,
    ready=False
)

values = {"lab": "A", "things": [1, 2, 3]}

self.assertEqual(fields.question(values).to_dict(), {
    "name": "a",
    "label": "A",
    "stuff": [1, 2, 3]
})

Documentation

It's lacking at this point, but check out the tests to see what you can do.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opengui-0.9.7.tar.gz (11.4 kB view details)

Uploaded Source

Built Distribution

opengui-0.9.7-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

Details for the file opengui-0.9.7.tar.gz.

File metadata

  • Download URL: opengui-0.9.7.tar.gz
  • Upload date:
  • Size: 11.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.8.5

File hashes

Hashes for opengui-0.9.7.tar.gz
Algorithm Hash digest
SHA256 1905bdb8e061a4f85101b4f3426cdf447e94e5f3626384c8bc69ec006c986f31
MD5 537e77008f1cd9e09386b0e1008e5c45
BLAKE2b-256 86a8e8e14f79c176c833a76e3c2fd010d6e2bfef19808fa74ab4b2368a6bafda

See more details on using hashes here.

File details

Details for the file opengui-0.9.7-py3-none-any.whl.

File metadata

  • Download URL: opengui-0.9.7-py3-none-any.whl
  • Upload date:
  • Size: 11.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.1 CPython/3.8.5

File hashes

Hashes for opengui-0.9.7-py3-none-any.whl
Algorithm Hash digest
SHA256 89d2642489f9e5dfd2ed5ff94e1f9e3b7bdf99796acdc39f65c6a6c9cfe7115f
MD5 d16130362339417c7d613f9265b841c1
BLAKE2b-256 0e26bc90a5334eb16760fdcfe9b07413161e185e4b16b1edc9b79b7b106838b0

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page