Merge branch 'next'
This commit is contained in:
commit
4772055629
11 changed files with 261 additions and 64 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,4 +1,3 @@
|
|||
* text=auto
|
||||
/mithril.js binary
|
||||
/mithril.min.js binary
|
||||
/stream/stream.js binary
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Change log
|
||||
|
||||
- [v1.1.1](#v111)
|
||||
- [v1.1.0](#v110)
|
||||
- [v1.0.1](#v101)
|
||||
- [Migrating from v0.2.x](#migrating-from-v02x)
|
||||
|
|
@ -7,12 +8,21 @@
|
|||
|
||||
---
|
||||
|
||||
### v1.1.1
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/lhorie/mithril.js/issues/#1752) / [#1753](https://github.com/lhorie/mithril.js/pull/#1753) ([@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/lhorie/mithril.js/issues/#1764) / [#1769](https://github.com/lhorie/mithril.js/pull/#1769)
|
||||
- documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake))
|
||||
|
||||
### v1.1.0
|
||||
|
||||
#### News
|
||||
|
||||
- support for ES6 class components
|
||||
- support for closure components
|
||||
- improvements in build and release automation
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ Vue | Mithril
|
|||
|
||||
Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, bi-directional bindings, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, but unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects.
|
||||
|
||||
Mithril has far less concepts and typically organizes applications in terms of components and a data layer. There are no different ways of defining components, and thus there's no need to install different sets of tools to make different flavors work.
|
||||
Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup.
|
||||
|
||||
#### Documentation
|
||||
|
||||
|
|
|
|||
16
docs/keys.md
16
docs/keys.md
|
|
@ -96,21 +96,21 @@ users.map(function(u) {
|
|||
|
||||
#### Avoid hiding keys in component root elements
|
||||
|
||||
If you refactor the code and put the button inside a component, the key must be moved out of the component and placed back where the component took the place of the button.
|
||||
If you refactor the code and make a user component, the key must be moved out of the component and put on the component itself, since it is now the immediate child of the array.
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
var Button = {
|
||||
var User = {
|
||||
view: function(vnode) {
|
||||
return m("button", {key: vnode.attrs.id}, u.name)
|
||||
return m("div", { key: vnode.attrs.user.id }, [
|
||||
m(Button, vnode.attrs.user.name)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// PREFER
|
||||
users.map(function(u) {
|
||||
return m("div", [
|
||||
m(Button, {key: u.id}, u.name) // key should be here, not in component
|
||||
])
|
||||
return m(User, { key: u.id, user: u }) // key should be here, not in component
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -195,12 +195,12 @@ users[0].key = 'c'
|
|||
// AVOID
|
||||
users.map(function(user){
|
||||
// The component for John will be destroyed and recreated
|
||||
return m(UserComponent, user)
|
||||
return m(UserComponent, user)
|
||||
})
|
||||
|
||||
// PREFER
|
||||
users.map(function(user){
|
||||
// Key is specifically extracted: data model is given its own property
|
||||
return m(UserComponent, {key: user.id, model: user})
|
||||
return m(UserComponent, {key: user.id, model: user})
|
||||
})
|
||||
```
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ module.exports = {
|
|||
oninput: m.withAttr("value", function(value) {User.current.lastName = value}),
|
||||
value: User.current.lastName
|
||||
}),
|
||||
m("button.button[type=button]", "Save"),
|
||||
m("button.button[type=submit]", "Save"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ function execSelector(state, attrs, children) {
|
|||
}
|
||||
}
|
||||
|
||||
if (className != null) {
|
||||
if (attrs.class != null) {
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ function hyperscript(selector) {
|
|||
var cached = selectorCache[selector] || compileSelector(selector)
|
||||
}
|
||||
|
||||
if (!attrs) {
|
||||
if (attrs == null) {
|
||||
attrs = {}
|
||||
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
|
||||
attrs = {}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../../test-utils/domMock.js"></script>
|
||||
<script src="../../test-utils/component.js"></script>
|
||||
<script src="../../test-utils/components.js"></script>
|
||||
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,108 @@ o.spec("hyperscript", function() {
|
|||
|
||||
o(vnode.tag).equals("a")
|
||||
})
|
||||
o("v1.0.1 bug-for-bug regression suite", function(){
|
||||
o
|
||||
console.log(m('a', {
|
||||
class: null
|
||||
}).attrs, {
|
||||
class: undefined,
|
||||
className: null
|
||||
})
|
||||
o(m('a', {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: null
|
||||
})
|
||||
o(m('a', {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
})
|
||||
o(m('a', {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: false
|
||||
})
|
||||
o(m('a', {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: true
|
||||
})
|
||||
console.log(m('a.x', {
|
||||
class: null
|
||||
}).attrs, {
|
||||
class: undefined,
|
||||
className: "x null"
|
||||
})
|
||||
o(m('a.x', {
|
||||
class: null
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x null"
|
||||
})
|
||||
o(m('a.x', {
|
||||
class: undefined
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x"
|
||||
})
|
||||
o(m('a.x', {
|
||||
class: false
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x false"
|
||||
})
|
||||
o(m('a.x', {
|
||||
class: true
|
||||
}).attrs).deepEquals({
|
||||
class: undefined,
|
||||
className: "x true"
|
||||
})
|
||||
o(m('a', {
|
||||
className: null
|
||||
}).attrs).deepEquals({
|
||||
className: null
|
||||
})
|
||||
o(m('a', {
|
||||
className: undefined
|
||||
}).attrs).deepEquals({
|
||||
className: undefined
|
||||
})
|
||||
o(m('a', {
|
||||
className: false
|
||||
}).attrs).deepEquals({
|
||||
className: false
|
||||
})
|
||||
o(m('a', {
|
||||
className: true
|
||||
}).attrs).deepEquals({
|
||||
className: true
|
||||
})
|
||||
o(m('a.x', {
|
||||
className: null
|
||||
}).attrs).deepEquals({
|
||||
className: "x"
|
||||
})
|
||||
o(m('a.x', {
|
||||
className: undefined
|
||||
}).attrs).deepEquals({
|
||||
className: "x"
|
||||
})
|
||||
o(m('a.x', {
|
||||
className: false
|
||||
}).attrs).deepEquals({
|
||||
className: "x"
|
||||
})
|
||||
o(m('a.x', {
|
||||
className: true
|
||||
}).attrs).deepEquals({
|
||||
className: "x true"
|
||||
})
|
||||
})
|
||||
o("handles class in selector", function() {
|
||||
var vnode = m(".a")
|
||||
|
||||
|
|
@ -273,6 +375,11 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.children[0]).equals(null)
|
||||
o(vnode.children[1]).equals(undefined)
|
||||
})
|
||||
o("handles falsy number single child without attrs", function() {
|
||||
var vnode = m("div", 0)
|
||||
|
||||
o(vnode.text).equals(0)
|
||||
})
|
||||
})
|
||||
o.spec("permutations", function() {
|
||||
o("handles null attr and children", function() {
|
||||
|
|
|
|||
|
|
@ -199,9 +199,8 @@ o.spec("onbeforeremove", function() {
|
|||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
|
||||
o(onremove.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o(onremove.callCount).equals(0)
|
||||
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(1)
|
||||
done()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,18 @@
|
|||
"use strict"
|
||||
|
||||
var parseURL = require("../test-utils/parseURL")
|
||||
var callAsync = require("../test-utils/callAsync.js")
|
||||
|
||||
function debouncedAsync(f) {
|
||||
var ref
|
||||
return function() {
|
||||
if (ref != null) return
|
||||
ref = callAsync(function(){
|
||||
ref = null
|
||||
f()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(options) {
|
||||
if (options == null) options = {}
|
||||
|
|
@ -29,7 +41,9 @@ module.exports = function(options) {
|
|||
if (data.search != null && data.search !== search) search = data.search, isNew = true
|
||||
if (data.hash != null && data.hash !== hash) {
|
||||
hash = data.hash
|
||||
if (!isNew) hashchange()
|
||||
if (!isNew) {
|
||||
hashchange()
|
||||
}
|
||||
}
|
||||
return isNew
|
||||
}
|
||||
|
|
@ -38,9 +52,10 @@ module.exports = function(options) {
|
|||
if (value === "") return ""
|
||||
return (value.charAt(0) !== prefix ? prefix : "") + value
|
||||
}
|
||||
function hashchange() {
|
||||
function _hashchange() {
|
||||
if (typeof $window.onhashchange === "function") $window.onhashchange({type: "hashchange"})
|
||||
}
|
||||
var hashchange = debouncedAsync(_hashchange)
|
||||
function popstate() {
|
||||
if (typeof $window.onpopstate === "function") $window.onpopstate({type: "popstate", state: $window.history.state})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
var o = require("../../ospec/ospec")
|
||||
var pushStateMock = require("../../test-utils/pushStateMock")
|
||||
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
o.spec("pushStateMock", function() {
|
||||
|
||||
var $window
|
||||
|
|
@ -478,93 +478,160 @@ o.spec("pushStateMock", function() {
|
|||
})
|
||||
})
|
||||
o.spec("onhashchance", function() {
|
||||
o("onhashchange triggers on location.href change", function() {
|
||||
o("onhashchange triggers on location.href change", function(done) {
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/#a"
|
||||
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
o($window.onhashchange.args[0].type).equals("hashchange")
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
o($window.onhashchange.args[0].type).equals("hashchange")
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("onhashchange triggers on relative location.href change", function() {
|
||||
o("onhashchange triggers on relative location.href change", function(done) {
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "#a"
|
||||
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("onhashchange triggers on location.hash change", function() {
|
||||
o("onhashchange triggers on location.hash change", function(done) {
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.hash = "#a"
|
||||
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on page change", function() {
|
||||
o("onhashchange does not trigger on page change", function(done) {
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/a"
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on page change with different hash", function() {
|
||||
o("onhashchange does not trigger on page change with different hash", function(done) {
|
||||
$window.location.href = "http://localhost/#a"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/a#b"
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/a#b"
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on page change with same hash", function() {
|
||||
o("onhashchange does not trigger on page change with same hash", function(done) {
|
||||
$window.location.href = "http://localhost/#b"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/a#b"
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.location.href = "http://localhost/a#b"
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange triggers on history.back()", function() {
|
||||
o("onhashchange triggers on history.back()", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange triggers on history.forward()", function() {
|
||||
o("onhashchange triggers on history.forward()", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
callAsync(function(){
|
||||
$window.history.forward()
|
||||
|
||||
o($window.onhashchange.callCount).equals(2)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on history.back() that causes page change with different hash", function() {
|
||||
o("onhashchange triggers once when the hash changes twice in a single tick", function(done) {
|
||||
$window.location.href = "#a"
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on history.back() that causes page change with different hash", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.location.href = "a#b"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on history.back() that causes page change with same hash", function() {
|
||||
o("onhashchange does not trigger on history.back() that causes page change with same hash", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.location.href = "a#a"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on history.forward() that causes page change with different hash", function() {
|
||||
o("onhashchange does not trigger on history.forward() that causes page change with different hash", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.location.href = "a#b"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
o("onhashchange does not trigger on history.forward() that causes page change with same hash", function() {
|
||||
o("onhashchange does not trigger on history.forward() that causes page change with same hash", function(done) {
|
||||
$window.location.href = "#a"
|
||||
$window.location.href = "a#b"
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
callAsync(function(){
|
||||
$window.onhashchange = o.spy()
|
||||
$window.history.back()
|
||||
$window.history.forward()
|
||||
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o($window.onhashchange.callCount).equals(0)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("onunload", function() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue