Skip to content

Commit

Permalink
Refactor TodoMVC to use component function
Browse files Browse the repository at this point in the history
  • Loading branch information
paldepind committed Aug 20, 2019
1 parent bda242a commit 0448195
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 410 deletions.
272 changes: 106 additions & 166 deletions examples/todo/src/Item.ts
Original file line number Diff line number Diff line change
@@ -1,186 +1,126 @@
import { combine } from "@funkia/jabz";
import {
Behavior,
changes,
filter,
keepWhen,
performStream,
sample,
snapshot,
stepper,
Stream,
lift,
toggle
} from "@funkia/hareactive";

import { modelView, elements, fgo, Component } from "../../../src";
import * as H from "@funkia/hareactive";
import { elements, fgo, component } from "../../../src";
const { div, li, input, label, button, checkbox } = elements;

import { setItemIO, itemBehavior, removeItemIO } from "./localstorage";

const enter = 13;
const esc = 27;
const isKey = (keyCode: number) => (ev: { keyCode: number }) =>
ev.keyCode === keyCode;
export const itemIdToPersistKey = (id: number) => `todoItem:${id}`;
export const itemOutputToId = ({ id }: Output) => id;

export type Item = {
taskName: Behavior<string>;
isComplete: Behavior<boolean>;
};

export type PersistedItem = {
taskName: string;
isComplete: boolean;
};

