Merge branch 'next' into ospec-named-suites
This commit is contained in:
commit
cbb3db7b88
124 changed files with 7553 additions and 2650 deletions
|
|
@ -1,10 +1,7 @@
|
|||
coverage
|
||||
.vscode
|
||||
examples
|
||||
docs
|
||||
node_modules
|
||||
tests
|
||||
test-utils
|
||||
ospec
|
||||
mithril.js
|
||||
mithril.min.js
|
||||
/coverage
|
||||
/docs/lib
|
||||
/examples
|
||||
/mithril.js
|
||||
/mithril.min.js
|
||||
/node_modules
|
||||
|
|
|
|||
18
.eslintrc.js
18
.eslintrc.js
|
|
@ -60,7 +60,14 @@ module.exports = {
|
|||
"id-blacklist": "error",
|
||||
"id-length": "off",
|
||||
"id-match": "error",
|
||||
"indent": "off",
|
||||
"indent": [
|
||||
"warn",
|
||||
"tab",
|
||||
{
|
||||
"outerIIFEBody": 0,
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "error",
|
||||
"key-spacing": "off",
|
||||
|
|
@ -188,7 +195,7 @@ module.exports = {
|
|||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
"avoid-escape"
|
||||
{"avoidEscape": true}
|
||||
],
|
||||
"radix": [
|
||||
"error",
|
||||
|
|
@ -209,7 +216,7 @@ module.exports = {
|
|||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
"strict": ["error", "global"],
|
||||
"template-curly-spacing": "error",
|
||||
"valid-jsdoc": "off",
|
||||
"vars-on-top": "off",
|
||||
|
|
@ -217,5 +224,6 @@ module.exports = {
|
|||
"wrap-regex": "error",
|
||||
"yield-star-spacing": "error",
|
||||
"yoda": "off"
|
||||
}
|
||||
};
|
||||
},
|
||||
"root": true
|
||||
};
|
||||
|
|
|
|||
4
.gitattributes
vendored
4
.gitattributes
vendored
|
|
@ -1 +1,3 @@
|
|||
* text=auto
|
||||
* text=auto
|
||||
/mithril.js binary
|
||||
/mithril.min.js binary
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ jsconfig.json
|
|||
npm-debug.log
|
||||
.vscode
|
||||
.DS_Store
|
||||
.eslintcache
|
||||
|
|
|
|||
99
.travis.yml
99
.travis.yml
|
|
@ -1,48 +1,97 @@
|
|||
sudo: false
|
||||
|
||||
# Only care about running tests against latest node
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
|
||||
# Keep node_modules around, it speeds up builds & they don't change often
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
# Custom install step so the travis scripts don't need to be in package.json
|
||||
# Custom install step so the travis-only stuff doesn't need to be in package.json
|
||||
install:
|
||||
- npm install
|
||||
- npm install @alrra/travis-scripts@^3.0.1
|
||||
- npm install
|
||||
- npm install @alrra/travis-scripts@^3.0.1 gh-pages@^0.12.0
|
||||
|
||||
# Lint (but don't fail build) before running tests
|
||||
before_script: npm run lint || true
|
||||
# Bundle before running tests so the bundle is always up-to-date
|
||||
before_script: npm run build --silent
|
||||
|
||||
# Run more than just npm test
|
||||
script: npm run build && npm test
|
||||
# Run tests, lint, and then check for perf regressions
|
||||
script:
|
||||
- npm test --silent
|
||||
- npm run perf --silent
|
||||
|
||||
# After a successful build create bundles & commit back to the repo
|
||||
# After a successful build commit changes back to repo
|
||||
after_success:
|
||||
- |
|
||||
|
||||
# Only want to commit things on commits to $BRANCH
|
||||
if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] || [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then
|
||||
echo "Artifacts only built on $BRANCH"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Set up SSH environment
|
||||
$(npm bin)/set-up-ssh --key "$encrypted_8b86e0359d64_key" \
|
||||
--iv "$encrypted_8b86e0359d64_iv" \
|
||||
--path-encrypted-key "./.deploy.enc"
|
||||
|
||||
# Build & commit changes
|
||||
$(npm bin)/commit-changes --commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
||||
--branch "$BRANCH"
|
||||
$(npm bin)/set-up-ssh \
|
||||
--key "$encrypted_8b86e0359d64_key" \
|
||||
--iv "$encrypted_8b86e0359d64_iv" \
|
||||
--path-encrypted-key "./.deploy.enc"
|
||||
|
||||
# Commit bundle changes generated in before_script step
|
||||
# --commands is a weird no-op but required for commit-changes to run
|
||||
# --branch arg is to ensure this only runs against the `next` branch
|
||||
$(npm bin)/commit-changes \
|
||||
--commands "echo committing" \
|
||||
--commit-message "Bundled output for commit $TRAVIS_COMMIT [skip ci]" \
|
||||
--branch "next"
|
||||
|
||||
# Only want to commit docs when building pushes on master &
|
||||
# this doesn't have the built-in branch protection like commit-changes
|
||||
if [ "$TRAVIS_EVENT_TYPE" == "push" ] && \
|
||||
[ "$TRAVIS_BRANCH" == "master" ] && \
|
||||
[ "$TRAVIS_REPO_SLUG" == "MithrilJS/mithril.js" ]
|
||||
then
|
||||
# Generate docs
|
||||
npm run gendocs
|
||||
|
||||
# Set up git env
|
||||
git config --global user.email "$GH_USER_EMAIL"
|
||||
git config --global user.name "$GH_USER_NAME"
|
||||
|
||||
# Commit docs to gh-pages branch
|
||||
# Using --add to ensure that archived versions aren't lost
|
||||
# Using --repo to force it to go over SSH so the saved keys are used (tschaub/gh-pages#160)
|
||||
$(npm bin)/gh-pages \
|
||||
--dist ./dist \
|
||||
--add \
|
||||
--repo "git@github.com:MithrilJS/mithril.js.git" \
|
||||
--message "Generated docs for commit $TRAVIS_COMMIT [skip ci]"
|
||||
else
|
||||
echo "Not submitting documentation updates"
|
||||
fi
|
||||
|
||||
# Environment configuration
|
||||
env:
|
||||
global:
|
||||
# Restrict the branch this will activate on
|
||||
- BRANCH=rewrite
|
||||
|
||||
# Set up GH_USER_EMAIL & GH_USER_NAME env variables used by travis-scripts package
|
||||
- secure: Xvqvm3+PvJu/rs3jl/NNn0RWLkkLkIoPHiL0GCfVRaywgjCYVN02g54NVvIDaOfybqPmu9E6PJFVs92vhF34NMFQHf4EWskynusIGV271R2BV0i+OJBfLMuLgiwm6zRn7/Zw4JvWIUGEwcnlz0qxbqdHsS0SOR3fIkFzePickW0=
|
||||
- secure: Rf/ldEO9d4vItJhe6EmqWpFAyCARzoCb422nHnjr1hYJknnwIXpgyZ1C/7On/9o7rWPPf+8WcHC/rgjK2rthKCldzdG5I60LfWSNzap9lk3Aa4TpSCoDBuEp7JVvDr5tc3rKnBXVT71hOay7RSx1StWzXiJs9mjaeVMJzYzRT78=
|
||||
|
||||
# Deploy to npm and github pages on tagged commits that successfully build
|
||||
deploy:
|
||||
- provider: releases
|
||||
api_key:
|
||||
secure: PauFuz+pn7oRpHn2JTl4k3+iWjOofyBYBvavPQVNdXgKws9mGj0i2n5k2oIDU09VD7NeyEkwP6tdLCUFNaR8uwTJH/TBXMZE95oxUEaliFreA0nOiI3WkG4NCW0GwUoIIn1yL14y6+9oEBinWUia8DIn9kZNS11DNDgQpIPnoQQ=
|
||||
file:
|
||||
- "mithril.js"
|
||||
- "mithril.min.js"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
repo: MithrilJS/mithril.js
|
||||
branch: master
|
||||
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email: npm@patcavit.com
|
||||
api_key:
|
||||
secure: ADElvD1oxn9GfEG7dDOggX96b36A/cGEybovAc0221CCKzv5kWCavMrtxneiJYI6N/n24abSlbM90vMfU84FEzH0Ev28dGVokRP4ad6VRkISszKlYVEP8Lds4QxfKh78jZlUxmxM0B3vmQ1kYJbTBqp3ICtaJ5ptEQHWhrLtxnc=
|
||||
on:
|
||||
tags: true
|
||||
repo: MithrilJS/mithril.js
|
||||
branch: master
|
||||
|
|
|
|||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Leo Horie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
268
README.md
268
README.md
|
|
@ -1,39 +1,267 @@
|
|||
# Mithril.js - A framework for building brilliant applications
|
||||
# Introduction
|
||||
|
||||
[Installation](docs/installation.md) | [API](docs/api.md) | [Examples](docs/examples.md) | [Changelog/Migration Guide](docs/change-log.md)
|
||||
- [What is Mithril?](#what-is-mithril)
|
||||
- [Getting started](#getting-started)
|
||||
- [Hello world](#hello-world)
|
||||
- [DOM elements](#dom-elements)
|
||||
- [Components](#components)
|
||||
- [Routing](#routing)
|
||||
- [XHR](#xhr)
|
||||
|
||||
Note: This branch is the upcoming version 1.0. It's a rewrite from the ground up and it's not backwards compatible with [Mithril 0.2.x](http://mithril.js.org). You can find preliminary [documentation here](docs) and [migration guide here](docs/change-log.md)
|
||||
---
|
||||
|
||||
This rewrite aims to fix longstanding API design issues, significantly improve performance, and clean up the codebase.
|
||||
### What is Mithril?
|
||||
|
||||
## Early Preview
|
||||
Mithril is a modern client-side Javascript framework for building Single Page Applications.
|
||||
It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box.
|
||||
|
||||
You can install this via NPM using this command:
|
||||
<div style="display:flex;margin:0 0 30px;">
|
||||
<div style="width:50%;">
|
||||
<h5>Download size</h5>
|
||||
<small>Mithril (8kb)</small>
|
||||
<div style="animation:grow 0.08s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:4%;"></div>
|
||||
<small style="color:#aaa;">Vue + Vue-Router + Vuex + fetch (40kb)</small>
|
||||
<div style="animation:grow 0.4s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:20%"></div>
|
||||
<small style="color:#aaa;">React + React-Router + Redux + fetch (64kb)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:32%"></div>
|
||||
<small style="color:#aaa;">Angular (135kb)</small>
|
||||
<div style="animation:grow 1.35s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:68%"></div>
|
||||
</div>
|
||||
<div style="width:50%;">
|
||||
<h5>Performance</h5>
|
||||
<small>Mithril (6.4ms)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:24%;"></div>
|
||||
<small style="color:#aaa;">Vue (9.8ms)</small>
|
||||
<div style="animation:grow 0.98s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:40%"></div>
|
||||
<small style="color:#aaa;">React (12.1ms)</small>
|
||||
<div style="animation:grow 1.21s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:48%"></div>
|
||||
<small style="color:#aaa;">Angular (11.5ms)</small>
|
||||
<div style="animation:grow 1.15s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:44%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
```
|
||||
npm install mithril@rewrite
|
||||
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess.
|
||||
|
||||
If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](http://mithril.js.org/framework-comparison.html) page.
|
||||
|
||||
Mithril supports browsers all the way back to IE9, no polyfills required.
|
||||
|
||||
---
|
||||
|
||||
### Getting started
|
||||
|
||||
The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
|
||||
|
||||
Let's create an HTML file to follow along:
|
||||
|
||||
```markup
|
||||
<body>
|
||||
<script src="//unpkg.com/mithril/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
|
||||
// your code goes here!
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
Examples run out of the box. Just open the HTML files.
|
||||
---
|
||||
|
||||
## Status
|
||||
### Hello world
|
||||
|
||||
The code is fairly stable and I'm using it in production, but there may be bugs still lurking.
|
||||
Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
|
||||
|
||||
Some examples of usage can be found in the [examples](examples) folder. [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html) has the largest API surface coverage.
|
||||
```javascript
|
||||
var root = document.body
|
||||
|
||||
Partial documentation can be found in the [`/docs`](docs) directory
|
||||
m.render(root, "Hello world")
|
||||
```
|
||||
|
||||
## Performance
|
||||
Now, let's change the text to something else. Add this line of code under the previous one:
|
||||
|
||||
Mithril's virtual DOM engine is around 500 lines of well organized code and it implements a modern search space reduction diff algorithm and a DOM recycling mechanism, which translate to top-of-class performance. See the [dbmon implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html) (for comparison, here are dbmon implementations for [React v15.3.2](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/react/index.html), [Angular v2.0.0-beta.17](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/angular/index.html) and [Vue 2](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/vue/index.html). All implementations are naive (i.e. apples-to-apples, no optimizations)
|
||||
```javascript
|
||||
m.render(root, "My first app")
|
||||
```
|
||||
|
||||
## Robustness
|
||||
As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch.
|
||||
|
||||
There are over 4000 assertions in the test suite, and tests cover even difficult-to-test things like `location.href`, `element.innerHTML` and `XMLHttpRequest` usage.
|
||||
---
|
||||
|
||||
## Modularity
|
||||
### DOM elements
|
||||
|
||||
Despite the huge improvements in performance and modularity, the new codebase is smaller than v0.2.x, currently clocking at <!-- size -->7.60 KB<!-- /size --> min+gzip
|
||||
Let's wrap our text in an `<h1>` tag.
|
||||
|
||||
In addition, Mithril is now completely modular: you can import only the modules that you need and easily integrate 3rd party modules if you wish to use a different library for routing, ajax, and even rendering
|
||||
```javascript
|
||||
m.render(root, m("h1", "My first app"))
|
||||
```
|
||||
|
||||
The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `<h1>`:
|
||||
|
||||
```javascript
|
||||
m("h1", {class: "title"}, "My first app")
|
||||
```
|
||||
|
||||
If you want to have multiple elements:
|
||||
|
||||
```javascript
|
||||
[
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
]
|
||||
```
|
||||
|
||||
And so on:
|
||||
|
||||
```javascript
|
||||
m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
```
|
||||
|
||||
Note: If you prefer `<html>` syntax, [it's possible to use it via a Babel plugin](http://mithril.js.org/jsx.html).
|
||||
|
||||
```jsx
|
||||
// HTML syntax via Babel's JSX plugin
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Components
|
||||
|
||||
A Mithril component is just an object with a `view` function. Here's the code above as a component:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", "A button"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To activate the component, we use `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
As you would expect, doing so creates this markup:
|
||||
|
||||
```markup
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
<button>A button</button>
|
||||
</main>
|
||||
```
|
||||
|
||||
The `m.mount` function is similar to `m.render`, but instead of rendering some HTML only once, it activates Mithril's auto-redrawing system. To understand what that means, let's add some events:
|
||||
|
||||
```javascript
|
||||
var count = 0 // added a variable
|
||||
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
// changed the next line
|
||||
m("button", {onclick: function() {count++}}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(root, Hello)
|
||||
```
|
||||
|
||||
We defined an `onclick` event on the button, which increments a variable `count` (which was declared at the top). We are now also rendering the value of that variable in the button label.
|
||||
|
||||
You can now update the label of the button by clicking the button. Since we used `m.mount`, you don't need to manually call `m.render` to apply the changes in the `count` variable to the HTML; Mithril does it for you.
|
||||
|
||||
If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates.
|
||||
|
||||
---
|
||||
|
||||
### Routing
|
||||
|
||||
Routing just means going from one screen to another in an application with several screens.
|
||||
|
||||
Let's add a splash page that appears before our click counter. First we create a component for it:
|
||||
|
||||
```javascript
|
||||
var Splash = {
|
||||
view: function() {
|
||||
return m("a", {href: "#!/hello"}, "Enter!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this component simply renders a link to `#!/hello`. The `#!` part is known as a hashbang, and it's a common convention used in Single Page Applications to indicate that the stuff after it (the `/hello` part) is a route path.
|
||||
|
||||
Now that we going to have more than one screen, we use `m.route` instead of `m.mount`.
|
||||
|
||||
```javascript
|
||||
m.route(root, "/splash", {
|
||||
"/splash": Splash,
|
||||
"/hello": Hello,
|
||||
})
|
||||
```
|
||||
|
||||
The `m.route` function still has the same auto-redrawing functionality that `m.mount` does, and it also enables URL awareness; in other words, it lets Mithril know what to do when it sees a `#!` in the URL.
|
||||
|
||||
The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `http://localhost`, then you get redirected to `http://localhost/#!/splash`.
|
||||
|
||||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button.
|
||||
|
||||
---
|
||||
|
||||
### XHR
|
||||
|
||||
Basically, XHR is just a way to talk to a server.
|
||||
|
||||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial.
|
||||
|
||||
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
|
||||
```javascript
|
||||
var count = 0
|
||||
var increment = function() {
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
|
||||
data: {count: count + 1},
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(data) {
|
||||
count = parseInt(data.count)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Calling the increment function [upserts](https://en.wiktionary.org/wiki/upsert) an object `{count: 1}` to the `/api/tutorial/1` endpoint. This endpoint returns an object with the same `count` value that was sent to it. Notice that the `count` variable is only updated after the request completes, and it's updated with the response value from the server now.
|
||||
|
||||
Let's replace the event handler in the component to call the `increment` function instead of incrementing the `count` variable directly:
|
||||
|
||||
```javascript
|
||||
var Hello = {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", {onclick: increment}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clicking the button should now update the count.
|
||||
|
||||
---
|
||||
|
||||
We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR.
|
||||
|
||||
This should be enough to get you started writing the frontend for a real application. Now that you are comfortable with the basics of the Mithril API, [be sure to check out the simple application tutorial](http://mithril.js.org/simple-application.html), which walks you through building a realistic application.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module.exports = function(redrawService) {
|
|||
return
|
||||
}
|
||||
|
||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
|
||||
var run = function() {
|
||||
redrawService.render(root, Vnode(component))
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ function throttle(callback) {
|
|||
module.exports = function($window) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
if (e.redraw === false) e.redraw = undefined
|
||||
else redraw()
|
||||
})
|
||||
|
||||
|
||||
var callbacks = []
|
||||
function subscribe(key, callback) {
|
||||
unsubscribe(key)
|
||||
|
|
@ -38,10 +39,10 @@ module.exports = function($window) {
|
|||
var index = callbacks.indexOf(key)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,19 @@ module.exports = function($window, redrawService) {
|
|||
var run = function() {
|
||||
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
var bail = function() {
|
||||
routeService.setPath(defaultRoute, null, {replace: true})
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||
var update = lastUpdate = function(routeResolver, comp) {
|
||||
if (update !== lastUpdate) return
|
||||
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs = params, currentPath = path, lastUpdate = null
|
||||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs = params, currentPath = path, lastUpdate = null
|
||||
render = (routeResolver.render || identity).bind(routeResolver)
|
||||
run()
|
||||
}
|
||||
if (payload.view) update({}, payload)
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
|
||||
|
|
@ -37,7 +39,10 @@ module.exports = function($window, redrawService) {
|
|||
redrawService.subscribe(root, run)
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
if (lastUpdate != null) options = {replace: true}
|
||||
if (lastUpdate != null) {
|
||||
options = options || {}
|
||||
options.replace = true
|
||||
}
|
||||
lastUpdate = null
|
||||
routeService.setPath(path, data, options)
|
||||
}
|
||||
|
|
@ -54,6 +59,10 @@ module.exports = function($window, redrawService) {
|
|||
route.set(href, undefined, undefined)
|
||||
}
|
||||
}
|
||||
route.param = function(key) {
|
||||
if(typeof attrs !== "undefined" && typeof key !== "undefined") return attrs[key]
|
||||
return attrs
|
||||
}
|
||||
|
||||
return route
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<script src="../../test-utils/pushStateMock.js"></script>
|
||||
<script src="../../test-utils/xhrMock.js"></script>
|
||||
<script src="../../test-utils/browserMock.js"></script>
|
||||
<script src="../../test-utils/component.js"></script>
|
||||
|
||||
<script src="../../promise/promise.js"></script>
|
||||
<script src="../../render/vnode.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
|
|
@ -19,220 +19,236 @@ o.spec("mount", function() {
|
|||
|
||||
redrawService = apiRedraw($window)
|
||||
mount = apiMounter(redrawService)
|
||||
render = coreRenderer($window).render
|
||||
render = redrawService.render
|
||||
})
|
||||
|
||||
o("throws on invalid `root` DOM node", function() {
|
||||
o("throws on invalid component", function() {
|
||||
var threw = false
|
||||
try {
|
||||
mount(null, {view: function() {}})
|
||||
mount(root, {})
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
o("renders into `root`", function() {
|
||||
mount(root, {
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
o("throws on invalid `root` DOM node", function() {
|
||||
var threw = false
|
||||
try {
|
||||
mount(null, createComponent({view: function() {}}))
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
o("mounting null unmounts", function() {
|
||||
mount(root, {
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
|
||||
mount(root, null)
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
mount(root, {
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit,
|
||||
onupdate : onupdate,
|
||||
onclick : onclick,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
o(onclick.callCount).equals(1)
|
||||
o(onclick.this).equals(root.firstChild)
|
||||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("redraws several mount points on events", function(done, timeout) {
|
||||
timeout(60)
|
||||
|
||||
var onupdate0 = o.spy()
|
||||
var oninit0 = o.spy()
|
||||
var onclick0 = o.spy()
|
||||
var onupdate1 = o.spy()
|
||||
var oninit1 = o.spy()
|
||||
var onclick1 = o.spy()
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [
|
||||
m("#child0"),
|
||||
m("#child1")
|
||||
])
|
||||
|
||||
mount(root.childNodes[0], {
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit0,
|
||||
onupdate : onupdate0,
|
||||
onclick : onclick0,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
o(oninit0.callCount).equals(1)
|
||||
o(onupdate0.callCount).equals(0)
|
||||
|
||||
mount(root.childNodes[1], {
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit1,
|
||||
onupdate : onupdate1,
|
||||
onclick : onclick1,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
o(oninit1.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(0)
|
||||
|
||||
root.childNodes[0].firstChild.dispatchEvent(e)
|
||||
o(onclick0.callCount).equals(1)
|
||||
o(onclick0.this).equals(root.childNodes[0].firstChild)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(1)
|
||||
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
mount(root, {
|
||||
view: function() {
|
||||
return m("div", {
|
||||
oninit: oninit,
|
||||
onupdate: onupdate,
|
||||
onclick: function(e) {
|
||||
e.redraw = false
|
||||
o("renders into `root`", function() {
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("mounting null unmounts", function() {
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
}))
|
||||
|
||||
mount(root, null)
|
||||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit,
|
||||
onupdate : onupdate,
|
||||
onclick : onclick,
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
o(onclick.callCount).equals(1)
|
||||
o(onclick.this).equals(root.firstChild)
|
||||
o(onclick.args[0].type).equals("click")
|
||||
o(onclick.args[0].target).equals(root.firstChild)
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("redraws several mount points on events", function(done, timeout) {
|
||||
timeout(60)
|
||||
|
||||
var onupdate0 = o.spy()
|
||||
var oninit0 = o.spy()
|
||||
var onclick0 = o.spy()
|
||||
var onupdate1 = o.spy()
|
||||
var oninit1 = o.spy()
|
||||
var onclick1 = o.spy()
|
||||
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
render(root, [
|
||||
m("#child0"),
|
||||
m("#child1")
|
||||
])
|
||||
|
||||
mount(root.childNodes[0], createComponent({
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit0,
|
||||
onupdate : onupdate0,
|
||||
onclick : onclick0,
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
o(oninit0.callCount).equals(1)
|
||||
o(onupdate0.callCount).equals(0)
|
||||
|
||||
mount(root.childNodes[1], createComponent({
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit : oninit1,
|
||||
onupdate : onupdate1,
|
||||
onclick : onclick1,
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
o(oninit1.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(0)
|
||||
|
||||
root.childNodes[0].firstChild.dispatchEvent(e)
|
||||
o(onclick0.callCount).equals(1)
|
||||
o(onclick0.this).equals(root.childNodes[0].firstChild)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(1)
|
||||
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
||||
mount(root, createComponent({
|
||||
view: function() {
|
||||
return m("div", {
|
||||
oninit: oninit,
|
||||
onupdate: onupdate,
|
||||
onclick: function(e) {
|
||||
e.redraw = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("redraws when the render function is run", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit: oninit,
|
||||
onupdate: onupdate
|
||||
})
|
||||
}
|
||||
}))
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
var i = 0
|
||||
mount(root, createComponent({view: function() {i++}}))
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
done()
|
||||
},40)
|
||||
})
|
||||
})
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("redraws when the render function is run", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
|
||||
mount(root, {
|
||||
view : function() {
|
||||
return m("div", {
|
||||
oninit: oninit,
|
||||
onupdate: onupdate
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onupdate.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
var i = 0
|
||||
mount(root, {view: function() {i++}})
|
||||
var before = i
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
done()
|
||||
},40)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ var browserMock = require("../../test-utils/browserMock")
|
|||
|
||||
var m = require("../../render/hyperscript")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiRouter = require("../../api/router")
|
||||
var Promise = require("../../promise/promise")
|
||||
|
|
@ -31,7 +30,7 @@ o.spec("route", function() {
|
|||
o("throws on invalid `root` DOM node", function() {
|
||||
var threw = false
|
||||
try {
|
||||
route(null, '/', {'/':{view: function() {}}})
|
||||
route(null, "/", {"/":{view: function() {}}})
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
|
|
@ -51,7 +50,7 @@ o.spec("route", function() {
|
|||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (#1275)", function() {
|
||||
o("routed mount points can redraw synchronously (POJO component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
|
@ -65,6 +64,39 @@ o.spec("route", function() {
|
|||
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (constructible component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
var Cmp = function(){}
|
||||
Cmp.prototype.view = view
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {"/":Cmp})
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (closure component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
function Cmp() {return {view: view}}
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {"/":Cmp})
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
|
||||
})
|
||||
|
||||
o("default route doesn't break back button", function(done) {
|
||||
$window.location.href = "http://old.com"
|
||||
$window.location.href = "http://new.com"
|
||||
|
|
@ -173,7 +205,6 @@ o.spec("route", function() {
|
|||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
e.initEvent("click", true, true)
|
||||
|
|
@ -193,9 +224,11 @@ o.spec("route", function() {
|
|||
}
|
||||
})
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
root.firstChild.dispatchEvent(e)
|
||||
|
||||
o(oninit.callCount).equals(1)
|
||||
o(e.redraw).notEquals(false)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
callAsync(function() {
|
||||
|
|
@ -321,11 +354,6 @@ o.spec("route", function() {
|
|||
o("accepts RouteResolver with onmatch that returns Promise<undefined>", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
|
|
@ -362,11 +390,6 @@ o.spec("route", function() {
|
|||
o("accepts RouteResolver with onmatch that returns Promise<any>", function(done) {
|
||||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
|
|
@ -404,14 +427,9 @@ o.spec("route", function() {
|
|||
var matchCount = 0
|
||||
var renderCount = 0
|
||||
var spy = o.spy()
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("span")
|
||||
}
|
||||
}
|
||||
|
||||
var resolver = {
|
||||
onmatch: function(args, requestedPath) {
|
||||
onmatch: function() {
|
||||
matchCount++
|
||||
return Promise.reject(new Error("error"))
|
||||
},
|
||||
|
|
@ -466,7 +484,7 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("changing `vnode.key` in `render` resets the component", function(done, timeout){
|
||||
o("changing `vnode.key` in `render` resets the component", function(done){
|
||||
var oninit = o.spy()
|
||||
var Component = {
|
||||
oninit: oninit,
|
||||
|
|
@ -512,25 +530,19 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(renderCount).equals(1)
|
||||
})
|
||||
|
||||
o("RouteResolver `render` does not have component semantics", function(done) {
|
||||
var renderCount = 0
|
||||
var A = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, "/a", {
|
||||
"/a" : {
|
||||
render: function(vnode) {
|
||||
render: function() {
|
||||
return m("div")
|
||||
},
|
||||
},
|
||||
"/b" : {
|
||||
render: function(vnode) {
|
||||
render: function() {
|
||||
return m("div")
|
||||
},
|
||||
},
|
||||
|
|
@ -599,7 +611,7 @@ o.spec("route", function() {
|
|||
onmatch: function() {
|
||||
matchCount++
|
||||
},
|
||||
render: function(vnode) {
|
||||
render: function() {
|
||||
renderCount++
|
||||
return {tag: Component}
|
||||
},
|
||||
|
|
@ -655,7 +667,7 @@ o.spec("route", function() {
|
|||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
route.set("/b", {}, {state: {a: 5}})
|
||||
},
|
||||
render: render
|
||||
},
|
||||
|
|
@ -674,6 +686,7 @@ o.spec("route", function() {
|
|||
o(view.callCount).equals(1)
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o($window.history.state).deepEquals({a: 5})
|
||||
|
||||
done()
|
||||
})
|
||||
|
|
@ -693,7 +706,7 @@ o.spec("route", function() {
|
|||
render: render
|
||||
},
|
||||
"/b" : {
|
||||
render: function(vnode){
|
||||
render: function(){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
|
|
@ -805,7 +818,7 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
callAsync(function() {
|
||||
route.set('/b')
|
||||
route.set("/b")
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
|
|
@ -832,7 +845,7 @@ o.spec("route", function() {
|
|||
render: render
|
||||
},
|
||||
"/b" : {
|
||||
onmatch: function(vnode){
|
||||
onmatch: function(){
|
||||
redirected = true
|
||||
return {view: function() {}}
|
||||
}
|
||||
|
|
@ -862,7 +875,7 @@ o.spec("route", function() {
|
|||
render: render
|
||||
},
|
||||
"/b" : {
|
||||
render: function(vnode){
|
||||
render: function(){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
|
|
@ -891,7 +904,7 @@ o.spec("route", function() {
|
|||
render: render
|
||||
},
|
||||
"/b" : {
|
||||
view: function(vnode){
|
||||
view: function(){
|
||||
redirected = true
|
||||
}
|
||||
}
|
||||
|
|
@ -999,7 +1012,7 @@ o.spec("route", function() {
|
|||
var render = o.spy(function() {return m("div")})
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, '/', {
|
||||
route(root, "/", {
|
||||
"/": {
|
||||
onmatch: onmatch,
|
||||
render: render
|
||||
|
|
@ -1048,23 +1061,23 @@ o.spec("route", function() {
|
|||
|
||||
o("routing with RouteResolver works more than once", function(done) {
|
||||
$window.location.href = prefix + "/a"
|
||||
route(root, '/a', {
|
||||
'/a': {
|
||||
route(root, "/a", {
|
||||
"/a": {
|
||||
render: function() {
|
||||
return m("a", "a")
|
||||
}
|
||||
},
|
||||
'/b': {
|
||||
"/b": {
|
||||
render: function() {
|
||||
return m("b", "b")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
route.set('/b')
|
||||
route.set("/b")
|
||||
|
||||
callAsync(function() {
|
||||
route.set('/a')
|
||||
route.set("/a")
|
||||
|
||||
callAsync(function() {
|
||||
o(root.firstChild.nodeName).equals("A")
|
||||
|
|
@ -1089,7 +1102,7 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
},
|
||||
render: function(vnode) {
|
||||
render: function() {
|
||||
rendered = true
|
||||
resolved = "a"
|
||||
}
|
||||
|
|
@ -1147,7 +1160,7 @@ o.spec("route", function() {
|
|||
route.set("/b")
|
||||
})
|
||||
},
|
||||
render: function(vnode) {
|
||||
render: function() {
|
||||
rendered = true
|
||||
resolved = "a"
|
||||
}
|
||||
|
|
@ -1177,7 +1190,7 @@ o.spec("route", function() {
|
|||
var i = 0
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
"/": {view: function(v) {i++}}
|
||||
"/": {view: function() {i++}}
|
||||
})
|
||||
var before = i
|
||||
|
||||
|
|
@ -1194,6 +1207,30 @@ o.spec("route", function() {
|
|||
done()
|
||||
}, FRAME_BUDGET * 2)
|
||||
})
|
||||
|
||||
o("m.route.param is available outside of route handlers", function(done) {
|
||||
$window.location.href = prefix + "/"
|
||||
|
||||
route(root, "/1", {
|
||||
"/:id" : {
|
||||
view : function() {
|
||||
o(route.param("id")).equals("1")
|
||||
|
||||
return m("div")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
o(route.param("id")).equals(undefined);
|
||||
o(route.param()).deepEquals(undefined);
|
||||
|
||||
callAsync(function() {
|
||||
o(route.param("id")).equals("1")
|
||||
o(route.param()).deepEquals({id:"1"})
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use strict"
|
||||
|
||||
var m = require("./index")
|
||||
if (typeof module !== "undefined") module["exports"] = m
|
||||
else window.m = m
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
require("../cli")
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ function run(input, output) {
|
|||
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
|
||||
.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version
|
||||
|
||||
code = "new function() {\n" + code + "\n}"
|
||||
code = ";(function() {\n" + code + "\n}());"
|
||||
|
||||
if (!isFile(output) || code !== read(output)) {
|
||||
//try {new Function(code); console.log("build completed at " + new Date())} catch (e) {}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var bundle = require("../bundle")
|
||||
|
||||
|
|
@ -5,10 +7,10 @@ var fs = require("fs")
|
|||
|
||||
var ns = "bundler/tests/"
|
||||
function read(filepath) {
|
||||
try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {}
|
||||
try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {/* ignore */}
|
||||
}
|
||||
function write(filepath, data) {
|
||||
try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {}
|
||||
try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {/* ignore */}
|
||||
if (exists) throw new Error("Don't call `write('" + filepath + "')`. Cannot overwrite file")
|
||||
fs.writeFileSync(ns + filepath, data, "utf8")
|
||||
}
|
||||
|
|
@ -18,255 +20,255 @@ function remove(filepath) {
|
|||
|
||||
o.spec("bundler", function() {
|
||||
o("relative imports works", function() {
|
||||
write("a.js", `var b = require("./b")`)
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("a.js", 'var b = require("./b")')
|
||||
write("b.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports works with semicolons", function() {
|
||||
write("a.js", `var b = require("./b");`)
|
||||
write("b.js", `module.exports = 1;`)
|
||||
write("a.js", 'var b = require("./b");')
|
||||
write("b.js", "module.exports = 1;")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b = 1;\n}`)
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b = 1;\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports works with let", function() {
|
||||
write("a.js", `let b = require("./b")`)
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("a.js", 'let b = require("./b")')
|
||||
write("b.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nlet b = 1\n}`)
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nlet b = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports works with const", function() {
|
||||
write("a.js", 'const b = require("./b")')
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("b.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nconst b = 1\n}`)
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nconst b = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports works with assignment", function() {
|
||||
write("a.js", `var a = {}\na.b = require("./b")`)
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("a.js", 'var a = {}\na.b = require("./b")')
|
||||
write("b.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar a = {}\na.b = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports works with reassignment", function() {
|
||||
write("a.js", `var b = {}\nb = require("./b")`)
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("a.js", 'var b = {}\nb = require("./b")')
|
||||
write("b.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b = {}\nb = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports removes extra use strict", function() {
|
||||
write("a.js", `"use strict"\nvar b = require("./b")`)
|
||||
write("b.js", `"use strict"\nmodule.exports = 1`)
|
||||
write("a.js", '"use strict"\nvar b = require("./b")')
|
||||
write("b.js", '"use strict"\nmodule.exports = 1')
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\n"use strict"\nvar b = 1\n}`)
|
||||
|
||||
o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());')
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports removes extra use strict using single quotes", function() {
|
||||
write("a.js", `'use strict'\nvar b = require("./b")`)
|
||||
write("b.js", `'use strict'\nmodule.exports = 1`)
|
||||
write("a.js", "'use strict'\nvar b = require(\"./b\")")
|
||||
write("b.js", "'use strict'\nmodule.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\n'use strict'\nvar b = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\n'use strict'\nvar b = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("relative imports removes extra use strict using mixed quotes", function() {
|
||||
write("a.js", `"use strict"\nvar b = require("./b")`)
|
||||
write("b.js", `'use strict'\nmodule.exports = 1`)
|
||||
write("a.js", '"use strict"\nvar b = require("./b")')
|
||||
write("b.js", "'use strict'\nmodule.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\n"use strict"\nvar b = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());')
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works w/ window", function() {
|
||||
write("a.js", `window.a = 1\nvar b = require("./b")`)
|
||||
write("b.js", `module.exports = function() {return a}`)
|
||||
write("a.js", 'window.a = 1\nvar b = require("./b")')
|
||||
write("b.js", "module.exports = function() {return a}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nwindow.a = 1\nvar b = function() {return a}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nwindow.a = 1\nvar b = function() {return a}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works without assignment", function() {
|
||||
write("a.js", `require("./b")`)
|
||||
write("b.js", `1 + 1`)
|
||||
write("a.js", 'require("./b")')
|
||||
write("b.js", "1 + 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\n1 + 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\n1 + 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used fluently", function() {
|
||||
write("a.js", `var b = require("./b").toString()`)
|
||||
write("b.js", `module.exports = []`)
|
||||
write("a.js", 'var b = require("./b").toString()')
|
||||
write("b.js", "module.exports = []")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0.toString()\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used fluently w/ multiline", function() {
|
||||
write("a.js", `var b = require("./b")\n\t.toString()`)
|
||||
write("b.js", `module.exports = []`)
|
||||
write("a.js", 'var b = require("./b")\n\t.toString()')
|
||||
write("b.js", "module.exports = []")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used w/ curry", function() {
|
||||
write("a.js", `var b = require("./b")()`)
|
||||
write("b.js", `module.exports = function() {}`)
|
||||
write("a.js", 'var b = require("./b")()')
|
||||
write("b.js", "module.exports = function() {}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = function() {}\nvar b = _0()\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0()\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used w/ curry w/ multiline", function() {
|
||||
write("a.js", `var b = require("./b")\n()`)
|
||||
write("b.js", `module.exports = function() {}`)
|
||||
write("a.js", 'var b = require("./b")\n()')
|
||||
write("b.js", "module.exports = function() {}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = function() {}\nvar b = _0\n()\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0\n()\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used fluently in one place and not in another", function() {
|
||||
write("a.js", `var b = require("./b").toString()\nvar c = require("./c")`)
|
||||
write("b.js", `module.exports = []`)
|
||||
write("c.js", `var b = require("./b")\nmodule.exports = function() {return b}`)
|
||||
write("a.js", 'var b = require("./b").toString()\nvar c = require("./c")')
|
||||
write("b.js", "module.exports = []")
|
||||
write("c.js", 'var b = require("./b")\nmodule.exports = function() {return b}')
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if used in sequence", function() {
|
||||
write("a.js", `var b = require("./b"), c = require("./c")`)
|
||||
write("b.js", `module.exports = 1`)
|
||||
write("c.js", `var x\nmodule.exports = 2`)
|
||||
write("a.js", 'var b = require("./b"), c = require("./c")')
|
||||
write("b.js", "module.exports = 1")
|
||||
write("c.js", "var x\nmodule.exports = 2")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b = 1\nvar x\nvar c = 2\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b = 1\nvar x\nvar c = 2\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if assigned to property", function() {
|
||||
write("a.js", `var x = {}\nx.b = require("./b")\nx.c = require("./c")`)
|
||||
write("b.js", `var bb = 1\nmodule.exports = bb`)
|
||||
write("c.js", `var cc = 2\nmodule.exports = cc`)
|
||||
write("a.js", 'var x = {}\nx.b = require("./b")\nx.c = require("./c")')
|
||||
write("b.js", "var bb = 1\nmodule.exports = bb")
|
||||
write("c.js", "var cc = 2\nmodule.exports = cc")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if assigned to property using bracket notation", function() {
|
||||
write("a.js", `var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")`)
|
||||
write("b.js", `var bb = 1\nmodule.exports = bb`)
|
||||
write("c.js", `var cc = 2\nmodule.exports = cc`)
|
||||
write("a.js", 'var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")')
|
||||
write("b.js", "var bb = 1\nmodule.exports = bb")
|
||||
write("c.js", "var cc = 2\nmodule.exports = cc")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(';(function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}());')
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if collision", function() {
|
||||
write("a.js", `var b = require("./b")`)
|
||||
write("b.js", `var b = 1\nmodule.exports = 2`)
|
||||
write("a.js", 'var b = require("./b")')
|
||||
write("b.js", "var b = 1\nmodule.exports = 2")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b0 = 1\nvar b = 2\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = 2\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if multiple aliases", function() {
|
||||
write("a.js", `var b = require("./b")\n`)
|
||||
write("b.js", `var b = require("./c")\nb.x = 1\nmodule.exports = b`)
|
||||
write("c.js", `var b = {}\nmodule.exports = b`)
|
||||
write("a.js", 'var b = require("./b")\n')
|
||||
write("b.js", 'var b = require("./c")\nb.x = 1\nmodule.exports = b')
|
||||
write("c.js", "var b = {}\nmodule.exports = b")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b = {}\nb.x = 1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("works if multiple collision", function() {
|
||||
write("a.js", `var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")`)
|
||||
write("b.js", `var a = 1\nmodule.exports = a`)
|
||||
write("c.js", `var a = 2\nmodule.exports = a`)
|
||||
write("d.js", `var a = 3\nmodule.exports = a`)
|
||||
write("a.js", 'var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")')
|
||||
write("b.js", "var a = 1\nmodule.exports = a")
|
||||
write("c.js", "var a = 2\nmodule.exports = a")
|
||||
write("d.js", "var a = 3\nmodule.exports = a")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
|
|
@ -274,38 +276,38 @@ o.spec("bundler", function() {
|
|||
remove("out.js")
|
||||
})
|
||||
o("works if included multiple times", function() {
|
||||
write("a.js", `module.exports = 123`)
|
||||
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
|
||||
write("c.js", `var a = require("./a").toString()\nvar b = require("./b")`)
|
||||
write("a.js", "module.exports = 123")
|
||||
write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
|
||||
write("c.js", 'var a = require("./a").toString()\nvar b = require("./b")')
|
||||
bundle(ns + "c.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
})
|
||||
o("works if included multiple times reverse", function() {
|
||||
write("a.js", `module.exports = 123`)
|
||||
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
|
||||
write("c.js", `var b = require("./b")\nvar a = require("./a").toString()`)
|
||||
write("a.js", "module.exports = 123")
|
||||
write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
|
||||
write("c.js", 'var b = require("./b")\nvar a = require("./a").toString()')
|
||||
bundle(ns + "c.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
})
|
||||
o("reuses binding if possible", function() {
|
||||
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
|
||||
write("b.js", `var d = require("./d")\nmodule.exports = function() {return d + 1}`)
|
||||
write("c.js", `var d = require("./d")\nmodule.exports = function() {return d + 2}`)
|
||||
write("d.js", `module.exports = 1`)
|
||||
write("a.js", 'var b = require("./b")\nvar c = require("./c")')
|
||||
write("b.js", 'var d = require("./d")\nmodule.exports = function() {return d + 1}')
|
||||
write("c.js", 'var d = require("./d")\nmodule.exports = function() {return d + 2}')
|
||||
write("d.js", "module.exports = 1")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
|
|
@ -313,71 +315,71 @@ o.spec("bundler", function() {
|
|||
remove("out.js")
|
||||
})
|
||||
o("disambiguates conflicts if imported collides with itself", function() {
|
||||
write("a.js", `var b = require("./b")`)
|
||||
write("b.js", `var b = 1\nmodule.exports = function() {return b}`)
|
||||
write("a.js", 'var b = require("./b")')
|
||||
write("b.js", "var b = 1\nmodule.exports = function() {return b}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b0 = 1\nvar b = function() {return b0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = function() {return b0}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("disambiguates conflicts if imported collides with something else", function() {
|
||||
write("a.js", `var a = 1\nvar b = require("./b")`)
|
||||
write("b.js", `var a = 2\nmodule.exports = function() {return a}`)
|
||||
write("a.js", 'var a = 1\nvar b = require("./b")')
|
||||
write("b.js", "var a = 2\nmodule.exports = function() {return a}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("disambiguates conflicts if imported collides with function declaration", function() {
|
||||
write("a.js", `function a() {}\nvar b = require("./b")`)
|
||||
write("b.js", `var a = 2\nmodule.exports = function() {return a}`)
|
||||
write("a.js", 'function a() {}\nvar b = require("./b")')
|
||||
write("b.js", "var a = 2\nmodule.exports = function() {return a}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("disambiguates conflicts if imported collides with another module's private", function() {
|
||||
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
|
||||
write("b.js", `var a = 1\nmodule.exports = function() {return a}`)
|
||||
write("c.js", `var a = 2\nmodule.exports = function() {return a}`)
|
||||
write("a.js", 'var b = require("./b")\nvar c = require("./c")')
|
||||
write("b.js", "var a = 1\nmodule.exports = function() {return a}")
|
||||
write("c.js", "var a = 2\nmodule.exports = function() {return a}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("c.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("does not mess up strings", function() {
|
||||
write("a.js", `var b = require("./b")`)
|
||||
write("b.js", `var b = "b b b \\\" b"\nmodule.exports = function() {return b}`)
|
||||
write("a.js", 'var b = require("./b")')
|
||||
write("b.js", 'var b = "b b b \\" b"\nmodule.exports = function() {return b}')
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(';(function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}());')
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
o("does not mess up properties", function() {
|
||||
write("a.js", `var b = require("./b")`)
|
||||
write("b.js", `var b = {b: 1}\nmodule.exports = function() {return b.b}`)
|
||||
write("a.js", 'var b = require("./b")')
|
||||
write("b.js", "var b = {b: 1}\nmodule.exports = function() {return b.b}")
|
||||
bundle(ns + "a.js", ns + "out.js")
|
||||
|
||||
o(read("out.js")).equals(`new function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}`)
|
||||
|
||||
|
||||
o(read("out.js")).equals(";(function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}());")
|
||||
|
||||
remove("a.js")
|
||||
remove("b.js")
|
||||
remove("out.js")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
|
|
@ -0,0 +1 @@
|
|||
mithril.js.org
|
||||
105
docs/animation.md
Normal file
105
docs/animation.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Animations
|
||||
|
||||
- [Technology choices](#technology-choices)
|
||||
- [Animation on element creation](#animation-on-element-creation)
|
||||
- [Animation on element removal](#animation-on-element-removal)
|
||||
- [Performance](#performance)
|
||||
|
||||
---
|
||||
|
||||
### Technology choices
|
||||
|
||||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast Javascript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge.
|
||||
|
||||
Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work.
|
||||
|
||||
---
|
||||
|
||||
### Animation on element creation
|
||||
|
||||
Animating an element via CSS when the element created couldn't be simpler. Just add an animation to a CSS class normally:
|
||||
|
||||
```css
|
||||
.fancy {animation:fade-in 0.5s;}
|
||||
@keyframes fade-in {
|
||||
from {opacity:0;}
|
||||
to {opacity:1;}
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
var FancyComponent = {
|
||||
view: function() {
|
||||
return m(".fancy", "Hello world")
|
||||
}
|
||||
}
|
||||
|
||||
m.mount(document.body, FancyComponent)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Animation on element removal
|
||||
|
||||
The problem with animating before removing an element is that we must wait until the animation is complete before we can actually remove the element. Fortunately, Mithril offers a [`onbeforeremove`](lifecycle-methods.md#onbeforeremove) hook that allows us to defer the removal of an element.
|
||||
|
||||
Let's create an `exit` animation that fades `opacity` from 1 to 0.
|
||||
|
||||
```css
|
||||
.exit {animation:fade-out 0.5s;}
|
||||
@keyframes fade-out {
|
||||
from {opacity:1;}
|
||||
to {opacity:0;}
|
||||
}
|
||||
```
|
||||
|
||||
Now let's create a contrived component that shows and hides the `FancyComponent` we created in the previous section:
|
||||
|
||||
```javascript
|
||||
var on = true
|
||||
|
||||
var Toggler = {
|
||||
view: function() {
|
||||
return [
|
||||
m("button", {onclick: function() {on = !on}}, "Toggle"),
|
||||
on ? m(FancyComponent) : null,
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Next, let's modify `FancyComponent` so that it fades out when removed:
|
||||
|
||||
```javascript
|
||||
var FancyComponent = {
|
||||
onbeforeremove: function(vnode) {
|
||||
vnode.dom.classList.add("exit")
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, 500)
|
||||
})
|
||||
},
|
||||
view: function() {
|
||||
return m(".fancy", "Hello world")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`vnode.dom` points to the root DOM element of the component (`<div class="fancy">`). We use the classList API here to add an `exit` class to `<div class="fancy">`.
|
||||
|
||||
Then we return a [Promise](promise.md) that resolves after half a second. When we return a promise from `onbeforeremove`, Mithril waits until the promise is resolved and only then it removes the element. In this case, it waits half a second, giving the exit animation the exact time it needs to complete.
|
||||
|
||||
We can verify that both the enter and exit animations work by mounting the `Toggler` component:
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, Toggler)
|
||||
```
|
||||
|
||||
Note that the `onbeforeremove` hook only fires on the element that loses its `parentNode` when an element gets detached from the DOM. This behavior is by design and exists to prevent a potential jarring user experience where every conceivable exit animation on the page would run on a route change. If your exit animation is not running, make sure to attach the `onbeforeremove` handler as high up the tree as it makes sense to ensure that your animation code is called.
|
||||
|
||||
---
|
||||
|
||||
### Performance
|
||||
|
||||
When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`.
|
||||
|
||||
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](http://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute postioned element over a fixed element).
|
||||
|
|
@ -87,9 +87,9 @@ m.route(document.body, "/", {
|
|||
|
||||
---
|
||||
|
||||
### When Mithril does not redraws
|
||||
### When Mithril does not redraw
|
||||
|
||||
Mithril does not redraw after `setTimeout`, `setInterval`, `requestAnimationFrame` and 3rd party library event handlers (e.g. Socket.io callbacks). In those cases, you must manually call [`m.redraw()`](redraw.md).
|
||||
Mithril does not redraw after `setTimeout`, `setInterval`, `requestAnimationFrame`, raw `Promise` resolutions and 3rd party library event handlers (e.g. Socket.io callbacks). In those cases, you must manually call [`m.redraw()`](redraw.md).
|
||||
|
||||
Mithril also does not redraw after lifecycle methods. Parts of the UI may be redrawn after an `oninit` handler, but other parts of the UI may already have been redrawn when a given `oninit` handler fires. Handlers like `oncreate` and `onupdate` fire after the UI has been redrawn.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,48 @@
|
|||
# Change log
|
||||
|
||||
- [v1.1.1](#v111)
|
||||
- [v1.1.0](#v110)
|
||||
- [v1.0.1](#v101)
|
||||
- [Migrating from v0.2.x](#migrating-from-v02x)
|
||||
- [Older docs](http://mithril.js.org/archive/v0.2.5/index.html)
|
||||
|
||||
---
|
||||
|
||||
### v1.1.1
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/MithrilJS/mithril.js/issues/1752) / [#1753](https://github.com/MithrilJS/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/MithrilJS/mithril.js/issues/1764) / [#1769](https://github.com/MithrilJS/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
|
||||
|
||||
- fix IE11 input[type] error - [#1610](https://github.com/MithrilJS/mithril.js/issues/1610)
|
||||
- apply [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) to unkeyed children case
|
||||
- fix abort detection [#1612](https://github.com/MithrilJS/mithril.js/issues/1612)
|
||||
- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/MithrilJS/mithril.js/issues/1593)
|
||||
|
||||
---
|
||||
|
||||
### v1.0.1
|
||||
|
||||
#### News
|
||||
|
||||
- performance improvements in IE [#1598](https://github.com/MithrilJS/mithril.js/pull/1598)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- prevent infinite loop in non-existent default route - [#1579](https://github.com/MithrilJS/mithril.js/issues/1579)
|
||||
- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/MithrilJS/mithril.js/issues/1609)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -16,6 +58,8 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
|
|||
- [Changes in redraw behaviour](#changes-in-redraw-behaviour)
|
||||
- [No more redraw locks](#no-more-redraw-locks)
|
||||
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers)
|
||||
- [Synchronous redraw removed](#synchronous-redraw-removed)
|
||||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
|
||||
- [Component `controller` function](#component-controller-function)
|
||||
- [Component arguments](#component-arguments)
|
||||
- [`view()` parameters](#view-parameters)
|
||||
|
|
@ -25,21 +69,21 @@ If you are migrating, consider using the [mithril-codemods](https://www.npmjs.co
|
|||
- [`m.route` and anchor tags](#mroute-and-anchor-tags)
|
||||
- [Reading/writing the current route](#readingwriting-the-current-route)
|
||||
- [Accessing route params](#accessing-route-params)
|
||||
- [Building/Parsing query strings](#buildingparsing-query-strings)
|
||||
- [Preventing unmounting](#preventing-unmounting)
|
||||
- [Run code on component removal](#run-code-on-component-removal)
|
||||
- [`m.request`](#mrequest)
|
||||
- [`m.deferred` removed](#mdeferred-removed)
|
||||
- [`m.sync` removed](#msync-removed)
|
||||
- [`xlink` namespace required](#xlink-namespace-required)
|
||||
- [Nested arrays in views](#nested-arrays-in-views)
|
||||
- [`vnode` equality checks](#vnode-equality-checks)
|
||||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
|
||||
- [Synchronous redraw removed](#synchronous-redraw-removed)
|
||||
|
||||
---
|
||||
|
||||
## `m.prop` removed
|
||||
|
||||
In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core.
|
||||
In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md).
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
|
|
@ -110,7 +154,8 @@ m("div", {
|
|||
onbeforeupdate : function(vnode, old) { /*...*/ },
|
||||
// Called after the node is updated
|
||||
onupdate : function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, return a Promise that resolves when ready for the node to be removed from the DOM
|
||||
// Called before the node is removed, return a Promise that resolves when
|
||||
// ready for the node to be removed from the DOM
|
||||
onbeforeremove : function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, but after onbeforeremove calls done()
|
||||
onremove : function(vnode) { /*...*/ }
|
||||
|
|
@ -133,7 +178,7 @@ In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked dr
|
|||
|
||||
`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`.
|
||||
|
||||
### `v0.2.x`
|
||||
#### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
|
|
@ -143,7 +188,7 @@ m("div", {
|
|||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
#### `v1.x`
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
|
|
@ -153,6 +198,26 @@ m("div", {
|
|||
})
|
||||
```
|
||||
|
||||
### Synchronous redraw removed
|
||||
|
||||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed.
|
||||
|
||||
#### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.redraw(true) // redraws immediately & synchronously
|
||||
```
|
||||
|
||||
#### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.redraw() // schedules a redraw on the next requestAnimationFrame tick
|
||||
```
|
||||
|
||||
### `m.startComputation`/`m.endComputation` removed
|
||||
|
||||
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x.
|
||||
|
||||
---
|
||||
|
||||
## Component `controller` function
|
||||
|
|
@ -400,13 +465,16 @@ m.route.set("/other/route")
|
|||
|
||||
## Accessing route params
|
||||
|
||||
In `v0.2.x` reading route params was all handled through the `m.route.param()` method. In `v1.x` any route params are passed as the `attrs` object on the vnode passed as the first argument to lifecycle methods/`view`.
|
||||
In `v0.2.x` reading route params was entirely handled through `m.route.param()`. This API is still available in `v1.x`, and additionally any route params are passed as properties in the `attrs` object on the vnode.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
controller : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
|
|
@ -421,9 +489,11 @@ m.route(document.body, "/booga", {
|
|||
"/:attr" : {
|
||||
oninit : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -431,6 +501,28 @@ m.route(document.body, "/booga", {
|
|||
|
||||
---
|
||||
|
||||
## Building/Parsing query strings
|
||||
|
||||
`v0.2.x` used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In `v1.x` these have been broken out and attached to the root `m`.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var qs = m.route.buildQueryString({ a : 1 });
|
||||
|
||||
var obj = m.route.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var qs = m.buildQueryString({ a : 1 });
|
||||
|
||||
var obj = m.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preventing unmounting
|
||||
|
||||
It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met.
|
||||
|
|
@ -462,6 +554,40 @@ var Component = {
|
|||
|
||||
---
|
||||
|
||||
## Run code on component removal
|
||||
|
||||
Components no longer call `this.onunload` when they are being removed. They now use the standardized lifecycle hook `onremove`.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
onremove : function() {
|
||||
// ...
|
||||
}
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## m.request
|
||||
|
||||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options.
|
||||
|
|
@ -499,7 +625,7 @@ setTimeout(function() {
|
|||
}, 1000)
|
||||
```
|
||||
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve its promise, and the `deserialize` callback is ignored.
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve the request promise, and the `deserialize` callback is ignored.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -526,11 +652,13 @@ greetAsync()
|
|||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var greetAsync = new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
var greetAsync = function() {
|
||||
return new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
.then(function(value) {return value + " world"})
|
||||
|
|
@ -602,27 +730,3 @@ Arrays now represent [fragments](fragment.md), which are structurally significan
|
|||
## `vnode` equality checks
|
||||
|
||||
If a vnode is strictly equal to the vnode occupying its place in the last draw, v1.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views).
|
||||
|
||||
---
|
||||
|
||||
## `m.startComputation`/`m.endComputation` removed
|
||||
|
||||
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x.
|
||||
|
||||
---
|
||||
|
||||
## Synchronous redraw removed
|
||||
|
||||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.redraw(true) // redraws immediately & synchronously
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.redraw() // schedules a redraw on the next requestAnimationFrame tick
|
||||
```
|
||||
|
|
|
|||
74
docs/code-of-conduct.md
Normal file
74
docs/code-of-conduct.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [github@patcavit.com](mailto:github@patcavit.com?subject=Mithril Code of Conduct). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
- [Structure](#structure)
|
||||
- [Lifecycle methods](#lifecycle-methods)
|
||||
- [Syntactic variants](#syntactic-variants)
|
||||
- [State](#state)
|
||||
- [Avoid-anti-patterns](#avoid-anti-patterns)
|
||||
- [Avoid anti-patterns](#avoid-anti-patterns)
|
||||
|
||||
### Structure
|
||||
|
||||
|
|
@ -100,6 +101,107 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl
|
|||
|
||||
---
|
||||
|
||||
### Syntactic variants
|
||||
|
||||
#### ES6 classes
|
||||
|
||||
Components can also be written using ES6 class syntax:
|
||||
|
||||
```javascript
|
||||
class ES6ClassComponent {
|
||||
constructor(vnode) {
|
||||
// vnode.state is undefined at this point
|
||||
this.kind = "ES6 class"
|
||||
}
|
||||
view() {
|
||||
return m("div", `Hello from an ${this.kind}`)
|
||||
}
|
||||
oncreate() {
|
||||
console.log(`A ${this.kind} component was created`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render.
|
||||
|
||||
They can be consumed in the same way regular components can.
|
||||
|
||||
```javascript
|
||||
// EXAMPLE: via m.render
|
||||
m.render(document.body, m(ES6ClassComponent))
|
||||
|
||||
// EXAMPLE: via m.mount
|
||||
m.mount(document.body, ES6ClassComponent)
|
||||
|
||||
// EXAMPLE: via m.route
|
||||
m.route(document.body, "/", {
|
||||
"/": ES6ClassComponent
|
||||
})
|
||||
|
||||
// EXAMPLE: component composition
|
||||
class AnotherES6ClassComponent {
|
||||
view() {
|
||||
return m("main", [
|
||||
m(ES6ClassComponent)
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Closure components
|
||||
|
||||
Functionally minded developers may prefer using the "closure component" syntax:
|
||||
|
||||
```javascript
|
||||
function closureComponent(vnode) {
|
||||
// vnode.state is undefined at this point
|
||||
var kind = "closure component"
|
||||
|
||||
return {
|
||||
view: function() {
|
||||
return m("div", "Hello from a " + kind)
|
||||
},
|
||||
oncreate: function() {
|
||||
console.log("We've created a " + kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The returned object must hold a `view` function, used to get the tree to render.
|
||||
|
||||
They can be consumed in the same way regular components can.
|
||||
|
||||
```javascript
|
||||
// EXAMPLE: via m.render
|
||||
m.render(document.body, m(closureComponent))
|
||||
|
||||
// EXAMPLE: via m.mount
|
||||
m.mount(document.body, closuresComponent)
|
||||
|
||||
// EXAMPLE: via m.route
|
||||
m.route(document.body, "/", {
|
||||
"/": closureComponent
|
||||
})
|
||||
|
||||
// EXAMPLE: component composition
|
||||
function anotherClosureComponent() {
|
||||
return {
|
||||
view: function() {
|
||||
return m("main", [
|
||||
m(closureComponent)
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Mixing component kinds
|
||||
|
||||
Components can be freely mixed. A Class component can have closure or POJO components as children, etc...
|
||||
|
||||
---
|
||||
|
||||
### State
|
||||
|
||||
Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns.
|
||||
|
|
@ -108,7 +210,7 @@ The state of a component can be accessed three ways: as a blueprint at initializ
|
|||
|
||||
#### At initialization
|
||||
|
||||
Any property attached to the component object is copied for every instance of the component. This allows simple state initialization.
|
||||
For POJO components, the component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple state initialization.
|
||||
|
||||
In the example below, `data` is a property of the `ComponentWithInitialState` component's state object.
|
||||
|
||||
|
|
@ -126,6 +228,10 @@ m(ComponentWithInitialState)
|
|||
// <div>Initial content</div>
|
||||
```
|
||||
|
||||
For class components, the state is an instance of the class, set right after the constructor is called.
|
||||
|
||||
For closure components, the state is the object returned by the closure, set right after the closure returns. The state object is mostly redundant for closure components (since variables defined in the closure scope can be used instead).
|
||||
|
||||
#### Via vnode.state
|
||||
|
||||
State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component.
|
||||
|
|
@ -174,6 +280,89 @@ Be aware that when using ES5 functions, the value of `this` in nested anonymous
|
|||
|
||||
Although Mithril is flexible, some code patterns are discouraged:
|
||||
|
||||
#### Avoid fat components
|
||||
|
||||
Generally speaking, a "fat" component is a component that has custom instance methods. In other words, you should avoid attaching functions to `vnode.state` or `this`. It's exceedingly rare to have logic that logically fits in a component instance method and that can't be reused by other components. It's relatively common that said logic might be needed by a different component down the road.
|
||||
|
||||
It's easier to refactor code if that logic is placed in the data layer than if it's tied to a component state.
|
||||
|
||||
Consider this fat component:
|
||||
|
||||
```javascript
|
||||
// views/Login.js
|
||||
// AVOID
|
||||
var Login = {
|
||||
username: "",
|
||||
password: "",
|
||||
setUsername: function(value) {
|
||||
this.username = value
|
||||
},
|
||||
setPassword: function(value) {
|
||||
this.password = value
|
||||
},
|
||||
canSubmit: function() {
|
||||
return this.username !== "" && this.password !== ""
|
||||
},
|
||||
login: function() {/*...*/},
|
||||
view: function() {
|
||||
return m(".login", [
|
||||
m("input[type=text]", {oninput: m.withAttr("value", this.setUsername.bind(this)), value: this.username}),
|
||||
m("input[type=password]", {oninput: m.withAttr("value", this.setPassword.bind(this)), value: this.password}),
|
||||
m("button", {disabled: !this.canSubmit(), onclick: this.login}, "Login"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Normally, in the context of a larger application, a login component like the one above exists alongside components for user registration and password recovery. Imagine that we want to be able to prepopulate the email field when navigating from the login screen to the registration or password recovery screens (or vice versa), so that the user doesn't need to re-type their email if they happened to fill the wrong page (or maybe you want to bump the user to the registration form if a username is not found).
|
||||
|
||||
Right away, we see that sharing the `username` and `password` fields from this component to another is difficult. This is because the fat component encapsulates its state, which by definition makes this state difficult to access from outside.
|
||||
|
||||
It makes more sense to refactor this component and pull the state code out of the component and into the application's data layer. This can be as simple as creating a new module:
|
||||
|
||||
```javascript
|
||||
// models/Auth.js
|
||||
// PREFER
|
||||
var Auth = {
|
||||
username: "",
|
||||
password: "",
|
||||
setUsername: function(value) {
|
||||
Auth.username = value
|
||||
},
|
||||
setPassword: function(value) {
|
||||
Auth.password = value
|
||||
},
|
||||
canSubmit: function() {
|
||||
return Auth.username !== "" && Auth.password !== ""
|
||||
},
|
||||
login: function() {/*...*/},
|
||||
}
|
||||
|
||||
module.exports = Auth
|
||||
```
|
||||
|
||||
Then, we can clean up the component:
|
||||
|
||||
```javascript
|
||||
// views/Login.js
|
||||
// PREFER
|
||||
var Auth = require("../models/Auth")
|
||||
|
||||
var Login = {
|
||||
view: function() {
|
||||
return m(".login", [
|
||||
m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}),
|
||||
m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}),
|
||||
m("button", {disabled: !Auth.canSubmit(), onclick: Auth.login}, "Login"),
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, the `Auth` module is now the source of truth for auth-related state, and a `Register` component can easily access this data, and even reuse methods like `canSubmit`, if needed. In addition, if validation code is required (for example, for the email field), you only need to modify `setEmail`, and that change will do email validation for any component that modifies an email field.
|
||||
|
||||
As a bonus, notice that we no longer need to use `.bind` to keep a reference to the state for the component's event handlers.
|
||||
|
||||
#### Avoid restrictive interfaces
|
||||
|
||||
Try to keep component interfaces generic - using `attrs` and `children` directly - unless the component requires special logic to operate on input.
|
||||
|
|
@ -206,7 +395,7 @@ var FlexibleComponent = {
|
|||
|
||||
#### Don't manipulate `children`
|
||||
|
||||
However, if a component is opinionated in how it applies attributes or children, you should switch to using custom attributes.
|
||||
If a component is opinionated in how it applies attributes or children, you should switch to using custom attributes.
|
||||
|
||||
Often it's desirable to define multiple sets of children, for example, if a component has a configurable title and body.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## How do I go about contributing ideas or new features?
|
||||
|
||||
Create an [issue thread on Github](https://github.com/lhorie/mithril.js/issues/new) to suggest your idea so the community can discuss it.
|
||||
Create an [issue thread on GitHub](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it.
|
||||
|
||||
If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities.
|
||||
|
||||
|
|
@ -20,12 +20,12 @@ Ideally, the best way to report bugs is to provide a small snippet of code where
|
|||
|
||||
To send a pull request:
|
||||
|
||||
- fork the repo (button at the top right in Github)
|
||||
- clone the forked repo to your computer (green button in Github)
|
||||
- fork the repo (button at the top right in GitHub)
|
||||
- clone the forked repo to your computer (green button in GitHub)
|
||||
- create a feature branch (run `git checkout -b the-feature-branch-name`)
|
||||
- make your changes
|
||||
- run the tests (run `npm t`)
|
||||
- submit a pull request (go to the pull requests tab in Github, click the green button and select your feature branch)
|
||||
- submit a pull request (go to the pull requests tab in GitHub, click the green button and select your feature branch)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Mithril was originally written by Leo Horie, but it is where it is today thanks
|
|||
|
||||
Special thanks to:
|
||||
|
||||
- Pat Cavit, who exposed most of the public API for Mithril 1.0 and brought in test coverage
|
||||
- Pat Cavit, who exposed most of the public API for Mithril 1.0, brought in test coverage and automated the publishing process
|
||||
- Isiah Meadows, who brought in linting, modernized the test suite and has been a strong voice in design discussions
|
||||
- Zoli Kahan, who replaced the original Promise implementation with one that actually worked properly
|
||||
- Alec Embke, who single-handedly wrote the JSON-P implementation
|
||||
|
|
@ -17,10 +17,10 @@ Special thanks to:
|
|||
- Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril 1.0
|
||||
- Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril
|
||||
- Pierre-Yves Gérardy, who consistently makes high quality contributions
|
||||
- Gyandeep Singh, who contributed significant IE performance improvements
|
||||
|
||||
Other people who also deserve recognition:
|
||||
|
||||
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html)
|
||||
- Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint)
|
||||
- the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril
|
||||
|
||||
|
|
|
|||
94
docs/css.md
Normal file
94
docs/css.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# CSS
|
||||
|
||||
- [Vanilla CSS](#vanilla-css)
|
||||
- [Tachyons](#tachyons)
|
||||
- [CSS-in-JS](#css-in-js)
|
||||
|
||||
---
|
||||
|
||||
### Vanilla CSS
|
||||
|
||||
For various reasons, CSS has a bad reputation and often developers reach for complex tools in an attempt to make styling more manageable. In this section, we'll take a step back and cover some tips on writing plain CSS:
|
||||
|
||||
- **Avoid using the space operator** - The vast majority of CSS maintainability issues are due to CSS specificity issues. The space operator defines a descendant (e.g. `.a .b`) and at the same time, it increases the level of specificity for the CSS rules that apply to that selector, sometimes overriding styles unexpectedly.
|
||||
|
||||
Instead, it's preferable to share a namespace prefix in all class names that belong to a logical group of elements:
|
||||
|
||||
```css
|
||||
/* AVOID */
|
||||
.chat.container {/*...*/}
|
||||
.chat .item {/*...*/}
|
||||
.chat .avatar {/*...*/}
|
||||
.chat .text {/*...*/}
|
||||
|
||||
/* PREFER */
|
||||
.chat-container {/*...*/}
|
||||
.chat-item {/*...*/}
|
||||
.chat-avatar {/*...*/}
|
||||
.chat-text {/*...*/}
|
||||
```
|
||||
|
||||
- **Use only single-class selectors** - This convention goes hand-in-hand with the previous one: avoiding high specificity selectors such as `#foo` or `div.bar` help decrease the likelyhood of specificity conflicts.
|
||||
|
||||
```css
|
||||
/* AVOID */
|
||||
#home {}
|
||||
input.highlighted {}
|
||||
|
||||
/* PREFER */
|
||||
.home {}
|
||||
.input-highlighted {}
|
||||
```
|
||||
|
||||
- **Develop naming conventions** - You can reduce naming collisions by defining keywords for certain types of UI elements. This is particularly effective when brand names are involved:
|
||||
|
||||
```css
|
||||
/* AVOID */
|
||||
.twitter {} /* icon link in footer */
|
||||
.facebook {} /* icon link in footer */
|
||||
/* later... */
|
||||
.modal.twitter {} /* tweet modal */
|
||||
.modal.facebook {} /* share modal */
|
||||
|
||||
/* PREFER */
|
||||
.link-twitter {}
|
||||
.link-facebook {}
|
||||
/* later... */
|
||||
.modal-twitter {}
|
||||
.modal-facebook {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Tachyons
|
||||
|
||||
[Tachyons](https://github.com/tachyons-css/tachyons) is a CSS framework, but the concept behind it can easily be used without the library itself.
|
||||
|
||||
The basic idea is that every class name must declare one and only one CSS rule. For example, `bw1` stands for `border-width:1px;`. To create a complex style, one simply combines class names representing each of the required CSS rules. For example, `.black.bg-dark-blue.br2` styles an element with blue background, black text and a 4px border-radius.
|
||||
|
||||
Since each class is small and atomic, it's essentially impossible to run into CSS conflicts.
|
||||
|
||||
As it turns out, the Tachyons convention fits extremely well with Mithril and JSX:
|
||||
|
||||
```jsx
|
||||
var Hero = ".black.bg-dark-blue.br2.pa3"
|
||||
|
||||
m.mount(document.body, <Hero>Hello</Hero>)
|
||||
// equivalent to `m(".black.bg-dark.br2.pa3", "Hello")`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CSS in JS
|
||||
|
||||
In plain CSS, all selectors live in the global scope and are prone to name collisions and specificity conflicts. CSS-in-JS aims to solve the issue of scoping in CSS, i.e. it groups related styles into non-global modules that are invisible to each other. CSS-in-JS is suitable for extremely large dev teams working on a single codebase, but it's not a good choice for most teams.
|
||||
|
||||
Nowadays there are [a lot of CSS-in-JS libraries with various degrees of robustness](https://github.com/MicheleBertoli/css-in-js).
|
||||
|
||||
The main problem with many of these libraries is that even though they require a non-trivial amount of transpiler tooling and configuration, they also require sacrificing code readability in order to work, e.g. `<a class={classnames(styles.button, styles.danger)}></a>` vs `<a class="button danger"></a>` (or `m("a.button.danger")` if we're using hyperscript).
|
||||
|
||||
Often sacrifices also need to be made at time of debugging, when mapping rendered CSS class names back to their source. Often all you get in browser developer tools is a class like `button_fvp6zc2gdj35evhsl73ffzq_0 danger_fgdl0s2a5fmle5g56rbuax71_0` with useless source maps (or worse, entirely criptic class names).
|
||||
|
||||
Another common issue is lack of support for less basic CSS features such as `@keyframes` and `@font-face`.
|
||||
|
||||
If you are adamant about using a CSS-in-JS library, consider using [J2C](https://github.com/j2css/j2c), which works without configuration and implements `@keyframes` and `@font-face`.
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
---
|
||||
|
||||
Mithril is written in ES5, and is fully compatible with ES6 as well.
|
||||
Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences.
|
||||
|
||||
In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5.
|
||||
|
||||
|
|
@ -70,10 +70,12 @@ Create a `.babelrc` file:
|
|||
Next, create a file called `webpack.config.js`
|
||||
|
||||
```javascript
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: './bin',
|
||||
path: path.resolve(__dirname, './bin'),
|
||||
filename: 'app.js',
|
||||
},
|
||||
module: {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
Here are some examples of Mithril in action
|
||||
|
||||
- [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html)
|
||||
- [DBMonster](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html)
|
||||
- [Markdown Editor](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/editor/index.html)
|
||||
- SVG: [Clock](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/tiger.html)
|
||||
- [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html)
|
||||
- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html)
|
||||
- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html)
|
||||
- [DBMonster](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html)
|
||||
- [Markdown Editor](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/editor/index.html)
|
||||
- SVG: [Clock](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/tiger.html)
|
||||
- [ThreadItJS](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html)
|
||||
- [TodoMVC](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/todomvc/index.html)
|
||||
|
||||
|
|
|
|||
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/favicon.png
Normal file
BIN
docs/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -35,11 +35,11 @@ Generates a fragment [vnode](vnodes.md)
|
|||
|
||||
`vnode = m.fragment(attrs, children)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`attrs` | `Object` | Yes | A map of attributes
|
||||
`children` | `Array<Vnode>` | Yes | A list of vnodes
|
||||
**returns** | `Vnode` | | A fragment [vnode](vnodes.md)
|
||||
Argument | Type | Required | Description
|
||||
----------- | --------------------------------------------------- | -------- | ---
|
||||
`attrs` | `Object` | Yes | A map of attributes
|
||||
`children` | `Array<Vnode|String|Number|Boolean|null|undefined>` | Yes | A list of vnodes
|
||||
**returns** | `Vnode` | | A fragment [vnode](vnodes.md)
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,23 +12,25 @@ If you're reading this page, you probably have used other frameworks to build ap
|
|||
|
||||
## Why not [insert favorite framework here]?
|
||||
|
||||
The reality is that most modern frameworks are fast, well-suited to build complex applications, and highly maintainable if you know how to use them effectively. There are examples of highly complex applications in the wild using just about every popular framework: Udemy uses Angular, AirBnB uses React, Gitlab uses Vue, Guild Wars 2 uses Mithril (yes, inside the game!). Clearly, these are all production-quality frameworks.
|
||||
The reality is that most modern frameworks are fast, well-suited to build complex applications, and maintainable if you know how to use them effectively. There are examples of highly complex applications in the wild using just about every popular framework: Udemy uses Angular, AirBnB uses React, Gitlab uses Vue, Guild Wars 2 uses Mithril (yes, inside the game!). Clearly, these are all production-quality frameworks.
|
||||
|
||||
As a rule of thumb, if your team is already heavily invested in another framework/library/stack, it makes more sense to stick with it, unless your team agrees that there's a very strong reason to justify a costly rewrite.
|
||||
|
||||
If you're starting something new, do consider giving Mithril a try, if nothing else, to see how much value Mithril adopters have been getting out of 8kb (gzipped) of code.
|
||||
However, if you're starting something new, do consider giving Mithril a try, if nothing else, to see how much value Mithril adopters have been getting out of 8kb (gzipped) of code. Mithril is used by many well-known companies (e.g. Vimeo, Nike, Fitbit), and it powers large open-sourced platforms too (e.g. Lichess, Flarum).
|
||||
|
||||
---
|
||||
|
||||
## Why use Mithril?
|
||||
|
||||
In one sentence: because **Mithril is pragmatic**. If you don't believe me, take 10 minutes to go over the [guide](introduction.md) to see how much it accomplishes, compared with official guides for other frameworks.
|
||||
In one sentence: because **Mithril is pragmatic**. This [10 minute guide](index.md) is a good example: that's how long it takes to learn components, XHR and routing - and that's just about the right amount of knowledge needed to build useful applications.
|
||||
|
||||
Mithril is all about getting stuff done. It comes out of the box with a compact set of tools that you'll likely need for building Single Page Applications, and no distractions. The Mithril API is small and focused, and it's designed to leverage previous knowledge - e.g. view language is Javascript, HTML attributes have no syntax caveats, Promises are Promises, hyperscript selectors mirror CSS and is JSX-compatible, `m.request` option names mirror [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) - all of this so you can get up to speed fast.
|
||||
Mithril is all about getting meaningful work done efficiently. Doing file uploads? [The docs show you how](request.md#file-uploads). Authentication? [Documented too](route.md#authentication). Exit animations? [You got it](animation.md). No extra libraries, no magic.
|
||||
|
||||
---
|
||||
|
||||
## React
|
||||
## Comparisons
|
||||
|
||||
### React
|
||||
|
||||
React is a view library maintained by Facebook.
|
||||
|
||||
|
|
@ -38,11 +40,11 @@ React and Mithril share a lot of similarities. If you already learned React, you
|
|||
- They both organize views via components
|
||||
- They both use Javascript as a flow control mechanism within views
|
||||
|
||||
The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project.
|
||||
The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project, and that those projects are that much more likely to cross the 1MB size line.
|
||||
|
||||
Mithril has built-in modules for common necessities such as routing and XHR, and the [guide](simple-application.md) demonstrates idiomatic usage. This approach is preferable for teams that value consistency and ease of onboarding.
|
||||
|
||||
### Performance
|
||||
#### Performance
|
||||
|
||||
Both React and Mithril care strongly about rendering performance, but go about it in different ways. In the past React had two DOM rendering implementations (one using the DOM API, and one using `innerHTML`). Its upcoming fiber architecture introduces scheduling and prioritization of units of work. React also has a sophisticated build system that disables various checks and error messages for production deployments, and various browser-specific optimizations. In addition, there are also several performance-oriented libraries that leverage React's `shouldComponentUpdate` hook and immutable data structure libraries' fast object equality checking properties to reduce virtual DOM reconciliation times. Generally speaking, React's approach to performance is to engineer relatively complex solutions.
|
||||
|
||||
|
|
@ -50,21 +52,21 @@ Mithril follows the less-is-more school of thought. It has a substantially small
|
|||
|
||||
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop:
|
||||
|
||||
React v15.4.1 | Mithril 1.0
|
||||
------------- | -------
|
||||
55.8 ms | 4.5 ms
|
||||
React | Mithril
|
||||
------- | -------
|
||||
55.8 ms | 4.5 ms
|
||||
|
||||
Library load times matter in applications that don't stay open for long periods of time (for example, anything in mobile) and cannot be improved via caching or other optimization techniques.
|
||||
|
||||
Since this is a micro-benchmark, you are encourage to replicate these tests yourself since hardware can heavily affect the numbers. Note that bundler frameworks like Webpack can move dependencies out before the timer calls to emulate static module resolution, so you should either copy the code from the compiled CDN files or open the output file from the bundler library, and manually add the high resolution timer calls `console.time` and `console.timeEnd` to the bundled script. Avoid using `new Date` and `performance.now`, as those mechanisms are not as statistically accurate.
|
||||
Since this is a micro-benchmark, you are encouraged to replicate these tests yourself since hardware can heavily affect the numbers. Note that bundler frameworks like Webpack can move dependencies out before the timer calls to emulate static module resolution, so you should either copy the code from the compiled CDN files or open the output file from the bundler library, and manually add the high resolution timer calls `console.time` and `console.timeEnd` to the bundled script. Avoid using `new Date` and `performance.now`, as those mechanisms are not as statistically accurate.
|
||||
|
||||
For your reading convenience, here's a version of that benchmark adapted to use CDNs on the web: the [benchmark for React is here](https://jsfiddle.net/0ovkv64u/), and the [benchmark for Mithril is here](https://jsfiddle.net/o7hxooqL/). Note that we're benchmarking all of Mithril rather than benchmarking only the rendering module (which would be equivalent in scope to React). Also note that this CDN-driven setup incurs some overheads due to fetching resources from disk cache (~2ms per resource). Due to those reasons, the numbers here are not entirely accurate, but they should be sufficient to observe that Mithril's initialization speed is noticeably better than React.
|
||||
|
||||
Here's a slightly more meaningful benchmark: measuring the scripting time for creating 10,000 divs (and 10,000 text nodes). Again, here's the benchmark code for [React](https://jsfiddle.net/bfoeay4f/) and [Mithril](https://jsfiddle.net/fft0ht7n/). Their best results are shown below:
|
||||
|
||||
React v15.4.1 | Mithril 1.0
|
||||
------------- | -------
|
||||
99.7 ms | 42.8 ms
|
||||
React | Mithril
|
||||
------- | -------
|
||||
99.7 ms | 42.8 ms
|
||||
|
||||
What these numbers show is that not only does Mithril initializes significantly faster, it can process upwards of 20,000 virtual DOM nodes before React is ready to use.
|
||||
|
||||
|
|
@ -72,37 +74,49 @@ What these numbers show is that not only does Mithril initializes significantly
|
|||
|
||||
Update performance can be even more important than first-render performance, since updates can happen many times while a Single Page Application is running.
|
||||
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Sample results are shown below:
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below:
|
||||
|
||||
React v15.4.1 | Mithril 1.0
|
||||
------------- | -------
|
||||
12.1 ms | 6.4 ms
|
||||
React | Mithril
|
||||
------- | -------
|
||||
12.1 ms | 6.4 ms
|
||||
|
||||
##### Development performance
|
||||
|
||||
Another thing to keep in mind is that because React adds extra checks and helpful error messages in development mode, it is slower in development than the production version used for the benchmarks above. To illustrate, [here's the 10,000 node benchmark from above using the development version of React](https://jsfiddle.net/r1jfckrd/).
|
||||
|
||||
### Complexity
|
||||
##### Drop-in replacements
|
||||
|
||||
There are [several](https://preactjs.com/) [projects](https://github.com/Lucifier129/react-lite) [that](https://infernojs.org/) [claim](https://github.com/alibaba/rax) API parity with React (some via compatibility layer libraries), but they are not fully compatible (e.g. PropType support is usually stubbed out, synthetic events are sometimes not supported, and some APIs have different semantics). Note that these libraries typically also include features of their own that are not part of the official React API, which may become problematic down the road if one decides to switch back to React Fiber.
|
||||
|
||||
Claims about small download size (compared to React) are accurate, but most of these libraries are slightly larger than Mithril's renderer module. Preact is the only exception.
|
||||
|
||||
Be wary of aggressive performance claims, as benchmarks used by some of these projects are known to be out-of-date and flawed (in the sense that they can be - and are - exploited). Boris Kaul (author of some of the benchmarks) has [written in detail about how benchmarks are gamed](https://medium.com/@localvoid/how-to-win-in-web-framework-benchmarks-8bc31af76ce7). Another thing to keep in mind is that some benchmarks aggressively use advanced optimization features and thus demonstrate *potential* performance, i.e. performance that is possible given some caveats, but realistically unlikely unless you actively spend the time to go over your entire codebase identifying optimization candidates and evaluating the regression risks brought by the optimization caveats.
|
||||
|
||||
In the spirit of demonstrating *typical* performance characteristics, the benchmarks presented in this comparison page are implemented in an apples-to-apples, naive, idiomatic way (i.e. the way you would normally write 99% of your code) and do not employ tricks or advanced optimizations to make one or other framework look artificially better. You are encouraged to contribute a PR if you feel any DbMonster implementation here could be written more idiomatically.
|
||||
|
||||
#### Complexity
|
||||
|
||||
Both React and Mithril have relatively small API surfaces compared to other frameworks, which help ease learning curve. However, whereas idiomatic Mithril can be written without loss of readability using plain ES5 and no other dependencies, idiomatic React relies heavily on complex tooling (e.g. Babel, JSX plugin, etc), and this level of complexity frequently extends to popular parts of its ecosystem, be it in the form of syntax extensions (e.g. non-standard object spread syntax in Redux), architectures (e.g. ones using immutable data libraries), or bells and whistles (e.g. hot module reloading).
|
||||
|
||||
### Learning curve
|
||||
While complex toolchains are also possible with Mithril and other frameworks alike, it's *strongly* recommended that you follow the [KISS](https://en.wikipedia.org/wiki/KISS_principle) and [YAGNI](https://en.wikipedia.org/wiki/You_aren't_gonna_need_it) principles when using Mithril.
|
||||
|
||||
#### Learning curve
|
||||
|
||||
Both React and Mithril have relatively small learning curves. React's learning curve mostly involves understanding components and their lifecycle. The learning curve for Mithril components is nearly identical. There are obviously more APIs to learn in Mithril, since Mithril also includes routing and XHR, but the learning curve would be fairly similar to learning React, React Router and a XHR library like superagent or axios.
|
||||
|
||||
Idiomatic React requires working knowledge of JSX and its caveats, and therefore there's also a small learning curve related to Babel.
|
||||
|
||||
### Documentation
|
||||
#### Documentation
|
||||
|
||||
React documentation is clear and well written, and includes a good API reference, tutorials for getting started, as well as pages covering various advanced concepts.
|
||||
React documentation is clear and well written, and includes a good API reference, tutorials for getting started, as well as pages covering various advanced concepts. Unfortunately, since React is limited to being only a view library, its documentation does not explore how to use React idiomatically in the context of a real-life application. As a result, there are many popular state management libraries and thus architectures using React can differ drastically from company to company (or even between projects).
|
||||
|
||||
Mithril documentation also includes [introductory](introduction.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
|
||||
Mithril documentation also includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
|
||||
|
||||
Unfortunately, since React is limited to being only a view library, its documentation does not explore how to use React in the context of a real-life application. As a result, there are many popular state management libraries and as a result, architectures using React can differ drastically from company to company (or even between projects).
|
||||
Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
|
||||
|
||||
---
|
||||
|
||||
## Angular
|
||||
### Angular
|
||||
|
||||
Angular is a web application framework maintained by Google.
|
||||
|
||||
|
|
@ -113,7 +127,7 @@ Angular and Mithril are fairly different, but they share a few similarities:
|
|||
|
||||
The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain Javascript, and flow control is done with Javascript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate Javascript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in Javascript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates Javascript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions).
|
||||
|
||||
### Performance
|
||||
#### Performance
|
||||
|
||||
Angular has made a lot of progress in terms of performance over the years. Angular 1 used a mechanism known as dirty checking which tended to get slow due to the need to constantly diff large `$scope` structures. Angular 2 uses a template change detection mechanism that is much more performant. However, even despite Angular's improvements, Mithril is often faster than Angular, due to the ease of auditing that Mithril's small codebase size affords.
|
||||
|
||||
|
|
@ -125,33 +139,36 @@ Also, remember that frameworks like Angular and Mithril are designed for non-tri
|
|||
|
||||
##### Update performance
|
||||
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below:
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below:
|
||||
|
||||
Angular | Mithril
|
||||
------- | -------
|
||||
11.5 ms | 6.4 ms
|
||||
|
||||
### Complexity
|
||||
#### Complexity
|
||||
|
||||
Angular is superior to Mithril in the amount of tools it offers (in the form of various directives and services), but it is also far more complex. Compare [Angular's API surface](https://angular.io/docs/ts/latest/api/) with [Mithril's](api.md). You can make your own judgment on which API is more self-descriptive and more relevant to your needs.
|
||||
|
||||
Angular 2 has a lot more concepts to understand: on the language level, Typescript is the recommended language, and on top of that there's also Angular-specific template syntax such as bindings, pipes, "safe navigator operator". You also need to learn about architectural concepts such as modules, components, services, directives, etc, and where it's appropriate to use what.
|
||||
|
||||
### Learning curve
|
||||
#### Learning curve
|
||||
|
||||
If we compare apples to apples, Angular 2 and Mithril have similar learning curves: in both, components are a central aspect of architecture, and both have reasonable routing and XHR tools.
|
||||
|
||||
With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what Javascript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about.
|
||||
|
||||
### Documentation
|
||||
#### Documentation
|
||||
|
||||
Angular 2 documentation provides an extensive introductory tutorial, and another tutorial that implements an application. It also has various guides for advanced concepts, a cheatsheet and a style guide. Unfortunately, at the moment, the API reference leaves much to be desired. Several APIs are either undocumented or provide no context for what the API might be used for.
|
||||
|
||||
Mithril documentation includes [introductory](introduction.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
|
||||
Mithril documentation includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference.
|
||||
|
||||
Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Vue
|
||||
### Vue
|
||||
|
||||
Vue is a view library similar to Angular.
|
||||
|
||||
|
|
@ -160,11 +177,11 @@ Vue and Mithril have a lot of differences but they also share some similarities:
|
|||
- They both use virtual DOM and lifecycle methods
|
||||
- Both organize views via components
|
||||
|
||||
Vue also provides tools for routing and state management as separate modules. Vue looks very similar to Angular and provides a similar directive system, HTML-based templates and logic flow directives. It differs from Angular in that it implements a monkeypatching reactive system that overwrites native methods in a component's data (whereas Angular 1 uses dirty checking and digest/apply cycles to achieve similar results). Similar to Angular 2, Vue compiles HTML templates into functions, but the compiled functions look more like Mithril or React views, rather than Angular's compiled rendering functions.
|
||||
Vue 2 uses a fork of Snabbdom as its virtual DOM system. In addition, Vue also provides tools for routing and state management as separate modules. Vue looks very similar to Angular and provides a similar directive system, HTML-based templates and logic flow directives. It differs from Angular in that it implements a monkeypatching reactive system that overwrites native methods in a component's data tree (whereas Angular 1 uses dirty checking and digest/apply cycles to achieve similar results). Similar to Angular 2, Vue compiles HTML templates into functions, but the compiled functions look more like Mithril or React views, rather than Angular's compiled rendering functions.
|
||||
|
||||
Vue is significantly smaller than Angular when comparing apples to apples, but not as small as Mithril (Vue core is around 23kb gzipped, whereas the equivalent rendering module in Mithril is around 4kb gzipped). Both have similar performance characteristics, but benchmarks often suggest Mithril is slightly faster.
|
||||
Vue is significantly smaller than Angular when comparing apples to apples, but not as small as Mithril (Vue core is around 23kb gzipped, whereas the equivalent rendering module in Mithril is around 4kb gzipped). Both have similar performance characteristics, but benchmarks usually suggest Mithril is slightly faster.
|
||||
|
||||
### Performance
|
||||
#### Performance
|
||||
|
||||
Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop:
|
||||
|
||||
|
|
@ -176,24 +193,24 @@ Library load times matter in applications that don't stay open for long periods
|
|||
|
||||
##### Update performance
|
||||
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below:
|
||||
A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below:
|
||||
|
||||
Vue | Mithril
|
||||
------ | -------
|
||||
9.8 ms | 6.4 ms
|
||||
|
||||
### Complexity
|
||||
#### Complexity
|
||||
|
||||
Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, `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, and unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects.
|
||||
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
|
||||
#### Documentation
|
||||
|
||||
Both Vue and Mithril have good documentation. Both include a good API reference with examples, tutorials for getting started, as well as pages covering various advanced concepts.
|
||||
|
||||
However, due to Vue's many-ways-to-do-one-thing approach, some things are not adequately documented. For example, there's no documentation on hyperscript syntax or usage.
|
||||
However, due to Vue's many-ways-to-do-one-thing approach, some things may not be adequately documented. For example, there's no documentation on hyperscript syntax or usage.
|
||||
|
||||
Mithril documentation typically errs on the side of being overly thorough if a topic involves things outside of the scope of Mithril. For example, when a topic involves a 3rd party library, Mithril documentation walks through the installation process for the 3rd party library.
|
||||
Mithril documentation typically errs on the side of being overly thorough if a topic involves things outside of the scope of Mithril. For example, when a topic involves a 3rd party library, Mithril documentation walks through the installation process for the 3rd party library. Mithril documentation also often demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries.
|
||||
|
||||
Mithril's tutorials also cover a lot more ground than Vue's: the [Vue tutorial](https://vuejs.org/v2/guide/#Getting-Started) finishes with a static list of foodstuff. [Mithril's 10 minute guide](introduction.md) covers the majority of its API and goes over key aspects of real-life applications, such as fetching data from a server and routing (and there's a [longer, more thorough tutorial](simple-application.md) if that's not enough).
|
||||
Mithril's tutorials also cover a lot more ground than Vue's: the [Vue tutorial](https://vuejs.org/v2/guide/#Getting-Started) finishes with a static list of foodstuff. [Mithril's 10 minute guide](index.md) covers the majority of its API and goes over key aspects of real-life applications, such as fetching data from a server and routing (and there's a [longer, more thorough tutorial](simple-application.md) if that's not enough).
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
var marked = require("marked")
|
||||
var layout = fs.readFileSync("./docs/layout.html", "utf-8")
|
||||
var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version
|
||||
try {fs.mkdirSync("docs/archive/")} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version)} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version + "/lib")} catch (e) {}
|
||||
try {fs.mkdirSync("docs/archive/" + version + "/lib/prism")} catch (e) {}
|
||||
try {fs.mkdirSync("./dist")} catch (e) {/* ignore */}
|
||||
try {fs.mkdirSync("./dist/archive")} catch (e) {/* ignore */}
|
||||
try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */}
|
||||
|
||||
var guides = fs.readFileSync("docs/guides.md", "utf-8")
|
||||
var methods = fs.readFileSync("docs/methods.md", "utf-8")
|
||||
var guides = fs.readFileSync("docs/nav-guides.md", "utf-8")
|
||||
var methods = fs.readFileSync("docs/nav-methods.md", "utf-8")
|
||||
|
||||
var index = fs.readFileSync("docs/index.md", "utf-8")
|
||||
fs.writeFileSync("README.md", index.replace(/(\]\()(.+?)\.md(\))/g, "$1http://mithril.js.org/$2.html$3"), "utf-8")
|
||||
|
||||
generate("docs")
|
||||
|
||||
|
|
@ -19,16 +23,16 @@ function generate(pathname) {
|
|||
generate(pathname + "/" + filename)
|
||||
})
|
||||
}
|
||||
else if (!pathname.match(/tutorials|archive/)) {
|
||||
else if (!pathname.match(/tutorials|archive|nav-/)) {
|
||||
if (pathname.match(/\.md$/)) {
|
||||
var outputFilename = pathname.replace(/\.md$/, ".html")
|
||||
var markdown = fs.readFileSync(pathname, "utf-8")
|
||||
var anchors = {}
|
||||
var fixed = markdown
|
||||
.replace(/(`[^`]+?)<(.*`)/gim, "$1<$2") // fix generic syntax
|
||||
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
|
||||
return "<code>" + (a + b + c).replace(/\|/g, "|") + "</code>"
|
||||
})
|
||||
.replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav, space) { // inject menu
|
||||
.replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav) { // inject menu
|
||||
var file = path.basename(pathname)
|
||||
var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))")
|
||||
var replace = function(match, space, li, link) {
|
||||
|
|
@ -40,17 +44,31 @@ function generate(pathname) {
|
|||
.replace(/(\]\([^\)]+)(\.md)/gim, function(match, path, extension) {
|
||||
return path + (path.match(/http/) ? extension : ".html")
|
||||
}) // fix links
|
||||
var markedHtml = marked(fixed)
|
||||
.replace(/(\W)Array<([^/<]+?)>/gim, "$1Array<$2>") // Fix type signatures containing Array<...>
|
||||
var title = fixed.match(/^#([^\n\r]+)/i) || []
|
||||
var html = layout
|
||||
.replace(/\[version\]/, version) // update version
|
||||
.replace(/\[body\]/, marked(fixed))
|
||||
.replace(/<h5 id="([^"]+?)">([^<]+?)<\/h5>/gim, function(match, id, text) { // fix anchors
|
||||
return "<h5 id=\"" + text.toLowerCase().replace(/\.|\[|\]|"|\//g, "") + "\">" + text + "</h5>"
|
||||
.replace(/<title>Mithril\.js<\/title>/, "<title>" + title[1] + " - Mithril.js</title>")
|
||||
.replace(/\[version\]/g, version) // update version
|
||||
.replace(/\[body\]/, markedHtml)
|
||||
.replace(/<h(.) id="([^"]+?)">(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors
|
||||
var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/<a.*?>.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-");
|
||||
|
||||
if(anchor in anchors) {
|
||||
anchor += ++anchors[anchor]
|
||||
} else {
|
||||
anchors[anchor] = 0;
|
||||
}
|
||||
|
||||
return `<h${n} id="${anchor}"><a href="#${anchor}">${text}</a></h${n}>`;
|
||||
})
|
||||
fs.writeFileSync("docs/archive/" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
}
|
||||
else {
|
||||
fs.writeFileSync("docs/archive/" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
|
||||
else if (!pathname.match(/lint|generate/)) {
|
||||
var encoding = (/\.(ico|png)$/i).test(path.extname(pathname)) ? "binary" : "utf-8";
|
||||
fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
|
||||
fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ Mithril supports both strings and objects as valid `style` values. In other word
|
|||
```javascript
|
||||
m("div", {style: "background:red;"})
|
||||
m("div", {style: {background: "red"}})
|
||||
m("div[style=background:red")
|
||||
m("div[style=background:red]")
|
||||
```
|
||||
|
||||
Using a string as a `style` would overwrite all inline styles in the element if it is redrawn, and not only CSS rules whose values have changed.
|
||||
|
|
@ -181,7 +181,7 @@ Mithril does not attempt to add units to number values.
|
|||
|
||||
### Events
|
||||
|
||||
Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on<event>` property, such as `touchstart`
|
||||
Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on${event}` property, such as `touchstart`
|
||||
|
||||
```javascript
|
||||
function doSomething(e) {
|
||||
|
|
|
|||
|
|
@ -12,36 +12,71 @@
|
|||
|
||||
### What is Mithril?
|
||||
|
||||
Mithril is a client-side Javascript framework for building Single Page Applications.
|
||||
Mithril is a modern client-side Javascript framework for building Single Page Applications.
|
||||
It's small (< 8kb gzip), fast and provides routing and XHR utilities out of the box.
|
||||
|
||||
<div style="display:flex;margin:0 0 30px;">
|
||||
<div style="width:50%;">
|
||||
<h5>Download size</h5>
|
||||
<small>Mithril (8kb)</small>
|
||||
<div style="animation:grow 0.08s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:4%;"></div>
|
||||
<small style="color:#aaa;">Vue + Vue-Router + Vuex + fetch (40kb)</small>
|
||||
<div style="animation:grow 0.4s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:20%"></div>
|
||||
<small style="color:#aaa;">React + React-Router + Redux + fetch (64kb)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:32%"></div>
|
||||
<small style="color:#aaa;">Angular (135kb)</small>
|
||||
<div style="animation:grow 1.35s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:68%"></div>
|
||||
</div>
|
||||
<div style="width:50%;">
|
||||
<h5>Performance</h5>
|
||||
<small>Mithril (6.4ms)</small>
|
||||
<div style="animation:grow 0.64s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:24%;"></div>
|
||||
<small style="color:#aaa;">Vue (9.8ms)</small>
|
||||
<div style="animation:grow 0.98s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:40%"></div>
|
||||
<small style="color:#aaa;">React (12.1ms)</small>
|
||||
<div style="animation:grow 1.21s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:48%"></div>
|
||||
<small style="color:#aaa;">Angular (11.5ms)</small>
|
||||
<div style="animation:grow 1.15s;background:#1e5799;height:3px;margin:0 10px 10px 0;transform-origin:0;width:44%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess.
|
||||
|
||||
If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](framework-comparison.md) page.
|
||||
|
||||
---
|
||||
Mithril supports browsers all the way back to IE9, no polyfills required.
|
||||
|
||||
Note: This introduction assumes you have basic level of Javacript knowledge. If you don't, there are many great resources to learn. [Speaking Javascript](http://speakingjs.com/es5/index.html) is a good e-book for absolute beginners. If you're already familiar with other programming languages, the [Eloquent Javascript](http://eloquentjavascript.net/) e-book might be more suitable for you. [Codecademy](https://www.codecademy.com/learn/javascript) is another good resource that emphasizes learning via interactivity.
|
||||
---
|
||||
|
||||
### Getting started
|
||||
|
||||
The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
|
||||
An easy way to try out Mithril is to include it from a CDN and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes.
|
||||
|
||||
Let's create an HTML file to follow along:
|
||||
|
||||
```markup
|
||||
<body></body>
|
||||
<script src="http://cdn.rawgit.com/lhorie/mithril.js/rewrite/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
<body>
|
||||
<script src="//unpkg.com/mithril/mithril.js"></script>
|
||||
<script>
|
||||
var root = document.body
|
||||
|
||||
// your code goes here!
|
||||
</script>
|
||||
// your code goes here!
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
To make things simpler you can fork this pen which already has the latest version of mithril loaded.
|
||||
|
||||
<p data-height="265" data-theme-id="light" data-slug-hash="XRrXVR" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Scaffold" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/XRrXVR/">Mithril Scaffold</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
Mithril is also loaded onto this page already, so you can start poking at the `m` object in the developer console right away if you'd like!
|
||||
|
||||
---
|
||||
|
||||
### Hello world
|
||||
|
||||
Let's start as small as well can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
|
||||
Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better)
|
||||
|
||||
```javascript
|
||||
var root = document.body
|
||||
|
|
@ -57,6 +92,11 @@ m.render(root, "My first app")
|
|||
|
||||
As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch.
|
||||
|
||||
#### Live Example
|
||||
|
||||
<p data-height="265" data-theme-id="light" data-slug-hash="KmPdOO" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Hello World" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/KmPdOO/">Mithril Hello World</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
||||
### DOM elements
|
||||
|
|
@ -67,7 +107,7 @@ Let's wrap our text in an `<h1>` tag.
|
|||
m.render(root, m("h1", "My first app"))
|
||||
```
|
||||
|
||||
The `m()` function can be used to describe any HTML structure you want. So if you to add a class to the `<h1>`:
|
||||
The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `<h1>`:
|
||||
|
||||
```javascript
|
||||
m("h1", {class: "title"}, "My first app")
|
||||
|
|
@ -91,9 +131,14 @@ m("main", [
|
|||
])
|
||||
```
|
||||
|
||||
#### Live Example
|
||||
|
||||
<p data-height="275" data-theme-id="light" data-slug-hash="gWYade" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Simple Mithril Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/gWYade/">Simple Mithril Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
Note: If you prefer `<html>` syntax, [it's possible to use it via a Babel plugin](jsx.md).
|
||||
|
||||
```markup
|
||||
```jsx
|
||||
// HTML syntax via Babel's JSX plugin
|
||||
<main>
|
||||
<h1 class="title">My first app</h1>
|
||||
|
|
@ -142,7 +187,8 @@ var Hello = {
|
|||
view: function() {
|
||||
return m("main", [
|
||||
m("h1", {class: "title"}, "My first app"),
|
||||
m("button", {onclick: function() {count++}}, count + " clicks"), // changed this line
|
||||
// changed the next line
|
||||
m("button", {onclick: function() {count++}}, count + " clicks"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +202,11 @@ You can now update the label of the button by clicking the button. Since we used
|
|||
|
||||
If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates.
|
||||
|
||||
#### Live Example
|
||||
|
||||
<p data-height="300" data-theme-id="light" data-slug-hash="rmBOQV" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Component Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/rmBOQV/">Mithril Component Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
||||
### Routing
|
||||
|
|
@ -189,6 +240,11 @@ The `"/splash"` right after `root` means that's the default route, i.e. if the h
|
|||
|
||||
Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button.
|
||||
|
||||
#### Live Example
|
||||
|
||||
<p data-height="300" data-theme-id="light" data-slug-hash="qmWOvr" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril Routing Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/qmWOvr/">Mithril Routing Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
||||
### XHR
|
||||
|
|
@ -197,16 +253,16 @@ Basically, XHR is just a way to talk to a server.
|
|||
|
||||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial.
|
||||
|
||||
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `useCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
|
||||
```javascript
|
||||
var count = 0
|
||||
var increment = function() {
|
||||
m.request({
|
||||
method: "PUT",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/tutorial/1",
|
||||
url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
|
||||
data: {count: count + 1},
|
||||
useCredentials: true,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(data) {
|
||||
count = parseInt(data.count)
|
||||
|
|
@ -231,6 +287,11 @@ var Hello = {
|
|||
|
||||
Clicking the button should now update the count.
|
||||
|
||||
#### Live Example
|
||||
|
||||
<p data-height="265" data-theme-id="light" data-slug-hash="WjeQBW" data-default-tab="js,result" data-user="tivac" data-embed-version="2" data-pen-title="Mithril XHR Example" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/tivac/pen/WjeQBW/">Mithril XHR Example</a> by Pat Cavit (<a href="http://codepen.io/tivac">@tivac</a>) on <a href="http://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
||||
We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR.
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
If you're new to Javascript or just want a very simple setup to get your feet wet, you can get Mithril from a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network):
|
||||
|
||||
```markup
|
||||
<script src="http://cdn.rawgit.com/lhorie/mithril.js/rewrite/mithril.js"></script>
|
||||
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -19,7 +19,7 @@ If you're new to Javascript or just want a very simple setup to get your feet we
|
|||
|
||||
```bash
|
||||
# 1) install
|
||||
npm install mithril@rewrite --save
|
||||
npm install mithril --save
|
||||
|
||||
npm install webpack --save
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ npm init --yes
|
|||
Then, to install Mithril, run:
|
||||
|
||||
```bash
|
||||
npm install mithril@rewrite --save
|
||||
npm install mithril --save
|
||||
```
|
||||
|
||||
This will create a folder called `node_modules`, and a `mithril` folder inside of it. It will also add an entry under `dependencies` in the `package.json` file
|
||||
|
|
@ -178,7 +178,7 @@ Live reload is a feature where code changes automatically trigger the page to re
|
|||
|
||||
```bash
|
||||
# 1) install
|
||||
npm install mithril@rewrite --save
|
||||
npm install mithril --save
|
||||
npm install budo -g
|
||||
|
||||
# 2) add this line into the scripts section in package.json
|
||||
|
|
@ -219,7 +219,7 @@ If you don't have the ability to run a bundler script due to company security po
|
|||
<title>Hello world</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="http://cdn.rawgit.com/lhorie/mithril.js/rewrite/mithril.js"></script>
|
||||
<script src="https://cdn.rawgit.com/MithrilJS/mithril.js/master/mithril.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ Argument | Type | Required | Descript
|
|||
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
|
||||
`options.callbackName` | `String` | No | The name of the function that will be called as the callback. Defaults to a randomized string (e.g. `_mithril_6888197422121285_0({a: 1})`
|
||||
`options.callbackKey` | `String` | No | The name of the querystring parameter name that specifies the callback name. Defaults to `callback` (e.g. `/someapi?callback=_mithril_6888197422121285_0`)
|
||||
`options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`.
|
||||
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through `type` method
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
|
|
|||
16
docs/jsx.md
16
docs/jsx.md
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
### Description
|
||||
|
||||
JSX is a syntax extension that enables you to write HTML tags interspersed with Javascript.
|
||||
JSX is a syntax extension that enables you to write HTML tags interspersed with Javascript. It's not part of any Javascript standards and it's not required for building applications, but it may be more pleasing to use depending on your team's preferences.
|
||||
|
||||
```jsx
|
||||
var MyComponent = {
|
||||
|
|
@ -39,14 +39,14 @@ When using JSX, it's possible to interpolate Javascript expressions within JSX t
|
|||
var greeting = "Hello"
|
||||
var url = "http://google.com"
|
||||
var link = <a href={url}>{greeting + "!"}</a>
|
||||
// yields <a href="http://google.com">Hello</a>
|
||||
// yields <a href="http://google.com">Hello!</a>
|
||||
```
|
||||
|
||||
Components can be used by using a convention of uppercasing the first letter of the component name:
|
||||
|
||||
```jsx
|
||||
m.mount(document.body, <MyComponent />)
|
||||
// equivalent to m.mount(document.body, m(MyComponent))
|
||||
m.render(document.body, <MyComponent />)
|
||||
// equivalent to m.render(document.body, m(MyComponent))
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -112,10 +112,12 @@ Create a `.babelrc` file:
|
|||
Next, create a file called `webpack.config.js`
|
||||
|
||||
```javascript
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: './bin',
|
||||
path: path.resolve(__dirname, './bin'),
|
||||
filename: 'app.js',
|
||||
},
|
||||
module: {
|
||||
|
|
@ -186,8 +188,8 @@ JSX is useful for teams where HTML is primarily written by someone without Javas
|
|||
|
||||
Hyperscript is the compiled representation of JSX. It's designed to be readable and can also be used as-is, instead of JSX (as is done in most of the documentation). Hyperscript tends to be terser than JSX for a couple of reasons:
|
||||
|
||||
1 - it does not require repeating the tag name in closing tags (e.g. `m("div")` vs `<div></div>`)
|
||||
2 - static attributes can be written using CSS selector syntax (i.e. `m("a.button")` vs `<div class="button"></div>`
|
||||
- it does not require repeating the tag name in closing tags (e.g. `m("div")` vs `<div></div>`)
|
||||
- static attributes can be written using CSS selector syntax (i.e. `m("a.button")` vs `<div class="button"></div>`
|
||||
|
||||
In addition, since hyperscript is plain Javascript, it's often more natural to indent than JSX:
|
||||
|
||||
|
|
|
|||
68
docs/keys.md
68
docs/keys.md
|
|
@ -3,7 +3,6 @@
|
|||
- [What are keys](#what-are-keys)
|
||||
- [How to use](#how-to-use)
|
||||
- [Debugging key related issues](#debugging-key-related-issues)
|
||||
- [Avoid anti-patterns](#avoid-anti-patterns)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -97,19 +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, {id: 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
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -150,3 +151,56 @@ var things = [
|
|||
]
|
||||
```
|
||||
|
||||
#### Avoid mixing keyed and non-keyed vnodes in the same array
|
||||
|
||||
An array of vnodes must have only keyed vnodes or non-keyed vnodes, but not both. If you need to mix them, create a nested array.
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
m("div", [
|
||||
m("div", "a"),
|
||||
m("div", {key: 1}, "b"),
|
||||
])
|
||||
|
||||
// PREFER
|
||||
m("div", [
|
||||
m("div", {key: 0}, "a"),
|
||||
m("div", {key: 1}, "b"),
|
||||
])
|
||||
|
||||
|
||||
// PREFER
|
||||
m("div", [
|
||||
m("div", "a"),
|
||||
[
|
||||
m("div", {key: 1}, "b"),
|
||||
]
|
||||
])
|
||||
```
|
||||
|
||||
#### Avoid passing model data directly to components if the model uses `key` as a data property
|
||||
|
||||
The `key` property may appear in your data model in a way that conflicts with Mithril's key logic. For example, a component may represent an entity whose `key` property is expected to change over time. This can lead to components receiving the wrong data, re-initialise, or change positions unexpectedly. If your data model uses the `key` property, make sure to wrap the data such that Mithril doesn't misinterpret it as a rendering instruction:
|
||||
|
||||
```javascript
|
||||
// Data model
|
||||
var users = [
|
||||
{id: 1, name: "John", key: 'a'},
|
||||
{id: 2, name: "Mary", key: 'b'},
|
||||
]
|
||||
|
||||
// Later on...
|
||||
users[0].key = 'c'
|
||||
|
||||
// AVOID
|
||||
users.map(function(user){
|
||||
// The component for John will be destroyed and recreated
|
||||
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})
|
||||
})
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Mithril.js</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css' />
|
||||
<link href="lib/prism/prism.css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" />
|
||||
<link href="style.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<section>
|
||||
<h1>Mithril <small>[version]</small></h1>
|
||||
<a class="hamburger" href="javascript:;">≡</a>
|
||||
<h1><img src="logo.svg"> Mithril <small>[version]</small></h1>
|
||||
<nav>
|
||||
<a href="introduction.html">Guide</a>
|
||||
<a href="index.html">Guide</a>
|
||||
<a href="api.html">API</a>
|
||||
<a href="https://gitter.im/lhorie/mithril.js">Chat</a>
|
||||
<a href="https://github.com/lhorie/mithril.js">Github</a>
|
||||
<a href="https://gitter.im/MithrilJS/mithril.js">Chat</a>
|
||||
<a href="https://github.com/MithrilJS/mithril.js">Github</a>
|
||||
</nav>
|
||||
</section>
|
||||
</header>
|
||||
|
|
@ -25,6 +27,16 @@
|
|||
<small>License: MIT. © Leo Horie.</small>
|
||||
</section>
|
||||
</main>
|
||||
<script src="lib/prism/prism.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js" defer></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-jsx.min.js" defer></script>
|
||||
<script src="https://unpkg.com/mithril@[version]/mithril.js" async></script>
|
||||
<script>
|
||||
document.querySelector(".hamburger").onclick = function() {
|
||||
document.body.className = document.body.className === "navigating" ? "" : "navigating"
|
||||
document.querySelector("h1 + ul").onclick = function() {
|
||||
document.body.className = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
.token.comment,.token.prolog,.token.doctype,.token.cdata {color:#888;}
|
||||
.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol {color:#905;}
|
||||
.token.selector,.token.attr-name,.token.string,.token.builtin {color:#690;}
|
||||
.token.atrule,.token.attr-value,.token.punctuation,.token.keyword {color:#1e5799;}
|
||||
.token.regex,.token.important {color:#e90;}
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* Prism: Lightweight, robust, elegant syntax highlighting
|
||||
* MIT license http://www.opensource.org/licenses/mit-license.php/
|
||||
* @author Lea Verou http://lea.verou.me
|
||||
*/(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ");var l={element:r,language:o,grammar:u,code:f};t.hooks.run("before-highlight",l);if(i&&self.Worker){var c=new Worker(t.filename);c.onmessage=function(e){l.highlightedCode=n.stringify(JSON.parse(e.data),o);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(l.element);t.hooks.run("after-highlight",l)};c.postMessage(JSON.stringify({language:l.language,code:l.code}))}else{l.highlightedCode=t.highlight(l.code,l.grammar,l.language);t.hooks.run("before-insert",l);l.element.innerHTML=l.highlightedCode;s&&s.call(r);t.hooks.run("after-highlight",l)}},highlight:function(e,r,i){return n.stringify(t.tokenize(e,r),i)},tokenize:function(e,n,r){var i=t.Token,s=[e],o=n.rest;if(o){for(var u in o)n[u]=o[u];delete n.rest}e:for(var u in n){if(!n.hasOwnProperty(u)||!n[u])continue;var a=n[u],f=a.inside,l=!!a.lookbehind,c=0;a=a.pattern||a;for(var h=0;h<s.length;h++){var p=s[h];if(s.length>e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+"</"+s.tag+">"};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();;
|
||||
Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});;
|
||||
Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g};
|
||||
;
|
||||
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});;
|
||||
|
|
@ -65,7 +65,7 @@ var ComponentWithState = {
|
|||
this.data = vnode.attrs.data
|
||||
},
|
||||
view: function() {
|
||||
return m("div", this.data)
|
||||
return m("div", this.data) // displays data from initialization time
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ var Fader = {
|
|||
|
||||
### onremove
|
||||
|
||||
The `onremove(vnode)` hook is called before a DOM element is removed from the document. If a `onbeforeremove` hook is also defined, the `onremove` hook runs after the `done` callback is called.
|
||||
The `onremove(vnode)` hook is called before a DOM element is removed from the document. If a `onbeforeremove` hook is also defined, the `onremove` hook runs after the promise returned from `onbeforeremove` is completed.
|
||||
|
||||
This hook is called on any element that is removed from the document, regardless of whether it was directly detached from its parent or whether it is a child of another element that was detached.
|
||||
|
||||
|
|
@ -194,64 +194,6 @@ This hook is useful to reduce lag in updates in cases where there is a overly la
|
|||
|
||||
Although Mithril is flexible, some code patterns are discouraged:
|
||||
|
||||
#### Do not redraw synchronously from lifecycle hooks
|
||||
|
||||
The [`m.render`](render.md) method modifies DOM state, and therefore it's [non-reentrant](https://en.wikipedia.org/wiki/Reentrancy_(computing)). All lifecyle methods are called by `m.render()`, and therefore you cannot call `m.render`, `m.mount`, `m.route` or `m.redraw` from a lifecycle method. Redrawing synchronously from a lifecycle method will result in **undefined behavior**.
|
||||
|
||||
Typically, redrawing from an `oninit` or `onbeforeupdate` hook is meaningless since the element in question renders shortly after them anyways. If redrawing is required from any other hooks, you should consider moving code up the execution path; for example, refactor it so that the application code runs on an event handler, before its natural redraw occurs.
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
var greeting = ""
|
||||
|
||||
var BrokenComponent = {
|
||||
onupdate: function() {
|
||||
this.greeting = greeting()
|
||||
m.redraw()
|
||||
},
|
||||
view: function() {
|
||||
return m("div[title=Hello]", {onclick: m.withAttr("title", function(v) {greeting = v})}, this.greeting)
|
||||
}
|
||||
}
|
||||
|
||||
// PREFER
|
||||
var greeting = ""
|
||||
|
||||
var WorkingComponent = {
|
||||
view: function() {
|
||||
return m("div[title=Hello]", {onclick: m.withAttr("title", function(v) {greeting = v})}, greeting)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On rare occasions, there may not be a way to refactor a redraw out of a lifecycle method due to dependencies on layout values (e.g. scrollbar position, an element's updated offsetHeight, etc). In those cases, you should redraw asynchronously, by wrapping the redraw call in a `requestAnimationFrame`, `setTimeout` or similar function.
|
||||
|
||||
```javascript
|
||||
// AVOID
|
||||
var BrokenComponent = {
|
||||
onupdate: function(vnode) {
|
||||
var oldWidth = this.width
|
||||
this.width = vnode.dom.offsetWidth
|
||||
if (oldWidth !== this.width) m.redraw()
|
||||
},
|
||||
view: function() {
|
||||
return m("div", {onclick: function() {console.log("calculating width")}}, "Width is: " + this.width)
|
||||
}
|
||||
}
|
||||
|
||||
// PREFER
|
||||
var WorkingComponent = {
|
||||
onupdate: function(vnode) {
|
||||
var oldWidth = this.width
|
||||
this.width = vnode.dom.offsetWidth
|
||||
if (oldWidth !== this.width) requestAnimationFrame(m.redraw)
|
||||
},
|
||||
view: function() {
|
||||
return m("div", {onclick: function() {console.log("calculating width")}}, "Width is: " + this.width)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Avoid premature optimizations
|
||||
|
||||
You should only use `onbeforeupdate` to skip diffing as a last resort. Avoid using it unless you have a noticeable performance issue.
|
||||
|
|
|
|||
38
docs/lint.js
38
docs/lint.js
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
|
|
@ -37,7 +38,7 @@ function ensureCodeIsSyntaticallyValid(file, data) {
|
|||
function ensureCodeIsRunnable(file, data) {
|
||||
var codeBlocks = data.match(/```javascript(.|\n|\r)*?```/gim) || []
|
||||
var code = codeBlocks.map(function(block) {return block.slice(13, -3)}).join(";")
|
||||
|
||||
|
||||
//stubs
|
||||
var silentConsole = {log: function() {}}
|
||||
var fetch = function() {
|
||||
|
|
@ -54,7 +55,7 @@ function ensureCodeIsRunnable(file, data) {
|
|||
if (dep.indexOf("mithril/ospec/ospec") === 0) return global.o
|
||||
if (dep.indexOf("mithril/stream") === 0) return global.stream
|
||||
if (dep === "mithril") return global.m
|
||||
|
||||
|
||||
if (dep === "../model/User") return {
|
||||
list: [],
|
||||
current: {},
|
||||
|
|
@ -74,7 +75,6 @@ function ensureCodeIsRunnable(file, data) {
|
|||
})
|
||||
}
|
||||
catch (e) {console.log(file + " - javascript code cannot run\n\n" + e.stack + "\n\n" + code + "\n\n---\n\n")}
|
||||
|
||||
}
|
||||
|
||||
function ensureCommentStyle(file, data) {
|
||||
|
|
@ -86,10 +86,10 @@ function ensureCommentStyle(file, data) {
|
|||
}
|
||||
|
||||
function ensureLinkIsValid(file, data) {
|
||||
var links = data.match(/\]\(([^\)]+)\)/gim)
|
||||
var links = data.match(/\]\(([^\)]+?)\)/gim) || []
|
||||
links.forEach(function(match) {
|
||||
var link = match.slice(2, -1)
|
||||
var path = link.match(/[\w-]+\.md/)
|
||||
var path = (link.match(/[\w-#]+\.md/) || [])[0]
|
||||
if (link.match(/http/)) {
|
||||
var u = url.parse(link)
|
||||
http.request({method: "HEAD", host: u.host, path: u.pathname, port: 80}).on("error", function() {
|
||||
|
|
@ -101,46 +101,46 @@ function ensureLinkIsValid(file, data) {
|
|||
}
|
||||
|
||||
function initMocks() {
|
||||
global.window = require("../test-utils/browserMock")()
|
||||
global.window = require("../test-utils/browserMock")() // eslint-disable-line global-require
|
||||
global.document = window.document
|
||||
global.m = require("../index")
|
||||
global.o = require("../ospec/ospec")
|
||||
global.stream = require("../stream")
|
||||
global.m = require("../index") // eslint-disable-line global-require
|
||||
global.o = require("../ospec/ospec") // eslint-disable-line global-require
|
||||
global.stream = require("../stream") // eslint-disable-line global-require
|
||||
global.alert = function() {}
|
||||
|
||||
//routes consumed by request.md
|
||||
global.window.$defineRoutes({
|
||||
"GET /api/v1/users": function(request) {
|
||||
"GET /api/v1/users": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{name: ""}])}
|
||||
},
|
||||
"GET /api/v1/users/search": function(request) {
|
||||
"GET /api/v1/users/search": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{id: 1, name: ""}])}
|
||||
},
|
||||
"GET /api/v1/users/1/projects": function(request) {
|
||||
"GET /api/v1/users/1/projects": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{id: 1, name: ""}])}
|
||||
},
|
||||
"GET /api/v1/todos": function(request) {
|
||||
"GET /api/v1/todos": function() {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
"PUT /api/v1/users/1": function(request) {
|
||||
return {status: 200, responseText: request.query.callback ? request.query.callback + "([])" : "[]"}
|
||||
},
|
||||
"POST /api/v1/upload": function(request) {
|
||||
"POST /api/v1/upload": function() {
|
||||
return {status: 200, responseText: JSON.stringify([])}
|
||||
},
|
||||
"GET /files/icon.svg": function(request) {
|
||||
"GET /files/icon.svg": function() {
|
||||
return {status: 200, responseText: "<svg></svg>"}
|
||||
},
|
||||
"GET /files/data.csv": function(request) {
|
||||
"GET /files/data.csv": function() {
|
||||
return {status: 200, responseText: "a,b,c"}
|
||||
},
|
||||
"GET /api/v1/users/123": function(request) {
|
||||
"GET /api/v1/users/123": function() {
|
||||
return {status: 200, responseText: JSON.stringify({id: 123})}
|
||||
},
|
||||
"GET /api/v1/users/foo:bar": function(request) {
|
||||
"GET /api/v1/users/foo:bar": function() {
|
||||
return {status: 200, responseText: JSON.stringify({id: 123})}
|
||||
},
|
||||
"GET /files/image.svg": function(request) {
|
||||
"GET /files/image.svg": function() {
|
||||
return {status: 200, responseText: "<svg></svg>"}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
54
docs/logo.svg
Normal file
54
docs/logo.svg
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 38.044 38.044" style="enable-background:new 0 0 38.044 38.044;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#010002;" d="M31.716,13.5C31.699,6.47,25.974,0.755,18.94,0.755c-7.045,0-12.777,5.732-12.777,12.777
|
||||
c0,0.022,0.004,0.043,0.004,0.065C2.477,15.84,0,19.887,0,24.511c0,7.046,5.731,12.777,12.777,12.777
|
||||
c2.268,0,4.395-0.601,6.244-1.642c1.849,1.041,3.977,1.642,6.245,1.642c7.046,0,12.777-5.732,12.777-12.777
|
||||
C38.043,19.82,35.495,15.722,31.716,13.5z M19.021,32.961c-2.312-1.713-3.906-4.341-4.22-7.352c1.3,0.448,2.689,0.702,4.139,0.702
|
||||
c1.514,0,2.96-0.278,4.307-0.764C22.949,28.584,21.349,31.236,19.021,32.961z M8.517,14.898c1.303-0.579,2.743-0.909,4.26-0.909
|
||||
c1.475,0,2.879,0.307,4.154,0.858c-2.114,1.826-3.629,4.325-4.195,7.167C10.473,20.352,8.898,17.814,8.517,14.898z M18.94,24.055
|
||||
c-1.457,0-2.846-0.298-4.109-0.837c0.361-2.928,1.929-5.482,4.19-7.157c2.243,1.662,3.802,4.187,4.18,7.085
|
||||
C21.897,23.727,20.457,24.055,18.94,24.055z M21.111,14.846c1.275-0.55,2.679-0.858,4.154-0.858c1.457,0,2.846,0.298,4.11,0.837
|
||||
c-0.356,2.885-1.883,5.404-4.089,7.082C24.704,19.108,23.199,16.65,21.111,14.846z M18.94,3.01c5.432,0,9.915,4.137,10.466,9.425
|
||||
c-1.3-0.447-2.689-0.702-4.14-0.702c-2.268,0-4.396,0.601-6.245,1.642c-1.848-1.041-3.975-1.642-6.244-1.642
|
||||
c-1.514,0-2.96,0.278-4.307,0.763C8.993,7.179,13.488,3.01,18.94,3.01z M12.777,35.034c-5.803,0-10.523-4.72-10.523-10.523
|
||||
c0-3.418,1.645-6.451,4.177-8.375c0.744,3.581,2.999,6.607,6.059,8.408c0.011,3.847,1.735,7.293,4.442,9.631
|
||||
C15.656,34.727,14.253,35.034,12.777,35.034z M25.266,35.034c-1.475,0-2.879-0.307-4.154-0.858
|
||||
c2.715-2.345,4.444-5.804,4.444-9.664c0-0.022-0.004-0.044-0.004-0.065c3.007-1.829,5.209-4.852,5.918-8.416
|
||||
c2.613,1.917,4.319,4.999,4.319,8.48C35.788,30.313,31.068,35.034,25.266,35.034z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -27,6 +27,10 @@ var Counter = {
|
|||
m.mount(document.body, Counter)
|
||||
```
|
||||
|
||||
To pass arguments when mounting a component use:
|
||||
```javascript
|
||||
m.mount(element, {view: function () {return m(Component, attrs)}})
|
||||
```
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
@ -69,7 +73,7 @@ In contrast, traversing a javascript data structure has a much more predictable
|
|||
|
||||
### Differences from m.render
|
||||
|
||||
A component rendered via `m.mount` automatically auto-redraws in response to view events, `m.redraw()` calls or `m.request()` calls. Vnodes rendered via `m.render()` do not. Note that calls to `m.prop()` do not trigger auto-redraws.
|
||||
A component rendered via `m.mount` automatically auto-redraws in response to view events, `m.redraw()` calls or `m.request()` calls. Vnodes rendered via `m.render()` do not.
|
||||
|
||||
`m.mount()` is suitable for application developers integrating Mithril widgets into existing codebases where routing is handled by another library or framework, while still enjoying Mithril's auto-redrawing facilities.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
- Tutorials
|
||||
- [Installation](installation.md)
|
||||
- [Introduction](introduction.md)
|
||||
- [Introduction](index.md)
|
||||
- [Tutorial](simple-application.md)
|
||||
- Resources
|
||||
- [JSX](jsx.md)
|
||||
- [ES6](es6.md)
|
||||
- [CSS](css.md)
|
||||
- [Animation](animation.md)
|
||||
- [Testing](testing.md)
|
||||
- [Examples](examples.md)
|
||||
- Key concepts
|
||||
|
|
@ -13,9 +16,10 @@
|
|||
- [Keys](keys.md)
|
||||
- [Autoredraw system](autoredraw.md)
|
||||
- Social
|
||||
- [Mithril Jobs](https://github.com/lhorie/mithril.js/wiki/JOBS)
|
||||
- [Mithril Jobs](https://github.com/MithrilJS/mithril.js/wiki/JOBS)
|
||||
- [How to contribute](contributing.md)
|
||||
- [Credits](credits.md)
|
||||
- [Code of Conduct](code-of-conduct.md)
|
||||
- Misc
|
||||
- [Framework comparison](framework-comparison.md)
|
||||
- [Change log/Migration](change-log.md)
|
||||
|
|
@ -16,4 +16,4 @@
|
|||
- Optional
|
||||
- [Stream](stream.md)
|
||||
- Tooling
|
||||
- [Ospec](https://github.com/lhorie/mithril.js/blob/rewrite/ospec)
|
||||
- [Ospec](https://github.com/MithrilJS/mithril.js/blob/master/ospec)
|
||||
|
|
@ -14,7 +14,7 @@ You DON'T need to call it if data is modified within the execution context of an
|
|||
|
||||
You DO need to call it in `setTimeout`/`setInterval`/`requestAnimationFrame` callbacks, or callbacks from 3rd party libraries.
|
||||
|
||||
Typically, `m.redraw` triggers an asynchronous redraws, but it may trigger synchronously if Mithril detects it's possible to improves performance by doing so (i.e. if no redraw was requested within the last animation frame). You should write code assuming that it always redraws asynchronously.
|
||||
Typically, `m.redraw` triggers an asynchronous redraws, but it may trigger synchronously if Mithril detects it's possible to improve performance by doing so (i.e. if no redraw was requested within the last animation frame). You should write code assuming that it always redraws asynchronously.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
114
docs/releasing.md
Normal file
114
docs/releasing.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Mithril Release Processes
|
||||
|
||||
**Note** These steps all assume that `MithrilJS/mithril.js` is a git remote named `mithriljs`, adjust accordingly if that doesn't match your setup.
|
||||
|
||||
## Releasing a new Mithril version
|
||||
|
||||
### Prepare the release
|
||||
|
||||
1. Ensure your local branch is up to date
|
||||
|
||||
```bash
|
||||
$ git co next
|
||||
$ git pull --rebase mithriljs next
|
||||
```
|
||||
|
||||
2. Determine patch level of the change
|
||||
3. Update information in `docs/change-log.md` to match reality of the new version being prepared for release
|
||||
4. Commit changes to `next`
|
||||
|
||||
```
|
||||
$ git add .
|
||||
$ git commit -m "Preparing for release"
|
||||
|
||||
# Push to your branch
|
||||
$ git push
|
||||
|
||||
# Push to MithrilJS/mithril.js
|
||||
$ git push mithriljs next
|
||||
```
|
||||
|
||||
### Merge from `next` to `master`
|
||||
|
||||
5. Switch to `master` and make sure it's up to date
|
||||
|
||||
```bash
|
||||
$ git co master
|
||||
$ git pull --rebase mithriljs master
|
||||
```
|
||||
|
||||
6. merge `next` on top of it
|
||||
|
||||
```bash
|
||||
$ git merge next
|
||||
```
|
||||
|
||||
7. Clean & update npm dependencies and ensure the tests are passing.
|
||||
|
||||
```bash
|
||||
$ npm prune
|
||||
$ npm i
|
||||
$ npm test
|
||||
```
|
||||
|
||||
### Publish the release
|
||||
|
||||
8. `npm run release <major|minor|patch|semver>`, see the docs for [`npm version`](https://docs.npmjs.com/cli/version)
|
||||
9. The changes will be automatically pushed to your fork
|
||||
10. Push the changes to `MithrilJS/mithril.js`
|
||||
|
||||
```bash
|
||||
$ git push mithriljs master
|
||||
```
|
||||
|
||||
11. Travis will push the new release to npm & create a GitHub release
|
||||
|
||||
### Merge `master` back into `next`
|
||||
|
||||
This helps to ensure that the `version` field of `package.json` doesn't get out of date.
|
||||
|
||||
12. Switch to `next` and make sure it's up to date
|
||||
|
||||
```bash
|
||||
$ git co next
|
||||
$ git pull --rebase mithriljs next
|
||||
```
|
||||
|
||||
13. Merge `master` back onto `next`
|
||||
|
||||
```bash
|
||||
$ git merge master
|
||||
```
|
||||
|
||||
14. Push the changes to your fork & `MithrilJS/mithril.js`
|
||||
|
||||
```bash
|
||||
$ git push
|
||||
$ git push mithriljs next
|
||||
```
|
||||
|
||||
### Update the GitHub release
|
||||
|
||||
15. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build.
|
||||
|
||||
## Updating mithril.js.org
|
||||
|
||||
Fixes to documentation can land whenever, updates to the site are published via Travis.
|
||||
|
||||
```bash
|
||||
# These steps assume that MithrilJS/mithril.js is a git remote named "mithriljs"
|
||||
|
||||
# Ensure your next branch is up to date
|
||||
$ git co next
|
||||
$ git pull mithriljs next
|
||||
|
||||
# Splat the docs folder from next onto master
|
||||
$ git co master
|
||||
$ git co next -- ./docs
|
||||
|
||||
# Manually ensure that no new feature docs were added
|
||||
|
||||
$ git push mithriljs
|
||||
```
|
||||
|
||||
After the Travis build completes the updated docs should appear on https://mithril.js.org in a few minutes.
|
||||
|
|
@ -53,8 +53,8 @@ Argument | Type | Required | Descr
|
|||
`options.headers` | `Object` | No | Headers to append to the request before sending it (applied right before `options.config`).
|
||||
`options.type` | `any = Function(any)` | No | A constructor to be applied to each object in the response. Defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function).
|
||||
`options.serialize` | `string = Function(any)` | No | A serialization method to be applied to `data`. Defaults to `JSON.stringify`, or if `options.data` is an instance of [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData), defaults to the [identity function](https://en.wikipedia.org/wiki/Identity_function) (i.e. `function(value) {return value}`).
|
||||
`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the response. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses.
|
||||
`options.extract` | `string = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for reading response headers and cookies. Defaults to a function that returns `xhr.responseText`. If defined, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. If a custom `extract` callback is set, `options.deserialize` is ignored and the string returned from the extract callback will not be parsed as JSON.
|
||||
`options.deserialize` | `any = Function(string)` | No | A deserialization method to be applied to the `xhr.responseText`. Defaults to a small wrapper around `JSON.parse` that returns `null` for empty responses. If `extract` is defined, `deserialize` will be skipped.
|
||||
`options.extract` | `any = Function(xhr, options)` | No | A hook to specify how the XMLHttpRequest response should be read. Useful for processing response data, reading headers and cookies. By default this is a function that returns `xhr.responseText`, which is in turn passed to `deserialize`. If a custom `extract` callback is provided, the `xhr` parameter is the XMLHttpRequest instance used for the request, and `options` is the object that was passed to the `m.request` call. Additionally, `deserialize` will be skipped and the value returned from the extract callback will not automatically be parsed as JSON.
|
||||
`options.useBody` | `Boolean` | No | Force the use of the HTTP body section for `data` in `GET` requests when set to `true`, or the use of querystring for other HTTP methods when set to `false`. Defaults to `false` for `GET` requests and `true` for other methods.
|
||||
`options.background` | `Boolean` | No | If `false`, redraws mounted components upon completion of the request. If `true`, it does not. Defaults to `false`.
|
||||
**returns** | `Promise` | | A promise that resolves to the response data, after it has been piped through the `extract`, `deserialize` and `type` methods
|
||||
|
|
@ -77,7 +77,7 @@ m.request({
|
|||
})
|
||||
```
|
||||
|
||||
A call to `m.request` return a [promise](promise.md) and trigger a redraw upon completion of its promise chain.
|
||||
A call to `m.request` returns a [promise](promise.md) and triggers a redraw upon completion of its promise chain.
|
||||
|
||||
By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array).
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ function upload(e) {
|
|||
|
||||
var data = new FormData()
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
data.append("file" + i, file)
|
||||
data.append("file" + i, files[i])
|
||||
}
|
||||
|
||||
m.request({
|
||||
|
|
|
|||
129
docs/route.md
129
docs/route.md
|
|
@ -7,6 +7,7 @@
|
|||
- [m.route.get](#mrouteget)
|
||||
- [m.route.prefix](#mrouteprefix)
|
||||
- [m.route.link](#mroutelink)
|
||||
- [m.route.param](#mrouteparam)
|
||||
- [RouteResolver](#routeresolver)
|
||||
- [routeResolver.onmatch](#routeresolveronmatch)
|
||||
- [routeResolver.render](#routeresolverrender)
|
||||
|
|
@ -100,16 +101,47 @@ Argument | Type | Required | Description
|
|||
|
||||
##### m.route.link
|
||||
|
||||
`eventHandler = m.route.link(vnode)`
|
||||
This function can be used as the `oncreate` (and `onupdate`) hook in a `m("a")` vnode:
|
||||
|
||||
```JS
|
||||
m("a[href=/]", {oncreate: m.route.link})`.
|
||||
```
|
||||
|
||||
Using `m.route.link` as a `oncreate` hook causes the link to behave as a router link (i.e. it navigates to the route specified in `href`, instead of nagivating away from the current page to the URL specified in `href`.
|
||||
|
||||
If the `href` attribute is not static, the `onupdate` hook must also be set:
|
||||
|
||||
```JS
|
||||
m("a", {href: someVariable, oncreate: m.route.link, onupdate: m.route.link})`
|
||||
```
|
||||
|
||||
`m.route.link(vnode)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------------- | ----------- | -------- | ---
|
||||
`vnode` | `Vnode` | Yes | This method is meant to be used in conjunction with an `<a>` [vnode](vnodes.md)'s [`oncreate` hook](lifecycle-methods.md)
|
||||
**returns** | Function(e) | | Returns an event handler that calls `m.route.set` with the link's `href` as the `path`
|
||||
`vnode` | `Vnode` | Yes | This method is meant to be used as or in conjunction with an `<a>` [vnode](vnodes.md)'s [`oncreate` and `onupdate` hooks](lifecycle-methods.md)
|
||||
**returns** | | | Returns `undefined`
|
||||
|
||||
##### m.route.param
|
||||
|
||||
Retrieves a route parameter from the last fully resolved route. A route parameter is a key-value pair. Route parameters may come from a few different places:
|
||||
|
||||
- route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`)
|
||||
- router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`)
|
||||
- `history.state` (e.g. if history.state is `{foo: "bar"}`, the route parameter has key `foo` and value `"bar"`)
|
||||
|
||||
`value = m.route.param(key)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------------- | --------------- | -------- | ---
|
||||
`key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`)
|
||||
**returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys
|
||||
|
||||
Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument.
|
||||
|
||||
#### RouteResolver
|
||||
|
||||
A RouterResolver is an object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. A RouteResolver is not a component, and therefore it does NOT have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules.
|
||||
A RouteResolver is an object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. A RouteResolver is not a component, and therefore it does NOT have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules.
|
||||
|
||||
`routeResolver = {onmatch, render}`
|
||||
|
||||
|
|
@ -123,11 +155,11 @@ For more information on `onmatch`, see the [advanced component resolution](#adva
|
|||
|
||||
`routeResolver.onmatch(args, requestedPath)`
|
||||
|
||||
Argument | Type | Description
|
||||
--------------- | ------------------------------ | ---
|
||||
`args` | `Object` | The [routing parameters](#routing-parameters)
|
||||
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
|
||||
**returns** | `Component|Promise<Component>` | Returns a component or a promise that resolves to a component
|
||||
Argument | Type | Description
|
||||
--------------- | ---------------------------------------- | ---
|
||||
`args` | `Object` | The [routing parameters](#routing-parameters)
|
||||
`requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path.
|
||||
**returns** | `Component|Promise<Component>|undefined` | Returns a component or a promise that resolves to a component
|
||||
|
||||
If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`.
|
||||
|
||||
|
|
@ -139,11 +171,11 @@ The `render` method is called on every redraw for a matching route. It is simila
|
|||
|
||||
`vnode = routeResolve.render(vnode)`
|
||||
|
||||
Argument | Type | Description
|
||||
------------------- | --------------- | -----------
|
||||
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"`
|
||||
`vnode.attrs` | `Object` | A map of URL parameter values
|
||||
**returns** | `Vnode` | Returns a vnode
|
||||
Argument | Type | Description
|
||||
------------------- | -------------------- | -----------
|
||||
`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"`
|
||||
`vnode.attrs` | `Object` | A map of URL parameter values
|
||||
**returns** | `Array<Vnode>|Vnode` | The [vnodes](vnodes.md) to be rendered
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -257,8 +289,6 @@ In the example above, we defined a route `/edit/:id`. This creates a dynamic rou
|
|||
|
||||
It's possible to have multiple arguments in a route, for example `/edit/:projectID/:userID` would yield the properties `projectID` and `userID` on the component's vnode attributes object.
|
||||
|
||||
In addition to routing parameters, the `attrs` object also includes a `path` property that contains the current route path, and a `route` property that contains the matched routed.
|
||||
|
||||
#### Key parameter
|
||||
|
||||
When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event.
|
||||
|
|
@ -291,6 +321,20 @@ m.route(document.body, "/edit/pictures/image.jpg", {
|
|||
})
|
||||
```
|
||||
|
||||
#### Handling 404s
|
||||
|
||||
For isomorphic / universal javascript app, an url param and a variadic route combined is very usefull to display custom 404 error page.
|
||||
|
||||
In a case of 404 Not Found error, the server send back the custom page to client. When Mithril is loaded, it will redirect client to the default route because it can't know that route.
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/", {
|
||||
"/": homeComponent,
|
||||
// [...]
|
||||
"/:404...": errorPageComponent
|
||||
});
|
||||
```
|
||||
|
||||
#### History state
|
||||
|
||||
It's possible to take full advantage of the underlying `history.pushState` API to improve user's navigation experience. For example, an application could "remember" the state of a large form when the user leaves a page by navigating away, such that if the user pressed the back button in the browser, they'd have the form filled rather than a blank form.
|
||||
|
|
@ -385,7 +429,7 @@ var Layout = {
|
|||
}
|
||||
```
|
||||
|
||||
In the example above, the layout merely consists of a `<div class="layout">` that contains the children passed to the component, but in a real life scenario it could be as complex as neeeded.
|
||||
In the example above, the layout merely consists of a `<div class="layout">` that contains the children passed to the component, but in a real life scenario it could be as complex as needed.
|
||||
|
||||
One way to wrap the layout is to define an anonymous component in the routes map:
|
||||
|
||||
|
|
@ -464,7 +508,7 @@ In example 2, since `Layout` is the top-level component in both routes, the DOM
|
|||
|
||||
#### Authentication
|
||||
|
||||
The RouterResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login.
|
||||
The RouteResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login.
|
||||
|
||||
```javascript
|
||||
var isLoggedIn = false
|
||||
|
|
@ -486,9 +530,7 @@ m.route(document.body, "/secret", {
|
|||
"/secret": {
|
||||
onmatch: function() {
|
||||
if (!isLoggedIn) m.route.set("/login")
|
||||
},
|
||||
render: function() {
|
||||
return m(Home)
|
||||
else return Home
|
||||
}
|
||||
},
|
||||
"/login": Login
|
||||
|
|
@ -497,7 +539,50 @@ m.route(document.body, "/secret", {
|
|||
|
||||
When the application loads, `onmatch` is called and since `isLoggedIn` is false, the application redirects to `/login`. Once the user pressed the login button, `isLoggedIn` would be set to true, and the application would redirect to `/secret`. The `onmatch` hook would run once again, and since `isLoggedIn` is true this time, the application would render the `Home` component.
|
||||
|
||||
For the sake of simplicity, in the example above, the user's logged in status is kept in a global variable, and that flag is merely toggled when the user clicks the login button. In a real life application, a user would obviously have to supply proper login credentials, and clicking the login button would trigger a request to a server to authenticate the user.
|
||||
For the sake of simplicity, in the example above, the user's logged in status is kept in a global variable, and that flag is merely toggled when the user clicks the login button. In a real life application, a user would obviously have to supply proper login credentials, and clicking the login button would trigger a request to a server to authenticate the user:
|
||||
|
||||
```javascript
|
||||
var Auth = {
|
||||
username: "",
|
||||
password: "",
|
||||
|
||||
setUsername: function(value) {
|
||||
Auth.username = value
|
||||
},
|
||||
setPassword: function(value) {
|
||||
Auth.password = value
|
||||
},
|
||||
login: function() {
|
||||
m.request({
|
||||
url: "/api/v1/auth",
|
||||
data: {username: Auth.username, password: Auth.password}
|
||||
}).then(function(data) {
|
||||
localStorage.setItem("auth-token": data.token)
|
||||
m.route.set("/secret")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var Login = {
|
||||
view: function() {
|
||||
return m("form", [
|
||||
m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}),
|
||||
m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}),
|
||||
m("button[type=button]", {onclick: Auth.login, "Login")
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
m.route(document.body, "/secret", {
|
||||
"/secret": {
|
||||
onmatch: function() {
|
||||
if (!localStorage.getItem("auth-token")) m.route.set("/login")
|
||||
else return Home
|
||||
}
|
||||
},
|
||||
"/login": Login
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ First let's create an entry point for the application. Create a file `index.html
|
|||
<title>My Application</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js"></script>
|
||||
<script src="bin/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The `<!doctype html>` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application.
|
||||
|
||||
We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `app.js`.
|
||||
We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`.
|
||||
|
||||
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command:
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ module.exports = User
|
|||
Next we create a function that will trigger an XHR call. Let's call it `loadList`
|
||||
|
||||
```javascript
|
||||
// models/User.js
|
||||
// src/models/User.js
|
||||
var m = require("mithril")
|
||||
|
||||
var User = {
|
||||
|
|
@ -74,10 +74,10 @@ var User = {
|
|||
module.exports = User
|
||||
```
|
||||
|
||||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET http://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
|
||||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
|
||||
|
||||
```javascript
|
||||
// models/User.js
|
||||
// src/models/User.js
|
||||
var m = require("mithril")
|
||||
|
||||
var User = {
|
||||
|
|
@ -85,7 +85,7 @@ var User = {
|
|||
loadList: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users",
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users",
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -99,7 +99,7 @@ module.exports = User
|
|||
|
||||
The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API).
|
||||
|
||||
The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `reesult.data` array to `User.list`.
|
||||
The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`.
|
||||
|
||||
Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request.
|
||||
|
||||
|
|
@ -112,15 +112,17 @@ Now, let's create a view module so that we can display data from our User model
|
|||
Create a file called `src/views/UserList.js`. First, let's include Mithril and our model, since we'll need to use both:
|
||||
|
||||
```javascript
|
||||
// src/views/UserList.js
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
```
|
||||
|
||||
Next, let's create a Mithril component. A component is simply an object that has a `view` method:
|
||||
|
||||
```javascript
|
||||
// src/views/UserList.js
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
view: function() {
|
||||
|
|
@ -135,7 +137,7 @@ Let's use Mithril hyperscript to create a list of items. Hyperscript is the most
|
|||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
view: function() {
|
||||
|
|
@ -149,16 +151,15 @@ The `".user-list"` string is a CSS selector, and as you would expect, `.user-lis
|
|||
Now, let's reference the list of users from the model we created earlier (`User.list`) to dynamically loop through data:
|
||||
|
||||
```javascript
|
||||
// src/views/UserList.js
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
view: function() {
|
||||
return m(".user-list", [
|
||||
User.list.map(function(user) {
|
||||
return m(".user-list-item", user.firstName + " " + user.lastName)
|
||||
})
|
||||
])
|
||||
return m(".user-list", User.list.map(function(user) {
|
||||
return m(".user-list-item", user.firstName + " " + user.lastName)
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -168,38 +169,38 @@ Since `User.list` is a Javascript array, and since hyperscript views are just Ja
|
|||
The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md):
|
||||
|
||||
```javascript
|
||||
// src/views/UserList.js
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
oninit: User.loadList,
|
||||
view: function() {
|
||||
return m(".user-list", [
|
||||
User.list.map(function(user) {
|
||||
return m(".user-list-item", user.firstName + " " + user.lastName)
|
||||
})
|
||||
])
|
||||
return m(".user-list", User.list.map(function(user) {
|
||||
return m(".user-list-item", user.firstName + " " + user.lastName)
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that we added an `oninit` method to the component, which references `User.loadList`. This means that when the component initializes, User.loadList will be called, triggering an XHR request. When the server returns a response, `User.list` gets populated.
|
||||
|
||||
Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common newbie mistake: calling the function immediately means that the XHR request will fire even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected.
|
||||
Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to javascript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected.
|
||||
|
||||
---
|
||||
|
||||
Let's render the view from the entry point file `index.js` we created earlier:
|
||||
Let's render the view from the entry point file `src/index.js` we created earlier:
|
||||
|
||||
```javascript
|
||||
// src/index.js
|
||||
var m = require("mithril")
|
||||
|
||||
var UserList = require("./view/UserList")
|
||||
var UserList = require("./views/UserList")
|
||||
|
||||
m.mount(document.body, UserList)
|
||||
```
|
||||
|
||||
The `m.mount` call renders the specified component (`UserList`) into a DOM element (`document.body`), erasing any DOM that were there previously. Opening the HTML file in a browser should now display a list of person names.
|
||||
The `m.mount` call renders the specified component (`UserList`) into a DOM element (`document.body`), erasing any DOM that was there previously. Opening the HTML file in a browser should now display a list of person names.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -207,9 +208,9 @@ Right now, the list looks rather plain because we have not defined any styles.
|
|||
|
||||
There are many similar conventions and libraries that help organize application styles nowadays. Some, like [Bootstrap](http://getbootstrap.com/) dictate a specific set of HTML structures and semantically meaningful class names, which has the upside of providing low cognitive dissonance, but the downside of making customization more difficult. Others, like [Tachyons](http://tachyons.io/) provide a large number of self-describing, atomic class names at the cost of making the class names themselves non-semantic. "CSS-in-JS" is another type of CSS system that is growing in popularity, which basically consists of scoping CSS via transpilation tooling. CSS-in-JS libraries achieve maintainability by reducing the size of the problem space, but come at the cost of having high complexity.
|
||||
|
||||
Regardless of what CSS convention/library you choose, a good rule of thumb is to avoid the cascading aspect of CSS. To keep this tutorial simple, we'll just use plain CSS with overly explicit class names, so that the styles themselves provide the atomicity of Tachyons, and class name collisions are made unlikely through the verbosity of the class names. Plain CSS can be sufficient for low-complexity projects (e.g. 3 to 6 man-months of initial implementation time and few project phases)
|
||||
Regardless of what CSS convention/library you choose, a good rule of thumb is to avoid the cascading aspect of CSS. To keep this tutorial simple, we'll just use plain CSS with overly explicit class names, so that the styles themselves provide the atomicity of Tachyons, and class name collisions are made unlikely through the verbosity of the class names. Plain CSS can be sufficient for low-complexity projects (e.g. 3 to 6 man-months of initial implementation time and few project phases).
|
||||
|
||||
To add styles, let's first create a file called `styles.css` and include it in the `index.html` file
|
||||
To add styles, let's first create a file called `styles.css` and include it in the `index.html` file:
|
||||
|
||||
```markup
|
||||
<!doctype html>
|
||||
|
|
@ -221,7 +222,7 @@ To add styles, let's first create a file called `styles.css` and include it in t
|
|||
<link href="styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js"></script>
|
||||
<script src="bin/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
|
@ -249,9 +250,10 @@ Routing means binding a screen to a unique URL, to create the ability to go from
|
|||
We can add routing by changing the `m.mount` call to a `m.route` call:
|
||||
|
||||
```javascript
|
||||
// src/index.js
|
||||
var m = require("mithril")
|
||||
|
||||
var UserList = require("./view/UserList")
|
||||
var UserList = require("./views/UserList")
|
||||
|
||||
m.route(document.body, "/list", {
|
||||
"/list": UserList
|
||||
|
|
@ -278,14 +280,14 @@ module.exports = {
|
|||
}
|
||||
```
|
||||
|
||||
Then we can `require` this new module from `index.js`
|
||||
Then we can `require` this new module from `src/index.js`
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
// src/index.js
|
||||
var m = require("mithril")
|
||||
|
||||
var UserList = require("./view/UserList")
|
||||
var UserForm = require("./view/UserForm")
|
||||
var UserList = require("./views/UserList")
|
||||
var UserForm = require("./views/UserForm")
|
||||
|
||||
m.route(document.body, "/list", {
|
||||
"/list": UserList
|
||||
|
|
@ -295,11 +297,11 @@ m.route(document.body, "/list", {
|
|||
And finally, we can create a route that references it:
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
// src/index.js
|
||||
var m = require("mithril")
|
||||
|
||||
var UserList = require("./view/UserList")
|
||||
var UserForm = require("./view/UserForm")
|
||||
var UserList = require("./views/UserList")
|
||||
var UserForm = require("./views/UserForm")
|
||||
|
||||
m.route(document.body, "/list", {
|
||||
"/list": UserList,
|
||||
|
|
@ -322,7 +324,7 @@ module.exports = {
|
|||
m("input.input[type=text][placeholder=First name]"),
|
||||
m("label.label", "Last name"),
|
||||
m("input.input[placeholder=Last name]"),
|
||||
m("button.button[type=submit]", "Save"),
|
||||
m("button.button[type=button]", "Save"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -344,7 +346,7 @@ body,.input,.button {font:normal 16px Verdana;margin:0;}
|
|||
.button:hover {background:#e8e8e8;}
|
||||
```
|
||||
|
||||
Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/model/User.js`. This is how the code is right now:
|
||||
Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/models/User.js`. This is how the code is right now:
|
||||
|
||||
```javascript
|
||||
// src/models/User.js
|
||||
|
|
@ -355,7 +357,7 @@ var User = {
|
|||
loadList: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users",
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users",
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -378,7 +380,7 @@ var User = {
|
|||
loadList: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users",
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users",
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -390,8 +392,7 @@ var User = {
|
|||
load: function(id) {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users/:id",
|
||||
data: {id: id},
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users/" + id,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -408,7 +409,7 @@ Notice we added a `User.current` property, and a `User.load(id)` method which po
|
|||
```javascript
|
||||
// src/views/UserForm.js
|
||||
var m = require("mithril")
|
||||
var User = require("./model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
oninit: function(vnode) {User.load(vnode.attrs.id)},
|
||||
|
|
@ -418,7 +419,7 @@ module.exports = {
|
|||
m("input.input[type=text][placeholder=First name]", {value: User.current.firstName}),
|
||||
m("label.label", "Last name"),
|
||||
m("input.input[placeholder=Last name]", {value: User.current.lastName}),
|
||||
m("button.button[type=submit]", "Save"),
|
||||
m("button.button[type=button]", "Save"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -429,18 +430,16 @@ Similar to the `UserList` component, `oninit` calls `User.load()`. Remember we h
|
|||
Now, let's modify the `UserList` view so that we can navigate from there to a `UserForm`:
|
||||
|
||||
```javascript
|
||||
// src/views/UserForm.js
|
||||
// src/views/UserList.js
|
||||
var m = require("mithril")
|
||||
var User = require("../model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
oninit: User.loadList,
|
||||
view: function() {
|
||||
return m(".user-list", [
|
||||
User.list.map(function(user) {
|
||||
return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName)
|
||||
})
|
||||
])
|
||||
return m(".user-list", User.list.map(function(user) {
|
||||
return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName)
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -456,12 +455,17 @@ The form itself still doesn't save when you press "Save". Let's make this form w
|
|||
```javascript
|
||||
// src/views/UserForm.js
|
||||
var m = require("mithril")
|
||||
var User = require("./model/User")
|
||||
var User = require("../models/User")
|
||||
|
||||
module.exports = {
|
||||
oninit: function(vnode) {User.load(vnode.attrs.id)},
|
||||
view: function() {
|
||||
return m("form", [
|
||||
return m("form", {
|
||||
onsubmit: function(e) {
|
||||
e.preventDefault()
|
||||
User.save()
|
||||
}
|
||||
}, [
|
||||
m("label.label", "First name"),
|
||||
m("input.input[type=text][placeholder=First name]", {
|
||||
oninput: m.withAttr("value", function(value) {User.current.firstName = value}),
|
||||
|
|
@ -472,7 +476,7 @@ module.exports = {
|
|||
oninput: m.withAttr("value", function(value) {User.current.lastName = value}),
|
||||
value: User.current.lastName
|
||||
}),
|
||||
m("button.button[type=submit]", {onclick: User.save}, "Save"),
|
||||
m("button.button[type=submit]", "Save"),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -491,7 +495,7 @@ var User = {
|
|||
loadList: function() {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users",
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users",
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -503,8 +507,7 @@ var User = {
|
|||
load: function(id) {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users/:id",
|
||||
data: {id: id},
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users/" + id,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then(function(result) {
|
||||
|
|
@ -515,7 +518,7 @@ var User = {
|
|||
save: function() {
|
||||
return m.request({
|
||||
method: "PUT",
|
||||
url: "http://rem-rest-api.herokuapp.com/api/users/:id",
|
||||
url: "https://rem-rest-api.herokuapp.com/api/users/" + User.current.id,
|
||||
data: User.current,
|
||||
withCredentials: true,
|
||||
})
|
||||
|
|
@ -536,7 +539,9 @@ Currently, we're only able to navigate back to the user list via the browser bac
|
|||
Let's create a file `src/views/Layout.js`:
|
||||
|
||||
```javascript
|
||||
var Layout = {
|
||||
var m = require("mithril")
|
||||
|
||||
module.exports = {
|
||||
view: function(vnode) {
|
||||
return m("main.layout", [
|
||||
m("nav.menu", [
|
||||
|
|
@ -571,14 +576,15 @@ body,.input,.button {font:normal 16px Verdana;margin:0;}
|
|||
.button:hover {background:#e8e8e8;}
|
||||
```
|
||||
|
||||
Let's change the router in `index.js` to add our layout into the mix:
|
||||
Let's change the router in `src/index.js` to add our layout into the mix:
|
||||
|
||||
```javascript
|
||||
// index.js
|
||||
// src/index.js
|
||||
var m = require("mithril")
|
||||
|
||||
var UserList = require("./view/UserList")
|
||||
var UserForm = require("./view/UserForm")
|
||||
var UserList = require("./views/UserList")
|
||||
var UserForm = require("./views/UserForm")
|
||||
var Layout = require("./views/Layout")
|
||||
|
||||
m.route(document.body, "/list", {
|
||||
"/list": {
|
||||
|
|
@ -598,7 +604,7 @@ We replaced each component with a [RouteResolver](route.md#routeresolver) (basic
|
|||
|
||||
The interesting thing to pay attention to is how components can be used instead of a selector string in a `m()` call. Here, in the `/list` route, we have `m(Layout, m(UserList))`. This means there's a root vnode that represents an instance of `Layout`, which has a `UserList` vnode as its only child.
|
||||
|
||||
In the `/edit/:id` route, there's also a `vnode` argument that carries the route parameters into the `UserForm` component. So if the URL is `/edit/1`, then `vnode.attrs` in this case is `{id: 1}`, and this `m(UserForm, vnode.attrs)` is equivalent to `m(UserForm, {id: 1})`. The equivalent JSX code would be `<UserForm id={vnode.attrs} />`.
|
||||
In the `/edit/:id` route, there's also a `vnode` argument that carries the route parameters into the `UserForm` component. So if the URL is `/edit/1`, then `vnode.attrs` in this case is `{id: 1}`, and this `m(UserForm, vnode.attrs)` is equivalent to `m(UserForm, {id: 1})`. The equivalent JSX code would be `<UserForm id={vnode.attrs.id} />`.
|
||||
|
||||
Refresh the page in the browser and now you'll see the global navigation on every page in the app.
|
||||
|
||||
|
|
@ -608,5 +614,4 @@ This concludes the tutorial.
|
|||
|
||||
In this tutorial, we went through the process of creating a very simple application where we can list users from a server and edit them individually. As an extra exercise, try to implement user creation and deletion on your own.
|
||||
|
||||
If you want to see more examples of Mithril code, check the [examples](examples.md) page. If you have questions, feel free to drop by the [Mithril chat room](https://gitter.im/lhorie/mithril.js).
|
||||
|
||||
If you want to see more examples of Mithril code, check the [examples](examples.md) page. If you have questions, feel free to drop by the [Mithril chat room](https://gitter.im/MithrilJS/mithril.js).
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
- [Static members](#static-members)
|
||||
- [Stream.combine](#streamcombine)
|
||||
- [Stream.merge](#streammerge)
|
||||
- [Stream.scan](#streamscan)
|
||||
- [Stream.scanMerge](#streamscanmerge)
|
||||
- [Stream.HALT](#streamhalt)
|
||||
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||
- [Instance members](#static-members)
|
||||
|
|
@ -40,6 +42,14 @@ Streams are NOT bundled with Mithril's core distribution. To include the Streams
|
|||
var Stream = require("mithril/stream")
|
||||
```
|
||||
|
||||
You can also download the module directly if your environment does not support a bundling toolchain:
|
||||
|
||||
```markup
|
||||
<script src="https://unpkg.com/mithril-stream"></script>
|
||||
```
|
||||
|
||||
When loaded directly with a `<script>` tag (rather than required), the stream library will be exposed as `window.m.stream`. If `window.m` is already defined (e.g. because you also use the main Mithril script), it will attach itself to the existing object. Otherwise it creates a new `window.m`. If you want to use streams in conjunction with Mithril as raw script tags, you should include Mithril in your page before `mithril-stream`, because `mithril` will otherwise overwrite the `window.m` object defined by `mithril-stream`. This is not a concern when the libraries are consumed as CommonJS modules (using `require(...)`).
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
@ -83,7 +93,7 @@ Specifies how the value of a computed stream is generated. See [combining stream
|
|||
|
||||
Argument | Type | Required | Description
|
||||
------------ | -------------------- | -------- | ---
|
||||
`streams...` | splat of `Stream`s | No | Splat of zero or more streams that correspond to the streams passed as the second argument to [`stream.combine`](#stream-combine)
|
||||
`streams...` | splat of `Streams` | No | Splat of zero or more streams that correspond to the streams passed as the second argument to [`stream.combine`](#stream-combine)
|
||||
`changed` | `Array<Stream>` | Yes | List of streams that were affected by an update
|
||||
**returns** | `any` | | Returns a computed value
|
||||
|
||||
|
|
@ -106,6 +116,39 @@ Argument | Type | Required | Description
|
|||
|
||||
---
|
||||
|
||||
##### Stream.scan
|
||||
|
||||
Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value.
|
||||
|
||||
`stream = Stream.scan(fn, accumulator, stream)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------- | -------------------------------- | -------- | ---
|
||||
`fn` | `(accumulator, value) -> result` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value
|
||||
`accumulator` | `any` | Yes | The starting value for the accumulator
|
||||
`stream` | `Stream` | Yes | Stream containing the values
|
||||
**returns** | `Stream` | | Returns a new stream containing the result
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
---
|
||||
|
||||
##### Stream.scanMerge
|
||||
|
||||
Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream.
|
||||
|
||||
`stream = Stream.scanMerge(pairs, accumulator)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
------------- | ------------------------------------------------ | -------- | ---
|
||||
`pairs` | `Array<[Stream, (accumulator, value) -> value]>` | Yes | An array of tuples of stream and scan functions
|
||||
`accumulator` | `any` | Yes | The starting value for the accumulator
|
||||
**returns** | `Stream` | | Returns a new stream containing the result
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
---
|
||||
|
||||
##### Stream.HALT
|
||||
|
||||
A special value that can be returned to stream callbacks to halt execution of downstreams
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
body {-webkit-text-size-adjust: 100%;}
|
||||
body,table,h5 {font:normal 16px 'Open Sans';}
|
||||
header,main {margin:auto;max-width:1000px;}
|
||||
header section {position:absolute;width:250px;}
|
||||
|
|
@ -5,45 +6,92 @@ nav a {border-left:1px solid #ddd;padding:0 10px;}
|
|||
nav a:first-child {border:0;padding-left:0;}
|
||||
main {margin-bottom:100px;}
|
||||
main section {margin-left:270px;}
|
||||
h1 {font-size:24px;margin:0 0 15px;}
|
||||
h2 {font-size:22px;margin:30px 0 15px;}
|
||||
h3 {font-size:20px;margin:30px 0 15px;}
|
||||
h4 {font-size:18px;margin:15px 0 15px;}
|
||||
h5 {font-weight:bold;margin:15px 0 15px;}
|
||||
h1 small {font-size:16px;}
|
||||
pre,code {background:#eee;font-family:monospace;}
|
||||
pre {border-left:3px solid #1e5799;overflow:auto;padding:10px 20px;}
|
||||
code {border:1px solid #ddd;display:inline-block;margin:0 0 1px;padding:3px;white-space:pre;}
|
||||
p {margin:0 0 15px;}
|
||||
pre,code {background:#eee;font-family:monospace;font-size:14px;}
|
||||
pre {border-left:3px solid #1e5799;overflow:auto;padding:10px 20px;margin:20px 0;}
|
||||
code {border:1px solid #ddd;display:inline-block;margin:0 0 1px;padding:5px 3px;white-space:pre;}
|
||||
pre code {border:0;margin:0;padding:0;}
|
||||
table {border-collapse:collapse;width:100%;}
|
||||
table {border-collapse:collapse;margin:0 0 30px;width:100%;}
|
||||
tbody tr:nth-child(odd) {background:#fafafa;}
|
||||
thead tr,tbody tr:nth-child(even) {background:#f3f3f3;}
|
||||
tr {border-bottom:1px solid #eee;}
|
||||
th {text-align:left;}
|
||||
th,td {padding:3px 10px;vertical-align:top;}
|
||||
a {color:#1e5799;display:inline-block;text-decoration:none;}
|
||||
a {color:#1e5799;text-decoration:none;}
|
||||
a:hover {text-decoration:underline;}
|
||||
hr {border:0;border-bottom:1px solid #ddd;margin:30px 0;}
|
||||
|
||||
/* Headings */
|
||||
h1,h2,h3,h4,h5 {position:relative}
|
||||
h1 {font-size:24px;margin:0 0 15px;}
|
||||
h2 {font-size:22px;margin:45px 0 15px;}
|
||||
h3 {font-size:20px;margin:45px 0 15px;}
|
||||
h4 {font-size:18px;margin:30px 0 15px;}
|
||||
h5 {font-weight:bold;margin:15px 0 15px;}
|
||||
h1 img {transform:rotate(180deg);vertical-align:middle;width:20px;}
|
||||
h1 small {font-size:16px;}
|
||||
h2 a,h3 a,h4 a,h5 a,
|
||||
h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,
|
||||
h2 a:active,h3 a:active,h4 a:active,h5 a:active,
|
||||
h2 a:visited,h3 a:visited,h4 a:visited,h5 a:visited {color:#000;text-decoration:none;}
|
||||
h2::before,h3::before,h4::before,h5::before {content:"#";position:absolute;left:-20px;visibility:hidden;}
|
||||
h2:hover::before,h3:hover::before,h4:hover::before,h5:hover::before {visibility:visible;}
|
||||
#signature + p code {padding:3px 10px;}
|
||||
h1 + ul {margin:40px 0 0 -270px;padding:0;position:absolute;width:250px;}
|
||||
h1 + ul + hr {display:none;}
|
||||
h1 + ul li {list-style:none;margin:0;padding:0;}
|
||||
h1 + ul li:last-child {border-bottom:0;}
|
||||
h1 + ul ul {margin:0 0 10px;padding:0 0 0 15px;}
|
||||
h1 + ul ul {margin:0 0 2px;padding:0 0 0 15px;}
|
||||
h1 + ul ul li {border:0;}
|
||||
h1 + ul strong + ul {border-left:3px solid #1e5799;}
|
||||
|
||||
.hamburger {display:none;}
|
||||
|
||||
@keyframes grow {
|
||||
from {transform:scaleX(0)}
|
||||
to {transform:scaleX(100%)}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.hamburger {display:block;font-size:30px;padding:0 10px;position:fixed;right:0;top:0;z-index:2;}
|
||||
.hamburger:hover {text-decoration:none;}
|
||||
main section {margin:0;}
|
||||
header section {margin:0 0 20px;position:static;width:auto;}
|
||||
h1 + ul {background:#eee;border:1px solid #ccc;box-sizing:border-box;display:none;height:100%;margin:0;overflow:auto;padding:20px;position:fixed;right:0;top:0;width:100%;}
|
||||
h1 + ul + hr {display:block;}
|
||||
header section,h1 + ul {margin:0 0 20px;position:static;width:auto;}
|
||||
.navigating h1 + ul {display:block;}
|
||||
.navigating {overflow:hidden;}
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
#signature + p + table,#signature + p + table tbody,#signature + p + table tr,#signature + p + table th,#signature + p + table td {display:block;}
|
||||
#signature + p + table thead {display:none;}
|
||||
#signature + p + table td:before {display:inline-block;font-style:italic;padding:0 10px 0 0;width:100px;}
|
||||
#signature + p + table tr:not(:last-child) td:nth-child(1):before {content:"Argument:";}
|
||||
#signature + p + table td:nth-child(2):before {content:"Type:";}
|
||||
#signature + p + table td:nth-child(3):before {content:"Required:";}
|
||||
#signature + p + table td:nth-child(4):before {content:"Description:";}
|
||||
#signature + p + table tr:last-child td:nth-child(3) {display:none;}
|
||||
table,table tbody,table tr,table th,table td {display:block;}
|
||||
table thead {display:none;}
|
||||
table td:before {display:inline-block;font-style:italic;font-weight:bold;padding:0 10px 0 0;width:100px;}
|
||||
table tr:not(:last-child) td:nth-child(1):before {content:"Argument:";}
|
||||
table tr:last-child td:nth-child(3) {display:none;}
|
||||
table td:nth-child(2):before {content:"Type:";}
|
||||
table td:nth-child(3):before {content:"Required:";}
|
||||
table td:nth-child(4):before {content:"Description:";}
|
||||
#structure ~ table td:nth-child(1):before {content:"Property:";}
|
||||
#structure ~ table td:nth-child(2):before {content:"Type:";}
|
||||
#structure ~ table td:nth-child(3):before {content:"Description:";}
|
||||
#vnode-types ~ table td:nth-child(1):before {content:"Vnode type:";}
|
||||
#vnode-types ~ table td:nth-child(2):before {content:"Example:";}
|
||||
#vnode-types ~ table td:nth-child(3):before {content:"Description:";}
|
||||
#lifecycle-methods ~ table td:nth-child(1):before {content:"Hook:";}
|
||||
#lifecycle-methods ~ table td:nth-child(2):before {content:"Description:";}
|
||||
#react ~ table td:nth-child(1):before {content:"React:";}
|
||||
#angular ~ table td:nth-child(1):before {content:"Angular:";}
|
||||
#vue ~ table td:nth-child(1):before {content:"Vue:";}
|
||||
#comparisons ~ table td:nth-child(2):before {content:"Mithril:";}
|
||||
}
|
||||
@media print {
|
||||
nav,h1 + ul {display:none;}
|
||||
main section {margin:0;}
|
||||
}
|
||||
|
||||
/* prism theming */
|
||||
.token.comment,.token.prolog,.token.doctype,.token.cdata {color:#888;}
|
||||
.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol {color:#905;}
|
||||
.token.selector,.token.attr-name,.token.string,.token.builtin {color:#690;}
|
||||
.token.atrule,.token.attr-value,.token.punctuation,.token.keyword {color:#1e5799;}
|
||||
.token.regex,.token.important {color:#e90;}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Testing
|
||||
|
||||
Mithril comes with a testing framework called [ospec](https://github.com/lhorie/mithril.js/tree/rewrite/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis).
|
||||
Mithril comes with a testing framework called [ospec](https://github.com/MithrilJS/mithril.js/tree/master/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis).
|
||||
|
||||
The easist way to setup the test runner is to create an NPM script for it. Open your project's `package.json` file and edit the `test` line under the `scripts` section:
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ To setup a test suite, create a `tests` folder and inside of it, create a test f
|
|||
// file: tests/math-test.js
|
||||
var o = require("mithril/ospec/ospec")
|
||||
|
||||
o("math", function() {
|
||||
o.spec("math", function() {
|
||||
o("addition works", function() {
|
||||
o(1 + 2).equals(3)
|
||||
})
|
||||
|
|
@ -36,11 +36,27 @@ npm test
|
|||
|
||||
---
|
||||
|
||||
### Running mithril in a non-browser environment
|
||||
|
||||
Mithril has a few dependencies on globals that exist in all its supported browser environments but are missing in all non-browser environments. To work around this you can use the browser mocks that ship with the mithril npm package.
|
||||
|
||||
The simplest way to do this is ensure the following snippet of code runs **before** you include mithril itself in your project.
|
||||
|
||||
```js
|
||||
// Polyfill DOM env for mithril
|
||||
global.window = require("mithril/test-utils/browserMock.js")();
|
||||
global.document = window.document;
|
||||
```
|
||||
|
||||
Once that snippet has been run you can `require("mithril")` and it should be quite happy.
|
||||
|
||||
---
|
||||
|
||||
### Good testing practices
|
||||
|
||||
Generally speaking, there are two ways to write tests: upfront and after the fact.
|
||||
|
||||
Writing tests upfront requires specifications to be frozen. Upfront tests are a great way of codifying the rules that a yet-to-be-implemented API must obey. However, writing tests upfront is not a suitable strategy if you don't know exactly what your code will look like, if the scope of the API is not well known or if it's likely to change (e.g. based on previous history at the company).
|
||||
Writing tests upfront requires specifications to be frozen. Upfront tests are a great way of codifying the rules that a yet-to-be-implemented API must obey. However, writing tests upfront may not be a suitable strategy if you don't have a reasonable idea of what your project will look like, if the scope of the API is not well known or if it's likely to change (e.g. based on previous history at the company).
|
||||
|
||||
Writing tests after the fact is a way to document the behavior of a system and avoid regressions. They are useful to ensure that obscure corner cases are not inadvertedly broken and that previously fixed bugs do not get re-introduced by unrelated changes.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
### What is virtual DOM
|
||||
|
||||
A virtual DOM tree is a Javascript data structure that describes a DOM tree. It consists of nested virtual DOM nodes, also known *vnodes*.
|
||||
A virtual DOM tree is a Javascript data structure that describes a DOM tree. It consists of nested virtual DOM nodes, also known as *vnodes*.
|
||||
|
||||
The first time a virtual DOM tree is rendered, it is used as a blueprint to create a DOM tree that matches its structure.
|
||||
|
||||
Typically, Virtual DOM trees are then recreated every render cycle, which normally occurs in response to event handlers or to data changes. Mithril *diffs* a vnode tree against its previous version and only modifies DOM elements in spots where there are changes.
|
||||
Typically, virtual DOM trees are then recreated every render cycle, which normally occurs in response to event handlers or to data changes. Mithril *diffs* a vnode tree against its previous version and only modifies DOM elements in spots where there are changes.
|
||||
|
||||
It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern Javascript engines can create hundreds of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes.
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ For that reason, Mithril uses a sophisticated and highly optimized virtual DOM d
|
|||
|
||||
The reason Mithril goes to such great lengths to support a rendering model that recreates the entire virtual DOM tree on every render is to provide [retained mode rendering](https://en.wikipedia.org/wiki/Retained_mode), a style of rendering that makes it drastically easier to manage UI complexity.
|
||||
|
||||
To illustrate why retained mode is so important, consider the DOM API and HTML. The DOM API is an [immediate mode](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)) rendering system and requires writing out exact instructions to assemble a DOM tree procedurally. The imperative nature of the DOM API means you have many opportunities to micro-optimize your code, but it also means that you have more chances for introducing bugs and more chances to make code harder to understand.
|
||||
To illustrate why retained mode is so important, consider the DOM API and HTML. The DOM API is an [immediate mode](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)) rendering system and requires writing out exact instructions to assemble a DOM tree procedurally. The imperative nature of the DOM API means you have many opportunities to micro-optimize your code, but it also means that you have more chances of introducing bugs and more chances to make code harder to understand.
|
||||
|
||||
In contrast, HTML is a retained mode rendering system. With HTML, you can write a DOM tree in a far more natural and readable way, without worrying about forgetting to append a child to a parent, running into stack overflows when rendering extremely deep trees, etc.
|
||||
|
||||
|
|
@ -35,13 +35,13 @@ Virtual DOM goes one step further than HTML by allowing you to write *dynamic* D
|
|||
|
||||
Virtual DOM nodes, or *vnodes*, are javascript objects that represent DOM elements (or parts of the DOM). Mithril's virtual DOM engine consumes a tree of vnodes to produce a DOM tree.
|
||||
|
||||
Vnodes can be created via the [`m()`](hyperscript.md) hyperscript utility:
|
||||
Vnodes are created via the [`m()`](hyperscript.md) hyperscript utility:
|
||||
|
||||
```javascript
|
||||
m("div", {id: "test"}, "hello")
|
||||
```
|
||||
|
||||
Vnodes can also consume [components](components.md):
|
||||
Hyperscript can also consume [components](components.md):
|
||||
|
||||
```javascript
|
||||
// define a component
|
||||
|
|
@ -70,11 +70,15 @@ Property | Type | Description
|
|||
`key` | `String?` | The value used to map a DOM element to its respective item in a array of data.
|
||||
`attrs` | `Object?` | A hashmap of [DOM attributes](hyperscript.md#dom-attributes), [events](hyperscript.md#events), [properties](hyperscript.md#properties) and [lifecycle methods](hyperscript.md#lifecycle-methods).
|
||||
`children` | `(Array|String|Number|Boolean)?` | In most vnode types, the `children` property is an array of vnodes. For text and trusted HTML vnodes, The `children` property is either a string, a number or a boolean.
|
||||
`text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as its only child.
|
||||
`dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragment and trusted HTML vnodes, `dom` points to the first element in the range.
|
||||
`text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as their only child.
|
||||
`dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragments and trusted HTML vnodes, `dom` points to the first element in the range.
|
||||
`domSize` | `Number?` | This is only set in fragment and trusted HTML vnodes, and it's `undefined` in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the `dom` property).
|
||||
`state` | `Object` | An object that is persisted between redraws. In component vnodes, `state` is a shallow clone of the component object.
|
||||
`events` | `Object?` | An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The `events` property is `undefined` if there are no event handlers defined. This property is only used internally by Mithril, do not use it.
|
||||
`state` | `Object?` | An object that is persisted between redraws. It is provided by the core engine when needed. In POJO component vnodes, the `state` inherits prototypically from the component object/class. In class component vnodes it is an instance of the class. In closure components it is the object returned by the closure.
|
||||
`_state` | `Object?` | For components, a reference to the original `vnode.state` object, used to lookup the `view` and hooks. This property is only used internally by Mithril, do not use or modify it.
|
||||
`events` | `Object?` | An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The `events` property is `undefined` if there are no event handlers defined. This property is only used internally by Mithril, do not use or modify it.
|
||||
`instance` | `Object?` | For components, a storage location for the value returned by the `view`. This property is only used internally by Mithril, do not use or modify it.
|
||||
`skip` | `Boolean` | This property is only used internally by Mithril when diffing keyed lists, do not use or modify it.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -110,4 +114,4 @@ When creating libraries that emit vnodes, you should use this module instead of
|
|||
|
||||
Vnodes are supposed to represent the state of the DOM at a certain point in time. Mithril's rendering engine assumes a reused vnode is unchanged, so modifying a vnode that was used in a previous render will result in undefined behavior.
|
||||
|
||||
It is possible to reuse vnodes to prevent a diff, but it's preferable to use the `onbeforeupdate` hook to make your intent clear to other developers (or your future self).
|
||||
It is possible to reuse vnodes to prevent a diff, but it's preferable to use the `onbeforeupdate` hook to make your intent clear to other developers (or your future self).
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ m.mount(document.body, Component)
|
|||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`attrName` | `String` | Yes | The name of the attribute or property whose value will be used
|
||||
`callback` | `any -> Boolean?` | Yes | The callback
|
||||
`callback` | `any -> undefined` | Yes | The callback
|
||||
`thisArg` | `any` | No | An object to bind to the `this` keyword in the callback function
|
||||
**returns** | `Event -> Boolean?` | | An event handler function
|
||||
**returns** | `Event -> undefined` | | An event handler function
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
||||
|
|
@ -134,4 +134,3 @@ var state = {
|
|||
}
|
||||
m("input[type=checkbox]", {onclick: m.withAttr("checked", state.setSelected)})
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ function view() {
|
|||
}))
|
||||
}
|
||||
|
||||
function exit(vnode, done) {
|
||||
function exit(vnode) {
|
||||
vnode.dom.classList.add("exit")
|
||||
setTimeout(done, 1000)
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(resolve, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
function run() {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ var state = {
|
|||
update: function(title) {
|
||||
if (state.editing != null) {
|
||||
state.editing.title = title.trim()
|
||||
if (state.editing.title === "") destroy(state.editing)
|
||||
if (state.editing.title === "") state.destroy(state.editing)
|
||||
state.editing = null
|
||||
}
|
||||
},
|
||||
|
|
@ -104,7 +104,7 @@ var Todos = {
|
|||
m("label", {ondblclick: function() {state.dispatch("edit", [todo])}}, todo.title),
|
||||
m("button.destroy", {onclick: function() {state.dispatch("destroy", [todo])}}),
|
||||
]),
|
||||
m("input.edit", {onupdate: function(vnode) {ui.focus(vnode, todo)}, onkeypress: ui.save, onblur: ui.save})
|
||||
m("input.edit", {onupdate: function(vnode) {ui.focus(vnode, todo)}, onkeyup: ui.save, onblur: ui.save})
|
||||
])
|
||||
}),
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use strict"
|
||||
|
||||
var hyperscript = require("./render/hyperscript")
|
||||
|
||||
hyperscript.trust = require("./render/trust")
|
||||
|
|
|
|||
389
mithril.js
389
mithril.js
|
|
@ -1,7 +1,7 @@
|
|||
new function() {
|
||||
|
||||
;(function() {
|
||||
"use strict"
|
||||
function Vnode(tag, key, attrs0, children, text, dom) {
|
||||
return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false}
|
||||
return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
|
||||
}
|
||||
Vnode.normalize = function(node) {
|
||||
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
|
||||
|
|
@ -16,62 +16,82 @@ Vnode.normalizeChildren = function normalizeChildren(children) {
|
|||
}
|
||||
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
|
||||
var selectorCache = {}
|
||||
var hasOwn = {}.hasOwnProperty
|
||||
function compileSelector(selector) {
|
||||
var match, tag = "div", classes = [], attrs = {}
|
||||
while (match = selectorParser.exec(selector)) {
|
||||
var type = match[1], value = match[2]
|
||||
if (type === "" && value !== "") tag = value
|
||||
else if (type === "#") attrs.id = value
|
||||
else if (type === ".") classes.push(value)
|
||||
else if (match[3][0] === "[") {
|
||||
var attrValue = match[6]
|
||||
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
|
||||
if (match[4] === "class") classes.push(attrValue)
|
||||
else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attrs.className = classes.join(" ")
|
||||
return selectorCache[selector] = {tag: tag, attrs: attrs}
|
||||
}
|
||||
function execSelector(state, attrs, children) {
|
||||
var hasAttrs = false, childList, text
|
||||
var className = attrs.className || attrs.class
|
||||
for (var key in state.attrs) {
|
||||
if (hasOwn.call(state.attrs, key)) {
|
||||
attrs[key] = state.attrs[key]
|
||||
}
|
||||
}
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
if (state.attrs.className != null) {
|
||||
attrs.className = state.attrs.className + " " + className
|
||||
}
|
||||
}
|
||||
for (var key in attrs) {
|
||||
if (hasOwn.call(attrs, key) && key !== "key") {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
|
||||
text = children[0].children
|
||||
} else {
|
||||
childList = children
|
||||
}
|
||||
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
|
||||
}
|
||||
function hyperscript(selector) {
|
||||
if (selector == null || typeof selector !== "string" && selector.view == null) {
|
||||
// Because sloppy mode sucks
|
||||
var attrs = arguments[1], start = 2, children
|
||||
if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
|
||||
throw Error("The selector must be either a string or a component.");
|
||||
}
|
||||
if (typeof selector === "string" && selectorCache[selector] === undefined) {
|
||||
var match, tag, classes = [], attributes = {}
|
||||
while (match = selectorParser.exec(selector)) {
|
||||
var type = match[1], value = match[2]
|
||||
if (type === "" && value !== "") tag = value
|
||||
else if (type === "#") attributes.id = value
|
||||
else if (type === ".") classes.push(value)
|
||||
else if (match[3][0] === "[") {
|
||||
var attrValue = match[6]
|
||||
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
|
||||
if (match[4] === "class") classes.push(attrValue)
|
||||
else attributes[match[4]] = attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attributes.className = classes.join(" ")
|
||||
selectorCache[selector] = function(attrs, children) {
|
||||
var hasAttrs = false, childList, text
|
||||
var className = attrs.className || attrs.class
|
||||
for (var key in attributes) attrs[key] = attributes[key]
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
|
||||
}
|
||||
for (var key in attrs) {
|
||||
if (key !== "key") {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
|
||||
else childList = children
|
||||
return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined)
|
||||
}
|
||||
if (typeof selector === "string") {
|
||||
var cached = selectorCache[selector] || compileSelector(selector)
|
||||
}
|
||||
var attrs, children, childrenIndex
|
||||
if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) {
|
||||
attrs = arguments[1]
|
||||
childrenIndex = 2
|
||||
if (attrs == null) {
|
||||
attrs = {}
|
||||
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
|
||||
attrs = {}
|
||||
start = 1
|
||||
}
|
||||
else childrenIndex = 1
|
||||
if (arguments.length === childrenIndex + 1) {
|
||||
children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
|
||||
}
|
||||
else {
|
||||
if (arguments.length === start + 1) {
|
||||
children = arguments[start]
|
||||
if (!Array.isArray(children)) children = [children]
|
||||
} else {
|
||||
children = []
|
||||
for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i])
|
||||
while (start < arguments.length) children.push(arguments[start++])
|
||||
}
|
||||
var normalized = Vnode.normalizeChildren(children)
|
||||
if (typeof selector === "string") {
|
||||
return execSelector(cached, attrs, normalized)
|
||||
} else {
|
||||
return Vnode(selector, attrs.key, attrs, normalized)
|
||||
}
|
||||
if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children))
|
||||
return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined)
|
||||
}
|
||||
hyperscript.trust = function(html) {
|
||||
if (html == null) html = ""
|
||||
|
|
@ -203,6 +223,7 @@ var buildQueryString = function(object) {
|
|||
else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
|
||||
}
|
||||
}
|
||||
var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
|
||||
var _8 = function($window, Promise) {
|
||||
var callbackCount = 0
|
||||
var oncompletion
|
||||
|
|
@ -238,14 +259,20 @@ var _8 = function($window, Promise) {
|
|||
var promise0 = new Promise(function(resolve, reject) {
|
||||
if (args.method == null) args.method = "GET"
|
||||
args.method = args.method.toUpperCase()
|
||||
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
||||
var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true)
|
||||
if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
|
||||
if (typeof args.deserialize !== "function") args.deserialize = deserialize
|
||||
if (typeof args.extract !== "function") args.extract = extract
|
||||
args.url = interpolate(args.url, args.data)
|
||||
if (useBody) args.data = args.serialize(args.data)
|
||||
else args.url = assemble(args.url, args.data)
|
||||
var xhr = new $window.XMLHttpRequest()
|
||||
var xhr = new $window.XMLHttpRequest(),
|
||||
aborted = false,
|
||||
_abort = xhr.abort
|
||||
xhr.abort = function abort() {
|
||||
aborted = true
|
||||
_abort.call(xhr)
|
||||
}
|
||||
xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
|
||||
if (args.serialize === JSON.stringify && useBody) {
|
||||
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
|
||||
|
|
@ -259,10 +286,12 @@ var _8 = function($window, Promise) {
|
|||
}
|
||||
if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
|
||||
xhr.onreadystatechange = function() {
|
||||
// Don't throw errors on xhr.abort().
|
||||
if(aborted) return
|
||||
if (xhr.readyState === 4) {
|
||||
try {
|
||||
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
|
||||
resolve(cast(args.type, response))
|
||||
}
|
||||
else {
|
||||
|
|
@ -312,7 +341,6 @@ var _8 = function($window, Promise) {
|
|||
var key = tokens[i].slice(1)
|
||||
if (data[key] != null) {
|
||||
url = url.replace(tokens[i], data[key])
|
||||
delete data[key]
|
||||
}
|
||||
}
|
||||
return url
|
||||
|
|
@ -347,37 +375,47 @@ var requestService = _8(window, PromisePolyfill)
|
|||
var coreRenderer = function($window) {
|
||||
var $doc = $window.document
|
||||
var $emptyFragment = $doc.createDocumentFragment()
|
||||
var nameSpace = {
|
||||
svg: "http://www.w3.org/2000/svg",
|
||||
math: "http://www.w3.org/1998/Math/MathML"
|
||||
}
|
||||
var onevent
|
||||
function setEventCallback(callback) {return onevent = callback}
|
||||
function getNameSpace(vnode) {
|
||||
return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
|
||||
}
|
||||
//create
|
||||
function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
|
||||
for (var i = start; i < end; i++) {
|
||||
var vnode = vnodes[i]
|
||||
if (vnode != null) {
|
||||
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
|
||||
createNode(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
}
|
||||
function createNode(vnode, hooks, ns) {
|
||||
function createNode(parent, vnode, hooks, ns, nextSibling) {
|
||||
var tag = vnode.tag
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
if (typeof tag === "string") {
|
||||
vnode.state = {}
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
switch (tag) {
|
||||
case "#": return createText(vnode)
|
||||
case "<": return createHTML(vnode)
|
||||
case "[": return createFragment(vnode, hooks, ns)
|
||||
default: return createElement(vnode, hooks, ns)
|
||||
case "#": return createText(parent, vnode, nextSibling)
|
||||
case "<": return createHTML(parent, vnode, nextSibling)
|
||||
case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
|
||||
default: return createElement(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
else return createComponent(vnode, hooks, ns)
|
||||
else return createComponent(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
function createText(vnode) {
|
||||
return vnode.dom = $doc.createTextNode(vnode.children)
|
||||
function createText(parent, vnode, nextSibling) {
|
||||
vnode.dom = $doc.createTextNode(vnode.children)
|
||||
insertNode(parent, vnode.dom, nextSibling)
|
||||
return vnode.dom
|
||||
}
|
||||
function createHTML(vnode) {
|
||||
function createHTML(parent, vnode, nextSibling) {
|
||||
var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
|
||||
var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
|
||||
var temp = $doc.createElement(parent)
|
||||
var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
|
||||
var temp = $doc.createElement(parent1)
|
||||
temp.innerHTML = vnode.children
|
||||
vnode.dom = temp.firstChild
|
||||
vnode.domSize = temp.childNodes.length
|
||||
|
|
@ -386,9 +424,10 @@ var coreRenderer = function($window) {
|
|||
while (child = temp.firstChild) {
|
||||
fragment.appendChild(child)
|
||||
}
|
||||
insertNode(parent, fragment, nextSibling)
|
||||
return fragment
|
||||
}
|
||||
function createFragment(vnode, hooks, ns) {
|
||||
function createFragment(parent, vnode, hooks, ns, nextSibling) {
|
||||
var fragment = $doc.createDocumentFragment()
|
||||
if (vnode.children != null) {
|
||||
var children = vnode.children
|
||||
|
|
@ -396,16 +435,14 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
vnode.dom = fragment.firstChild
|
||||
vnode.domSize = fragment.childNodes.length
|
||||
insertNode(parent, fragment, nextSibling)
|
||||
return fragment
|
||||
}
|
||||
function createElement(vnode, hooks, ns) {
|
||||
function createElement(parent, vnode, hooks, ns, nextSibling) {
|
||||
var tag = vnode.tag
|
||||
switch (vnode.tag) {
|
||||
case "svg": ns = "http://www.w3.org/2000/svg"; break
|
||||
case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
|
||||
}
|
||||
var attrs2 = vnode.attrs
|
||||
var is = attrs2 && attrs2.is
|
||||
ns = getNameSpace(vnode) || ns
|
||||
var element = ns ?
|
||||
is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
|
||||
is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
|
||||
|
|
@ -413,6 +450,7 @@ var coreRenderer = function($window) {
|
|||
if (attrs2 != null) {
|
||||
setAttrs(vnode, attrs2, ns)
|
||||
}
|
||||
insertNode(parent, element, nextSibling)
|
||||
if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
|
||||
setContentEditable(vnode)
|
||||
}
|
||||
|
|
@ -429,19 +467,34 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
return element
|
||||
}
|
||||
function createComponent(vnode, hooks, ns) {
|
||||
vnode.state = Object.create(vnode.tag)
|
||||
var view = vnode.tag.view
|
||||
if (view.reentrantLock != null) return $emptyFragment
|
||||
view.reentrantLock = true
|
||||
initLifecycle(vnode.tag, vnode, hooks)
|
||||
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
|
||||
view.reentrantLock = null
|
||||
function initComponent(vnode, hooks) {
|
||||
var sentinel
|
||||
if (typeof vnode.tag.view === "function") {
|
||||
vnode.state = Object.create(vnode.tag)
|
||||
sentinel = vnode.state.view
|
||||
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
|
||||
sentinel.$$reentrantLock$$ = true
|
||||
} else {
|
||||
vnode.state = void 0
|
||||
sentinel = vnode.tag
|
||||
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
|
||||
sentinel.$$reentrantLock$$ = true
|
||||
vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
|
||||
}
|
||||
vnode._state = vnode.state
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
initLifecycle(vnode._state, vnode, hooks)
|
||||
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
||||
sentinel.$$reentrantLock$$ = null
|
||||
}
|
||||
function createComponent(parent, vnode, hooks, ns, nextSibling) {
|
||||
initComponent(vnode, hooks)
|
||||
if (vnode.instance != null) {
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments")
|
||||
var element = createNode(vnode.instance, hooks, ns)
|
||||
var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
||||
vnode.dom = vnode.instance.dom
|
||||
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
|
||||
insertNode(parent, element, nextSibling)
|
||||
return element
|
||||
}
|
||||
else {
|
||||
|
|
@ -450,9 +503,9 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
}
|
||||
//update
|
||||
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
||||
function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
|
||||
if (old === vnodes || old == null && vnodes == null) return
|
||||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||
else {
|
||||
if (old.length === vnodes.length) {
|
||||
|
|
@ -466,15 +519,18 @@ var coreRenderer = function($window) {
|
|||
if (isUnkeyed) {
|
||||
for (var i = 0; i < old.length; i++) {
|
||||
if (old[i] === vnodes[i]) continue
|
||||
else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
|
||||
else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
|
||||
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
var recycling = isRecyclable(old, vnodes)
|
||||
if (recycling) old = old.concat(old.pool)
|
||||
recycling = recycling || isRecyclable(old, vnodes)
|
||||
if (recycling) {
|
||||
var pool = old.pool
|
||||
old = old.concat(old.pool)
|
||||
}
|
||||
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
var o = old[oldStart], v = vnodes[start]
|
||||
|
|
@ -482,8 +538,9 @@ var coreRenderer = function($window) {
|
|||
else if (o == null) oldStart++
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
oldStart++, start++
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
|
||||
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
|
||||
}
|
||||
else {
|
||||
|
|
@ -492,7 +549,8 @@ var coreRenderer = function($window) {
|
|||
else if (o == null) oldEnd--
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
|
||||
if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
|
||||
oldEnd--, start++
|
||||
}
|
||||
|
|
@ -505,7 +563,8 @@ var coreRenderer = function($window) {
|
|||
else if (o == null) oldEnd--
|
||||
else if (v == null) end--
|
||||
else if (o.key === v.key) {
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
|
||||
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
|
||||
if (o.dom != null) nextSibling = o.dom
|
||||
oldEnd--, end--
|
||||
|
|
@ -516,14 +575,14 @@ var coreRenderer = function($window) {
|
|||
var oldIndex = map[v.key]
|
||||
if (oldIndex != null) {
|
||||
var movable = old[oldIndex]
|
||||
var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
insertNode(parent, toFragment(movable), nextSibling)
|
||||
old[oldIndex].skip = true
|
||||
if (movable.dom != null) nextSibling = movable.dom
|
||||
}
|
||||
else {
|
||||
var dom = createNode(v, hooks, undefined)
|
||||
insertNode(parent, dom, nextSibling)
|
||||
var dom = createNode(parent, v, hooks, ns, nextSibling)
|
||||
nextSibling = dom
|
||||
}
|
||||
}
|
||||
|
|
@ -539,24 +598,29 @@ var coreRenderer = function($window) {
|
|||
var oldTag = old.tag, tag = vnode.tag
|
||||
if (oldTag === tag) {
|
||||
vnode.state = old.state
|
||||
vnode._state = old._state
|
||||
vnode.events = old.events
|
||||
if (shouldUpdate(vnode, old)) return
|
||||
if (vnode.attrs != null) {
|
||||
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
|
||||
}
|
||||
if (!recycling && shouldNotUpdate(vnode, old)) return
|
||||
if (typeof oldTag === "string") {
|
||||
if (vnode.attrs != null) {
|
||||
if (recycling) {
|
||||
vnode.state = {}
|
||||
initLifecycle(vnode.attrs, vnode, hooks)
|
||||
}
|
||||
else updateLifecycle(vnode.attrs, vnode, hooks)
|
||||
}
|
||||
switch (oldTag) {
|
||||
case "#": updateText(old, vnode); break
|
||||
case "<": updateHTML(parent, old, vnode, nextSibling); break
|
||||
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
|
||||
default: updateElement(old, vnode, hooks, ns)
|
||||
case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
|
||||
default: updateElement(old, vnode, recycling, hooks, ns)
|
||||
}
|
||||
}
|
||||
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
|
||||
}
|
||||
else {
|
||||
removeNode(old, null)
|
||||
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
|
||||
createNode(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
function updateText(old, vnode) {
|
||||
|
|
@ -568,12 +632,12 @@ var coreRenderer = function($window) {
|
|||
function updateHTML(parent, old, vnode, nextSibling) {
|
||||
if (old.children !== vnode.children) {
|
||||
toFragment(old)
|
||||
insertNode(parent, createHTML(vnode), nextSibling)
|
||||
createHTML(parent, vnode, nextSibling)
|
||||
}
|
||||
else vnode.dom = old.dom, vnode.domSize = old.domSize
|
||||
}
|
||||
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
|
||||
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
|
||||
function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
|
||||
updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
|
||||
var domSize = 0, children = vnode.children
|
||||
vnode.dom = null
|
||||
if (children != null) {
|
||||
|
|
@ -587,12 +651,9 @@ var coreRenderer = function($window) {
|
|||
if (domSize !== 1) vnode.domSize = domSize
|
||||
}
|
||||
}
|
||||
function updateElement(old, vnode, hooks, ns) {
|
||||
function updateElement(old, vnode, recycling, hooks, ns) {
|
||||
var element = vnode.dom = old.dom
|
||||
switch (vnode.tag) {
|
||||
case "svg": ns = "http://www.w3.org/2000/svg"; break
|
||||
case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
|
||||
}
|
||||
ns = getNameSpace(vnode) || ns
|
||||
if (vnode.tag === "textarea") {
|
||||
if (vnode.attrs == null) vnode.attrs = {}
|
||||
if (vnode.text != null) {
|
||||
|
|
@ -610,14 +671,20 @@ var coreRenderer = function($window) {
|
|||
else {
|
||||
if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
|
||||
if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
|
||||
updateNodes(element, old.children, vnode.children, hooks, null, ns)
|
||||
updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
|
||||
}
|
||||
}
|
||||
function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
|
||||
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
|
||||
updateLifecycle(vnode.tag, vnode, hooks, recycling)
|
||||
if (recycling) {
|
||||
initComponent(vnode, hooks)
|
||||
} else {
|
||||
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
||||
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
|
||||
updateLifecycle(vnode._state, vnode, hooks)
|
||||
}
|
||||
if (vnode.instance != null) {
|
||||
if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling)
|
||||
if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
||||
else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
|
||||
vnode.dom = vnode.instance.dom
|
||||
vnode.domSize = vnode.instance.domSize
|
||||
|
|
@ -697,15 +764,15 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
function removeNode(vnode, context) {
|
||||
var expected = 1, called = 0
|
||||
if (vnode.attrs && vnode.attrs.onbeforeremove) {
|
||||
if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
|
||||
var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
|
||||
if (result != null && typeof result.then === "function") {
|
||||
expected++
|
||||
result.then(continuation, continuation)
|
||||
}
|
||||
}
|
||||
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
|
||||
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
|
||||
var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
|
||||
if (result != null && typeof result.then === "function") {
|
||||
expected++
|
||||
result.then(continuation, continuation)
|
||||
|
|
@ -737,8 +804,8 @@ var coreRenderer = function($window) {
|
|||
if (parent != null) parent.removeChild(node)
|
||||
}
|
||||
function onremove(vnode) {
|
||||
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
|
||||
if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
|
||||
if (vnode.instance != null) onremove(vnode.instance)
|
||||
else {
|
||||
var children = vnode.children
|
||||
|
|
@ -766,12 +833,26 @@ var coreRenderer = function($window) {
|
|||
else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
|
||||
else if (key2 === "style") updateStyle(element, old, value)
|
||||
else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
|
||||
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
||||
if (vnode.tag === "input" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "select" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return
|
||||
if (key2 === "value") {
|
||||
var normalized0 = "" + value // eslint-disable-line no-implicit-coercion
|
||||
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
||||
if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
|
||||
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "select") {
|
||||
if (value === null) {
|
||||
if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return
|
||||
} else {
|
||||
if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return
|
||||
}
|
||||
}
|
||||
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return
|
||||
}
|
||||
// If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur.
|
||||
if (vnode.tag === "input" && key2 === "type") {
|
||||
element.setAttribute(key2, value)
|
||||
return
|
||||
}
|
||||
element[key2] = value
|
||||
}
|
||||
else {
|
||||
|
|
@ -862,14 +943,13 @@ var coreRenderer = function($window) {
|
|||
if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
|
||||
if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
|
||||
}
|
||||
function updateLifecycle(source, vnode, hooks, recycling) {
|
||||
if (recycling) initLifecycle(source, vnode, hooks)
|
||||
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
|
||||
function updateLifecycle(source, vnode, hooks) {
|
||||
if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
|
||||
}
|
||||
function shouldUpdate(vnode, old) {
|
||||
function shouldNotUpdate(vnode, old) {
|
||||
var forceVnodeUpdate, forceComponentUpdate
|
||||
if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
|
||||
vnode.dom = old.dom
|
||||
vnode.domSize = old.domSize
|
||||
|
|
@ -882,10 +962,11 @@ var coreRenderer = function($window) {
|
|||
if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
|
||||
var hooks = []
|
||||
var active = $doc.activeElement
|
||||
var namespace = dom.namespaceURI
|
||||
// First time0 rendering into a node clears it out
|
||||
if (dom.vnodes == null) dom.textContent = ""
|
||||
if (!Array.isArray(vnodes)) vnodes = [vnodes]
|
||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
|
||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
|
||||
dom.vnodes = vnodes
|
||||
for (var i = 0; i < hooks.length; i++) hooks[i]()
|
||||
if ($doc.activeElement !== active) active.focus()
|
||||
|
|
@ -915,9 +996,9 @@ function throttle(callback) {
|
|||
var _11 = function($window) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
if (e.redraw === false) e.redraw = undefined
|
||||
else redraw()
|
||||
})
|
||||
|
||||
var callbacks = []
|
||||
function subscribe(key1, callback) {
|
||||
unsubscribe(key1)
|
||||
|
|
@ -927,11 +1008,11 @@ var _11 = function($window) {
|
|||
var index = callbacks.indexOf(key1)
|
||||
if (index > -1) callbacks.splice(index, 2)
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
function redraw() {
|
||||
for (var i = 1; i < callbacks.length; i += 2) {
|
||||
callbacks[i]()
|
||||
}
|
||||
}
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
var redrawService = _11(window)
|
||||
|
|
@ -944,7 +1025,7 @@ var _16 = function(redrawService0) {
|
|||
return
|
||||
}
|
||||
|
||||
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
|
||||
|
||||
var run0 = function() {
|
||||
redrawService0.render(root, Vnode(component))
|
||||
|
|
@ -1055,7 +1136,6 @@ var coreRouter = function($window) {
|
|||
var path = router.getPath()
|
||||
var params = {}
|
||||
var pathname = parsePath(path, params, params)
|
||||
|
||||
var state = $window.history.state
|
||||
if (state != null) {
|
||||
for (var k in state) params[k] = state[k]
|
||||
|
|
@ -1076,12 +1156,10 @@ var coreRouter = function($window) {
|
|||
}
|
||||
reject(path, params)
|
||||
}
|
||||
|
||||
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
|
||||
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
|
||||
resolveRoute()
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
var _20 = function($window, redrawService0) {
|
||||
|
|
@ -1093,17 +1171,19 @@ var _20 = function($window, redrawService0) {
|
|||
var run1 = function() {
|
||||
if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
|
||||
}
|
||||
var bail = function() {
|
||||
routeService.setPath(defaultRoute, null, {replace: true})
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
}
|
||||
routeService.defineRoutes(routes, function(payload, params, path) {
|
||||
var update = lastUpdate = function(routeResolver, comp) {
|
||||
if (update !== lastUpdate) return
|
||||
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, lastUpdate = null
|
||||
component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
|
||||
attrs3 = params, currentPath = path, lastUpdate = null
|
||||
render1 = (routeResolver.render || identity).bind(routeResolver)
|
||||
run1()
|
||||
}
|
||||
if (payload.view) update({}, payload)
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
if (payload.onmatch) {
|
||||
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
|
||||
|
|
@ -1116,7 +1196,10 @@ var _20 = function($window, redrawService0) {
|
|||
redrawService0.subscribe(root, run1)
|
||||
}
|
||||
route.set = function(path, data, options) {
|
||||
if (lastUpdate != null) options = {replace: true}
|
||||
if (lastUpdate != null) {
|
||||
options = options || {}
|
||||
options.replace = true
|
||||
}
|
||||
lastUpdate = null
|
||||
routeService.setPath(path, data, options)
|
||||
}
|
||||
|
|
@ -1133,12 +1216,16 @@ var _20 = function($window, redrawService0) {
|
|||
route.set(href, undefined, undefined)
|
||||
}
|
||||
}
|
||||
route.param = function(key3) {
|
||||
if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3]
|
||||
return attrs3
|
||||
}
|
||||
return route
|
||||
}
|
||||
m.route = _20(window, redrawService)
|
||||
m.withAttr = function(attrName, callback1, context) {
|
||||
return function(e) {
|
||||
return callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
|
||||
callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
|
||||
}
|
||||
}
|
||||
var _28 = coreRenderer(window)
|
||||
|
|
@ -1148,8 +1235,8 @@ m.request = requestService.request
|
|||
m.jsonp = requestService.jsonp
|
||||
m.parseQueryString = parseQueryString
|
||||
m.buildQueryString = buildQueryString
|
||||
m.version = "1.0.0-rc.7"
|
||||
m.version = "1.1.1"
|
||||
m.vnode = Vnode
|
||||
if (typeof module !== "undefined") module["exports"] = m
|
||||
else window.m = m
|
||||
}
|
||||
}());
|
||||
86
mithril.min.js
vendored
86
mithril.min.js
vendored
|
|
@ -1,42 +1,44 @@
|
|||
new function(){function w(a,c,k,d,h,m){return{tag:a,key:c,attrs:k,children:d,text:h,dom:m,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function B(a){if(null==a||"string"!==typeof a&&null==a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===G[a]){for(var c,k,d=[],h={};c=N.exec(a);){var m=c[1],q=c[2];""===m&&""!==q?k=q:"#"===m?h.id=q:"."===m?d.push(q):"["===c[3][0]&&((m=c[6])&&(m=m.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),
|
||||
"class"===c[4]?d.push(m):h[c[4]]=m||!0)}0<d.length&&(h.className=d.join(" "));G[a]=function(a,c){var m=!1,b,t,d=a.className||a["class"],l;for(l in h)a[l]=h[l];void 0!==d&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=d),void 0!==h.className&&(a.className=h.className+" "+d));for(l in a)if("key"!==l){m=!0;break}Array.isArray(c)&&1==c.length&&null!=c[0]&&"#"===c[0].tag?t=c[0].children:b=c;return w(k||"div",a.key,m?a:void 0,b,t,void 0)}}var l;null==arguments[1]||"object"===typeof arguments[1]&&
|
||||
void 0===arguments[1].tag&&!Array.isArray(arguments[1])?(l=arguments[1],d=2):d=1;if(arguments.length===d+1)c=Array.isArray(arguments[d])?arguments[d]:[arguments[d]];else for(c=[];d<arguments.length;d++)c.push(arguments[d]);return"string"===typeof a?G[a](l||{},w.normalizeChildren(c)):w(a,l&&l.key,l||{},w.normalizeChildren(c),void 0,void 0)}function O(a){var c=0,k=null,d="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var h=Date.now();0===c||16<=h-c?(c=h,
|
||||
a()):null===k&&(k=d(function(){k=null;a();c=Date.now()},16-(h-c)))}}w.normalize=function(a){return Array.isArray(a)?w("[",void 0,void 0,w.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?w("#",void 0,void 0,!1===a?"":a,void 0,void 0):a};w.normalizeChildren=function(a){for(var c=0;c<a.length;c++)a[c]=w.normalize(a[c]);return a};var N=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,G={};B.trust=function(a){null==a&&(a="");return w("<",void 0,void 0,a,void 0,
|
||||
void 0)};B.fragment=function(a,c){return w("[",a.key,a,w.normalizeChildren(c),void 0,void 0)};var x=function(a){function c(a,b){return function v(c){var q;try{if(!b||null==c||"object"!==typeof c&&"function"!==typeof c||"function"!==typeof(q=c.then))p(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",c);for(var d=0;d<a.length;d++)a[d](c);h.length=0;m.length=0;t.state=b;t.retry=function(){v(c)}});else{if(c===d)throw new TypeError("Promise can't be resolved w/ itself");
|
||||
k(q.bind(c))}}catch(P){l(P)}}}function k(a){function b(b){return function(a){0<c++||b(a)}}var c=0,d=b(l);try{a(b(q),d)}catch(D){d(D)}}if(!(this instanceof x))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var d=this,h=[],m=[],q=c(h,!0),l=c(m,!1),t=d._instance={resolvers:h,rejectors:m},p="function"===typeof setImmediate?setImmediate:setTimeout;k(a)};x.prototype.then=function(a,c){function k(a,c,k,q){c.push(function(b){if("function"!==
|
||||
typeof a)k(b);else try{h(a(b))}catch(r){m&&m(r)}});"function"===typeof d.retry&&q===d.state&&d.retry()}var d=this._instance,h,m,q=new x(function(a,c){h=a;m=c});k(a,d.resolvers,h,!0);k(c,d.rejectors,m,!1);return q};x.prototype["catch"]=function(a){return this.then(null,a)};x.resolve=function(a){return a instanceof x?a:new x(function(c){c(a)})};x.reject=function(a){return new x(function(c,k){k(a)})};x.all=function(a){return new x(function(c,k){var d=a.length,h=0,m=[];if(0===a.length)c([]);else for(var q=
|
||||
0;q<a.length;q++)(function(l){function t(a){h++;m[l]=a;h===d&&c(m)}null==a[l]||"object"!==typeof a[l]&&"function"!==typeof a[l]||"function"!==typeof a[l].then?t(a[l]):a[l].then(t,k)})(q)})};x.race=function(a){return new x(function(c,k){for(var d=0;d<a.length;d++)a[d].then(c,k)})};"undefined"!==typeof window?("undefined"===typeof window.Promise&&(window.Promise=x),x=window.Promise):"undefined"!==typeof global&&("undefined"===typeof global.Promise&&(global.Promise=x),x=global.Promise);var E=function(a){function c(a,
|
||||
d){if(Array.isArray(d))for(var h=0;h<d.length;h++)c(a+"["+h+"]",d[h]);else if("[object Object]"===Object.prototype.toString.call(d))for(h in d)c(a+"["+h+"]",d[h]);else k.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var k=[],d;for(d in a)c(d,a[d]);return k.join("&")},I=function(a,c){function k(){function b(){0===--a&&"function"===typeof z&&z()}var a=0;return function D(c){var d=c.then;c.then=function(){a++;
|
||||
var h=d.apply(c,arguments);h.then(b,function(c){b();if(0===a)throw c;});return D(h)};return c}}function d(b,a){if("string"===typeof b){var c=b;b=a||{};null==b.url&&(b.url=c)}return b}function h(b,a){if(null==a)return b;for(var c=b.match(/:[^\/]+/gi)||[],d=0;d<c.length;d++){var h=c[d].slice(1);null!=a[h]&&(b=b.replace(c[d],a[h]),delete a[h])}return b}function m(b,a){var c=E(a);if(""!==c){var d=0>b.indexOf("?")?"?":"&";b+=d+c}return b}function q(a){try{return""!==a?JSON.parse(a):null}catch(r){throw Error(a);
|
||||
}}function l(a){return a.responseText}function t(a,c){if("function"===typeof a)if(Array.isArray(c))for(var b=0;b<c.length;b++)c[b]=new a(c[b]);else return new a(c);return c}var p=0,z;return{request:function(b,p){var z=k();b=d(b,p);var r=new c(function(c,d){null==b.method&&(b.method="GET");b.method=b.method.toUpperCase();var k="boolean"===typeof b.useBody?b.useBody:"GET"!==b.method&&"TRACE"!==b.method;"function"!==typeof b.serialize&&(b.serialize="undefined"!==typeof FormData&&b.data instanceof FormData?
|
||||
function(a){return a}:JSON.stringify);"function"!==typeof b.deserialize&&(b.deserialize=q);"function"!==typeof b.extract&&(b.extract=l);b.url=h(b.url,b.data);k?b.data=b.serialize(b.data):b.url=m(b.url,b.data);var n=new a.XMLHttpRequest;n.open(b.method,b.url,"boolean"===typeof b.async?b.async:!0,"string"===typeof b.user?b.user:void 0,"string"===typeof b.password?b.password:void 0);b.serialize===JSON.stringify&&k&&n.setRequestHeader("Content-Type","application/json; charset=utf-8");b.deserialize===
|
||||
q&&n.setRequestHeader("Accept","application/json, text/*");b.withCredentials&&(n.withCredentials=b.withCredentials);for(var p in b.headers)({}).hasOwnProperty.call(b.headers,p)&&n.setRequestHeader(p,b.headers[p]);"function"===typeof b.config&&(n=b.config(n,b)||n);n.onreadystatechange=function(){if(4===n.readyState)try{var a=b.extract!==l?b.extract(n,b):b.deserialize(b.extract(n,b));if(200<=n.status&&300>n.status||304===n.status)c(t(b.type,a));else{var g=Error(n.responseText),e;for(e in a)g[e]=a[e];
|
||||
d(g)}}catch(f){d(f)}};k&&null!=b.data?n.send(b.data):n.send()});return!0===b.background?r:z(r)},jsonp:function(b,l){var q=k();b=d(b,l);var z=new c(function(c,d){var k=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+p++,l=a.document.createElement("script");a[k]=function(d){l.parentNode.removeChild(l);c(t(b.type,d));delete a[k]};l.onerror=function(){l.parentNode.removeChild(l);d(Error("JSONP request failed"));delete a[k]};null==b.data&&(b.data={});b.url=h(b.url,b.data);b.data[b.callbackKey||
|
||||
"callback"]=k;l.src=m(b.url,b.data);a.document.documentElement.appendChild(l)});return!0===b.background?z:q(z)},setCompletionCallback:function(a){z=a}}}(window,x),M=function(a){function c(g,e,a,b,c,d,h){for(;a<b;a++){var f=e[a];null!=f&&t(g,k(f,c,h),d)}}function k(g,e,a){var b=g.tag;null!=g.attrs&&B(g.attrs,g,e);if("string"===typeof b)switch(b){case "#":return g.dom=n.createTextNode(g.children);case "<":return d(g);case "[":var f=n.createDocumentFragment();null!=g.children&&(b=g.children,c(f,b,0,
|
||||
b.length,e,null,a));g.dom=f.firstChild;g.domSize=f.childNodes.length;return f;default:var h=g.tag;switch(g.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var l=(b=g.attrs)&&b.is,h=a?l?n.createElementNS(a,h,{is:l}):n.createElementNS(a,h):l?n.createElement(h,{is:l}):n.createElement(h);g.dom=h;if(null!=b)for(f in l=a,b)v(g,f,null,b[f],l);null!=g.attrs&&null!=g.attrs.contenteditable?p(g):(null!=g.text&&(""!==g.text?h.textContent=g.text:g.children=
|
||||
[w("#",void 0,void 0,g.text,void 0,void 0)]),null!=g.children&&(f=g.children,c(h,f,0,f.length,e,null,a),e=g.attrs,"select"===g.tag&&null!=e&&("value"in e&&v(g,"value",null,e.value,void 0),"selectedIndex"in e&&v(g,"selectedIndex",null,e.selectedIndex,void 0))));return h}else{g.state=Object.create(g.tag);f=g.tag.view;if(null!=f.reentrantLock)g=L;else if(f.reentrantLock=!0,B(g.tag,g,e),g.instance=w.normalize(f.call(g.state,g)),f.reentrantLock=null,null!=g.instance){if(g.instance===g)throw Error("A view cannot return the vnode it received as arguments");
|
||||
e=k(g.instance,e,a);g.dom=g.instance.dom;g.domSize=null!=g.dom?g.instance.domSize:0;g=e}else g.domSize=0,g=L;return g}}function d(g){var e={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(g.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",e=n.createElement(e);e.innerHTML=g.children;g.dom=e.firstChild;g.domSize=e.childNodes.length;g=n.createDocumentFragment();for(var a;a=e.firstChild;)g.appendChild(a);return g}function h(g,e,a,b,
|
||||
d,h){if(e!==a&&(null!=e||null!=a))if(null==e)c(g,a,0,a.length,b,d,void 0);else if(null==a)z(e,0,e.length,a);else{if(e.length===a.length){for(var f=!1,u=0;u<a.length;u++)if(null!=a[u]&&null!=e[u]){f=null==a[u].key&&null==e[u].key;break}if(f){for(u=0;u<e.length;u++)e[u]!==a[u]&&(null==e[u]&&null!=a[u]?t(g,k(a[u],b,h),l(e,u+1,d)):null==a[u]?z(e,u,u+1,a):m(g,e[u],a[u],b,l(e,u+1,d),!1,h));return}}a:{if(null!=e.pool&&Math.abs(e.pool.length-a.length)<=Math.abs(e.length-a.length)&&(f=a[0]&&a[0].children&&
|
||||
a[0].children.length||0,Math.abs((e.pool[0]&&e.pool[0].children&&e.pool[0].children.length||0)-f)<=Math.abs((e[0]&&e[0].children&&e[0].children.length||0)-f))){f=!0;break a}f=!1}f&&(e=e.concat(e.pool));for(var n=u=0,p=e.length-1,r=a.length-1,A;p>=u&&r>=n;){var y=e[u],v=a[n];if(y!==v||f)if(null==y)u++;else if(null==v)n++;else if(y.key===v.key)u++,n++,m(g,y,v,b,l(e,u,d),f,h),f&&y.tag===v.tag&&t(g,q(y),d);else if(y=e[p],y!==v||f)if(null==y)p--;else if(null==v)n++;else if(y.key===v.key)m(g,y,v,b,l(e,
|
||||
p+1,d),f,h),(f||n<r)&&t(g,q(y),l(e,u,d)),p--,n++;else break;else p--,n++;else u++,n++}for(;p>=u&&r>=n;){y=e[p];v=a[r];if(y!==v||f)if(null==y)p--;else{if(null!=v)if(y.key===v.key)m(g,y,v,b,l(e,p+1,d),f,h),f&&y.tag===v.tag&&t(g,q(y),d),null!=y.dom&&(d=y.dom),p--;else{if(!A){A=e;var y=p,C={},w;for(w=0;w<y;w++){var x=A[w];null!=x&&(x=x.key,null!=x&&(C[x]=w))}A=C}null!=v&&(y=A[v.key],null!=y?(C=e[y],m(g,C,v,b,l(e,p+1,d),f,h),t(g,q(C),d),e[y].skip=!0,null!=C.dom&&(d=C.dom)):(v=k(v,b,void 0),t(g,v,d),d=
|
||||
v))}r--}else p--,r--;if(r<n)break}c(g,a,n,r+1,b,d,h);z(e,u,p+1,a)}}function m(a,e,f,c,l,n,r){var g=e.tag;if(g===f.tag){f.state=e.state;f.events=e.events;var u;var z;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(u=f.attrs.onbeforeupdate.call(f.state,f,e));"string"!==typeof f.tag&&"function"===typeof f.tag.onbeforeupdate&&(z=f.tag.onbeforeupdate.call(f.state,f,e));void 0===u&&void 0===z||u||z?u=!1:(f.dom=e.dom,f.domSize=e.domSize,f.instance=e.instance,u=!0);if(!u)if(null!=f.attrs&&K(f.attrs,
|
||||
f,c,n),"string"===typeof g)switch(g){case "#":e.children.toString()!==f.children.toString()&&(e.dom.nodeValue=f.children);f.dom=e.dom;break;case "<":e.children!==f.children?(q(e),t(a,d(f),l)):(f.dom=e.dom,f.domSize=e.domSize);break;case "[":h(a,e.children,f.children,c,l,r);e=0;c=f.children;f.dom=null;if(null!=c){for(var A=0;A<c.length;A++)a=c[A],null!=a&&null!=a.dom&&(null==f.dom&&(f.dom=a.dom),e+=a.domSize||1);1!==e&&(f.domSize=e)}break;default:a=r;l=f.dom=e.dom;switch(f.tag){case "svg":a="http://www.w3.org/2000/svg";
|
||||
break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===f.tag&&(null==f.attrs&&(f.attrs={}),null!=f.text&&(f.attrs.value=f.text,f.text=void 0));n=e.attrs;r=f.attrs;g=a;if(null!=r)for(A in r)v(f,A,n&&n[A],r[A],g);if(null!=n)for(A in n)null!=r&&A in r||("className"===A&&(A="class"),"o"!==A[0]||"n"!==A[1]||D(A)?"key"!==A&&f.dom.removeAttribute(A):x(f,A,void 0));null!=f.attrs&&null!=f.attrs.contenteditable?p(f):null!=e.text&&null!=f.text&&""!==f.text?e.text.toString()!==f.text.toString()&&
|
||||
(e.dom.firstChild.nodeValue=f.text):(null!=e.text&&(e.children=[w("#",void 0,void 0,e.text,void 0,e.dom.firstChild)]),null!=f.text&&(f.children=[w("#",void 0,void 0,f.text,void 0,void 0)]),h(l,e.children,f.children,c,null,a))}else f.instance=w.normalize(f.tag.view.call(f.state,f)),K(f.tag,f,c,n),null!=f.instance?(null==e.instance?t(a,k(f.instance,c,r),l):m(a,e.instance,f.instance,c,l,n,r),f.dom=f.instance.dom,f.domSize=f.instance.domSize):null!=e.instance?(b(e.instance,null),f.dom=void 0,f.domSize=
|
||||
0):(f.dom=e.dom,f.domSize=e.domSize)}else b(e,null),t(a,k(f,c,r),l)}function q(a){var g=a.domSize;if(null!=g||null==a.dom){var b=n.createDocumentFragment();if(0<g){for(a=a.dom;--g;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}function l(a,e,b){for(;e<a.length;e++)if(null!=a[e]&&null!=a[e].dom)return a[e].dom;return b}function t(a,e,b){b&&b.parentNode?a.insertBefore(e,b):a.appendChild(e)}function p(a){var e=a.children;if(null!=e&&1===e.length&&"<"===e[0].tag)e=
|
||||
e[0].children,a.dom.innerHTML!==e&&(a.dom.innerHTML=e);else if(null!=a.text||null!=e&&0!==e.length)throw Error("Child node of a contenteditable must be trusted");}function z(a,e,f,c){for(;e<f;e++){var g=a[e];null!=g&&(g.skip?g.skip=!1:b(g,c))}}function b(a,e){function b(){if(++c===g&&(r(a),a.dom)){var b=a.domSize||1;if(1<b)for(var f=a.dom;--b;){var d=f.nextSibling,h=d.parentNode;null!=h&&h.removeChild(d)}b=a.dom;f=b.parentNode;null!=f&&f.removeChild(b);if(b=null!=e&&null==a.domSize)b=a.attrs,b=!(null!=
|
||||
b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(e.pool?e.pool.push(a):e.pool=[a])}}var g=1,c=0;if(a.attrs&&a.attrs.onbeforeremove){var d=a.attrs.onbeforeremove.call(a.state,a);null!=d&&"function"===typeof d.then&&(g++,d.then(b,b))}"string"!==typeof a.tag&&a.tag.onbeforeremove&&(d=a.tag.onbeforeremove.call(a.state,a),null!=d&&"function"===typeof d.then&&(g++,d.then(b,b)));b()}function r(a){a.attrs&&a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==
|
||||
typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);if(null!=a.instance)r(a.instance);else if(a=a.children,Array.isArray(a))for(var b=0;b<a.length;b++){var g=a[b];null!=g&&r(g)}}function v(a,b,c,d,h){var e=a.dom;if("key"!==b&&"is"!==b&&(c!==d||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&&a.dom===n.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!D(b)){var g=b.indexOf(":");if(-1<g&&"xlink"===b.substr(0,g))e.setAttributeNS("http://www.w3.org/1999/xlink",b.slice(g+
|
||||
1),d);else if("o"===b[0]&&"n"===b[1]&&"function"===typeof d)x(a,b,d);else if("style"===b)if(a=c,a===d&&(e.style.cssText="",a=null),null==d)e.style.cssText="";else if("string"===typeof d)e.style.cssText=d;else{"string"===typeof a&&(e.style.cssText="");for(var f in d)e.style[f]=d[f];if(null!=a&&"string"!==typeof a)for(f in a)f in d||(e.style[f]="")}else b in e&&"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b&&void 0===h&&!(a.attrs.is||-1<a.tag.indexOf("-"))?"input"===a.tag&&"value"===
|
||||
b&&a.dom.value===d&&a.dom===n.activeElement||"select"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===n.activeElement||"option"===a.tag&&"value"===b&&a.dom.value===d||(e[b]=d):"boolean"===typeof d?d?e.setAttribute(b,""):e.removeAttribute(b):e.setAttribute("className"===b?"class":b,d)}}function D(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function x(a,b,d){var e=a.dom,c="function"!==typeof H?d:function(a){var b=d.call(e,a);H.call(e,
|
||||
a);return b};if(b in e)e[b]="function"===typeof d?c:null;else{var f=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==c&&(null!=a.events[b]&&e.removeEventListener(f,a.events[b],!1),"function"===typeof d&&(a.events[b]=c,e.addEventListener(f,a.events[b],!1)))}}function B(a,b,d){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,b))}function K(a,b,d,c){c?B(a,b,d):"function"===typeof a.onupdate&&d.push(a.onupdate.bind(b.state,
|
||||
b))}var n=a.document,L=n.createDocumentFragment(),H;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var d=[],e=n.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);h(a,a.vnodes,w.normalizeChildren(b),d,null,void 0);a.vnodes=b;for(var c=0;c<d.length;c++)d[c]();n.activeElement!==e&&e.focus()},setEventCallback:function(a){return H=a}}},F=function(a){function c(a){a=d.indexOf(a);-1<a&&d.splice(a,
|
||||
2)}function k(){for(var a=1;a<d.length;a+=2)d[a]()}a=M(a);a.setEventCallback(function(a){!1!==a.redraw&&k()});var d=[];return{subscribe:function(a,k){c(a);d.push(a,O(k))},unsubscribe:c,redraw:k,render:a.render}}(window);I.setCompletionCallback(F.redraw);B.mount=function(a){return function(c,k){if(null===k)a.render(c,[]),a.unsubscribe(c);else{if(null==k.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c,w(k))});a.redraw()}}}(F);var Q=
|
||||
x,J=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},k={},d=0;d<a.length;d++){var h=a[d].split("="),m=decodeURIComponent(h[0]),h=2===h.length?decodeURIComponent(h[1]):"";"true"===h?h=!0:"false"===h&&(h=!1);var q=m.split(/\]\[?|\[/),l=c;-1<m.indexOf("[")&&q.pop();for(var t=0;t<q.length;t++){var m=q[t],p=q[t+1],p=""==p||!isNaN(parseInt(p,10)),z=t===q.length-1;""===m&&(m=q.slice(0,t).join(),null==k[m]&&(k[m]=0),m=k[m]++);null==l[m]&&(l[m]=z?h:p?[]:
|
||||
{});l=l[m]}}return c},R=function(a){function c(d){var c=a.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==c[0]&&(c="/"+c);return c}function k(a){return function(){null==q&&(q=m(function(){q=null;a()}))}}function d(a,d,c){var b=a.indexOf("?"),h=a.indexOf("#"),l=-1<b?b:-1<h?h:a.length;if(-1<b){var b=J(a.slice(b+1,-1<h?h:a.length)),k;for(k in b)d[k]=b[k]}if(-1<h)for(k in d=J(a.slice(h+1)),d)c[k]=d[k];return a.slice(0,l)}var h="function"===typeof a.history.pushState,
|
||||
m="function"===typeof setImmediate?setImmediate:setTimeout,q,l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case "#":return c("hash").slice(l.prefix.length);case "?":return c("search").slice(l.prefix.length)+c("hash");default:return c("pathname").slice(l.prefix.length)+c("search")+c("hash")}},setPath:function(c,k,m){var b={},r={};c=d(c,b,r);if(null!=k){for(var v in k)b[v]=k[v];c=c.replace(/:([^\/]+)/g,function(a,c){delete b[c];return k[c]})}(v=E(b))&&(c+="?"+v);(r=E(r))&&(c+="#"+r);
|
||||
h?(r=m?m.state:null,v=m?m.title:null,a.onpopstate(),m&&m.replace?a.history.replaceState(r,v,l.prefix+c):a.history.pushState(r,v,l.prefix+c)):a.location.href=l.prefix+c},defineRoutes:function(c,m,q){function b(){var b=l.getPath(),h={},k=d(b,h,h),p=a.history.state;if(null!=p)for(var t in p)h[t]=p[t];for(var z in c)if(p=new RegExp("^"+z.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),p.test(k)){k.replace(p,function(){for(var a=z.match(/:[^\/]+/g)||[],d=[].slice.call(arguments,
|
||||
1,-2),k=0;k<a.length;k++)h[a[k].replace(/:|\./g,"")]=decodeURIComponent(d[k]);m(c[z],h,b,z)});return}q(b,h)}h?a.onpopstate=k(b):"#"===l.prefix.charAt(0)&&(a.onhashchange=b);b()}};return l};B.route=function(a,c){var k=R(a),d=function(a){return a},h,m,q,l,t,p=function(a,b,p){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var r=function(){null!=h&&c.render(a,h(w(m,q.key,q)))},z=function(){k.setPath(b,null,{replace:!0})};k.defineRoutes(p,function(a,b,c){var k=
|
||||
t=function(a,n){k===t&&(m=null!=n&&"function"===typeof n.view?n:"div",q=b,l=c,t=null,h=(a.render||d).bind(a),r())};a.view?k({},a):a.onmatch?Q.resolve(a.onmatch(b,c)).then(function(b){k(a,b)},z):k(a,"div")},z);c.subscribe(a,r)};p.set=function(a,b,c){null!=t&&(c={replace:!0});t=null;k.setPath(a,b,c)};p.get=function(){return l};p.prefix=function(a){k.prefix=a};p.link=function(a){a.dom.setAttribute("href",k.prefix+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||
|
||||
(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(k.prefix)&&(a=a.slice(k.prefix.length)),p.set(a,void 0,void 0))}};return p}(window,F);B.withAttr=function(a,c,k){return function(d){return c.call(k||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=M(window);B.render=S.render;B.redraw=F.redraw;B.request=I.request;B.jsonp=I.jsonp;B.parseQueryString=J;B.buildQueryString=E;B.version="1.0.0-rc.7";B.vnode=w;"undefined"!==typeof module?module.exports=
|
||||
B:window.m=B};
|
||||
(function(){function y(b,d,e,f,g,l){return{tag:b,key:d,attrs:e,children:f,text:g,dom:l,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function A(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var g="div";for(var l=[],k={};d=P.exec(b);){var r=d[1],p=d[2];""===r&&""!==p?g=p:"#"===r?k.id=p:"."===r?l.push(p):
|
||||
"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?l.push(r):k[d[4]]=""===r?r:r||!0)}0<l.length&&(k.className=l.join(" "));d=M[b]={tag:g,attrs:k}}if(null==e)e={};else if("object"!==typeof e||null!=e.tag||Array.isArray(e))e={},f=1;if(arguments.length===f+1)g=arguments[f],Array.isArray(g)||(g=[g]);else for(g=[];f<arguments.length;)g.push(arguments[f++]);f=y.normalizeChildren(g);if("string"===typeof b){g=!1;var m,n;l=e.className||e["class"];for(var a in d.attrs)N.call(d.attrs,
|
||||
a)&&(e[a]=d.attrs[a]);void 0!==l&&(void 0!==e["class"]&&(e["class"]=void 0,e.className=l),null!=d.attrs.className&&(e.className=d.attrs.className+" "+l));for(a in e)if(N.call(e,a)&&"key"!==a){g=!0;break}Array.isArray(f)&&1===f.length&&null!=f[0]&&"#"===f[0].tag?n=f[0].children:m=f;return y(d.tag,e.key,g?e:void 0,m,n)}return y(b,e.key,e,f)}function Q(b){var d=0,e=null,f="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var g=Date.now();0===d||16<=g-d?(d=g,
|
||||
b()):null===e&&(e=f(function(){e=null;b();d=Date.now()},16-(g-d)))}}y.normalize=function(b){return Array.isArray(b)?y("[",void 0,void 0,y.normalizeChildren(b),void 0,void 0):null!=b&&"object"!==typeof b?y("#",void 0,void 0,!1===b?"":b,void 0,void 0):b};y.normalizeChildren=function(b){for(var d=0;d<b.length;d++)b[d]=y.normalize(b[d]);return b};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,M={},N={}.hasOwnProperty;A.trust=function(b){null==b&&(b="");return y("<",
|
||||
void 0,void 0,b,void 0,void 0)};A.fragment=function(b,d){return y("[",b.key,b,y.normalizeChildren(d),void 0,void 0)};var w=function(b){function d(b,a){return function t(d){var k;try{if(!a||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(k=d.then))m(function(){a||0!==b.length||console.error("Possible unhandled promise rejection:",d);for(var e=0;e<b.length;e++)b[e](d);g.length=0;l.length=0;p.state=a;p.retry=function(){t(d)}});else{if(d===f)throw new TypeError("Promise can't be resolved w/ itself");
|
||||
e(k.bind(d))}}catch(R){r(R)}}}function e(b){function a(a){return function(b){0<d++||a(b)}}var d=0,e=a(r);try{b(a(k),e)}catch(C){e(C)}}if(!(this instanceof w))throw Error("Promise must be called with `new`");if("function"!==typeof b)throw new TypeError("executor must be a function");var f=this,g=[],l=[],k=d(g,!0),r=d(l,!1),p=f._instance={resolvers:g,rejectors:l},m="function"===typeof setImmediate?setImmediate:setTimeout;e(b)};w.prototype.then=function(b,d){function e(b,d,e,k){d.push(function(a){if("function"!==
|
||||
typeof b)e(a);else try{g(b(a))}catch(z){l&&l(z)}});"function"===typeof f.retry&&k===f.state&&f.retry()}var f=this._instance,g,l,k=new w(function(b,d){g=b;l=d});e(b,f.resolvers,g,!0);e(d,f.rejectors,l,!1);return k};w.prototype["catch"]=function(b){return this.then(null,b)};w.resolve=function(b){return b instanceof w?b:new w(function(d){d(b)})};w.reject=function(b){return new w(function(d,e){e(b)})};w.all=function(b){return new w(function(d,e){var f=b.length,g=0,l=[];if(0===b.length)d([]);else for(var k=
|
||||
0;k<b.length;k++)(function(k){function p(b){g++;l[k]=b;g===f&&d(l)}null==b[k]||"object"!==typeof b[k]&&"function"!==typeof b[k]||"function"!==typeof b[k].then?p(b[k]):b[k].then(p,e)})(k)})};w.race=function(b){return new w(function(d,e){for(var f=0;f<b.length;f++)b[f].then(d,e)})};"undefined"!==typeof window?("undefined"===typeof window.Promise&&(window.Promise=w),w=window.Promise):"undefined"!==typeof global&&("undefined"===typeof global.Promise&&(global.Promise=w),w=global.Promise);var G=function(b){function d(b,
|
||||
f){if(Array.isArray(f))for(var g=0;g<f.length;g++)d(b+"["+g+"]",f[g]);else if("[object Object]"===Object.prototype.toString.call(f))for(g in f)d(b+"["+g+"]",f[g]);else e.push(encodeURIComponent(b)+(null!=f&&""!==f?"="+encodeURIComponent(f):""))}if("[object Object]"!==Object.prototype.toString.call(b))return"";var e=[],f;for(f in b)d(f,b[f]);return e.join("&")},S=/^file:\/\//i,K=function(b,d){function e(){function a(){0===--b&&"function"===typeof n&&n()}var b=0;return function C(d){var e=d.then;d.then=
|
||||
function(){b++;var g=e.apply(d,arguments);g.then(a,function(d){a();if(0===b)throw d;});return C(g)};return d}}function f(a,b){if("string"===typeof a){var d=a;a=b||{};null==a.url&&(a.url=d)}return a}function g(a,b){if(null==b)return a;for(var d=a.match(/:[^\/]+/gi)||[],e=0;e<d.length;e++){var g=d[e].slice(1);null!=b[g]&&(a=a.replace(d[e],b[g]))}return a}function l(a,b){var d=G(b);if(""!==d){var e=0>a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(z){throw Error(a);
|
||||
}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;d<b.length;d++)b[d]=new a(b[d]);else return new a(b);return b}var m=0,n;return{request:function(a,m){var n=e();a=f(a,m);var z=new d(function(d,e){null==a.method&&(a.method="GET");a.method=a.method.toUpperCase();var f="GET"===a.method||"TRACE"===a.method?!1:"boolean"===typeof a.useBody?a.useBody:!0;"function"!==typeof a.serialize&&(a.serialize="undefined"!==typeof FormData&&a.data instanceof
|
||||
FormData?function(a){return a}:JSON.stringify);"function"!==typeof a.deserialize&&(a.deserialize=k);"function"!==typeof a.extract&&(a.extract=r);a.url=g(a.url,a.data);f?a.data=a.serialize(a.data):a.url=l(a.url,a.data);var m=new b.XMLHttpRequest,n=!1,z=m.abort;m.abort=function(){n=!0;z.call(m)};m.open(a.method,a.url,"boolean"===typeof a.async?a.async:!0,"string"===typeof a.user?a.user:void 0,"string"===typeof a.password?a.password:void 0);a.serialize===JSON.stringify&&f&&m.setRequestHeader("Content-Type",
|
||||
"application/json; charset=utf-8");a.deserialize===k&&m.setRequestHeader("Accept","application/json, text/*");a.withCredentials&&(m.withCredentials=a.withCredentials);for(var t in a.headers)({}).hasOwnProperty.call(a.headers,t)&&m.setRequestHeader(t,a.headers[t]);"function"===typeof a.config&&(m=a.config(m,a)||m);m.onreadystatechange=function(){if(!n&&4===m.readyState)try{var b=a.extract!==r?a.extract(m,a):a.deserialize(a.extract(m,a));if(200<=m.status&&300>m.status||304===m.status||S.test(a.url))d(p(a.type,
|
||||
b));else{var h=Error(m.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?z:n(z)},jsonp:function(a,k){var n=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(p(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null==
|
||||
a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=l(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:n(r)},setCompletionCallback:function(a){n=a}}}(window,w),O=function(b){function d(h,c,q,a,b,d,g){for(;q<a;q++){var u=c[q];null!=u&&e(h,u,b,g,d)}}function e(h,c,q,a,b){var u=c.tag;if("string"===typeof u)switch(c.state={},null!=c.attrs&&A(c.attrs,c,q),u){case "#":return c.dom=x.createTextNode(c.children),m(h,c.dom,b),c.dom;case "<":return f(h,
|
||||
c,b);case "[":var k=x.createDocumentFragment();null!=c.children&&(u=c.children,d(k,u,0,u.length,q,null,a));c.dom=k.firstChild;c.domSize=k.childNodes.length;m(h,k,b);return k;default:var p=c.tag,l=(u=c.attrs)&&u.is;p=(a=c.attrs&&c.attrs.xmlns||G[c.tag]||a)?l?x.createElementNS(a,p,{is:l}):x.createElementNS(a,p):l?x.createElement(p,{is:l}):x.createElement(p);c.dom=p;if(null!=u)for(k in l=a,u)C(c,k,null,u[k],l);m(h,p,b);null!=c.attrs&&null!=c.attrs.contenteditable?n(c):(null!=c.text&&(""!==c.text?p.textContent=
|
||||
c.text:c.children=[y("#",void 0,void 0,c.text,void 0,void 0)]),null!=c.children&&(h=c.children,d(p,h,0,h.length,q,null,a),h=c.attrs,"select"===c.tag&&null!=h&&("value"in h&&C(c,"value",null,h.value,void 0),"selectedIndex"in h&&C(c,"selectedIndex",null,h.selectedIndex,void 0))));return p}else return g(c,q),null!=c.instance?(q=e(h,c.instance,q,a,b),c.dom=c.instance.dom,c.domSize=null!=c.dom?c.instance.domSize:0,m(h,q,b),c=q):(c.domSize=0,c=J),c}function f(h,c,q){var a={caption:"table",thead:"table",
|
||||
tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(c.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div";a=x.createElement(a);a.innerHTML=c.children;c.dom=a.firstChild;c.domSize=a.childNodes.length;c=x.createDocumentFragment();for(var b;b=a.firstChild;)c.appendChild(b);m(h,c,q);return c}function g(h,c){if("function"===typeof h.tag.view){h.state=Object.create(h.tag);var a=h.state.view;if(null!=a.$$reentrantLock$$)return J;a.$$reentrantLock$$=!0}else{h.state=void 0;
|
||||
a=h.tag;if(null!=a.$$reentrantLock$$)return J;a.$$reentrantLock$$=!0;h.state=null!=h.tag.prototype&&"function"===typeof h.tag.prototype.view?new h.tag(h):h.tag(h)}h._state=h.state;null!=h.attrs&&A(h.attrs,h,c);A(h._state,h,c);h.instance=y.normalize(h._state.view.call(h.state,h));if(h.instance===h)throw Error("A view cannot return the vnode it received as argument");a.$$reentrantLock$$=null}function l(h,c,q,b,g,f,l){if(c!==q&&(null!=c||null!=q))if(null==c)d(h,q,0,q.length,g,f,l);else if(null==q)a(c,
|
||||
0,c.length,q);else{if(c.length===q.length){var u=!1;for(var n=0;n<q.length;n++)if(null!=q[n]&&null!=c[n]){u=null==q[n].key&&null==c[n].key;break}if(u){for(n=0;n<c.length;n++)c[n]!==q[n]&&(null==c[n]&&null!=q[n]?e(h,q[n],g,l,p(c,n+1,f)):null==q[n]?a(c,n,n+1,q):k(h,c[n],q[n],g,p(c,n+1,f),b,l));return}}if(!b)a:{if(null!=c.pool&&Math.abs(c.pool.length-q.length)<=Math.abs(c.length-q.length)&&(b=q[0]&&q[0].children&&q[0].children.length||0,Math.abs((c.pool[0]&&c.pool[0].children&&c.pool[0].children.length||
|
||||
0)-b)<=Math.abs((c[0]&&c[0].children&&c[0].children.length||0)-b))){b=!0;break a}b=!1}if(b){var z=c.pool;c=c.concat(c.pool)}for(var B=n=0,v=c.length-1,E=q.length-1,H;v>=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var x=null!=z&&n>=c.length-z.length||null==z&&b;n++;B++;k(h,t,u,g,p(c,n,f),x,l);b&&t.tag===u.tag&&m(h,r(t),f)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,
|
||||
k(h,t,u,g,p(c,v+1,f),x,l),(b||B<E)&&m(h,r(t),p(c,n,f)),v--,B++;else break;else v--,B++;else n++,B++}for(;v>=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,k(h,t,u,g,p(c,v+1,f),x,l),b&&t.tag===u.tag&&m(h,r(t),f),null!=t.dom&&(f=t.dom),v--;else{if(!H){H=c;x=v;t={};var D;for(D=0;D<x;D++){var w=H[D];null!=w&&(w=w.key,null!=w&&(t[w]=D))}H=t}null!=u&&(x=H[u.key],null!=x?(t=c[x],k(h,t,u,g,p(c,v+1,f),b,l),m(h,r(t),f),c[x].skip=
|
||||
!0,null!=t.dom&&(f=t.dom)):f=e(h,u,g,l,f))}E--}else v--,E--;if(E<B)break}d(h,q,B,E+1,g,f,l);a(c,n,v+1,q)}}function k(h,c,a,b,d,p,m){var q=c.tag;if(q===a.tag){a.state=c.state;a._state=c._state;a.events=c.events;var u;if(u=!p){var t,B;null!=a.attrs&&"function"===typeof a.attrs.onbeforeupdate&&(t=a.attrs.onbeforeupdate.call(a.state,a,c));"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeupdate&&(B=a._state.onbeforeupdate.call(a.state,a,c));void 0===t&&void 0===B||t||B?u=!1:(a.dom=c.dom,a.domSize=
|
||||
c.domSize,a.instance=c.instance,u=!0)}if(!u)if("string"===typeof q)switch(null!=a.attrs&&(p?(a.state={},A(a.attrs,a,b)):I(a.attrs,a,b)),q){case "#":c.children.toString()!==a.children.toString()&&(c.dom.nodeValue=a.children);a.dom=c.dom;break;case "<":c.children!==a.children?(r(c),f(h,a,d)):(a.dom=c.dom,a.domSize=c.domSize);break;case "[":l(h,c.children,a.children,p,b,d,m);c=0;b=a.children;a.dom=null;if(null!=b){for(p=0;p<b.length;p++){var v=b[p];null!=v&&null!=v.dom&&(null==a.dom&&(a.dom=v.dom),c+=
|
||||
v.domSize||1)}1!==c&&(a.domSize=c)}break;default:h=a.dom=c.dom;m=a.attrs&&a.attrs.xmlns||G[a.tag]||m;"textarea"===a.tag&&(null==a.attrs&&(a.attrs={}),null!=a.text&&(a.attrs.value=a.text,a.text=void 0));d=c.attrs;q=a.attrs;u=m;if(null!=q)for(v in q)C(a,v,d&&d[v],q[v],u);if(null!=d)for(v in d)null!=q&&v in q||("className"===v&&(v="class"),"o"!==v[0]||"n"!==v[1]||D(v)?"key"!==v&&a.dom.removeAttribute(v):w(a,v,void 0));null!=a.attrs&&null!=a.attrs.contenteditable?n(a):null!=c.text&&null!=a.text&&""!==
|
||||
a.text?c.text.toString()!==a.text.toString()&&(c.dom.firstChild.nodeValue=a.text):(null!=c.text&&(c.children=[y("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=a.text&&(a.children=[y("#",void 0,void 0,a.text,void 0,void 0)]),l(h,c.children,a.children,p,b,null,m))}else{if(p)g(a,b);else{a.instance=y.normalize(a._state.view.call(a.state,a));if(a.instance===a)throw Error("A view cannot return the vnode it received as argument");null!=a.attrs&&I(a.attrs,a,b);I(a._state,a,b)}null!=a.instance?
|
||||
(null==c.instance?e(h,a.instance,b,m,d):k(h,c.instance,a.instance,b,d,p,m),a.dom=a.instance.dom,a.domSize=a.instance.domSize):null!=c.instance?(z(c.instance,null),a.dom=void 0,a.domSize=0):(a.dom=c.dom,a.domSize=c.domSize)}}else z(c,null),e(h,a,b,m,d)}function r(a){var c=a.domSize;if(null!=c||null==a.dom){var b=x.createDocumentFragment();if(0<c){for(a=a.dom;--c;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}function p(a,c,b){for(;c<a.length;c++)if(null!=a[c]&&null!=
|
||||
a[c].dom)return a[c].dom;return b}function m(a,c,b){b&&b.parentNode?a.insertBefore(c,b):a.appendChild(c)}function n(a){var c=a.children;if(null!=c&&1===c.length&&"<"===c[0].tag)c=c[0].children,a.dom.innerHTML!==c&&(a.dom.innerHTML=c);else if(null!=a.text||null!=c&&0!==c.length)throw Error("Child node of a contenteditable must be trusted");}function a(a,c,b,d){for(;c<b;c++){var h=a[c];null!=h&&(h.skip?h.skip=!1:z(h,d))}}function z(a,c){function b(){if(++d===h&&(t(a),a.dom)){var b=a.domSize||1;if(1<
|
||||
b)for(var e=a.dom;--b;){var f=e.nextSibling,g=f.parentNode;null!=g&&g.removeChild(f)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=c&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(c.pool?c.pool.push(a):c.pool=[a])}}var h=1,d=0;if(a.attrs&&"function"===typeof a.attrs.onbeforeremove){var e=a.attrs.onbeforeremove.call(a.state,a);null!=e&&"function"===typeof e.then&&(h++,e.then(b,b))}"string"!==typeof a.tag&&"function"===
|
||||
typeof a._state.onbeforeremove&&(e=a._state.onbeforeremove.call(a.state,a),null!=e&&"function"===typeof e.then&&(h++,e.then(b,b)));b()}function t(a){a.attrs&&"function"===typeof a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&"function"===typeof a._state.onremove&&a._state.onremove.call(a.state,a);if(null!=a.instance)t(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c<a.length;c++){var b=a[c];null!=b&&t(b)}}function C(a,c,b,d,e){var h=a.dom;if("key"!==c&&
|
||||
"is"!==c&&(b!==d||"value"===c||"checked"===c||"selectedIndex"===c||"selected"===c&&a.dom===x.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!D(c)){var f=c.indexOf(":");if(-1<f&&"xlink"===c.substr(0,f))h.setAttributeNS("http://www.w3.org/1999/xlink",c.slice(f+1),d);else if("o"===c[0]&&"n"===c[1]&&"function"===typeof d)w(a,c,d);else if("style"===c)if(a=b,a===d&&(h.style.cssText="",a=null),null==d)h.style.cssText="";else if("string"===typeof d)h.style.cssText=d;else{"string"===typeof a&&
|
||||
(h.style.cssText="");for(var g in d)h.style[g]=d[g];if(null!=a&&"string"!==typeof a)for(g in a)g in d||(h.style[g]="")}else if(c in h&&"href"!==c&&"list"!==c&&"form"!==c&&"width"!==c&&"height"!==c&&void 0===e&&!(a.attrs.is||-1<a.tag.indexOf("-"))){if("value"===c){g=""+d;if(("input"===a.tag||"textarea"===a.tag)&&a.dom.value===g&&a.dom===x.activeElement)return;if("select"===a.tag)if(null===d){if(-1===a.dom.selectedIndex&&a.dom===x.activeElement)return}else if(null!==b&&a.dom.value===g&&a.dom===x.activeElement)return;
|
||||
if("option"===a.tag&&null!=b&&a.dom.value===g)return}"input"===a.tag&&"type"===c?h.setAttribute(c,d):h[c]=d}else"boolean"===typeof d?d?h.setAttribute(c,""):h.removeAttribute(c):h.setAttribute("className"===c?"class":c,d)}}function D(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function w(a,b,d){var c=a.dom,e="function"!==typeof F?d:function(a){var b=d.call(c,a);F.call(c,a);return b};if(b in c)c[b]="function"===typeof d?e:null;else{var h=
|
||||
b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==e&&(null!=a.events[b]&&c.removeEventListener(h,a.events[b],!1),"function"===typeof d&&(a.events[b]=e,c.addEventListener(h,a.events[b],!1)))}}function A(a,b,d){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,b))}function I(a,b,d){"function"===typeof a.onupdate&&d.push(a.onupdate.bind(b.state,b))}var x=b.document,J=x.createDocumentFragment(),G={svg:"http://www.w3.org/2000/svg",
|
||||
math:"http://www.w3.org/1998/Math/MathML"},F;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=[],d=x.activeElement,e=a.namespaceURI;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);l(a,a.vnodes,y.normalizeChildren(b),!1,c,null,"http://www.w3.org/1999/xhtml"===e?void 0:e);a.vnodes=b;for(e=0;e<c.length;e++)c[e]();x.activeElement!==d&&d.focus()},setEventCallback:function(a){return F=a}}},F=function(b){function d(b){b=
|
||||
f.indexOf(b);-1<b&&f.splice(b,2)}function e(){for(var b=1;b<f.length;b+=2)f[b]()}b=O(b);b.setEventCallback(function(b){!1===b.redraw?b.redraw=void 0:e()});var f=[];return{subscribe:function(b,e){d(b);f.push(b,Q(e))},unsubscribe:d,redraw:e,render:b.render}}(window);K.setCompletionCallback(F.redraw);A.mount=function(b){return function(d,e){if(null===e)b.render(d,[]),b.unsubscribe(d);else{if(null==e.view&&"function"!==typeof e)throw Error("m.mount(element, component) expects a component, not a vnode");
|
||||
b.subscribe(d,function(){b.render(d,y(e))});b.redraw()}}}(F);var T=w,L=function(b){if(""===b||null==b)return{};"?"===b.charAt(0)&&(b=b.slice(1));b=b.split("&");for(var d={},e={},f=0;f<b.length;f++){var g=b[f].split("=");var l=decodeURIComponent(g[0]);g=2===g.length?decodeURIComponent(g[1]):"";"true"===g?g=!0:"false"===g&&(g=!1);var k=l.split(/\]\[?|\[/),r=d;-1<l.indexOf("[")&&k.pop();for(var p=0;p<k.length;p++){l=k[p];var m=k[p+1];m=""==m||!isNaN(parseInt(m,10));var n=p===k.length-1;""===l&&(l=k.slice(0,
|
||||
p).join(),null==e[l]&&(e[l]=0),l=e[l]++);null==r[l]&&(r[l]=n?g:m?[]:{});r=r[l]}}return d},U=function(b){function d(d){var e=b.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==e[0]&&(e="/"+e);return e}function e(b){return function(){null==k&&(k=l(function(){k=null;b()}))}}function f(b,d,e){var a=b.indexOf("?"),f=b.indexOf("#"),g=-1<a?a:-1<f?f:b.length;if(-1<a){a=L(b.slice(a+1,-1<f?f:b.length));for(var n in a)d[n]=a[n]}if(-1<f)for(n in d=L(b.slice(f+1)),d)e[n]=
|
||||
d[n];return b.slice(0,g)}var g="function"===typeof b.history.pushState,l="function"===typeof setImmediate?setImmediate:setTimeout,k,r={prefix:"#!",getPath:function(){switch(r.prefix.charAt(0)){case "#":return d("hash").slice(r.prefix.length);case "?":return d("search").slice(r.prefix.length)+d("hash");default:return d("pathname").slice(r.prefix.length)+d("search")+d("hash")}},setPath:function(d,e,n){var a={},k={};d=f(d,a,k);if(null!=e){for(var l in e)a[l]=e[l];d=d.replace(/:([^\/]+)/g,function(b,
|
||||
d){delete a[d];return e[d]})}(l=G(a))&&(d+="?"+l);(k=G(k))&&(d+="#"+k);g?(k=n?n.state:null,l=n?n.title:null,b.onpopstate(),n&&n.replace?b.history.replaceState(k,l,r.prefix+d):b.history.pushState(k,l,r.prefix+d)):b.location.href=r.prefix+d},defineRoutes:function(d,k,n){function a(){var a=r.getPath(),e={},g=f(a,e,e),l=b.history.state;if(null!=l)for(var m in l)e[m]=l[m];for(var p in d)if(l=new RegExp("^"+p.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),l.test(g)){g.replace(l,
|
||||
function(){for(var b=p.match(/:[^\/]+/g)||[],f=[].slice.call(arguments,1,-2),g=0;g<b.length;g++)e[b[g].replace(/:|\./g,"")]=decodeURIComponent(f[g]);k(d[p],e,a,p)});return}n(a,e)}g?b.onpopstate=e(a):"#"===r.prefix.charAt(0)&&(b.onhashchange=a);a()}};return r};A.route=function(b,d){var e=U(b),f=function(b){return b},g,l,k,r,p,m=function(b,a,m){if(null==b)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var n=function(){null!=g&&d.render(b,g(y(l,k.key,k)))},w=function(b){if(b!==
|
||||
a)e.setPath(a,null,{replace:!0});else throw Error("Could not resolve default route "+a);};e.defineRoutes(m,function(a,b,d){var e=p=function(a,m){e===p&&(l=null==m||"function"!==typeof m.view&&"function"!==typeof m?"div":m,k=b,r=d,p=null,g=(a.render||f).bind(a),n())};a.view||"function"===typeof a?e({},a):a.onmatch?T.resolve(a.onmatch(b,d)).then(function(b){e(a,b)},w):e(a,"div")},w);d.subscribe(b,n)};m.set=function(b,a,d){null!=p&&(d=d||{},d.replace=!0);p=null;e.setPath(b,a,d)};m.get=function(){return r};
|
||||
m.prefix=function(b){e.prefix=b};m.link=function(b){b.dom.setAttribute("href",e.prefix+b.attrs.href);b.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(e.prefix)&&(a=a.slice(e.prefix.length)),m.set(a,void 0,void 0))}};m.param=function(b){return"undefined"!==typeof k&&"undefined"!==typeof b?k[b]:k};return m}(window,F);A.withAttr=function(b,d,e){return function(f){d.call(e||this,b in f.currentTarget?f.currentTarget[b]:
|
||||
f.currentTarget.getAttribute(b))}};var V=O(window);A.render=V.render;A.redraw=F.redraw;A.request=K.request;A.jsonp=K.jsonp;A.parseQueryString=L;A.buildQueryString=G;A.version="1.1.1";A.vnode=y;"undefined"!==typeof module?module.exports=A:window.m=A})();
|
||||
4
mount.js
4
mount.js
|
|
@ -1,3 +1,5 @@
|
|||
"use strict"
|
||||
|
||||
var redrawService = require("./redraw")
|
||||
|
||||
module.exports = require("./api/mount")(redrawService)
|
||||
module.exports = require("./api/mount")(redrawService)
|
||||
|
|
|
|||
|
|
@ -278,12 +278,20 @@ ospec will automatically evaluate all `*.js` files in any folder named `/tests`.
|
|||
$ npm test
|
||||
```
|
||||
|
||||
#### Installing ospec globally
|
||||
#### Direct use from the command line
|
||||
|
||||
While it's recommended to install ospec locally to maintain reproducible environments, sometimes it may be deemed appropriate to install it globally. To do so, run this command:
|
||||
Ospec doesn't work when installed globally. Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally.
|
||||
|
||||
To work around this limitation, you can use [`npm-run`](https://www.npmjs.com/package/npm-run) which enables one to run the binaries of locally installed packages.
|
||||
|
||||
```
|
||||
npm install ospec -g
|
||||
npm install npm-run -g
|
||||
```
|
||||
|
||||
Then, from a project that has ospec installed as a (dev) dependency:
|
||||
|
||||
```
|
||||
npm-run ospec
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
|
|
@ -10,12 +11,13 @@ function traverseDirectory(pathname, callback) {
|
|||
return new Promise(function(resolve, reject) {
|
||||
fs.lstat(pathname, function(err, stat) {
|
||||
if (err) reject(err)
|
||||
if (stat.isDirectory()) {
|
||||
if (stat && stat.isDirectory()) {
|
||||
fs.readdir(pathname, function(err, pathnames) {
|
||||
if (err) reject(err)
|
||||
var promises = []
|
||||
for (var i = 0; i < pathnames.length; i++) {
|
||||
if (pathnames[i] === "node_modules") continue
|
||||
if (pathnames[i][0] === ".") continue
|
||||
pathnames[i] = path.join(pathname, pathnames[i])
|
||||
promises.push(traverseDirectory(pathnames[i], callback))
|
||||
}
|
||||
|
|
@ -31,9 +33,9 @@ function traverseDirectory(pathname, callback) {
|
|||
})
|
||||
}
|
||||
|
||||
traverseDirectory(".", function(pathname, stat, children) {
|
||||
traverseDirectory(".", function(pathname) {
|
||||
if (pathname.match(/(?:^|\/)tests\/.*\.js$/)) {
|
||||
require(path.normalize(process.cwd()) + "/" + pathname)
|
||||
require(path.normalize(process.cwd()) + "/" + pathname) // eslint-disable-line global-require
|
||||
}
|
||||
})
|
||||
.then(o.run)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
/* eslint-disable no-bitwise, no-process-exit */
|
||||
"use strict"
|
||||
|
||||
module.exports = new function init(name) {
|
||||
var spec = {}, subjects = [], results = [], only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
|
||||
var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
|
||||
|
||||
if (name != null) spec[name] = ctx = {}
|
||||
|
||||
function o(subject, predicate) {
|
||||
if (predicate === undefined) return new Assert(subject)
|
||||
ctx[unique(subject)] = predicate
|
||||
if (predicate === undefined) {
|
||||
if (results == null) throw new Error("Assertions should not occur outside test definitions")
|
||||
return new Assert(subject)
|
||||
}
|
||||
else if (results == null) {
|
||||
ctx[unique(subject)] = predicate
|
||||
} else {
|
||||
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
|
||||
}
|
||||
}
|
||||
o.before = hook("__before")
|
||||
o.after = hook("__after")
|
||||
|
|
@ -20,7 +28,10 @@ module.exports = new function init(name) {
|
|||
predicate()
|
||||
ctx = parent
|
||||
}
|
||||
o.only = function(subject, predicate) {o(subject, only = predicate)}
|
||||
o.only = function(subject, predicate, silent) {
|
||||
if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
|
||||
o(subject, only = predicate)
|
||||
}
|
||||
o.spy = function(fn) {
|
||||
var spy = function() {
|
||||
spy.this = this
|
||||
|
|
@ -39,6 +50,7 @@ module.exports = new function init(name) {
|
|||
return spy
|
||||
}
|
||||
o.run = function() {
|
||||
results = []
|
||||
start = new Date
|
||||
test(spec, [], [], report)
|
||||
|
||||
|
|
@ -112,8 +124,8 @@ module.exports = new function init(name) {
|
|||
}
|
||||
function unique(subject) {
|
||||
if (hasOwn.call(ctx, subject)) {
|
||||
console.warn("A test or a spec named `" + subject + "` was already defined")
|
||||
while (hasOwn.call(ctx, subject)) subject += '*'
|
||||
console.warn("A test or a spec named `" + subject + "` was already defined")
|
||||
while (hasOwn.call(ctx, subject)) subject += "*"
|
||||
}
|
||||
return subject
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,5 +12,5 @@
|
|||
"bin": {
|
||||
"ospec": "./bin/ospec"
|
||||
},
|
||||
"repository": "lhorie/mithril.js#rewrite"
|
||||
"repository": "MithrilJS/mithril.js"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ new function(o) {
|
|||
})
|
||||
o.only(".only()", function() {
|
||||
o(2).equals(2)
|
||||
})
|
||||
}, true)
|
||||
})
|
||||
|
||||
o.run()
|
||||
|
|
@ -20,7 +20,7 @@ new function(o) {
|
|||
|
||||
o.spec("ospec", function() {
|
||||
o.spec("sync", function() {
|
||||
var a = 0, b = 0
|
||||
var a = 0, b = 0, illegalAssertionThrows = false
|
||||
|
||||
o.before(function() {a = 1})
|
||||
o.after(function() {a = 0})
|
||||
|
|
@ -28,7 +28,15 @@ o.spec("ospec", function() {
|
|||
o.beforeEach(function() {b = 1})
|
||||
o.afterEach(function() {b = 0})
|
||||
|
||||
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true}
|
||||
|
||||
o("assertions", function() {
|
||||
var nestedTestDeclarationThrows = false
|
||||
try {o("illegal nested test", function(){})} catch (e) {nestedTestDeclarationThrows = true}
|
||||
|
||||
o(illegalAssertionThrows).equals(true)
|
||||
o(nestedTestDeclarationThrows).equals(true)
|
||||
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
|
||||
|
|
@ -43,7 +51,7 @@ o.spec("ospec", function() {
|
|||
o(undef1).notDeepEquals(undef2)
|
||||
o(undef1).notDeepEquals({})
|
||||
o({}).notDeepEquals(undef1)
|
||||
|
||||
|
||||
var sparse1 = [void 1, void 2, void 3]
|
||||
delete sparse1[0]
|
||||
var sparse2 = [void 1, void 2, void 3]
|
||||
|
|
@ -55,7 +63,7 @@ o.spec("ospec", function() {
|
|||
monkeypatch1.field = 3
|
||||
var monkeypatch2 = [1, 2]
|
||||
monkeypatch2.field = 4
|
||||
|
||||
|
||||
o(monkeypatch1).notDeepEquals([1, 2])
|
||||
o(monkeypatch1).notDeepEquals(monkeypatch2)
|
||||
|
||||
|
|
|
|||
981
package-lock.json
generated
Normal file
981
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,981 @@
|
|||
{
|
||||
"name": "mithril",
|
||||
"version": "1.1.1",
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"@alrra/travis-scripts": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@alrra/travis-scripts/-/travis-scripts-3.0.1.tgz",
|
||||
"integrity": "sha1-RdW5NXMXtsxVU9/ZmTGEOuCw5To="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
|
||||
"integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "https://registry.npmjs.org/acorn/-/acorn-5.0.3.tgz",
|
||||
"integrity": "sha1-xGDfCEkUY/AozLguqzcwvwEIez0=",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
|
||||
"integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
|
||||
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
|
||||
"integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
|
||||
"dev": true
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz",
|
||||
"integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=",
|
||||
"dev": true
|
||||
},
|
||||
"align-text": {
|
||||
"version": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
|
||||
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
|
||||
"dev": true
|
||||
},
|
||||
"amdefine": {
|
||||
"version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
|
||||
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-escapes": {
|
||||
"version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
|
||||
"integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
},
|
||||
"argparse": {
|
||||
"version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
|
||||
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
|
||||
"dev": true
|
||||
},
|
||||
"array-union": {
|
||||
"version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk="
|
||||
},
|
||||
"array-uniq": {
|
||||
"version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
|
||||
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
|
||||
},
|
||||
"arrify": {
|
||||
"version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
|
||||
"integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
|
||||
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
|
||||
},
|
||||
"benchmark": {
|
||||
"version": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
|
||||
"integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz",
|
||||
"integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k="
|
||||
},
|
||||
"buffer-shims": {
|
||||
"version": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
|
||||
"integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=",
|
||||
"dev": true
|
||||
},
|
||||
"caller-path": {
|
||||
"version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
|
||||
"integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=",
|
||||
"dev": true
|
||||
},
|
||||
"callsites": {
|
||||
"version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz",
|
||||
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
|
||||
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"center-align": {
|
||||
"version": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
|
||||
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true
|
||||
},
|
||||
"circular-json": {
|
||||
"version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz",
|
||||
"integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=",
|
||||
"dev": true
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
|
||||
"integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
|
||||
"dev": true
|
||||
},
|
||||
"cli-width": {
|
||||
"version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz",
|
||||
"integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=",
|
||||
"dev": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
|
||||
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
|
||||
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"co": {
|
||||
"version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
|
||||
"dev": true
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"dev": true
|
||||
},
|
||||
"collections": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/collections/-/collections-0.2.2.tgz",
|
||||
"integrity": "sha1-HyMCay7zb5J+7MkB6ZxfDUj6M04="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz",
|
||||
"integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=",
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
},
|
||||
"d": {
|
||||
"version": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
|
||||
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "https://registry.npmjs.org/debug/-/debug-2.6.6.tgz",
|
||||
"integrity": "sha1-qfpvvpykPPHnn3O3XAGJy7fW21o=",
|
||||
"dev": true
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
|
||||
"dev": true
|
||||
},
|
||||
"del": {
|
||||
"version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
|
||||
"integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
|
||||
"dev": true
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz",
|
||||
"integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=",
|
||||
"dev": true
|
||||
},
|
||||
"es5-ext": {
|
||||
"version": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.15.tgz",
|
||||
"integrity": "sha1-wzClk0we4hKEp8CBqG5f2TfJHqY=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-iterator": {
|
||||
"version": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
|
||||
"integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-map": {
|
||||
"version": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
|
||||
"integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-set": {
|
||||
"version": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
|
||||
"integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-symbol": {
|
||||
"version": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
|
||||
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
|
||||
"dev": true
|
||||
},
|
||||
"es6-weak-map": {
|
||||
"version": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
|
||||
"integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"escodegen": {
|
||||
"version": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
|
||||
"integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esprima": {
|
||||
"version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
|
||||
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
|
||||
"dev": true
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
|
||||
"integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"escope": {
|
||||
"version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
|
||||
"integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=",
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz",
|
||||
"integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=",
|
||||
"dev": true
|
||||
},
|
||||
"espree": {
|
||||
"version": "https://registry.npmjs.org/espree/-/espree-3.4.2.tgz",
|
||||
"integrity": "sha1-ONve2+3JW4lhofvwRzSo9qnIxZI=",
|
||||
"dev": true
|
||||
},
|
||||
"esprima": {
|
||||
"version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
|
||||
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
|
||||
"dev": true
|
||||
},
|
||||
"esquery": {
|
||||
"version": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
|
||||
"integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
|
||||
"dev": true
|
||||
},
|
||||
"esrecurse": {
|
||||
"version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
|
||||
"integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz",
|
||||
"integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
|
||||
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"dev": true
|
||||
},
|
||||
"event-emitter": {
|
||||
"version": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
|
||||
"dev": true
|
||||
},
|
||||
"exit-hook": {
|
||||
"version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
|
||||
"integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
|
||||
"dev": true
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
|
||||
"dev": true
|
||||
},
|
||||
"figures": {
|
||||
"version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
|
||||
"integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
|
||||
"dev": true
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz",
|
||||
"integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=",
|
||||
"dev": true
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz",
|
||||
"integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
|
||||
"integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
|
||||
"integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
|
||||
"dev": true
|
||||
},
|
||||
"gh-pages": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-0.12.0.tgz",
|
||||
"integrity": "sha1-2VHj7Zi4VpnUsEGOsaFbGgSYjcE=",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.1.2.tgz",
|
||||
"integrity": "sha1-YSpKtF70KnDN6Aa62G7m2wR+g4U="
|
||||
},
|
||||
"globby": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw="
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.10.tgz",
|
||||
"integrity": "sha1-8tcgwiCS90Mih3XHXjYSYyUB8TE="
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
|
||||
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg="
|
||||
},
|
||||
"globals": {
|
||||
"version": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz",
|
||||
"integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY=",
|
||||
"dev": true
|
||||
},
|
||||
"globby": {
|
||||
"version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
|
||||
"integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.7.tgz",
|
||||
"integrity": "sha1-6XMlrrjqC54SucTdc8TDEq0O3lk=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
|
||||
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "https://registry.npmjs.org/ignore/-/ignore-3.2.7.tgz",
|
||||
"integrity": "sha1-SBDKXx2OylWVITo0uU8utO2Sa70=",
|
||||
"dev": true
|
||||
},
|
||||
"imurmurhash": {
|
||||
"version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
|
||||
"integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
|
||||
"dev": true
|
||||
},
|
||||
"interpret": {
|
||||
"version": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz",
|
||||
"integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=",
|
||||
"dev": true
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
|
||||
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"dev": true
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz",
|
||||
"integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=",
|
||||
"dev": true
|
||||
},
|
||||
"is-path-cwd": {
|
||||
"version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
|
||||
"integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-path-in-cwd": {
|
||||
"version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
|
||||
"integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=",
|
||||
"dev": true
|
||||
},
|
||||
"is-path-inside": {
|
||||
"version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
|
||||
"integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=",
|
||||
"dev": true
|
||||
},
|
||||
"is-property": {
|
||||
"version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
|
||||
"dev": true
|
||||
},
|
||||
"is-resolvable": {
|
||||
"version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
|
||||
"integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"istanbul": {
|
||||
"version": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz",
|
||||
"integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esprima": {
|
||||
"version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
|
||||
"integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
|
||||
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
|
||||
"integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
|
||||
"integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz",
|
||||
"integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.3.tgz",
|
||||
"integrity": "sha1-M6BexIHIUMiHWSkWb+G+thxyh2Y=",
|
||||
"dev": true
|
||||
},
|
||||
"json-stable-stringify": {
|
||||
"version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
|
||||
"integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
|
||||
"dev": true
|
||||
},
|
||||
"jsonify": {
|
||||
"version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
|
||||
"dev": true
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
|
||||
"integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=",
|
||||
"dev": true
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.0.tgz",
|
||||
"integrity": "sha1-tYq+TVwEStM3JqjBUltIz4kb/wc=",
|
||||
"dev": true
|
||||
},
|
||||
"lazy-cache": {
|
||||
"version": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
|
||||
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"levn": {
|
||||
"version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
|
||||
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
|
||||
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
|
||||
},
|
||||
"longest": {
|
||||
"version": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
|
||||
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
|
||||
"dev": true
|
||||
},
|
||||
"marked": {
|
||||
"version": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz",
|
||||
"integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz",
|
||||
"integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA="
|
||||
},
|
||||
"mimeparse": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/mimeparse/-/mimeparse-0.1.4.tgz",
|
||||
"integrity": "sha1-2vsCdSNw/SJgk64xUsJxrwGsJUo="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz",
|
||||
"integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=",
|
||||
"dev": true
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||
"integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
|
||||
"dev": true
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
|
||||
"dev": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
|
||||
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
|
||||
"dev": true
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"once": {
|
||||
"version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E="
|
||||
},
|
||||
"onetime": {
|
||||
"version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||
"dev": true
|
||||
},
|
||||
"optimist": {
|
||||
"version": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wordwrap": {
|
||||
"version": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionator": {
|
||||
"version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
|
||||
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
|
||||
"dev": true
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-is-inside": {
|
||||
"version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
|
||||
"integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
||||
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o="
|
||||
},
|
||||
"platform": {
|
||||
"version": "https://registry.npmjs.org/platform/-/platform-1.3.4.tgz",
|
||||
"integrity": "sha1-bw+xftqqSPIUQrOpdcBjEw8cPr0=",
|
||||
"dev": true
|
||||
},
|
||||
"pluralize": {
|
||||
"version": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz",
|
||||
"integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
|
||||
"dev": true
|
||||
},
|
||||
"progress": {
|
||||
"version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
|
||||
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
|
||||
"dev": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
|
||||
"integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4="
|
||||
},
|
||||
"q-io": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/q-io/-/q-io-1.13.2.tgz",
|
||||
"integrity": "sha1-7qEw1IHdteGqG8WmaFX3OR0G8AM="
|
||||
},
|
||||
"qs": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.2.tgz",
|
||||
"integrity": "sha1-GbV/8k3CqZzh+L32r82ln472H4g="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz",
|
||||
"integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=",
|
||||
"dev": true
|
||||
},
|
||||
"readline2": {
|
||||
"version": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
|
||||
"integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
|
||||
"dev": true
|
||||
},
|
||||
"rechoir": {
|
||||
"version": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
||||
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
||||
"dev": true
|
||||
},
|
||||
"repeat-string": {
|
||||
"version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
|
||||
"dev": true
|
||||
},
|
||||
"require-uncached": {
|
||||
"version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz",
|
||||
"integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
|
||||
"integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz",
|
||||
"integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=",
|
||||
"dev": true
|
||||
},
|
||||
"restore-cursor": {
|
||||
"version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
|
||||
"integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
|
||||
"dev": true
|
||||
},
|
||||
"right-align": {
|
||||
"version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
|
||||
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
|
||||
"integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0="
|
||||
},
|
||||
"run-async": {
|
||||
"version": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
|
||||
"integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
|
||||
"dev": true
|
||||
},
|
||||
"rx-lite": {
|
||||
"version": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
|
||||
"integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
|
||||
"dev": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.7.tgz",
|
||||
"integrity": "sha1-svXHfvlxSPS09uImguELuoZnz/E=",
|
||||
"dev": true
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
|
||||
"integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
|
||||
"integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.0.tgz",
|
||||
"integrity": "sha1-8G9BFXtmTYYGn4S9vcmw2KsoFmc=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
|
||||
"dev": true
|
||||
},
|
||||
"table": {
|
||||
"version": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
|
||||
"integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz",
|
||||
"integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
"version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||
"dev": true
|
||||
},
|
||||
"through": {
|
||||
"version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
},
|
||||
"tryit": {
|
||||
"version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
|
||||
"integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
|
||||
"dev": true
|
||||
},
|
||||
"type-check": {
|
||||
"version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
|
||||
"dev": true
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.22.tgz",
|
||||
"integrity": "sha1-1Uk0d4qNoUkD+imjJvskwKtRoaA=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"uglify-to-browserify": {
|
||||
"version": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
|
||||
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"url2": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/url2/-/url2-0.0.0.tgz",
|
||||
"integrity": "sha1-Tqq9HVw6yQ1iq0SFyZhCKGWgSxo="
|
||||
},
|
||||
"user-home": {
|
||||
"version": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
|
||||
"integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||
"dev": true
|
||||
},
|
||||
"weak-map": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.0.tgz",
|
||||
"integrity": "sha1-tm5Wqd8L0lp2u/G1FNsSkIBhSjc="
|
||||
},
|
||||
"which": {
|
||||
"version": "https://registry.npmjs.org/which/-/which-1.2.14.tgz",
|
||||
"integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=",
|
||||
"dev": true
|
||||
},
|
||||
"window-size": {
|
||||
"version": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
|
||||
"integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write": {
|
||||
"version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz",
|
||||
"integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=",
|
||||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
"version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
|
||||
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
69
package.json
69
package.json
|
|
@ -1,32 +1,41 @@
|
|||
{
|
||||
"name": "mithril",
|
||||
"version": "1.0.0-rc.7",
|
||||
"description": "A framework for building brilliant applications",
|
||||
"author": "Leo Horie",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"repository": "lhorie/mithril.js",
|
||||
"scripts": {
|
||||
"dev": "node bundler/cli browser.js -o mithril.js -w",
|
||||
"build": "npm run build-browser & npm run build-min",
|
||||
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
||||
"lintdocs": "node docs/lint",
|
||||
"gendocs": "node docs/generate",
|
||||
"lint": "eslint .",
|
||||
"test": "node ospec/bin/ospec",
|
||||
"cover": "istanbul cover --print both ospec/bin/ospec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^2.10.2",
|
||||
"istanbul": "^0.4.3",
|
||||
"marked": "^0.3.6"
|
||||
},
|
||||
"publishConfig": {
|
||||
"tag": "rewrite"
|
||||
},
|
||||
"bin": {
|
||||
"ospec": "./ospec/bin/ospec",
|
||||
"bundle": "./bundler/bin/bundle"
|
||||
}
|
||||
"name": "mithril",
|
||||
"version": "1.1.1",
|
||||
"description": "A framework for building brilliant applications",
|
||||
"author": "Leo Horie",
|
||||
"license": "MIT",
|
||||
"main": "mithril.js",
|
||||
"repository": "MithrilJS/mithril.js",
|
||||
"scripts": {
|
||||
"dev": "node bundler/cli browser.js -o mithril.js -w",
|
||||
"build": "npm run build-browser & npm run build-min",
|
||||
"build-browser": "node bundler/cli browser.js -o mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -o mithril.min.js -m",
|
||||
"lintdocs": "node docs/lint",
|
||||
"gendocs": "node docs/generate",
|
||||
"lint": "eslint . || true",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"perf": "node performance/test-perf.js",
|
||||
"test": "node ospec/bin/ospec",
|
||||
"posttest": "npm run lint",
|
||||
"cover": "istanbul cover --print both ospec/bin/ospec",
|
||||
"release": "npm version -m 'v%s'",
|
||||
"preversion": "npm run test",
|
||||
"version": "npm run build && git add mithril.js mithril.min.js",
|
||||
"postversion": "git push --follow-tags"
|
||||
},
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"eslint": "^3.19.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"marked": "^0.3.6"
|
||||
},
|
||||
"bin": {
|
||||
"ospec": "./ospec/bin/ospec",
|
||||
"bundle": "./bundler/bin/bundle"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alrra/travis-scripts": "^3.0.1",
|
||||
"gh-pages": "^0.12.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
performance/index.html
Normal file
23
performance/index.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../module/module.js"></script>
|
||||
<script src="../ospec/ospec.js"></script>
|
||||
<script src="../querystring/parse.js"></script>
|
||||
<script src="../test-utils/parseURL.js"></script>
|
||||
<script src="../test-utils/callAsync.js"></script>
|
||||
<script src="../test-utils/domMock.js"></script>
|
||||
<script src="../test-utils/pushStateMock.js"></script>
|
||||
<script src="../test-utils/xhrMock.js"></script>
|
||||
<script src="../test-utils/browserMock.js"></script>
|
||||
<script src="../mithril.js"></script>
|
||||
<script src="../node_modules/lodash/lodash.js"></script>
|
||||
<script src="../node_modules/benchmark/benchmark.js"></script>
|
||||
<script src="test-perf.js"></script>
|
||||
|
||||
<!-- test-perf runs itself for CLI usage -->
|
||||
</body>
|
||||
</html>
|
||||
336
performance/test-perf.js
Normal file
336
performance/test-perf.js
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/* global Benchmark */
|
||||
"use strict"
|
||||
|
||||
/* Based off of preact's perf tests, so including their MIT license */
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Jason Miller
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
var browserMock = require("../test-utils/browserMock")
|
||||
|
||||
// Do this silly dance so browser testing works
|
||||
var B = typeof Benchmark === "undefined" ? require("benchmark") : Benchmark
|
||||
|
||||
var m, scratch;
|
||||
|
||||
// set up browser env on before running tests
|
||||
var doc = typeof document !== "undefined" ? document : null
|
||||
|
||||
if(!doc) {
|
||||
var mock = browserMock()
|
||||
if (typeof global !== "undefined") { global.window = mock }
|
||||
|
||||
doc = mock.document
|
||||
}
|
||||
|
||||
// Have to include mithril AFTER browser polyfill is set up
|
||||
m = require("../mithril") // eslint-disable-line global-require
|
||||
|
||||
scratch = doc.createElement("div");
|
||||
|
||||
(doc.body || doc.documentElement).appendChild(scratch)
|
||||
|
||||
// Initialize benchmark suite
|
||||
var suite = new B.Suite("mithril perf")
|
||||
|
||||
suite.on("start", function() {
|
||||
this.start = Date.now();
|
||||
})
|
||||
|
||||
suite.on("cycle", function(e) {
|
||||
console.log(e.target.toString())
|
||||
|
||||
scratch.innerHTML = ""
|
||||
})
|
||||
|
||||
suite.on("complete", function() {
|
||||
console.log("Completed perf tests in " + (Date.now() - this.start) + "ms")
|
||||
})
|
||||
|
||||
suite.on("error", console.error.bind(console))
|
||||
|
||||
suite.add({
|
||||
name : "rerender without changes",
|
||||
onStart : function() {
|
||||
this.vdom = m("div", {class: "foo bar", "data-foo": "bar", p: 2},
|
||||
m("header",
|
||||
m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"),
|
||||
m("nav",
|
||||
m("a", {href: "/foo"}, "Foo"),
|
||||
m("a", {href: "/bar"}, "Bar")
|
||||
)
|
||||
),
|
||||
m("main",
|
||||
m("form", {onSubmit: function onSubmit() {}},
|
||||
m("input", {type: "checkbox", checked: true}),
|
||||
m("input", {type: "checkbox", checked: false}),
|
||||
m("fieldset",
|
||||
m("label",
|
||||
m("input", {type: "radio", checked: true})
|
||||
),
|
||||
m("label",
|
||||
m("input", {type: "radio"})
|
||||
)
|
||||
),
|
||||
m("button-bar",
|
||||
m("button",
|
||||
{style: "width:10px; height:10px; border:1px solid #FFF;"},
|
||||
"Normal CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "top:0 ; right: 20"},
|
||||
"Poor CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true},
|
||||
"Poorer CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: {margin: 0, padding: "10px", overflow: "visible"}},
|
||||
"Object CSS"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
fn : function() {
|
||||
m.render(scratch, this.vdom)
|
||||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "construct large VDOM tree",
|
||||
|
||||
onStart : function() {
|
||||
var fields = []
|
||||
|
||||
for(var i=100; i--;) {
|
||||
fields.push((i * 999).toString(36))
|
||||
}
|
||||
|
||||
this.fields = fields;
|
||||
},
|
||||
|
||||
fn : function () {
|
||||
m("div", {class: "foo bar", "data-foo": "bar", p: 2},
|
||||
m("header",
|
||||
m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"),
|
||||
m("nav",
|
||||
m("a", {href: "/foo"}, "Foo"),
|
||||
m("a", {href: "/bar"}, "Bar")
|
||||
)
|
||||
),
|
||||
m("main",
|
||||
m("form",
|
||||
{onSubmit: function onSubmit() {}},
|
||||
m("input", {type: "checkbox", checked: true}),
|
||||
m("input", {type: "checkbox"}),
|
||||
m("fieldset",
|
||||
this.fields.map(function (field) {
|
||||
return m("label",
|
||||
field,
|
||||
":",
|
||||
m("input", {placeholder: field})
|
||||
)
|
||||
})
|
||||
),
|
||||
m("button-bar",
|
||||
m("button",
|
||||
{style: "width:10px; height:10px; border:1px solid #FFF;"},
|
||||
"Normal CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "top:0 ; right: 20"},
|
||||
"Poor CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true},
|
||||
"Poorer CSS"
|
||||
),
|
||||
m("button",
|
||||
{style: {margin: 0, padding: "10px", overflow: "visible"}},
|
||||
"Object CSS"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "mutate styles/properties",
|
||||
|
||||
onStart : function () {
|
||||
var counter = 0
|
||||
var keyLooper = function (n) { return function (c) { return c % n ? (c + "px") : c } }
|
||||
var get = function (obj, i) { return obj[i%obj.length] }
|
||||
var classes = ["foo", "foo bar", "", "baz-bat", null, "fooga"]
|
||||
var styles = []
|
||||
var multivalue = ["0 1px", "0 0 1px 0", "0", "1px", "20px 10px", "7em 5px", "1px 0 5em 2px"]
|
||||
var stylekeys = [
|
||||
["left", keyLooper(3)],
|
||||
["top", keyLooper(2)],
|
||||
["margin", function (c) { return get(multivalue, c).replace("1px", c+"px") }],
|
||||
["padding", function (c) { return get(multivalue, c) }],
|
||||
["position", function (c) { return c%5 ? c%2 ? "absolute" : "relative" : null }],
|
||||
["display", function (c) { return c%10 ? c%2 ? "block" : "inline" : "none" }],
|
||||
["color", function (c) { return ("rgba(" + (c%255) + ", " + (255 - c%255) + ", " + (50+c%150) + ", " + (c%50/50) + ")") }],
|
||||
["border", function (c) { return c%5 ? ((c%10) + "px " + (c%2?"solid":"dotted") + " " + (stylekeys[6][1](c))) : "" }]
|
||||
]
|
||||
var i, j, style, conf
|
||||
|
||||
for (i=0; i<1000; i++) {
|
||||
style = {}
|
||||
for (j=0; j<i%10; j++) {
|
||||
conf = get(stylekeys, ++counter)
|
||||
style[conf[0]] = conf[1](counter)
|
||||
}
|
||||
styles[i] = style
|
||||
}
|
||||
|
||||
this.count = 0
|
||||
this.app = function (index) {
|
||||
return m("div",
|
||||
{
|
||||
class: get(classes, index),
|
||||
"data-index": index,
|
||||
title: index.toString(36)
|
||||
},
|
||||
m("input", {type: "checkbox", checked: index % 3 == 0}),
|
||||
m("input", {value: "test " + (Math.floor(index / 4)), disabled: index % 10 ? null : true}),
|
||||
m("div", {class: get(classes, index * 11)},
|
||||
m("p", {style: get(styles, index)}, "p1"),
|
||||
m("p", {style: get(styles, index + 1)}, "p2"),
|
||||
m("p", {style: get(styles, index * 2)}, "p3"),
|
||||
m("p", {style: get(styles, index * 3 + 1)}, "p4")
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
fn : function () {
|
||||
m.render(scratch, this.app(++this.count))
|
||||
}
|
||||
})
|
||||
|
||||
// Shared components for node recyling benchmarks
|
||||
var Header = {
|
||||
view : function () {
|
||||
return m("header",
|
||||
m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"),
|
||||
m("nav",
|
||||
m("a", {href: "/foo"}, "Foo"),
|
||||
m("a", {href: "/bar"}, "Bar")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var Form = {
|
||||
view : function () {
|
||||
return m("form", {onSubmit: function onSubmit() {}},
|
||||
m("input", {type: "checkbox", checked: true}),
|
||||
m("input", {type: "checkbox", checked: false}),
|
||||
m("fieldset",
|
||||
m("label",
|
||||
m("input", {type: "radio", checked: true})
|
||||
),
|
||||
m("label",
|
||||
m("input", {type: "radio"})
|
||||
)
|
||||
),
|
||||
m(ButtonBar, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var ButtonBar = {
|
||||
view : function () {
|
||||
return m("button-bar",
|
||||
m(Button,
|
||||
{style: "width:10px; height:10px; border:1px solid #FFF;"},
|
||||
"Normal CSS"
|
||||
),
|
||||
m(Button,
|
||||
{style: "top:0 ; right: 20"},
|
||||
"Poor CSS"
|
||||
),
|
||||
m(Button,
|
||||
{style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true},
|
||||
"Poorer CSS"
|
||||
),
|
||||
m(Button,
|
||||
{style: {margin: 0, padding: "10px", overflow: "visible"}},
|
||||
"Object CSS"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var Button = {
|
||||
view : function (vnode) {
|
||||
return m("button", vnode.attrs, vnode.children)
|
||||
}
|
||||
}
|
||||
|
||||
var Main = {
|
||||
view : function () {
|
||||
return m(Form)
|
||||
}
|
||||
}
|
||||
|
||||
var Root = {
|
||||
view : function () {
|
||||
return m("div",
|
||||
{class: "foo bar", "data-foo": "bar", p: 2},
|
||||
m(Header, null),
|
||||
m(Main, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suite.add({
|
||||
name : "repeated trees (recycling)",
|
||||
fn : function () {
|
||||
m.render(scratch, [m(Root)])
|
||||
m.render(scratch, [])
|
||||
}
|
||||
})
|
||||
|
||||
suite.add({
|
||||
name : "repeated trees (no recycling)",
|
||||
fn : function () {
|
||||
m.render(scratch, [m(Root)])
|
||||
m.render(scratch, [])
|
||||
|
||||
// Second empty render is to clear out the pool of nodes
|
||||
// so that there's nothing that can be recycled
|
||||
m.render(scratch, [])
|
||||
}
|
||||
})
|
||||
|
||||
suite.run({
|
||||
async : true
|
||||
})
|
||||
|
|
@ -57,7 +57,7 @@ o.spec("promise", function() {
|
|||
o.spec("resolve", function() {
|
||||
o("resolves once", function(done) {
|
||||
var callCount = 0
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
resolve(1)
|
||||
resolve(2)
|
||||
callAsync(function() {resolve(3)})
|
||||
|
|
@ -89,7 +89,7 @@ o.spec("promise", function() {
|
|||
var promise = Promise.resolve()
|
||||
|
||||
state = 1
|
||||
promise.then(function(value) {
|
||||
promise.then(function() {
|
||||
o(state).equals(2)
|
||||
done()
|
||||
})
|
||||
|
|
@ -104,7 +104,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("resolves asynchronously via executor", function(done) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
callAsync(function() {resolve(1)})
|
||||
})
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ o.spec("promise", function() {
|
|||
var promise = Promise.reject()
|
||||
|
||||
state = 1
|
||||
promise.then(null, function(value) {
|
||||
promise.then(null, function() {
|
||||
o(state).equals(2)
|
||||
done()
|
||||
})
|
||||
|
|
@ -232,7 +232,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("rejects via executor on error", function(done) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function() {
|
||||
throw 1
|
||||
})
|
||||
|
||||
|
|
@ -281,7 +281,7 @@ o.spec("promise", function() {
|
|||
}).then(done)
|
||||
})
|
||||
o("absorbs resolved promise in executor resolve", function(done) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
var p = Promise.resolve(1)
|
||||
resolve(p)
|
||||
})
|
||||
|
|
@ -310,7 +310,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("absorbs rejected promise in executor resolve", function(done) {
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
resolve(Promise.reject(1))
|
||||
})
|
||||
|
||||
|
|
@ -330,7 +330,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("absorbs pending promise that resolves via static resolver", function(done) {
|
||||
var pending = new Promise(function(resolve, reject) {
|
||||
var pending = new Promise(function(resolve) {
|
||||
setTimeout(function() {resolve(1)}, 10)
|
||||
})
|
||||
var promise = Promise.resolve(pending)
|
||||
|
|
@ -341,10 +341,10 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("absorbs pending promise that resolves in executor resolve", function(done) {
|
||||
var pending = new Promise(function(resolve, reject) {
|
||||
var pending = new Promise(function(resolve) {
|
||||
setTimeout(function() {resolve(1)}, 10)
|
||||
})
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
resolve(pending)
|
||||
})
|
||||
|
||||
|
|
@ -354,7 +354,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("absorbs pending promise that resolves on fulfillment", function(done) {
|
||||
var pending = new Promise(function(resolve, reject) {
|
||||
var pending = new Promise(function(resolve) {
|
||||
setTimeout(function() {resolve(1)}, 10)
|
||||
})
|
||||
var promise = Promise.resolve()
|
||||
|
|
@ -381,7 +381,7 @@ o.spec("promise", function() {
|
|||
var pending = new Promise(function(resolve, reject) {
|
||||
setTimeout(function() {reject(1)}, 10)
|
||||
})
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
var promise = new Promise(function(resolve) {
|
||||
resolve(pending)
|
||||
})
|
||||
|
||||
|
|
@ -521,7 +521,7 @@ o.spec("promise", function() {
|
|||
o.spec("race", function() {
|
||||
o("resolves to first resolved", function(done) {
|
||||
var a = Promise.resolve(1)
|
||||
var b = new Promise(function(resolve, reject) {
|
||||
var b = new Promise(function(resolve) {
|
||||
callAsync(function() {resolve(2)})
|
||||
})
|
||||
Promise.race([a, b]).then(function(value) {
|
||||
|
|
@ -542,7 +542,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
o.spec("all", function() {
|
||||
o("resolves to array", function(done) {
|
||||
var a = new Promise(function(resolve, reject) {
|
||||
var a = new Promise(function(resolve) {
|
||||
callAsync(function() {resolve(1)})
|
||||
})
|
||||
var b = Promise.resolve(2)
|
||||
|
|
@ -558,7 +558,7 @@ o.spec("promise", function() {
|
|||
})
|
||||
})
|
||||
o("resolves non-promise to itself", function(done) {
|
||||
var a = new Promise(function(resolve, reject) {
|
||||
var a = new Promise(function(resolve) {
|
||||
callAsync(function() {resolve(1)})
|
||||
})
|
||||
var b = Promise.resolve(2)
|
||||
|
|
@ -584,18 +584,18 @@ o.spec("promise", function() {
|
|||
var readCount = 0
|
||||
var promise = Promise.resolve(1).then(function() {
|
||||
return Object.create(null, {
|
||||
then: {
|
||||
get: function () {
|
||||
++readCount
|
||||
return function(onFulfilled) {
|
||||
onFulfilled()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
then: {
|
||||
get: function () {
|
||||
++readCount
|
||||
return function(onFulfilled) {
|
||||
onFulfilled()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
promise.then(function(value) {
|
||||
promise.then(function() {
|
||||
o(readCount).equals(1)
|
||||
done()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module.exports = function(object) {
|
|||
for (var key in object) {
|
||||
destructure(key, object[key])
|
||||
}
|
||||
|
||||
return args.join("&")
|
||||
|
||||
function destructure(key, value) {
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
module.exports = require("./api/redraw")(window)
|
||||
"use strict"
|
||||
|
||||
module.exports = require("./api/redraw")(window)
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
module.exports = require("./render/render")(window)
|
||||
"use strict"
|
||||
|
||||
module.exports = require("./render/render")(window)
|
||||
|
|
|
|||
|
|
@ -4,66 +4,97 @@ var Vnode = require("../render/vnode")
|
|||
|
||||
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
|
||||
var selectorCache = {}
|
||||
var hasOwn = {}.hasOwnProperty
|
||||
|
||||
function compileSelector(selector) {
|
||||
var match, tag = "div", classes = [], attrs = {}
|
||||
while (match = selectorParser.exec(selector)) {
|
||||
var type = match[1], value = match[2]
|
||||
if (type === "" && value !== "") tag = value
|
||||
else if (type === "#") attrs.id = value
|
||||
else if (type === ".") classes.push(value)
|
||||
else if (match[3][0] === "[") {
|
||||
var attrValue = match[6]
|
||||
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
|
||||
if (match[4] === "class") classes.push(attrValue)
|
||||
else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attrs.className = classes.join(" ")
|
||||
return selectorCache[selector] = {tag: tag, attrs: attrs}
|
||||
}
|
||||
|
||||
function execSelector(state, attrs, children) {
|
||||
var hasAttrs = false, childList, text
|
||||
var className = attrs.className || attrs.class
|
||||
|
||||
for (var key in state.attrs) {
|
||||
if (hasOwn.call(state.attrs, key)) {
|
||||
attrs[key] = state.attrs[key]
|
||||
}
|
||||
}
|
||||
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
|
||||
if (state.attrs.className != null) {
|
||||
attrs.className = state.attrs.className + " " + className
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in attrs) {
|
||||
if (hasOwn.call(attrs, key) && key !== "key") {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
|
||||
text = children[0].children
|
||||
} else {
|
||||
childList = children
|
||||
}
|
||||
|
||||
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
|
||||
}
|
||||
|
||||
function hyperscript(selector) {
|
||||
if (selector == null || typeof selector !== "string" && selector.view == null) {
|
||||
// Because sloppy mode sucks
|
||||
var attrs = arguments[1], start = 2, children
|
||||
|
||||
if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
|
||||
throw Error("The selector must be either a string or a component.");
|
||||
}
|
||||
|
||||
if (typeof selector === "string" && selectorCache[selector] === undefined) {
|
||||
var match, tag, classes = [], attributes = {}
|
||||
while (match = selectorParser.exec(selector)) {
|
||||
var type = match[1], value = match[2]
|
||||
if (type === "" && value !== "") tag = value
|
||||
else if (type === "#") attributes.id = value
|
||||
else if (type === ".") classes.push(value)
|
||||
else if (match[3][0] === "[") {
|
||||
var attrValue = match[6]
|
||||
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
|
||||
if (match[4] === "class") classes.push(attrValue)
|
||||
else attributes[match[4]] = attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attributes.className = classes.join(" ")
|
||||
selectorCache[selector] = function(attrs, children) {
|
||||
var hasAttrs = false, childList, text
|
||||
var className = attrs.className || attrs.class
|
||||
for (var key in attributes) attrs[key] = attributes[key]
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
|
||||
}
|
||||
for (var key in attrs) {
|
||||
if (key !== "key") {
|
||||
hasAttrs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
|
||||
else childList = children
|
||||
if (typeof selector === "string") {
|
||||
var cached = selectorCache[selector] || compileSelector(selector)
|
||||
}
|
||||
|
||||
return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined)
|
||||
}
|
||||
if (attrs == null) {
|
||||
attrs = {}
|
||||
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
|
||||
attrs = {}
|
||||
start = 1
|
||||
}
|
||||
var attrs, children, childrenIndex
|
||||
if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) {
|
||||
attrs = arguments[1]
|
||||
childrenIndex = 2
|
||||
}
|
||||
else childrenIndex = 1
|
||||
if (arguments.length === childrenIndex + 1) {
|
||||
children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
|
||||
}
|
||||
else {
|
||||
|
||||
if (arguments.length === start + 1) {
|
||||
children = arguments[start]
|
||||
if (!Array.isArray(children)) children = [children]
|
||||
} else {
|
||||
children = []
|
||||
for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i])
|
||||
while (start < arguments.length) children.push(arguments[start++])
|
||||
}
|
||||
|
||||
if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children))
|
||||
var normalized = Vnode.normalizeChildren(children)
|
||||
|
||||
return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined)
|
||||
if (typeof selector === "string") {
|
||||
return execSelector(cached, attrs, normalized)
|
||||
} else {
|
||||
return Vnode(selector, attrs.key, attrs, normalized)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = hyperscript
|
||||
|
|
|
|||
211
render/render.js
211
render/render.js
|
|
@ -6,38 +6,50 @@ module.exports = function($window) {
|
|||
var $doc = $window.document
|
||||
var $emptyFragment = $doc.createDocumentFragment()
|
||||
|
||||
var nameSpace = {
|
||||
svg: "http://www.w3.org/2000/svg",
|
||||
math: "http://www.w3.org/1998/Math/MathML"
|
||||
}
|
||||
|
||||
var onevent
|
||||
function setEventCallback(callback) {return onevent = callback}
|
||||
|
||||
function getNameSpace(vnode) {
|
||||
return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag]
|
||||
}
|
||||
|
||||
//create
|
||||
function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
|
||||
for (var i = start; i < end; i++) {
|
||||
var vnode = vnodes[i]
|
||||
if (vnode != null) {
|
||||
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
|
||||
createNode(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
}
|
||||
function createNode(vnode, hooks, ns) {
|
||||
function createNode(parent, vnode, hooks, ns, nextSibling) {
|
||||
var tag = vnode.tag
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
if (typeof tag === "string") {
|
||||
vnode.state = {}
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
switch (tag) {
|
||||
case "#": return createText(vnode)
|
||||
case "<": return createHTML(vnode)
|
||||
case "[": return createFragment(vnode, hooks, ns)
|
||||
default: return createElement(vnode, hooks, ns)
|
||||
case "#": return createText(parent, vnode, nextSibling)
|
||||
case "<": return createHTML(parent, vnode, nextSibling)
|
||||
case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
|
||||
default: return createElement(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
else return createComponent(vnode, hooks, ns)
|
||||
else return createComponent(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
function createText(vnode) {
|
||||
return vnode.dom = $doc.createTextNode(vnode.children)
|
||||
function createText(parent, vnode, nextSibling) {
|
||||
vnode.dom = $doc.createTextNode(vnode.children)
|
||||
insertNode(parent, vnode.dom, nextSibling)
|
||||
return vnode.dom
|
||||
}
|
||||
function createHTML(vnode) {
|
||||
function createHTML(parent, vnode, nextSibling) {
|
||||
var match = vnode.children.match(/^\s*?<(\w+)/im) || []
|
||||
var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match[1]] || "div"
|
||||
var temp = $doc.createElement(parent)
|
||||
var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match[1]] || "div"
|
||||
var temp = $doc.createElement(parent1)
|
||||
|
||||
temp.innerHTML = vnode.children
|
||||
vnode.dom = temp.firstChild
|
||||
|
|
@ -47,9 +59,10 @@ module.exports = function($window) {
|
|||
while (child = temp.firstChild) {
|
||||
fragment.appendChild(child)
|
||||
}
|
||||
insertNode(parent, fragment, nextSibling)
|
||||
return fragment
|
||||
}
|
||||
function createFragment(vnode, hooks, ns) {
|
||||
function createFragment(parent, vnode, hooks, ns, nextSibling) {
|
||||
var fragment = $doc.createDocumentFragment()
|
||||
if (vnode.children != null) {
|
||||
var children = vnode.children
|
||||
|
|
@ -57,18 +70,16 @@ module.exports = function($window) {
|
|||
}
|
||||
vnode.dom = fragment.firstChild
|
||||
vnode.domSize = fragment.childNodes.length
|
||||
insertNode(parent, fragment, nextSibling)
|
||||
return fragment
|
||||
}
|
||||
function createElement(vnode, hooks, ns) {
|
||||
function createElement(parent, vnode, hooks, ns, nextSibling) {
|
||||
var tag = vnode.tag
|
||||
switch (vnode.tag) {
|
||||
case "svg": ns = "http://www.w3.org/2000/svg"; break
|
||||
case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
|
||||
}
|
||||
|
||||
var attrs = vnode.attrs
|
||||
var is = attrs && attrs.is
|
||||
|
||||
ns = getNameSpace(vnode) || ns
|
||||
|
||||
var element = ns ?
|
||||
is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
|
||||
is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
|
||||
|
|
@ -78,6 +89,8 @@ module.exports = function($window) {
|
|||
setAttrs(vnode, attrs, ns)
|
||||
}
|
||||
|
||||
insertNode(parent, element, nextSibling)
|
||||
|
||||
if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
|
||||
setContentEditable(vnode)
|
||||
}
|
||||
|
|
@ -94,19 +107,34 @@ module.exports = function($window) {
|
|||
}
|
||||
return element
|
||||
}
|
||||
function createComponent(vnode, hooks, ns) {
|
||||
vnode.state = Object.create(vnode.tag)
|
||||
var view = vnode.tag.view
|
||||
if (view.reentrantLock != null) return $emptyFragment
|
||||
view.reentrantLock = true
|
||||
initLifecycle(vnode.tag, vnode, hooks)
|
||||
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
|
||||
view.reentrantLock = null
|
||||
function initComponent(vnode, hooks) {
|
||||
var sentinel
|
||||
if (typeof vnode.tag.view === "function") {
|
||||
vnode.state = Object.create(vnode.tag)
|
||||
sentinel = vnode.state.view
|
||||
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
|
||||
sentinel.$$reentrantLock$$ = true
|
||||
} else {
|
||||
vnode.state = void 0
|
||||
sentinel = vnode.tag
|
||||
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
|
||||
sentinel.$$reentrantLock$$ = true
|
||||
vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
|
||||
}
|
||||
vnode._state = vnode.state
|
||||
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
|
||||
initLifecycle(vnode._state, vnode, hooks)
|
||||
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
||||
sentinel.$$reentrantLock$$ = null
|
||||
}
|
||||
function createComponent(parent, vnode, hooks, ns, nextSibling) {
|
||||
initComponent(vnode, hooks)
|
||||
if (vnode.instance != null) {
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments")
|
||||
var element = createNode(vnode.instance, hooks, ns)
|
||||
var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
||||
vnode.dom = vnode.instance.dom
|
||||
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
|
||||
insertNode(parent, element, nextSibling)
|
||||
return element
|
||||
}
|
||||
else {
|
||||
|
|
@ -116,9 +144,9 @@ module.exports = function($window) {
|
|||
}
|
||||
|
||||
//update
|
||||
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
||||
function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
|
||||
if (old === vnodes || old == null && vnodes == null) return
|
||||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
|
||||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
||||
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
|
||||
else {
|
||||
if (old.length === vnodes.length) {
|
||||
|
|
@ -132,15 +160,18 @@ module.exports = function($window) {
|
|||
if (isUnkeyed) {
|
||||
for (var i = 0; i < old.length; i++) {
|
||||
if (old[i] === vnodes[i]) continue
|
||||
else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
|
||||
else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
|
||||
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
|
||||
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
var recycling = isRecyclable(old, vnodes)
|
||||
if (recycling) old = old.concat(old.pool)
|
||||
recycling = recycling || isRecyclable(old, vnodes)
|
||||
if (recycling) {
|
||||
var pool = old.pool
|
||||
old = old.concat(old.pool)
|
||||
}
|
||||
|
||||
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
|
|
@ -149,8 +180,9 @@ module.exports = function($window) {
|
|||
else if (o == null) oldStart++
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
oldStart++, start++
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
|
||||
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
|
||||
}
|
||||
else {
|
||||
|
|
@ -159,7 +191,8 @@ module.exports = function($window) {
|
|||
else if (o == null) oldEnd--
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
|
||||
if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
|
||||
oldEnd--, start++
|
||||
}
|
||||
|
|
@ -172,7 +205,8 @@ module.exports = function($window) {
|
|||
else if (o == null) oldEnd--
|
||||
else if (v == null) end--
|
||||
else if (o.key === v.key) {
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
|
||||
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
|
||||
if (o.dom != null) nextSibling = o.dom
|
||||
oldEnd--, end--
|
||||
|
|
@ -183,14 +217,14 @@ module.exports = function($window) {
|
|||
var oldIndex = map[v.key]
|
||||
if (oldIndex != null) {
|
||||
var movable = old[oldIndex]
|
||||
var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
|
||||
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
|
||||
insertNode(parent, toFragment(movable), nextSibling)
|
||||
old[oldIndex].skip = true
|
||||
if (movable.dom != null) nextSibling = movable.dom
|
||||
}
|
||||
else {
|
||||
var dom = createNode(v, hooks, undefined)
|
||||
insertNode(parent, dom, nextSibling)
|
||||
var dom = createNode(parent, v, hooks, ns, nextSibling)
|
||||
nextSibling = dom
|
||||
}
|
||||
}
|
||||
|
|
@ -206,24 +240,29 @@ module.exports = function($window) {
|
|||
var oldTag = old.tag, tag = vnode.tag
|
||||
if (oldTag === tag) {
|
||||
vnode.state = old.state
|
||||
vnode._state = old._state
|
||||
vnode.events = old.events
|
||||
if (shouldUpdate(vnode, old)) return
|
||||
if (vnode.attrs != null) {
|
||||
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
|
||||
}
|
||||
if (!recycling && shouldNotUpdate(vnode, old)) return
|
||||
if (typeof oldTag === "string") {
|
||||
if (vnode.attrs != null) {
|
||||
if (recycling) {
|
||||
vnode.state = {}
|
||||
initLifecycle(vnode.attrs, vnode, hooks)
|
||||
}
|
||||
else updateLifecycle(vnode.attrs, vnode, hooks)
|
||||
}
|
||||
switch (oldTag) {
|
||||
case "#": updateText(old, vnode); break
|
||||
case "<": updateHTML(parent, old, vnode, nextSibling); break
|
||||
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
|
||||
default: updateElement(old, vnode, hooks, ns)
|
||||
case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
|
||||
default: updateElement(old, vnode, recycling, hooks, ns)
|
||||
}
|
||||
}
|
||||
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
|
||||
}
|
||||
else {
|
||||
removeNode(old, null)
|
||||
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
|
||||
createNode(parent, vnode, hooks, ns, nextSibling)
|
||||
}
|
||||
}
|
||||
function updateText(old, vnode) {
|
||||
|
|
@ -235,12 +274,12 @@ module.exports = function($window) {
|
|||
function updateHTML(parent, old, vnode, nextSibling) {
|
||||
if (old.children !== vnode.children) {
|
||||
toFragment(old)
|
||||
insertNode(parent, createHTML(vnode), nextSibling)
|
||||
createHTML(parent, vnode, nextSibling)
|
||||
}
|
||||
else vnode.dom = old.dom, vnode.domSize = old.domSize
|
||||
}
|
||||
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
|
||||
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
|
||||
function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
|
||||
updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
|
||||
var domSize = 0, children = vnode.children
|
||||
vnode.dom = null
|
||||
if (children != null) {
|
||||
|
|
@ -254,12 +293,10 @@ module.exports = function($window) {
|
|||
if (domSize !== 1) vnode.domSize = domSize
|
||||
}
|
||||
}
|
||||
function updateElement(old, vnode, hooks, ns) {
|
||||
function updateElement(old, vnode, recycling, hooks, ns) {
|
||||
var element = vnode.dom = old.dom
|
||||
switch (vnode.tag) {
|
||||
case "svg": ns = "http://www.w3.org/2000/svg"; break
|
||||
case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
|
||||
}
|
||||
ns = getNameSpace(vnode) || ns
|
||||
|
||||
if (vnode.tag === "textarea") {
|
||||
if (vnode.attrs == null) vnode.attrs = {}
|
||||
if (vnode.text != null) {
|
||||
|
|
@ -277,14 +314,20 @@ module.exports = function($window) {
|
|||
else {
|
||||
if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
|
||||
if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
|
||||
updateNodes(element, old.children, vnode.children, hooks, null, ns)
|
||||
updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
|
||||
}
|
||||
}
|
||||
function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
|
||||
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
|
||||
updateLifecycle(vnode.tag, vnode, hooks, recycling)
|
||||
if (recycling) {
|
||||
initComponent(vnode, hooks)
|
||||
} else {
|
||||
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
|
||||
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
|
||||
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
|
||||
updateLifecycle(vnode._state, vnode, hooks)
|
||||
}
|
||||
if (vnode.instance != null) {
|
||||
if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling)
|
||||
if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
|
||||
else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
|
||||
vnode.dom = vnode.instance.dom
|
||||
vnode.domSize = vnode.instance.domSize
|
||||
|
|
@ -367,15 +410,15 @@ module.exports = function($window) {
|
|||
}
|
||||
function removeNode(vnode, context) {
|
||||
var expected = 1, called = 0
|
||||
if (vnode.attrs && vnode.attrs.onbeforeremove) {
|
||||
if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
|
||||
var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
|
||||
if (result != null && typeof result.then === "function") {
|
||||
expected++
|
||||
result.then(continuation, continuation)
|
||||
}
|
||||
}
|
||||
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
|
||||
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
|
||||
var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
|
||||
if (result != null && typeof result.then === "function") {
|
||||
expected++
|
||||
result.then(continuation, continuation)
|
||||
|
|
@ -407,8 +450,8 @@ module.exports = function($window) {
|
|||
if (parent != null) parent.removeChild(node)
|
||||
}
|
||||
function onremove(vnode) {
|
||||
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
|
||||
if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
|
||||
if (vnode.instance != null) onremove(vnode.instance)
|
||||
else {
|
||||
var children = vnode.children
|
||||
|
|
@ -437,12 +480,26 @@ module.exports = function($window) {
|
|||
else if (key[0] === "o" && key[1] === "n" && typeof value === "function") updateEvent(vnode, key, value)
|
||||
else if (key === "style") updateStyle(element, old, value)
|
||||
else if (key in element && !isAttribute(key) && ns === undefined && !isCustomElement(vnode)) {
|
||||
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
||||
if (vnode.tag === "input" && key === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "select" && key === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
|
||||
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "option" && key === "value" && vnode.dom.value === value) return
|
||||
if (key === "value") {
|
||||
var normalized = "" + value // eslint-disable-line no-implicit-coercion
|
||||
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome
|
||||
if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return
|
||||
//setting select[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "select") {
|
||||
if (value === null) {
|
||||
if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return
|
||||
} else {
|
||||
if (old !== null && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return
|
||||
}
|
||||
}
|
||||
//setting option[value] to same value while having select open blinks select dropdown in Chrome
|
||||
if (vnode.tag === "option" && old != null && vnode.dom.value === normalized) return
|
||||
}
|
||||
// If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur.
|
||||
if (vnode.tag === "input" && key === "type") {
|
||||
element.setAttribute(key, value)
|
||||
return
|
||||
}
|
||||
element[key] = value
|
||||
}
|
||||
else {
|
||||
|
|
@ -536,14 +593,13 @@ module.exports = function($window) {
|
|||
if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
|
||||
if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
|
||||
}
|
||||
function updateLifecycle(source, vnode, hooks, recycling) {
|
||||
if (recycling) initLifecycle(source, vnode, hooks)
|
||||
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
|
||||
function updateLifecycle(source, vnode, hooks) {
|
||||
if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
|
||||
}
|
||||
function shouldUpdate(vnode, old) {
|
||||
function shouldNotUpdate(vnode, old) {
|
||||
var forceVnodeUpdate, forceComponentUpdate
|
||||
if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
|
||||
if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
|
||||
vnode.dom = old.dom
|
||||
vnode.domSize = old.domSize
|
||||
|
|
@ -557,12 +613,13 @@ module.exports = function($window) {
|
|||
if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
|
||||
var hooks = []
|
||||
var active = $doc.activeElement
|
||||
var namespace = dom.namespaceURI
|
||||
|
||||
// First time rendering into a node clears it out
|
||||
if (dom.vnodes == null) dom.textContent = ""
|
||||
|
||||
if (!Array.isArray(vnodes)) vnodes = [vnodes]
|
||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
|
||||
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace)
|
||||
dom.vnodes = vnodes
|
||||
for (var i = 0; i < hooks.length; i++) hooks[i]()
|
||||
if ($doc.activeElement !== active) active.focus()
|
||||
|
|
|
|||
|
|
@ -8,6 +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/components.js"></script>
|
||||
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
|
|
|
|||
|
|
@ -11,20 +11,60 @@ o.spec("attributes", function() {
|
|||
root = $window.document.body
|
||||
render = vdom($window).render
|
||||
})
|
||||
o.spec("basics", function() {
|
||||
o("works (create/update/remove)", function() {
|
||||
|
||||
var a = {tag: "div", attrs: {}}
|
||||
var b = {tag: "div", attrs: {id: "test"}}
|
||||
var c = {tag: "div", attrs: {}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.hasAttribute("id")).equals(false)
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(b.dom.getAttribute("id")).equals("test")
|
||||
|
||||
render(root, [c]);
|
||||
|
||||
o(c.dom.hasAttribute("id")).equals(false)
|
||||
})
|
||||
o("undefined attr is equivalent to a lack of attr", function() {
|
||||
var a = {tag: "div", attrs: {id: undefined}}
|
||||
var b = {tag: "div", attrs: {id: "test"}}
|
||||
var c = {tag: "div", attrs: {id: undefined}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.hasAttribute("id")).equals(false)
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(b.dom.hasAttribute("id")).equals(true)
|
||||
o(b.dom.getAttribute("id")).equals("test")
|
||||
|
||||
render(root, [c]);
|
||||
|
||||
// #1804
|
||||
// TODO: uncomment
|
||||
// o(c.dom.hasAttribute("id")).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("customElements", function(){
|
||||
|
||||
|
||||
o("when vnode is customElement, custom setAttribute called", function(){
|
||||
|
||||
var normal = [
|
||||
{ tag: "input", attrs: { value: 'hello' } },
|
||||
{ tag: "input", attrs: { value: 'hello' } },
|
||||
{ tag: "input", attrs: { value: 'hello' } }
|
||||
var normal = [
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}},
|
||||
{tag: "input", attrs: {value: "hello"}}
|
||||
]
|
||||
|
||||
|
||||
var custom = [
|
||||
{ tag: "custom-element", attrs: { custom: 'x' } },
|
||||
{ tag: "input", attrs: { is: 'something-special', custom: 'x' } },
|
||||
{ tag: "custom-element", attrs: { is: 'something-special', custom: 'x' } }
|
||||
{tag: "custom-element", attrs: {custom: "x"}},
|
||||
{tag: "input", attrs: {is: "something-special", custom: "x"}},
|
||||
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
|
||||
]
|
||||
|
||||
var view = normal.concat(custom)
|
||||
|
|
@ -43,8 +83,8 @@ o.spec("attributes", function() {
|
|||
}
|
||||
|
||||
render(root, view)
|
||||
|
||||
o(spy.callCount).equals( custom.length )
|
||||
|
||||
o(spy.callCount).equals(custom.length)
|
||||
})
|
||||
|
||||
})
|
||||
|
|
@ -54,7 +94,7 @@ o.spec("attributes", function() {
|
|||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.attributes["readonly"].nodeValue).equals("")
|
||||
o(a.dom.attributes["readonly"].value).equals("")
|
||||
})
|
||||
o("when input readonly is false, attribute is not present", function() {
|
||||
var a = {tag: "input", attrs: {readonly: false}}
|
||||
|
|
@ -96,6 +136,196 @@ o.spec("attributes", function() {
|
|||
o(a.dom.attributes["checked"]).equals(undefined)
|
||||
})
|
||||
})
|
||||
o.spec("input.value", function() {
|
||||
o("can be set as text", function() {
|
||||
var a = {tag: "input", attrs: {value: "test"}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("test")
|
||||
})
|
||||
o("a lack of attribute removes `value`", function() {
|
||||
var a = {tag: "input", attrs: {}}
|
||||
var b = {tag: "input", attrs: {value: "test"}}
|
||||
// var c = {tag: "input", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(a.dom.value).equals("test")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [c])
|
||||
|
||||
// o(a.dom.value).equals("")
|
||||
})
|
||||
o("can be set as number", function() {
|
||||
var a = {tag: "input", attrs: {value: 1}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("1")
|
||||
})
|
||||
o("null becomes the empty string", function() {
|
||||
var a = {tag: "input", attrs: {value: null}}
|
||||
var b = {tag: "input", attrs: {value: "test"}}
|
||||
var c = {tag: "input", attrs: {value: null}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.getAttribute("value")).equals(null)
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(b.dom.value).equals("test")
|
||||
o(b.dom.getAttribute("value")).equals(null)
|
||||
|
||||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("")
|
||||
o(c.dom.getAttribute("value")).equals(null)
|
||||
})
|
||||
o("'' and 0 are different values", function() {
|
||||
var a = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
var b = {tag: "input", attrs: {value: ""}, children:[{tag:"#", children:""}]}
|
||||
var c = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("0")
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(b.dom.value).equals("")
|
||||
|
||||
// #1595 redux
|
||||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("0")
|
||||
})
|
||||
o("isn't set when equivalent to the previous value and focused", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
var root = $window.document.body
|
||||
var render = vdom($window).render
|
||||
|
||||
var a = {tag: "input"}
|
||||
var b = {tag: "input", attrs: {value: "1"}}
|
||||
var c = {tag: "input", attrs: {value: "1"}}
|
||||
var d = {tag: "input", attrs: {value: 1}}
|
||||
var e = {tag: "input", attrs: {value: 2}}
|
||||
|
||||
render(root, [a])
|
||||
var spies = $window.__getSpies(a.dom)
|
||||
a.dom.focus()
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(c.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [d])
|
||||
|
||||
o(d.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [e])
|
||||
|
||||
o(d.dom.value).equals("2")
|
||||
o(spies.valueSetter.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
o.spec("input.type", function() {
|
||||
o("the input.type setter is never used", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
var root = $window.document.body
|
||||
var render = vdom($window).render
|
||||
|
||||
var a = {tag: "input", attrs: {type: "radio"}}
|
||||
var b = {tag: "input", attrs: {type: "text"}}
|
||||
var c = {tag: "input", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
var spies = $window.__getSpies(a.dom)
|
||||
|
||||
o(spies.typeSetter.callCount).equals(0)
|
||||
o(a.dom.getAttribute("type")).equals("radio")
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(spies.typeSetter.callCount).equals(0)
|
||||
o(b.dom.getAttribute("type")).equals("text")
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(spies.typeSetter.callCount).equals(0)
|
||||
o(c.dom.hasAttribute("type")).equals(false)
|
||||
})
|
||||
})
|
||||
o.spec("textarea.value", function() {
|
||||
o("can be removed by not passing a value", function() {
|
||||
var a = {tag: "textarea", attrs: {value:"x"}}
|
||||
// var b = {tag: "textarea", attrs: {}}
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("x")
|
||||
|
||||
// https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235
|
||||
// TODO: Uncomment
|
||||
// render(root, [b])
|
||||
|
||||
// o(b.dom.value).equals("")
|
||||
})
|
||||
o("isn't set when equivalent to the previous value and focused", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
var root = $window.document.body
|
||||
var render = vdom($window).render
|
||||
|
||||
var a = {tag: "textarea"}
|
||||
var b = {tag: "textarea", attrs: {value: "1"}}
|
||||
var c = {tag: "textarea", attrs: {value: "1"}}
|
||||
var d = {tag: "textarea", attrs: {value: 1}}
|
||||
var e = {tag: "textarea", attrs: {value: 2}}
|
||||
|
||||
render(root, [a])
|
||||
var spies = $window.__getSpies(a.dom)
|
||||
a.dom.focus()
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(c.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [d])
|
||||
|
||||
o(d.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [e])
|
||||
|
||||
o(d.dom.value).equals("2")
|
||||
o(spies.valueSetter.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
o.spec("link href", function() {
|
||||
o("when link href is true, attribute is present", function() {
|
||||
var a = {tag: "a", attrs: {href: true}}
|
||||
|
|
@ -118,7 +348,7 @@ o.spec("attributes", function() {
|
|||
|
||||
render(root, canvas)
|
||||
|
||||
o(canvas.dom.attributes["width"].nodeValue).equals("100%")
|
||||
o(canvas.dom.attributes["width"].value).equals("100%")
|
||||
o(canvas.dom.width).equals(100)
|
||||
})
|
||||
})
|
||||
|
|
@ -128,12 +358,223 @@ o.spec("attributes", function() {
|
|||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.attributes["class"].nodeValue).equals("test")
|
||||
o(a.dom.attributes["class"].value).equals("test")
|
||||
})
|
||||
})
|
||||
o.spec("option.value", function() {
|
||||
o("can be set as text", function() {
|
||||
var a = {tag: "option", attrs: {value: "test"}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("test")
|
||||
})
|
||||
o("can be set as number", function() {
|
||||
var a = {tag: "option", attrs: {value: 1}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("1")
|
||||
})
|
||||
o("null becomes the empty string", function() {
|
||||
var a = {tag: "option", attrs: {value: null}}
|
||||
var b = {tag: "option", attrs: {value: "test"}}
|
||||
var c = {tag: "option", attrs: {value: null}}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.getAttribute("value")).equals("")
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(b.dom.value).equals("test")
|
||||
o(b.dom.getAttribute("value")).equals("test")
|
||||
|
||||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("")
|
||||
o(c.dom.getAttribute("value")).equals("")
|
||||
})
|
||||
o("'' and 0 are different values", function() {
|
||||
var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
var b = {tag: "option", attrs: {value: ""}, children:[{tag:"#", children:""}]}
|
||||
var c = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]}
|
||||
|
||||
render(root, [a]);
|
||||
|
||||
o(a.dom.value).equals("0")
|
||||
|
||||
render(root, [b]);
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
|
||||
// #1595 redux
|
||||
render(root, [c]);
|
||||
|
||||
o(c.dom.value).equals("0")
|
||||
})
|
||||
o("isn't set when equivalent to the previous value", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
var root = $window.document.body
|
||||
var render = vdom($window).render
|
||||
|
||||
var a = {tag: "option"}
|
||||
var b = {tag: "option", attrs: {value: "1"}}
|
||||
var c = {tag: "option", attrs: {value: "1"}}
|
||||
var d = {tag: "option", attrs: {value: 1}}
|
||||
var e = {tag: "option", attrs: {value: 2}}
|
||||
|
||||
render(root, [a])
|
||||
var spies = $window.__getSpies(a.dom)
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(c.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [d])
|
||||
|
||||
o(d.dom.value).equals("1")
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
|
||||
render(root, [e])
|
||||
|
||||
o(d.dom.value).equals("2")
|
||||
o(spies.valueSetter.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
o.spec("select.value", function() {
|
||||
function makeSelect(value) {
|
||||
var attrs = (arguments.length === 0) ? {} : {value: value}
|
||||
return {tag: "select", attrs: attrs, children: [
|
||||
{tag:"option", attrs: {value: "1"}},
|
||||
{tag:"option", attrs: {value: "2"}},
|
||||
{tag:"option", attrs: {value: "a"}},
|
||||
{tag:"option", attrs: {value: "0"}},
|
||||
{tag:"option", attrs: {value: ""}}
|
||||
]}
|
||||
}
|
||||
o("can be set as text", function() {
|
||||
var a = makeSelect()
|
||||
var b = makeSelect("2")
|
||||
var c = makeSelect("a")
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("1")
|
||||
o(a.dom.selectedIndex).equals(0)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("2")
|
||||
o(b.dom.selectedIndex).equals(1)
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(c.dom.value).equals("a")
|
||||
o(c.dom.selectedIndex).equals(2)
|
||||
})
|
||||
o("setting null unsets the value", function() {
|
||||
var a = makeSelect(null)
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.selectedIndex).equals(-1)
|
||||
})
|
||||
o("values are type converted", function() {
|
||||
var a = makeSelect(1)
|
||||
var b = makeSelect(2)
|
||||
|
||||
render(root, [a])
|
||||
|
||||
o(a.dom.value).equals("1")
|
||||
o(a.dom.selectedIndex).equals(0)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("2")
|
||||
o(b.dom.selectedIndex).equals(1)
|
||||
})
|
||||
o("'' and 0 are different values when focused", function() {
|
||||
var a = makeSelect("")
|
||||
var b = makeSelect(0)
|
||||
|
||||
render(root, [a])
|
||||
a.dom.focus()
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
|
||||
// #1595 redux
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("0")
|
||||
})
|
||||
o("'' and null are different values when focused", function() {
|
||||
var a = makeSelect("")
|
||||
var b = makeSelect(null)
|
||||
var c = makeSelect("")
|
||||
|
||||
render(root, [a])
|
||||
a.dom.focus()
|
||||
|
||||
o(a.dom.value).equals("")
|
||||
o(a.dom.selectedIndex).equals(4)
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(b.dom.value).equals("")
|
||||
o(b.dom.selectedIndex).equals(-1)
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(c.dom.value).equals("")
|
||||
o(c.dom.selectedIndex).equals(4)
|
||||
})
|
||||
o("updates with the same value do not re-set the attribute if the select has focus", function() {
|
||||
var $window = domMock({spy: o.spy})
|
||||
var root = $window.document.body
|
||||
var render = vdom($window).render
|
||||
|
||||
var a = makeSelect()
|
||||
var b = makeSelect("1")
|
||||
var c = makeSelect(1)
|
||||
var d = makeSelect("2")
|
||||
|
||||
render(root, [a])
|
||||
var spies = $window.__getSpies(a.dom)
|
||||
a.dom.focus()
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
o(a.dom.value).equals("1")
|
||||
|
||||
render(root, [b])
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
o(b.dom.value).equals("1")
|
||||
|
||||
render(root, [c])
|
||||
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
o(c.dom.value).equals("1")
|
||||
|
||||
render(root, [d])
|
||||
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
o(d.dom.value).equals("2")
|
||||
})
|
||||
})
|
||||
o.spec("contenteditable throws on untrusted children", function() {
|
||||
o("including text nodes", function() {
|
||||
var div = {tag: "div", attrs: {contenteditable: true}, text: ''}
|
||||
var div = {tag: "div", attrs: {contenteditable: true}, text: ""}
|
||||
var succeeded = false
|
||||
|
||||
try {
|
||||
|
|
@ -141,7 +582,7 @@ o.spec("attributes", function() {
|
|||
|
||||
succeeded = true
|
||||
}
|
||||
catch(e){}
|
||||
catch(e){/* ignore */}
|
||||
|
||||
o(succeeded).equals(false)
|
||||
})
|
||||
|
|
@ -154,7 +595,7 @@ o.spec("attributes", function() {
|
|||
|
||||
succeeded = true
|
||||
}
|
||||
catch(e){}
|
||||
catch(e){/* ignore */}
|
||||
|
||||
o(succeeded).equals(false)
|
||||
})
|
||||
|
|
@ -167,7 +608,7 @@ o.spec("attributes", function() {
|
|||
|
||||
succeeded = true
|
||||
}
|
||||
catch(e){}
|
||||
catch(e){/* ignore */}
|
||||
|
||||
o(succeeded).equals(true)
|
||||
})
|
||||
|
|
@ -180,7 +621,7 @@ o.spec("attributes", function() {
|
|||
|
||||
succeeded = true
|
||||
}
|
||||
catch(e){}
|
||||
catch(e){/* ignore */}
|
||||
|
||||
o(succeeded).equals(true)
|
||||
})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-script-url */
|
||||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
|
|
@ -23,8 +24,8 @@ o.spec("createElement", function() {
|
|||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("DIV")
|
||||
o(vnode.dom.attributes["id"].nodeValue).equals("a")
|
||||
o(vnode.dom.attributes["title"].nodeValue).equals("b")
|
||||
o(vnode.dom.attributes["id"].value).equals("a")
|
||||
o(vnode.dom.attributes["title"].value).equals("b")
|
||||
})
|
||||
o("creates style", function() {
|
||||
var vnode = {tag: "div", attrs: {style: {backgroundColor: "red"}}}
|
||||
|
|
@ -47,28 +48,34 @@ o.spec("createElement", function() {
|
|||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("DIV")
|
||||
o(vnode.dom.attributes["id"].nodeValue).equals("a")
|
||||
o(vnode.dom.attributes["title"].nodeValue).equals("b")
|
||||
o(vnode.dom.attributes["id"].value).equals("a")
|
||||
o(vnode.dom.attributes["title"].value).equals("b")
|
||||
o(vnode.dom.childNodes.length).equals(2)
|
||||
o(vnode.dom.childNodes[0].nodeName).equals("A")
|
||||
o(vnode.dom.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("creates svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}]}
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [
|
||||
{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}},
|
||||
{tag: "foreignObject", children: [{tag: "body", attrs: {xmlns: "http://www.w3.org/1999/xhtml"}}]}
|
||||
]}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.nodeName).equals("svg")
|
||||
o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(vnode.dom.firstChild.nodeName).equals("a")
|
||||
o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(vnode.dom.firstChild.attributes["href"].nodeValue).equals("javascript:;")
|
||||
o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;")
|
||||
o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
o(vnode.dom.childNodes[1].nodeName).equals("foreignObject")
|
||||
o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body")
|
||||
o(vnode.dom.childNodes[1].firstChild.namespaceURI).equals("http://www.w3.org/1999/xhtml")
|
||||
})
|
||||
o("sets attributes correctly for svg", function() {
|
||||
var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}}
|
||||
render(root, [vnode])
|
||||
|
||||
o(vnode.dom.attributes["viewBox"].nodeValue).equals("0 0 100 100")
|
||||
o(vnode.dom.attributes["viewBox"].value).equals("0 0 100 100")
|
||||
})
|
||||
o("creates mathml", function() {
|
||||
var vnode = {tag: "math", ns: "http://www.w3.org/1998/Math/MathML", children: [{tag: "mrow", ns: "http://www.w3.org/1998/Math/MathML"}]}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ o.spec("event", function() {
|
|||
o(onevent.args[0].type).equals("click")
|
||||
o(onevent.args[0].target).equals(div.dom)
|
||||
o(div.dom).equals(updated.dom)
|
||||
o(div.dom.attributes["id"].nodeValue).equals("b")
|
||||
o(div.dom.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("handles ontransitionend", function() {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var m = require("../../render/hyperscript")
|
||||
|
||||
|
|
@ -14,6 +16,95 @@ o.spec("hyperscript", function() {
|
|||
|
||||
o(vnode.tag).equals("a")
|
||||
})
|
||||
o("v1.0.1 bug-for-bug regression suite", function(){
|
||||
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
|
||||
})
|
||||
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")
|
||||
|
||||
|
|
@ -127,6 +218,18 @@ o.spec("hyperscript", function() {
|
|||
o(vnode.tag).equals("div")
|
||||
o(vnode.attrs.a).equals(true)
|
||||
})
|
||||
o("handles explicit empty string value for input", function() {
|
||||
var vnode = m('input[value=""]')
|
||||
|
||||
o(vnode.tag).equals("input")
|
||||
o(vnode.attrs.value).equals("")
|
||||
})
|
||||
o("handles explicit empty string value for option", function() {
|
||||
var vnode = m('option[value=""]')
|
||||
|
||||
o(vnode.tag).equals("option")
|
||||
o(vnode.attrs.value).equals("")
|
||||
})
|
||||
})
|
||||
o.spec("attrs", function() {
|
||||
o("handles string attr", function() {
|
||||
|
|
@ -271,6 +374,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() {
|
||||
|
|
@ -424,7 +532,7 @@ o.spec("hyperscript", function() {
|
|||
})
|
||||
})
|
||||
o.spec("components", function() {
|
||||
o("works", function() {
|
||||
o("works with POJOs", function() {
|
||||
var component = {
|
||||
view: function() {
|
||||
return m("div")
|
||||
|
|
@ -432,6 +540,19 @@ o.spec("hyperscript", function() {
|
|||
}
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
o(vnode.children[0].tag).equals("#")
|
||||
o(vnode.children[0].children).equals("b")
|
||||
})
|
||||
o("works with functions", function() {
|
||||
var component = o.spy()
|
||||
|
||||
var vnode = m(component, {id: "a"}, "b")
|
||||
|
||||
o(component.callCount).equals(0)
|
||||
|
||||
o(vnode.tag).equals(component)
|
||||
o(vnode.attrs.id).equals("a")
|
||||
o(vnode.children.length).equals(1)
|
||||
|
|
|
|||
|
|
@ -80,6 +80,57 @@ o.spec("form inputs", function() {
|
|||
o(select.dom.selectedIndex).equals(0)
|
||||
})
|
||||
|
||||
o("select option can have empty string value", function() {
|
||||
var select = {tag: "select", children :[
|
||||
{tag: "option", attrs: {value: ""}, text: "aaa"}
|
||||
]}
|
||||
|
||||
render(root, [select])
|
||||
|
||||
o(select.dom.firstChild.value).equals("")
|
||||
})
|
||||
|
||||
o("option value defaults to textContent unless explicitly set", function() {
|
||||
var select = {tag: "select", children :[
|
||||
{tag: "option", text: "aaa"}
|
||||
]}
|
||||
|
||||
render(root, [select])
|
||||
|
||||
o(select.dom.firstChild.value).equals("aaa")
|
||||
o(select.dom.value).equals("aaa")
|
||||
|
||||
//test that value changes when content changes
|
||||
select = {tag: "select", children :[
|
||||
{tag: "option", text: "bbb"}
|
||||
]}
|
||||
|
||||
render(root, [select])
|
||||
|
||||
o(select.dom.firstChild.value).equals("bbb")
|
||||
o(select.dom.value).equals("bbb")
|
||||
|
||||
//test that value can be set to "" in subsequent render
|
||||
select = {tag: "select", children :[
|
||||
{tag: "option", attrs: {value: ""}, text: "aaa"}
|
||||
]}
|
||||
|
||||
render(root, [select])
|
||||
|
||||
o(select.dom.firstChild.value).equals("")
|
||||
o(select.dom.value).equals("")
|
||||
|
||||
//test that value reverts to textContent when value omitted
|
||||
select = {tag: "select", children :[
|
||||
{tag: "option", text: "aaa"}
|
||||
]}
|
||||
|
||||
render(root, [select])
|
||||
|
||||
o(select.dom.firstChild.value).equals("aaa")
|
||||
o(select.dom.value).equals("aaa")
|
||||
})
|
||||
|
||||
o("select yields invalid value without children", function() {
|
||||
var select = {tag: "select", attrs: {value: "a"}}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
var Promise = require("../../promise/promise")
|
||||
|
|
@ -16,7 +17,6 @@ o.spec("onbeforeremove", function() {
|
|||
|
||||
o("does not call onbeforeremove when creating", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var vnode = {tag: "div", attrs: {onbeforeremove: create}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
|
@ -141,7 +141,7 @@ o.spec("onbeforeremove", function() {
|
|||
o(vnode.dom.attributes["onbeforeremove"]).equals(undefined)
|
||||
})
|
||||
o("does not recycle when there's an onbeforeremove", function() {
|
||||
var remove = function(vnode) {}
|
||||
var remove = function() {}
|
||||
var vnode = {tag: "div", key: 1, attrs: {onbeforeremove: remove}}
|
||||
var updated = {tag: "div", key: 1, attrs: {onbeforeremove: remove}}
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ o.spec("onbeforeremove", function() {
|
|||
o(vnode.dom).notEquals(updated.dom)
|
||||
})
|
||||
o("does not leave elements out of order during removal", function(done) {
|
||||
var remove = function(vnode) {return Promise.resolve()}
|
||||
var remove = function() {return Promise.resolve()}
|
||||
var vnodes = [{tag: "div", key: 1, attrs: {onbeforeremove: remove}, text: "1"}, {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"}]
|
||||
var updated = {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"}
|
||||
|
||||
|
|
@ -169,39 +169,43 @@ o.spec("onbeforeremove", function() {
|
|||
done()
|
||||
})
|
||||
})
|
||||
o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
|
||||
var onremove = o.spy()
|
||||
var onbeforeremove = function(){return Promise.resolve()}
|
||||
var component = {
|
||||
onbeforeremove: onbeforeremove,
|
||||
onremove: onremove,
|
||||
view: function() {},
|
||||
}
|
||||
render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
|
||||
render(root, [])
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("awaits promise resolution before removing the node", function(done) {
|
||||
var view = o.spy()
|
||||
var onremove = o.spy()
|
||||
var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
|
||||
var component = {
|
||||
onbeforeremove: onbeforeremove,
|
||||
onremove: onremove,
|
||||
view: view,
|
||||
}
|
||||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
|
||||
var onremove = o.spy()
|
||||
var onbeforeremove = function(){return Promise.resolve()}
|
||||
var component = createComponent({
|
||||
onbeforeremove: onbeforeremove,
|
||||
onremove: onremove,
|
||||
view: function() {},
|
||||
})
|
||||
render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
|
||||
render(root, [])
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("awaits promise resolution before removing the node", function(done) {
|
||||
var view = o.spy()
|
||||
var onremove = o.spy()
|
||||
var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
|
||||
var component = createComponent({
|
||||
onbeforeremove: onbeforeremove,
|
||||
onremove: onremove,
|
||||
view: view,
|
||||
})
|
||||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
|
||||
callAsync(function(){
|
||||
o(onremove.callCount).equals(0)
|
||||
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(1)
|
||||
done()
|
||||
o(onremove.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(1)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
})
|
||||
|
||||
o("prevents update in text", function() {
|
||||
|
|
@ -56,86 +57,6 @@ o.spec("onbeforeupdate", function() {
|
|||
o(root.firstChild.nodeValue).equals("a")
|
||||
})
|
||||
|
||||
o("prevents update in component", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", children: vnode.children}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, children: [{tag: "#", children: "a"}]}
|
||||
var updated = {tag: component, children: [{tag: "#", children: "b"}]}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.firstChild.nodeValue).equals("a")
|
||||
})
|
||||
|
||||
o("prevents update if returning false in component and false in vnode", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true in component and true in vnode", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning false in component but true in vnode", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true in component but false in vnode", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true", function() {
|
||||
var onbeforeupdate = function() {return true}
|
||||
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
|
||||
|
|
@ -144,23 +65,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true from component", function() {
|
||||
var component = {
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
}
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("accepts arguments for comparison", function() {
|
||||
|
|
@ -181,66 +86,16 @@ o.spec("onbeforeupdate", function() {
|
|||
}
|
||||
|
||||
o(count).equals(1)
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
})
|
||||
|
||||
o("accepts arguments for comparison in component", function() {
|
||||
var component = {
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
}
|
||||
var count = 0
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
count++
|
||||
|
||||
o(old.attrs.id).equals("a")
|
||||
o(vnode.attrs.id).equals("b")
|
||||
|
||||
return old.attrs.id !== vnode.attrs.id
|
||||
}
|
||||
|
||||
o(count).equals(1)
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("is not called on creation", function() {
|
||||
var count = 0
|
||||
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
|
||||
var updated = {tag: "div", attrs: {id: "b", onbeforeupdate: onbeforeupdate}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
|
||||
o(count).equals(0)
|
||||
})
|
||||
|
||||
o("is not called on component creation", function() {
|
||||
var component = {
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var vnode = {tag: "div", attrs: {id: "a"}}
|
||||
var updated = {tag: "div", attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
function onbeforeupdate() {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
|
|
@ -256,7 +111,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
function onbeforeupdate() {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
|
|
@ -264,26 +119,191 @@ o.spec("onbeforeupdate", function() {
|
|||
o(count).equals(1)
|
||||
})
|
||||
|
||||
o("is called only once on component update", function() {
|
||||
var component = {
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
}
|
||||
o("doesn't fire on recycled nodes", function() {
|
||||
var onbeforeupdate = o.spy()
|
||||
var vnodes = [{tag: "div", key: 1}]
|
||||
var temp = []
|
||||
var updated = [{tag: "div", key: 1, attrs: {onbeforeupdate: onbeforeupdate}}]
|
||||
|
||||
var count = 0
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
render(root, vnodes)
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
o(vnodes[0].dom).equals(updated[0].dom)
|
||||
o(updated[0].dom.nodeName).equals("DIV")
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
||||
o(count).equals(1)
|
||||
o("prevents update in component", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", children: vnode.children}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, children: [{tag: "#", children: "a"}]}
|
||||
var updated = {tag: component, children: [{tag: "#", children: "b"}]}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.firstChild.nodeValue).equals("a")
|
||||
})
|
||||
|
||||
o("prevents update if returning false in component and false in vnode", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true in component and true in vnode", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning false in component but true in vnode", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return false},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true in component but false in vnode", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: {id: vnode.attrs.id}}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}}
|
||||
var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true from component", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: function() {return true},
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
})
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("accepts arguments for comparison in component", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
})
|
||||
var count = 0
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
function onbeforeupdate(vnode, old) {
|
||||
count++
|
||||
|
||||
o(old.attrs.id).equals("a")
|
||||
o(vnode.attrs.id).equals("b")
|
||||
|
||||
return old.attrs.id !== vnode.attrs.id
|
||||
}
|
||||
|
||||
o(count).equals(1)
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("is not called on component creation", function() {
|
||||
createComponent({
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
})
|
||||
|
||||
var count = 0
|
||||
var vnode = {tag: "div", attrs: {id: "a"}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
||||
function onbeforeupdate() {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
|
||||
o(count).equals(0)
|
||||
})
|
||||
|
||||
o("is called only once on component update", function() {
|
||||
var component = createComponent({
|
||||
onbeforeupdate: onbeforeupdate,
|
||||
view: function(vnode) {
|
||||
return {tag: "div", attrs: vnode.attrs}
|
||||
},
|
||||
})
|
||||
|
||||
var count = 0
|
||||
var vnode = {tag: component, attrs: {id: "a"}}
|
||||
var updated = {tag: component, attrs: {id: "b"}}
|
||||
|
||||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
function onbeforeupdate() {
|
||||
count++
|
||||
return true
|
||||
}
|
||||
|
||||
o(count).equals(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ o.spec("oncreate", function() {
|
|||
})
|
||||
o("does not call oncreate when removing", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var vnode = {tag: "div", attrs: {oncreate: create}, state: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
|
|
|||
|
|
@ -128,7 +128,6 @@ o.spec("oninit", function() {
|
|||
})
|
||||
o("does not call oninit when removing", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var vnode = {tag: "div", attrs: {oninit: create}, state: {}}
|
||||
|
||||
render(root, [vnode])
|
||||
|
|
@ -187,7 +186,7 @@ o.spec("oninit", function() {
|
|||
called = true
|
||||
|
||||
o(vnode.dom).equals(undefined)
|
||||
o(root.childNodes.length).equals(0)
|
||||
o(root.childNodes.length).equals(1)
|
||||
}
|
||||
o(called).equals(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
var m = require("../../render/hyperscript")
|
||||
|
|
@ -80,39 +81,6 @@ o.spec("onremove", function() {
|
|||
o(remove.this).equals(vnode.state)
|
||||
o(remove.args[0]).equals(vnode)
|
||||
})
|
||||
o("calls onremove on nested component", function() {
|
||||
var spy = o.spy()
|
||||
var comp = {
|
||||
view: function() {return m(outer)}
|
||||
}
|
||||
var outer = {
|
||||
view: function() {return m(inner)}
|
||||
}
|
||||
var inner = {
|
||||
onremove: spy,
|
||||
view: function() {return m("div")}
|
||||
}
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("calls onremove on nested component child", function() {
|
||||
var spy = o.spy()
|
||||
var comp = {
|
||||
view: function() {return m(outer)}
|
||||
}
|
||||
var outer = {
|
||||
view: function() {return m(inner, m("a", {onremove: spy}))}
|
||||
}
|
||||
var inner = {
|
||||
view: function(vnode) {return m("div", vnode.children)}
|
||||
}
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("does not set onremove as an event handler", function() {
|
||||
var remove = o.spy()
|
||||
var vnode = {tag: "div", attrs: {onremove: remove}, children: []}
|
||||
|
|
@ -145,4 +113,43 @@ o.spec("onremove", function() {
|
|||
|
||||
o(vnode.dom).notEquals(updated.dom)
|
||||
})
|
||||
})
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
||||
o("calls onremove on nested component", function() {
|
||||
var spy = o.spy()
|
||||
var comp = createComponent({
|
||||
view: function() {return m(outer)}
|
||||
})
|
||||
var outer = createComponent({
|
||||
view: function() {return m(inner)}
|
||||
})
|
||||
var inner = createComponent({
|
||||
onremove: spy,
|
||||
view: function() {return m("div")}
|
||||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("calls onremove on nested component child", function() {
|
||||
var spy = o.spy()
|
||||
var comp = createComponent({
|
||||
view: function() {return m(outer)}
|
||||
})
|
||||
var outer = createComponent({
|
||||
view: function() {return m(inner, m("a", {onremove: spy}))}
|
||||
})
|
||||
var inner = createComponent({
|
||||
view: function(vnode) {return m("div", vnode.children)}
|
||||
})
|
||||
render(root, {tag: comp})
|
||||
render(root, null)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -59,7 +59,6 @@ o.spec("onupdate", function() {
|
|||
})
|
||||
o("does not call old onupdate when removing the onupdate property in new vnode", function() {
|
||||
var create = o.spy()
|
||||
var update = o.spy()
|
||||
var vnode = {tag: "a", attrs: {onupdate: create}}
|
||||
var updated = {tag: "a"}
|
||||
|
||||
|
|
@ -171,9 +170,9 @@ o.spec("onupdate", function() {
|
|||
function update(vnode) {
|
||||
called = true
|
||||
|
||||
o(vnode.dom.parentNode.attributes["id"].nodeValue).equals("11")
|
||||
o(vnode.dom.attributes["id"].nodeValue).equals("22")
|
||||
o(vnode.dom.childNodes[0].attributes["id"].nodeValue).equals("33")
|
||||
o(vnode.dom.parentNode.attributes["id"].value).equals("11")
|
||||
o(vnode.dom.attributes["id"].value).equals("22")
|
||||
o(vnode.dom.childNodes[0].attributes["id"].value).equals("33")
|
||||
}
|
||||
o(called).equals(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ o.spec("render", function() {
|
|||
|
||||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
|
||||
o("throws on invalid root node", function() {
|
||||
var threw = false
|
||||
try {
|
||||
|
|
@ -31,8 +31,8 @@ o.spec("render", function() {
|
|||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
o("does not enter infinite loop when oninit triggers render and view throws", function(done) {
|
||||
|
||||
o("does not enter infinite loop when oninit triggers render and view throws with an object literal component", function(done) {
|
||||
var A = {
|
||||
oninit: init,
|
||||
view: function() {throw new Error("error")}
|
||||
|
|
@ -44,15 +44,259 @@ o.spec("render", function() {
|
|||
setTimeout(function() {
|
||||
var threwInner = false
|
||||
try {run()} catch (e) {threwInner = true}
|
||||
|
||||
|
||||
o(threwInner).equals(false)
|
||||
done()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
||||
var threwOuter = false
|
||||
try {run()} catch (e) {threwOuter = true}
|
||||
|
||||
|
||||
o(threwOuter).equals(true)
|
||||
})
|
||||
o("does not try to re-initialize a constructibe component whose view has thrown", function() {
|
||||
var oninit = o.spy()
|
||||
var onbeforeupdate = o.spy()
|
||||
function A(){}
|
||||
A.prototype.view = function() {throw new Error("error")}
|
||||
A.prototype.oninit = oninit
|
||||
A.prototype.onbeforeupdate = onbeforeupdate
|
||||
var throwCount = 0
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
o("does not try to re-initialize a constructible component whose oninit has thrown", function() {
|
||||
var oninit = o.spy(function(){throw new Error("error")})
|
||||
var onbeforeupdate = o.spy()
|
||||
function A(){}
|
||||
A.prototype.view = function(){}
|
||||
A.prototype.oninit = oninit
|
||||
A.prototype.onbeforeupdate = onbeforeupdate
|
||||
var throwCount = 0
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
o("does not try to re-initialize a constructible component whose constructor has thrown", function() {
|
||||
var oninit = o.spy()
|
||||
var onbeforeupdate = o.spy()
|
||||
function A(){throw new Error("error")}
|
||||
A.prototype.view = function() {}
|
||||
A.prototype.oninit = oninit
|
||||
A.prototype.onbeforeupdate = onbeforeupdate
|
||||
var throwCount = 0
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(0)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(0)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
o("does not try to re-initialize a closure component whose view has thrown", function() {
|
||||
var oninit = o.spy()
|
||||
var onbeforeupdate = o.spy()
|
||||
function A() {
|
||||
return {
|
||||
view: function() {throw new Error("error")},
|
||||
oninit: oninit,
|
||||
onbeforeupdate: onbeforeupdate
|
||||
}
|
||||
}
|
||||
var throwCount = 0
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
o("does not try to re-initialize a closure component whose oninit has thrown", function() {
|
||||
var oninit = o.spy(function() {throw new Error("error")})
|
||||
var onbeforeupdate = o.spy()
|
||||
function A() {
|
||||
return {
|
||||
view: function() {},
|
||||
oninit: oninit,
|
||||
onbeforeupdate: onbeforeupdate
|
||||
}
|
||||
}
|
||||
var throwCount = 0
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
o(oninit.callCount).equals(1)
|
||||
o(onbeforeupdate.callCount).equals(0)
|
||||
})
|
||||
o("does not try to re-initialize a closure component whose closure has thrown", function() {
|
||||
function A() {
|
||||
throw new Error("error")
|
||||
}
|
||||
var throwCount = 0
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
|
||||
try {render(root, {tag: A})} catch (e) {throwCount++}
|
||||
|
||||
o(throwCount).equals(1)
|
||||
})
|
||||
o("lifecycle methods work in keyed children of recycled keyed", function() {
|
||||
var createA = o.spy()
|
||||
var updateA = o.spy()
|
||||
var removeA = o.spy()
|
||||
var createB = o.spy()
|
||||
var updateB = o.spy()
|
||||
var removeB = o.spy()
|
||||
var a = function() {
|
||||
return {tag: "div", key: 1, children: [
|
||||
{tag: "div", key: 11, attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
|
||||
{tag: "div", key: 12}
|
||||
]}
|
||||
}
|
||||
var b = function() {
|
||||
return {tag: "div", key: 2, children: [
|
||||
{tag: "div", key: 21, attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
|
||||
{tag: "div", key: 22}
|
||||
]}
|
||||
}
|
||||
render(root, a())
|
||||
render(root, b())
|
||||
render(root, a())
|
||||
|
||||
o(createA.callCount).equals(2)
|
||||
o(updateA.callCount).equals(0)
|
||||
o(removeA.callCount).equals(1)
|
||||
o(createB.callCount).equals(1)
|
||||
o(updateB.callCount).equals(0)
|
||||
o(removeB.callCount).equals(1)
|
||||
})
|
||||
o("lifecycle methods work in unkeyed children of recycled keyed", function() {
|
||||
var createA = o.spy()
|
||||
var updateA = o.spy()
|
||||
var removeA = o.spy()
|
||||
var createB = o.spy()
|
||||
var updateB = o.spy()
|
||||
var removeB = o.spy()
|
||||
var a = function() {
|
||||
return {tag: "div", key: 1, children: [
|
||||
{tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
|
||||
]}
|
||||
}
|
||||
var b = function() {
|
||||
return {tag: "div", key: 2, children: [
|
||||
{tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
|
||||
]}
|
||||
}
|
||||
render(root, a())
|
||||
render(root, b())
|
||||
render(root, a())
|
||||
|
||||
o(createA.callCount).equals(2)
|
||||
o(updateA.callCount).equals(0)
|
||||
o(removeA.callCount).equals(1)
|
||||
o(createB.callCount).equals(1)
|
||||
o(updateB.callCount).equals(0)
|
||||
o(removeB.callCount).equals(1)
|
||||
})
|
||||
o("update lifecycle methods work on children of recycled keyed", function() {
|
||||
var createA = o.spy()
|
||||
var updateA = o.spy()
|
||||
var removeA = o.spy()
|
||||
var createB = o.spy()
|
||||
var updateB = o.spy()
|
||||
var removeB = o.spy()
|
||||
|
||||
var a = function() {
|
||||
return {tag: "div", key: 1, children: [
|
||||
{tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
|
||||
]}
|
||||
}
|
||||
var b = function() {
|
||||
return {tag: "div", key: 2, children: [
|
||||
{tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
|
||||
]}
|
||||
}
|
||||
render(root, a())
|
||||
render(root, a())
|
||||
o(createA.callCount).equals(1)
|
||||
o(updateA.callCount).equals(1)
|
||||
o(removeA.callCount).equals(0)
|
||||
|
||||
render(root, b())
|
||||
o(createA.callCount).equals(1)
|
||||
o(updateA.callCount).equals(1)
|
||||
o(removeA.callCount).equals(1)
|
||||
|
||||
render(root, a())
|
||||
render(root, a())
|
||||
|
||||
o(createA.callCount).equals(2)
|
||||
o(updateA.callCount).equals(2)
|
||||
o(removeA.callCount).equals(1)
|
||||
})
|
||||
o("svg namespace is preserved in keyed diff (#1820)", function(){
|
||||
// note that this only exerciese one branch of the keyed diff algo
|
||||
var svg = {tag:"svg", children: [
|
||||
{tag:"g", key: 0},
|
||||
{tag:"g", key: 1}
|
||||
]}
|
||||
render(root, [svg])
|
||||
|
||||
o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
|
||||
svg = {tag:"svg", children: [
|
||||
{tag:"g", key: 1, attrs: {x: 1}},
|
||||
{tag:"g", key: 2, attrs: {x: 2}}
|
||||
]}
|
||||
render(root, [svg])
|
||||
|
||||
o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
o("the namespace of the root is passed to children", function() {
|
||||
render(root, [{tag: "svg"}])
|
||||
o(root.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
render(root.childNodes[0], [{tag: "g"}])
|
||||
o(root.childNodes[0].childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
o(updated.dom).equals(vnode.dom)
|
||||
o(updated.dom).equals(root.firstChild)
|
||||
o(updated.dom.attributes["id"].nodeValue).equals("c")
|
||||
o(updated.dom.attributes["id"].value).equals("c")
|
||||
})
|
||||
o("adds attr", function() {
|
||||
var vnode = {tag: "a", attrs: {id: "b"}}
|
||||
|
|
@ -32,7 +32,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
o(updated.dom).equals(vnode.dom)
|
||||
o(updated.dom).equals(root.firstChild)
|
||||
o(updated.dom.attributes["title"].nodeValue).equals("d")
|
||||
o(updated.dom.attributes["title"].value).equals("d")
|
||||
})
|
||||
o("adds attr from empty attrs", function() {
|
||||
var vnode = {tag: "a"}
|
||||
|
|
@ -43,7 +43,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
o(updated.dom).equals(vnode.dom)
|
||||
o(updated.dom).equals(root.firstChild)
|
||||
o(updated.dom.attributes["title"].nodeValue).equals("d")
|
||||
o(updated.dom.attributes["title"].value).equals("d")
|
||||
})
|
||||
o("removes attr", function() {
|
||||
var vnode = {tag: "a", attrs: {id: "b", title: "d"}}
|
||||
|
|
@ -209,14 +209,14 @@ o.spec("updateElement", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(updated.dom.attributes["class"].nodeValue).equals("b")
|
||||
o(updated.dom.attributes["class"].value).equals("b")
|
||||
})
|
||||
o("updates svg child", function() {
|
||||
var vnode = {tag: "svg", children: [{
|
||||
tag: 'circle'
|
||||
tag: "circle"
|
||||
}]}
|
||||
var updated = {tag: "svg", children: [{
|
||||
tag: 'line'
|
||||
tag: "line"
|
||||
}]}
|
||||
|
||||
render(root, [vnode])
|
||||
|
|
@ -235,7 +235,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
render(root, [vnode])
|
||||
var c = vnode.dom
|
||||
|
||||
|
||||
o(root.childNodes.length).equals(1)
|
||||
o(a).equals(c)
|
||||
})
|
||||
|
|
@ -254,7 +254,7 @@ o.spec("updateElement", function() {
|
|||
|
||||
render(root, [e, b, f])
|
||||
var y = root.childNodes[1]
|
||||
|
||||
|
||||
o(root.childNodes.length).equals(3)
|
||||
o(x).equals(y)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var vdom = require("../../render/render")
|
||||
|
||||
|
|
@ -726,7 +727,7 @@ o.spec("updateNodes", function() {
|
|||
})
|
||||
o("change type, position and length", function() {
|
||||
var vnodes = {tag: "div", children: [
|
||||
undefined,
|
||||
undefined,
|
||||
{tag: "#", children: "a"}
|
||||
]}
|
||||
var updated = {tag: "div", children: [
|
||||
|
|
@ -737,7 +738,7 @@ o.spec("updateNodes", function() {
|
|||
|
||||
render(root, vnodes)
|
||||
render(root, updated)
|
||||
|
||||
|
||||
o(root.firstChild.childNodes.length).equals(1)
|
||||
})
|
||||
o("removes then recreates then reverses children", function() {
|
||||
|
|
@ -838,38 +839,6 @@ o.spec("updateNodes", function() {
|
|||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("fragment child toggles from null when followed by null component then tag", function() {
|
||||
var component = {view: function() {return null}}
|
||||
var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
|
||||
var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
|
||||
var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("fragment child toggles from null in component when followed by null component then tag", function() {
|
||||
var flag = true
|
||||
var a = {view: function() {return flag ? {tag: "a"} : null}}
|
||||
var b = {view: function() {return null}}
|
||||
var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
flag = false
|
||||
render(root, temp)
|
||||
flag = true
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("S")
|
||||
})
|
||||
o("cached, non-keyed nodes skip diff", function () {
|
||||
var onupdate = o.spy();
|
||||
var cached = {tag:"a", attrs:{onupdate: onupdate}}
|
||||
|
|
@ -895,13 +864,13 @@ o.spec("updateNodes", function() {
|
|||
var vnodes = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var temp = [null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var updated = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
|
||||
|
||||
render(root, vnodes)
|
||||
var before = vnodes[1].dom
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
var after = updated[1].dom
|
||||
|
||||
|
||||
o(before).equals(after)
|
||||
o(create.callCount).equals(1)
|
||||
o(update.callCount).equals(2)
|
||||
|
|
@ -914,26 +883,63 @@ o.spec("updateNodes", function() {
|
|||
var vnodes = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var temp = [{tag: "b"}, null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
var updated = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
|
||||
|
||||
|
||||
render(root, vnodes)
|
||||
var before = vnodes[2].dom
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
var after = updated[2].dom
|
||||
|
||||
|
||||
o(before).equals(after)
|
||||
o(create.callCount).equals(1)
|
||||
o(update.callCount).equals(2)
|
||||
o(remove.callCount).equals(0)
|
||||
})
|
||||
o("component is recreated if key changes to undefined", function () {
|
||||
o("node is recreated if key changes to undefined", function () {
|
||||
var vnode = {tag: "b", key: 1}
|
||||
var updated = {tag: "b"}
|
||||
|
||||
|
||||
render(root, vnode)
|
||||
var dom = vnode.dom
|
||||
render(root, updated)
|
||||
|
||||
|
||||
o(vnode.dom).notEquals(updated.dom)
|
||||
})
|
||||
components.forEach(function(cmp){
|
||||
o.spec(cmp.kind, function(){
|
||||
var createComponent = cmp.create
|
||||
|
||||
o("fragment child toggles from null when followed by null component then tag", function() {
|
||||
var component = createComponent({view: function() {return null}})
|
||||
var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
|
||||
var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
|
||||
var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
render(root, temp)
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("B")
|
||||
})
|
||||
o("fragment child toggles from null in component when followed by null component then tag", function() {
|
||||
var flag = true
|
||||
var a = createComponent({view: function() {return flag ? {tag: "a"} : null}})
|
||||
var b = createComponent({view: function() {return null}})
|
||||
var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
|
||||
|
||||
render(root, vnodes)
|
||||
flag = false
|
||||
render(root, temp)
|
||||
flag = true
|
||||
render(root, updated)
|
||||
|
||||
o(root.childNodes.length).equals(2)
|
||||
o(root.childNodes[0].nodeName).equals("A")
|
||||
o(root.childNodes[1].nodeName).equals("S")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
function Vnode(tag, key, attrs, children, text, dom) {
|
||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false}
|
||||
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
|
||||
}
|
||||
Vnode.normalize = function(node) {
|
||||
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
|
||||
|
|
|
|||
|
|
@ -1,2 +1,4 @@
|
|||
"use strict"
|
||||
|
||||
var PromisePolyfill = require("./promise/promise")
|
||||
module.exports = require("./request/request")(window, PromisePolyfill)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@
|
|||
|
||||
var buildQueryString = require("../querystring/build")
|
||||
|
||||
var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
|
||||
|
||||
module.exports = function($window, Promise) {
|
||||
var callbackCount = 0
|
||||
|
||||
var oncompletion
|
||||
function setCompletionCallback(callback) {oncompletion = callback}
|
||||
|
||||
function finalizer() {
|
||||
var count = 0
|
||||
function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
|
||||
|
|
@ -42,7 +45,7 @@ module.exports = function($window, Promise) {
|
|||
if (args.method == null) args.method = "GET"
|
||||
args.method = args.method.toUpperCase()
|
||||
|
||||
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
|
||||
var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true)
|
||||
|
||||
if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
|
||||
if (typeof args.deserialize !== "function") args.deserialize = deserialize
|
||||
|
|
@ -52,7 +55,16 @@ module.exports = function($window, Promise) {
|
|||
if (useBody) args.data = args.serialize(args.data)
|
||||
else args.url = assemble(args.url, args.data)
|
||||
|
||||
var xhr = new $window.XMLHttpRequest()
|
||||
var xhr = new $window.XMLHttpRequest(),
|
||||
aborted = false,
|
||||
_abort = xhr.abort
|
||||
|
||||
|
||||
xhr.abort = function abort() {
|
||||
aborted = true
|
||||
_abort.call(xhr)
|
||||
}
|
||||
|
||||
xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
|
||||
|
||||
if (args.serialize === JSON.stringify && useBody) {
|
||||
|
|
@ -70,10 +82,13 @@ module.exports = function($window, Promise) {
|
|||
if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
// Don't throw errors on xhr.abort().
|
||||
if(aborted) return
|
||||
|
||||
if (xhr.readyState === 4) {
|
||||
try {
|
||||
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
|
||||
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
|
||||
resolve(cast(args.type, response))
|
||||
}
|
||||
else {
|
||||
|
|
@ -128,7 +143,6 @@ module.exports = function($window, Promise) {
|
|||
var key = tokens[i].slice(1)
|
||||
if (data[key] != null) {
|
||||
url = url.replace(tokens[i], data[key])
|
||||
delete data[key]
|
||||
}
|
||||
}
|
||||
return url
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ var Promise = require("../../promise/promise")
|
|||
var parseQueryString = require("../../querystring/parse")
|
||||
|
||||
o.spec("jsonp", function() {
|
||||
var mock, jsonp, spy, complete
|
||||
var mock, jsonp, complete
|
||||
o.beforeEach(function() {
|
||||
mock = xhrMock()
|
||||
var requestService = Request(mock, Promise)
|
||||
|
|
@ -28,7 +28,6 @@ o.spec("jsonp", function() {
|
|||
}).then(done)
|
||||
})
|
||||
o("first argument can be a string aliasing url property", function(done){
|
||||
var s = new Date
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
var queryData = parseQueryString(request.query)
|
||||
|
|
@ -104,8 +103,8 @@ o.spec("jsonp", function() {
|
|||
return {status: 200, responseText: queryData["callback"] + "([])"}
|
||||
}
|
||||
})
|
||||
var promise = jsonp("/item", {background: true}).then(function() {})
|
||||
|
||||
jsonp("/item", {background: true}).then(function() {})
|
||||
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(0)
|
||||
done()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ o.spec("xhr", function() {
|
|||
|
||||
o.spec("success", function() {
|
||||
o("works via GET", function(done) {
|
||||
var s = new Date
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({a: 1})}
|
||||
|
|
@ -31,7 +30,6 @@ o.spec("xhr", function() {
|
|||
})
|
||||
})
|
||||
o("implicit GET method", function(done){
|
||||
var s = new Date
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({a: 1})}
|
||||
|
|
@ -44,7 +42,6 @@ o.spec("xhr", function() {
|
|||
})
|
||||
})
|
||||
o("first argument can be a string aliasing url property", function(done){
|
||||
var s = new Date
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({a: 1})}
|
||||
|
|
@ -123,7 +120,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item/:x", data: {x: "y"}}).then(function(data) {
|
||||
o(data).deepEquals({a: "/item/y", b: {}})
|
||||
o(data).deepEquals({a: "/item/y", b: "?x=y"})
|
||||
}).then(done)
|
||||
})
|
||||
o("works w/ parameterized url via POST", function(done) {
|
||||
|
|
@ -133,7 +130,17 @@ o.spec("xhr", function() {
|
|||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/item/:x", data: {x: "y"}}).then(function(data) {
|
||||
o(data).deepEquals({a: "/item/y", b: {}})
|
||||
o(data).deepEquals({a: "/item/y", b: {x: "y"}})
|
||||
}).then(done)
|
||||
})
|
||||
o("works w/ array", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"POST /items": function(request) {
|
||||
return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})}
|
||||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/items", data: [{x: "y"}]}).then(function(data) {
|
||||
o(data).deepEquals({a: "/items", b: [{x: "y"}]})
|
||||
}).then(done)
|
||||
})
|
||||
o("ignores unresolved parameter via GET", function(done) {
|
||||
|
|
@ -162,7 +169,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify([{id: 1}, {id: 2}, {id: 3}])}
|
||||
}
|
||||
})
|
||||
|
|
@ -176,7 +183,7 @@ o.spec("xhr", function() {
|
|||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({id: 1})}
|
||||
}
|
||||
})
|
||||
|
|
@ -218,12 +225,12 @@ o.spec("xhr", function() {
|
|||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({test: 123})}
|
||||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) {
|
||||
o(data).equals("{\"test\":123}")
|
||||
o(data).equals('{"test":123}')
|
||||
}).then(done)
|
||||
})
|
||||
o("deserialize parameter works in POST", function(done) {
|
||||
|
|
@ -232,40 +239,40 @@ o.spec("xhr", function() {
|
|||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({test: 123})}
|
||||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) {
|
||||
o(data).equals("{\"test\":123}")
|
||||
o(data).equals('{"test":123}')
|
||||
}).then(done)
|
||||
})
|
||||
o("extract parameter works in GET", function(done) {
|
||||
var extract = function(data) {
|
||||
var extract = function() {
|
||||
return JSON.stringify({test: 123})
|
||||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item", extract: extract}).then(function(data) {
|
||||
o(data).equals("{\"test\":123}")
|
||||
o(data).equals('{"test":123}')
|
||||
}).then(done)
|
||||
})
|
||||
o("extract parameter works in POST", function(done) {
|
||||
var extract = function(data) {
|
||||
var extract = function() {
|
||||
return JSON.stringify({test: 123})
|
||||
}
|
||||
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
xhr({method: "POST", url: "/item", extract: extract}).then(function(data) {
|
||||
o(data).equals("{\"test\":123}")
|
||||
o(data).equals('{"test":123}')
|
||||
}).then(done)
|
||||
})
|
||||
o("ignores deserialize if extract is defined", function(done) {
|
||||
|
|
@ -275,7 +282,7 @@ o.spec("xhr", function() {
|
|||
var deserialize = o.spy()
|
||||
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
|
|
@ -287,7 +294,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("config parameter works", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
|
|
@ -301,7 +308,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("requests don't block each other", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
|
|
@ -318,7 +325,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("requests trigger finally once with a chained then", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
|
|
@ -332,11 +339,11 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("requests does not trigger finally when background: true", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: "[]"}
|
||||
}
|
||||
})
|
||||
var promise = xhr("/item", {background: true}).then(function() {})
|
||||
xhr("/item", {background: true}).then(function() {})
|
||||
|
||||
setTimeout(function() {
|
||||
o(complete.callCount).equals(0)
|
||||
|
|
@ -345,7 +352,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("headers are set when header arg passed", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
|
|
@ -357,7 +364,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("headers are with higher precedence than default headers", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
|
|
@ -369,7 +376,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("json headers are set to the correct default value", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"POST /item": function(request) {
|
||||
"POST /item": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
|
|
@ -380,11 +387,71 @@ o.spec("xhr", function() {
|
|||
o(xhr.getRequestHeader("Accept")).equals("application/json, text/*")
|
||||
}
|
||||
})
|
||||
o("doesn't fail on abort", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 200, responseText: JSON.stringify({a: 1})}
|
||||
}
|
||||
})
|
||||
|
||||
var failed = false
|
||||
var resolved = false
|
||||
function handleAbort(xhr) {
|
||||
var onreadystatechange = xhr.onreadystatechange // probably not set yet
|
||||
var testonreadystatechange = function() {
|
||||
onreadystatechange.call(xhr)
|
||||
setTimeout(function() { // allow promises to (not) resolve first
|
||||
o(failed).equals(false)
|
||||
o(resolved).equals(false)
|
||||
done()
|
||||
}, 0)
|
||||
}
|
||||
Object.defineProperty(xhr, "onreadystatechange", {
|
||||
set: function(val) { onreadystatechange = val },
|
||||
get: function() { return testonreadystatechange }
|
||||
})
|
||||
xhr.abort()
|
||||
}
|
||||
xhr({method: "GET", url: "/item", config: handleAbort}).catch(function() {
|
||||
failed = true
|
||||
})
|
||||
.then(function() {
|
||||
resolved = true
|
||||
})
|
||||
})
|
||||
o("doesn't fail on file:// status 0", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 0, responseText: JSON.stringify({a: 1})}
|
||||
}
|
||||
})
|
||||
var failed = false
|
||||
xhr({method: "GET", url: "file:///item"}).catch(function() {
|
||||
failed = true
|
||||
}).then(function(data) {
|
||||
o(failed).equals(false)
|
||||
o(data).deepEquals({a: 1})
|
||||
}).then(function() {
|
||||
done()
|
||||
})
|
||||
})
|
||||
/*o("data maintains after interpolate", function() {
|
||||
mock.$defineRoutes({
|
||||
"PUT /items/:x": function() {
|
||||
return {status: 200, responseText: ""}
|
||||
}
|
||||
})
|
||||
var data = {x: 1, y: 2}
|
||||
var dataCopy = Object.assign({}, data);
|
||||
xhr({method: "PUT", url: "/items/:x", data})
|
||||
|
||||
o(data).deepEquals(dataCopy)
|
||||
})*/
|
||||
})
|
||||
o.spec("failure", function() {
|
||||
o("rejects on server error", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: JSON.stringify({error: "error"})}
|
||||
}
|
||||
})
|
||||
|
|
@ -395,7 +462,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("extends Error with JSON response", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})}
|
||||
}
|
||||
})
|
||||
|
|
@ -407,7 +474,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("rejects on non-JSON server error", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: "error"}
|
||||
}
|
||||
})
|
||||
|
|
@ -417,7 +484,7 @@ o.spec("xhr", function() {
|
|||
})
|
||||
o("triggers all branched catches upon rejection", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function(request) {
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: "error"}
|
||||
}
|
||||
})
|
||||
|
|
@ -441,5 +508,15 @@ o.spec("xhr", function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
o("rejects on cors-like error", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 0}
|
||||
}
|
||||
})
|
||||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
}).then(done)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue