|
1 | | -import { Component, statefulComponent, connect, render, map, element, onClick, stopDirtyChecking } from "ivi"; |
| 1 | +import { statelessComponent, render, map, element, onClick, stopDirtyChecking, setupScheduler, invalidateHandler } from "ivi"; |
2 | 2 | import { h1, div, span, table, tbody, tr, td, a, button } from "ivi-html"; |
3 | | -import { store } from "./store"; |
| 3 | +import { createStore } from "ivi-state"; |
4 | 4 |
|
5 | | -const GlyphIcon = element(span("glyphicon glyphicon-remove").a({ "aria-hidden": "true" })); |
| 5 | +function random(max) { |
| 6 | + return Math.round(Math.random() * 1000) % max; |
| 7 | +} |
6 | 8 |
|
7 | | -const Row = statefulComponent(class extends Component { |
8 | | - constructor(props) { |
9 | | - super(props); |
10 | | - this.click = onClick(() => { store.dispatch({ type: "select", item: this.props.item }); }); |
11 | | - this.del = onClick(() => { store.dispatch({ type: "delete", item: this.props.item }); }); |
12 | | - } |
| 9 | +const A = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean", |
| 10 | + "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive", |
| 11 | + "cheap", "expensive", "fancy"]; |
| 12 | +const C = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"]; |
| 13 | +const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", |
| 14 | + "keyboard"]; |
13 | 15 |
|
14 | | - render() { |
15 | | - const { item, selected } = this.props; |
16 | | - return stopDirtyChecking( |
17 | | - tr(selected ? "danger" : "").c( |
18 | | - td("col-md-1").c(item.id), |
19 | | - td("col-md-4").c(a().e(this.click).c(item.label)), |
20 | | - td("col-md-1").c( |
21 | | - a().e(this.del).c(GlyphIcon()), |
22 | | - ), |
23 | | - td("col-md-6"), |
24 | | - ), |
25 | | - ); |
| 16 | +let nextId = 1; |
| 17 | + |
| 18 | +function buildData(count) { |
| 19 | + const data = new Array(count); |
| 20 | + for (let i = 0; i < count; i++) { |
| 21 | + data[i] = { |
| 22 | + id: nextId++, |
| 23 | + label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`, |
| 24 | + }; |
26 | 25 | } |
27 | | -}); |
| 26 | + return data; |
| 27 | +} |
28 | 28 |
|
29 | | -const RowConnector = connect( |
30 | | - (prev, id) => { |
31 | | - const item = store.getState().data.value[id]; |
32 | | - const selected = store.getState().selected === item; |
33 | | - return (prev !== null && prev.item === item && prev.selected === selected) ? prev : |
34 | | - { item, selected }; |
| 29 | +const STORE = createStore( |
| 30 | + { data: [], selected: null }, |
| 31 | + function (state, action) { |
| 32 | + const data = state.data; |
| 33 | + const selected = state.selected; |
| 34 | + switch (action.type) { |
| 35 | + case "delete": |
| 36 | + data.splice(data.findIndex((d) => d.id === action.id), 1); |
| 37 | + return { data, selected }; |
| 38 | + case "run": |
| 39 | + return { data: buildData(1000), selected: null }; |
| 40 | + case "add": |
| 41 | + return { data: state.data.concat(buildData(1000)), selected }; |
| 42 | + case "update": |
| 43 | + for (let i = 0; i < data.length; i += 10) { |
| 44 | + const r = data[i]; |
| 45 | + data[i] = { id: r.id, label: r.label + " !!!" }; |
| 46 | + } |
| 47 | + return { data, selected }; |
| 48 | + case "select": |
| 49 | + return { data, selected: data.find((d) => d.id === action.id) }; |
| 50 | + case "runlots": |
| 51 | + return { data: buildData(10000), selected: null }; |
| 52 | + case "clear": |
| 53 | + return { data: [], selected: null }; |
| 54 | + case "swaprows": |
| 55 | + if (data.length > 998) { |
| 56 | + const a = data[1]; |
| 57 | + data[1] = data[998]; |
| 58 | + data[998] = a; |
| 59 | + } |
| 60 | + return { data, selected }; |
| 61 | + } |
| 62 | + return state; |
35 | 63 | }, |
36 | | - Row, |
| 64 | + update, |
37 | 65 | ); |
38 | 66 |
|
39 | | -const RowListConnector = connect( |
40 | | - (prev) => { |
41 | | - const rows = store.getState().data; |
42 | | - return (prev !== null && prev.rows === rows) ? prev : |
43 | | - { rows, value: rows.value }; |
44 | | - }, |
45 | | - ({ value }) => tbody().c(map(value, (r, i) => RowConnector(i).k(r.id))), |
46 | | -); |
| 67 | +const GlyphIcon = element(span("", { "aria-hidden": "true" })); |
| 68 | + |
| 69 | +const Row = statelessComponent(({ id, label, selected }) => ( |
| 70 | + stopDirtyChecking( |
| 71 | + tr(selected ? "danger" : "").c( |
| 72 | + td("col-md-1").c(id), |
| 73 | + td("col-md-4").c(a().c(label)), |
| 74 | + td("col-md-1").c(a().c(GlyphIcon("glyphicon glyphicon-remove delete"))), |
| 75 | + td("col-md-6"), |
| 76 | + ), |
| 77 | + ) |
| 78 | +)); |
47 | 79 |
|
48 | 80 | function Button(text, id) { |
49 | 81 | return div("col-sm-6 smallpad").c( |
50 | | - button("btn btn-primary btn-block") |
51 | | - .e(onClick(() => { store.dispatch({ type: id }); })) |
52 | | - .a({ "type": "button", "id": id }) |
| 82 | + button("btn btn-primary btn-block", { type: "button", id }) |
| 83 | + .e(onClick(() => { STORE.dispatch({ type: id }); })) |
53 | 84 | .c(text), |
54 | 85 | ); |
55 | 86 | } |
56 | 87 |
|
57 | | -render( |
58 | | - div("container").c( |
59 | | - div("jumbotron").c( |
60 | | - div("row").c( |
61 | | - div("col-md-6").c(h1().c("ivi")), |
62 | | - div("col-md-6").c( |
63 | | - div("row").c( |
64 | | - Button("Create 1,000 rows", "run"), |
65 | | - Button("Create 10,000 rows", "runlots"), |
66 | | - Button("Append 1,000 rows", "add"), |
67 | | - Button("Update every 10th row", "update"), |
68 | | - Button("Clear", "clear"), |
69 | | - Button("Swap Rows", "swaprows"), |
70 | | - ), |
| 88 | +const Jumbotron = statelessComponent(() => ( |
| 89 | + div("jumbotron").c( |
| 90 | + div("row").c( |
| 91 | + div("col-md-6").c(h1().c("ivi")), |
| 92 | + div("col-md-6").c( |
| 93 | + div("row").c( |
| 94 | + Button("Create 1,000 rows", "run"), |
| 95 | + Button("Create 10,000 rows", "runlots"), |
| 96 | + Button("Append 1,000 rows", "add"), |
| 97 | + Button("Update every 10th row", "update"), |
| 98 | + Button("Clear", "clear"), |
| 99 | + Button("Swap Rows", "swaprows"), |
71 | 100 | ), |
72 | 101 | ), |
73 | 102 | ), |
74 | | - table("table table-hover table-striped test-data").c(RowListConnector()), |
75 | | - GlyphIcon("preloadicon glyphicon glyphicon-remove"), |
76 | | - ), |
77 | | - document.getElementById("main"), |
78 | | -); |
| 103 | + ) |
| 104 | +)); |
| 105 | + |
| 106 | +setupScheduler(invalidateHandler); |
| 107 | +const CONTAINER = document.getElementById("main"); |
| 108 | +function update() { |
| 109 | + const { data, selected } = STORE.state; |
| 110 | + render( |
| 111 | + div("container").c( |
| 112 | + Jumbotron(), |
| 113 | + table("table table-hover table-striped test-data").c( |
| 114 | + tbody() |
| 115 | + .e(onClick((ev) => { |
| 116 | + const target = ev.target; |
| 117 | + STORE.dispatch({ |
| 118 | + type: target.matches(".delete") ? "delete" : "select", |
| 119 | + id: +target.closest("tr").firstChild.textContent, |
| 120 | + }); |
| 121 | + })) |
| 122 | + .c(map(data, (item) => Row(item === selected ? { id: item.id, label: item.label, selected: item === selected } : item).k(item.id))), |
| 123 | + ), |
| 124 | + GlyphIcon("preloadicon glyphicon glyphicon-remove"), |
| 125 | + ), |
| 126 | + CONTAINER, |
| 127 | + ); |
| 128 | +} |
| 129 | +update(); |
0 commit comments