Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into init-stores-with-…
Browse files Browse the repository at this point in the history
…state

* upstream/master:
  6.12.1
  6.12.0
  typings: add app to app.use cb (choojs#665)
  update dependencies (choojs#663)
  ci: Node 7 → Node 10 (choojs#661)
  Update bel and yo-yoify references in docs to nanohtml. (choojs#660)
  Build for UMD (choojs#617)
  6.11.0
  Use nanoassert in the browser (choojs#651)
  Switch to nanohtml (choojs#644)
  v6.11.0-preview1
  choo components (choojs#639)
  • Loading branch information
tornqvist committed Jun 21, 2018
2 parents 44d3b9c + c3a6401 commit 40f4b45
Show file tree
Hide file tree
Showing 23 changed files with 433 additions and 277 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
node_js:
- '6'
- '7'
- '8'
- '10'
sudo: false
language: node_js
env:
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,9 @@ We use the `require('assert')` module from Node core to provide helpful error
messages in development. In production you probably want to strip this using
[unassertify][unassertify].

To convert inlined HTML to valid DOM nodes we use `require('bel')`. This has
To convert inlined HTML to valid DOM nodes we use `require('nanohtml')`. This has
overhead during runtime, so for production environments we should unwrap this
using [yo-yoify][yo-yoify].
using the [nanohtml transform][nanohtml].

Setting up browserify transforms can sometimes be a bit of hassle; to make this
more convenient we recommend using [bankai build][bankai] to build your assets for production.
Expand Down Expand Up @@ -414,9 +414,9 @@ answer short: we're using something even better.
### How can I support older browsers?
Template strings aren't supported in all browsers, and parsing them creates
significant overhead. To optimize we recommend running `browserify` with
[yo-yoify][yo-yoify] as a global transform or using [bankai][bankai] directly.
[nanohtml][nanohtml] as a global transform or using [bankai][bankai] directly.
```sh
$ browserify -g yo-yoify
$ browserify -g nanohtml
```

### Is choo production ready?
Expand Down Expand Up @@ -479,11 +479,11 @@ Render the application to a string. Useful for rendering on the server.

### `choo/html`
Create DOM nodes from template string literals. Exposes
[bel](https://github.com/shama/bel). Can be optimized using
[yo-yoify][yo-yoify].
[nanohtml](https://github.com/choojs/nanohtml). Can be optimized using
[nanohtml][nanohtml].

### `choo/html/raw`
Exposes [bel/raw](https://github.com/shama/bel#unescaping) helper for rendering raw HTML content.
Exposes [nanohtml/raw](https://github.com/shama/nanohtml#unescaping) helper for rendering raw HTML content.

## Installation
```sh
Expand Down Expand Up @@ -585,6 +585,7 @@ Become a backer, and buy us a coffee (or perhaps lunch?) every month or so.

[bankai]: https://github.com/choojs/bankai
[bel]: https://github.com/shama/bel
[nanohtml]: https://github.com/choojs/nanohtml
[browserify]: https://github.com/substack/node-browserify
[budo]: https://github.com/mattdesl/budo
[es2020]: https://github.com/yoshuawuyts/es2020
Expand All @@ -594,6 +595,5 @@ Become a backer, and buy us a coffee (or perhaps lunch?) every month or so.
[nanomorph]: https://github.com/choojs/nanomorph
[nanorouter]: https://github.com/choojs/nanorouter
[yo-yo]: https://github.com/maxogden/yo-yo
[yo-yoify]: https://github.com/shama/yo-yoify
[unassertify]: https://github.com/unassert-js/unassertify
[window-performance]: https://developer.mozilla.org/en-US/docs/Web/API/Performance
41 changes: 41 additions & 0 deletions component/cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var assert = require('assert')
var LRU = require('nanolru')

module.exports = ChooComponentCache

function ChooComponentCache (state, emit, lru) {
assert.ok(this instanceof ChooComponentCache, 'ChooComponentCache should be created with `new`')

assert.equal(typeof state, 'object', 'ChooComponentCache: state should be type object')
assert.equal(typeof emit, 'function', 'ChooComponentCache: emit should be type function')

if (typeof lru === 'number') this.cache = new LRU(lru)
else this.cache = lru || new LRU(100)
this.state = state
this.emit = emit
}

// Get & create component instances.
ChooComponentCache.prototype.render = function (Component, id) {
assert.equal(typeof Component, 'function', 'ChooComponentCache.render: Component should be type function')
assert.ok(typeof id === 'string' || typeof id === 'number', 'ChooComponentCache.render: id should be type string or type number')

var el = this.cache.get(id)
if (!el) {
var args = []
for (var i = 2, len = arguments.length; i < len; i++) {
args.push(arguments[i])
}
args.unshift(Component, id, this.state, this.emit)
el = newCall.apply(newCall, args)
this.cache.set(id, el)
}

return el
}

// Because you can't call `new` and `.apply()` at the same time. This is a mad
// hack, but hey it works so we gonna go for it. Whoop.
function newCall (Cls) {
return new (Cls.bind.apply(Cls, arguments)) // eslint-disable-line
}
1 change: 1 addition & 0 deletions component/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('nanocomponent')
15 changes: 15 additions & 0 deletions example/components/footer/clear-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
var html = require('bel')

module.exports = deleteCompleted

function deleteCompleted (emit) {
return html`
<button class="clear-completed" onclick=${deleteAllCompleted}>
Clear completed
</button>
`

function deleteAllCompleted () {
emit('todos:deleteCompleted')
}
}
18 changes: 18 additions & 0 deletions example/components/footer/filter-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
var html = require('bel')

module.exports = filterButton

function filterButton (name, filter, currentFilter, emit) {
var filterClass = filter === currentFilter
? 'selected'
: ''

var uri = '#' + name.toLowerCase()
if (uri === '#all') uri = '/'

return html`<li>
<a href=${uri} class=${filterClass}>
${name}
</a>
</li>`
}
50 changes: 50 additions & 0 deletions example/components/footer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var Component = require('../../../component')
var html = require('bel')

var clearButton = require('./clear-button')
var filterButton = require('./filter-button')

module.exports = class Footer extends Component {
constructor (name, state, emit) {
super(name)
this.state = state
this.emit = emit

this.local = this.state.components.footer = {}
this.setState()
}

setState () {
this.local.rawTodos = this.state.todos.clock
this.local.rawHref = this.state.href

this.local.filter = this.state.href.replace(/^\//, '') || ''
this.local.activeCount = this.state.todos.active.length
this.local.hasDone = this.state.todos.done.length || null
}

update () {
if (this.local.rawTodos !== this.state.todos.clock ||
this.local.rawHref !== this.state.href) {
this.setState()
return true
} else {
return false
}
}

createElement () {
return html`<footer class="footer">
<span class="todo-count">
<strong>${this.local.activeCount}</strong>
item${this.state.todos.all === 1 ? '' : 's'} left
</span>
<ul class="filters">
${filterButton('All', '', this.local.filter, this.emit)}
${filterButton('Active', 'active', this.local.filter, this.emit)}
${filterButton('Completed', 'completed', this.local.filter, this.emit)}
</ul>
${this.local.hasDone && clearButton(this.emit)}
</footer>`
}
}
32 changes: 32 additions & 0 deletions example/components/header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var Component = require('../../component')
var html = require('bel')

module.exports = class Header extends Component {
constructor (name, state, emit) {
super(name)
this.state = state
this.emit = emit
}

update () {
return false
}

createElement () {
return html`<header class="header">
<h1>todos</h1>
<input class="new-todo"
autofocus
placeholder="What needs to be done?"
onkeydown=${this.createTodo.bind(this)} />
</header>`
}

createTodo (e) {
var value = e.target.value
if (e.keyCode === 13) {
e.target.value = ''
this.emit('todos:create', value)
}
}
}
16 changes: 16 additions & 0 deletions example/components/info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var Component = require('../../component')
var html = require('bel')

module.exports = class Info extends Component {
update () {
return false
}

createElement () {
return html`<footer class="info">
<p>Double-click to edit a todo</p>
<p>choo by <a href="https://yoshuawuyts.com/">Yoshua Wuyts</a></p>
<p>Created by <a href="http://shuheikagawa.com">Shuhei Kagawa</a></p>
</footer>`
}
}
65 changes: 65 additions & 0 deletions example/components/todos/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
var Component = require('../../../component')
var html = require('bel')

var Todo = require('./todo')

module.exports = class Header extends Component {
constructor (name, state, emit) {
super(name)
this.state = state
this.emit = emit
this.local = this.state.components[name] = {}
this.setState()
}

setState () {
this.local.rawTodos = this.state.todos.clock
this.local.rawHref = this.state.href

this.local.allDone = this.state.todos.done.length === this.state.todos.all.length
this.local.filter = this.state.href.replace(/^\//, '') || ''
this.local.todos = this.local.filter === 'completed'
? this.state.todos.done
: this.local.filter === 'active'
? this.state.todos.active
: this.state.todos.all
}

update () {
if (this.local.rawTodos !== this.state.todos.clock ||
this.local.rawHref !== this.state.href) {
this.setState()
return true
} else {
return false
}
}

createElement () {
return html`<section class="main">
<input
class="toggle-all"
type="checkbox"
checked=${this.local.allDone}
onchange=${() => this.toggleAll()}/>
<label for="toggle-all" style="display: none;">
Mark all as done
</label>
<ul class="todo-list">
${this.local.todos.map(todo => Todo(todo, this.emit))}
</ul>
</section>`
}

createTodo (e) {
var value = e.target.value
if (e.keyCode === 13) {
e.target.value = ''
this.emit('todos:create', value)
}
}

toggleAll () {
this.emit('todos:toggleAll')
}
}
64 changes: 64 additions & 0 deletions example/components/todos/todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
var html = require('bel')

module.exports = Todo

function Todo (todo, emit) {
var clx = classList({ completed: todo.done, editing: todo.editing })
return html`
<li id=${todo.id} class=${clx}>
<div class="view">
<input
type="checkbox"
class="toggle"
checked="${todo.done}"
onchange=${toggle} />
<label ondblclick=${edit}>${todo.name}</label>
<button
class="destroy"
onclick=${destroy}
></button>
</div>
<input
class="edit"
value=${todo.name}
onkeydown=${handleEditKeydown}
onblur=${update} />
</li>
`

function toggle (e) {
emit('todos:toggle', todo.id)
}

function edit (e) {
emit('todos:edit', todo.id)
}

function destroy (e) {
emit('todos:delete', todo.id)
}

function update (e) {
emit('todos:update', {
id: todo.id,
editing: false,
name: e.target.value
})
}

function handleEditKeydown (e) {
if (e.keyCode === 13) update(e) // Enter
else if (e.code === 27) emit('todos:unedit') // Escape
}

function classList (classes) {
var str = ''
var keys = Object.keys(classes)
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i]
var val = classes[key]
if (val) str += (key + ' ')
}
return str
}
}
10 changes: 5 additions & 5 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ var app = choo()
if (process.env.NODE_ENV !== 'production') {
app.use(require('choo-devtools')())
}
app.use(require('./store'))
app.use(require('./stores/todos'))

app.route('/', require('./view'))
app.route('#active', require('./view'))
app.route('#completed', require('./view'))
app.route('*', require('./view'))
app.route('/', require('./views/main'))
app.route('#active', require('./views/main'))
app.route('#completed', require('./views/main'))
app.route('*', require('./views/main'))

module.exports = app.mount('body')
Loading

0 comments on commit 40f4b45

Please sign in to comment.