Overview
Login templates are .ect (Embedded CoffeeScript Templates) files rendered server-side by the render() function inside your login handler. They collect user input — server URLs, credentials, organization selections — before authentication proceeds.
Template Chain
Every template extends a two-level chain:
your-template.ect → login-template.ect → base-template.ect- base-template.ect — full HTML page: CI HUB header with logo, Bulma CSS, background, container
- login-template.ect — error notification area (
@error), contact info footer (@contact) - your template — only the content block (the form)
You write the form. Everything else is inherited.
Minimal Template
<% extend 'login-template.ect' %>
<p>Enter your server URL.</p>
<br>
<form action="<%- @action %>" method="post">
<div class="field">
<label class="label">Server URL</label>
<div class="control">
<input class="inputfield" type="url" name="serverUrl" value="<%= @serverUrl %>" required>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link" type="submit">Login</button>
</div>
</div>
</form>ECT Syntax
| Syntax | Purpose |
|---|---|
<% extend 'login-template.ect' %> | Inherit from parent template (required first line) |
<% content %> | Marks where child content is injected (used by parent templates) |
<%= @var %> | Output escaped variable |
<%- @var %> | Output unescaped variable (for URLs, HTML) |
<% if @var? : %> ... <% end %> | Conditional — renders block only if @var exists |
<% if @var == "value" : %> ... <% else : %> ... <% end %> | Conditional with else |
<% for item in @list : %> ... <% end %> | Loop over array |
Use <%= %> (escaped) for user-supplied values inside attributes. Use <%- %> (unescaped) for URLs and trusted values like @action.
Template Variables
Auto-injected by the template chain
| Variable | Source | Description |
|---|---|---|
@logo | Passed in render() data | Adapter logo — rendered in the header by base-template.ect |
@contact | Passed in render() data | Support contact info — rendered in the footer by login-template.ect |
@error | Passed in render() data | Error message — shown as a red notification banner by login-template.ect |
@title | Passed in render() data | Page heading — rendered above the form box by base-template.ect |
Required in your form
| Variable | Description |
|---|---|
@action | The POST URL for the form. Always use locals.endpointUrl (with state params appended) |
Custom variables
Pass any additional variables your template needs via the render() data object. They become @variableName in the template.
CSS Framework
Templates use Bulma (v0.7.4). Key classes:
| Class | Element |
|---|---|
.field | Form field wrapper |
.label | Field label |
.control | Input wrapper |
.inputfield | Text/password/URL input (CI HUB custom class) |
.select | Select dropdown wrapper |
.button.is-link | Submit button |
.notification.is-danger | Error message (handled by login-template.ect) |
.field.is-grouped | Groups buttons horizontally |
render() Function
render(templatePath: string, data: Record<string, unknown>): Promise<RenderEnvelope>Call from your login handler. First argument is the template path without the .ect extension. Place templates next to your index.ts.
return render(path.join(import.meta.dirname, 'select-serverurl'), {
title: 'Select Portal',
action: urlObject.toString(),
serverUrl: defaultUrl,
logo,
contact
})The form POSTs back to action, which re-invokes your login handler with requestData.body populated from the form fields. The name attribute of each <input> becomes a key in requestData.body.