Pyjoyment 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjoyment.Plugin.TagHelpers - Tag helpers plugin
|
|
3
|
+
================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
# Pyjoyment
|
|
7
|
+
app.plugin('TagHelpers')
|
|
8
|
+
|
|
9
|
+
# Pyjoyment.Lite
|
|
10
|
+
plugin('TagHelpers')
|
|
11
|
+
|
|
12
|
+
:mod:`Pyjoyment.Plugin.TagHelpers` is a collection of HTML tag helpers for
|
|
13
|
+
:mod:`Pyjoyment`, based on the
|
|
14
|
+
`HTML Living Standard <https://html.spec.whatwg.org>`_.
|
|
15
|
+
|
|
16
|
+
Most form helpers can automatically pick up previous input values and will
|
|
17
|
+
show them as default. You can also use the helper ``param`` of
|
|
18
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` to set them manually and let necessary
|
|
19
|
+
attributes always be generated automatically. ::
|
|
20
|
+
|
|
21
|
+
% if not param('country'):
|
|
22
|
+
% param('country', 'germany')
|
|
23
|
+
% end
|
|
24
|
+
<%= radio_button('country', 'germany') %> Germany
|
|
25
|
+
<%= radio_button('country', 'france') %> France
|
|
26
|
+
<%= radio_button('country', 'uk') %> UK
|
|
27
|
+
|
|
28
|
+
For fields that failed validation with the helper ``validation`` of
|
|
29
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers` the ``field-with-error`` class will be
|
|
30
|
+
automatically added through ``tag_with_error``, to make styling with CSS
|
|
31
|
+
easier. ::
|
|
32
|
+
|
|
33
|
+
<input class="field-with-error" name="age" type="text" value="250">
|
|
34
|
+
|
|
35
|
+
The helpers take the attributes of the tag as keyword arguments. An attribute
|
|
36
|
+
whose name is not an identifier of Python, or is a keyword of it, as
|
|
37
|
+
``class``, ``for`` or ``data-id``, is passed with ``**``, and an attribute
|
|
38
|
+
without a value has the value ``None``. ::
|
|
39
|
+
|
|
40
|
+
%= text_field('first_name', **{'class': 'user'}, required=None)
|
|
41
|
+
|
|
42
|
+
A block of a template, between ``begin`` and ``end``, is the content of the tag
|
|
43
|
+
when it is the last positional argument, and the attributes follow it. ::
|
|
44
|
+
|
|
45
|
+
%= label_for('first_name', begin
|
|
46
|
+
First name
|
|
47
|
+
%= end, **{'class': 'user'})
|
|
48
|
+
|
|
49
|
+
This is a core plugin, that means it is always enabled and its code a good
|
|
50
|
+
example for learning how to build new plugins, you're welcome to fork it.
|
|
51
|
+
|
|
52
|
+
See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
|
|
53
|
+
default.
|
|
54
|
+
|
|
55
|
+
Helpers
|
|
56
|
+
-------
|
|
57
|
+
|
|
58
|
+
:mod:`Pyjoyment.Plugin.TagHelpers` implements the following helpers.
|
|
59
|
+
|
|
60
|
+
asset_tag ::
|
|
61
|
+
|
|
62
|
+
%= asset_tag('/app.js')
|
|
63
|
+
%= asset_tag('/app.js', **{'async': 'async'})
|
|
64
|
+
|
|
65
|
+
Generate ``script``, ``link`` or ``img`` tag for static asset.
|
|
66
|
+
|
|
67
|
+
button_to ::
|
|
68
|
+
|
|
69
|
+
%= button_to('Test', 'some_get_route')
|
|
70
|
+
%= button_to('Test', 'some_get_route', {'id': 23}, **{'class': 'menu'})
|
|
71
|
+
%= button_to('Test', 'http://example.com/test', **{'class': 'menu'})
|
|
72
|
+
%= button_to('Remove', 'some_delete_route')
|
|
73
|
+
|
|
74
|
+
Generate portable ``form`` tag with ``form_for``, containing a single button. ::
|
|
75
|
+
|
|
76
|
+
<form action="/path/to/get/route">
|
|
77
|
+
<input type="submit" value="Test">
|
|
78
|
+
</form>
|
|
79
|
+
<form action="/path/to/get/route/23" class="menu">
|
|
80
|
+
<input type="submit" value="Test">
|
|
81
|
+
</form>
|
|
82
|
+
<form action="http://example.com/test" class="menu">
|
|
83
|
+
<input type="submit" value="Test">
|
|
84
|
+
</form>
|
|
85
|
+
<form action="/path/to/delete/route?_method=DELETE" method="POST">
|
|
86
|
+
<input type="submit" value="Remove">
|
|
87
|
+
</form>
|
|
88
|
+
|
|
89
|
+
check_box ::
|
|
90
|
+
|
|
91
|
+
%= check_box('employed')
|
|
92
|
+
%= check_box('employed', 1)
|
|
93
|
+
%= check_box('employed', 1, checked=None, id='foo')
|
|
94
|
+
|
|
95
|
+
Generate ``input`` tag of type ``checkbox``. Previous input values will
|
|
96
|
+
automatically get picked up and shown as default. ::
|
|
97
|
+
|
|
98
|
+
<input name="employed" type="checkbox">
|
|
99
|
+
<input name="employed" type="checkbox" value="1">
|
|
100
|
+
<input checked id="foo" name="employed" type="checkbox" value="1">
|
|
101
|
+
|
|
102
|
+
color_field ::
|
|
103
|
+
|
|
104
|
+
%= color_field('background')
|
|
105
|
+
%= color_field('background', '#ffffff')
|
|
106
|
+
%= color_field('background', '#ffffff', id='foo')
|
|
107
|
+
|
|
108
|
+
Generate ``input`` tag of type ``color``. Previous input values will
|
|
109
|
+
automatically get picked up and shown as default. ::
|
|
110
|
+
|
|
111
|
+
<input name="background" type="color">
|
|
112
|
+
<input name="background" type="color" value="#ffffff">
|
|
113
|
+
<input id="foo" name="background" type="color" value="#ffffff">
|
|
114
|
+
|
|
115
|
+
csrf_button_to ::
|
|
116
|
+
|
|
117
|
+
%= csrf_button_to('Remove', 'some_delete_route')
|
|
118
|
+
|
|
119
|
+
Same as ``button_to``, but also includes a ``csrf_field``. ::
|
|
120
|
+
|
|
121
|
+
<form action="/path/to/delete/route?_method=DELETE" method="POST">
|
|
122
|
+
<input name="csrf_token" type="hidden" value="fa6a08...">
|
|
123
|
+
<input type="submit" value="Remove">
|
|
124
|
+
</form>
|
|
125
|
+
|
|
126
|
+
csrf_field ::
|
|
127
|
+
|
|
128
|
+
%= csrf_field()
|
|
129
|
+
|
|
130
|
+
Generate ``input`` tag of type ``hidden`` with the helper ``csrf_token`` of
|
|
131
|
+
:mod:`Pyjoyment.Plugin.DefaultHelpers`. ::
|
|
132
|
+
|
|
133
|
+
<input name="csrf_token" type="hidden" value="fa6a08...">
|
|
134
|
+
|
|
135
|
+
date_field ::
|
|
136
|
+
|
|
137
|
+
%= date_field('end')
|
|
138
|
+
%= date_field('end', '2012-12-21')
|
|
139
|
+
%= date_field('end', '2012-12-21', id='foo')
|
|
140
|
+
|
|
141
|
+
Generate ``input`` tag of type ``date``. Previous input values will
|
|
142
|
+
automatically get picked up and shown as default. ::
|
|
143
|
+
|
|
144
|
+
<input name="end" type="date">
|
|
145
|
+
<input name="end" type="date" value="2012-12-21">
|
|
146
|
+
<input id="foo" name="end" type="date" value="2012-12-21">
|
|
147
|
+
|
|
148
|
+
datetime_field ::
|
|
149
|
+
|
|
150
|
+
%= datetime_field('end')
|
|
151
|
+
%= datetime_field('end', '2012-12-21T23:59:59')
|
|
152
|
+
%= datetime_field('end', '2012-12-21T23:59:59', id='foo')
|
|
153
|
+
|
|
154
|
+
Generate ``input`` tag of type ``datetime-local``. Previous input values will
|
|
155
|
+
automatically get picked up and shown as default. ::
|
|
156
|
+
|
|
157
|
+
<input name="end" type="datetime-local">
|
|
158
|
+
<input name="end" type="datetime-local" value="2012-12-21T23:59:59">
|
|
159
|
+
<input id="foo" name="end" type="datetime-local" value="2012-12-21T23:59:59">
|
|
160
|
+
|
|
161
|
+
email_field ::
|
|
162
|
+
|
|
163
|
+
%= email_field('notify')
|
|
164
|
+
%= email_field('notify', 'nospam@example.com')
|
|
165
|
+
%= email_field('notify', 'nospam@example.com', id='foo')
|
|
166
|
+
|
|
167
|
+
Generate ``input`` tag of type ``email``. Previous input values will
|
|
168
|
+
automatically get picked up and shown as default. ::
|
|
169
|
+
|
|
170
|
+
<input name="notify" type="email">
|
|
171
|
+
<input name="notify" type="email" value="nospam@example.com">
|
|
172
|
+
<input id="foo" name="notify" type="email" value="nospam@example.com">
|
|
173
|
+
|
|
174
|
+
favicon ::
|
|
175
|
+
|
|
176
|
+
%= favicon()
|
|
177
|
+
%= favicon('/favicon.ico')
|
|
178
|
+
|
|
179
|
+
Generate ``link`` tag for favicon, defaulting to the one that comes bundled
|
|
180
|
+
with Pyjoyment. ::
|
|
181
|
+
|
|
182
|
+
<link href="/favicon.ico" rel="icon">
|
|
183
|
+
<link href="/favicon.ico" rel="icon">
|
|
184
|
+
|
|
185
|
+
file_field ::
|
|
186
|
+
|
|
187
|
+
%= file_field('avatar')
|
|
188
|
+
%= file_field('avatar', id='foo')
|
|
189
|
+
|
|
190
|
+
Generate ``input`` tag of type ``file``. ::
|
|
191
|
+
|
|
192
|
+
<input name="avatar" type="file">
|
|
193
|
+
<input id="foo" name="avatar" type="file">
|
|
194
|
+
|
|
195
|
+
form_for ::
|
|
196
|
+
|
|
197
|
+
%= form_for('login', begin
|
|
198
|
+
%= text_field('first_name')
|
|
199
|
+
%= submit_button()
|
|
200
|
+
%= end)
|
|
201
|
+
%= form_for('login', {'format': 'txt'}, begin
|
|
202
|
+
%= text_field('first_name')
|
|
203
|
+
%= submit_button()
|
|
204
|
+
%= end, method='POST')
|
|
205
|
+
%= form_for('/login', begin
|
|
206
|
+
%= text_field('first_name', disabled='disabled')
|
|
207
|
+
%= submit_button()
|
|
208
|
+
%= end, enctype='multipart/form-data')
|
|
209
|
+
%= form_for('http://example.com/login', begin
|
|
210
|
+
%= text_field('first_name')
|
|
211
|
+
%= submit_button()
|
|
212
|
+
%= end, method='POST')
|
|
213
|
+
%= form_for('some_delete_route', begin
|
|
214
|
+
%= submit_button('Remove')
|
|
215
|
+
%= end)
|
|
216
|
+
|
|
217
|
+
Generate portable ``form`` tag with
|
|
218
|
+
:meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for`. For routes that do
|
|
219
|
+
not allow ``GET``, a ``method`` attribute with the value ``POST`` will be
|
|
220
|
+
automatically added. And for methods other than ``GET`` or ``POST``, an
|
|
221
|
+
``_method`` query parameter will be added as well. ::
|
|
222
|
+
|
|
223
|
+
<form action="/path/to/login">
|
|
224
|
+
<input name="first_name" type="text">
|
|
225
|
+
<input type="submit" value="Ok">
|
|
226
|
+
</form>
|
|
227
|
+
<form action="/path/to/login.txt" method="POST">
|
|
228
|
+
<input name="first_name" type="text">
|
|
229
|
+
<input type="submit" value="Ok">
|
|
230
|
+
</form>
|
|
231
|
+
<form action="/path/to/login" enctype="multipart/form-data">
|
|
232
|
+
<input disabled="disabled" name="first_name" type="text">
|
|
233
|
+
<input type="submit" value="Ok">
|
|
234
|
+
</form>
|
|
235
|
+
<form action="http://example.com/login" method="POST">
|
|
236
|
+
<input name="first_name" type="text">
|
|
237
|
+
<input type="submit" value="Ok">
|
|
238
|
+
</form>
|
|
239
|
+
<form action="/path/to/delete/route?_method=DELETE" method="POST">
|
|
240
|
+
<input type="submit" value="Remove">
|
|
241
|
+
</form>
|
|
242
|
+
|
|
243
|
+
hidden_field ::
|
|
244
|
+
|
|
245
|
+
%= hidden_field('foo', 'bar')
|
|
246
|
+
%= hidden_field('foo', 'bar', id='bar')
|
|
247
|
+
|
|
248
|
+
Generate ``input`` tag of type ``hidden``. ::
|
|
249
|
+
|
|
250
|
+
<input name="foo" type="hidden" value="bar">
|
|
251
|
+
<input id="bar" name="foo" type="hidden" value="bar">
|
|
252
|
+
|
|
253
|
+
image ::
|
|
254
|
+
|
|
255
|
+
%= image('/images/foo.png')
|
|
256
|
+
%= image('/images/foo.png', alt='Foo')
|
|
257
|
+
|
|
258
|
+
Generate portable ``img`` tag. ::
|
|
259
|
+
|
|
260
|
+
<img src="/path/to/images/foo.png">
|
|
261
|
+
<img alt="Foo" src="/path/to/images/foo.png">
|
|
262
|
+
|
|
263
|
+
input_tag ::
|
|
264
|
+
|
|
265
|
+
%= input_tag('first_name')
|
|
266
|
+
%= input_tag('first_name', 'Default')
|
|
267
|
+
%= input_tag('employed', type='checkbox')
|
|
268
|
+
|
|
269
|
+
Generate ``input`` tag. Previous input values will automatically get picked up
|
|
270
|
+
and shown as default. ::
|
|
271
|
+
|
|
272
|
+
<input name="first_name">
|
|
273
|
+
<input name="first_name" value="Default">
|
|
274
|
+
<input name="employed" type="checkbox">
|
|
275
|
+
|
|
276
|
+
javascript ::
|
|
277
|
+
|
|
278
|
+
%= javascript('/script.js')
|
|
279
|
+
%= javascript('/script.js', defer=None)
|
|
280
|
+
%= javascript(begin
|
|
281
|
+
const a = 'b';
|
|
282
|
+
%= end)
|
|
283
|
+
|
|
284
|
+
Generate portable ``script`` tag for JavaScript asset. ::
|
|
285
|
+
|
|
286
|
+
<script src="/path/to/script.js"></script>
|
|
287
|
+
<script defer src="/path/to/script.js"></script>
|
|
288
|
+
<script>//<![CDATA[
|
|
289
|
+
|
|
290
|
+
const a = 'b';
|
|
291
|
+
|
|
292
|
+
//]]></script>
|
|
293
|
+
|
|
294
|
+
label_for ::
|
|
295
|
+
|
|
296
|
+
%= label_for('first_name', 'First name')
|
|
297
|
+
%= label_for('first_name', 'First name', **{'class': 'user'})
|
|
298
|
+
%= label_for('first_name', begin
|
|
299
|
+
First name
|
|
300
|
+
%= end)
|
|
301
|
+
%= label_for('first_name', begin
|
|
302
|
+
First name
|
|
303
|
+
%= end, **{'class': 'user'})
|
|
304
|
+
|
|
305
|
+
Generate ``label`` tag. ::
|
|
306
|
+
|
|
307
|
+
<label for="first_name">First name</label>
|
|
308
|
+
<label class="user" for="first_name">First name</label>
|
|
309
|
+
<label for="first_name">
|
|
310
|
+
First name
|
|
311
|
+
</label>
|
|
312
|
+
<label class="user" for="first_name">
|
|
313
|
+
First name
|
|
314
|
+
</label>
|
|
315
|
+
|
|
316
|
+
link_to ::
|
|
317
|
+
|
|
318
|
+
%= link_to('Home', 'index')
|
|
319
|
+
%= link_to('Home', 'index', {'format': 'txt'}, **{'class': 'menu'})
|
|
320
|
+
%= link_to('index', {'format': 'txt'}, begin
|
|
321
|
+
Home
|
|
322
|
+
%= end, **{'class': 'menu'})
|
|
323
|
+
%= link_to('Contact', 'mailto:sri@example.com')
|
|
324
|
+
<%= link_to('index', begin %>Home<% end) %>
|
|
325
|
+
<%= link_to('/file.txt', begin %>File<% end) %>
|
|
326
|
+
<%= link_to('https://mojolicious.org', begin %>Mojolicious<% end) %>
|
|
327
|
+
<%= link_to(url_for().set(query={'foo': 'bar'}).to_abs(), begin %>Retry<% end) %>
|
|
328
|
+
|
|
329
|
+
Generate portable ``a`` tag with
|
|
330
|
+
:meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for`, defaults to using
|
|
331
|
+
the capitalized link target as content. ::
|
|
332
|
+
|
|
333
|
+
<a href="/path/to/index">Home</a>
|
|
334
|
+
<a class="menu" href="/path/to/index.txt">Home</a>
|
|
335
|
+
<a class="menu" href="/path/to/index.txt">
|
|
336
|
+
Home
|
|
337
|
+
</a>
|
|
338
|
+
<a href="mailto:sri@example.com">Contact</a>
|
|
339
|
+
<a href="/path/to/index">Home</a>
|
|
340
|
+
<a href="/path/to/file.txt">File</a>
|
|
341
|
+
<a href="https://mojolicious.org">Mojolicious</a>
|
|
342
|
+
<a href="http://127.0.0.1:3000/current/path?foo=bar">Retry</a>
|
|
343
|
+
|
|
344
|
+
The first argument to ``link_to`` is the link content, except when the last
|
|
345
|
+
positional argument is a callable, such as a template block (created with the
|
|
346
|
+
``begin`` and ``end`` keywords); in that case, the link content is omitted at
|
|
347
|
+
the start of the argument list, and the block will become the link content.
|
|
348
|
+
|
|
349
|
+
month_field ::
|
|
350
|
+
|
|
351
|
+
%= month_field('vacation')
|
|
352
|
+
%= month_field('vacation', '2012-12')
|
|
353
|
+
%= month_field('vacation', '2012-12', id='foo')
|
|
354
|
+
|
|
355
|
+
Generate ``input`` tag of type ``month``. Previous input values will
|
|
356
|
+
automatically get picked up and shown as default. ::
|
|
357
|
+
|
|
358
|
+
<input name="vacation" type="month">
|
|
359
|
+
<input name="vacation" type="month" value="2012-12">
|
|
360
|
+
<input id="foo" name="vacation" type="month" value="2012-12">
|
|
361
|
+
|
|
362
|
+
number_field ::
|
|
363
|
+
|
|
364
|
+
%= number_field('age')
|
|
365
|
+
%= number_field('age', 25)
|
|
366
|
+
%= number_field('age', 25, id='foo', min=0, max=200)
|
|
367
|
+
|
|
368
|
+
Generate ``input`` tag of type ``number``. Previous input values will
|
|
369
|
+
automatically get picked up and shown as default. ::
|
|
370
|
+
|
|
371
|
+
<input name="age" type="number">
|
|
372
|
+
<input name="age" type="number" value="25">
|
|
373
|
+
<input id="foo" max="200" min="0" name="age" type="number" value="25">
|
|
374
|
+
|
|
375
|
+
password_field ::
|
|
376
|
+
|
|
377
|
+
%= password_field('pass')
|
|
378
|
+
%= password_field('pass', id='foo')
|
|
379
|
+
|
|
380
|
+
Generate ``input`` tag of type ``password``. ::
|
|
381
|
+
|
|
382
|
+
<input name="pass" type="password">
|
|
383
|
+
<input id="foo" name="pass" type="password">
|
|
384
|
+
|
|
385
|
+
radio_button ::
|
|
386
|
+
|
|
387
|
+
%= radio_button('test')
|
|
388
|
+
%= radio_button('country', 'germany')
|
|
389
|
+
%= radio_button('country', 'germany', checked=None, id='foo')
|
|
390
|
+
|
|
391
|
+
Generate ``input`` tag of type ``radio``. Previous input values will
|
|
392
|
+
automatically get picked up and shown as default. ::
|
|
393
|
+
|
|
394
|
+
<input name="test" type="radio">
|
|
395
|
+
<input name="country" type="radio" value="germany">
|
|
396
|
+
<input checked id="foo" name="country" type="radio" value="germany">
|
|
397
|
+
|
|
398
|
+
range_field ::
|
|
399
|
+
|
|
400
|
+
%= range_field('age')
|
|
401
|
+
%= range_field('age', 25)
|
|
402
|
+
%= range_field('age', 25, id='foo', min=0, max=200)
|
|
403
|
+
|
|
404
|
+
Generate ``input`` tag of type ``range``. Previous input values will
|
|
405
|
+
automatically get picked up and shown as default. ::
|
|
406
|
+
|
|
407
|
+
<input name="age" type="range">
|
|
408
|
+
<input name="age" type="range" value="25">
|
|
409
|
+
<input id="foo" max="200" min="0" name="age" type="range" value="25">
|
|
410
|
+
|
|
411
|
+
search_field ::
|
|
412
|
+
|
|
413
|
+
%= search_field('q')
|
|
414
|
+
%= search_field('q', 'perl')
|
|
415
|
+
%= search_field('q', 'perl', id='foo')
|
|
416
|
+
|
|
417
|
+
Generate ``input`` tag of type ``search``. Previous input values will
|
|
418
|
+
automatically get picked up and shown as default. ::
|
|
419
|
+
|
|
420
|
+
<input name="q" type="search">
|
|
421
|
+
<input name="q" type="search" value="perl">
|
|
422
|
+
<input id="foo" name="q" type="search" value="perl">
|
|
423
|
+
|
|
424
|
+
select_field ::
|
|
425
|
+
|
|
426
|
+
%= select_field('country', ['de', 'en'])
|
|
427
|
+
%= select_field('country', [['Germany', 'de'], 'en'], id='eu')
|
|
428
|
+
%= select_field('country', [['Germany', 'de', {'selected': 'selected'}], 'en'])
|
|
429
|
+
%= select_field('country', [c.c('EU', [['Germany', 'de'], 'en'], {'id': 'eu'})])
|
|
430
|
+
%= select_field('country', [c.c('EU', ['de', 'en']), c.c('Asia', ['cn', 'jp'])])
|
|
431
|
+
|
|
432
|
+
Generate ``select`` and ``option`` tags from lists and ``optgroup`` tags from
|
|
433
|
+
:mod:`Pyjo.Collection` objects. An option is its value or a list of its label,
|
|
434
|
+
its value and a dict of its attributes; a group is a collection of its label,
|
|
435
|
+
its options and a dict of its attributes. Previous input values will
|
|
436
|
+
automatically get picked up and shown as default. ::
|
|
437
|
+
|
|
438
|
+
<select name="country">
|
|
439
|
+
<option value="de">de</option>
|
|
440
|
+
<option value="en">en</option>
|
|
441
|
+
</select>
|
|
442
|
+
<select id="eu" name="country">
|
|
443
|
+
<option value="de">Germany</option>
|
|
444
|
+
<option value="en">en</option>
|
|
445
|
+
</select>
|
|
446
|
+
<select name="country">
|
|
447
|
+
<option selected="selected" value="de">Germany</option>
|
|
448
|
+
<option value="en">en</option>
|
|
449
|
+
</select>
|
|
450
|
+
<select name="country">
|
|
451
|
+
<optgroup id="eu" label="EU">
|
|
452
|
+
<option value="de">Germany</option>
|
|
453
|
+
<option value="en">en</option>
|
|
454
|
+
</optgroup>
|
|
455
|
+
</select>
|
|
456
|
+
<select name="country">
|
|
457
|
+
<optgroup label="EU">
|
|
458
|
+
<option value="de">de</option>
|
|
459
|
+
<option value="en">en</option>
|
|
460
|
+
</optgroup>
|
|
461
|
+
<optgroup label="Asia">
|
|
462
|
+
<option value="cn">cn</option>
|
|
463
|
+
<option value="jp">jp</option>
|
|
464
|
+
</optgroup>
|
|
465
|
+
</select>
|
|
466
|
+
|
|
467
|
+
stylesheet ::
|
|
468
|
+
|
|
469
|
+
%= stylesheet('/foo.css')
|
|
470
|
+
%= stylesheet('/foo.css', title='Foo style')
|
|
471
|
+
%= stylesheet(begin
|
|
472
|
+
body {color: #000}
|
|
473
|
+
%= end)
|
|
474
|
+
|
|
475
|
+
Generate portable ``style`` or ``link`` tag for CSS asset. ::
|
|
476
|
+
|
|
477
|
+
<link href="/path/to/foo.css" rel="stylesheet">
|
|
478
|
+
<link href="/path/to/foo.css" rel="stylesheet" title="Foo style">
|
|
479
|
+
<style>/*<![CDATA[*/
|
|
480
|
+
|
|
481
|
+
body {color: #000}
|
|
482
|
+
|
|
483
|
+
/*]]>*/</style>
|
|
484
|
+
|
|
485
|
+
submit_button ::
|
|
486
|
+
|
|
487
|
+
%= submit_button()
|
|
488
|
+
%= submit_button('Ok!', id='foo')
|
|
489
|
+
|
|
490
|
+
Generate ``input`` tag of type ``submit``. ::
|
|
491
|
+
|
|
492
|
+
<input type="submit" value="Ok">
|
|
493
|
+
<input id="foo" type="submit" value="Ok!">
|
|
494
|
+
|
|
495
|
+
t ::
|
|
496
|
+
|
|
497
|
+
%= t('div', 'test & 123')
|
|
498
|
+
|
|
499
|
+
Alias for ``tag``. ::
|
|
500
|
+
|
|
501
|
+
<div>test & 123</div>
|
|
502
|
+
|
|
503
|
+
tag ::
|
|
504
|
+
|
|
505
|
+
%= tag('br')
|
|
506
|
+
%= tag('div')
|
|
507
|
+
%= tag('div', id='foo', hidden=None)
|
|
508
|
+
%= tag('div', 'test & 123')
|
|
509
|
+
%= tag('div', 'test & 123', id='foo')
|
|
510
|
+
%= tag('div', 'test & 123', data={'my_id': 1, 'Name': 'test'})
|
|
511
|
+
%= tag('div', begin
|
|
512
|
+
test & 123
|
|
513
|
+
%= end)
|
|
514
|
+
<%= tag('div', begin %>test & 123<% end, id='foo') %>
|
|
515
|
+
|
|
516
|
+
Alias for :meth:`Pyjo.DOM.Pyjo_DOM.new_tag`. ::
|
|
517
|
+
|
|
518
|
+
<br>
|
|
519
|
+
<div></div>
|
|
520
|
+
<div hidden id="foo"></div>
|
|
521
|
+
<div>test & 123</div>
|
|
522
|
+
<div id="foo">test & 123</div>
|
|
523
|
+
<div data-my-id="1" data-name="test">test & 123</div>
|
|
524
|
+
<div>
|
|
525
|
+
test & 123
|
|
526
|
+
</div>
|
|
527
|
+
<div id="foo">test & 123</div>
|
|
528
|
+
|
|
529
|
+
Very useful for reuse in more specific tag helpers. ::
|
|
530
|
+
|
|
531
|
+
output = c.tag('meta')
|
|
532
|
+
output = c.tag('meta', charset='UTF-8')
|
|
533
|
+
output = c.tag('div', '<p>This will be escaped</p>')
|
|
534
|
+
output = c.tag('div', lambda: '<p>This will not be escaped</p>')
|
|
535
|
+
|
|
536
|
+
Results are automatically wrapped in :mod:`Pyjo.String.HTML` objects to
|
|
537
|
+
prevent accidental double escaping in ``ep`` templates.
|
|
538
|
+
|
|
539
|
+
tag_with_error ::
|
|
540
|
+
|
|
541
|
+
%= tag_with_error('input', **{'class': 'foo'})
|
|
542
|
+
|
|
543
|
+
Same as ``tag``, but adds the class ``field-with-error``. ::
|
|
544
|
+
|
|
545
|
+
<input class="foo field-with-error">
|
|
546
|
+
|
|
547
|
+
tel_field ::
|
|
548
|
+
|
|
549
|
+
%= tel_field('work')
|
|
550
|
+
%= tel_field('work', '123456789')
|
|
551
|
+
%= tel_field('work', '123456789', id='foo')
|
|
552
|
+
|
|
553
|
+
Generate ``input`` tag of type ``tel``. Previous input values will
|
|
554
|
+
automatically get picked up and shown as default. ::
|
|
555
|
+
|
|
556
|
+
<input name="work" type="tel">
|
|
557
|
+
<input name="work" type="tel" value="123456789">
|
|
558
|
+
<input id="foo" name="work" type="tel" value="123456789">
|
|
559
|
+
|
|
560
|
+
text_area ::
|
|
561
|
+
|
|
562
|
+
%= text_area('story')
|
|
563
|
+
%= text_area('story', cols=40)
|
|
564
|
+
%= text_area('story', 'Default', cols=40)
|
|
565
|
+
%= text_area('story', begin
|
|
566
|
+
Default
|
|
567
|
+
%= end, cols=40)
|
|
568
|
+
|
|
569
|
+
Generate ``textarea`` tag. Previous input values will automatically get picked
|
|
570
|
+
up and shown as default. ::
|
|
571
|
+
|
|
572
|
+
<textarea name="story"></textarea>
|
|
573
|
+
<textarea cols="40" name="story"></textarea>
|
|
574
|
+
<textarea cols="40" name="story">Default</textarea>
|
|
575
|
+
<textarea cols="40" name="story">
|
|
576
|
+
Default
|
|
577
|
+
</textarea>
|
|
578
|
+
|
|
579
|
+
text_field ::
|
|
580
|
+
|
|
581
|
+
%= text_field('first_name')
|
|
582
|
+
%= text_field('first_name', 'Default')
|
|
583
|
+
%= text_field('first_name', 'Default', **{'class': 'user'})
|
|
584
|
+
|
|
585
|
+
Generate ``input`` tag of type ``text``. Previous input values will
|
|
586
|
+
automatically get picked up and shown as default. ::
|
|
587
|
+
|
|
588
|
+
<input name="first_name" type="text">
|
|
589
|
+
<input name="first_name" type="text" value="Default">
|
|
590
|
+
<input class="user" name="first_name" type="text" value="Default">
|
|
591
|
+
|
|
592
|
+
time_field ::
|
|
593
|
+
|
|
594
|
+
%= time_field('start')
|
|
595
|
+
%= time_field('start', '23:59:59')
|
|
596
|
+
%= time_field('start', '23:59:59', id='foo')
|
|
597
|
+
|
|
598
|
+
Generate ``input`` tag of type ``time``. Previous input values will
|
|
599
|
+
automatically get picked up and shown as default. ::
|
|
600
|
+
|
|
601
|
+
<input name="start" type="time">
|
|
602
|
+
<input name="start" type="time" value="23:59:59">
|
|
603
|
+
<input id="foo" name="start" type="time" value="23:59:59">
|
|
604
|
+
|
|
605
|
+
url_field ::
|
|
606
|
+
|
|
607
|
+
%= url_field('address')
|
|
608
|
+
%= url_field('address', 'https://mojolicious.org')
|
|
609
|
+
%= url_field('address', 'https://mojolicious.org', id='foo')
|
|
610
|
+
|
|
611
|
+
Generate ``input`` tag of type ``url``. Previous input values will
|
|
612
|
+
automatically get picked up and shown as default. ::
|
|
613
|
+
|
|
614
|
+
<input name="address" type="url">
|
|
615
|
+
<input name="address" type="url" value="https://mojolicious.org">
|
|
616
|
+
<input id="foo" name="address" type="url" value="https://mojolicious.org">
|
|
617
|
+
|
|
618
|
+
week_field ::
|
|
619
|
+
|
|
620
|
+
%= week_field('vacation')
|
|
621
|
+
%= week_field('vacation', '2012-W17')
|
|
622
|
+
%= week_field('vacation', '2012-W17', id='foo')
|
|
623
|
+
|
|
624
|
+
Generate ``input`` tag of type ``week``. Previous input values will
|
|
625
|
+
automatically get picked up and shown as default. ::
|
|
626
|
+
|
|
627
|
+
<input name="vacation" type="week">
|
|
628
|
+
<input name="vacation" type="week" value="2012-W17">
|
|
629
|
+
<input id="foo" name="vacation" type="week" value="2012-W17">
|
|
630
|
+
|
|
631
|
+
See also
|
|
632
|
+
--------
|
|
633
|
+
|
|
634
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
635
|
+
|
|
636
|
+
Ported from `Mojolicious::Plugin::TagHelpers
|
|
637
|
+
<https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/TagHelpers.pm>`_ of Mojolicious 9.49.
|
|
638
|
+
"""
|
|
639
|
+
|
|
640
|
+
import Pyjo.Collection
|
|
641
|
+
import Pyjo.String.HTML
|
|
642
|
+
import Pyjo.URL
|
|
643
|
+
import Pyjoyment.Plugin
|
|
644
|
+
|
|
645
|
+
from Pyjo.DOM.HTML import tag_to_html
|
|
646
|
+
from Pyjoyment.Controller import Helper
|
|
647
|
+
|
|
648
|
+
import functools
|
|
649
|
+
|
|
650
|
+
from collections.abc import Callable
|
|
651
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
if TYPE_CHECKING:
|
|
655
|
+
import Pyjoyment
|
|
656
|
+
import Pyjoyment.Controller
|
|
657
|
+
|
|
658
|
+
type Controller = Pyjoyment.Controller.object
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class Pyjoyment_Plugin_TagHelpers(Pyjoyment.Plugin.object):
|
|
662
|
+
"""
|
|
663
|
+
:mod:`Pyjoyment.Plugin.TagHelpers` inherits all methods from
|
|
664
|
+
:mod:`Pyjoyment.Plugin` and implements the following new ones.
|
|
665
|
+
"""
|
|
666
|
+
|
|
667
|
+
def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
|
|
668
|
+
"""::
|
|
669
|
+
|
|
670
|
+
plugin.register(Pyjoyment.new())
|
|
671
|
+
|
|
672
|
+
Register helpers in :mod:`Pyjoyment` application.
|
|
673
|
+
"""
|
|
674
|
+
# Text field variations
|
|
675
|
+
time = ('date', 'month', 'time', 'week')
|
|
676
|
+
for name in (*time, 'color', 'email', 'number', 'range', 'search', 'tel', 'text', 'url'):
|
|
677
|
+
app.helper(f'{name}_field', functools.partial(_field, name))
|
|
678
|
+
app.helper('datetime_field', functools.partial(_field, 'datetime-local'))
|
|
679
|
+
|
|
680
|
+
app.helper('asset_tag', _asset_tag)
|
|
681
|
+
app.helper('csrf_field', _csrf_field)
|
|
682
|
+
app.helper('form_for', _form_for)
|
|
683
|
+
app.helper('hidden_field', _hidden_field)
|
|
684
|
+
app.helper('javascript', _javascript)
|
|
685
|
+
app.helper('label_for', _label_for)
|
|
686
|
+
app.helper('link_to', _link_to)
|
|
687
|
+
app.helper('select_field', _select_field)
|
|
688
|
+
app.helper('stylesheet', _stylesheet)
|
|
689
|
+
app.helper('submit_button', _submit_button)
|
|
690
|
+
app.helper('tag_with_error', _tag_with_error)
|
|
691
|
+
app.helper('text_area', _text_area)
|
|
692
|
+
|
|
693
|
+
app.helper('button_to', functools.partial(_button_to, False))
|
|
694
|
+
app.helper('check_box', functools.partial(_field, 'checkbox'))
|
|
695
|
+
app.helper('csrf_button_to', functools.partial(_button_to, True))
|
|
696
|
+
app.helper('favicon', _favicon)
|
|
697
|
+
app.helper('file_field', functools.partial(_empty_field, 'file'))
|
|
698
|
+
app.helper('image', _image)
|
|
699
|
+
app.helper('input_tag', _input)
|
|
700
|
+
app.helper('password_field', functools.partial(_empty_field, 'password'))
|
|
701
|
+
app.helper('radio_button', functools.partial(_field, 'radio'))
|
|
702
|
+
|
|
703
|
+
# "t" is just a shortcut for the "tag" helper
|
|
704
|
+
for name in ('t', 'tag'):
|
|
705
|
+
app.helper(name, _tag_helper)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def _asset_tag(c: 'Controller', target: str, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
709
|
+
url = c.url_for_asset(target)
|
|
710
|
+
|
|
711
|
+
helpers: Any = c.helpers
|
|
712
|
+
if target.endswith('.js'):
|
|
713
|
+
return helpers.javascript(url, **attrs)
|
|
714
|
+
if target.endswith('.css'):
|
|
715
|
+
return helpers.stylesheet(url, **attrs)
|
|
716
|
+
return helpers.image(url, **attrs)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _button_to(csrf: bool, c: 'Controller', text: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
720
|
+
prefix = _csrf_field(c) if csrf else ''
|
|
721
|
+
return _form_for(c, *args, lambda: prefix + _submit_button(c, text), **attrs)
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def _content(content: Any) -> 'str | Callable[[], str] | None':
|
|
725
|
+
# A number is its string, as Perl stringifies it
|
|
726
|
+
if content is None or isinstance(content, str):
|
|
727
|
+
return content
|
|
728
|
+
if callable(content):
|
|
729
|
+
return cast(Callable[[], str], content)
|
|
730
|
+
return str(content)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def _csrf_field(c: 'Controller', /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
734
|
+
helpers: Any = c.helpers
|
|
735
|
+
return _hidden_field(c, 'csrf_token', helpers.csrf_token(), **attrs)
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _empty_field(type: str, c: 'Controller', name: str, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
739
|
+
return _validation(c, name, 'input', None, **{'name': name, **attrs, 'type': type})
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def _favicon(c: 'Controller', file: Any = None, /) -> Pyjo.String.HTML.object:
|
|
743
|
+
return _tag('link', rel='icon', href=_file_url(c, file if file is not None else 'favicon.ico'))
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _field(type: str, c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
747
|
+
return _input(c, name, *args, **{**attrs, 'type': type})
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _file_url(c: 'Controller', url: Any) -> Pyjo.URL.object:
|
|
751
|
+
return url if isinstance(url, Pyjo.URL.object) else c.url_for_file(url)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _form_for(c: 'Controller', target: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
755
|
+
url: list[Any] = [target]
|
|
756
|
+
rest = list(args)
|
|
757
|
+
if rest and isinstance(rest[0], dict):
|
|
758
|
+
url.append(rest.pop(0))
|
|
759
|
+
|
|
760
|
+
# Method detection
|
|
761
|
+
r = c.app.routes.lookup(url[0]) if isinstance(url[0], str) else None
|
|
762
|
+
method = r.suggested_method if r is not None else 'GET'
|
|
763
|
+
post = {'method': 'POST'} if method != 'GET' else {}
|
|
764
|
+
|
|
765
|
+
action = c.url_for(*url)
|
|
766
|
+
if post and method != 'POST':
|
|
767
|
+
action.query = {'_method': method}
|
|
768
|
+
return _tag('form', *rest, **{'action': action, **post, **attrs})
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _hidden_field(c: 'Controller', name: str, value: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
772
|
+
return _tag('input', **{'name': name, 'value': value, **attrs, 'type': 'hidden'})
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _image(c: 'Controller', url: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
776
|
+
return _tag('img', **{'src': _file_url(c, url), **attrs})
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _input(c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
780
|
+
if args:
|
|
781
|
+
attrs = {'value': args[0], **attrs}
|
|
782
|
+
|
|
783
|
+
if values := [str(v) for v in c.every_param(name) if v is not None]:
|
|
784
|
+
# Checkbox or radiobutton
|
|
785
|
+
type = attrs.get('type') or ''
|
|
786
|
+
if type == 'checkbox' or type == 'radio':
|
|
787
|
+
value = attrs.get('value')
|
|
788
|
+
value = 'on' if value is None else str(value)
|
|
789
|
+
attrs.pop('checked', None)
|
|
790
|
+
if value in values:
|
|
791
|
+
attrs['checked'] = None
|
|
792
|
+
|
|
793
|
+
# Others
|
|
794
|
+
else:
|
|
795
|
+
attrs['value'] = values[-1]
|
|
796
|
+
|
|
797
|
+
return _validation(c, name, 'input', None, **{'name': name, **attrs})
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def _javascript(c: 'Controller', /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
801
|
+
rest = list(args)
|
|
802
|
+
content = f'//<![CDATA[\n{rest.pop()()}\n//]]>' if rest and callable(rest[-1]) else ''
|
|
803
|
+
src = {'src': _file_url(c, rest[0])} if rest else {}
|
|
804
|
+
return _tag('script', lambda: content, **{**src, **attrs})
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _label_for(c: 'Controller', name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
808
|
+
return _validation(c, name, 'label', content, **{'for': name, **attrs})
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def _link_to(c: 'Controller', content: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
812
|
+
url: list[Any] = [content]
|
|
813
|
+
rest = list(args)
|
|
814
|
+
|
|
815
|
+
# Content
|
|
816
|
+
if rest and callable(rest[-1]):
|
|
817
|
+
content = rest.pop()
|
|
818
|
+
else:
|
|
819
|
+
url = [rest.pop(0)] if rest else [content]
|
|
820
|
+
|
|
821
|
+
# Captures
|
|
822
|
+
if rest and isinstance(rest[0], dict):
|
|
823
|
+
url.append(rest.pop(0))
|
|
824
|
+
|
|
825
|
+
return _tag('a', content, **{'href': c.url_for(*url), **attrs})
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
# Unlike Mojolicious: the attributes of an option or a group are a dict after its value, where Mojo takes a flat list of
|
|
829
|
+
# names and values, as a list of Python keeps them as one value
|
|
830
|
+
def _option(values: set[str], pair: Any) -> Pyjo.String.HTML.object:
|
|
831
|
+
if not isinstance(pair, list | tuple):
|
|
832
|
+
pair = [pair, pair]
|
|
833
|
+
pair = list(cast(list[Any] | tuple[Any, ...], pair))
|
|
834
|
+
attrs: dict[str, Any] = {'value': pair[1], **(pair[2] if len(pair) > 2 else {})}
|
|
835
|
+
if values:
|
|
836
|
+
attrs.pop('selected', None)
|
|
837
|
+
if str(pair[1]) in values:
|
|
838
|
+
attrs['selected'] = None
|
|
839
|
+
|
|
840
|
+
return _tag('option', pair[0], **attrs)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _select_field(c: 'Controller', name: str, options: list[Any], /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
844
|
+
values = {str(v) for v in c.every_param(name) if v is not None}
|
|
845
|
+
|
|
846
|
+
groups = ''
|
|
847
|
+
for group in options:
|
|
848
|
+
# "optgroup" tag
|
|
849
|
+
if isinstance(group, Pyjo.Collection.object):
|
|
850
|
+
items = list(cast(Pyjo.Collection.object[Any], group))
|
|
851
|
+
label, group_values = items[0], items[1]
|
|
852
|
+
group_attrs: dict[str, Any] = items[2] if len(items) > 2 else {}
|
|
853
|
+
content = ''.join(_option(values, value) for value in group_values)
|
|
854
|
+
groups += _tag('optgroup', lambda content=content: content, **{'label': label, **group_attrs})
|
|
855
|
+
|
|
856
|
+
# "option" tag
|
|
857
|
+
else:
|
|
858
|
+
groups += _option(values, group)
|
|
859
|
+
|
|
860
|
+
return _validation(c, name, 'select', lambda: groups, **{'name': name, **attrs})
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _stylesheet(c: 'Controller', /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
864
|
+
rest = list(args)
|
|
865
|
+
content = f'/*<![CDATA[*/\n{rest.pop()()}\n/*]]>*/' if rest and callable(rest[-1]) else ''
|
|
866
|
+
if not rest:
|
|
867
|
+
return _tag('style', lambda: content, **attrs)
|
|
868
|
+
return _tag('link', **{'rel': 'stylesheet', 'href': _file_url(c, rest[0]), **attrs})
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _submit_button(c: 'Controller', value: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
872
|
+
return _tag('input', **{'value': value if value is not None else 'Ok', **attrs, 'type': 'submit'})
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def _tag(name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
876
|
+
return Pyjo.String.HTML.new(tag_to_html(name, _content(content), **attrs))
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _tag_helper(c: 'Controller', name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
880
|
+
return _tag(name, content, **attrs)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _tag_with_error(c: 'Controller', tag: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
884
|
+
attrs['class'] = f'{attrs["class"]} field-with-error' if attrs.get('class') else 'field-with-error'
|
|
885
|
+
return _tag(tag, content, **attrs)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def _text_area(c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
889
|
+
rest = list(args)
|
|
890
|
+
cb = rest.pop() if rest and callable(rest[-1]) else None
|
|
891
|
+
content = rest[0] if rest else None
|
|
892
|
+
if (value := c.param(name)) is not None:
|
|
893
|
+
content = value
|
|
894
|
+
elif content is None:
|
|
895
|
+
content = cb if cb is not None else ''
|
|
896
|
+
|
|
897
|
+
return _validation(c, name, 'textarea', content, **{'name': name, **attrs})
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _validation(c: 'Controller', name: str, tag: str, content: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
|
|
901
|
+
if not c.helpers.validation().has_error(name):
|
|
902
|
+
return _tag(tag, content, **attrs)
|
|
903
|
+
return c.helpers.tag_with_error(tag, content, **attrs)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
# Not in Mojolicious: the types of the helpers, for type checkers, which Pyjoyment.Controller and Pyjoyment have
|
|
907
|
+
class Helpers:
|
|
908
|
+
"""
|
|
909
|
+
The types of the helpers of :mod:`Pyjoyment.Plugin.TagHelpers`, for type
|
|
910
|
+
checkers and editors, which :mod:`Pyjoyment.Controller` and
|
|
911
|
+
:mod:`Pyjoyment` have, while the helpers themselves are found in the
|
|
912
|
+
renderer of the application at run time. See
|
|
913
|
+
:class:`Pyjoyment.Controller.Helper`.
|
|
914
|
+
"""
|
|
915
|
+
|
|
916
|
+
color_field = Helper(functools.partial(_field, 'color'))
|
|
917
|
+
date_field = Helper(functools.partial(_field, 'date'))
|
|
918
|
+
datetime_field = Helper(functools.partial(_field, 'datetime-local'))
|
|
919
|
+
email_field = Helper(functools.partial(_field, 'email'))
|
|
920
|
+
month_field = Helper(functools.partial(_field, 'month'))
|
|
921
|
+
number_field = Helper(functools.partial(_field, 'number'))
|
|
922
|
+
range_field = Helper(functools.partial(_field, 'range'))
|
|
923
|
+
search_field = Helper(functools.partial(_field, 'search'))
|
|
924
|
+
tel_field = Helper(functools.partial(_field, 'tel'))
|
|
925
|
+
text_field = Helper(functools.partial(_field, 'text'))
|
|
926
|
+
time_field = Helper(functools.partial(_field, 'time'))
|
|
927
|
+
url_field = Helper(functools.partial(_field, 'url'))
|
|
928
|
+
week_field = Helper(functools.partial(_field, 'week'))
|
|
929
|
+
|
|
930
|
+
asset_tag = Helper(_asset_tag)
|
|
931
|
+
csrf_field = Helper(_csrf_field)
|
|
932
|
+
form_for = Helper(_form_for)
|
|
933
|
+
hidden_field = Helper(_hidden_field)
|
|
934
|
+
javascript = Helper(_javascript)
|
|
935
|
+
label_for = Helper(_label_for)
|
|
936
|
+
link_to = Helper(_link_to)
|
|
937
|
+
select_field = Helper(_select_field)
|
|
938
|
+
stylesheet = Helper(_stylesheet)
|
|
939
|
+
submit_button = Helper(_submit_button)
|
|
940
|
+
tag_with_error = Helper(_tag_with_error)
|
|
941
|
+
text_area = Helper(_text_area)
|
|
942
|
+
|
|
943
|
+
button_to = Helper(functools.partial(_button_to, False))
|
|
944
|
+
check_box = Helper(functools.partial(_field, 'checkbox'))
|
|
945
|
+
csrf_button_to = Helper(functools.partial(_button_to, True))
|
|
946
|
+
favicon = Helper(_favicon)
|
|
947
|
+
file_field = Helper(functools.partial(_empty_field, 'file'))
|
|
948
|
+
image = Helper(_image)
|
|
949
|
+
input_tag = Helper(_input)
|
|
950
|
+
password_field = Helper(functools.partial(_empty_field, 'password'))
|
|
951
|
+
radio_button = Helper(functools.partial(_field, 'radio'))
|
|
952
|
+
|
|
953
|
+
t = Helper(_tag_helper)
|
|
954
|
+
tag = Helper(_tag_helper)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
new = Pyjoyment_Plugin_TagHelpers.new
|
|
958
|
+
object = Pyjoyment_Plugin_TagHelpers
|
|
959
|
+
TagHelpers = Pyjoyment_Plugin_TagHelpers
|