Merge remote-tracking branch 'MithrilJS/next' into next
This commit is contained in:
commit
82322ad5a8
77 changed files with 7108 additions and 794 deletions
3
.babelrc
Normal file
3
.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// This is solely to avoid https://github.com/danger/danger-js/issues/261
|
||||
// and should be fine to remove before much longer
|
||||
{}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
.vscode
|
||||
/coverage
|
||||
/docs/lib
|
||||
/examples
|
||||
.vscode/
|
||||
coverage/
|
||||
docs/lib/
|
||||
examples/
|
||||
/mithril.js
|
||||
/mithril.min.js
|
||||
/node_modules
|
||||
node_modules/
|
||||
|
|
|
|||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,3 +1,5 @@
|
|||
* text=auto
|
||||
/mithril.js binary
|
||||
/mithril.min.js binary
|
||||
/package-lock.json binary
|
||||
/yarn.lock binary
|
||||
|
|
|
|||
11
.github/CODEOWNERS
vendored
Normal file
11
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Leo owns everything, for better or worse
|
||||
* @lhorie
|
||||
|
||||
.travis.yml @tivac
|
||||
package.json @tivac
|
||||
.npmignore @tivac
|
||||
.eslintrc.js @tivac
|
||||
.eslintignore @tivac
|
||||
README.md @tivac
|
||||
docs/ @tivac
|
||||
performance/ @tivac
|
||||
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used:
|
||||
* Browser Name and version:
|
||||
* Operating System and version (desktop or mobile):
|
||||
* Link to your project:
|
||||
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
- [ ] I have updated `docs/change-log.md`
|
||||
27
.travis.yml
27
.travis.yml
|
|
@ -13,13 +13,20 @@ cache:
|
|||
# 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 gh-pages@^0.12.0
|
||||
# This is to prevent lint-staged/prettier from running on the bundles
|
||||
- npm rm husky
|
||||
|
||||
# Bundle before running tests so the bundle is always up-to-date
|
||||
before_script: npm run build
|
||||
# Run danger, build bundles (so they're always up to date)
|
||||
before_script:
|
||||
- npx danger run
|
||||
- npm run build-browser
|
||||
# Pass -save so it'll update the readme as well
|
||||
- npm run build-min -- -save
|
||||
|
||||
# This is the default, but leaving so it is obvious
|
||||
# script: npm test
|
||||
# Run tests, lint, and then check for perf regressions
|
||||
script:
|
||||
- npm test
|
||||
- npm run perf
|
||||
|
||||
# After a successful build commit changes back to repo
|
||||
after_success:
|
||||
|
|
@ -42,7 +49,7 @@ after_success:
|
|||
# this doesn't have the built-in branch protection like commit-changes
|
||||
if [ "$TRAVIS_EVENT_TYPE" == "push" ] && \
|
||||
[ "$TRAVIS_BRANCH" == "master" ] && \
|
||||
[ "$TRAVIS_REPO_SLUG" == "lhorie/mithril.js" ]
|
||||
[ "$TRAVIS_REPO_SLUG" == "MithrilJS/mithril.js" ]
|
||||
then
|
||||
# Generate docs
|
||||
npm run gendocs
|
||||
|
|
@ -57,7 +64,7 @@ after_success:
|
|||
$(npm bin)/gh-pages \
|
||||
--dist ./dist \
|
||||
--add \
|
||||
--repo "git@github.com:lhorie/mithril.js.git" \
|
||||
--repo "git@github.com:MithrilJS/mithril.js.git" \
|
||||
--message "Generated docs for commit $TRAVIS_COMMIT [skip ci]"
|
||||
else
|
||||
echo "Not submitting documentation updates"
|
||||
|
|
@ -81,8 +88,7 @@ deploy:
|
|||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
repo: lhorie/mithril.js
|
||||
branch: master
|
||||
repo: MithrilJS/mithril.js
|
||||
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
|
|
@ -91,5 +97,4 @@ deploy:
|
|||
secure: ADElvD1oxn9GfEG7dDOggX96b36A/cGEybovAc0221CCKzv5kWCavMrtxneiJYI6N/n24abSlbM90vMfU84FEzH0Ev28dGVokRP4ad6VRkISszKlYVEP8Lds4QxfKh78jZlUxmxM0B3vmQ1kYJbTBqp3ICtaJ5ptEQHWhrLtxnc=
|
||||
on:
|
||||
tags: true
|
||||
repo: lhorie/mithril.js
|
||||
branch: master
|
||||
repo: MithrilJS/mithril.js
|
||||
|
|
|
|||
316
README.md
316
README.md
|
|
@ -1,267 +1,61 @@
|
|||
# Introduction
|
||||
mithril.js [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril)
|
||||
==========
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/MithrilJS/mithril.js">
|
||||
<img src="https://img.shields.io/travis/MithrilJS/mithril.js/next.svg" alt="Build Status">
|
||||
</a>
|
||||
<a href="https://gitter.im/mithriljs/mithril.js">
|
||||
<img src="https://img.shields.io/gitter/room/mithriljs/mithril.js.svg" alt="Gitter" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
- [What is Mithril?](#what-is-mithril)
|
||||
- [Getting started](#getting-started)
|
||||
- [Hello world](#hello-world)
|
||||
- [DOM elements](#dom-elements)
|
||||
- [Components](#components)
|
||||
- [Routing](#routing)
|
||||
- [XHR](#xhr)
|
||||
- [Installation](#installation)
|
||||
- [Documentation](#documentation)
|
||||
- [Getting Help](#getting-help)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## What is Mithril?
|
||||
|
||||
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->8.22 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box.
|
||||
|
||||
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍.
|
||||
|
||||
Browsers all the way back to IE9 are supported, no polyfills required 👌.
|
||||
|
||||
## Installation
|
||||
|
||||
### CDN
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/mithril"></script>
|
||||
```
|
||||
|
||||
### npm
|
||||
|
||||
```bash
|
||||
$ npm install mithril
|
||||
```
|
||||
|
||||
The ["Getting started" guide](https://mithril.js.org/#getting-started) is a good place to start learning how to use mithril.
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation lives on [mithril.js.org](https://mithril.js.org).
|
||||
|
||||
You may be interested in the [API Docs](https://mithril.js.org/api.html), a [Simple Application](https://mithril.js.org/simple-application.html), or perhaps some [Examples](https://mithril.js.org/examples.html).
|
||||
|
||||
## Getting Help
|
||||
|
||||
Mithril has an active & welcoming community on [Gitter](https://gitter.im/mithriljs/mithril.js), or feel free to ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/mithril.js) using the `mithril.js` tag.
|
||||
|
||||
## Contributing
|
||||
|
||||
There's a [Contributing FAQ](https://mithril.js.org/contributing.html) on the mithril site that hopefully helps, but if not definitely hop into the [Gitter Room](https://gitter.im/mithriljs/mithril.js) and ask away!
|
||||
|
||||
---
|
||||
|
||||
### What is Mithril?
|
||||
Thanks for reading!
|
||||
|
||||
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](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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Hello world
|
||||
|
||||
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
|
||||
|
||||
m.render(root, "Hello world")
|
||||
```
|
||||
|
||||
Now, let's change the text to something else. Add this line of code under the previous one:
|
||||
|
||||
```javascript
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
### DOM elements
|
||||
|
||||
Let's wrap our text in an `<h1>` tag.
|
||||
|
||||
```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.
|
||||
🎁
|
||||
|
|
|
|||
|
|
@ -16,6 +16,6 @@ module.exports = function(redrawService) {
|
|||
redrawService.render(root, Vnode(component))
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
redrawService.redraw()
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,44 +4,48 @@ var coreRenderer = require("../render/render")
|
|||
|
||||
function throttle(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var delay = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function() {
|
||||
var now = Date.now()
|
||||
if (last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
var elapsed = Date.now() - last
|
||||
if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}, delay - elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function($window) {
|
||||
|
||||
module.exports = function($window, throttleMock) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
if (e.redraw === false) e.redraw = undefined
|
||||
else redraw()
|
||||
})
|
||||
|
||||
var callbacks = []
|
||||
var rendering = false
|
||||
|
||||
function subscribe(key, callback) {
|
||||
unsubscribe(key)
|
||||
callbacks.push(key, throttle(callback))
|
||||
callbacks.push(key, callback)
|
||||
}
|
||||
function unsubscribe(key) {
|
||||
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 sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {/*noop*/}
|
||||
rendering = false
|
||||
}
|
||||
|
||||
var redraw = (throttleMock || throttle)(sync)
|
||||
redraw.sync = sync
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,14 @@ module.exports = function($window, redrawService) {
|
|||
var render, component, attrs, currentPath, lastUpdate
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var run = function() {
|
||||
function run() {
|
||||
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
|
||||
}
|
||||
var redraw = function() {
|
||||
run()
|
||||
redraw = redrawService.redraw
|
||||
}
|
||||
redrawService.subscribe(root, run)
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
|
|
@ -24,7 +29,7 @@ module.exports = function($window, redrawService) {
|
|||
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()
|
||||
redraw()
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
|
|
@ -36,10 +41,12 @@ module.exports = function($window, redrawService) {
|
|||
else update(payload, "div")
|
||||
}
|
||||
}, bail)
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,27 @@
|
|||
var o = require("../../ospec/ospec")
|
||||
var components = require("../../test-utils/components")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var coreRenderer = require("../../render/render")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
var apiMounter = require("../../api/mount")
|
||||
|
||||
o.spec("mount", function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redrawService, mount, render
|
||||
var $window, root, redrawService, mount, render, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = domMock()
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redrawService = apiRedraw($window)
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
mount = apiMounter(redrawService)
|
||||
render = coreRenderer($window).render
|
||||
render = redrawService.render
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("throws on invalid component", function() {
|
||||
|
|
@ -47,7 +50,7 @@ o.spec("mount", function() {
|
|||
o(threw).equals(true)
|
||||
})
|
||||
|
||||
o("renders into `root`", function() {
|
||||
o("renders into `root` synchronoulsy", function() {
|
||||
mount(root, createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
|
|
@ -69,7 +72,37 @@ o.spec("mount", function() {
|
|||
o(root.childNodes.length).equals(0)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
o("Mounting a second root doesn't cause the first one to redraw", function() {
|
||||
var view = o.spy(function() {
|
||||
return m("div")
|
||||
})
|
||||
|
||||
render(root, [
|
||||
m("#child0"),
|
||||
m("#child1")
|
||||
])
|
||||
|
||||
mount(root.childNodes[0], createComponent({
|
||||
view : view
|
||||
}))
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
mount(root.childNodes[1], createComponent({
|
||||
view : function() {
|
||||
return m("div")
|
||||
}
|
||||
}))
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
|
|
@ -97,17 +130,12 @@ o.spec("mount", function() {
|
|||
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)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws several mount points on events", function(done, timeout) {
|
||||
timeout(60)
|
||||
|
||||
o("redraws several mount points on events", function() {
|
||||
var onupdate0 = o.spy()
|
||||
var oninit0 = o.spy()
|
||||
var onclick0 = o.spy()
|
||||
|
|
@ -154,26 +182,26 @@ o.spec("mount", function() {
|
|||
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)
|
||||
throttleMock.fire()
|
||||
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
o(onupdate0.callCount).equals(1)
|
||||
o(onupdate1.callCount).equals(1)
|
||||
|
||||
setTimeout(function() {
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
root.childNodes[1].firstChild.dispatchEvent(e)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
}, FRAME_BUDGET)
|
||||
o(onclick1.callCount).equals(1)
|
||||
o(onclick1.this).equals(root.childNodes[1].firstChild)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate0.callCount).equals(2)
|
||||
o(onupdate1.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
var onupdate = o.spy()
|
||||
o("event handlers can skip redraw", function() {
|
||||
var onupdate = o.spy(function(){
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
var oninit = o.spy()
|
||||
var e = $window.document.createEvent("MouseEvents")
|
||||
|
||||
|
|
@ -195,15 +223,12 @@ o.spec("mount", function() {
|
|||
|
||||
o(oninit.callCount).equals(1)
|
||||
|
||||
// Wrapped to ensure no redraw fired
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(0)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(0)
|
||||
})
|
||||
|
||||
o("redraws when the render function is run", function(done) {
|
||||
o("redraws when the render function is run", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
|
||||
|
|
@ -221,17 +246,12 @@ o.spec("mount", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
// Wrapped to give time for the rate-limited redraw to fire
|
||||
setTimeout(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
throttleMock.fire()
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
o("throttles", function() {
|
||||
var i = 0
|
||||
mount(root, createComponent({view: function() {i++}}))
|
||||
var before = i
|
||||
|
|
@ -243,12 +263,11 @@ o.spec("mount", function() {
|
|||
|
||||
var after = i
|
||||
|
||||
setTimeout(function(){
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
done()
|
||||
},40)
|
||||
throttleMock.fire()
|
||||
|
||||
o(before).equals(1) // mounts synchronously
|
||||
o(after).equals(1) // throttles rest
|
||||
o(i).equals(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
var o = require("../../ospec/ospec")
|
||||
var domMock = require("../../test-utils/domMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
var apiRedraw = require("../../api/redraw")
|
||||
|
||||
o.spec("redrawService", function() {
|
||||
|
|
@ -17,25 +18,39 @@ o.spec("redrawService", function() {
|
|||
redrawService.redraw()
|
||||
})
|
||||
|
||||
o("honours throttleMock", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
redrawService = apiRedraw(domMock(), throttleMock.throttle)
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("should run a single renderer entry", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(2)
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
|
@ -54,27 +69,29 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
setTimeout(function() {
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("should stop running after unsubscribe", function() {
|
||||
var spy = o.spy()
|
||||
o("should stop running after unsubscribe", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
redrawService.unsubscribe(root, spy)
|
||||
|
|
@ -82,9 +99,33 @@ o.spec("redrawService", function() {
|
|||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function() {
|
||||
o("should stop running after unsubscribe, even if it occurs after redraw is requested", function(done) {
|
||||
var spy = o.spy(function() {
|
||||
throw new Error("This shouldn't have been called")
|
||||
})
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
||||
redrawService.redraw()
|
||||
|
||||
redrawService.unsubscribe(root, spy)
|
||||
|
||||
o(spy.callCount).equals(0)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("does nothing on invalid unsubscribe", function(done) {
|
||||
var spy = o.spy()
|
||||
|
||||
redrawService.subscribe(root, spy)
|
||||
|
|
@ -92,6 +133,39 @@ o.spec("redrawService", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
setTimeout(function() {
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
}, 20)
|
||||
})
|
||||
|
||||
o("redraw.sync() redraws all roots synchronously", function() {
|
||||
var el1 = $document.createElement("div")
|
||||
var el2 = $document.createElement("div")
|
||||
var el3 = $document.createElement("div")
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
var spy3 = o.spy()
|
||||
|
||||
redrawService.subscribe(el1, spy1)
|
||||
redrawService.subscribe(el2, spy2)
|
||||
redrawService.subscribe(el3, spy3)
|
||||
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
o(spy3.callCount).equals(0)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
o(spy3.callCount).equals(1)
|
||||
|
||||
redrawService.redraw.sync()
|
||||
|
||||
o(spy1.callCount).equals(2)
|
||||
o(spy2.callCount).equals(2)
|
||||
o(spy3.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
var o = require("../../ospec/ospec")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
var browserMock = require("../../test-utils/browserMock")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
var m = require("../../render/hyperscript")
|
||||
var callAsync = require("../../test-utils/callAsync")
|
||||
|
|
@ -14,19 +15,23 @@ o.spec("route", function() {
|
|||
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
|
||||
void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
|
||||
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
|
||||
var FRAME_BUDGET = Math.floor(1000 / 60)
|
||||
var $window, root, redrawService, route
|
||||
var $window, root, redrawService, route, throttleMock
|
||||
|
||||
o.beforeEach(function() {
|
||||
$window = browserMock(env)
|
||||
throttleMock = throttleMocker()
|
||||
|
||||
root = $window.document.body
|
||||
|
||||
redrawService = apiRedraw($window)
|
||||
redrawService = apiRedraw($window, throttleMock.throttle)
|
||||
route = apiRouter($window, redrawService)
|
||||
route.prefix(prefix)
|
||||
})
|
||||
|
||||
o.afterEach(function() {
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
})
|
||||
|
||||
o("throws on invalid `root` DOM node", function() {
|
||||
var threw = false
|
||||
try {
|
||||
|
|
@ -50,7 +55,7 @@ o.spec("route", function() {
|
|||
o(root.firstChild.nodeName).equals("DIV")
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (POJO component)", function() {
|
||||
o("routed mount points only redraw asynchronously (POJO component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
$window.location.href = prefix + "/"
|
||||
|
|
@ -60,11 +65,14 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (constructible component)", function() {
|
||||
o("routed mount points only redraw asynchronously (constructible component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
var Cmp = function(){}
|
||||
|
|
@ -77,11 +85,14 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("routed mount points can redraw synchronously (closure component)", function() {
|
||||
o("routed mount points only redraw asynchronously (closure component)", function() {
|
||||
var view = o.spy()
|
||||
|
||||
function Cmp() {return {view: view}}
|
||||
|
|
@ -93,8 +104,11 @@ o.spec("route", function() {
|
|||
|
||||
redrawService.redraw()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(view.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
|
||||
o("default route doesn't break back button", function(done) {
|
||||
|
|
@ -160,11 +174,12 @@ o.spec("route", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("redraws on events", function(done) {
|
||||
o("redraws on events", function() {
|
||||
var onupdate = o.spy()
|
||||
var oninit = o.spy()
|
||||
var onclick = o.spy()
|
||||
|
|
@ -194,12 +209,9 @@ o.spec("route", function() {
|
|||
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
|
||||
callAsync(function() {
|
||||
o(onupdate.callCount).equals(1)
|
||||
|
||||
done()
|
||||
})
|
||||
throttleMock.fire()
|
||||
o(onupdate.callCount).equals(1)
|
||||
})
|
||||
|
||||
o("event handlers can skip redraw", function(done) {
|
||||
|
|
@ -224,9 +236,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() {
|
||||
|
|
@ -500,7 +514,10 @@ o.spec("route", function() {
|
|||
o(oninit.callCount).equals(1)
|
||||
route.set("/def")
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(oninit.callCount).equals(2)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
|
@ -536,23 +553,28 @@ o.spec("route", function() {
|
|||
route(root, "/a", {
|
||||
"/a" : {
|
||||
render: function() {
|
||||
return m("div")
|
||||
return m("div", m("p"))
|
||||
},
|
||||
},
|
||||
"/b" : {
|
||||
render: function() {
|
||||
return m("div")
|
||||
return m("div", m("a"))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
var dom = root.firstChild
|
||||
var child = dom.firstChild
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
|
||||
route.set("/b")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild).equals(dom)
|
||||
o(root.firstChild.firstChild).notEquals(child)
|
||||
|
||||
done()
|
||||
})
|
||||
|
|
@ -586,6 +608,7 @@ o.spec("route", function() {
|
|||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
|
@ -621,6 +644,7 @@ o.spec("route", function() {
|
|||
o(renderCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(matchCount).equals(1)
|
||||
o(renderCount).equals(2)
|
||||
|
|
@ -665,7 +689,7 @@ o.spec("route", function() {
|
|||
route(root, "/a", {
|
||||
"/a" : {
|
||||
onmatch: function() {
|
||||
route.set("/b")
|
||||
route.set("/b", {}, {state: {a: 5}})
|
||||
},
|
||||
render: render
|
||||
},
|
||||
|
|
@ -684,6 +708,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()
|
||||
})
|
||||
|
|
@ -815,10 +840,14 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
route.set("/b")
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(render.callCount).equals(0)
|
||||
o(component.view.callCount).equals(2)
|
||||
|
||||
|
|
@ -939,6 +968,7 @@ o.spec("route", function() {
|
|||
o(onmatch.callCount).equals(1)
|
||||
|
||||
redrawService.redraw()
|
||||
throttleMock.fire()
|
||||
|
||||
o(view.callCount).equals(2)
|
||||
o(onmatch.callCount).equals(1)
|
||||
|
|
@ -1017,6 +1047,8 @@ o.spec("route", function() {
|
|||
})
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(onmatch.callCount).equals(1)
|
||||
o(render.callCount).equals(1)
|
||||
|
||||
|
|
@ -1024,6 +1056,8 @@ o.spec("route", function() {
|
|||
|
||||
callAsync(function() {
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(onmatch.callCount).equals(2)
|
||||
o(render.callCount).equals(2)
|
||||
|
||||
|
|
@ -1074,9 +1108,15 @@ o.spec("route", function() {
|
|||
route.set("/b")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild.nodeName).equals("B")
|
||||
|
||||
route.set("/a")
|
||||
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
|
||||
o(root.firstChild.nodeName).equals("A")
|
||||
|
||||
done()
|
||||
|
|
@ -1141,7 +1181,9 @@ o.spec("route", function() {
|
|||
|
||||
route.set("/b")
|
||||
|
||||
// setting the route is asynchronous
|
||||
callAsync(function() {
|
||||
throttleMock.fire()
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
done()
|
||||
|
|
@ -1181,9 +1223,7 @@ o.spec("route", function() {
|
|||
})
|
||||
})
|
||||
|
||||
o("throttles", function(done, timeout) {
|
||||
timeout(200)
|
||||
|
||||
o("throttles", function() {
|
||||
var i = 0
|
||||
$window.location.href = prefix + "/"
|
||||
route(root, "/", {
|
||||
|
|
@ -1197,12 +1237,11 @@ o.spec("route", function() {
|
|||
redrawService.redraw()
|
||||
var after = i
|
||||
|
||||
setTimeout(function() {
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(2) // redraws synchronously
|
||||
o(i).equals(3) // throttles rest
|
||||
done()
|
||||
}, FRAME_BUDGET * 2)
|
||||
throttleMock.fire()
|
||||
|
||||
o(before).equals(1) // routes synchronously
|
||||
o(after).equals(1) // redraws asynchronously
|
||||
o(i).equals(2)
|
||||
})
|
||||
|
||||
o("m.route.param is available outside of route handlers", function(done) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ var fs = require("fs");
|
|||
var bundle = require("./bundle")
|
||||
var minify = require("./minify")
|
||||
|
||||
var aliases = {o: "output", m: "minify", w: "watch", a: "aggressive"}
|
||||
var aliases = {o: "output", m: "minify", w: "watch", a: "aggressive", s: "save"}
|
||||
var params = {}
|
||||
var args = process.argv.slice(2), command = null
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
|
|
@ -27,8 +27,6 @@ function add(value) {
|
|||
bundle(params.input, params.output, {watch: params.watch})
|
||||
if (params.minify) {
|
||||
minify(params.output, params.output, {watch: params.watch, advanced: params.aggressive}, function(stats) {
|
||||
var readme, kb;
|
||||
|
||||
function format(n) {
|
||||
return n.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,")
|
||||
}
|
||||
|
|
@ -36,14 +34,16 @@ if (params.minify) {
|
|||
console.log("Original size: " + format(stats.originalGzipSize) + " bytes gzipped (" + format(stats.originalSize) + " bytes uncompressed)")
|
||||
console.log("Compiled size: " + format(stats.compressedGzipSize) + " bytes gzipped (" + format(stats.compressedSize) + " bytes uncompressed)")
|
||||
|
||||
readme = fs.readFileSync("./README.md", "utf8")
|
||||
kb = stats.compressedGzipSize / 1024
|
||||
if (params.save) {
|
||||
var readme = fs.readFileSync("./README.md", "utf8")
|
||||
var kb = stats.compressedGzipSize / 1000
|
||||
|
||||
fs.writeFileSync("./README.md",
|
||||
readme.replace(
|
||||
/(<!-- size -->)(.+?)(<!-- \/size -->)/,
|
||||
"$1" + (kb % 1 ? kb.toFixed(2) : kb) + " KB$3"
|
||||
fs.writeFileSync("./README.md",
|
||||
readme.replace(
|
||||
/(<!-- size -->)(.+?)(<!-- \/size -->)/,
|
||||
"$1" + (kb % 1 ? kb.toFixed(2) : kb) + " KB$3"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
58
dangerfile.js
Normal file
58
dangerfile.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* global danger warn fail */
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs"),
|
||||
path = require("path"),
|
||||
|
||||
locater = require("locater"),
|
||||
pinpoint = require("pinpoint"),
|
||||
dedent = require("dedent");
|
||||
|
||||
// Various views of changed/added files
|
||||
var jsfiles = danger.git.created_files
|
||||
.concat(danger.git.modified_files)
|
||||
.filter((file) => path.extname(file) === ".js"),
|
||||
|
||||
changelog = danger.git.modified_files.find((file) =>
|
||||
file === "docs/change-log.md"
|
||||
),
|
||||
|
||||
appfiles = jsfiles.filter((file) =>
|
||||
file.indexOf("tests/") === -1
|
||||
);
|
||||
|
||||
function link(file, anchor, text) {
|
||||
var repo = danger.github.pr.head.repo.html_url,
|
||||
ref = danger.github.pr.head.ref;
|
||||
|
||||
return danger.utils.href(`${repo}/blob/${ref}/${file}${anchor || ""}`, file || text);
|
||||
}
|
||||
|
||||
// All PRs should be targeted against `next`
|
||||
if(danger.github.pr.base.ref !== "next") {
|
||||
warn("PRs should be based on `next`, rebase before submitting please");
|
||||
}
|
||||
|
||||
// Any non-test JS changes should probably have a change-log entry
|
||||
if(appfiles.length && !changelog) {
|
||||
warn(dedent(`
|
||||
Please add an entry to ${link("docs/change-log.md")}.
|
||||
`))
|
||||
}
|
||||
|
||||
// Call out if `o.only(...)` was left in
|
||||
jsfiles
|
||||
.filter((file) => file.indexOf("tests/") > -1)
|
||||
.forEach((file) => {
|
||||
var code = fs.readFileSync(file, "utf8"),
|
||||
locs = locater.find("o.only", code);
|
||||
|
||||
locs.forEach((loc) =>
|
||||
fail(dedent(`
|
||||
Please remove the \`o.only\` from ${link(file, `#L${loc.line}`)}.
|
||||
<pre lang="javascript">
|
||||
${pinpoint(code, {line: loc.line, column : loc.cursor})}
|
||||
</pre>
|
||||
`))
|
||||
)
|
||||
});
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
# Change log
|
||||
|
||||
- [v1.1.3](#v113)
|
||||
- [v1.1.2](#v112)
|
||||
- [v1.1.1](#v111)
|
||||
- [v1.1.0](#v110)
|
||||
- [v1.0.1](#v101)
|
||||
|
|
@ -8,14 +10,69 @@
|
|||
|
||||
---
|
||||
|
||||
### v2.0.0 (WIP)
|
||||
|
||||
#### Breaking changes
|
||||
|
||||
- API: `m.redraw()` is always asynchronous ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
- API: `m.mount()` will only render its own root when called, it will not trigger a `redraw()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
|
||||
#### News
|
||||
|
||||
- API: Introduction of `m.redraw.sync()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- API: `m.route.set()` causes all mount points to be redrawn ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.3
|
||||
|
||||
#### Bug fixes:
|
||||
|
||||
- move out npm dependencies added by mistake
|
||||
|
||||
---
|
||||
|
||||
### v1.1.2
|
||||
|
||||
#### Bug fixes:
|
||||
|
||||
- core: Namespace fixes [#1819](https://github.com/MithrilJS/mithril.js/issues/1819), ([#1825](https://github.com/MithrilJS/mithril.js/pull/1825) [@SamuelTilly](https://github.com/SamuelTilly)), [#1820](https://github.com/MithrilJS/mithril.js/issues/1820) ([#1864](https://github.com/MithrilJS/mithril.js/pull/1864)), [#1872](https://github.com/MithrilJS/mithril.js/issues/1872) ([#1873](https://github.com/MithrilJS/mithril.js/pull/1873))
|
||||
- core: Fix select option to allow empty string value [#1814](https://github.com/MithrilJS/mithril.js/issues/1814) ([#1828](https://github.com/MithrilJS/mithril.js/pull/1828) [@spacejack](https://github.com/spacejack))
|
||||
- core: Reset e.redraw when it was set to `false` [#1850](https://github.com/MithrilJS/mithril.js/issues/1850) ([#1890](https://github.com/MithrilJS/mithril.js/pull/1890))
|
||||
- core: differentiate between `{ value: "" }` and `{ value: 0 }` for form elements [#1595 comment](https://github.com/MithrilJS/mithril.js/pull/1595#issuecomment-304071453) ([#1862](https://github.com/MithrilJS/mithril.js/pull/1862))
|
||||
- core: Don't reset the cursor of textareas in IE10 when setting an identical `value` [#1870](https://github.com/MithrilJS/mithril.js/issues/1870) ([#1871](https://github.com/MithrilJS/mithril.js/pull/1871))
|
||||
- hypertext: Correct handling of `[value=""]` ([#1843](https://github.com/MithrilJS/mithril.js/issues/1843), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards))
|
||||
- router: Don't overwrite the options object when redirecting from `onmatch with m.route.set()` [#1857](https://github.com/MithrilJS/mithril.js/issues/1857) ([#1889](https://github.com/MithrilJS/mithril.js/pull/1889))
|
||||
- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893))
|
||||
|
||||
#### Ospec improvements:
|
||||
|
||||
- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout))
|
||||
- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529))
|
||||
|
||||
#### Docs / Repo maintenance:
|
||||
|
||||
Our thanks to [@0joshuaolson1](https://github.com/0joshuaolson1), [@ACXgit](https://github.com/ACXgit), [@cavemansspa](https://github.com/cavemansspa), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards), [@dlepaux](https://github.com/dlepaux), [@isaaclyman](https://github.com/isaaclyman), [@kevinkace](https://github.com/kevinkace), [@micellius](https://github.com/micellius), [@spacejack](https://github.com/spacejack) and [@yurivish](https://github.com/yurivish)
|
||||
|
||||
#### Other:
|
||||
|
||||
- Addition of a performance regression test suite ([#1789](https://github.com/MithrilJS/mithril.js/issues/1789))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.1
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/lhorie/mithril.js/issues/#1752) / [#1753](https://github.com/lhorie/mithril.js/pull/#1753) ([@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- hyperscript: remove `attrs.class` after normalizing to `attrs.className` - [#1764](https://github.com/lhorie/mithril.js/issues/#1764) / [#1769](https://github.com/lhorie/mithril.js/pull/#1769)
|
||||
- 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
|
||||
|
|
@ -26,10 +83,10 @@
|
|||
|
||||
#### Bug fixes
|
||||
|
||||
- fix IE11 input[type] error - [#1610](https://github.com/lhorie/mithril.js/issues/1610)
|
||||
- apply [#1609](https://github.com/lhorie/mithril.js/issues/1609) to unkeyed children case
|
||||
- fix abort detection [#1612](https://github.com/lhorie/mithril.js/issues/1612)
|
||||
- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/lhorie/mithril.js/issues/1593)
|
||||
- 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)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -37,12 +94,12 @@
|
|||
|
||||
#### News
|
||||
|
||||
- performance improvements in IE [#1598](https://github.com/lhorie/mithril.js/pull/1598)
|
||||
- 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/lhorie/mithril.js/issues/1579)
|
||||
- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/lhorie/mithril.js/issues/1609)
|
||||
- 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)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -155,7 +212,7 @@ m("div", {
|
|||
// 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
|
||||
// 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) { /*...*/ }
|
||||
|
|
@ -472,9 +529,9 @@ In `v0.2.x` reading route params was entirely handled through `m.route.param()`.
|
|||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
controller : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
controller : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
|
|
@ -489,11 +546,11 @@ m.route(document.body, "/booga", {
|
|||
"/:attr" : {
|
||||
oninit : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -531,14 +588,14 @@ It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefaul
|
|||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
if (condition) e.preventDefault()
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("a[href=/]", {config: m.route})
|
||||
}
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
if (condition) e.preventDefault()
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("a[href=/]", {config: m.route})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -546,9 +603,9 @@ var Component = {
|
|||
|
||||
```javascript
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("a", {onclick: function() {if (!condition) m.route.set("/")}})
|
||||
}
|
||||
view: function() {
|
||||
return m("a", {onclick: function() {if (!condition) m.route.set("/")}})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -562,14 +619,14 @@ Components no longer call `this.onunload` when they are being removed. They now
|
|||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -577,12 +634,12 @@ var Component = {
|
|||
|
||||
```javascript
|
||||
var Component = {
|
||||
onremove : function() {
|
||||
// ...
|
||||
}
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
onremove : function() {
|
||||
// ...
|
||||
}
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -598,13 +655,13 @@ In addition, requests no longer have `m.startComputation`/`m.endComputation` sem
|
|||
|
||||
```javascript
|
||||
var data = m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
initialValue: [],
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
initialValue: [],
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data())
|
||||
console.log(data())
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
|
|
@ -613,15 +670,15 @@ setTimeout(function() {
|
|||
```javascript
|
||||
var data = []
|
||||
m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
})
|
||||
.then(function (responseBody) {
|
||||
data = responseBody
|
||||
data = responseBody
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data) // note: not a getter-setter
|
||||
console.log(data) // note: not a getter-setter
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
|
|
@ -653,11 +710,11 @@ greetAsync()
|
|||
|
||||
```javascript
|
||||
var greetAsync = function() {
|
||||
return new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
return new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
|
|
@ -679,7 +736,7 @@ m.sync([
|
|||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -691,7 +748,7 @@ Promise.all([
|
|||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -706,7 +763,7 @@ In `v0.2.x`, the `xlink` namespace was the only supported attribute namespace, a
|
|||
```javascript
|
||||
m("svg",
|
||||
// the `href` attribute is namespaced automatically
|
||||
m("image[href='image.gif']")
|
||||
m("image[href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
|
|
@ -715,7 +772,7 @@ m("svg",
|
|||
```javascript
|
||||
m("svg",
|
||||
// User-specified namespace on the `href` attribute
|
||||
m("image[xlink:href='image.gif']")
|
||||
m("image[xlink:href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
|
|
|
|||
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/
|
||||
|
|
@ -316,7 +316,7 @@ var 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 our state, which by definition makes this state difficult to access from outside.
|
||||
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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
---
|
||||
|
||||
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 application, but it may be pleasing to use depending on your team's preferences.
|
||||
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 |
|
|
@ -74,7 +74,7 @@ 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 | Mithril
|
||||
------- | -------
|
||||
|
|
@ -139,7 +139,7 @@ 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
|
||||
------- | -------
|
||||
|
|
@ -193,7 +193,7 @@ 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 a [Vue 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
|
||||
------ | -------
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */}
|
|||
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")
|
||||
|
||||
function generate(pathname) {
|
||||
|
|
@ -27,6 +24,7 @@ function generate(pathname) {
|
|||
if (pathname.match(/\.md$/)) {
|
||||
var outputFilename = pathname.replace(/\.md$/, ".html")
|
||||
var markdown = fs.readFileSync(pathname, "utf-8")
|
||||
var anchors = {}
|
||||
var fixed = markdown
|
||||
.replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags
|
||||
return "<code>" + (a + b + c).replace(/\|/g, "|") + "</code>"
|
||||
|
|
@ -48,19 +46,26 @@ function generate(pathname) {
|
|||
var title = fixed.match(/^#([^\n\r]+)/i) || []
|
||||
var html = layout
|
||||
.replace(/<title>Mithril\.js<\/title>/, "<title>" + title[1] + " - Mithril.js</title>")
|
||||
.replace(/\[version\]/, version) // update version
|
||||
.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("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8")
|
||||
}
|
||||
else if (!pathname.match(/lint|generate/)) {
|
||||
fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
|
||||
fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, "utf-8"), "utf-8")
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ 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.
|
||||
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:
|
||||
|
||||
|
|
@ -65,6 +65,13 @@ Let's create an HTML file to follow along:
|
|||
</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
|
||||
|
|
@ -85,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
|
||||
|
|
@ -119,6 +131,11 @@ 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).
|
||||
|
||||
```jsx
|
||||
|
|
@ -185,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
|
||||
|
|
@ -218,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
|
||||
|
|
@ -260,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.
|
||||
|
|
|
|||
|
|
@ -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="https://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>
|
||||
|
|
|
|||
10
docs/jsx.md
10
docs/jsx.md
|
|
@ -39,7 +39,7 @@ 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:
|
||||
|
|
@ -98,7 +98,7 @@ npm install babel-core babel-loader babel-preset-es2015 babel-plugin-transform-r
|
|||
|
||||
Create a `.babelrc` file:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
|
|
@ -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: {
|
||||
|
|
@ -128,6 +130,8 @@ module.exports = {
|
|||
}
|
||||
```
|
||||
|
||||
For those familiar with Webpack already, please note that adding the Babel options to the `babel-loader` section of your `webpack.config.js` will throw an error, so you need to include them in the separate `.babelrc` file.
|
||||
|
||||
This configuration assumes the source code file for the application entry point is in `src/index.js`, and this will output the bundle to `bin/app.js`.
|
||||
|
||||
To run the bundler, setup an npm script. Open `package.json` and add this entry under `"scripts"`:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<title>Mithril.js</title>
|
||||
<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>
|
||||
|
|
@ -14,8 +15,8 @@
|
|||
<nav>
|
||||
<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>
|
||||
|
|
@ -26,8 +27,9 @@
|
|||
<small>License: MIT. © Leo Horie.</small>
|
||||
</section>
|
||||
</main>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-jsx.min.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"
|
||||
|
|
|
|||
|
|
@ -27,16 +27,20 @@ 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
|
||||
|
||||
`m.mount(element, component)`
|
||||
`m.mount(element, Component)`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
`element` | `Element` | Yes | A DOM element that will be the parent node to the subtree
|
||||
`component` | `Component|null` | Yes | The [component](components.md) to be rendered. `null` unmounts the tree and cleans up internal state.
|
||||
`Component` | `Component|null` | Yes | The [component](components.md) to be rendered. `null` unmounts the tree and cleans up internal state.
|
||||
**returns** | | | Returns nothing
|
||||
|
||||
[How to read signatures](signatures.md)
|
||||
|
|
@ -45,7 +49,9 @@ Argument | Type | Required | Description
|
|||
|
||||
### How it works
|
||||
|
||||
Similar to [`m.render()`](render.md), the `m.mount()` method takes a component and mounts a corresponding DOM tree into `element`. If `element` already has a DOM tree mounted via a previous `m.mount()` call, the component is diffed against the previous vnode tree and the existing DOM tree is modified only where needed to reflect the changes. Unchanged DOM nodes are not touched at all.
|
||||
`m.mount(element, Component)`, when called renders the component into the element and subscribe the `(element, Component)` pair to the redraw subsystem. That tree will be re-rendered when [manual](redraw.md) or [automatic](autoredraw.md) redraws are triggered.
|
||||
|
||||
On redraw, the new vDOM tree is compared (or "diffed") with the old one, and the existing DOM tree is modified only where needed to reflect the changes. Unchanged DOM nodes are not touched at all.
|
||||
|
||||
#### Replace a component
|
||||
|
||||
|
|
@ -69,7 +75,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.
|
||||
A component rendered via `m.mount` [automatically redraws](autoredraw.md) 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -16,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)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
- [Description](#description)
|
||||
- [Signature](#signature)
|
||||
- [Static members](#static-members)
|
||||
-[m.redraw.sync()](#mredrawsync)
|
||||
- [How it works](#how-it-works)
|
||||
|
||||
---
|
||||
|
|
@ -10,12 +12,10 @@
|
|||
|
||||
Updates the DOM after a change in the application data layer.
|
||||
|
||||
You DON'T need to call it if data is modified within the execution context of an event handler defined in a Mithril view, or after request completion when using `m.request`/`m.jsonp`.
|
||||
You DON'T need to call it if data is modified within the execution context of an event handler defined in a Mithril view, or after request completion when using `m.request`/`m.jsonp`. The [autoredraw](autoredraw.md) system, which is built on top of `m.redraw()` will take care of it.
|
||||
|
||||
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 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.
|
||||
|
||||
---
|
||||
|
||||
### Signature
|
||||
|
|
@ -26,6 +26,16 @@ Argument | Type | Required | Description
|
|||
----------- | -------------------- | -------- | ---
|
||||
**returns** | | | Returns nothing
|
||||
|
||||
#### Static members
|
||||
|
||||
##### m.redraw.sync
|
||||
|
||||
`m.redraw.sync()`
|
||||
|
||||
Argument | Type | Required | Description
|
||||
----------- | -------------------- | -------- | ---
|
||||
**returns** | | | Returns nothing
|
||||
|
||||
---
|
||||
|
||||
### How it works
|
||||
|
|
@ -34,4 +44,12 @@ When callbacks outside of Mithril run, you need to notify Mithril's rendering en
|
|||
|
||||
To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you used `m.mount` or `m.route`. If you rendered via `m.render`, you should use `m.render` to redraw.
|
||||
|
||||
You should not call m.redraw from a [lifecycle method](lifecycle-methods.md). Doing so will result in undefined behavior.
|
||||
`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()` (we provide a fallback for IE9). It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate.
|
||||
|
||||
`m.redraw.sync()` is mostly intended to make videos play work in iOS. That only works in response to user-triggered events. It comes with several caveat:
|
||||
|
||||
- You should not call `m.redraw.sync()` from a [lifecycle method](lifecycle-methods.md) or the `view()` method of a component. Doing so will result in undefined behavior (it throws an error when possible).
|
||||
- `m.redraw.sync()` called from an event handler can cause the DOM to be modified while an event is bubbling. Depending on the structure of the old and new DOM trees, the event can finish the bubbling phase in the new tree and trigger unwanted handlers.
|
||||
- It is not throttled. One call to `m.redraw.sync()` causes immediately one `m.render()` call per root registered with [`m.mount()`](mount.md) or [`m.route()`](route.md).
|
||||
|
||||
`m.redraw()` doesn't have any of those issues, you can call it from wherever you like.
|
||||
|
|
@ -1,49 +1,114 @@
|
|||
# Releasing
|
||||
# Mithril Release Processes
|
||||
|
||||
## Publishing to NPM
|
||||
**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 new builds of mithril to NPM is mostly automated via `npm run release`
|
||||
## Releasing a new Mithril version
|
||||
|
||||
1. Update information in `docs/change-log.md` to match reality & the new version that will be released
|
||||
2. `npm run release <major|minor|patch|semver>`
|
||||
### Prepare the release
|
||||
|
||||
All further steps are automated and run as follows:
|
||||
1. Ensure your local branch is up to date
|
||||
|
||||
3. New bundles are generated using updated version
|
||||
4. Tests are run
|
||||
5. Linting is run (but doesn't fail build)
|
||||
6. Version number in package.json is incremented
|
||||
7. `git add` called on bundle output
|
||||
8. `package.json` and updated bundles are committed to git
|
||||
9. previous commit is tagged using new version number
|
||||
10. `git push --follow-tags` pushes up new version commit & tag to github
|
||||
11. Travis sees new release, starts build
|
||||
12. Travis generates new bundles before running tests
|
||||
13. Travis runs tests
|
||||
14. Travis lints files (but can't fail build)
|
||||
15. If build fails, abort
|
||||
16. Build succeeded, so travis will commit back any changes to the repo (but there won't be any)
|
||||
17. Travis sees that this commit has a tag associated with it
|
||||
18. Travis will use the encrypted npm creds in `.travis.yml` to publish a new version to npm
|
||||
```bash
|
||||
$ git co next
|
||||
$ git pull --rebase mithriljs next
|
||||
```
|
||||
|
||||
## Publishing a GitHub release
|
||||
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`
|
||||
|
||||
Happens automatically as part of the [Publishing to NPM](#publishing-to-npm) process described above.
|
||||
```
|
||||
$ git add .
|
||||
$ git commit -m "Preparing for release"
|
||||
|
||||
Does require a manual description to be added though, as the auto-generated one isn't very interesting. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build.
|
||||
# Push to your branch
|
||||
$ git push
|
||||
|
||||
## Updating `docs/change-log.md`
|
||||
# Push to MithrilJS/mithril.js
|
||||
$ git push mithriljs next
|
||||
```
|
||||
|
||||
This is still a manual process, I'm sorry.
|
||||
### Merge from `next` to `master`
|
||||
|
||||
## Updating docs (outside of a new version)
|
||||
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.
|
||||
|
||||
1. `git co next`
|
||||
2. `git pull lhorie next`
|
||||
3. `git co master`
|
||||
4. `git co next -- ./docs`
|
||||
5. Ensure that no new features are added
|
||||
6. `git push lhorie`
|
||||
7. After the Travis build completes new docs should appear in ~3 minutes
|
||||
```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.
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
@ -329,7 +329,7 @@ function upload(e) {
|
|||
url: "/api/v1/upload",
|
||||
data: data,
|
||||
config: function(xhr) {
|
||||
xhr.addEventListener("progress", function(e) {
|
||||
xhr.upload.addEventListener("progress", function(e) {
|
||||
progress = e.loaded / e.total
|
||||
|
||||
m.redraw() // tell Mithril that data changed and a re-render is needed
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Argument | Type | Required | D
|
|||
|
||||
##### m.route.set
|
||||
|
||||
Redirects to a matching route, or to the default route if no matching routes can be found.
|
||||
Redirects to a matching route, or to the default route if no matching routes can be found. Triggers an asynchronous redraw off all mount points.
|
||||
|
||||
`m.route.set(path, data, options)`
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ Argument | Type | Required | Description
|
|||
|
||||
##### m.route.param
|
||||
|
||||
Retrieves a route parameter. A route parameter is a key-value pair. Route parameters may come from a few different places:
|
||||
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"`)
|
||||
|
|
@ -137,9 +137,11 @@ 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}`
|
||||
|
||||
|
|
@ -319,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.
|
||||
|
|
@ -492,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
|
||||
|
|
|
|||
|
|
@ -392,8 +392,7 @@ var User = {
|
|||
load: function(id) {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "https://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) {
|
||||
|
|
@ -508,8 +507,7 @@ var User = {
|
|||
load: function(id) {
|
||||
return m.request({
|
||||
method: "GET",
|
||||
url: "https://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) {
|
||||
|
|
@ -520,7 +518,7 @@ var User = {
|
|||
save: function() {
|
||||
return m.request({
|
||||
method: "PUT",
|
||||
url: "https://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,
|
||||
})
|
||||
|
|
@ -616,4 +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).
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ var state = {
|
|||
//view
|
||||
var Todos = {
|
||||
add: function(e) {
|
||||
if (e.keyCode === 13) {
|
||||
if (e.keyCode === 13 && e.target.value) {
|
||||
state.dispatch("createTodo", [e.target.value])
|
||||
e.target.value = ""
|
||||
}
|
||||
|
|
|
|||
106
mithril.js
106
mithril.js
|
|
@ -28,7 +28,7 @@ function compileSelector(selector) {
|
|||
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 || true
|
||||
else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attrs.className = classes.join(" ")
|
||||
|
|
@ -42,8 +42,8 @@ function execSelector(state, attrs, children) {
|
|||
attrs[key] = state.attrs[key]
|
||||
}
|
||||
}
|
||||
if (className != null) {
|
||||
if (attrs.class != null) {
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
|
|
@ -296,7 +296,8 @@ var _8 = function($window, Promise) {
|
|||
}
|
||||
else {
|
||||
var error = new Error(xhr.responseText)
|
||||
for (var key in response) error[key] = response[key]
|
||||
error.code = xhr.status
|
||||
error.response = response
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
|
@ -375,8 +376,15 @@ 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++) {
|
||||
|
|
@ -433,12 +441,9 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
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)
|
||||
|
|
@ -501,7 +506,7 @@ var coreRenderer = function($window) {
|
|||
//update
|
||||
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) {
|
||||
|
|
@ -578,7 +583,7 @@ var coreRenderer = function($window) {
|
|||
if (movable.dom != null) nextSibling = movable.dom
|
||||
}
|
||||
else {
|
||||
var dom = createNode(parent, v, hooks, undefined, nextSibling)
|
||||
var dom = createNode(parent, v, hooks, ns, nextSibling)
|
||||
nextSibling = dom
|
||||
}
|
||||
}
|
||||
|
|
@ -649,10 +654,7 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
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) {
|
||||
|
|
@ -832,12 +834,21 @@ 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)
|
||||
|
|
@ -952,10 +963,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
|
||||
// First time0 rendering into a node clears it out
|
||||
var namespace = dom.namespaceURI
|
||||
// First time rendering0 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), false, 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()
|
||||
|
|
@ -964,43 +976,44 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
function throttle(callback) {
|
||||
//60fps translates to 16.6ms, round it down since setTimeout requires int
|
||||
var time = 16
|
||||
var delay = 16
|
||||
var last = 0, pending = null
|
||||
var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
|
||||
return function() {
|
||||
var now = Date.now()
|
||||
if (last === 0 || now - last >= time) {
|
||||
last = now
|
||||
callback()
|
||||
}
|
||||
else if (pending === null) {
|
||||
var elapsed = Date.now() - last
|
||||
if (pending === null) {
|
||||
pending = timeout(function() {
|
||||
pending = null
|
||||
callback()
|
||||
last = Date.now()
|
||||
}, time - (now - last))
|
||||
}, delay - elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
var _11 = function($window) {
|
||||
var _11 = function($window, throttleMock) {
|
||||
var renderService = coreRenderer($window)
|
||||
renderService.setEventCallback(function(e) {
|
||||
if (e.redraw !== false) redraw()
|
||||
if (e.redraw === false) e.redraw = undefined
|
||||
else redraw()
|
||||
})
|
||||
var callbacks = []
|
||||
var rendering = false
|
||||
function subscribe(key1, callback) {
|
||||
unsubscribe(key1)
|
||||
callbacks.push(key1, throttle(callback))
|
||||
callbacks.push(key1, callback)
|
||||
}
|
||||
function unsubscribe(key1) {
|
||||
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 sync() {
|
||||
if (rendering) throw new Error("Nested m.redraw.sync() call")
|
||||
rendering = true
|
||||
for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {/*noop*/}
|
||||
rendering = false
|
||||
}
|
||||
var redraw = (throttleMock || throttle)(sync)
|
||||
redraw.sync = sync
|
||||
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
|
||||
}
|
||||
var redrawService = _11(window)
|
||||
|
|
@ -1019,7 +1032,7 @@ var _16 = function(redrawService0) {
|
|||
redrawService0.render(root, Vnode(component))
|
||||
}
|
||||
redrawService0.subscribe(root, run0)
|
||||
redrawService0.redraw()
|
||||
run0()
|
||||
}
|
||||
}
|
||||
m.mount = _16(redrawService)
|
||||
|
|
@ -1156,9 +1169,14 @@ var _20 = function($window, redrawService0) {
|
|||
var render1, component, attrs3, currentPath, lastUpdate
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
var run1 = function() {
|
||||
function run1() {
|
||||
if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
|
||||
}
|
||||
var redraw2 = function() {
|
||||
run1()
|
||||
redraw2 = redrawService0.redraw
|
||||
}
|
||||
redrawService0.subscribe(root, run1)
|
||||
var bail = function(path) {
|
||||
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
|
||||
else throw new Error("Could not resolve default route " + defaultRoute)
|
||||
|
|
@ -1169,7 +1187,7 @@ var _20 = function($window, redrawService0) {
|
|||
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()
|
||||
redraw2()
|
||||
}
|
||||
if (payload.view || typeof payload === "function") update({}, payload)
|
||||
else {
|
||||
|
|
@ -1181,10 +1199,12 @@ var _20 = function($window, redrawService0) {
|
|||
else update(payload, "div")
|
||||
}
|
||||
}, bail)
|
||||
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)
|
||||
}
|
||||
|
|
@ -1220,7 +1240,7 @@ m.request = requestService.request
|
|||
m.jsonp = requestService.jsonp
|
||||
m.parseQueryString = parseQueryString
|
||||
m.buildQueryString = buildQueryString
|
||||
m.version = "1.0.1"
|
||||
m.version = "1.1.3"
|
||||
m.vnode = Vnode
|
||||
if (typeof module !== "undefined") module["exports"] = m
|
||||
else window.m = m
|
||||
|
|
|
|||
87
mithril.min.js
vendored
87
mithril.min.js
vendored
|
|
@ -1,43 +1,44 @@
|
|||
(function(){function B(b,d,f,g,e,n){return{tag:b,key:d,attrs:f,children:g,text:e,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function C(b){var d=arguments[1],f=2,g;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){var e;if(!(e=M[b])){g="div";for(var n=[],k={};e=P.exec(b);){var q=e[1],m=e[2];""===q&&""!==m?g=m:"#"===q?k.id=m:"."===q?
|
||||
n.push(m):"["===e[3][0]&&((q=e[6])&&(q=q.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===e[4]?n.push(q):k[e[4]]=q||!0)}0<n.length&&(k.className=n.join(" "));e=M[b]={tag:g,attrs:k}}}if(null==d)d={};else if("object"!==typeof d||null!=d.tag||Array.isArray(d))d={},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=B.normalizeChildren(g);if("string"===typeof b){g=!1;var l,u,n=d.className||d["class"],a;for(a in e.attrs)N.call(e.attrs,
|
||||
a)&&(d[a]=e.attrs[a]);null!=n&&(null!=d["class"]&&(d["class"]=void 0,d.className=n),null!=e.attrs.className&&(d.className=e.attrs.className+" "+n));for(a in d)if(N.call(d,a)&&"key"!==a){g=!0;break}Array.isArray(f)&&1===f.length&&null!=f[0]&&"#"===f[0].tag?u=f[0].children:l=f;return B(e.tag,d.key,g?d:void 0,l,u)}return B(b,d.key,d,f)}function Q(b){var d=0,f=null,g="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var e=Date.now();0===d||16<=e-d?(d=e,b()):
|
||||
null===f&&(f=g(function(){f=null;b();d=Date.now()},16-(e-d)))}}B.normalize=function(b){return Array.isArray(b)?B("[",void 0,void 0,B.normalizeChildren(b),void 0,void 0):null!=b&&"object"!==typeof b?B("#",void 0,void 0,!1===b?"":b,void 0,void 0):b};B.normalizeChildren=function(b){for(var d=0;d<b.length;d++)b[d]=B.normalize(b[d]);return b};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,M={},N={}.hasOwnProperty;C.trust=function(b){null==b&&(b="");return B("<",void 0,
|
||||
void 0,b,void 0,void 0)};C.fragment=function(b,d){return B("[",b.key,b,B.normalizeChildren(d),void 0,void 0)};var x=function(b){function d(b,a){return function r(d){var k;try{if(!a||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(k=d.then))l(function(){a||0!==b.length||console.error("Possible unhandled promise rejection:",d);for(var f=0;f<b.length;f++)b[f](d);e.length=0;n.length=0;m.state=a;m.retry=function(){r(d)}});else{if(d===g)throw new TypeError("Promise can't be resolved w/ itself");
|
||||
f(k.bind(d))}}catch(R){q(R)}}}function f(b){function a(a){return function(b){0<d++||a(b)}}var d=0,e=a(q);try{b(a(k),e)}catch(A){e(A)}}if(!(this instanceof x))throw Error("Promise must be called with `new`");if("function"!==typeof b)throw new TypeError("executor must be a function");var g=this,e=[],n=[],k=d(e,!0),q=d(n,!1),m=g._instance={resolvers:e,rejectors:n},l="function"===typeof setImmediate?setImmediate:setTimeout;f(b)};x.prototype.then=function(b,d){function f(b,d,f,k){d.push(function(a){if("function"!==
|
||||
typeof b)f(a);else try{e(b(a))}catch(w){n&&n(w)}});"function"===typeof g.retry&&k===g.state&&g.retry()}var g=this._instance,e,n,k=new x(function(b,d){e=b;n=d});f(b,g.resolvers,e,!0);f(d,g.rejectors,n,!1);return k};x.prototype["catch"]=function(b){return this.then(null,b)};x.resolve=function(b){return b instanceof x?b:new x(function(d){d(b)})};x.reject=function(b){return new x(function(d,f){f(b)})};x.all=function(b){return new x(function(d,f){var g=b.length,e=0,n=[];if(0===b.length)d([]);else for(var k=
|
||||
0;k<b.length;k++)(function(k){function m(b){e++;n[k]=b;e===g&&d(n)}null==b[k]||"object"!==typeof b[k]&&"function"!==typeof b[k]||"function"!==typeof b[k].then?m(b[k]):b[k].then(m,f)})(k)})};x.race=function(b){return new x(function(d,f){for(var g=0;g<b.length;g++)b[g].then(d,f)})};"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 F=function(b){function d(b,
|
||||
g){if(Array.isArray(g))for(var e=0;e<g.length;e++)d(b+"["+e+"]",g[e]);else if("[object Object]"===Object.prototype.toString.call(g))for(e in g)d(b+"["+e+"]",g[e]);else f.push(encodeURIComponent(b)+(null!=g&&""!==g?"="+encodeURIComponent(g):""))}if("[object Object]"!==Object.prototype.toString.call(b))return"";var f=[],g;for(g in b)d(g,b[g]);return f.join("&")},S=/^file:\/\//i,K=function(b,d){function f(){function a(){0===--b&&"function"===typeof u&&u()}var b=0;return function A(d){var e=d.then;d.then=
|
||||
function(){b++;var f=e.apply(d,arguments);f.then(a,function(d){a();if(0===b)throw d;});return A(f)};return d}}function g(a,b){if("string"===typeof a){var d=a;a=b||{};null==a.url&&(a.url=d)}return a}function e(a,b){if(null==b)return a;for(var d=a.match(/:[^\/]+/gi)||[],e=0;e<d.length;e++){var f=d[e].slice(1);null!=b[f]&&(a=a.replace(d[e],b[f]))}return a}function n(a,b){var d=F(b);if(""!==d){var e=0>a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a);
|
||||
}}function q(a){return a.responseText}function m(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 l=0,u;return{request:function(a,l){var u=f();a=g(a,l);var w=new d(function(d,f){null==a.method&&(a.method="GET");a.method=a.method.toUpperCase();var g="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(h){return h}:JSON.stringify);"function"!==typeof a.deserialize&&(a.deserialize=k);"function"!==typeof a.extract&&(a.extract=q);a.url=e(a.url,a.data);g?a.data=a.serialize(a.data):a.url=n(a.url,a.data);var l=new b.XMLHttpRequest,u=!1,w=l.abort;l.abort=function(){u=!0;w.call(l)};l.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&&g&&l.setRequestHeader("Content-Type",
|
||||
"application/json; charset=utf-8");a.deserialize===k&&l.setRequestHeader("Accept","application/json, text/*");a.withCredentials&&(l.withCredentials=a.withCredentials);for(var r in a.headers)({}).hasOwnProperty.call(a.headers,r)&&l.setRequestHeader(r,a.headers[r]);"function"===typeof a.config&&(l=a.config(l,a)||l);l.onreadystatechange=function(){if(!u&&4===l.readyState)try{var h=a.extract!==q?a.extract(l,a):a.deserialize(a.extract(l,a));if(200<=l.status&&300>l.status||304===l.status||S.test(a.url))d(m(a.type,
|
||||
h));else{var c=Error(l.responseText),p;for(p in h)c[p]=h[p];f(c)}}catch(v){f(v)}};g&&null!=a.data?l.send(a.data):l.send()});return!0===a.background?w:u(w)},jsonp:function(a,k){var u=f();a=g(a,k);var q=new d(function(d,f){var g=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=b.document.createElement("script");b[g]=function(e){k.parentNode.removeChild(k);d(m(a.type,e));delete b[g]};k.onerror=function(){k.parentNode.removeChild(k);f(Error("JSONP request failed"));delete b[g]};null==
|
||||
a.data&&(a.data={});a.url=e(a.url,a.data);a.data[a.callbackKey||"callback"]=g;k.src=n(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?q:u(q)},setCompletionCallback:function(a){u=a}}}(window,x),O=function(b){function d(h,c,p,a,b,d,e){for(;p<a;p++){var v=c[p];null!=v&&f(h,v,b,e,d)}}function f(h,c,p,a,b){var v=c.tag;if("string"===typeof v)switch(c.state={},null!=c.attrs&&C(c.attrs,c,p),v){case "#":return c.dom=D.createTextNode(c.children),l(h,c.dom,b),c.dom;case "<":return g(h,
|
||||
c,b);case "[":var k=D.createDocumentFragment();null!=c.children&&(v=c.children,d(k,v,0,v.length,p,null,a));c.dom=k.firstChild;c.domSize=k.childNodes.length;l(h,k,b);return k;default:var m=c.tag;switch(c.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var t=(v=c.attrs)&&v.is,m=a?t?D.createElementNS(a,m,{is:t}):D.createElementNS(a,m):t?D.createElement(m,{is:t}):D.createElement(m);c.dom=m;if(null!=v)for(k in t=a,v)A(c,k,null,v[k],t);l(h,m,b);null!=
|
||||
c.attrs&&null!=c.attrs.contenteditable?u(c):(null!=c.text&&(""!==c.text?m.textContent=c.text:c.children=[B("#",void 0,void 0,c.text,void 0,void 0)]),null!=c.children&&(h=c.children,d(m,h,0,h.length,p,null,a),h=c.attrs,"select"===c.tag&&null!=h&&("value"in h&&A(c,"value",null,h.value,void 0),"selectedIndex"in h&&A(c,"selectedIndex",null,h.selectedIndex,void 0))));return m}else return e(c,p),null!=c.instance?(p=f(h,c.instance,p,a,b),c.dom=c.instance.dom,c.domSize=null!=c.dom?c.instance.domSize:0,l(h,
|
||||
p,b),c=p):(c.domSize=0,c=J),c}function g(h,c,p){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=D.createElement(a);a.innerHTML=c.children;c.dom=a.firstChild;c.domSize=a.childNodes.length;c=D.createDocumentFragment();for(var b;b=a.firstChild;)c.appendChild(b);l(h,c,p);return c}function e(h,c){var a;if("function"===typeof h.tag.view){h.state=Object.create(h.tag);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&&C(h.attrs,h,c);C(h._state,h,c);h.instance=B.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 n(h,c,p,b,e,g,n){if(c!==
|
||||
p&&(null!=c||null!=p))if(null==c)d(h,p,0,p.length,e,g,void 0);else if(null==p)a(c,0,c.length,p);else{if(c.length===p.length){for(var v=!1,t=0;t<p.length;t++)if(null!=p[t]&&null!=c[t]){v=null==p[t].key&&null==c[t].key;break}if(v){for(t=0;t<c.length;t++)c[t]!==p[t]&&(null==c[t]&&null!=p[t]?f(h,p[t],e,n,m(c,t+1,g)):null==p[t]?a(c,t,t+1,p):k(h,c[t],p[t],e,m(c,t+1,g),b,n));return}}if(!b)a:{if(null!=c.pool&&Math.abs(c.pool.length-p.length)<=Math.abs(c.length-p.length)&&(b=p[0]&&p[0].children&&p[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 u=c.pool;c=c.concat(c.pool)}for(var t=v=0,w=c.length-1,y=p.length-1,G;w>=v&&y>=t;){var r=c[v],z=p[t];if(r!==z||b)if(null==r)v++;else if(null==z)t++;else if(r.key===z.key){var A=null!=u&&v>=c.length-u.length||null==u&&b;v++;t++;k(h,r,z,e,m(c,v,g),A,n);b&&r.tag===z.tag&&l(h,q(r),g)}else if(r=c[w],r!==z||b)if(null==r)w--;else if(null==z)t++;
|
||||
else if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),(b||t<y)&&l(h,q(r),m(c,v,g)),w--,t++;else break;else w--,t++;else v++,t++}for(;w>=v&&y>=t;){r=c[w];z=p[y];if(r!==z||b)if(null==r)w--;else{if(null!=z)if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),b&&r.tag===z.tag&&l(h,q(r),g),null!=r.dom&&(g=r.dom),w--;else{if(!G){G=c;var r=w,A={},E;for(E=0;E<r;E++){var x=G[E];null!=x&&(x=x.key,null!=x&&(A[x]=E))}G=A}null!=z&&(r=G[z.key],
|
||||
null!=r?(A=c[r],k(h,A,z,e,m(c,w+1,g),b,n),l(h,q(A),g),c[r].skip=!0,null!=A.dom&&(g=A.dom)):g=f(h,z,e,void 0,g))}y--}else w--,y--;if(y<t)break}d(h,p,t,y+1,e,g,n);a(c,v,w+1,p)}}function k(h,c,a,b,d,m,l){var p=c.tag;if(p===a.tag){a.state=c.state;a._state=c._state;a.events=c.events;var v;if(v=!m){var r,z;null!=a.attrs&&"function"===typeof a.attrs.onbeforeupdate&&(r=a.attrs.onbeforeupdate.call(a.state,a,c));"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeupdate&&(z=a._state.onbeforeupdate.call(a.state,
|
||||
a,c));void 0===r&&void 0===z||r||z?v=!1:(a.dom=c.dom,a.domSize=c.domSize,a.instance=c.instance,v=!0)}if(!v)if("string"===typeof p)switch(null!=a.attrs&&(m?(a.state={},C(a.attrs,a,b)):I(a.attrs,a,b)),p){case "#":c.children.toString()!==a.children.toString()&&(c.dom.nodeValue=a.children);a.dom=c.dom;break;case "<":c.children!==a.children?(q(c),g(h,a,d)):(a.dom=c.dom,a.domSize=c.domSize);break;case "[":n(h,c.children,a.children,m,b,d,l);c=0;b=a.children;a.dom=null;if(null!=b){for(m=0;m<b.length;m++){var y=
|
||||
b[m];null!=y&&null!=y.dom&&(null==a.dom&&(a.dom=y.dom),c+=y.domSize||1)}1!==c&&(a.domSize=c)}break;default:h=l;d=a.dom=c.dom;switch(a.tag){case "svg":h="http://www.w3.org/2000/svg";break;case "math":h="http://www.w3.org/1998/Math/MathML"}"textarea"===a.tag&&(null==a.attrs&&(a.attrs={}),null!=a.text&&(a.attrs.value=a.text,a.text=void 0));l=c.attrs;p=a.attrs;v=h;if(null!=p)for(y in p)A(a,y,l&&l[y],p[y],v);if(null!=l)for(y in l)null!=p&&y in p||("className"===y&&(y="class"),"o"!==y[0]||"n"!==y[1]||E(y)?
|
||||
"key"!==y&&a.dom.removeAttribute(y):x(a,y,void 0));null!=a.attrs&&null!=a.attrs.contenteditable?u(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=[B("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=a.text&&(a.children=[B("#",void 0,void 0,a.text,void 0,void 0)]),n(d,c.children,a.children,m,b,null,h))}else{if(m)e(a,b);else{a.instance=B.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?f(h,a.instance,b,l,d):k(h,c.instance,a.instance,b,d,m,l),a.dom=a.instance.dom,a.domSize=a.instance.domSize):null!=c.instance?(w(c.instance,null),a.dom=void 0,a.domSize=0):(a.dom=c.dom,a.domSize=c.domSize)}}else w(c,null),f(h,a,b,l,d)}function q(a){var c=a.domSize;if(null!=c||null==a.dom){var b=D.createDocumentFragment();if(0<c){for(a=a.dom;--c;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}
|
||||
function m(a,c,b){for(;c<a.length;c++)if(null!=a[c]&&null!=a[c].dom)return a[c].dom;return b}function l(a,c,b){b&&b.parentNode?a.insertBefore(c,b):a.appendChild(c)}function u(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:w(h,d))}}function w(a,c){function b(){if(++d===
|
||||
h&&(r(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,f=g.parentNode;null!=f&&f.removeChild(g)}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 r(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)r(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c<a.length;c++){var b=a[c];null!=b&&r(b)}}
|
||||
function A(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===D.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!E(c)){var g=c.indexOf(":");if(-1<g&&"xlink"===c.substr(0,g))h.setAttributeNS("http://www.w3.org/1999/xlink",c.slice(g+1),d);else if("o"===c[0]&&"n"===c[1]&&"function"===typeof d)x(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 f in d)h.style[f]=d[f];if(null!=a&&"string"!==typeof a)for(f in a)f in d||(h.style[f]="")}else c in h&&"href"!==c&&"list"!==c&&"form"!==c&&"width"!==c&&"height"!==c&&void 0===e&&!(a.attrs.is||-1<a.tag.indexOf("-"))?"input"===a.tag&&"value"===c&&a.dom.value==d&&a.dom===D.activeElement||"select"===a.tag&&"value"===c&&a.dom.value==d&&a.dom===D.activeElement||"option"===a.tag&&"value"===c&&a.dom.value==d||("input"===a.tag&&"type"===c?h.setAttribute(c,
|
||||
d):h[c]=d):"boolean"===typeof d?d?h.setAttribute(c,""):h.removeAttribute(c):h.setAttribute("className"===c?"class":c,d)}}function E(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function x(a,c,b){var d=a.dom,e="function"!==typeof F?b:function(a){var c=b.call(d,a);F.call(d,a);return c};if(c in d)d[c]="function"===typeof b?e:null;else{var h=c.slice(2);void 0===a.events&&(a.events={});a.events[c]!==e&&(null!=a.events[c]&&d.removeEventListener(h,
|
||||
a.events[c],!1),"function"===typeof b&&(a.events[c]=e,d.addEventListener(h,a.events[c],!1)))}}function C(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 D=b.document,J=D.createDocumentFragment(),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=D.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);n(a,a.vnodes,B.normalizeChildren(b),!1,c,null,void 0);a.vnodes=b;for(var e=0;e<c.length;e++)c[e]();D.activeElement!==d&&d.focus()},setEventCallback:function(a){return F=a}}},H=function(b){function d(b){b=g.indexOf(b);-1<b&&g.splice(b,2)}function f(){for(var b=1;b<g.length;b+=2)g[b]()}b=O(b);b.setEventCallback(function(b){!1!==b.redraw&&f()});var g=[];return{subscribe:function(b,f){d(b);g.push(b,Q(f))},unsubscribe:d,
|
||||
redraw:f,render:b.render}}(window);K.setCompletionCallback(H.redraw);C.mount=function(b){return function(d,f){if(null===f)b.render(d,[]),b.unsubscribe(d);else{if(null==f.view&&"function"!==typeof f)throw Error("m.mount(element, component) expects a component, not a vnode");b.subscribe(d,function(){b.render(d,B(f))});b.redraw()}}}(H);var T=x,L=function(b){if(""===b||null==b)return{};"?"===b.charAt(0)&&(b=b.slice(1));b=b.split("&");for(var d={},f={},g=0;g<b.length;g++){var e=b[g].split("="),n=decodeURIComponent(e[0]),
|
||||
e=2===e.length?decodeURIComponent(e[1]):"";"true"===e?e=!0:"false"===e&&(e=!1);var k=n.split(/\]\[?|\[/),q=d;-1<n.indexOf("[")&&k.pop();for(var m=0;m<k.length;m++){var n=k[m],l=k[m+1],l=""==l||!isNaN(parseInt(l,10)),u=m===k.length-1;""===n&&(n=k.slice(0,m).join(),null==f[n]&&(f[n]=0),n=f[n]++);null==q[n]&&(q[n]=u?e:l?[]:{});q=q[n]}}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 f(b){return function(){null==
|
||||
k&&(k=n(function(){k=null;b()}))}}function g(b,d,e){var a=b.indexOf("?"),g=b.indexOf("#"),f=-1<a?a:-1<g?g:b.length;if(-1<a){var a=L(b.slice(a+1,-1<g?g:b.length)),k;for(k in a)d[k]=a[k]}if(-1<g)for(k in d=L(b.slice(g+1)),d)e[k]=d[k];return b.slice(0,f)}var e="function"===typeof b.history.pushState,n="function"===typeof setImmediate?setImmediate:setTimeout,k,q={prefix:"#!",getPath:function(){switch(q.prefix.charAt(0)){case "#":return d("hash").slice(q.prefix.length);case "?":return d("search").slice(q.prefix.length)+
|
||||
d("hash");default:return d("pathname").slice(q.prefix.length)+d("search")+d("hash")}},setPath:function(d,f,k){var a={},l={};d=g(d,a,l);if(null!=f){for(var m in f)a[m]=f[m];d=d.replace(/:([^\/]+)/g,function(b,d){delete a[d];return f[d]})}(m=F(a))&&(d+="?"+m);(l=F(l))&&(d+="#"+l);e?(l=k?k.state:null,m=k?k.title:null,b.onpopstate(),k&&k.replace?b.history.replaceState(l,m,q.prefix+d):b.history.pushState(l,m,q.prefix+d)):b.location.href=q.prefix+d},defineRoutes:function(d,k,n){function a(){var a=q.getPath(),
|
||||
e={},f=g(a,e,e),l=b.history.state;if(null!=l)for(var m in l)e[m]=l[m];for(var u in d)if(l=new RegExp("^"+u.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),l.test(f)){f.replace(l,function(){for(var b=u.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),f=0;f<b.length;f++)e[b[f].replace(/:|\./g,"")]=decodeURIComponent(g[f]);k(d[u],e,a,u)});return}n(a,e)}e?b.onpopstate=f(a):"#"===q.prefix.charAt(0)&&(b.onhashchange=a);a()}};return q};C.route=function(b,d){var f=U(b),g=function(b){return b},
|
||||
e,n,k,q,m,l=function(b,a,l){if(null==b)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var u=function(){null!=e&&d.render(b,e(B(n,k.key,k)))},w=function(b){if(b!==a)f.setPath(a,null,{replace:!0});else throw Error("Could not resolve default route "+a);};f.defineRoutes(l,function(a,b,d){var f=m=function(a,l){f===m&&(n=null==l||"function"!==typeof l.view&&"function"!==typeof l?"div":l,k=b,q=d,m=null,e=(a.render||g).bind(a),u())};a.view||"function"===typeof a?f({},
|
||||
a):a.onmatch?T.resolve(a.onmatch(b,d)).then(function(b){f(a,b)},w):f(a,"div")},w);d.subscribe(b,u)};l.set=function(b,a,d){null!=m&&(d={replace:!0});m=null;f.setPath(b,a,d)};l.get=function(){return q};l.prefix=function(b){f.prefix=b};l.link=function(b){b.dom.setAttribute("href",f.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(f.prefix)&&(a=a.slice(f.prefix.length)),l.set(a,void 0,
|
||||
void 0))}};l.param=function(b){return"undefined"!==typeof k&&"undefined"!==typeof b?k[b]:k};return l}(window,H);C.withAttr=function(b,d,f){return function(g){d.call(f||this,b in g.currentTarget?g.currentTarget[b]:g.currentTarget.getAttribute(b))}};var V=O(window);C.render=V.render;C.redraw=H.redraw;C.request=K.request;C.jsonp=K.jsonp;C.parseQueryString=L;C.buildQueryString=F;C.version="1.0.1";C.vnode=B;"undefined"!==typeof module?module.exports=C:window.m=C})();
|
||||
(function(){function z(b,d,e,l,n,g){return{tag:b,key:d,attrs:e,children:l,text:n,dom:g,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],l=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 n="div";for(var g=[],h={};d=Q.exec(b);){var r=d[1],f=d[2];""===r&&""!==f?n=f:"#"===r?h.id=f:"."===r?g.push(f):
|
||||
"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?g.push(r):h[d[4]]=""===r?r:r||!0)}0<g.length&&(h.className=g.join(" "));d=M[b]={tag:n,attrs:h}}if(null==e)e={};else if("object"!==typeof e||null!=e.tag||Array.isArray(e))e={},l=1;if(arguments.length===l+1)n=arguments[l],Array.isArray(n)||(n=[n]);else for(n=[];l<arguments.length;)n.push(arguments[l++]);l=z.normalizeChildren(n);if("string"===typeof b){n=!1;var m,p,g=e.className||e["class"],a;for(a in d.attrs)N.call(d.attrs,
|
||||
a)&&(e[a]=d.attrs[a]);void 0!==g&&(void 0!==e["class"]&&(e["class"]=void 0,e.className=g),null!=d.attrs.className&&(e.className=d.attrs.className+" "+g));for(a in e)if(N.call(e,a)&&"key"!==a){n=!0;break}Array.isArray(l)&&1===l.length&&null!=l[0]&&"#"===l[0].tag?p=l[0].children:m=l;return z(d.tag,e.key,n?e:void 0,m,p)}return z(b,e.key,e,l)}function R(b){var d=0,e=null,l="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var n=Date.now()-d;null===e&&(e=l(function(){e=
|
||||
null;b();d=Date.now()},16-n))}}z.normalize=function(b){return Array.isArray(b)?z("[",void 0,void 0,z.normalizeChildren(b),void 0,void 0):null!=b&&"object"!==typeof b?z("#",void 0,void 0,!1===b?"":b,void 0,void 0):b};z.normalizeChildren=function(b){for(var d=0;d<b.length;d++)b[d]=z.normalize(b[d]);return b};var Q=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,M={},N={}.hasOwnProperty;A.trust=function(b){null==b&&(b="");return z("<",void 0,void 0,b,void 0,void 0)};A.fragment=
|
||||
function(b,d){return z("[",b.key,b,z.normalizeChildren(d),void 0,void 0)};var w=function(b){function d(b,a){return function t(d){var h;try{if(!a||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(h=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);n.length=0;g.length=0;f.state=a;f.retry=function(){t(d)}});else{if(d===l)throw new TypeError("Promise can't be resolved w/ itself");e(h.bind(d))}}catch(G){r(G)}}}
|
||||
function e(b){function a(a){return function(b){0<d++||a(b)}}var d=0,f=a(r);try{b(a(h),f)}catch(C){f(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 l=this,n=[],g=[],h=d(n,!0),r=d(g,!1),f=l._instance={resolvers:n,rejectors:g},m="function"===typeof setImmediate?setImmediate:setTimeout;e(b)};w.prototype.then=function(b,d){function e(b,d,e,h){d.push(function(a){if("function"!==typeof b)e(a);else try{n(b(a))}catch(y){g&&
|
||||
g(y)}});"function"===typeof l.retry&&h===l.state&&l.retry()}var l=this._instance,n,g,h=new w(function(b,d){n=b;g=d});e(b,l.resolvers,n,!0);e(d,l.rejectors,g,!1);return h};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 l=b.length,n=0,g=[];if(0===b.length)d([]);else for(var h=0;h<b.length;h++)(function(h){function f(b){n++;
|
||||
g[h]=b;n===l&&d(g)}null==b[h]||"object"!==typeof b[h]&&"function"!==typeof b[h]||"function"!==typeof b[h].then?f(b[h]):b[h].then(f,e)})(h)})};w.race=function(b){return new w(function(d,e){for(var l=0;l<b.length;l++)b[l].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 D=function(b){function d(b,g){if(Array.isArray(g))for(var h=
|
||||
0;h<g.length;h++)d(b+"["+h+"]",g[h]);else if("[object Object]"===Object.prototype.toString.call(g))for(h in g)d(b+"["+h+"]",g[h]);else e.push(encodeURIComponent(b)+(null!=g&&""!==g?"="+encodeURIComponent(g):""))}if("[object Object]"!==Object.prototype.toString.call(b))return"";var e=[],l;for(l in b)d(l,b[l]);return e.join("&")},S=/^file:\/\//i,K=function(b,d){function e(){function a(){0===--b&&"function"===typeof p&&p()}var b=0;return function C(d){var e=d.then;d.then=function(){b++;var f=e.apply(d,
|
||||
arguments);f.then(a,function(d){a();if(0===b)throw d;});return C(f)};return d}}function l(a,b){if("string"===typeof a){var d=a;a=b||{};null==a.url&&(a.url=d)}return a}function n(a,b){if(null==b)return a;for(var d=a.match(/:[^\/]+/gi)||[],e=0;e<d.length;e++){var f=d[e].slice(1);null!=b[f]&&(a=a.replace(d[e],b[f]))}return a}function g(a,b){var d=D(b);if(""!==d){var e=0>a.indexOf("?")?"?":"&";a+=e+d}return a}function h(a){try{return""!==a?JSON.parse(a):null}catch(y){throw Error(a);}}function r(a){return a.responseText}
|
||||
function f(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,p;return{request:function(a,m){var p=e();a=l(a,m);var y=new d(function(d,e){null==a.method&&(a.method="GET");a.method=a.method.toUpperCase();var l="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=h);"function"!==typeof a.extract&&(a.extract=r);a.url=n(a.url,a.data);l?a.data=a.serialize(a.data):a.url=g(a.url,a.data);var m=new b.XMLHttpRequest,p=!1,y=m.abort;m.abort=function(){p=!0;y.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&&l&&m.setRequestHeader("Content-Type","application/json; charset=utf-8");a.deserialize===
|
||||
h&&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(!p&&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(f(a.type,b));else{var k=Error(m.responseText);k.code=
|
||||
m.status;k.response=b;e(k)}}catch(c){e(c)}};l&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?y:p(y)},jsonp:function(a,h){var r=e();a=l(a,h);var p=new d(function(d,e){var h=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,l=b.document.createElement("script");b[h]=function(e){l.parentNode.removeChild(l);d(f(a.type,e));delete b[h]};l.onerror=function(){l.parentNode.removeChild(l);e(Error("JSONP request failed"));delete b[h]};null==a.data&&(a.data={});a.url=n(a.url,
|
||||
a.data);a.data[a.callbackKey||"callback"]=h;l.src=g(a.url,a.data);b.document.documentElement.appendChild(l)});return!0===a.background?p:r(p)},setCompletionCallback:function(a){p=a}}}(window,w),P=function(b){function d(k,c,q,a,b,d,f){for(;q<a;q++){var u=c[q];null!=u&&e(k,u,b,f,d)}}function e(k,c,q,a,b){var f=c.tag;if("string"===typeof f)switch(c.state={},null!=c.attrs&&A(c.attrs,c,q),f){case "#":return c.dom=x.createTextNode(c.children),m(k,c.dom,b),c.dom;case "<":return l(k,c,b);case "[":var u=x.createDocumentFragment();
|
||||
null!=c.children&&(f=c.children,d(u,f,0,f.length,q,null,a));c.dom=u.firstChild;c.domSize=u.childNodes.length;m(k,u,b);return u;default:var h=c.tag,g=(f=c.attrs)&&f.is,h=(a=c.attrs&&c.attrs.xmlns||O[c.tag]||a)?g?x.createElementNS(a,h,{is:g}):x.createElementNS(a,h):g?x.createElement(h,{is:g}):x.createElement(h);c.dom=h;if(null!=f)for(u in g=a,f)C(c,u,null,f[u],g);m(k,h,b);null!=c.attrs&&null!=c.attrs.contenteditable?p(c):(null!=c.text&&(""!==c.text?h.textContent=c.text:c.children=[z("#",void 0,void 0,
|
||||
c.text,void 0,void 0)]),null!=c.children&&(k=c.children,d(h,k,0,k.length,q,null,a),k=c.attrs,"select"===c.tag&&null!=k&&("value"in k&&C(c,"value",null,k.value,void 0),"selectedIndex"in k&&C(c,"selectedIndex",null,k.selectedIndex,void 0))));return h}else return n(c,q),null!=c.instance?(q=e(k,c.instance,q,a,b),c.dom=c.instance.dom,c.domSize=null!=c.dom?c.instance.domSize:0,m(k,q,b),c=q):(c.domSize=0,c=J),c}function l(k,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(k,c,q);return c}function n(k,c){if("function"===typeof k.tag.view){k.state=Object.create(k.tag);var a=k.state.view;if(null!=a.$$reentrantLock$$)return J;a.$$reentrantLock$$=!0}else{k.state=void 0;a=k.tag;if(null!=a.$$reentrantLock$$)return J;
|
||||
a.$$reentrantLock$$=!0;k.state=null!=k.tag.prototype&&"function"===typeof k.tag.prototype.view?new k.tag(k):k.tag(k)}k._state=k.state;null!=k.attrs&&A(k.attrs,k,c);A(k._state,k,c);k.instance=z.normalize(k._state.view.call(k.state,k));if(k.instance===k)throw Error("A view cannot return the vnode it received as argument");a.$$reentrantLock$$=null}function g(k,c,q,b,l,g,n){if(c!==q&&(null!=c||null!=q))if(null==c)d(k,q,0,q.length,l,g,n);else if(null==q)a(c,0,c.length,q);else{if(c.length===q.length){var u=
|
||||
!1;for(var p=0;p<q.length;p++)if(null!=q[p]&&null!=c[p]){u=null==q[p].key&&null==c[p].key;break}if(u){for(p=0;p<c.length;p++)c[p]!==q[p]&&(null==c[p]&&null!=q[p]?e(k,q[p],l,n,f(c,p+1,g)):null==q[p]?a(c,p,p+1,q):h(k,c[p],q[p],l,f(c,p+1,g),b,n));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 y=c.pool;c=c.concat(c.pool)}for(var B=p=0,v=c.length-1,E=q.length-1,F;v>=p&&E>=B;){var t=c[p];u=q[B];if(t!==u||b)if(null==t)p++;else if(null==u)B++;else if(t.key===u.key){var x=null!=y&&p>=c.length-y.length||null==y&&b;p++;B++;h(k,t,u,l,f(c,p,g),x,n);b&&t.tag===u.tag&&m(k,r(t),g)}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!=y&&v>=c.length-y.length||null==y&&b,h(k,t,u,l,f(c,v+1,g),x,n),(b||B<E)&&m(k,r(t),f(c,p,g)),v--,
|
||||
B++;else break;else v--,B++;else p++,B++}for(;v>=p&&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!=y&&v>=c.length-y.length||null==y&&b,h(k,t,u,l,f(c,v+1,g),x,n),b&&t.tag===u.tag&&m(k,r(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!F){F=c;x=v;t={};var C;for(C=0;C<x;C++){var w=F[C];null!=w&&(w=w.key,null!=w&&(t[w]=C))}F=t}null!=u&&(x=F[u.key],null!=x?(t=c[x],h(k,t,u,l,f(c,v+1,g),b,n),m(k,r(t),g),c[x].skip=!0,null!=t.dom&&(g=t.dom)):g=e(k,u,l,n,g))}E--}else v--,
|
||||
E--;if(E<B)break}d(k,q,B,E+1,l,g,n);a(c,p,v+1,q)}}function h(k,c,a,b,d,f,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=!f){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&&(f?(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),l(k,a,d)):(a.dom=c.dom,a.domSize=c.domSize);break;case "[":g(k,c.children,a.children,f,b,d,m);c=0;b=a.children;a.dom=null;if(null!=b){for(f=0;f<b.length;f++){var v=b[f];null!=v&&null!=v.dom&&(null==a.dom&&(a.dom=v.dom),c+=v.domSize||1)}1!==c&&(a.domSize=c)}break;default:k=a.dom=
|
||||
c.dom;m=a.attrs&&a.attrs.xmlns||O[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]||w(v)?"key"!==v&&a.dom.removeAttribute(v):G(a,v,void 0));null!=a.attrs&&null!=a.attrs.contenteditable?p(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=[z("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=a.text&&(a.children=[z("#",void 0,void 0,a.text,void 0,void 0)]),g(k,c.children,a.children,f,b,null,m))}else{if(f)n(a,b);else{a.instance=z.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(k,a.instance,b,m,d):h(k,c.instance,a.instance,b,d,f,m),
|
||||
a.dom=a.instance.dom,a.domSize=a.instance.domSize):null!=c.instance?(y(c.instance,null),a.dom=void 0,a.domSize=0):(a.dom=c.dom,a.domSize=c.domSize)}}else y(c,null),e(k,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 f(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 p(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 k=a[c];null!=k&&(k.skip?k.skip=!1:y(k,d))}}function y(a,c){function b(){if(++d===k&&(t(a),a.dom)){var b=a.domSize||1;if(1<b)for(var f=a.dom;--b;){var e=f.nextSibling,g=e.parentNode;null!=g&&g.removeChild(e)}b=
|
||||
a.dom;f=b.parentNode;null!=f&&f.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 k=1,d=0;if(a.attrs&&"function"===typeof a.attrs.onbeforeremove){var f=a.attrs.onbeforeremove.call(a.state,a);null!=f&&"function"===typeof f.then&&(k++,f.then(b,b))}"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeremove&&(f=a._state.onbeforeremove.call(a.state,a),
|
||||
null!=f&&"function"===typeof f.then&&(k++,f.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,f){var k=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&&!w(c)){var e=c.indexOf(":");if(-1<e&&"xlink"===c.substr(0,e))k.setAttributeNS("http://www.w3.org/1999/xlink",c.slice(e+1),d);else if("o"===c[0]&&"n"===c[1]&&"function"===typeof d)G(a,c,d);else if("style"===c)if(a=b,a===d&&(k.style.cssText="",a=null),null==d)k.style.cssText="";else if("string"===typeof d)k.style.cssText=d;else{"string"===typeof a&&(k.style.cssText="");for(var g in d)k.style[g]=d[g];if(null!=a&&"string"!==typeof a)for(g in a)g in
|
||||
d||(k.style[g]="")}else if(c in k&&"href"!==c&&"list"!==c&&"form"!==c&&"width"!==c&&"height"!==c&&void 0===f&&!(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?
|
||||
k.setAttribute(c,d):k[c]=d}else"boolean"===typeof d?d?k.setAttribute(c,""):k.removeAttribute(c):k.setAttribute("className"===c?"class":c,d)}}function w(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function G(a,b,d){var c=a.dom,f="function"!==typeof D?d:function(a){var b=d.call(c,a);D.call(c,a);return b};if(b in c)c[b]="function"===typeof d?f:null;else{var k=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==f&&(null!=a.events[b]&&
|
||||
c.removeEventListener(k,a.events[b],!1),"function"===typeof d&&(a.events[b]=f,c.addEventListener(k,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(),O={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"},D;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,f=a.namespaceURI;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);g(a,a.vnodes,z.normalizeChildren(b),!1,c,null,"http://www.w3.org/1999/xhtml"===f?void 0:f);a.vnodes=b;for(f=0;f<c.length;f++)c[f]();x.activeElement!==d&&d.focus()},setEventCallback:function(a){return D=a}}},H=function(b,d){function e(b){b=g.indexOf(b);-1<b&&g.splice(b,2)}function l(){if(h)throw Error("Nested m.redraw.sync() call");h=!0;for(var b=1;b<g.length;b+=2)try{g[b]()}catch(m){}h=!1}var n=
|
||||
P(b);n.setEventCallback(function(b){!1===b.redraw?b.redraw=void 0:r()});var g=[],h=!1,r=(d||R)(l);r.sync=l;return{subscribe:function(b,d){e(b);g.push(b,d)},unsubscribe:e,redraw:r,render:n.render}}(window);K.setCompletionCallback(H.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");var l=function(){b.render(d,z(e))};b.subscribe(d,l);l()}}}(H);
|
||||
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={},l=0;l<b.length;l++){var n=b[l].split("=");var g=decodeURIComponent(n[0]);n=2===n.length?decodeURIComponent(n[1]):"";"true"===n?n=!0:"false"===n&&(n=!1);var h=g.split(/\]\[?|\[/),r=d;-1<g.indexOf("[")&&h.pop();for(var f=0;f<h.length;f++){g=h[f];var m=h[f+1],m=""==m||!isNaN(parseInt(m,10)),p=f===h.length-1;""===g&&(g=h.slice(0,f).join(),null==e[g]&&(e[g]=0),g=e[g]++);null==r[g]&&(r[g]=
|
||||
p?n:m?[]:{});r=r[g]}}return d},U=function(b){function d(d){var f=b.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==f[0]&&(f="/"+f);return f}function e(b){return function(){null==h&&(h=g(function(){h=null;b()}))}}function l(b,d,e){var a=b.indexOf("?"),f=b.indexOf("#"),g=-1<a?a:-1<f?f:b.length;if(-1<a){var a=L(b.slice(a+1,-1<f?f:b.length)),l;for(l in a)d[l]=a[l]}if(-1<f)for(l in d=L(b.slice(f+1)),d)e[l]=d[l];return b.slice(0,g)}var n="function"===typeof b.history.pushState,
|
||||
g="function"===typeof setImmediate?setImmediate:setTimeout,h,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,g){var a={},f={};d=l(d,a,f);if(null!=e){for(var h in e)a[h]=e[h];d=d.replace(/:([^\/]+)/g,function(b,d){delete a[d];return e[d]})}(h=D(a))&&(d+="?"+h);(f=D(f))&&(d+="#"+f);
|
||||
n?(f=g?g.state:null,h=g?g.title:null,b.onpopstate(),g&&g.replace?b.history.replaceState(f,h,r.prefix+d):b.history.pushState(f,h,r.prefix+d)):b.location.href=r.prefix+d},defineRoutes:function(d,g,h){function a(){var a=r.getPath(),f={},e=l(a,f,f),p=b.history.state;if(null!=p)for(var m in p)f[m]=p[m];for(var n in d)if(p=new RegExp("^"+n.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),p.test(e)){e.replace(p,function(){for(var b=n.match(/:[^\/]+/g)||[],e=[].slice.call(arguments,
|
||||
1,-2),h=0;h<b.length;h++)f[b[h].replace(/:|\./g,"")]=decodeURIComponent(e[h]);g(d[n],f,a,n)});return}h(a,f)}n?b.onpopstate=e(a):"#"===r.prefix.charAt(0)&&(b.onhashchange=a);a()}};return r};A.route=function(b,d){var e=U(b),l=function(b){return b},n,g,h,r,f,m=function(b,a,m){function p(){null!=n&&d.render(b,n(z(g,h.key,h)))}if(null==b)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var w=function(){p();w=d.redraw};d.subscribe(b,p);var y=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=f=function(a,p){e===f&&(g=null==p||"function"!==typeof p.view&&"function"!==typeof p?"div":p,h=b,r=d,f=null,n=(a.render||l).bind(a),w())};a.view||"function"===typeof a?e({},a):a.onmatch?T.resolve(a.onmatch(b,d)).then(function(b){e(a,b)},y):e(a,"div")},y)};m.set=function(b,a,d){null!=f&&(d=d||{},d.replace=!0);f=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 h&&"undefined"!==typeof b?h[b]:h};return m}(window,H);A.withAttr=function(b,d,e){return function(l){d.call(e||this,b in l.currentTarget?l.currentTarget[b]:l.currentTarget.getAttribute(b))}};
|
||||
var V=P(window);A.render=V.render;A.redraw=H.redraw;A.request=K.request;A.jsonp=K.jsonp;A.parseQueryString=L;A.buildQueryString=D;A.version="1.1.3";A.vnode=z;"undefined"!==typeof module?module.exports=A:window.m=A})();
|
||||
21
ospec/LICENSE
Normal file
21
ospec/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.
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
# ospec
|
||||
ospec [](https://www.npmjs.com/package/ospec) [](https://www.npmjs.com/package/ospec)
|
||||
=====
|
||||
|
||||
[About](#about) | [Usage](#usage) | [API](#api) | [Goals](#goals)
|
||||
|
||||
Noiseless testing framework
|
||||
|
||||
Version: 1.2.3
|
||||
License: MIT
|
||||
|
||||
## About
|
||||
|
||||
- ~180 LOC
|
||||
|
|
@ -252,7 +250,7 @@ o.run()
|
|||
The `o.new()` method can be used to create new instances of ospec, which can be run in parallel. Note that each instance will report independently, and there's no aggregation of results.
|
||||
|
||||
```javascript
|
||||
var _o = o.new()
|
||||
var _o = o.new('optional name')
|
||||
_o("a test", function() {
|
||||
_o(1).equals(1)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ function traverseDirectory(pathname, callback) {
|
|||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
/* eslint-disable no-bitwise, no-process-exit */
|
||||
"use strict"
|
||||
|
||||
module.exports = new function init() {
|
||||
module.exports = new function init(name) {
|
||||
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) {
|
||||
if (results == null) throw new Error("Assertions should not occur outside test definitions")
|
||||
|
|
@ -222,6 +224,7 @@ module.exports = new function init() {
|
|||
}
|
||||
}
|
||||
console.log(
|
||||
(name ? name + ": " : "") +
|
||||
results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
|
||||
"of which " + results.filter(function(result){return result.error}).length + " failed"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ospec",
|
||||
"version": "1.2.3",
|
||||
"version": "1.3.0",
|
||||
"description": "Noiseless testing framework",
|
||||
"main": "ospec.js",
|
||||
"directories": {
|
||||
|
|
@ -12,5 +12,5 @@
|
|||
"bin": {
|
||||
"ospec": "./bin/ospec"
|
||||
},
|
||||
"repository": "lhorie/mithril.js#rewrite"
|
||||
"repository": "MithrilJS/mithril.js"
|
||||
}
|
||||
|
|
|
|||
4358
package-lock.json
generated
Normal file
4358
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
42
package.json
42
package.json
|
|
@ -1,22 +1,24 @@
|
|||
{
|
||||
"name": "mithril",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.3",
|
||||
"description": "A framework for building brilliant applications",
|
||||
"author": "Leo Horie",
|
||||
"license": "MIT",
|
||||
"main": "mithril.js",
|
||||
"repository": "lhorie/mithril.js",
|
||||
"repository": "MithrilJS/mithril.js",
|
||||
"scripts": {
|
||||
"dev": "node bundler/cli browser.js -o mithril.js -w",
|
||||
"dev": "node bundler/cli browser.js -output mithril.js -watch",
|
||||
"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",
|
||||
"build-browser": "node bundler/cli browser.js -output mithril.js",
|
||||
"build-min": "node bundler/cli browser.js -output mithril.min.js -minify",
|
||||
"precommit": "lint-staged",
|
||||
"lintdocs": "node docs/lint",
|
||||
"gendocs": "node docs/generate",
|
||||
"lint": "eslint .",
|
||||
"lint": "eslint . || true",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"perf": "node performance/test-perf.js",
|
||||
"test": "node ospec/bin/ospec",
|
||||
"posttest": "npm run lint || true",
|
||||
"posttest": "npm run lint",
|
||||
"cover": "istanbul cover --print both ospec/bin/ospec",
|
||||
"release": "npm version -m 'v%s'",
|
||||
"preversion": "npm run test",
|
||||
|
|
@ -24,12 +26,26 @@
|
|||
"postversion": "git push --follow-tags"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^3.16.1",
|
||||
"istanbul": "^0.4.3",
|
||||
"marked": "^0.3.6"
|
||||
"@alrra/travis-scripts": "^3.0.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"danger": "^1.0.0",
|
||||
"dedent": "^0.7.0",
|
||||
"eslint": "^3.19.0",
|
||||
"gh-pages": "^0.12.0",
|
||||
"istanbul": "^0.4.5",
|
||||
"lint-staged": "^4.0.2",
|
||||
"locater": "^1.3.0",
|
||||
"marked": "^0.3.6",
|
||||
"pinpoint": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"ospec": "./ospec/bin/ospec",
|
||||
"bundle": "./bundler/bin/bundle"
|
||||
}
|
||||
"ospec": "./ospec/bin/ospec"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint . --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
|
|||
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
|
||||
})
|
||||
|
|
@ -17,7 +17,7 @@ function compileSelector(selector) {
|
|||
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 || true
|
||||
else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
|
||||
}
|
||||
}
|
||||
if (classes.length > 0) attrs.className = classes.join(" ")
|
||||
|
|
@ -34,8 +34,8 @@ function execSelector(state, attrs, children) {
|
|||
}
|
||||
}
|
||||
|
||||
if (className != null) {
|
||||
if (attrs.class != null) {
|
||||
if (className !== undefined) {
|
||||
if (attrs.class !== undefined) {
|
||||
attrs.class = undefined
|
||||
attrs.className = className
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,18 @@ 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++) {
|
||||
|
|
@ -66,14 +75,11 @@ module.exports = function($window) {
|
|||
}
|
||||
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)
|
||||
|
|
@ -140,7 +146,7 @@ module.exports = function($window) {
|
|||
//update
|
||||
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) {
|
||||
|
|
@ -218,7 +224,7 @@ module.exports = function($window) {
|
|||
if (movable.dom != null) nextSibling = movable.dom
|
||||
}
|
||||
else {
|
||||
var dom = createNode(parent, v, hooks, undefined, nextSibling)
|
||||
var dom = createNode(parent, v, hooks, ns, nextSibling)
|
||||
nextSibling = dom
|
||||
}
|
||||
}
|
||||
|
|
@ -289,10 +295,8 @@ module.exports = function($window) {
|
|||
}
|
||||
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) {
|
||||
|
|
@ -476,12 +480,21 @@ 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)
|
||||
|
|
@ -600,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), false, 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,7 +8,7 @@
|
|||
<script src="../../ospec/ospec.js"></script>
|
||||
<script src="../../test-utils/callAsync.js"></script>
|
||||
<script src="../../test-utils/domMock.js"></script>
|
||||
<script src="../../test-utils/component.js"></script>
|
||||
<script src="../../test-utils/components.js"></script>
|
||||
|
||||
<script src="../../render/vnode.js"></script>
|
||||
<script src="../../render/trust.js"></script>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,46 @@ 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(){
|
||||
|
|
@ -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,7 +358,218 @@ 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() {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ o.spec("component", function() {
|
|||
render(root, [node])
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("receives arguments", function() {
|
||||
|
|
@ -44,7 +44,7 @@ o.spec("component", function() {
|
|||
render(root, [node])
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("updates", function() {
|
||||
|
|
@ -57,7 +57,7 @@ o.spec("component", function() {
|
|||
render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])
|
||||
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("c")
|
||||
o(root.firstChild.attributes["id"].value).equals("c")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("d")
|
||||
})
|
||||
o("updates root from null", function() {
|
||||
|
|
@ -400,7 +400,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("calls oninit when returning fragment", function() {
|
||||
|
|
@ -423,7 +423,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("calls oninit before view", function() {
|
||||
|
|
@ -479,7 +479,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("does not calls oncreate on redraw", function() {
|
||||
|
|
@ -520,7 +520,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("calls onupdate", function() {
|
||||
|
|
@ -546,7 +546,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("calls onupdate when returning fragment", function() {
|
||||
|
|
@ -572,7 +572,7 @@ o.spec("component", function() {
|
|||
|
||||
o(called).equals(1)
|
||||
o(root.firstChild.nodeName).equals("DIV")
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("a")
|
||||
o(root.firstChild.attributes["id"].value).equals("a")
|
||||
o(root.firstChild.firstChild.nodeValue).equals("b")
|
||||
})
|
||||
o("calls onremove", function() {
|
||||
|
|
|
|||
|
|
@ -24,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"}}}
|
||||
|
|
@ -48,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() {
|
||||
|
|
|
|||
|
|
@ -16,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")
|
||||
|
||||
|
|
@ -129,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() {
|
||||
|
|
|
|||
|
|
@ -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"}}
|
||||
|
||||
|
|
|
|||
|
|
@ -199,9 +199,8 @@ o.spec("onbeforeremove", function() {
|
|||
render(root, [{tag: component}])
|
||||
render(root, [])
|
||||
|
||||
o(onremove.callCount).equals(0)
|
||||
callAsync(function(){
|
||||
o(onremove.callCount).equals(0)
|
||||
|
||||
callAsync(function() {
|
||||
o(onremove.callCount).equals(1)
|
||||
done()
|
||||
|
|
|
|||
|
|
@ -21,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() {
|
||||
|
|
@ -65,7 +65,7 @@ o.spec("onbeforeupdate", function() {
|
|||
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() {
|
||||
|
|
@ -86,7 +86,7 @@ o.spec("onbeforeupdate", function() {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
|
@ -167,7 +167,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("does not prevent update if returning true in component and true in vnode", function() {
|
||||
|
|
@ -183,7 +183,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning false in component but true in vnode", function() {
|
||||
|
|
@ -199,7 +199,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true in component but false in vnode", function() {
|
||||
|
|
@ -215,7 +215,7 @@ o.spec("onbeforeupdate", function() {
|
|||
render(root, [vnode])
|
||||
render(root, [updated])
|
||||
|
||||
o(root.firstChild.attributes["id"].nodeValue).equals("b")
|
||||
o(root.firstChild.attributes["id"].value).equals("b")
|
||||
})
|
||||
|
||||
o("does not prevent update if returning true from component", function() {
|
||||
|
|
@ -231,7 +231,7 @@ o.spec("onbeforeupdate", function() {
|
|||
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 in component", function() {
|
||||
|
|
@ -258,7 +258,7 @@ o.spec("onbeforeupdate", function() {
|
|||
}
|
||||
|
||||
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 component creation", function() {
|
||||
|
|
|
|||
|
|
@ -170,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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -271,4 +271,32 @@ o.spec("render", function() {
|
|||
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,7 +209,7 @@ 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: [{
|
||||
|
|
|
|||
|
|
@ -93,7 +93,8 @@ module.exports = function($window, Promise) {
|
|||
}
|
||||
else {
|
||||
var error = new Error(xhr.responseText)
|
||||
for (var key in response) error[key] = response[key]
|
||||
error.code = xhr.status
|
||||
error.response = response
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,9 +458,10 @@ o.spec("xhr", function() {
|
|||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
o(e.message).equals(JSON.stringify({error: "error"}))
|
||||
o(e.code).equals(500)
|
||||
}).then(done)
|
||||
})
|
||||
o("extends Error with JSON response", function(done) {
|
||||
o("adds response to Error", function(done) {
|
||||
mock.$defineRoutes({
|
||||
"GET /item": function() {
|
||||
return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})}
|
||||
|
|
@ -468,8 +469,8 @@ o.spec("xhr", function() {
|
|||
})
|
||||
xhr({method: "GET", url: "/item"}).catch(function(e) {
|
||||
o(e instanceof Error).equals(true)
|
||||
o(e.message).equals("error")
|
||||
o(e.stack).equals("error on line 1")
|
||||
o(e.response.message).equals("error")
|
||||
o(e.response.stack).equals("error on line 1")
|
||||
}).then(done)
|
||||
})
|
||||
o("rejects on non-JSON server error", function(done) {
|
||||
|
|
|
|||
21
stream/LICENSE
Normal file
21
stream/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.
|
||||
6
stream/README.md
Normal file
6
stream/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
mithril-stream [](https://www.npmjs.com/package/mithril-stream) [](https://www.npmjs.com/package/mithril-stream)
|
||||
==============
|
||||
|
||||
Mithril's `m.stream` as a standalone module.
|
||||
|
||||
See [mithril.js.org/stream.html](https://mithril.js.org/stream.html) for docs/usage.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mithril-stream",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "Streaming data, mithril-style",
|
||||
"main": "stream.js",
|
||||
"directories": {
|
||||
|
|
@ -9,5 +9,5 @@
|
|||
"keywords": [ "stream", "reactive", "data" ],
|
||||
"author": "Leo Horie <lhorie@hotmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": "lhorie/mithril.js"
|
||||
"repository": "MithrilJS/mithril.js"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
/* eslint-disable */
|
||||
;(function() {
|
||||
"use strict"
|
||||
/* eslint-enable */
|
||||
|
||||
var guid = 0, HALT = {}
|
||||
function createStream() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,39 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = function() {
|
||||
/*
|
||||
Known limitations:
|
||||
|
||||
- `option.selected` can't be set/read when the option doesn't have a `select` parent
|
||||
- `element.attributes` is just a map of attribute names => Attr objects stubs
|
||||
- ...
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
options:
|
||||
- spy:(f: Function) => Function
|
||||
*/
|
||||
|
||||
module.exports = function(options) {
|
||||
options = options || {}
|
||||
var spy = options.spy || function(f){return f}
|
||||
var spymap = []
|
||||
function registerSpies(element, spies) {
|
||||
if(options.spy) {
|
||||
var i = spymap.indexOf(element)
|
||||
if (i === -1) {
|
||||
spymap.push(element, spies)
|
||||
} else {
|
||||
var existing = spymap[i + 1]
|
||||
for (var k in spies) existing[k] = spies[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
function getSpies(element) {
|
||||
if (element == null || typeof element !== "object") throw new Error("Element expected")
|
||||
if(options.spy) return spymap[spymap.indexOf(element) + 1]
|
||||
}
|
||||
|
||||
function isModernEvent(type) {
|
||||
return type === "transitionstart" || type === "transitionend" || type === "animationstart" || type === "animationend"
|
||||
}
|
||||
|
|
@ -62,14 +95,26 @@ module.exports = function() {
|
|||
}
|
||||
function getAttribute(name) {
|
||||
if (this.attributes[name] == null) return null
|
||||
return this.attributes[name].nodeValue
|
||||
return this.attributes[name].value
|
||||
}
|
||||
function setAttribute(name, value) {
|
||||
var nodeValue = String(value)
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
// this is the correct kind of conversion, passing a Symbol throws in browsers too.
|
||||
var nodeValue = "" + value
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
|
||||
this.attributes[name] = {
|
||||
namespaceURI: null,
|
||||
get value() {return nodeValue},
|
||||
set value(value) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
nodeValue = "" + value
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
},
|
||||
get nodeValue() {return nodeValue},
|
||||
set nodeValue(value) {nodeValue = String(value)},
|
||||
set nodeValue(value) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
function setAttributeNS(ns, name, value) {
|
||||
|
|
@ -79,6 +124,9 @@ module.exports = function() {
|
|||
function removeAttribute(name) {
|
||||
delete this.attributes[name]
|
||||
}
|
||||
function hasAttribute(name) {
|
||||
return name in this.attributes
|
||||
}
|
||||
var declListTokenizer = /;|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'/g
|
||||
/**
|
||||
* This will split a semicolon-separated CSS declaration list into an array of
|
||||
|
|
@ -150,6 +198,7 @@ module.exports = function() {
|
|||
appendChild: appendChild,
|
||||
removeChild: removeChild,
|
||||
insertBefore: insertBefore,
|
||||
hasAttribute: hasAttribute,
|
||||
getAttribute: getAttribute,
|
||||
setAttribute: setAttribute,
|
||||
setAttributeNS: setAttributeNS,
|
||||
|
|
@ -204,7 +253,7 @@ module.exports = function() {
|
|||
throw new Error("setting element.style is not portable")
|
||||
},
|
||||
get className() {
|
||||
return this.attributes["class"] ? this.attributes["class"].nodeValue : ""
|
||||
return this.attributes["class"] ? this.attributes["class"].value : ""
|
||||
},
|
||||
set className(value) {
|
||||
if (this.namespaceURI === "http://www.w3.org/2000/svg") throw new Error("Cannot set property className of SVGElement")
|
||||
|
|
@ -222,7 +271,7 @@ module.exports = function() {
|
|||
}
|
||||
},
|
||||
dispatchEvent: function(e) {
|
||||
if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].nodeValue === "checkbox" && e.type === "click") {
|
||||
if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") {
|
||||
this.checked = !this.checked
|
||||
}
|
||||
|
||||
|
|
@ -256,30 +305,73 @@ module.exports = function() {
|
|||
enumerable: true,
|
||||
})
|
||||
|
||||
element.value = ""
|
||||
}
|
||||
|
||||
if (element.nodeName === "TEXTAREA") {
|
||||
var value
|
||||
var value = ""
|
||||
var valueSetter = spy(function(v) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
value = v === null ? "" : "" + v
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
get: function() {
|
||||
return value != null ? value :
|
||||
this.firstChild ? this.firstChild.nodeValue : ""
|
||||
return value
|
||||
},
|
||||
set: function(v) {value = v},
|
||||
set: valueSetter,
|
||||
enumerable: true,
|
||||
})
|
||||
|
||||
// we currently emulate the non-ie behavior, but emulating ie may be more useful (throw when an invalid type is set)
|
||||
var typeSetter = spy(function(v) {
|
||||
this.setAttribute("type", v)
|
||||
})
|
||||
Object.defineProperty(element, "type", {
|
||||
get: function() {
|
||||
if (!this.hasAttribute("type")) return "text"
|
||||
var type = this.getAttribute("type")
|
||||
return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/)
|
||||
.test(type)
|
||||
? type
|
||||
: "text"
|
||||
},
|
||||
set: typeSetter,
|
||||
enumerable: true,
|
||||
})
|
||||
registerSpies(element, {
|
||||
valueSetter: valueSetter,
|
||||
typeSetter: typeSetter
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (element.nodeName === "TEXTAREA") {
|
||||
var wasNeverSet = true
|
||||
var value = ""
|
||||
var valueSetter = spy(function(v) {
|
||||
wasNeverSet = false
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
value = v === null ? "" : "" + v
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
get: function() {
|
||||
return wasNeverSet && this.firstChild ? this.firstChild.nodeValue : value
|
||||
},
|
||||
set: valueSetter,
|
||||
enumerable: true,
|
||||
})
|
||||
registerSpies(element, {
|
||||
valueSetter: valueSetter
|
||||
})
|
||||
}
|
||||
|
||||
/* eslint-disable radix */
|
||||
|
||||
if (element.nodeName === "CANVAS") {
|
||||
Object.defineProperty(element, "width", {
|
||||
get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].nodeValue) || 0) : 300},
|
||||
get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].value) || 0) : 300},
|
||||
set: function(value) {this.setAttribute("width", Math.floor(Number(value) || 0).toString())},
|
||||
})
|
||||
Object.defineProperty(element, "height", {
|
||||
get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].nodeValue) || 0) : 300},
|
||||
get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].value) || 0) : 300},
|
||||
set: function(value) {this.setAttribute("height", Math.floor(Number(value) || 0).toString())},
|
||||
})
|
||||
}
|
||||
|
|
@ -296,7 +388,7 @@ module.exports = function() {
|
|||
}
|
||||
function getOptionValue(element) {
|
||||
return element.attributes["value"] != null ?
|
||||
element.attributes["value"].nodeValue :
|
||||
element.attributes["value"].value :
|
||||
element.firstChild != null ? element.firstChild.nodeValue : ""
|
||||
}
|
||||
if (element.nodeName === "SELECT") {
|
||||
|
|
@ -317,14 +409,14 @@ module.exports = function() {
|
|||
},
|
||||
enumerable: true,
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
get: function() {
|
||||
if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex])
|
||||
return ""
|
||||
},
|
||||
set: function(value) {
|
||||
var valueSetter = spy(function(value) {
|
||||
if (value === null) {
|
||||
selectedIndex = -1
|
||||
} else {
|
||||
var options = getOptions(this)
|
||||
var stringValue = String(value)
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
var stringValue = "" + value
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
if (getOptionValue(options[i]) === stringValue) {
|
||||
// selectedValue = stringValue
|
||||
|
|
@ -334,19 +426,37 @@ module.exports = function() {
|
|||
}
|
||||
// selectedValue = stringValue
|
||||
selectedIndex = -1
|
||||
}
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
get: function() {
|
||||
if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex])
|
||||
return ""
|
||||
},
|
||||
set: valueSetter,
|
||||
enumerable: true,
|
||||
})
|
||||
registerSpies(element, {
|
||||
valueSetter: valueSetter
|
||||
})
|
||||
}
|
||||
if (element.nodeName === "OPTION") {
|
||||
var valueSetter = spy(function(value) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
this.setAttribute("value", value === null ? "" : "" + value)
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
})
|
||||
Object.defineProperty(element, "value", {
|
||||
get: function() {return getOptionValue(this)},
|
||||
set: function(value) {
|
||||
this.setAttribute("value", value)
|
||||
},
|
||||
set: valueSetter,
|
||||
enumerable: true,
|
||||
})
|
||||
registerSpies(element, {
|
||||
valueSetter: valueSetter
|
||||
})
|
||||
|
||||
Object.defineProperty(element, "selected", {
|
||||
// TODO? handle `selected` without a parent (works in browsers)
|
||||
get: function() {
|
||||
var options = getOptions(this.parentNode)
|
||||
var index = options.indexOf(this)
|
||||
|
|
@ -372,13 +482,19 @@ module.exports = function() {
|
|||
return element
|
||||
},
|
||||
createTextNode: function(text) {
|
||||
var nodeValue = String(text)
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
var nodeValue = "" + text
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
return {
|
||||
nodeType: 3,
|
||||
nodeName: "#text",
|
||||
parentNode: null,
|
||||
get nodeValue() {return nodeValue},
|
||||
set nodeValue(value) {nodeValue = String(value)},
|
||||
set nodeValue(value) {
|
||||
/*eslint-disable no-implicit-coercion*/
|
||||
nodeValue = "" + value
|
||||
/*eslint-enable no-implicit-coercion*/
|
||||
},
|
||||
}
|
||||
},
|
||||
createDocumentFragment: function() {
|
||||
|
|
@ -409,5 +525,7 @@ module.exports = function() {
|
|||
$window.document.documentElement.appendChild($window.document.body)
|
||||
activeElement = $window.document.body
|
||||
|
||||
if (options.spy) $window.__getSpies = getSpies
|
||||
|
||||
return $window
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict"
|
||||
|
||||
var parseURL = require("../test-utils/parseURL")
|
||||
var callAsync = require("../test-utils/callAsync.js")
|
||||
var callAsync = require("../test-utils/callAsync")
|
||||
|
||||
function debouncedAsync(f) {
|
||||
var ref
|
||||
|
|
|
|||
|
|
@ -77,6 +77,27 @@ o.spec("domMock", function() {
|
|||
|
||||
o(node.nodeValue).equals("true")
|
||||
})
|
||||
if (typeof Symbol === "function") {
|
||||
o("doesn't work with symbols", function(){
|
||||
var threw = false
|
||||
try {
|
||||
$document.createTextNode(Symbol("nono"))
|
||||
} catch(e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
o("symbols can't be used as nodeValue", function(){
|
||||
var threw = false
|
||||
try {
|
||||
var node = $document.createTextNode("a")
|
||||
node.nodeValue = Symbol("nono")
|
||||
} catch(e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
o.spec("createDocumentFragment", function() {
|
||||
|
|
@ -327,6 +348,7 @@ o.spec("domMock", function() {
|
|||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", "aaa")
|
||||
|
||||
o(div.attributes["id"].value).equals("aaa")
|
||||
o(div.attributes["id"].nodeValue).equals("aaa")
|
||||
o(div.attributes["id"].namespaceURI).equals(null)
|
||||
})
|
||||
|
|
@ -334,32 +356,51 @@ o.spec("domMock", function() {
|
|||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", 123)
|
||||
|
||||
o(div.attributes["id"].nodeValue).equals("123")
|
||||
o(div.attributes["id"].value).equals("123")
|
||||
})
|
||||
o("works w/ null", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", null)
|
||||
|
||||
o(div.attributes["id"].nodeValue).equals("null")
|
||||
o(div.attributes["id"].value).equals("null")
|
||||
})
|
||||
o("works w/ undefined", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", undefined)
|
||||
|
||||
o(div.attributes["id"].nodeValue).equals("undefined")
|
||||
o(div.attributes["id"].value).equals("undefined")
|
||||
})
|
||||
o("works w/ object", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", {})
|
||||
|
||||
o(div.attributes["id"].nodeValue).equals("[object Object]")
|
||||
o(div.attributes["id"].value).equals("[object Object]")
|
||||
})
|
||||
o("setting via attributes map stringifies", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttribute("id", "a")
|
||||
div.attributes["id"].nodeValue = 123
|
||||
div.attributes["id"].value = 123
|
||||
|
||||
o(div.attributes["id"].nodeValue).equals("123")
|
||||
o(div.attributes["id"].value).equals("123")
|
||||
|
||||
div.attributes["id"].nodeValue = 456
|
||||
|
||||
o(div.attributes["id"].value).equals("456")
|
||||
})
|
||||
})
|
||||
o.spec("hasAttribute", function() {
|
||||
o("works", function() {
|
||||
var div = $document.createElement("div")
|
||||
|
||||
o(div.hasAttribute("id")).equals(false)
|
||||
|
||||
div.setAttribute("id", "aaa")
|
||||
|
||||
o(div.hasAttribute("id")).equals(true)
|
||||
|
||||
div.removeAttribute("id")
|
||||
|
||||
o(div.hasAttribute("id")).equals(false)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -368,14 +409,14 @@ o.spec("domMock", function() {
|
|||
var div = $document.createElement("div")
|
||||
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa")
|
||||
|
||||
o(div.attributes["href"].nodeValue).equals("aaa")
|
||||
o(div.attributes["href"].value).equals("aaa")
|
||||
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
})
|
||||
o("works w/ number", function() {
|
||||
var div = $document.createElement("div")
|
||||
div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123)
|
||||
|
||||
o(div.attributes["href"].nodeValue).equals("123")
|
||||
o(div.attributes["href"].value).equals("123")
|
||||
o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink")
|
||||
})
|
||||
})
|
||||
|
|
@ -416,18 +457,18 @@ o.spec("domMock", function() {
|
|||
o(div.childNodes[0].nodeName).equals("BR")
|
||||
o(div.childNodes[1].nodeType).equals(1)
|
||||
o(div.childNodes[1].nodeName).equals("A")
|
||||
o(div.childNodes[1].attributes["class"].nodeValue).equals("aaa")
|
||||
o(div.childNodes[1].attributes["id"].nodeValue).equals("xyz")
|
||||
o(div.childNodes[1].attributes["class"].value).equals("aaa")
|
||||
o(div.childNodes[1].attributes["id"].value).equals("xyz")
|
||||
o(div.childNodes[1].childNodes[0].nodeType).equals(3)
|
||||
o(div.childNodes[1].childNodes[0].nodeValue).equals("123")
|
||||
o(div.childNodes[1].childNodes[1].nodeType).equals(1)
|
||||
o(div.childNodes[1].childNodes[1].nodeName).equals("B")
|
||||
o(div.childNodes[1].childNodes[1].attributes["class"].nodeValue).equals("bbb")
|
||||
o(div.childNodes[1].childNodes[1].attributes["class"].value).equals("bbb")
|
||||
o(div.childNodes[1].childNodes[2].nodeType).equals(3)
|
||||
o(div.childNodes[1].childNodes[2].nodeValue).equals("234")
|
||||
o(div.childNodes[1].childNodes[3].nodeType).equals(1)
|
||||
o(div.childNodes[1].childNodes[3].nodeName).equals("BR")
|
||||
o(div.childNodes[1].childNodes[3].attributes["class"].nodeValue).equals("ccc")
|
||||
o(div.childNodes[1].childNodes[3].attributes["class"].value).equals("ccc")
|
||||
o(div.childNodes[1].childNodes[4].nodeType).equals(3)
|
||||
o(div.childNodes[1].childNodes[4].nodeValue).equals("345")
|
||||
})
|
||||
|
|
@ -628,14 +669,14 @@ o.spec("domMock", function() {
|
|||
a.setAttribute("href", "")
|
||||
|
||||
o(a.href).notEquals("")
|
||||
o(a.attributes["href"].nodeValue).equals("")
|
||||
o(a.attributes["href"].value).equals("")
|
||||
})
|
||||
o("is path if property is set", function() {
|
||||
var a = $document.createElement("a")
|
||||
a.href = ""
|
||||
|
||||
o(a.href).notEquals("")
|
||||
o(a.attributes["href"].nodeValue).equals("")
|
||||
o(a.attributes["href"].value).equals("")
|
||||
})
|
||||
})
|
||||
o.spec("input[checked]", function() {
|
||||
|
|
@ -656,7 +697,7 @@ o.spec("domMock", function() {
|
|||
input.setAttribute("checked", "")
|
||||
|
||||
o(input.checked).equals(true)
|
||||
o(input.attributes["checked"].nodeValue).equals("")
|
||||
o(input.attributes["checked"].value).equals("")
|
||||
|
||||
input.removeAttribute("checked")
|
||||
|
||||
|
|
@ -699,20 +740,95 @@ o.spec("domMock", function() {
|
|||
o("value" in input).equals(true)
|
||||
o("value" in a).equals(false)
|
||||
})
|
||||
o("converts null to ''", function() {
|
||||
var input = $document.createElement("input")
|
||||
input.value = "x"
|
||||
|
||||
o(input.value).equals("x")
|
||||
|
||||
input.value = null
|
||||
|
||||
o(input.value).equals("")
|
||||
})
|
||||
o("converts values to strings", function() {
|
||||
var input = $document.createElement("input")
|
||||
input.value = 5
|
||||
|
||||
o(input.value).equals("5")
|
||||
|
||||
input.value = 0
|
||||
|
||||
o(input.value).equals("0")
|
||||
|
||||
input.value = undefined
|
||||
|
||||
o(input.value).equals("undefined")
|
||||
})
|
||||
if (typeof Symbol === "function") o("throws when set to a symbol", function() {
|
||||
var threw = false
|
||||
var input = $document.createElement("input")
|
||||
try {
|
||||
input.value = Symbol("")
|
||||
} catch (e) {
|
||||
o(e instanceof TypeError).equals(true)
|
||||
threw = true
|
||||
}
|
||||
|
||||
o(input.value).equals("")
|
||||
o(threw).equals(true)
|
||||
})
|
||||
})
|
||||
o.spec("input[type]", function(){
|
||||
o("only exists in input elements", function() {
|
||||
var input = $document.createElement("input")
|
||||
var a = $document.createElement("a")
|
||||
|
||||
o("type" in input).equals(true)
|
||||
o("type" in a).equals(false)
|
||||
})
|
||||
o("is 'text' by default", function() {
|
||||
var input = $document.createElement("input")
|
||||
|
||||
o(input.type).equals("text")
|
||||
})
|
||||
"radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image"
|
||||
.split("|").forEach(function(type) {
|
||||
o("can be set to " + type, function(){
|
||||
var input = $document.createElement("input")
|
||||
input.type = type
|
||||
|
||||
o(input.getAttribute("type")).equals(type)
|
||||
o(input.type).equals(type)
|
||||
})
|
||||
o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){
|
||||
var input = $document.createElement("input")
|
||||
input.type = "badbad" + type
|
||||
|
||||
o(input.getAttribute("type")).equals("badbad" + type)
|
||||
o(input.type).equals("text")
|
||||
})
|
||||
})
|
||||
})
|
||||
o.spec("textarea[value]", function() {
|
||||
o("reads from child if no value", function() {
|
||||
var input = $document.createElement("textarea")
|
||||
input.appendChild($document.createTextNode("aaa"))
|
||||
o("reads from child if no value was ever set", function() {
|
||||
var textarea = $document.createElement("textarea")
|
||||
textarea.appendChild($document.createTextNode("aaa"))
|
||||
|
||||
o(input.value).equals("aaa")
|
||||
o(textarea.value).equals("aaa")
|
||||
})
|
||||
o("ignores child if value set", function() {
|
||||
var input = $document.createElement("textarea")
|
||||
input.value = "aaa"
|
||||
input.setAttribute("value", "bbb")
|
||||
var textarea = $document.createElement("textarea")
|
||||
textarea.value = null
|
||||
textarea.appendChild($document.createTextNode("aaa"))
|
||||
|
||||
o(input.value).equals("aaa")
|
||||
o(textarea.value).equals("")
|
||||
})
|
||||
o("textarea[value] doesn't reflect `attributes.value`", function() {
|
||||
var textarea = $document.createElement("textarea")
|
||||
textarea.value = "aaa"
|
||||
textarea.setAttribute("value", "bbb")
|
||||
|
||||
o(textarea.value).equals("aaa")
|
||||
})
|
||||
})
|
||||
o.spec("select[value] and select[selectedIndex]", function() {
|
||||
|
|
@ -773,10 +889,76 @@ o.spec("domMock", function() {
|
|||
option2.setAttribute("value", "b")
|
||||
select.appendChild(option2)
|
||||
|
||||
var option3 = $document.createElement("option")
|
||||
option3.setAttribute("value", "")
|
||||
select.appendChild(option3)
|
||||
|
||||
var option4 = $document.createElement("option")
|
||||
option4.setAttribute("value", "null")
|
||||
select.appendChild(option4)
|
||||
|
||||
select.value = "b"
|
||||
|
||||
o(select.value).equals("b")
|
||||
o(select.selectedIndex).equals(1)
|
||||
|
||||
select.value = ""
|
||||
|
||||
o(select.value).equals("")
|
||||
o(select.selectedIndex).equals(2)
|
||||
|
||||
select.value = "null"
|
||||
|
||||
o(select.value).equals("null")
|
||||
o(select.selectedIndex).equals(3)
|
||||
|
||||
select.value = null
|
||||
|
||||
o(select.value).equals("")
|
||||
o(select.selectedIndex).equals(-1)
|
||||
})
|
||||
o("setting valid value works with type conversion", function() {
|
||||
var select = $document.createElement("select")
|
||||
|
||||
var option1 = $document.createElement("option")
|
||||
option1.setAttribute("value", "0")
|
||||
select.appendChild(option1)
|
||||
|
||||
var option2 = $document.createElement("option")
|
||||
option2.setAttribute("value", "undefined")
|
||||
select.appendChild(option2)
|
||||
|
||||
var option3 = $document.createElement("option")
|
||||
option3.setAttribute("value", "")
|
||||
select.appendChild(option3)
|
||||
|
||||
select.value = 0
|
||||
|
||||
o(select.value).equals("0")
|
||||
o(select.selectedIndex).equals(0)
|
||||
|
||||
select.value = undefined
|
||||
|
||||
o(select.value).equals("undefined")
|
||||
o(select.selectedIndex).equals(1)
|
||||
|
||||
if (typeof Symbol === "function") {
|
||||
var threw = false
|
||||
try {
|
||||
select.value = Symbol("x")
|
||||
} catch (e) {
|
||||
threw = true
|
||||
}
|
||||
o(threw).equals(true)
|
||||
o(select.value).equals("undefined")
|
||||
o(select.selectedIndex).equals(1)
|
||||
}
|
||||
})
|
||||
o("option.value = null is converted to the empty string", function() {
|
||||
var option = $document.createElement("option")
|
||||
option.value = null
|
||||
|
||||
o(option.value).equals("")
|
||||
})
|
||||
o("setting valid value works with optgroup", function() {
|
||||
var select = $document.createElement("select")
|
||||
|
|
@ -920,55 +1102,55 @@ o.spec("domMock", function() {
|
|||
var canvas = $document.createElement("canvas")
|
||||
|
||||
canvas.width = 100
|
||||
o(canvas.attributes["width"].nodeValue).equals("100")
|
||||
o(canvas.attributes["width"].value).equals("100")
|
||||
o(canvas.width).equals(100)
|
||||
|
||||
canvas.height = 100
|
||||
o(canvas.attributes["height"].nodeValue).equals("100")
|
||||
o(canvas.attributes["height"].value).equals("100")
|
||||
o(canvas.height).equals(100)
|
||||
})
|
||||
o("setting string casts to number", function() {
|
||||
var canvas = $document.createElement("canvas")
|
||||
|
||||
canvas.width = "100"
|
||||
o(canvas.attributes["width"].nodeValue).equals("100")
|
||||
o(canvas.attributes["width"].value).equals("100")
|
||||
o(canvas.width).equals(100)
|
||||
|
||||
canvas.height = "100"
|
||||
o(canvas.attributes["height"].nodeValue).equals("100")
|
||||
o(canvas.attributes["height"].value).equals("100")
|
||||
o(canvas.height).equals(100)
|
||||
})
|
||||
o("setting float casts to int", function() {
|
||||
var canvas = $document.createElement("canvas")
|
||||
|
||||
canvas.width = 1.2
|
||||
o(canvas.attributes["width"].nodeValue).equals("1")
|
||||
o(canvas.attributes["width"].value).equals("1")
|
||||
o(canvas.width).equals(1)
|
||||
|
||||
canvas.height = 1.2
|
||||
o(canvas.attributes["height"].nodeValue).equals("1")
|
||||
o(canvas.attributes["height"].value).equals("1")
|
||||
o(canvas.height).equals(1)
|
||||
})
|
||||
o("setting percentage fails", function() {
|
||||
var canvas = $document.createElement("canvas")
|
||||
|
||||
canvas.width = "100%"
|
||||
o(canvas.attributes["width"].nodeValue).equals("0")
|
||||
o(canvas.attributes["width"].value).equals("0")
|
||||
o(canvas.width).equals(0)
|
||||
|
||||
canvas.height = "100%"
|
||||
o(canvas.attributes["height"].nodeValue).equals("0")
|
||||
o(canvas.attributes["height"].value).equals("0")
|
||||
o(canvas.height).equals(0)
|
||||
})
|
||||
o("setting attribute works", function() {
|
||||
var canvas = $document.createElement("canvas")
|
||||
|
||||
canvas.setAttribute("width", "100%")
|
||||
o(canvas.attributes["width"].nodeValue).equals("100%")
|
||||
o(canvas.attributes["width"].value).equals("100%")
|
||||
o(canvas.width).equals(100)
|
||||
|
||||
canvas.setAttribute("height", "100%")
|
||||
o(canvas.attributes["height"].nodeValue).equals("100%")
|
||||
o(canvas.attributes["height"].value).equals("100%")
|
||||
o(canvas.height).equals(100)
|
||||
})
|
||||
})
|
||||
|
|
@ -979,7 +1161,7 @@ o.spec("domMock", function() {
|
|||
el.className = "a"
|
||||
|
||||
o(el.className).equals("a")
|
||||
o(el.attributes["class"].nodeValue).equals("a")
|
||||
o(el.attributes["class"].value).equals("a")
|
||||
})
|
||||
o("setter throws in svg", function(done) {
|
||||
var el = $document.createElementNS("http://www.w3.org/2000/svg", "svg")
|
||||
|
|
@ -991,4 +1173,85 @@ o.spec("domMock", function() {
|
|||
}
|
||||
})
|
||||
})
|
||||
o.spec("spies", function() {
|
||||
var $window
|
||||
o.beforeEach(function() {
|
||||
$window = domMock({spy: o.spy})
|
||||
})
|
||||
o("basics", function() {
|
||||
o(typeof $window.__getSpies).equals("function")
|
||||
o("__getSpies" in domMock()).equals(false)
|
||||
})
|
||||
o("input elements have spies on value and type setters", function() {
|
||||
var input = $window.document.createElement("input")
|
||||
|
||||
var spies = $window.__getSpies(input)
|
||||
|
||||
o(typeof spies).equals("object")
|
||||
o(spies).notEquals(null)
|
||||
o(typeof spies.valueSetter).equals("function")
|
||||
o(typeof spies.typeSetter).equals("function")
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
o(spies.typeSetter.callCount).equals(0)
|
||||
|
||||
input.value = "aaa"
|
||||
input.type = "radio"
|
||||
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
o(spies.valueSetter.this).equals(input)
|
||||
o(spies.valueSetter.args[0]).equals("aaa")
|
||||
|
||||
o(spies.typeSetter.callCount).equals(1)
|
||||
o(spies.typeSetter.this).equals(input)
|
||||
o(spies.typeSetter.args[0]).equals("radio")
|
||||
})
|
||||
o("select elements have spies on value setters", function() {
|
||||
var select = $window.document.createElement("select")
|
||||
|
||||
var spies = $window.__getSpies(select)
|
||||
|
||||
o(typeof spies).equals("object")
|
||||
o(spies).notEquals(null)
|
||||
o(typeof spies.valueSetter).equals("function")
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
select.value = "aaa"
|
||||
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
o(spies.valueSetter.this).equals(select)
|
||||
o(spies.valueSetter.args[0]).equals("aaa")
|
||||
})
|
||||
o("option elements have spies on value setters", function() {
|
||||
var option = $window.document.createElement("option")
|
||||
|
||||
var spies = $window.__getSpies(option)
|
||||
|
||||
o(typeof spies).equals("object")
|
||||
o(spies).notEquals(null)
|
||||
o(typeof spies.valueSetter).equals("function")
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
option.value = "aaa"
|
||||
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
o(spies.valueSetter.this).equals(option)
|
||||
o(spies.valueSetter.args[0]).equals("aaa")
|
||||
})
|
||||
o("textarea elements have spies on value setters", function() {
|
||||
var textarea = $window.document.createElement("textarea")
|
||||
|
||||
var spies = $window.__getSpies(textarea)
|
||||
|
||||
o(typeof spies).equals("object")
|
||||
o(spies).notEquals(null)
|
||||
o(typeof spies.valueSetter).equals("function")
|
||||
o(spies.valueSetter.callCount).equals(0)
|
||||
|
||||
textarea.value = "aaa"
|
||||
|
||||
o(spies.valueSetter.callCount).equals(1)
|
||||
o(spies.valueSetter.this).equals(textarea)
|
||||
o(spies.valueSetter.args[0]).equals("aaa")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
91
test-utils/tests/test-throttleMock.js
Normal file
91
test-utils/tests/test-throttleMock.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"use strict"
|
||||
|
||||
var o = require("../../ospec/ospec")
|
||||
var throttleMocker = require("../../test-utils/throttleMock")
|
||||
|
||||
o.spec("throttleMock", function() {
|
||||
o("works with one callback", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
var spy = o.spy()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
|
||||
var throttled = throttleMock.throttle(spy)
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttled()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttled()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy.callCount).equals(1)
|
||||
})
|
||||
o("works with two callbacks", function() {
|
||||
var throttleMock = throttleMocker()
|
||||
var spy1 = o.spy()
|
||||
var spy2 = o.spy()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
|
||||
var throttled1 = throttleMock.throttle(spy1)
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled1()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled1()
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
var throttled2 = throttleMock.throttle(spy2)
|
||||
|
||||
o(throttleMock.queueLength()).equals(1)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled2()
|
||||
|
||||
o(throttleMock.queueLength()).equals(2)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttled2()
|
||||
|
||||
o(throttleMock.queueLength()).equals(2)
|
||||
o(spy1.callCount).equals(0)
|
||||
o(spy2.callCount).equals(0)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(throttleMock.queueLength()).equals(0)
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
|
||||
throttleMock.fire()
|
||||
|
||||
o(spy1.callCount).equals(1)
|
||||
o(spy2.callCount).equals(1)
|
||||
})
|
||||
})
|
||||
27
test-utils/throttleMock.js
Normal file
27
test-utils/throttleMock.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = function() {
|
||||
var queue = []
|
||||
return {
|
||||
throttle: function(fn) {
|
||||
var pending = false
|
||||
return function() {
|
||||
if (!pending) {
|
||||
queue.push(function(){
|
||||
pending = false
|
||||
fn()
|
||||
})
|
||||
pending = true
|
||||
}
|
||||
}
|
||||
},
|
||||
fire: function() {
|
||||
var tasks = queue
|
||||
queue = []
|
||||
tasks.forEach(function(fn) {fn()})
|
||||
},
|
||||
queueLength: function(){
|
||||
return queue.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,14 +163,24 @@ o.spec("api", function() {
|
|||
var count = 0
|
||||
var root = window.document.createElement("div")
|
||||
m.mount(root, createComponent({view: function() {count++}}))
|
||||
o(count).equals(1)
|
||||
m.redraw()
|
||||
o(count).equals(1)
|
||||
setTimeout(function() {
|
||||
m.redraw()
|
||||
|
||||
o(count).equals(2)
|
||||
|
||||
done()
|
||||
}, FRAME_BUDGET)
|
||||
})
|
||||
o("sync", function() {
|
||||
var root = window.document.createElement("div")
|
||||
var view = o.spy()
|
||||
m.mount(root, createComponent({view: view}))
|
||||
o(view.callCount).equals(1)
|
||||
m.redraw.sync()
|
||||
o(view.callCount).equals(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue