How to open and close Unpoly layer
Table of contents
- Opening a modal layer with Unpoly
- Markup of a modal layer
- Flask application
- Submitting form when a field changes
- Markup of modal layer with an automatically submitted form
- Telling the server that the form is automatically submitted
- Flask route for an automatically submitted form
- Index of used functions and methods
This post presents a minimal example of how to open and close an Unpoly layer in a Flask application.
Unpoly is a lightweight JavaScript library designed to enhance server-rendered web applications. For example, it allows updating only fragments of a page in response to form submissions. This makes interactions faster and preserves client-side state in unaffected parts of the page.
By the end of this tutorial, you'll know:
- How to create modal layers using Unpoly library
- How to open modal layer
- How to emit server response to close modal layer
- How to create modal layer with an automatically submitted form
Opening a modal layer with Unpoly
Unpoly uses HTML attributes that enable additional behavior on HTML elements.
index.html
<a href="{{ url_for('new') }}" class="button"
up-layer="new modal" up-size="medium" up-history="false"
up-on-accepted="up.navigate({ url: '{{ url_for('home') }}' })">
New
</a>
up-layer="new modal" follows the link and opens the resulting page in a new modal overlay.
up-on-accepted sets a JavaScript snippet that is called, when the overlay is accepted. Here's the up.navigate() function, which navigates to a URL built by Flask's url_for() function.
up-history="false" sets that changes to fragments within the overlay will never update the address bar.
up-size sets the size of overlay.
href="{{ url_for('new') }}" — Flask uses Jinja2 to render HTML files, so commands in curly braces are Jinja2 expressions that call Python functions.
Markup of a modal layer
layer_modal.html
<main up-main="modal">
<form method="post" action="{{ url_for('new') }}"
up-submit="" up-fail-target="form">
<div>
<label for="field_1">
Field 1
{% if error +%}
<b>{{ error }}</b>
{% endif %}
</label>
<input id="field_1" name="field_1" />
</div>
<div>
<button class="button" type="submit">Add</button>
</div>
</form>
</main>
action={{ url_for('new') }} is an endpoint built by Flask's url_for() function.
up-submit="" submits the form via JavaScript and updates a fragment with the server response.
up-fail-target="form" sets the form element to update after a failed response.
{% if error %} is a Jinja2 conditional expression that uses the variable error passed to the template by Flask's route function.
Flask application
hello.py
from flask import Flask, make_response, render_template, request
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html')
@app.route('/new', methods=['GET', 'POST'])
def new():
template_name = 'layer_modal.html'
context = {
'error': None,
'field_1': None,
}
if request.method == 'POST':
context['field_1'] = request.form.get('field_1')
if not context['field_1']:
context['error'] = 'Empty field'
return render_template(
template_name, **context
), 422
response = make_response()
response.headers['X-Up-Accept-Layer'] = 'true'
return response
# GET request
return render_template(template_name, **context)
If field_1 is empty, the server returns the layer_modal.html template with error and a 422 status code. As specified by the up-fail-target attribute, Unpoly will then update the form element using this response.
The example then shows the server response Unpoly requires to accept and close the layer. Upon acceptance, the browser will navigate to url_for('home'), as directed by the up-on-accepted attribute.
Submitting form when a field changes
Now, we address a more complex task. We need to open a modal layer with a form that has two fields. The first, field_1, must be filled out. The second, another_field, is optional. The form should automatically submit data whenever the another_field value changes. Unpoly has the up-autosubmit attribute to achieve this. For example, this could show a user a preview of an image they have just uploaded, even before they hit the submit button.
Also, if the user clicks the submit button, the server should first check if field_1 has been filled out. If it has, then the form is sent.
So, the server needs to detect whether the form was submitted automatically (due to a change in another_field) or by the user clicking the submit button.
We need to know this because field_1 only needs to be checked when the user clicks submit, not during automatic submission.
Markup of modal layer with an automatically submitted form
The form element attributes are exactly the same as in the first example. Note the use of the up-autosubmit attribute on the input element.
layer_autosubmit.html
<main up-main="modal">
<form method="post" action="{{ url_for('new_with_autosubmit') }}"
up-submit="" up-fail-target="form" novalidate="">
<fieldset>
<div>
<label for="field_1">
Field 1
{% if error +%}
<b>{{ error }}</b>
{% endif %}
</label>
<input id="field_1" name="field_1" autofocus
{% if field_1 +%}
value="{{ field_1|e }}"
{% endif %} />
</div>
<div>
<label for="another_field">Another field</label>
<input id="another_field" name="another_field"
up-autosubmit up-watch-delay="1000"
{% if another_field +%}
value="{{ another_field|e }}"
{% endif %}/>
</div>
{% if preview +%}
<div>{{ preview }}</div>
{% endif %}
<div>
<button class="button" type="submit">Add</button>
</div>
</fieldset>
</form>
</main>
up-watch-delay="1000" sets the delay (in milliseconds) before the form is submitted after a change.
Telling the server that the form is automatically submitted
The trick here is to pass a parameter to the form before it was submitted. This can be done using Unpoly up.on() function that will be called on up:form:submit event. We check that event.target.name is another_field and set the parameter to pass. (If the user hits the submit button, then event.target is the submit button.)
project.js
up.on('up:form:submit', function(event) {
if (event.target.name === 'another_field') {
event.params.set('autosubmit', '1');
}
})
Where should this JavaScript code be placed in your project? I prefer separate project.js along with other static files. It is important to load project.js after unpoly.js.
Flask route for an automatically submitted form
hello.py
@app.route('/new-with-autosubmit', methods=['GET', 'POST'])
def new_with_autosubmit():
template_name = 'layer_autosubmit.html'
context = {
'error': None,
'field_1': None,
'another_field': None,
'preview': None,
}
if request.method == 'POST':
context['field_1'] = request.form.get('field_1')
context['another_field'] = request.form.get(
'another_field'
)
autosubmit = request.form.get('autosubmit') == '1'
context['preview'] = context['another_field']
if autosubmit:
return render_template(template_name, **context)
if not context['field_1']:
context['error'] = 'Empty field'
return render_template(
template_name, **context
), 422
response = make_response()
response.headers['X-Up-Accept-Layer'] = 'true'
return response
# GET request
return render_template(template_name, **context)
autosubmit = request.form.get('autosubmit') == '1'
If autosubmit is 1, the server returns the layer_autosubmit.html template with preview.