Skip to content

Update ivi to 0.20.0 #494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions frameworks/keyed/ivi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
"build-prod": "rollup -c rollup.config.js"
},
"dependencies": {
"ivi-html": "0.16.0",
"ivi-state": "0.16.0",
"ivi": "0.16.0"
"ivi-html": "0.20.0",
"ivi-state": "0.20.0",
"ivi": "0.20.0"
},
"devDependencies": {
"rollup": "0.66.6",
"rollup": "0.67.4",
"rollup-plugin-node-resolve": "3.4.0",
"rollup-plugin-replace": "2.1.0",
"rollup-plugin-terser": "3.0.0"
}
}
}
180 changes: 100 additions & 80 deletions frameworks/keyed/ivi/src/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { connect, render, map, element, onClick, stopDirtyChecking, setupScheduler, invalidateHandler, invalidate } from "ivi";
import { _, render, Events, onClick, withNextFrame, requestDirtyCheck, elementProto, component, selector, TrackByKey, key } from "ivi";
import { h1, div, span, table, tbody, tr, td, a, button } from "ivi-html";
import { createStore, createBox } from "ivi-state";
import { createStore } from "ivi-state";

// @localvoid
// Implemented in almost exactly the same way as react-redux implementation:
// - state is completely immutable
// - each row is a stateful component
// - two selectors per each row (react-redux is using one selector)

function random(max) {
return Math.round(Math.random() * 1000) % max;
Expand All @@ -16,7 +22,7 @@ const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "s
let nextId = 1;

function buildData(count) {
const data = new Array(count);
const data = Array(count);
for (let i = 0; i < count; i++) {
data[i] = {
id: nextId++,
Expand All @@ -26,100 +32,114 @@ function buildData(count) {
return data;
}

const STORE = createStore(
{ data: createBox([]), selected: 0 },
function (state, action) {
const STORE = createStore({ data: [], selected: 0 },
(state, action) => {
const { data, selected } = state;
const itemList = data.value;
switch (action.type) {
case "delete":
itemList.splice(itemList.findIndex((d) => d.id === action.id), 1);
return { data: createBox(itemList), selected };
case "delete": {
const idx = data.indexOf(action.item);
return { data: [...data.slice(0, idx), ...data.slice(idx + 1)], selected };
}
case "run":
return { data: createBox(buildData(1000)), selected: 0 };
return { data: buildData(1000), selected: 0 };
case "add":
return { data: createBox(itemList.concat(buildData(1000))), selected };
case "update":
for (let i = 0; i < itemList.length; i += 10) {
const r = itemList[i];
itemList[i] = { id: r.id, label: r.label + " !!!" };
return { data: data.concat(buildData(1000)), selected };
case "update": {
const newData = data.slice();
for (let i = 0; i < newData.length; i += 10) {
const r = newData[i];
newData[i] = { id: r.id, label: r.label + " !!!" };
}
return { data, selected };
return { data: newData, selected };
}
case "select":
return { data, selected: action.id };
return { data, selected: action.item.id };
case "runlots":
return { data: createBox(buildData(10000)), selected: 0 };
return { data: buildData(10000), selected: 0 };
case "clear":
return { data: createBox([]), selected: 0 };
return { data: [], selected: 0 };
case "swaprows":
if (itemList.length > 998) {
const a = itemList[1];
itemList[1] = itemList[998];
itemList[998] = a;
}
return { data: createBox(itemList), selected };
return { data: [data[0], data[998], ...data.slice(2, 998), data[1], data[999]], selected };
}
return state;
},
invalidate,
withNextFrame(requestDirtyCheck),
);

const GlyphIcon = element(span("", { "aria-hidden": "true" }));
const RemoveRowButton = element(td("col-md-1").c(a().c(GlyphIcon("glyphicon glyphicon-remove"))));
const useItems = selector(() => STORE.state.data);
const useItem = selector((idx) => STORE.state.data[idx]);
const useSelected = selector((item) => STORE.state.selected === item.id);

const Row = connect(
(_, idx) => {
const state = STORE.state;
const item = state.data.value[idx];
return state.selected === item.id ? { id: item.id, label: item.label, selected: true } : item;
},
(item) => (
stopDirtyChecking(tr(item.selected === true ? "danger" : "").c(
td("col-md-1").t(item.id),
td("col-md-4").c(a().t(item.label)),
RemoveRowButton(),
td("col-md-6"),
))
),
);
const GlyphIcon = elementProto(span("glyphicon glyphicon-remove", { "aria-hidden": "true" }));
const RemoveButton = a(_, _, GlyphIcon());

const RowList = connect(
() => STORE.state.data,
({ value }) => (
tbody().e(onClick((ev) => {
const target = ev.target;
STORE.dispatch({
type: target.matches(".glyphicon") ? "delete" : "select",
id: +target.closest("tr").firstChild.textContent,
});
})).c(map(value, ({ id }, i) => Row(i).k(id)))
),
);
const Row = component((c) => {
let item;
// @localvoid: it is possible to combine multiple selectors into one, like it is traditionally done in react-redux.
// It will slightly improve performance and reduce memory consumption, but I have nothing to hide, selectors are
// super cheap in ivi.
const getItem = useItem(c);
const isSelected = useSelected(c);

function Button(text, id) {
return div("col-sm-6 smallpad").c(
button("btn btn-primary btn-block", { type: "button", id })
.e(onClick(() => { STORE.dispatch({ type: id }); }))
.t(text),
const selectItem = onClick(() => { STORE.dispatch({ type: "select", item }); });
const deleteItem = onClick(() => { STORE.dispatch({ type: "delete", item }); });

return (idx) => (
item = getItem(idx),

tr(isSelected(item) ? "danger" : "", _, [
td("col-md-1", _, item.id),
td("col-md-4", _,
Events(selectItem,
a(_, _, item.label),
),
),
td("col-md-1", _,
Events(deleteItem,
RemoveButton,
),
),
td("col-md-6"),
])
);
}
});

const RowList = component((c) => {
const getItems = useItems(c);
return () => tbody(_, _, TrackByKey(getItems().map(({ id }, i) => key(id, Row(i)))));
});

setupScheduler(invalidateHandler);
render(
div("container").c(
stopDirtyChecking(div("jumbotron").c(div("row").c(
div("col-md-6").c(h1().t("ivi")),
div("col-md-6").c(div("row").c(
Button("Create 1,000 rows", "run"),
Button("Create 10,000 rows", "runlots"),
Button("Append 1,000 rows", "add"),
Button("Update every 10th row", "update"),
Button("Clear", "clear"),
Button("Swap Rows", "swaprows"),
)),
))),
table("table table-hover table-striped test-data").c(RowList()),
GlyphIcon("preloadicon glyphicon glyphicon-remove"),
),
document.getElementById("main"),
const Button = (text, id) => (
div("col-sm-6 smallpad", _,
Events(onClick(() => { STORE.dispatch({ type: id }); }),
button("btn btn-primary btn-block", { type: "button", id }, text),
)
)
);
// `withNextFrame()` runs rendering function inside of a sync frame update tick.
withNextFrame(() => {
render(
div("container", _, [
div("jumbotron", _,
div("row", _, [
div("col-md-6", _,
h1(_, _, "ivi")
),
div("col-md-6", _,
div("row", _, [
Button("Create 1,000 rows", "run"),
Button("Create 10,000 rows", "runlots"),
Button("Append 1,000 rows", "add"),
Button("Update every 10th row", "update"),
Button("Clear", "clear"),
Button("Swap Rows", "swaprows"),
]),
),
]),
),
table("table table-hover table-striped test-data", _, RowList()),
GlyphIcon("preloadicon glyphicon glyphicon-remove")
]),
document.getElementById("main"),
);
})();