Keyboard and forms
Mouse events start things. Keyboard and form events finish them: a search that filters as you type, a shortcut that saves, a message that says what is wrong with a field. This lesson goes through the events of keys, focus, text fields, forms, the clipboard and the elements that open and close, with an example you can try under each.
- Keys: down, up and press
- Enter, Escape, arrows and shortcuts
- Focus and blur
- Typing in a field: input, change and before input
- Selecting text
- Submitting, resetting and form data
- Checking fields: invalid
- Copy, cut and paste
- Details, popovers, dialogs and find in page
- Reference: every keyboard and form event
- Try it yourself
Keys: down, up and press
Pressing a key sends key down. If you keep it pressed the browser sends key down again and again, with a property repeat set to true on the repeats. Letting go sends key up. An older third event, key press, fires only for keys that produce a character and is deprecated, so write new code with key down and key up and leave key press to old pages.
Key events go to the element that has focus: a text field the user clicked, a button reached with Tab, or an element with tab index = 0. They then bubble up to its ancestors, as clicks do, so a handler on a wrapper box hears the keys typed into everything inside it. You cannot put a handler on the page itself, so a shortcut works only while the focus is somewhere inside the element that carries the handler.
A handler that wants the event is a named function with one i64 parameter, exactly as in the lesson on mouse events. dom.EventKey(e) returns the key as text. Other properties are read from the event with dom.GetStr and dom.GetBool. The example shows the two most useful: code and repeat. Click the field and press different keys, hold one down, and watch the lines change.
import dom
func pressed(e: i64):
dom.SetText(dom.ById("down"), "key down: " + dom.EventKey(e) + ", code " + dom.GetStr(e, "code"))
if dom.GetBool(e, "repeat"):
dom.SetText(dom.ById("note"), "The key is held, so the browser repeats it.")
else:
dom.SetText(dom.ById("note"), "")
end
end
func released(e: i64):
dom.SetText(dom.ById("up"), "key up: " + dom.EventKey(e))
dom.SetText(dom.ById("note"), "")
end
page:
title = "Which key?"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
input: id = "field" key down = pressed key up = released placeholder = "Click here, then press keys" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: id = "down" content = "key down: nothing yet" font family = monospace end
paragraph: id = "up" content = "key up: nothing yet" font family = monospace end
paragraph: id = "note" content = "" color = "#b45309" end
endkey is what the key means right now: a, A with Shift held, Enter, Escape, Tab, Backspace, a space is the one-character string " ", and the arrows are ArrowUp, ArrowDown, ArrowLeft and ArrowRight. code is which physical key it is, such as KeyA, whatever the keyboard layout. Use key when the character matters and code when the position matters, as in games. Modifiers are the booleans shiftKey, ctrlKey, altKey and metaKey (Command on a Mac), read with dom.GetBool(e, "ctrlKey").
With a Persian or any other non-Latin layout, key for the key labelled S is a Persian letter, not s, so Ctrl+S written as key == "s" never matches. For shortcuts compare code (KeyS), which does not change with the layout.
Enter, Escape, arrows and shortcuts
Most uses of key events are a small table: this key does that. The first one is a task list. Enter adds the text of the field as a new item, made with dom.Create, and empties the field. Escape only empties it. The handler checks that the field is not empty before it adds an item, with dom.GetValue(field).len() > 0.
import dom
func typing(e: i64):
field := dom.ById("task")
key := dom.EventKey(e)
if key == "Enter" and dom.GetValue(field).len() > 0:
item := dom.Create("li")
dom.SetText(item, dom.GetValue(field))
dom.Append(dom.ById("list"), item)
dom.SetValue(field, "")
else key == "Escape":
dom.SetValue(field, "")
end
end
page:
title = "Task list"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" max width = "28rem" end
input: id = "task" key down = typing placeholder = "New task, then Enter" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: content = "Enter adds the task. Escape clears the box." color = "#64748b" font size = "0.9rem" end
list: id = "list" end
endThe second moves a ball with the arrow keys. The box has tab index = 0, which makes it reachable with Tab and lets it have focus, so it receives the key events. Without that attribute a box never gets them. The handler ignores every other key by returning early, and for arrows it calls dom.PreventDefault(e), because the browser would otherwise scroll the page. The position lives in two mut variables and is written into left and top through dom.SetStyle.
import dom
mut x := 120
mut y := 60
func steer(e: i64):
key := dom.EventKey(e)
if key == "ArrowLeft":
x -= 12
else key == "ArrowRight":
x += 12
else key == "ArrowUp":
y -= 12
else key == "ArrowDown":
y += 12
else:
ret
end
dom.PreventDefault(e)
x = x < 0 ? 0 : (x > 270 ? 270 : x)
y = y < 0 ? 0 : (y > 120 ? 120 : y)
dom.SetStyle(dom.ById("ball"), "left", x + "px")
dom.SetStyle(dom.ById("ball"), "top", y + "px")
end
page:
title = "Arrow keys"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
box:
id = "arena"
tab index = 0
key down = steer
position = relative
width = 300
height = 150
background = "#ecfeff"
border = "2px solid #67e8f9"
border radius = 10
box: id = "ball" position = absolute left = 120 top = 60 width = 30 height = 30 border radius = "50%" background = "#0891b2" end
end
paragraph: content = "Click the box, then use the arrow keys." end
endThe third reads modifier keys. A wrapper box around a text area listens to key down for everything typed inside. When Ctrl (or Command) is held and the key is S, the handler prevents the default, which would open the browser's own save dialog, and runs its own action. Always prevent the default of a shortcut you take over, and never take over one the user depends on, such as Ctrl+C or Ctrl+W.
import dom
func shortcut(e: i64):
held := dom.GetBool(e, "ctrlKey") or dom.GetBool(e, "metaKey")
key := dom.EventKey(e).lower()
if held and key == "s":
dom.PreventDefault(e)
dom.SetText(dom.ById("status"), "Saved with Ctrl+S")
else held and key == "enter":
dom.PreventDefault(e)
dom.SetText(dom.ById("status"), "Sent with Ctrl+Enter")
end
end
page:
title = "Shortcuts"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
box:
id = "editor"
key down = shortcut
text area: id = "notes" rows = 5 placeholder = "Write here. Try Ctrl+S and Ctrl+Enter." width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
end
paragraph: id = "status" content = "Nothing yet." color = "#047857" end
endFocus and blur
An element has focus when it is the one that receives the keys. focus runs when an element gains focus, by a click, by Tab or from a script, and blur when it loses it. Together they are the natural place for a hint that appears next to the field being filled, and for checking a field when the user leaves it. The first example does both: the hint changes with the field, and leaving the name field empty paints it red.
import dom
func hint(text: str):
dom.SetText(dom.ById("hint"), text)
end
func check_name:
if dom.GetValue(dom.ById("name")).len() == 0:
hint("The name is required.")
dom.SetStyle(dom.ById("name"), "border-color", "#dc2626")
else:
hint("Thanks.")
dom.SetStyle(dom.ById("name"), "border-color", "")
end
end
page:
title = "Focus and blur"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "input" display = block width = "100%" padding = 10 margin = "0 0 10px 0" font size = "1rem" border = "2px solid #cbd5e1" border radius = 6 box sizing = "border-box" end
input: id = "name" placeholder = "Full name" focus = (): hint("Your name as it is on your card.") end blur = check_name end
input: id = "email" placeholder = "Email" focus = (): hint("We never share it.") end blur = (): hint("") end end
paragraph: id = "hint" content = "Click a field." color = "#475569" end
endfocus and blur do not bubble, so a handler on a wrapper never hears them from the fields inside. Their two siblings focus in and focus out do bubble, so one handler on a form section or a card can follow the focus of everything in it. They are also the way to style a whole group by what is inside it. CSS can do that too, with the :focus-within selector, and where it can you should use it.
import dom
page:
title = "A group that follows focus"
style: content = "#group.active { border-color: #2563eb; background: #eff6ff }" end
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "input" display = block width = "100%" padding = 8 margin = "6px 0" font size = "1rem" box sizing = "border-box" end
box:
id = "group"
focus in = (): dom.AddClass(dom.ById("group"), "active") end
focus out = (): dom.RemoveClass(dom.ById("group"), "active") end
padding = 14
border = "2px solid #cbd5e1"
border radius = 10
transition = "background 0.2s"
paragraph: content = "Delivery address" font weight = bold margin = 0 end
input: id = "street" placeholder = "Street" end
input: id = "city" placeholder = "City" end
end
endThe group has one handler for each event. focus in adds the class that draws the blue border and focus out removes it. When you press Tab from one field to the next, focus out and focus in arrive one right after the other, before the browser draws anything, so the border does not flash. Inside a focus out handler, event.relatedTarget is the element that will receive the focus next, if you ever need to tell leaving the group from moving inside it.
Typing in a field: input, change and before input
A text field has several events for the same act, and the difference is the moment. input fires after every change to the value: a typed letter, a deletion, a paste, a drop, an undo. change fires when the user is finished: when Enter is pressed in a one line field, or when the field loses focus after its value was changed. before input fires first, before the value changes, and its handler may call preventDefault to refuse the edit. Type into the field below and press Enter, then type some more and click away, and compare the two counters.
import dom
mut inputs := 0
mut changes := 0
func show:
dom.SetText(dom.ById("counts"), "input fired " + inputs + " times, change fired " + changes + " times")
end
page:
title = "Input and change"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
input: id = "field" input = (): inputs += 1 show() end change = (): changes += 1 show() end placeholder = "Type, then press Enter or click away" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: id = "counts" content = "input fired 0 times, change fired 0 times" end
paragraph: content = "input follows every keystroke. change waits until you are done." color = "#64748b" end
endA handler for input is what live features are made of. A character counter shows how much is left. Setting maxlength on the text area makes the browser stop typing at the limit, and the counter only has to show the length. It turns red near the end.
import dom
func count:
n := dom.GetValue(dom.ById("msg")).len()
left := dom.ById("left")
dom.SetText(left, n + " / 80")
if n >= 70:
dom.SetStyle(left, "color", "#b91c1c")
else:
dom.SetStyle(left, "color", "#475569")
end
end
page:
title = "Character counter"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
text area: id = "msg" input = count maxlength = 80 rows = 3 placeholder = "A short message" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: id = "left" content = "0 / 80" text align = right margin = "4px 0" end
endA search box filters a list on every keystroke. The handler reads the field, makes it lower case and goes through the items found by dom.QueryAll: a matching item gets display set to an empty string, which puts back its normal look, and the others get none. Empty text matches everything, so clearing the box brings all items back with no extra code.
import dom
func filter:
needle := dom.GetValue(dom.ById("q")).lower()
items := dom.QueryAll("#fruits li")
repeat dom.CountOf(items) in i:
item := dom.ItemAt(items, i)
if dom.GetText(item).lower().includes(needle):
dom.SetStyle(item, "display", "")
else:
dom.SetStyle(item, "display", "none")
end
end
end
page:
title = "Live search"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
input: id = "q" input = filter placeholder = "Search fruit" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
list: id = "fruits"
item: content = "Apple" end
item: content = "Banana" end
item: content = "Cherry" end
item: content = "Mango" end
item: content = "Orange" end
item: content = "Pear" end
end
endThe loop uses dom.CountOf for the number of items and dom.ItemAt for each one. QueryAll returns the elements that exist at that moment, so call it inside the handler, as here, and not once at the start, if items may be added later.
Selecting text
Three events report selection. select fires on a text field when the user selects some of its text, and not for a plain caret move. selection change fires whenever the selection or the caret in the field changes, which is also when the caret moves. select start fires when a new selection begins in any element, even one that is not editable, and a handler can call preventDefault to forbid the selection. The positions are the properties selectionStart and selectionEnd of the field, read with dom.GetNum.
import dom
func chosen:
field := dom.ById("text")
a := dom.GetNum(field, "selectionStart") as i64
b := dom.GetNum(field, "selectionEnd") as i64
dom.SetText(dom.ById("sel"), "select: " + (b - a) + " characters, from " + a + " to " + b)
end
func caret:
at := dom.GetNum(dom.ById("text"), "selectionStart") as i64
dom.SetText(dom.ById("caret"), "selection change: caret at " + at)
end
func started:
dom.SetText(dom.ById("start"), "select start: a new selection began in the paragraph.")
end
page:
title = "Selecting text"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
text area: id = "text" select = chosen selection change = caret rows = 3 content = "Select some of these words with the mouse or with Shift and the arrow keys." width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: id = "sel" content = "select: nothing selected" end
paragraph: id = "caret" content = "selection change: no caret yet" end
paragraph: id = "para" select start = started content = "You can also select this paragraph." background = "#fef9c3" padding = 8 end
paragraph: id = "start" content = "select start: nothing yet" end
endSelect with the mouse, or hold Shift and use the arrow keys. The lines under the text area show a count of characters and the caret position, and the yellow paragraph reports that a selection began in it. Support for selection change on a text field is newer than for the other two. A browser may send it only to the document, so test it in the browsers you care about.
Submitting, resetting and form data
A form sends submit when the user presses a submit button, or presses Enter in a field, and the browser's own checks have passed. Left alone, the browser then sends the fields to the server and loads the answer. A page that handles the form itself calls dom.PreventDefault(e) first and then reads the fields with dom.GetValue. Put the handler on the form, not on the button, because Enter in a field submits without clicking the button. The reset event fires when a reset button is pressed or the form is reset by script, just before the fields go back to their starting values.
import dom
func send(e: i64):
dom.PreventDefault(e)
who := dom.GetValue(dom.ById("who"))
mail := dom.GetValue(dom.ById("mail"))
dom.SetText(dom.ById("result"), "Thank you, " + who + ". We will write to " + mail + ".")
end
func cleared:
dom.SetText(dom.ById("result"), "The form was cleared.")
end
page:
title = "Sign up"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
global: selector = "input" display = block width = "100%" padding = 10 margin = "0 0 10px 0" font size = "1rem" box sizing = "border-box" end
global: selector = "button" padding = "8px 16px" margin = "0 8px 0 0" font size = "1rem" end
form:
id = "signup"
submit = send
reset = cleared
input: id = "who" placeholder = "Your name" end
input: id = "mail" type = email placeholder = "Your email" end
button: id = "go" type = submit content = "Sign up" end
button: id = "clear" type = reset content = "Clear" end
end
paragraph: id = "result" content = "" color = "#047857" end
endGive your fields and buttons ids that differ from the names of your functions. Inside a form, the browser looks up a name among the form's fields and buttons first, so a button with id = "send" hides a function called send and the handler reports "is not a function".
The third form event is form data. It fires every time the browser builds the list of fields of a form, which a submit does, and so does a script that writes new FormData(form). A handler gets event.formData and may add or change entries before they are used. The example adds a hidden field when the button builds the data.
import dom
func extra(e: i64):
data := dom.GetProp(e, "formData")
_r := dom.CallS2(data, "append", "source", "newsletter")
dom.SetText(dom.ById("out"), "form data: the form added a hidden field named source.")
end
page:
title = "Form data"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
form:
id = "news"
form data = extra
input: name = "email" value = "sara@example.com" padding = 10 font size = "1rem" end
button: id = "build" type = button click = "new FormData(document.getElementById('news'))" content = "Build the form data" padding = "8px 16px" margin = "0 0 0 8px" end
end
paragraph: id = "out" content = "Nothing built yet." end
endThe button uses a short JavaScript string, because the dom helpers have no way to write new. The handler is a normal Salam function: dom.GetProp(e, "formData") gives the FormData object and dom.CallS2 calls its append method with a name and a value.
Checking fields: invalid
HTML can check fields without a line of script: required, type = email, min, max, pattern and maxlength all take part. When the browser checks a field and it fails, the field gets an invalid event. A check runs when the form is submitted, and also when a script calls checkValidity() or reportValidity() on the form or on a field. The second one also shows the browser's bubble with the message. invalid does not bubble, so the handler goes on the field itself.
The handler below replaces the browser's message with its own by calling setCustomValidity on the field. It looks at the field's validity object to tell an empty field from a wrong one. The message stays on the field until you remove it, so another handler, on input, sets it back to an empty string every time the user types. If you forget that, the field stays invalid forever, even after the user fixes it.
import dom
func explain(e: i64):
field := dom.EventTarget(e)
if dom.GetBool(dom.GetProp(field, "validity"), "valueMissing"):
_a := dom.CallS1(field, "setCustomValidity", "Please enter your email.")
else:
_b := dom.CallS1(field, "setCustomValidity", "That does not look like an email address.")
end
dom.SetText(dom.ById("why"), "invalid: " + dom.GetStr(field, "validationMessage"))
end
func retry:
_c := dom.CallS1(dom.ById("mail"), "setCustomValidity", "")
end
func check_mail:
_d := dom.Call0(dom.ById("form"), "reportValidity")
if dom.GetBool(dom.GetProp(dom.ById("mail"), "validity"), "valid"):
dom.SetText(dom.ById("why"), "The address is valid.")
end
end
page:
title = "Custom messages"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
form:
id = "form"
input: id = "mail" type = email required = true invalid = explain input = retry placeholder = "Your email" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
button: id = "recheck" type = button click = check_mail content = "Check" margin = "10px 0 0 0" padding = "8px 16px" end
end
paragraph: id = "why" content = "Press Check." end
endChecks in the browser are help for the user, not protection. Anybody can send a request with any values, so the server must check everything again.
Copy, cut and paste
The three clipboard events fire when the user copies, cuts or pastes by keyboard or menu. They fire on the element that has the selection or the focus, and they bubble. copy and cut run before the browser does its part, so a handler can call preventDefault and put its own text on the clipboard with clipboardData.setData. paste gives the pasted content in event.clipboardData, and getData("text") is the plain text. A handler may prevent the default and insert a cleaned up version instead. A page cannot read the clipboard at any other time without the user's permission, and that is intended.
import dom
mut copies := 0
mut cuts := 0
func counts:
dom.SetText(dom.ById("stats"), "copied " + copies + " times, cut " + cuts + " times")
end
func copied:
copies += 1
counts()
end
func snipped:
cuts += 1
counts()
end
func pasted(e: i64):
text := dom.CallS1(dom.GetProp(e, "clipboardData"), "getData", "text")
dom.SetProp(dom.ById("last"), "textContent", text)
end
page:
title = "Clipboard"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
input: id = "field" copy = copied cut = snipped paste = pasted value = "Copy, cut or paste in this box" width = "100%" padding = 10 font size = "1rem" box sizing = "border-box" end
paragraph: id = "stats" content = "copied 0 times, cut 0 times" end
paragraph:
content = "Last pasted text: "
span: id = "last" content = "none" font weight = bold end
end
endThe paste handler passes the string to the page without touching it: dom.CallS1 returns it as a JavaScript value, and dom.SetProp sets the textContent of the span with it. Select all in the box with Ctrl+A, then copy, cut and paste, and watch the counters and the last line.
Details, popovers, dialogs and find in page
Some elements open and close by themselves, and they have events for it. toggle fires on a details element after it opens or closes, and on an element with the popover attribute after it is shown or hidden. A popover event carries newState, which is the text open or closed. The event fires a moment after the change, not at the same time. before toggle fires first, can be cancelled, and exists for popovers (and, in newer browsers, for dialogs). The example below shows the state that is about to start, newState, before the popover changes, and the state it reached after.
import dom
func opened:
if dom.GetBool(dom.ById("more"), "open"):
dom.SetText(dom.ById("dstate"), "The details are open.")
else:
dom.SetText(dom.ById("dstate"), "The details are closed.")
end
end
func popped(e: i64):
dom.SetText(dom.ById("pstate"), "The popover is now " + dom.GetStr(e, "newState") + ".")
end
func about_to(e: i64):
dom.SetText(dom.ById("before"), "before toggle: about to be " + dom.GetStr(e, "newState") + ".")
end
page:
title = "Toggle"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
details:
id = "more"
toggle = opened
summary: content = "More about Salam" end
paragraph: content = "Salam is a language for the web and for programs." end
end
paragraph: id = "dstate" content = "The details are closed." end
button: popover target = "tip" content = "Show a tip" padding = "8px 16px" end
box: id = "tip" popover = auto before toggle = about_to toggle = popped content = "Tips close when you press Escape." padding = 14 border radius = 8 end
paragraph: id = "before" content = "No popover yet." end
paragraph: id = "pstate" content = "" end
endA dialog opened with showModal() is shown over the page and blocks everything behind it until it closes. dom.Call0(dialog, "showModal") opens it. When the user presses Escape the dialog sends cancel, which can be prevented to keep it open, and then it closes and sends close. A dialog closed by code, with dom.CallS1(dialog, "close", "deleted"), sends only close, and the text you passed is its returnValue.
import dom
func open_dialog:
dom.SetText(dom.ById("log"), "Dialog is open.")
_r := dom.Call0(dom.ById("dlg"), "showModal")
end
func canceled:
dom.SetText(dom.ById("log"), "cancel fired (Escape was pressed).")
end
func finished:
dom.SetText(dom.ById("end"), "close fired, return value: " + dom.GetStr(dom.ById("dlg"), "returnValue"))
end
func confirm_it:
_r := dom.CallS1(dom.ById("dlg"), "close", "deleted")
end
page:
title = "Dialog"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
button: id = "open" click = open_dialog content = "Delete the file" padding = "8px 16px" end
dialog:
id = "dlg"
cancel = canceled
close = finished
paragraph: content = "Delete this file for good?" end
button: id = "yes" click = confirm_it content = "Delete" padding = "6px 14px" end
end
paragraph: id = "log" content = "Nothing yet." end
paragraph: id = "end" content = "" end
endThe last event is before match. An element with hidden = "until-found" is not drawn, but its text stays searchable. When the user finds it with the browser's find in page, or opens a link to its id, the browser shows it and sends before match just before. A handler can open the section around it, such as a closed accordion, so the match is visible. In the example, try Ctrl+F and look for pomegranate, or open the page with #secret at the end of its address. Not all browsers support it yet; in those the text simply stays hidden until a script shows it. Chrome does not run an inline onbeforematch attribute even though it fires the event, so this one event is registered from a script element with addEventListener, as the example does. The attribute before match is the spelling the standard defines, and where a browser honours it you can use it instead.
page:
title = "Find in page"
global: selector = "body" font family = "system-ui, sans-serif" margin = "1.5rem" end
paragraph: id = "state" content = "The text below stays hidden until the browser finds it." end
box: id = "secret" hidden = "until-found" content = "The secret word is pomegranate." padding = 14 background = "#fef3c7" border radius = 8 end
script: content = "document.getElementById('secret').addEventListener('beforematch', function () { document.getElementById('state').textContent = 'before match fired: the browser is revealing the text'; });" end
endReference: every keyboard and form event
Each row gives the attribute, the HTML attribute it becomes and when it fires. The snippets are checked by the compiler. Events that need the event object itself use a JavaScript string in these small snippets. The snippet for before match uses a script element for the reason given in the last section.
| Attribute | When it fires | Example |
|---|---|---|
key downفشردن کلید onkeydown | Runs when a key goes down on the focused element, and again for each repeat while the key is held (repeat is then true). dom.EventKey(e) is the key, such as a, Enter or ArrowUp. | input: key down = (): dom.SetText(dom.ById("out"), "A key went down") end placeholder = "Press a key here" end
paragraph: id = "out" content = "-" end |
key upرها کردن کلید onkeyup | Runs when the key comes back up. There is exactly one key up for each press, however long the key was held. | input: key up = (): dom.SetText(dom.ById("out"), "A key came up") end placeholder = "Press and release a key" end
paragraph: id = "out" content = "-" end |
key pressفشار کلید onkeypress | Deprecated. Fires after key down for keys that produce a character, and not for Escape or the arrows. Use key down in new code. | input: key press = (): dom.SetText(dom.ById("out"), "A character key was pressed") end placeholder = "Type a letter" end
paragraph: id = "out" content = "-" end |
focusفوکوس onfocus | Runs when the element gains focus, by a click, the Tab key or a script. It does not bubble; focus in is the bubbling version. | input: focus = (): dom.SetText(dom.ById("out"), "The field has focus") end placeholder = "Click or Tab into me" end
paragraph: id = "out" content = "-" end |
blurاز دست رفتن فوکوس onblur | Runs when the element loses focus. A good moment to check what the user typed. It does not bubble. | input: blur = (): dom.SetText(dom.ById("out"), "The field lost focus") end placeholder = "Click in, then click away" end
paragraph: id = "out" content = "-" end |
focus inورود فوکوس onfocusin | Like focus, but it bubbles, so one handler on a group hears the focus of every field inside. | box: focus in = (): dom.SetText(dom.ById("out"), "Focus is inside the group") end input: placeholder = "First" end input: placeholder = "Second" end end
paragraph: id = "out" content = "-" end |
focus outخروج فوکوس onfocusout | Like blur, but it bubbles. event.relatedTarget is the element receiving the focus next, which lets you tell leaving the group from moving inside it. | box: focus out = (): dom.SetText(dom.ById("out"), "Focus left a field of the group") end input: placeholder = "First" end input: placeholder = "Second" end end
paragraph: id = "out" content = "-" end |
inputورودی oninput | Runs after every change to the value of a field: typing, deleting, pasting, dropping, undoing. The right event for live features such as counters and filters. | input: input = (): dom.SetText(dom.ById("out"), "You typed " + dom.GetValue(dom.ById("box")).len() + " characters") end id = "box" placeholder = "Type here" end
paragraph: id = "out" content = "-" end |
before inputپیش از ورودی onbeforeinput | Runs before the value changes and can be cancelled, which refuses the edit. event.data holds the text about to be inserted. | input: before input = "if (event.data === ' ') { event.preventDefault(); }" placeholder = "No spaces allowed" end |
changeتغییر onchange | Runs when the user has finished a change: Enter in a one line field, or the field losing focus after its value changed. For checkboxes, radio buttons and lists it runs at once. | input: change = (): dom.SetText(dom.ById("out"), "The value was committed") end placeholder = "Type, then click away" end
paragraph: id = "out" content = "-" end |
selectانتخاب onselect | Runs on a text field when the user selects some of its text. The range is in the properties selectionStart and selectionEnd. | input: select = (): dom.SetText(dom.ById("out"), "Some text was selected") end value = "Select part of this text" end
paragraph: id = "out" content = "-" end |
selection changeتغییر انتخاب onselectionchange | Runs when the selection or the caret changes in a text field. Support on the field itself is newer; some browsers send it only to the document. | text area: selection change = (): dom.SetText(dom.ById("out"), "The caret moved") end content = "Click inside this text" end
paragraph: id = "out" content = "-" end |
select startشروع انتخاب onselectstart | Runs when the user starts a new selection, in any element, editable or not. It can be cancelled to forbid selecting. | paragraph: select start = (): dom.SetText(dom.ById("out"), "A selection began") end content = "Select some of this text." end
paragraph: id = "out" content = "-" end |
submitارسال onsubmit | Runs when a form is submitted by its submit button or by Enter in a field, after the browser's checks passed. Call dom.PreventDefault(e) to stop the browser from sending the form and loading a new page. | form: submit = "event.preventDefault(); document.getElementById('out').textContent = 'Sent: ' + this.elements.name.value" input: name = "name" value = "Sara" end button: content = "Send" end end
paragraph: id = "out" content = "-" end |
resetبازنشانی onreset | Runs when the form is reset, by a reset button or by form.reset(), just before the fields go back to their starting values. It can be cancelled. | form: reset = (): dom.SetText(dom.ById("out"), "The form was reset") end input: value = "Change me" end button: type = reset content = "Reset" end end
paragraph: id = "out" content = "-" end |
invalidنامعتبر oninvalid | Runs on a field that fails the browser's checks (required, type = email, pattern, ...) when the form is submitted or when checkValidity() or reportValidity() is called. It does not bubble, so it goes on the field. | form: input: required = true invalid = (): dom.SetText(dom.ById("out"), "This field is required") end placeholder = "Required" end button: type = button click = "this.form.reportValidity()" content = "Check" end end
paragraph: id = "out" content = "-" end |
form dataداده فرم onformdata | Runs on a form each time the browser builds its field list: on a submit, and on new FormData(form). event.formData may be changed before it is used. | form: id = "f" form data = (): dom.SetText(dom.ById("out"), "The form data was built") end input: name = "q" value = "x" end button: type = button click = "new FormData(document.getElementById('f'))" content = "Build" end end
paragraph: id = "out" content = "-" end |
copyرونوشت oncopy | Runs when the user copies. A handler may call preventDefault and set its own text on the clipboard with clipboardData.setData. | input: copy = (): dom.SetText(dom.ById("out"), "Copied") end value = "Select me and press Ctrl+C" end
paragraph: id = "out" content = "-" end |
cutبرش oncut | Runs when the user cuts the selection, before the text is removed. | input: cut = (): dom.SetText(dom.ById("out"), "Cut") end value = "Select me and press Ctrl+X" end
paragraph: id = "out" content = "-" end |
pasteچسباندن onpaste | Runs when the user pastes, before the text is inserted. event.clipboardData.getData('text') is the pasted text. | input: paste = (): dom.SetText(dom.ById("out"), "Pasted") end placeholder = "Press Ctrl+V here" end
paragraph: id = "out" content = "-" end |
toggleتغییر حالت ontoggle | Runs after a details element opens or closes, and after a popover is shown or hidden (newState is open or closed). It fires a moment after the change. | details: toggle = (): dom.SetText(dom.ById("out"), "The details changed") end summary: content = "More" end paragraph: content = "Hidden text" end end
paragraph: id = "out" content = "-" end |
before toggleپیش از تغییر حالت onbeforetoggle | Runs just before a popover (and, in newer browsers, a dialog) opens or closes, and can be cancelled. event.newState is the state about to start. | button: popover target = "tip" content = "Show" end
box: id = "tip" popover = auto before toggle = (): dom.SetText(dom.ById("out"), "About to change") end content = "A popover" end
paragraph: id = "out" content = "-" end |
cancelانصراف oncancel | Runs on a dialog when the user asks to close it with Escape. It can be cancelled to keep the dialog open. A dialog closed by code does not send it. | button: click = "document.getElementById('d').showModal()" content = "Open" end
dialog: id = "d" cancel = (): dom.SetText(dom.ById("out"), "Escape was pressed") end paragraph: content = "Press Escape" end end
paragraph: id = "out" content = "-" end |
closeبسته شدن onclose | Runs on a dialog after it has closed, by Escape, by close() or by a form with method = dialog. Its returnValue is the text passed to close(). | button: click = "document.getElementById('d').showModal()" content = "Open" end
dialog: id = "d" close = (): dom.SetText(dom.ById("out"), "The dialog closed") end button: click = "this.parentNode.close('ok')" content = "Close" end end
paragraph: id = "out" content = "-" end |
before matchپیش از تطبیق onbeforematch | Runs on an element with hidden = "until-found" just before the browser reveals it because find in page or a link to its id matched. A handler can open the section around it. Chrome fires the event but ignores the inline attribute, so register it from a script element with addEventListener('beforematch', ...). | paragraph: id = "out" content = "Find the word below with Ctrl+F." end
box: id = "s" hidden = "until-found" content = "Hidden word: pomegranate" end
script: content = "document.getElementById('s').addEventListener('beforematch', function () { document.getElementById('out').textContent = 'Found it'; });" end |
Try it yourself
- Make the task list refuse an empty task and show a red message under the field instead. Clear the message the next time the user types.
- Let the ball in the arrow example move with the keys W, A, S and D as well, using
codeso that it works on every layout. - Add to the search box a line that says how many items match. Update it from the same handler.
- Write a sign up form that needs two passwords. On
inputin either field, compare them and usesetCustomValidityso that the form cannot be sent while they differ.