Comparing version 0.0.18 to 0.0.19
@@ -52,3 +52,3 @@ var patch = require("snabbdom").init([ | ||
if (target && target.host === location.host && !target.hasAttribute("data-no-routing")) { | ||
dispatch.setLocation(e.target.pathname) | ||
dispatch.setLocation(target.pathname) | ||
e.preventDefault() | ||
@@ -55,0 +55,0 @@ } |
{ | ||
"name": "flea", | ||
"description": "Tiny UI library based in Snabbdom and ES6 tagged template literals", | ||
"version": "0.0.18", | ||
"version": "0.0.19", | ||
"main": "index.js", | ||
@@ -24,3 +24,3 @@ "author": "Jorge Bucaran", | ||
"hyperx-to-snabbdom": "^0.2.4", | ||
"snabbdom": "^0.5.4" | ||
"snabbdom": "^0.6.0" | ||
}, | ||
@@ -27,0 +27,0 @@ "devDependencies": { |
419
README.md
# flea | ||
Flea is a tiny JavaScript UI library based in [Snabbdom] and ES6 tagged template literals. | ||
flea is a tiny JavaScript UI library based in [Snabbdom] and ES6 tagged template literals. | ||
[See live examples](https://flea.gomix.me/). | ||
## Install | ||
@@ -13,5 +11,19 @@ | ||
## Usage | ||
CDN | ||
```html | ||
<script src="https://cdn.rawgit.com/fleajs/flea/master/dist/flea.min.js"></script> | ||
<script src="https://cdn.rawgit.com/fleajs/flea/master/dist/html.min.js"></script> | ||
``` | ||
Browserify | ||
``` | ||
browserify index.js > bundle.js | ||
``` | ||
## Examples | ||
### [Hello world](http://codepen.io/jbucaran/pen/Qdwpxy?editors=0010) | ||
<details> | ||
<summary>Hello world</summary> | ||
@@ -25,4 +37,9 @@ ```js | ||
### [Counter](http://codepen.io/jbucaran/pen/zNxZLP?editors=0010) | ||
[View online](http://codepen.io/jbucaran/pen/Qdwpxy?editors=0010) | ||
</details> | ||
<details> | ||
<summary>Counter</summary> | ||
```js | ||
@@ -44,4 +61,9 @@ app({ | ||
### [Heading bound to input](http://codepen.io/jbucaran/pen/ggbmdN?editors=0010#) | ||
[View online](http://codepen.io/jbucaran/pen/zNxZLP?editors=0010) | ||
</details> | ||
<details> | ||
<summary>Input</summary> | ||
```js | ||
@@ -61,28 +83,165 @@ app({ | ||
## Usage | ||
[View online](http://codepen.io/jbucaran/pen/ggbmdN?editors=0010#) | ||
</details> | ||
CDN | ||
```html | ||
<script src="https://cdn.rawgit.com/fleajs/flea/master/dist/flea.min.js"></script> | ||
<script src="https://cdn.rawgit.com/fleajs/flea/master/dist/html.min.js"></script> | ||
``` | ||
Browserify | ||
<details> | ||
<summary>Drag and Drop</summary> | ||
```js | ||
const model = { | ||
dragging: false, | ||
position: { | ||
x: 0, y: 0, offsetX: 0, offsetY: 0 | ||
} | ||
} | ||
const view = (model, msg) => html` | ||
<div | ||
onmousedown=${e => msg.drag({ | ||
position: { | ||
x: e.pageX, y: e.pageY, offsetX: e.offsetX, offsetY: e.offsetY | ||
} | ||
})} | ||
style=${{ | ||
userSelect: "none", | ||
cursor: "move", | ||
position: "absolute", | ||
padding: "10px", | ||
left: `${model.position.x - model.position.offsetX}px`, | ||
top: `${model.position.y - model.position.offsetY}px`, | ||
backgroundColor: model.dragging ? "gold" : "deepskyblue" | ||
}} | ||
>Drag Me! | ||
</div>` | ||
const update = { | ||
drop: model => ({ dragging: false }), | ||
drag: (model, { position }) => ({ dragging: true, position }), | ||
move: (model, { x, y }) => model.dragging | ||
? ({ position: { ...model.position, x, y } }) | ||
: model | ||
} | ||
const subs = [ | ||
(_, msg) => addEventListener("mouseup", msg.drop), | ||
(_, msg) => addEventListener("mousemove", e => | ||
msg.move({ x: e.pageX, y: e.pageY })) | ||
] | ||
app({ model, view, update, subs }) | ||
``` | ||
browserify index.js > bundle.js | ||
``` | ||
```html | ||
<!doctype html> | ||
<html> | ||
<body> | ||
<script src="bundle.js"></script> | ||
</body> | ||
</html> | ||
[View online](http://codepen.io/jbucaran/pen/apzYvo?editors=0010) | ||
</details> | ||
<details> | ||
<summary>Todo</summary> | ||
```js | ||
const FilterInfo = { All: 0, Todo: 1, Done: 2 } | ||
const model = { | ||
todos: [], | ||
filter: FilterInfo.All, | ||
input: "", | ||
placeholder: "Add new todo!" | ||
} | ||
const view = (model, msg) => { | ||
return html` | ||
<div> | ||
<h1>Todo</h1> | ||
<p> | ||
Show: ${ | ||
Object.keys(FilterInfo) | ||
.filter(key => FilterInfo[key] !== model.filter) | ||
.map(key => html` | ||
<span><a href="#" onclick=${_ => msg.filter({ | ||
value: FilterInfo[key] | ||
})}>${key}</a> </span> | ||
`)} | ||
</p> | ||
<p><ul> | ||
${model.todos | ||
.filter(t => | ||
model.filter === FilterInfo.Done | ||
? t.done : | ||
model.filter === FilterInfo.Todo | ||
? !t.done : | ||
model.filter === FilterInfo.All) | ||
.map(t => html` | ||
<li style=${{ | ||
color: t.done ? "gray" : "black", | ||
textDecoration: t.done ? "line-through" : "none" | ||
}} | ||
onclick=${e => msg.toggle({ | ||
value: t.done, | ||
id: t.id | ||
})}>${t.value} | ||
</li>`)} | ||
</ul></p> | ||
<p> | ||
<input | ||
type="text" | ||
onkeyup=${e => e.keyCode === 13 ? msg.add() : ""} | ||
oninput=${e => msg.input({ value: e.target.value })} | ||
value=${model.input} | ||
placeholder=${model.placeholder} | ||
/> | ||
<button onclick=${msg.add}>add</button> | ||
</p> | ||
</div>` | ||
} | ||
const update = { | ||
add: model => ({ | ||
input: "", | ||
todos: model.todos.concat({ | ||
done: false, | ||
value: model.input, | ||
id: model.todos.length + 1 | ||
}) | ||
}), | ||
toggle: (model, { id, value }) => ({ | ||
todos: model.todos.map(t => | ||
id === t.id | ||
? Object.assign({}, t, { done: !value }) | ||
: t) | ||
}), | ||
input: (model, { value }) => ({ input: value }), | ||
filter: (model, { value }) => ({ filter: value }) | ||
} | ||
app({ model, view, update }) | ||
``` | ||
## API | ||
[View online](http://codepen.io/jbucaran/pen/zNxRLy?editors=0010) | ||
</details> | ||
[See more examples](https://flea.gomix.me/) | ||
## Documentation | ||
* [html](#html) | ||
* [app](#app) | ||
* [model](#model) | ||
* [update](#update) | ||
* [view](#view) | ||
* [effects](#effects) | ||
* [subs](#subs) | ||
* [hooks](#hooks) | ||
* [onAction](#onaction) | ||
* [onUpdate](#onupdate) | ||
* [onError](#onerror) | ||
* [root](#root) | ||
* [Routing](#routing) | ||
* [setLocation](#setlocation) | ||
* [href](#href) | ||
## html | ||
@@ -139,4 +298,4 @@ | ||
* `model` is the current model, | ||
* `msg` is the object you use to send actions (call reducers or cause effects) and | ||
* `params` are the route parameters if the view belongs to a [route](#routing). | ||
* `msg` is an object you use to send actions (call reducers or cause effects) and | ||
* `params` are the [route](#routing) parameters. | ||
@@ -147,36 +306,2 @@ ```js | ||
#### Routing | ||
Instead of a view as a single function, declare an object with multiple views and use the route path as the key. | ||
```js | ||
app({ | ||
view: { | ||
"*": (model, msg) => {} | ||
"/": (model, msg) => {} | ||
"/:slug": (model, msg, params) => {} | ||
} | ||
}) | ||
``` | ||
* `*` default route used when no other route matches, e.g, 404 page, etc. | ||
* `/` index route | ||
* `/:a/:b/:c` matches a route with three components using the regular expression `[A-Za-z0-9]+` and stores each captured group in the params object, which is passed into the view function. | ||
The route path syntax is based in the same syntax found in [Express](https://expressjs.com/en/guide/routing.html). | ||
##### setLocation | ||
To update the address bar relative location and render a different view, use `msg.setLocation(path)`. | ||
##### Anchors | ||
As a bonus, we intercept all `<a href="/path">...</a>` clicks and call `msg.setLocation("/path")` for you. If you want to opt out of this, add the custom attribute `data-no-routing` to any anchor element that should be handled differently. | ||
```html | ||
<a data-no-routing>...</a> | ||
``` | ||
### effects | ||
@@ -192,13 +317,21 @@ | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
const update = { | ||
add: model => model + 1 | ||
const effects = { | ||
randomColor: _ => | ||
document.body.style.backgroundColor = "#" + ((1<<24) * Math.random() | 0).toString(16) | ||
} | ||
const effects = { | ||
waitThenAdd: (_, msg) => setTimeout(msg.add, 1000) | ||
} | ||
const subs = [ | ||
(_, msg) => addEventListener("mousemove", msg.randomColor) | ||
] | ||
app({ effects, subs }) | ||
``` | ||
[View online](http://codepen.io/jbucaran/pen/OWPvPj?editors=0010) | ||
</details> | ||
### subs | ||
@@ -212,12 +345,22 @@ | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
const update = { | ||
move: (model, { x, y }) => ({ x, y }) | ||
} | ||
const subs = [ | ||
(_, msg) => addEventListener("mousemove", e => msg.move({ x: e.clientX, y: e.clientY })) | ||
] | ||
app({ | ||
model: { x: 0, y: 0 }, | ||
update: { | ||
move: (_, { x, y }) => ({ x, y }) | ||
}, | ||
view: model => html`<h1>${model.x}, ${model.y}</h1>`, | ||
subs: [ | ||
(_, msg) => addEventListener("mousemove", e => msg.move({ x: e.clientX, y: e.clientY })) | ||
] | ||
}) | ||
``` | ||
[View online](http://codepen.io/jbucaran/pen/Bpyraw?editors=0010) | ||
</details> | ||
### hooks | ||
@@ -227,2 +370,34 @@ | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
app({ | ||
model: true, | ||
view: (model, msg) => html` | ||
<div> | ||
<button onclick=${msg.doSomething}>Log</button> | ||
<button onclick=${msg.boom}>Error</button> | ||
</div>`, | ||
update: { | ||
doSomething: model => !model, | ||
}, | ||
effects: { | ||
boom: (model, msg, data, err) => setTimeout(_ => err(Error("BOOM")), 1000) | ||
}, | ||
hooks: { | ||
onError: e => | ||
console.log("[Error] %c%s", "color: red", e), | ||
onAction: name => | ||
console.log("[Action] %c%s", "color: blue", name), | ||
onUpdate: (last, model) => | ||
console.log("[Update] %c%s -> %c%s", "color: gray", last, "color: blue", model) | ||
} | ||
}) | ||
``` | ||
[View online](http://codepen.io/jbucaran/pen/xgbzEy?editors=0010) | ||
</details> | ||
#### onUpdate | ||
@@ -244,3 +419,105 @@ | ||
## Routing | ||
Instead of a view as a single function, declare an object with multiple views and use the route path as the key. | ||
```js | ||
app({ | ||
view: { | ||
"*": (model, msg) => {}, | ||
"/": (model, msg) => {}, | ||
"/:slug": (model, msg, params) => {} | ||
} | ||
}) | ||
``` | ||
* `*` default route used when no other route matches, e.g, 404 page, etc. | ||
* `/` index route | ||
* `/:a/:b/:c` matches a route with three components using the regular expression `[A-Za-z0-9]+` and stores each captured group in the params object, which is passed into the view function. | ||
The route path syntax is based in the same syntax found in [Express](https://expressjs.com/en/guide/routing.html). | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
const { app, html } = require("flea") | ||
const anchor = n => html`<h1><a href=${"/" + n}>${n}</a></h1>` | ||
app({ | ||
view: { | ||
"/": _ => anchor(Math.floor(Math.random() * 999)), | ||
"/:key": (model, msg, { key }) => html` | ||
<div> | ||
<h1>${key}</h1> | ||
<a href="/">Back</a> | ||
</div>` | ||
} | ||
}) | ||
``` | ||
[View online](https://flea-routing.gomix.me/) | ||
</details> | ||
### setLocation | ||
To update the address bar relative location and render a different view, use `msg.setLocation(path)`. | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
app({ | ||
view: { | ||
"/": (model, msg) => html` | ||
<div> | ||
<h1>Home</h1> | ||
<button onclick=${_ => msg.setLocation("/about")}>About</button> | ||
</div>`, | ||
"/about": (model, msg) => html` | ||
<div> | ||
<h1>About</h1> | ||
<button onclick=${_ => msg.setLocation("/")}>Home</button> | ||
</div>` | ||
} | ||
}) | ||
``` | ||
[View online](https://flea-set-location.gomix.me/) | ||
</details> | ||
### href | ||
As a bonus, we intercept all `<a href="/path">...</a>` clicks and call `msg.setLocation("/path")` for you. If you want to opt out of this, add the custom attribute `data-no-routing` to any anchor element that should be handled differently. | ||
```html | ||
<a data-no-routing>...</a> | ||
``` | ||
<details> | ||
<summary><i>Example</i></summary> | ||
```js | ||
app({ | ||
view: { | ||
"/": (model, msg) => html` | ||
<div> | ||
<h1>Home</h1> | ||
<a href="/about">About</a> | ||
</div>`, | ||
"/about": (model, msg) => html` | ||
<div> | ||
<h1>About</h1> | ||
<a href="/">Home</a> | ||
</div>` | ||
} | ||
}) | ||
``` | ||
[View online](https://flea-href.gomix.me/) | ||
</details> | ||
[Snabbdom]: https://github.com/snabbdom/snabbdom | ||
@@ -247,0 +524,0 @@ [Hyperx]: https://github.com/substack/hyperx |
79113
517
+ Addedsnabbdom@0.6.9(transitive)
- Removedsnabbdom@0.5.4(transitive)
Updatedsnabbdom@^0.6.0