Skip to content

Commit

Permalink
Add view function (#109)
Browse files Browse the repository at this point in the history
* Add view function

* Add view as method

* Correct all to active in TodoMVC
  • Loading branch information
paldepind authored Aug 14, 2019
1 parent 851f207 commit ad91580
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 47 deletions.
75 changes: 39 additions & 36 deletions examples/todo/src/TodoFooter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Behavior, Stream, moment, combine } from "@funkia/hareactive";
import { elements, modelView, fgo } from "../../../src";
import { elements, modelView, view, fgo } from "../../../src";
const { span, button, ul, li, a, footer, strong } = elements;
import { navigate, Router, routePath } from "@funkia/rudolph";

Expand All @@ -16,51 +16,45 @@ export type Out = {
};

type FromView = {
filterBtnAll: Stream<any>;
filterBtnActive: Stream<any>;
filterBtnCompleted: Stream<any>;
clearCompleted: Stream<any>;
selectAll: Stream<string>;
selectActive: Stream<string>;
selectCompleted: Stream<string>;
clearCompleted: Stream<unknown>;
};

const isEmpty = (list: any[]) => list.length === 0;
const formatRemainer = (value: number) => ` item${value === 1 ? "" : "s"} left`;

const filterItem = (name: string, selectedClass: Behavior<string>) =>
li(
a(
{
style: {
cursor: "pointer"
view(
li(
a(
{
style: {
cursor: "pointer"
},
class: {
selected: selectedClass.map((s) => s === name)
}
},
class: {
selected: selectedClass.map((s) => s === name)
}
},
name
).output({
[`filterBtn${name}`]: "click"
})
name
).output({ click: "click" })
)
);

const model = function*(
{
filterBtnActive,
filterBtnAll,
filterBtnCompleted,
clearCompleted
}: FromView,
function* todoFooterModel(
{ selectAll, selectActive, selectCompleted, clearCompleted }: FromView,
{ router }: { router: Router }
) {
const navs = combine(
filterBtnAll.mapTo("all"),
filterBtnActive.mapTo("active"),
filterBtnCompleted.mapTo("completed")
);
const navs = combine(selectAll, selectActive, selectCompleted);
yield navigate(router, navs);
return { clearCompleted };
};
}

const view = ({ }: Out, { router, todosB, areAnyCompleted }: Params) => {
const todoFooterView = (
{ }: Out,
{ router, todosB, areAnyCompleted }: Params
) => {
const hidden = todosB.map(isEmpty);
const itemsLeft = moment(
(at) => at(todosB).filter((t) => !at(t.completed)).length
Expand All @@ -81,9 +75,15 @@ const view = ({ }: Out, { router, todosB, areAnyCompleted }: Params) => {
itemsLeft.map(formatRemainer)
]),
ul({ class: "filters" }, [
filterItem("All", selectedClass),
filterItem("Active", selectedClass),
filterItem("Completed", selectedClass)
filterItem("All", selectedClass).output((o) => ({
selectAll: o.click.mapTo("all")
})),
filterItem("Active", selectedClass).output((o) => ({
selectActive: o.click.mapTo("active")
})),
filterItem("Completed", selectedClass).output((o) => ({
selectCompleted: o.click.mapTo("completed")
}))
]),
button(
{
Expand All @@ -97,6 +97,9 @@ const view = ({ }: Out, { router, todosB, areAnyCompleted }: Params) => {
]);
};

const todoFooter = modelView<Out, FromView, Params>(fgo(model), view);
const todoFooter = modelView<Out, FromView, Params>(
fgo(todoFooterModel),
todoFooterView
);

export default todoFooter;
29 changes: 18 additions & 11 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@ export abstract class Component<O, A> implements Monad<A> {
): Component<O & Remap<A, B>, A>;
output(handler: any): Component<any, A> {
if (typeof handler === "function") {
return new HandleOutput((e, o) => mergeObj(e, handler(o)), this);
return new HandleOutput((e, o) => [mergeObj(e, handler(o)), o], this);
} else {
return new HandleOutput(
(e, o) => mergeObj(e, copyRemaps(handler, o)),
(e, o) => [mergeObj(e, copyRemaps(handler, o)), o],
this
);
}
}
view(): Component<{}, O> {
return view(this);
}
static multi: boolean = false;
multi: boolean = false;
abstract run(
Expand Down Expand Up @@ -122,17 +125,17 @@ export function liftNow<A>(now: Now<A>): Component<{}, A> {
return performComponent(() => runNow(now));
}

class HandleOutput<O, A, P> extends Component<P, A> {
class HandleOutput<O, A, P, B> extends Component<P, B> {
constructor(
private readonly handler: (explicit: O, output: A) => P,
private readonly handler: (explicit: O, output: A) => [P, B],
private readonly c: Component<O, A>
) {
super();
}
run(parent: DomApi, destroyed: Future<boolean>): Out<P, A> {
run(parent: DomApi, destroyed: Future<boolean>): Out<P, B> {
const { explicit, output } = this.c.run(parent, destroyed);
const newExplicit = this.handler(explicit, output);
return { explicit: newExplicit, output };
const [newExplicit, newOutput] = this.handler(explicit, output);
return { explicit: newExplicit, output: newOutput };
}
}

Expand Down Expand Up @@ -318,13 +321,13 @@ class ModelViewComponent<M extends ReactivesObject, V> extends Component<
constructor(
private args: any[],
private model: (...as: any[]) => Now<M>,
private view: (...as: any[]) => Child<V>,
private viewF: (...as: any[]) => Child<V>,
private placeholderNames?: string[]
) {
super();
}
run(parent: DomApi, destroyed: Future<boolean>): Out<{}, M> {
const { view, model, args } = this;
const { viewF, model, args } = this;
let placeholders: any;
if (supportsProxy) {
placeholders = new Proxy({}, placeholderProxyHandler);
Expand All @@ -337,11 +340,11 @@ class ModelViewComponent<M extends ReactivesObject, V> extends Component<
}
}
const { explicit: viewOutput } = toComponent(
view(placeholders, ...args)
viewF(placeholders, ...args)
).run(parent, destroyed);
const helpfulViewOutput = addErrorHandler(
model.name,
view.name,
viewF.name,
Object.assign(viewOutput, { destroyed })
);
const behaviors = runNow(model(helpfulViewOutput, ...args));
Expand Down Expand Up @@ -379,6 +382,10 @@ export function modelView<M extends ReactivesObject, V>(
new ModelViewComponent<M, V>(args, m, view, toViewReactiveNames);
}

export function view<O>(c: Component<O, any>): Component<{}, O> {
return new HandleOutput((explicit, _) => [{}, explicit], c);
}

// Child element
export type CE<O = any> =
| Component<O, any>
Expand Down
19 changes: 19 additions & 0 deletions test/component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
toComponent,
Component,
modelView,
view,
emptyComponent,
elements,
loop,
Expand Down Expand Up @@ -344,6 +345,24 @@ describe("modelView", () => {
});
});

describe("view", () => {
const obj = { a: 0, b: 1 };
it("turns selected output into available output", () => {
const c = view(Component.of(obj).output((o) => o));
const { out, explicit } = testComponent(c);
expect(explicit).to.deep.equal({});
expect(out).to.deep.equal(obj);
});
it("is available as method", () => {
const c = Component.of(obj)
.output((o) => o)
.view();
const { out, explicit } = testComponent(c);
expect(explicit).to.deep.equal({});
expect(out).to.deep.equal(obj);
});
});

describe("list", () => {
const createSpan = (content: string) => span(content);
const initial = ["Hello ", "there", "!"];
Expand Down

0 comments on commit ad91580

Please sign in to comment.