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](api/lig/service.py) that generate some basic field dynamically
```python
# 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](http://gaf3.github.io/dotroute/) [form.html](gui/www/form.html) to display a dynamic form:
```html
{{?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](http://gaf3.github.io/dotroute/) [fields.html](gui/www/fields.html) to display all sorts of combinations, including sub fields:
```html
{{~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](example.png)
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:
```python
@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](https://yaes.readthedocs.io/en/latest/) for transformaton each time a question is asked:
```python
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](test_opengui.py) to see what you can do.
Raw data
{
"_id": null,
"home_page": "https://opengui.readthedocs.io/en/0.9.8/",
"name": "opengui",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": null,
"author": "Gaffer Fitch",
"author_email": "opengui@gaf3.com",
"download_url": "https://files.pythonhosted.org/packages/7e/3b/e0576c87155ab02cee826f16d305786b58b93ad314d39899ca1b69d11483/opengui-0.9.8.tar.gz",
"platform": null,
"description": "opengui\n=======\n\nLibrary for building dynamic forms, forms whose options and even fields change based on values in other fields.\n\nThis doesn't involve rendering dynamic forms, just making creating and altering the structures thereof.\n\n# GUI Example\n\nSome Python code in [service.py](api/lig/service.py) that generate some basic field dynamically\n\n```python\n\n# Create a single multi select field\n\nfields = opengui.Fields(\n values=values,\n fields=[\n {\n \"name\": \"types\",\n \"options\": [\n \"textarea\",\n \"options\",\n \"fields\"\n ],\n \"multi\": True,\n \"trigger\": True\n }\n ]\n)\n\n# If they select textarea, add it\n\nif \"textarea\" in (fields[\"types\"].value or []):\n fields.append({\n \"name\": \"people\",\n \"style\": \"textarea\"\n })\n fields.ready = True\n\n# If they selected option, add a format, then check what format they selected\n\nif \"options\" in (fields[\"types\"].value or []):\n fields.append({\n \"name\": \"style\",\n \"options\": [\n \"radios\",\n \"select\"\n ],\n \"default\": \"radios\",\n \"trigger\": True\n })\n fields.append({\n \"name\": \"stuff\",\n \"options\": [\n \"fee\",\n \"fie\",\n \"foe\",\n \"fum\"\n ],\n \"style\": fields[\"style\"].value\n })\n fields.ready = True\n\n# If they add subfields, add two, and make the second optional\n\nif \"fields\" in (fields[\"types\"].value or []):\n fields.append({\n \"name\": \"things\",\n \"fields\": [\n {\n \"name\": \"yin\",\n },\n {\n \"name\": \"yang\",\n \"optional\": True\n }\n ]\n })\n fields.ready = True\n\n# Retrn as a dict\n\nreturn fields.to_dict(), 200\n```\n\nUniversal [doTRoute.js](http://gaf3.github.io/dotroute/) [form.html](gui/www/form.html) to display a dynamic form:\n\n```html\n{{?it.message}}\n<div class=\"uk-alert uk-alert-success\">\n {{=it.message}}\n</div>\n{{?}}\n{{?it.errors && it.errors.length}}\n<div class=\"uk-alert uk-alert-danger\">\n {{~it.errors :error}}\n {{=error}}<br/>\n {{~}}\n</div>\n{{?}}\n<form class=\"uk-form uk-form-horizontal\">\n {{= DRApp.templates.Fields(it) }}\n <br/>\n</form>\n```\n\nUniversal [doTRoute.js](http://gaf3.github.io/dotroute/) [fields.html](gui/www/fields.html) to display all sorts of combinations, including sub fields:\n\n```html\n{{~it.fields :field}}\n {{ var prefix = it.prefix || []; }}\n {{ var full_name = prefix.concat(field.name).join('-').replace(/\\./g, '-'); }}\n {{ var value = field.value || field.default || (field.multi ? [] : ''); }}\n {{ var readonly = field.readonly || it.readonly; }}\n {{?field.fields || field.name == \"yaml\"}}\n <div class=\"uk-form-row\"><hr/></div>\n {{?}}\n <div class=\"uk-form-row\">\n <label class=\"uk-form-label\" for=\"{{=field.name}}\"><strong>{{=field.label || field.name}}</strong></label>\n <div class=\"uk-form-controls\">\n {{?field.style == \"textarea\"}}\n {{?readonly}}\n {{?field.name == \"yaml\"}}<pre>{{?}}{{=value}}{{?field.name == \"yaml\"}}</pre>{{?}}\n {{??}}\n <textarea\n rows='7' cols='42'\n id=\"{{!full_name}}\"\n placeholder=\"{{!field.label || field.name}}\"\n {{?field.trigger}}OnInput=\"DRApp.current.controller.fields_change();\"{{?}}\n >{{=value}}</textarea>\n {{?}}\n {{??field.style == \"select\" && !readonly}}\n <select id=\"{{!full_name}}\" {{?field.trigger}}OnChange=\"DRApp.current.controller.fields_change();\"{{?}}>\n {{?field.optional}}\n <option value=''></option>\n {{?}}\n {{~field.options :option}}\n <option value='{{!option}}' {{?value == option}}selected{{?}}>\n {{= field.labels ? field.labels[option] : option}}\n </option>\n {{~}}\n </select>\n {{??field.options}}\n {{?readonly}}\n {{?field.multi}}\n {{~value :option}}\n {{= field.labels ? field.labels[option] : option}}<br/>\n {{~}}\n {{??}}\n {{= field.labels ? field.labels[value] : value}}<br/>\n {{?}}\n {{??}}\n {{~field.options :option}}\n <input\n value=\"{{!option}}\"\n {{?field.multi}}\n type=\"checkbox\" name=\"{{!full_name}}\"\n {{?value.indexOf(option) > -1}}checked{{?}}\n {{??}}\n type=\"radio\" name=\"{{!full_name}}\"\n {{?value == option}}checked{{?}}\n {{?}}\n {{?field.trigger}}OnClick=\"DRApp.current.controller.fields_change();\"{{?}}\n />\n {{= field.labels ? field.labels[option] : option}}<br/>\n {{~}}\n {{?}}\n {{??!field.fields}}\n {{?readonly}}\n {{= field.style == \"datetime\" ? (new Date(value*1000)).toLocaleString() : value}}\n {{??}}\n <input\n id=\"{{!full_name}}\"\n placeholder=\"{{!field.label || field.name}}\"\n value=\"{{!value}}\"\n {{?field.trigger}}OnInput=\"DRApp.current.controller.fields_change();\"{{?}}\n type=\"text\"\n />\n {{?}}\n <br/>\n {{?}}\n {{?field.errors}}\n <span class='uk-form uk-text-danger'>\n {{~field.errors :error}}\n {{=error}}\n {{~}}\n </span>\n {{?}}\n {{?field.description}}\n <dfn>{{=field.description.replace(/\\n/g, \"<br/>\")}}</dfn><br/>\n {{?}}\n {{?field.link}}\n {{ var links = Array.isArray(field.link) ? field.link : [field.link]; }}\n {{~links: link}}\n <a href=\"{{!link.url || link}}\" target=\"{{!link.target || '_blank'}}\">{{=link.name || link.url || link}}</a><br/>\n {{~}}\n {{?}}\n </div>\n </div>\n {{?field.fields}}\n {{= DRApp.templates.Fields({fields: field.fields, readonly: readonly, prefix: prefix.concat(field.name)}) }}\n {{?}}\n{{~}}\n```\n\nWhich looks like this:\n\n![OpenGUI Exmaple](example.png)\n\nIf you're familer with Docker Desktop, Kubernetes, and Tilt `make up` and hit space.\n\nNavigate to `http://localhost:7971/` and give it a whirl.\n\n# Cli Example\n\nJust took this from the from the unittests:\n\n```python\n@unittest.mock.patch(\"builtins.print\")\n@unittest.mock.patch(\"builtins.input\")\ndef test_ask(self, mock_input, mock_print):\n\n cli = opengui.Cli(\n fields=[\n {\n \"name\": \"basic\",\n \"description\": \"be basic\",\n \"default\": \"badass\",\n \"validation\": \"^bitch$\"\n },\n {\n \"name\": \"single\",\n \"options\": [\"yin\", \"yang\"],\n \"labels\": {\n \"yin\": \"Yin\",\n \"yang\": \"Yang\"\n },\n \"default\": \"yin\"\n },\n {\n \"name\": \"multiple\",\n \"multi\": True,\n \"options\": \"{[ fs ]}\",\n \"default\": [\"fun\", \"foe\"]\n },\n {\n \"name\": \"yah\",\n \"bool\": True,\n \"default\": True\n },\n {\n \"name\": \"sure\",\n \"bool\": True\n },\n {\n \"name\": \"nah\",\n \"bool\": True\n }\n ],\n values={\n \"fs\": [\"fee\", \"fie\", \"foe\", \"fum\"]\n }\n )\n\n mock_input.side_effect = [\n \"\",\n \"bitch\",\n \"fish\",\n \"0\",\n \"3\",\n \"1\",\n \"fish 0 6\",\n \"\",\n \"1 3\",\n \"\",\n \"y\",\n \"n\"\n ]\n\n\n self.assertEqual(cli.ask(), {\n \"basic\": \"bitch\",\n \"single\": \"yin\",\n \"multiple\": [\"fee\", \"foe\"],\n \"yah\": True,\n \"sure\": True,\n \"nah\": False,\n \"fs\": [\"fee\", \"fie\", \"foe\", \"fum\"]\n })\n\n mock_print.assert_has_calls([\n unittest.mock.call(' be basic'),\n unittest.mock.call(\"must match '^bitch$'\"),\n unittest.mock.call('[1] Yin'),\n unittest.mock.call('[2] Yang'),\n unittest.mock.call('invalid choice: fish'),\n unittest.mock.call('[1] Yin'),\n unittest.mock.call('[2] Yang'),\n unittest.mock.call('invalid choice: 0'),\n unittest.mock.call('[1] Yin'),\n unittest.mock.call('[2] Yang'),\n unittest.mock.call('invalid choice: 3'),\n unittest.mock.call('[1] Yin'),\n unittest.mock.call('[2] Yang'),\n unittest.mock.call('[1] fee'),\n unittest.mock.call('[2] fie'),\n unittest.mock.call('[3] foe'),\n unittest.mock.call('[4] fum'),\n unittest.mock.call(\"invalid choices: ['fish', '0', '6']\"),\n unittest.mock.call('[1] fee'),\n unittest.mock.call('[2] fie'),\n unittest.mock.call('[3] foe'),\n unittest.mock.call('[4] fum'),\n unittest.mock.call(\"invalid values ['fun']\"),\n unittest.mock.call('[1] fee'),\n unittest.mock.call('[2] fie'),\n unittest.mock.call('[3] foe'),\n unittest.mock.call('[4] fum')\n ])\n\n mock_input.assert_has_calls([\n unittest.mock.call('basic: '),\n unittest.mock.call('enter index - single: '),\n unittest.mock.call('enter index - single: '),\n unittest.mock.call('enter index - single: '),\n unittest.mock.call('enter index - single: '),\n unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),\n unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),\n unittest.mock.call('enter multiple indexes, separated by spaces - multiple: '),\n unittest.mock.call('enter value y/n - yah: '),\n unittest.mock.call('enter value y/n - sure: '),\n unittest.mock.call('enter value y/n - nah: ')\n ])\n```\n\nAnd fields use [yaes.Engine](https://yaes.readthedocs.io/en/latest/) for transformaton each time a question is asked:\n\n```python\nfields = opengui.Fields(\n fields=[\n {\"name\": \"a\", \"label\": \"{{ lab }}\", \"stuff\": \"{[ things ]}\"},\n {\"name\": \"b\"}\n ],\n errors=['boo'],\n valid=True,\n ready=False\n)\n\nvalues = {\"lab\": \"A\", \"things\": [1, 2, 3]}\n\nself.assertEqual(fields.question(values).to_dict(), {\n \"name\": \"a\",\n \"label\": \"A\",\n \"stuff\": [1, 2, 3]\n})\n```\n\n# Documentation\n\nIt's lacking at this point, but check out the [tests](test_opengui.py) to see what you can do.\n",
"bugtrack_url": null,
"license": null,
"summary": "Library for building dynamic forms",
"version": "0.9.8",
"project_urls": {
"Download": "https://github.com/gaf3/opengui",
"Homepage": "https://opengui.readthedocs.io/en/0.9.8/"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "a1b84f3f4f1e26ea8c3a4a3f2b47d83376f805342603f22866810fde3d7f9a93",
"md5": "baff6d44aa87a5a526dea1734ff8ec62",
"sha256": "444ef493205308f74dce7b2102b3a341e16b931fb622db27bf19b24d8f64738e"
},
"downloads": -1,
"filename": "opengui-0.9.8-py3-none-any.whl",
"has_sig": false,
"md5_digest": "baff6d44aa87a5a526dea1734ff8ec62",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 11386,
"upload_time": "2024-12-08T17:55:59",
"upload_time_iso_8601": "2024-12-08T17:55:59.357825Z",
"url": "https://files.pythonhosted.org/packages/a1/b8/4f3f4f1e26ea8c3a4a3f2b47d83376f805342603f22866810fde3d7f9a93/opengui-0.9.8-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "7e3be0576c87155ab02cee826f16d305786b58b93ad314d39899ca1b69d11483",
"md5": "62fd8b42d6ebdb30dbf94168ec3310c8",
"sha256": "505c1f5f87983ba0420fdb968ddd8bba5be0328dde91c52fbfcca392d8618a6f"
},
"downloads": -1,
"filename": "opengui-0.9.8.tar.gz",
"has_sig": false,
"md5_digest": "62fd8b42d6ebdb30dbf94168ec3310c8",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 11452,
"upload_time": "2024-12-08T17:56:00",
"upload_time_iso_8601": "2024-12-08T17:56:00.963160Z",
"url": "https://files.pythonhosted.org/packages/7e/3b/e0576c87155ab02cee826f16d305786b58b93ad314d39899ca1b69d11483/opengui-0.9.8.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-08 17:56:00",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "gaf3",
"github_project": "opengui",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "yaes",
"specs": [
[
"==",
"0.2.5"
]
]
},
{
"name": "sphinxter",
"specs": [
[
"==",
"0.1.7"
]
]
},
{
"name": "ptvsd",
"specs": [
[
"==",
"4.3.2"
]
]
},
{
"name": "coverage",
"specs": [
[
"==",
"6.4.2"
]
]
},
{
"name": "pylint",
"specs": [
[
"==",
"2.14.5"
]
]
}
],
"lcname": "opengui"
}