Sliding counter demo annotated source

Back to index

        

This 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