-
Notifications
You must be signed in to change notification settings - Fork 141
/
docs.md
2278 lines (1663 loc) · 76.2 KB
/
docs.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<style>
:root {
--line-length: 100ch;
}
#toc sub-title {
display: inline-block;
}
#toc li {
margin: calc(.5*var(--gap));
}
@media (min-width: 120ch) {
#toc-wrapper {
font-size: .9em;
float: left;
position: sticky;
top: 0;
border: none;
margin: 0;
}
#toc {
overflow: auto;
max-height: 100vh;
margin-inline-end: var(--gap);
margin-inline-start: calc(2*var(--gap) - var(--gutter-width));
}
#toc li {
margin: calc(.25*var(--gap));
}
#docs-content {
display: flow-root;
max-width: calc(100vw - 24ch - 3 * var(--gap));
}
#skip-to-content {
display: none;
}
}
#docs-content:target {
outline: none;
}
</style>
<header id="toc-wrapper" aria-labelledby="contents-h">
<div id=toc>
# _hyperscript <sub-title>documentation</sub-title> {.h2}
[Skip to content](#docs-content){#skip-to-content}
<nav aria-label="Table of contents">
[[toc]]
</nav>
</div>
</header>
<div id="docs-content">
## Introduction
Hyperscript is a scripting language for doing front end web development. It is designed to make it very easy to
respond to events and do simple DOM manipulation in code that is directly embedded on elements on a web page.
Here is a simple example of some hyperscript:
{% example %}
<button _="on click toggle .red on me">
Click Me
</button>
{% endexample %}
<style>
.red {
background: rgba(255,0,0,0.48) !important;
}
</style>
The first thing to notice is that hyperscript is defined *directly on the button*, using the `_` (underscore) attribute.
Embedding code directly on the button like this might seem strange at first, but hyperscript is one of a growing number
of technologies that de-emphasize [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns)
in favor of [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour/).
Other examples of libraries going this direction are [Tailwind CSS](https://tailwindcss.com/),
[AlpineJS](https://alpinejs.dev) and [htmx](https://htmx.org).
The next thing you will notice about hyperscript is its syntax, which is very different than most programming languages
used today. Hyperscript is part of the [xTalk](https://en.wikipedia.org/wiki/XTalk) family of scripting languages, which
ultimately derive from [HyperTalk](https://hypercard.org/HyperTalk%20Reference%202.4.pdf). These languages all read more
like english than the programming languages you are probably used to.
This unusual syntax has advantages, once you get over the initial shock:
* It is very distinctive, making it obvious when hyperscript is being used in a web page
* It is very easy to read, making it obvious what a script is doing
Hyperscript favors read time over write time when it comes to code. It can be a bit tricky to write at first
for some people who are used to other programming languages, but it reads very clearly once you are done.
Code is typically read many more times than it is written, so this tradeoff is a good one for simple
front end scripting needs.
Below you will find an overview of the various features, commands and expressions in hyperscript, as well as links to
more detailed treatments of each them.
Some other hypserscript resources you may want to check out are:
* The [cookbook](/cookbook) for existing hyperscripts you can start using and modifying for your own needs.
* The [VanillaJS/jQuery/hyperscript comparison](/comparison), which shows the differences between vanillajs, jQuery
and hyperscript implementations of various common UI patterns
* Syntax highlighting for [VSCode](https://marketplace.visualstudio.com/items?itemName=dz4k.vscode-hyperscript-org) or [Sublime](https://packagecontrol.io/packages/Hyperscript)
OK, let's get started with hyperscript!
## Install & Quick Start {#install}
Hyperscript is a dependency-free JavaScript library that can be included in a web page without any build step:
~~~ html
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
~~~
After you've done this, you can begin adding hyperscript to elements:
~~~ html
<div _="on click call alert('You clicked me!')">
Click Me!
</div>
~~~
You can also add hyperscript within script tags that are denoted as `text/hyperscript`:
~~~ html
<script type="text/hyperscript">
on mousedown
halt the event -- prevent text selection...
-- do other stuff...
end
</script>
~~~
Features defined in script tags will apply to the `body`.
Hyperscript has an open, pluggable grammar & some advanced features do not ship by default (e.g. [workers](#workers)).
To use a feature like workers you can either:
* install the extension directly by including `/dist/workers.js` after you include hyperscript
* use the "Whole 9 Yards" version of hyperscript, which includes everything by default and can be
found at `/dist/hyperscript_w9y.js`
## Language Basics {#basics}
A hyperscript script consists of a series of ["features"](/reference#features), the most common of which is an
event handler, as we saw in the first example. The body of a feature then consists of a series of
["commands"](/reference#commands), which are often called statements in other languages. These commands may include
one or more ["expressions"](/reference#expressions).
Going back to our original example:
~~~ html
<button _="on click toggle .red on me">
Click Me
</button>
~~~
In the script above:
* The `on click` is an [event handler feature](/features/on)
* [`toggle`](/commands/toggle) is a command
* `.red` and `me` are expressions that are part of the `toggle` command
All hyperscript scripts are made up of these basic building blocks.
It's worth mentioning that, if you prefer, you can use `script` or `data-script`
instead of `_` when using hyperscript:
{% example %}
<button script="on click toggle .red on me">
Click Me
</button>
{% endexample %}
### Comments
Comments in hyperscript start with the `--` characters and a whitespace character (space, tab, carriage return or newline) and go to the end of the line:
~~~ hyperscript
-- this is a comment
log "Yep, that was a comment"
~~~
To ease migrations to hyperscript, `//` and `/* ... */` comments are supported.
### Separators
Multiple commands may be optionally separated with a `then`, which acts like a semi-colon in JavaScript:
~~~ hyperscript
log "Hello" then log "World"
~~~
Using the `then` keyword is recommended when multiple commands are on the same line.
When commands have bodies that include other commands, such as
with the [`if`](/commands/if) command, the series of commands are terminated by an `end`:
~~~ hyperscript
if x > 10 -- start of the conditional block
log "Greater than 10"
end -- end of the conditional block
~~~
Features are also terminated by an `end`:
~~~ hyperscript
on click
log "Clicked!"
end
~~~
The `end` terminator can often be omitted for both features and statements if either of these conditions hold:
* The script ends:
~~~ html
<button _="on click if true log 'Clicked!'">
Click Me
</button>
~~~
* Another feature starts:
~~~ html
<button _="on click if true log 'Clicked!'
on mouseenter log 'Mouse entered!'">
Click Me
</button>
~~~
In practice, `end` is used only when necessary, in order to keep scripts small and neat.
### Expressions
Many expressions in hyperscript will be familiar to developers and are based on expressions available in JavaScript:
* Number literals - `1.1`
* String literals = `"hello world"`
* Array literals = `[1, 2, 3]`
Others are a bit more exotic and, for example, make it easy to work with the DOM:
* ID References: `#foo`
* Class References: `.tabs`
* Query References: `<div/>`
* Attribute References: `@count`
We will see how features, commands and expressions all fit together and what they can do in the coming sections.
### Variables {#variables}
In hyperscript, variables are created by the [`set`](/commands/set) or [`put`](/commands/put) commands,
with `set` being preferred.
Here is how you create a simple, local variable:
~~~ hyperscript
set x to 10
~~~
Here is an example that creates a local variable and then logs it to the console:
{% example "Local variable" %}
<button _="on click set x to 10 then log x">
Click Me
</button>
{% endexample %}
If you click this button and open up the console, you should see `10` being logged to it.
#### Scoping {#scoping}
hyperscript has three different variable scopes: `local`, `element`, and `global`.
* Global variables are globally available (and should be used sparingly)
* Element variables are scoped to the element they are declared on, but shared across all features on that element
* Local scoped variables are scoped to the currently executing feature
Note that hyperscript has a flat local scope, similar to JavaScript's `var` statement.
#### Variable Names & Scoping {#names_and_scoping}
In order to make non-locally scoped variables easy to create and recognize in code, hyperscript
supports the following naming conventions:
* If a variable starts with the `$` character, it will default to the global scope
* If a variable starts with the `:` character, it will default to the element scope
By using these prefixes it is easy to tell differently scoped variables from one another without a lot of additional
syntax:
~~~ hyperscript
set $foo to 10 -- sets a global named $foo
set :bar to 20 -- sets an element scoped variable named :bar
~~~
Here is an example of a click handler that uses an element scoped variable to maintain a counter:
{% example %}
<button _="on click increment :x then put it into the next <output/>">
Click Me
</button>
<output>--</output>
{% endexample %}
This script also uses the implicit `it` symbol, which we will discuss [below](#special-names).
#### Scoping Modifiers {#scoping_modifiers}
You may also use scope modifiers to give symbols particular scopes:
* A variable with a `global` prefix is a global
~~~ hyperscript
set global myGlobal to true
~~~
* A variable with an `element` prefix is element-scoped
~~~ hyperscript
set element myElementVar to true
~~~
* A variable with a `local` prefix is locally scoped
~~~ hyperscript
set local x to true
~~~
#### Attributes
In addition to scoped variables, another way to store data is to put it directly in the DOM, in an attribute of an
element.
You can access attributes on an element with the attribute literal syntax, using an `@` prefix:
~~~ hyperscript
set @my-attr to 10
~~~
This will store the value 10 in the attribute `my-attr` on the current element:
~~~ html
<div my-attr="10"></div>
~~~
Note that, unlike regular variables, attributes can only store strings. Anything else you store in them will be converted
to a string.
You can remember the `@` sign as the **at**tribute operator. We will discuss other DOM literals [below](#dom-literals).
Here is the above example, rewritten to use an attribute rather than an element-scoped variable:
{% example %}
<button _="on click increment @my-attr then put it into the next <output/>">
Click Me
</button>
<output>--</output>
{% endexample %}
If you click the above button a few times and then inspect it using your browsers developer tools, you'll note that it
has a `my-attr` attribute on it that holds a string value of the click count.
The [`increment` command](/commands/increment) is discussed [below](#math).
#### Special Names & Symbols
One of the interesting aspects of hyperscript is its use of implicit names for things, often with multiple ways
to refer to the same thing. This might sound crazy, and it kind of is, but it helps to make scripts much more
readable!
We have already seen the use of the `it` symbol above, to put the result of an `increment` command into an
element.
It turns out that `it` is an alias for `result`, which we could have used instead:
{% example "It" %}
<button _="on click increment :x then put result into the next <output/>">
Click Me
</button>
<output>--</output>
{% endexample %}
It may be equivalent, but it doesn't read as nicely does it?
That's why hyperscript supports the `it` symbol as well.
Another funny thing you might have noticed is the appearance of `the` in this script.
`the` is whitespace before any expression in hyperscript and can be used to make your code read more nicely.
For example, if we wanted to use `result` rather than it, we would write `the result` instead, which reads more nicely:
{% example "The" %}
<button _="on click increment :x then put the result into the next <output/>">
Click Me
</button>
<output>--</output>
{% endexample %}
This is exactly equivalent to the previous example, but reads better. Hyperscript is all about readability!
In this case, we'd probably stick with `it` :)
##### The Hyperscript Zoo {#zoo}
In addition to `result` and `it`, hyperscript has a number of other symbols that are automatically available, depending
on the context, that make your scripting life more convenient.
Here is a table of available symbols:
{% syntaxes %}
`result` `it` `its`
the result of the last command, if any (e.g. a `call` or `fetch`)
`me` `my` `I`
the element that the current event handler is running on
`event`
the event that triggered the event current handler, if any
`body`
the body of the current document, if any
`target`
the target of the current event, if any
`detail`
the detail of the event that triggered the current handler, if any
`sender`
the element that sent the current event, if any
{% endsyntaxes %}
Note that the `target` is the element that the event *originally* occurred on.
Event handlers, discussed [below](#on), may be placed on parent elements to take advantage
of [event bubbling](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture)
which can reduce redundancy in code.
### Logging To The Console
If you wish to print something to the `console` you can use the [`log` command](/commands/log):
~~~ hyperscript
log "Hello Console!"
~~~
Simplicity itself.
#### Objects
Hyperscript is not an object-oriented language: it is, rather, event-oriented. However it still allows you to work with
objects in an easy and convenient manner, which facilitates interoperability with all the functionality of JavaScript,
including the DOM APIs, JavaScript libraries and so on.
Here is how you can work with objects in hyperscript:
#### Properties
Hyperscript offers a few different ways to access properties of objects. The first two should be familiar
to JavaScript developers:
~~~ hyperscript
set x to {name : "Joe", age: 35} -- create an object with some properties
log x.name -- standard "dot" notation
log x['name'] -- standard array-index notation
~~~
The next mechanism is known as a [possessive expression](/expressions/possessive) and uses the standard english `'s`
to express a property access:
~~~ hyperscript
set x to {name : "Joe", age: 35} -- create an object with some properties
log x's name -- access the name property using a possessive
~~~
There are two special cases for the possessive expression, the symbols `my` and `its`, both of which
can be used without the `'s` for possessive expressions:
~~~ hyperscript
get the first <div/> then -- get the first div in the DOM, setting the `results` variable
set my innerHTML to its innerHTML -- use possessive expressions to set the current elements innerHTML
-- to the innerHTML of that div
~~~
Finally, you can also use the [of expression](/expressions/of) to get a property as well:
~~~ hyperscript
set x to {name : "Joe", age: 35} -- create an object with some properties
log the name of x -- access the name property using an of expression
~~~
The `of` operator flips the order of the property & the element that the property is on, which can sometimes
clarify your code.
Which of these options you choose for property access is up to you. We recommend the possessive form in
most cases as being the most "hyperscripty", with the `of` form being chosen when it helps to clarify some code by
putting the final property at the front of the expression.
##### Flat Mapping
Inspired by [jQuery](https://jquery.org), another feature of property access in hyperscript is that, when a property of an
Array-like object is accessed, it will [flat-map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)
the results to a single, linear array of that property applied to all values within the array.
~~~ hyperscript
set allDivs to <div/> -- get all divs
set allParents to the parent of allDivs -- get all parents of those divs as an array
set allChildren to the children of allDivs -- get all children of those divs as an array
~~~
On an array, only the `length` property will not perform a flat map in this manner.
##### Null Safety
Finally, all property accesses in hyperscript are null safe, so if the object that the property is being accessed on
is null, the result of the property access will be null as well, without a need to null-check:
~~~ hyperscript
set example to null
log example.prop -- logs null, because `example` is null
~~~
This null-safe behavior is appropriate for a scripting language intended for front-end work.
#### Creating New Objects
If you want to make new objects, you can use the [`make` command](/commands/make):
~~~ hyperscript
make a URL from "/path/", "https://origin.example.com"
~~~
Which is equal to the JavaScript `new URL("/path/", "https://origin.example.com")`
If you wish to assign an identifier to the new object you can use the ` called ` modifier:
~~~ hyperscript
make a URL from "/path/", "https://origin.example.com" called myURL
log myURL
~~~
You can also use [`query literals`](/expressions/query_references), discussed [below](#dom_literals), to create new HTML content:
~~~ hyperscript
make an <a.navlink/> then put it after me
~~~
#### Arrays
Hyperscript arrays work very similarly to JavaScript arrays:
~~~ hyperscript
set myArr to [1, 2, 3]
log myArr[0] -- logs "1"
~~~
You can use the `first`, `last` and `random` keywords, discussed [below](#positional), with arrays:
~~~ hyperscript
set myArr to [1, 2, 3]
log the first of myArr -- logs "1"
log the last of myArr -- logs "3"
log random in myArr -- logs a random element from the array
~~~
##### Closures
Hyperscript does not encourage the use of closures or callbacks nearly as much as JavaScript. Rather, it uses
[async transparency](#async) to handle many of the situations in which JavaScript would use them.
However, there is one area where closures provide a lot of value in hyperscript: data structure manipulation. The
hyperscript syntax for closures is inspired by [haskell](https://www.haskell.org/), starting with a `\` character,
then the arguments, then an arrow `->`, followed by an expression:
~~~ hyperscript
set strs to ["a", "list", "of", "strings"]
set lens to strs.map( \ s -> s.length )
log lens
~~~
### Control Flow
Conditional control flow in hyperscript is done with the [if command](/commands/if) or the `unless` modifier. The conditional expression
in an if statement is not parenthesized. Hyperscript uses `end` rather than curly-braces to delimit the conditional body.
The else-branch can use either the `else` keyword or the `otherwise` keyword.
{% example '"If" command' %}
<button _="on click increment :x
if :x <= 3
put :x into the next <output/>
else
put '3 is the max...' into the next <output/>
end">
Click Me
</button>
<output>--</output>
{% endexample %}
As mentioned in the introduction, `end` is often omitted when it isn't needed in order to make scripts smaller:
~~~ html
<button _="on click increment :x
if :x < 3
put :x into the next <output/>
otherwise
put '3 is the max...' into the next <output/>">
Click Me
</button>
<output>--</output>
~~~
You can chain `if/else` commands together in the usual manner.
All commands also support an `unless` modifier to conditionally execute them.
This allows for a very succinct way of expressing branching logic.
~~~ html
<button _="on click set error to functionCouldReturnError()
log error unless no error">
Log Result
</button>
~~~
See in the following example how the `.bordered` class is used to alter the behaviour of the second button.
{% example '"unless" modifier' %}
<button _="on click toggle .bordered on #second-button">
Toggle Next Border
</button>
<button id="second-button"
_="on click toggle .red unless I match .bordered">
Toggle My Background
</button>
{% endexample %}
<style>
.bordered {
border-width: thick;
border-color: green;
border-style: solid;
}
</style>
#### Comparisons & Logical Operators
In addition to the usual comparison operators from JavaScript, such as `==` and `!=`, hyperscript
supports [a rich set of natural language style comparisons](/expressions/comparison-operator) for use in `if` commands:
A small sampling is shown below:
{% syntaxes %}
`[[a]] is [[b]]`
Same as {%syntax "[[a]] == [[b]]"%}.
`[[a]] is not [[b]]`
Same as {%syntax "[[a]] != [[b]]"%}.
`no [[a]]`
Same as {%syntax "[[a]] == null or [[a]] == undefined or [[a.length]] == 0"%}.
`[[element]] matches [[selector]]`
Does a CSS test, i.e. `if I match .selected`.
`[[a]] exists`
Same as {%syntax "not (no [[a]])"%}.
`[[x]] is greater than [[y]]`
`[[x]] is less than [[y]]`
Same as `>` and `<`, respectively.
`[[collection]] is empty`
Tests if a collection is empty.
{% endsyntaxes %}
If the left hand side of the operator is `I`, then `is` can be replaced with `am`:
~~~ hyperscript
get chosenElement()
if I am the result then remove me end
~~~
Using these natural language alternatives allows you to write very readable scripts.
Comparisons can be combined via the `and`, `or` and `not` expressions in the usual manner:
~~~ hyperscript
if I am <:checked/> and the closest <form/> is <:focus/>
add .highlight to the closest <form/>
~~~
#### Loops {#loops}
The [repeat command](/commands/repeat) is the looping mechanism in hyperscript.
It supports a large number of variants, including a short hand `for` version:
~~~ hyperscript
-- a basic for loop
repeat for x in [1, 2, 3]
log x
end
-- you may omit the 'repeat' keyword for a for loop
for x in [1, 2, 3]
log x
end
-- you may repeat without an explicit loop variable and use
-- the implicit `it` symbol
repeat in [1, 2, 3]
log it
end
-- you may use a while clause to loop while a condition is true
repeat while x < 10
log x
end
-- you may use an until clause to loop until a condition is true
repeat until x is 10
log x
end
-- you may use the times clause to repeat a fixed number of times
repeat 3 times
log 'looping'
end
-- you may use the index clause on any of the above
-- to bind the loop index to a given symbol
for x in [1, 2, 3] index i
log i, "is", x
end
-- you can loop forever if you like
repeat forever
if I match :focus
break
end
wait 2s
end
~~~
Loops support both the [`break`](/commands/break) and [`continue`](/commands/continue) commands.
You can also use events to signal when a loop ends, see [the async section on loops](#async_loops)
#### Aggregate Operations {#aggregate_operations}
Note that loops are often not required in hyperscript. Many commands will automatically deal with arrays and
collections for you.
For example, if you want to add the class `.foo` to all elements that have the class `.bar` on it, you can simply
write this:
~~~ hyperscript
add .foo to .bar
~~~
The [`add`](/commands/add) command will take care of looping over all elements with the class `.bar`.
No need to loop explicitly over the results.
### Math Operations {#math}
Hyperscript supports most of the regular math operators:
~~~ hyperscript
set x to 10
set y to 20
set sum to x + y
set diff to x - y
set product to x * y
~~~
with one exception, the modulo operator uses the keyword `mod`:
~~~ hyperscript
set x to 10 mod 3
~~~
Hyperscript does not have a notion of mathematical operator precedence. Instead, math operators must be fully
parenthesized when used in combination with other math operators:
~~~ hyperscript
set x to 10
set y to 20
set sumOfSquares to (x * x) + (y * y)
~~~
If you did not fully parenthesize this expression it would be a parse error.
This clarifies any mathematical logic you are doing and encourages simpler expressions, which, again helps
readability.
Hyperscript also offers an [`increment`](/commands/increment) and [`decrement`](/commands/decrement) command for modifying
numbers:
~~~ hyperscript
set x to 1
increment x
puts x -- prints 2 to the console
~~~
A nice thing about the `increment` and `decrement` commands is that they will automatically handle string to number
conversions and, therefore, can be used with numbers stored in attributes on the DOM:
~~~ hyperscript
on click
increment @data-counter
if @data-counter as Int is greater than 4
add @disabled -- disable after the 5th click
~~~
### Strings {#strings}
Hyperscript supports strings that use either a single quotes or double quotes:
~~~ hyperscript
set hello to 'hello'
set world to "world"
set helloWorld to hello + " " + world
~~~
and also supports JavaScript style template strings:
~~~ hyperscript
set helloWorld to `${hello} ${world}`
~~~
The [`append`](/commands/append) command can append content to strings (as well as to arrays and the DOM):
~~~ hyperscript
get "hello" -- set result to "hello"
append " world" -- append " world" to the result
log it -- log it to the console
~~~
### Conversions {#conversions}
To convert values between different types, hyperscript has an [`as` operator](/expressions/as):
{% example "Converting Values" %}
<button _="on click
get the (value of the next <input/>) as an Int
increment it
put it into the next <output/>">
Add 1 To :
</button>
<input/>
<output>--</output>
{% endexample %}
Here we get the input value, which is a String, and we convert it to an Integer. Note that we need to use parenthesis
to ensure that the `as` expression does not bind too tightly.
We then increment the number and put it into the next `output` element.
Out of the box hyperscript offers a number of useful conversions:
* `Array` - convert to Array
* `Date` - convert to Date
* `Float` - convert to float
* `Fragment` - converts a string into an HTML Fragment
* `HTML` - converts NodeLists and arrays to an HTML string
* `Int` - convert to integer
* `JSON` - convert to a JSON String
* `Number` - convert to number
* `Object` - convert from a JSON String
* `String` - convert to String
* `Values` - converts a Form (or other element) into a struct containing its input names/values
* `Fixed<:N>` - convert to a fixed precision string representation of the number, with an optional precision of `N`
You can also [add your own conversions](/expressions/as) to the language as well.
### Calling Functions {#calling-functions}
There are many ways to invoke functions in hyperscript. Two commands let you invoke a function and automatically
assign the result to the `result` variable: [`call` and `get`](/commands/call):
~~~ hyperscript
call alert('hello world!')
get the nextInteger() then log it -- using the 'it' alias for 'result`
~~~
You can also invoke functions as stand-alone commands:
~~~ hyperscript
log "Getting the selection"
getSelection()
log "Got the selection"
log it
~~~
Finally, you can use the [`pseudo-command`](/commands/pseudo-commands) syntax, which allows you to put the method
name first on the line in a method call, to improve readability in some cases:
~~~ hyperscript
reload() the location of the window
writeText('evil') into the navigator's clipboard
reset() the #contact-form
~~~
These are called "pseudo-commands" because this syntax makes method calls look like a normal command in hyperscript.
### Events & Event Handlers {#event}
[Events](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events) are at the core of hyperscript,
and [event handlers](/features/on) are the primary entry point into most hyperscript code.
hyperscript's event handlers allow you to respond to any event (not just DOM events, as with `onClick` handlers) and
provide a slew of features for making working with events easier.
Here is an example:
{% example "Event Handlers" %}
<style>
.clicked::after {
content: " ... Clicked!"
}
</style>
<button _="on click add .clicked">
Add The "clicked" Class To Me
</button>
{% endexample %}
The script above, again, found on the `_` attribute, does, well, almost exactly what it says:
> On the 'click' event for this button, add the 'clicked' class to this button
This is the beauty of hyperscript: you probably knew what it was doing immediately, when reading it.
Event handlers have a *very* extensive syntax that allows you to, for example:
* Control the queuing behavior of events (how do you want events to queue up when an event handler is running?)
* Respond to events only in certain cases, either with counts (e.g. `on click 1`) or with event filters (`on keyup[key is 'Escape']`)
* Control debounce and throttle behavior
* Respond to events from other elements or from `elsewhere` (i.e. outside the current element)
You can read all the gory details on the [event handler](/features/on) page, but chances are, if you want some special
handling of an event, hyperscript has a nice, clear syntax for doing so.
#### Event Queueing {#event_queueing}
By default, the event handler will be run synchronously, so if the event is triggered again before the event handler
finished, the new event will be queued and handled only when the current event handler finishes.
You can modify this behavior in a few different ways:
##### The Every Modifier {#on_every}
An event handler with the `every` modifier will execute the event handler for every event that is received,
even if the preceding handler execution has not finished.
~~~ html
<button _="on every click add .clicked">
Add The "clicked" Class To Me
</button>
~~~
This is useful in cases where you want to make sure you get the handler logic for every event going immediately.
##### The Queue Modifier {#on_queue}
The `every` keyword is a prefix to the event name, but for other queuing options, you postfix the event name
with the `queue` keyword.
You may pick from one of four strategies:
* `none` - Any events that arrive while the event handler is active will be dropped
* `all` - All events that arrive will be added to a queue and handled in order
* `first` - The first event that arrives will be queued, all others will be dropped
* `last` - The last event that arrives will be queued, all others will be dropped
`queue last` is the default behavior
{% example "Queue All" %}
<button _="on click queue all
increment :count
wait 1s then put it into the next <output/>">
Click Me Quickly...
</button>
<output>--</output>
{% endexample %}
If you click quickly on the button above you will see that the count slowly increases as each event waits 1 second and
then completes, and the next event that has queued up executes.
#### Event Destructuring {#event_destructuring}
You can [destructure](https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/) properties found either on the
`event` or in the `event.detail` properties by appending a parenthesized list of names after the event name.
This will create a local variable of the same name as the referenced property:
{% example "Event Parameters" %}
<button _="on mousedown(button) put the button into the next <output/>">
Click Me With Different Buttons...
</button>
<output>--</output>
{% endexample %}
Here the `event.button` property is being destructured into a local variable, which we then put into the next