7. Forms & Voting#
Now we’ll integrate everything into the main app with proper form handling.
Create a Poll class#
Add a Poll class to _app.wren to keep logic separate from presentation:
class Template {
// ... your template methods
}
class Poll {
construct new() {}
options { `SELECT * FROM poll`.fetch() }
vote(opt) { `UPDATE poll SET votes = votes + 1
WHERE id = ?`.query(opt) }
}
construct new()defines the constructoroptionsis a getter — called like a property:poll.optionsvote(opt)takes the poll option ID and increments its vote count
Build the vote form#
In index.wren, import both classes and render the form:
import "_app" for Template, Poll
var poll = Poll.new()
return Template.layout(
<form action="/" method="post">
<h2>Has web development become overly complex?</h2>
<ul>
{{ poll.options.map {|opt| <li>
<input type="radio" id="{{ opt["answer"] }}" name="vote"
value="{{ opt["id"] }}" required />
<label for="{{ opt["answer"] }}">
<strong>{{ opt["answer"] }}</strong>
<p>{{ opt["comment"] }}</p>
</label>
</li> } }}
</ul>
<button type="submit">Vote</button>
</form>
)
Handle the form POST#
Handle form submission on the same page — show errors inline, then redirect on success:
import "_app" for Template, Poll
var poll = Poll.new()
if (Request.isPost) {
var vote = Request.post("vote")
poll.vote(vote)
Response.redirect("/results")
return
}
return Template.layout(
<form action="/" method="post">
...
</form>
)
Key points:
Request.isPostdetects form submissionsRequest.post("vote")reads the submitted valueResponse.redirect("/results")sends the browser to the results pagereturnafter redirect stops further script execution
This is the standard Bialet form pattern: check → process → redirect.
Previous: 6. Migrations | Next: 8. Results