5: Stemmen

Een peiling zonder stemmen is een lijst. In dit hoofdstuk wordt elke optie een formulier, ontvangt een POST route de stem, gaat de telling omhoog, en een omleiding her-render de pagina. Gewone HTTP, nog geen JavaScript: deze versie werkt in elke browser, met alles uit.

5.1: The vote forms

In poll.phlo, turn each button in the choices view into a tiny form:

view choices:
<section.card>
	<foreach type_poll::records() AS $option>
		<form method=post action="/poll/vote/$option->id">
			<button>$option->option</button>
		</form>
	</foreach>
</section>

Each form posts to its own URL: /poll/vote/1, /poll/vote/2, and so on. The id travels in the path, so the form needs no hidden fields. Note the quotes around the action value: attributes containing variables or slashes must be quoted. The form: display: inline rule from chapter 3 keeps the buttons on one row.

Reload http://localhost/poll: looks identical, but every button now submits a form. Clicking one gives a 404, because the route does not exist yet.

5.2: De POST route

Voeg de route toe aan poll.phlo, nabij de GET-route:

route POST poll vote $id {
	if (!$option = type_poll::record(id: (int)$id)) return false
	type_poll::change('id=?', (int)$id, votes: $option->votes + 1)
	location('/poll')
}

Lees het van boven naar beneden:

Herlaad en klik op Phlo. De pagina herlaadt, de telling leest 1, en de balk springt naar 100%. Klik op een andere optie en kijk hoe de percentages zich herverdelen. Controleer data/poll.json om de stemmen op schijf te zien.

5.3: Reading a payload instead

The id in the path is one style. The other is a request body, which you read through %payload. First enable the resource in data/app.json:

{
    "resources": [
        "DB/DB",
        "DB/model",
        "DB/JSONDB",
        "DB/JSON.result",
        "payload"
    ]
}

A payload-based version of the same route looks like this:

route POST poll vote @option {
	$id = (int)%payload->option
	if (!$option = type_poll::record(id: $id)) return false
	type_poll::change('id=?', $id, votes: $option->votes + 1)
	location('/poll')
}

@option validates the request body: the payload must contain exactly the key option, or the route does not match. Body keys are never bound as parameters; you read them via %payload->option. The matching form would be one form with <button name=option value="$option->id"> per choice.

Both styles are idiomatic. Keep the $id variant from 5.2 for this tutorial; it transitions cleanly into the async version.

5.4: What you have now

The build keeps itself current on every request; run the checks explicitly if you want the confirmation:

php www/app.php build::run
php www/app.php build::lint

Both settle at []. You have a complete, working poll: server-rendered, refresh-safe, zero JavaScript. Vote a few times, watch data/poll.json change, and notice the one thing that still feels 2004: the full page reload on every vote. That is the next chapter.

Laatst bijgewerkt op 23-08-2026

We gebruiken essentiële cookies om deze site te laten werken. Met uw toestemming gebruiken we ook analytics om de site te verbeteren.