@woofjs/app v0.8.1
@woofjs/app
Front end routing, components and state for dogs. š
@woofjs/app is a client-side JavaScript framework that shamelessly steals the best ideas from other frameworks; React, Angular, Choo, and Vue in particular.
Hello World
import { makeApp } from "@woofjs/app";
const app = makeApp();
app.routes((when) => {
when("*", ($) => {
return $("h1", "Hello World");
});
});
app.connect("#app");The code above renders an <h1> with the words "Hello World" into an element with an ID of #app, regardless of the current URL.
Routing
Woof determines what content to show using routes. Routes take a string to match against the current URL and a component to display when that route matches.
Route strings are a set of fragments separated by /. These fragments are of three types.
- Static:
/this/is/staticand will match only when the route is exactly this. - Dynamic:
/users/:id/editwill match anything that fits the static parts of the route and stores the parts beginning with:as named params. This can be anything, like/users/123/editor/users/BobJones/edit. You can access these values inside the component. - Wildcard:
/users/*will match anything beginning with/usersand store everything after that as awildcardparam. Wildcards must be at the end of a route.
when("users/:id", ($, self) => {
const id = self.$route.get("params.id");
return <p>User ID is {id}</p>;
});when("users/*", ($) => {
return (
<div>
<h1>Persistent Header</h1>
{$.routes((when) => {
when(":id", ($) => {
return <p>User Details</p>;
});
when(":id/edit", ($) => {
return <p>User Edit</p>;
});
when("*", ($) => {
return <p>Fallback</p>;
});
}}
</div>
);
});Reactivity
TODO
See @woofjs/state. Pass a state instead of a static value for any attribute and the DOM will update automatically as the state changes.
Dolla
when("users/:id", ($, self) => {
return $("main", [
$("p", "Here's some text in a paragraph."),
$("p", { style: { color: "red" } }, "This paragraph is red."),
]);
});Components
const Example = makeComponent(($, self) => {
const $title = self.$attrs.map("title");
return (
<div>
<h1>{$.text($title, "Default Title")}</h1>
<p>This is a reusable component now.</p>
</div>
);
});
app.routes((when) => {
// Mount directly on a route
when("example", Example);
// Use in the body of another component
when("other", ($) => {
return (
<div>
<Example title="In Another Component" />
</div>
);
});
});Services
Services are singletons, meaning only one copy of the service exists and all .getService(name) calls that access it get the same instance of name. You can use services to store state in a central location when you need to get to it from multiple places in your app.
The following example shows a counter with one page to display the number and another to modify it. Both routes share data through a counter service.
app.service("counter", () => {
const $count = makeState(0);
return {
$current: $count.map(), // Makes a read only version. Components can only change this through the methods.
increment() {
$count.set((current) => current + 1);
},
decrement() {
$count.set((current) => current - 1);
},
};
});
app.routes((when) => {
when("/counter", ($) => {
return (
<div>
<h1>World's Most Inconvenient Counter Demo</h1>
<a href="/counter/view">See the number</a>
<a href="/counter/controls">Change the number</a>
</div>
);
});
when("/counter/view", ($, self) => {
const { $current } = self.getService("counter");
return <h1>The Count is Now {$.text($current)}</h1>;
});
when("/counter/controls", ($, self) => {
const { increment, decrement } = self.getService("counter");
return (
<div>
<button onclick={increment}>Increment</button>
<button onclick={decrement}>Decrement</button>
</div>
);
});
});Testing
TO DO
š¦