Sliding counter demo annotated source
Back to indexThis counter example is a demonstration of the power of declaratively defined views, JDOM templates' expressive power, and how to compose simple functions together to form interesting Torus components.
4
Bootstrap the required globals from Torus, since we're not bundling
6for (const exportedName in Torus) {
7 window[exportedName] = Torus[exportedName];
8}
9
This component represents a single digit in the counter. This digit will slide between different vertical positions to only show the correct digit through the "window" of the containing element.
13const Digit = d => {
14 return jdom`<div class="digit" style="transform:translateY(-${d}em)">
15 <div class="digitSlice">0</div>
16 <div class="digitSlice">1</div>
17 <div class="digitSlice">2</div>
18 <div class="digitSlice">3</div>
19 <div class="digitSlice">4</div>
20 <div class="digitSlice">5</div>
21 <div class="digitSlice">6</div>
22 <div class="digitSlice">7</div>
23 <div class="digitSlice">8</div>
24 <div class="digitSlice">9</div>
25 <div class="digitSlice"> </div>
26 </div>`;
27}
28
The Counter
component is the container window through which all the digits
are shown. We take in a number, convert it to a string, and reduce it to add
commas in the thousandths places while mapping digits to the Digit
components.
32const Counter = number => {
33 return jdom`<div class="counter">
34 ${[
this is a padding digit to balance out the leading padding digit
36 Digit(10),
37 ...number.toString()
38 .split('')
We reverse the digits so the digits begin filling out on the lower places
40 .reverse()
41 .map(d => Digit(+d))
42 .reduce((acc, cur, i) => {
Quick, concise way to comma-separate the thousands places.
45 if (i % 3 === 0 && i > 0) {
46 acc.push(',');
47 }
48 return acc.concat(cur);
49 }, []),
Padding digit so a newly added digit will slide into place, rather than blink into existence.
52 Digit(10),
53 ]}
54 </div>`;
55}
56
Main counter application.
58class App extends StyledComponent {
59
60 init() {
61 this.value = 1024;
62 this.handleInput = this.handleInput.bind(this);
63 this.handleMinus = this.handleMinus.bind(this);
64 this.handlePlus = this.handlePlus.bind(this);
65 }
66
67 handleInput(evt) {
68 this.value = Math.max(~~evt.target.value, 0);
69 this.render();
70 }
71
72 handleMinus() {
73 this.value = Math.max(this.value - 1, 0);
74 this.render();
75 }
76
77 handlePlus() {
78 this.value ++;
79 this.render();
80 }
81
82 styles() {
83 return css`
84 font-family: system-ui, sans-serif;
85 height: 100vh;
86 width: 96%;
87 max-width: 600px;
88 margin: 0 auto;
89 display: flex;
90 flex-direction: column;
91 align-items: center;
92 justify-content: center;
93 .counter {
94 display: flex;
95 flex-direction: row-reverse;
96 height: 1em;
97 overflow: hidden;
98 font-size: 24vw;
99 }
100 .digit {
101 transition: transform .4s ease-out;
102 }
103 .digitSlice {
104 height: 1em;
105 text-align: center;
106 }
107 .inputGroup {
108 margin-top: 6vh;
109 input,
110 button {
111 font-size: 1.6em;
112 padding: 6px 10px;
113 border-radius: 6px;
114 box-shadow: none;
115 border: 2px solid #777;
116 background: #fff;
117 cursor: pointer;
118 }
119 input {
120 margin-left: 8px;
121 margin-right: 8px;
122 text-align: right;
123 }
124 button {
125 padding-left: 12px;
126 padding-right: 12px;
127 }
128 }
129 `;
130 }
131
132 compose() {
133 return jdom`<main>
134 ${Counter(this.value)}
135 <div class="inputGroup">
136 <button onclick="${this.handleMinus}">-</button>
137 <input type="number" value="${this.value}" oninput="${this.handleInput}" autofocus/>
138 <button onclick="${this.handlePlus}">+</button>
139 </div>
140 </main>`;
141 }
142
143}
144
Create an instance of the app, and append to the DOM.
146const app = new App();
147document.body.appendChild(app.node);
148document.body.style.padding = '0';
149document.body.style.margin = '0';
150