export type Input = {
export type Props = {
name: string;
id: number;
toggleAll: Stream<boolean>;
currentFilter: Behavior<string>;
toggleAll: H.Stream<boolean>;
currentFilter: H.Behavior<string>;
};

type FromView = {
toggleTodo: Stream<boolean>;
taskName: Behavior<string>;
startEditing: Stream<any>;
nameBlur: Stream<any>;
deleteClicked: Stream<any>;
nameKeyup: Stream<any>;
newNameInput: Stream<any>;
toggleTodo: H.Stream<boolean>;
taskName: H.Behavior<string>;
startEditing: H.Stream<any>;
nameBlur: H.Stream<any>;
deleteClicked: H.Stream<any>;
cancel: H.Stream<any>;
enter: H.Stream<any>;
newNameInput: H.Stream<any>;
};

export type Output = {
taskName: Behavior<string>;
isComplete: Behavior<boolean>;
newName: Behavior<string>;
isEditing: Behavior<boolean>;
focusInput: Stream<any>;
hidden: Behavior<boolean>;
destroyItemId: Stream<number>;
completed: Behavior<boolean>;
destroyItemId: H.Stream<number>;
completed: H.Behavior<boolean>;
id: number;
};

const itemModel = fgo(function*(
{
toggleTodo,
startEditing,
nameBlur,
deleteClicked,
nameKeyup,
newNameInput,
taskName
}: FromView,
{ toggleAll, name: initialName, id, currentFilter }: Input
): any {
const enterPress = filter(isKey(enter), nameKeyup);
const enterNotPressed = yield toggle(true, startEditing, enterPress);
const cancel = filter(isKey(esc), nameKeyup);
const notCancelled = yield toggle(true, startEditing, cancel);
const stopEditing = combine(
enterPress,
keepWhen(nameBlur, enterNotPressed),
cancel
);
const isEditing = yield toggle(false, startEditing, stopEditing);
const newName = yield stepper(
initialName,
combine(
newNameInput.map((ev) => ev.target.value),
snapshot(taskName, cancel)
)
export default (props: Props) =>
component<FromView, Output>(
fgo(function*(on) {
const enterNotPressed = yield H.toggle(true, on.startEditing, on.enter);
const notCancelled = yield H.toggle(true, on.startEditing, on.cancel);
const stopEditing = combine(
on.enter,
H.keepWhen(on.nameBlur, enterNotPressed),
on.cancel
);
const editing = yield H.toggle(false, on.startEditing, stopEditing);
const newName = yield H.stepper(
props.name,
combine(
on.newNameInput.map((ev) => ev.target.value),
H.snapshot(on.taskName, on.cancel)
)
);
const nameChange = H.snapshot(
newName,
H.keepWhen(stopEditing, notCancelled)
);

// Restore potentially persisted todo item
const persistKey = "todoItem:" + props.id;
const savedItem = yield H.sample(itemBehavior(persistKey));
const initial =
savedItem === null
? { taskName: props.name, completed: false }
: savedItem;

// Initialize task to restored values
const taskName: H.Behavior<string> = yield H.stepper(
initial.taskName,
nameChange
);
const completed: H.Behavior<boolean> = yield H.stepper(
initial.completed,
combine(on.toggleTodo, props.toggleAll)
);

// Persist todo item
const item = H.lift(
(taskName, completed) => ({ taskName, completed }),
taskName,
completed
);
yield H.performStream(
H.changes(item).map((i) => setItemIO(persistKey, i))
);

const destroyItem = combine(
on.deleteClicked,
nameChange.filter((s) => s === "")
);
const destroyItemId = destroyItem.mapTo(props.id);

// Remove persist todo item
yield H.performStream(destroyItem.mapTo(removeItemIO(persistKey)));

const hidden = H.lift(
(complete, filter) =>
(filter === "completed" && !complete) ||
(filter === "active" && complete),
completed,
props.currentFilter
);

return li({ class: ["todo", { completed, editing, hidden }] }, [
div({ class: "view" }, [
checkbox({
class: "toggle",
props: { checked: completed }
}).output({ toggleTodo: "checkedChange" }),
label(taskName).output({ startEditing: "dblclick" }),
button({ class: "destroy" }).output({ deleteClicked: "click" })
]),
input({
class: "edit",
value: taskName,
actions: { focus: on.startEditing }
}).output((o) => ({
newNameInput: o.input,
nameBlur: o.blur,
enter: o.keyup.filter((ev) => ev.keyCode === enter),
cancel: o.keyup.filter((ev) => ev.keyCode === esc)
}))
])
.output(() => ({ taskName }))
.result({ destroyItemId, completed, id: props.id });
})
);
const nameChange = snapshot(newName, keepWhen(stopEditing, notCancelled));

// Restore potentially persisted todo item
const persistKey = itemIdToPersistKey(id);
const savedItem = yield sample(itemBehavior(persistKey));
const initial =
savedItem === null
? { taskName: initialName, isComplete: false }
: savedItem;

// Initialize task to restored values
const taskName_: Behavior<string> = yield stepper(
initial.taskName,
nameChange
);
const isComplete: Behavior<boolean> = yield stepper(
initial.isComplete,
combine(toggleTodo, toggleAll)
);

// Persist todo item
const item = lift(
(taskName, isComplete) => ({ taskName, isComplete }),
taskName_,
isComplete
);
yield performStream(
changes(item).map((i: PersistedItem) => setItemIO(persistKey, i))
);

const destroyItem = combine(
deleteClicked,
nameChange.filter((s) => s === "")
);
const destroyItemId = destroyItem.mapTo(id);

// Remove persist todo item
yield performStream(destroyItem.mapTo(removeItemIO(persistKey)));

const hidden = lift(
(complete, filter) =>
(filter === "completed" && !complete) ||
(filter === "active" && complete),
isComplete,
currentFilter
);

return {
taskName: taskName_,
isComplete,
isEditing,
newName,
focusInput: startEditing,
id,
destroyItemId,
completed: isComplete,
hidden
};
});

function itemView(
{ taskName, isComplete, isEditing, focusInput, hidden }: Output,
_: Input
): Component<any, FromView> {
return li(
{
class: ["todo", { completed: isComplete, editing: isEditing, hidden }]
},
[
div({ class: "view" }, [
checkbox({
class: "toggle",
props: { checked: isComplete }
}).output({ toggleTodo: "checkedChange" }),
label(taskName).output({ startEditing: "dblclick" }),
button({ class: "destroy" }).output({ deleteClicked: "click" })
]),
input({
class: "edit",
props: { value: taskName },
actions: { focus: focusInput }
}).output({
newNameInput: "input",
nameKeyup: "keyup",
nameBlur: "blur"
})
]
).output((o) => ({ taskName, ...o }));
}

export default modelView(itemModel, itemView);
Loading

0 comments on commit 0448195

Please sign in to comment.