Update migration, fix various minor issues
- Lot of people couldn't migrate to v1 and plan to reevaluate when v2 is released. - It's "npm" not "NPM". It doesn't stand for anything, and it never has - it was initially chosen simply because it was easy to type. It has a lot of unofficial backronyms with "Node Package Manager" being one of the most common ones, but it's never officially stood for anything as an acronym *or* initialism. - Fixed a few errors in the change log, like non-breaking changes being included in the "Breaking Changes" section and an inaccuracy in the summary of a particular change. - Fixed RawGit URLs to point to GitHack, which is a lighter proxy that offloads caching to Cloudflare instead of also implementing it itself. (It also just uses nginx for all the important server logic, so it scales better.) - Add a few more v0.2 references as appropriate
This commit is contained in:
parent
8186818e10
commit
234b1c9302
26 changed files with 1389 additions and 935 deletions
18
README.md
18
README.md
|
|
@ -1,4 +1,4 @@
|
|||
mithril.js [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://opencollective.com/mithriljs)
|
||||
mithril.js [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://www.npmjs.com/package/mithril) [](https://opencollective.com/mithriljs)
|
||||
==========
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -29,17 +29,19 @@ Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge,
|
|||
### CDN
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/mithril@next/mithril.js"></script>
|
||||
<!-- or -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/mithril@next/mithril.js"></script>
|
||||
<!-- Development: whichever you prefer -->
|
||||
<script src="https://unpkg.com/mithril/mithril.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.js"></script>
|
||||
|
||||
<!-- Production: whichever you prefer -->
|
||||
<script src="https://unpkg.com/mithril/mithril.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.min.js"></script>
|
||||
```
|
||||
|
||||
### npm
|
||||
|
||||
```bash
|
||||
# For the most recent stable version
|
||||
$ npm install mithril --save
|
||||
# For the most recent unstable version
|
||||
$ npm install mithril@next --save
|
||||
npm install mithril --save
|
||||
```
|
||||
|
||||
The ["Getting started" guide](https://mithril.js.org/#getting-started) is a good place to start learning how to use mithril.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
### Technology choices
|
||||
|
||||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge.
|
||||
Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](https://velocityjs.org/) that provide fast JavaScript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge.
|
||||
|
||||
Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work.
|
||||
|
||||
|
|
@ -102,4 +102,4 @@ Note that the `onbeforeremove` hook only fires on the element that loses its `pa
|
|||
|
||||
When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`.
|
||||
|
||||
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](http://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute positioned element over a fixed element).
|
||||
It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](https://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute positioned element over a fixed element).
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ var Home = {
|
|||
}
|
||||
|
||||
m.route(document.body, "/home", {
|
||||
"/home": Home, // defines `http://localhost/#!/home`
|
||||
"/home": Home, // defines `https://example.com/#!/home`
|
||||
})
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
# Change log
|
||||
|
||||
- [v2.0.0-rc](#v200rc)
|
||||
- [v1.1.6](#v116)
|
||||
- [v1.1.5](#v115)
|
||||
- [v1.1.4](#v114)
|
||||
- [v1.1.3](#v113)
|
||||
- [v1.1.2](#v112)
|
||||
- [v1.1.1](#v111)
|
||||
- [v1.1.0](#v110)
|
||||
- [v1.0.1](#v101)
|
||||
- [Migrating from v0.2.x](#migrating-from-v02x)
|
||||
- [v1.x docs](http://mithril.js.org/archive/v1.1.6/index.html)
|
||||
- [v0.2 docs](http://mithril.js.org/archive/v0.2.5/index.html)
|
||||
- [v2.0.0](#v200)
|
||||
- [Migrating from v1.x](migration-v1x.md)
|
||||
- [Migrating from v0.2.x](migration-v02x.md)
|
||||
- [v1.x changelog](https://mithril.js.org/archive/v1.1.6/change-log.html)
|
||||
- [v1.x docs](https://mithril.js.org/archive/v1.1.6/index.html)
|
||||
- [v0.2 docs](https://mithril.js.org/archive/v0.2.5/index.html)
|
||||
- [`ospec` change log](https://github.com/MithrilJS/mithril.js/blob/master/ospec/change-log.md)
|
||||
- [`mithril/stream` change log](https://github.com/MithrilJS/mithril.js/blob/master/stream/change-log.md)
|
||||
|
||||
|
|
@ -19,7 +13,7 @@
|
|||
|
||||
### Upcoming...
|
||||
|
||||
### v2.0.0-rc
|
||||
### v2.0.0
|
||||
|
||||
#### Breaking changes
|
||||
|
||||
|
|
@ -31,9 +25,8 @@
|
|||
- hyperscript: when attributes have a `null` or `undefined` value, they are treated as if they were absent. [#1773](https://github.com/MithrilJS/mithril.js/issues/1773) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174))
|
||||
- API: `m.request` errors no longer copy response fields to the error, but instead assign the parsed JSON response to `error.response` and the HTTP status code `error.code`.
|
||||
- hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174))
|
||||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221))
|
||||
- cast className using toString ([#2309](https://github.com/MithrilJS/mithril.js/pull/2309))
|
||||
- render: call attrs' hooks first, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297))
|
||||
- render: call attrs' hooks last, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297))
|
||||
- API: `m.withAttr` removed. ([#2317](https://github.com/MithrilJS/mithril.js/pull/2317))
|
||||
- request: `data` has now been split to `params` and `body` and `useBody` has been removed in favor of just using `body`. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361))
|
||||
- route, request: Interpolated arguments are URL-escaped (and for declared routes, URL-unescaped) automatically. If you want to use a raw route parameter, use a variadic parameter like in `/asset/:path.../view`. This was previously only available in `m.route` route definitions, but it's now usable in both that and where paths are accepted. ([#2361](https://github.com/MithrilJS/mithril.js/pull/2361))
|
||||
|
|
@ -69,11 +62,6 @@
|
|||
- The new component handles many more edge cases around user interaction, including accessibility.
|
||||
- Link navigation can be disabled and cancelled.
|
||||
- Link targets can be trivially changed.
|
||||
- API: Full DOM no longer required to execute `require("mithril")`. You just need to set the necessary globals to *something*, even if `null` or `undefined`, so they can be properly used. ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- This enables isomorphic use of `m.route.Link` and `m.route.prefix`.
|
||||
- This enables isomorphic use of `m.request`, provided the `background: true` option is set and that an `XMLHttpRequest` polyfill is included as necessary.
|
||||
- Note that methods requiring DOM operations will still throw errors, such as `m.render(...)`, `m.redraw()`, and `m.route(...)`.
|
||||
|
||||
|
||||
#### News
|
||||
|
||||
|
|
@ -100,6 +88,11 @@
|
|||
- route: Use `m.mount(root, null)` to unsubscribe and clean up after a `m.route(root, ...)` call. ([#2453](https://github.com/MithrilJS/mithril.js/pull/2453))
|
||||
- render: new `redraw` parameter exposed any time a child event handler is used ([#2458](https://github.com/MithrilJS/mithril.js/pull/2458) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- route: `m.route.SKIP` can be returned from route resolvers to skip to the next route ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- API: Full DOM no longer required to execute `require("mithril")`. You just need to set the necessary globals to *something*, even if `null` or `undefined`, so they can be properly used. ([#2469](https://github.com/MithrilJS/mithril.js/pull/2469) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- This enables isomorphic use of `m.route.Link` and `m.route.prefix`.
|
||||
- This enables isomorphic use of `m.request`, provided the `background: true` option is set and that an `XMLHttpRequest` polyfill is included as necessary.
|
||||
- Note that methods requiring DOM operations will still throw errors, such as `m.render(...)`, `m.redraw()`, and `m.route(...)`.
|
||||
- render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221))
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
|
|
@ -135,826 +128,3 @@
|
|||
- docs: clarify valid key usage ([#2452](https://github.com/MithrilJS/mithril.js/pull/2452) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- route: don't pollute globals ([#2453](https://github.com/MithrilJS/mithril.js/pull/2453) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
- request: track xhr replacements correctly ([#2455](https://github.com/MithrilJS/mithril.js/pull/2455) [@isiahmeadows](https://github.com/isiahmeadows))
|
||||
|
||||
---
|
||||
|
||||
|
||||
### v1.2.0
|
||||
|
||||
#### News
|
||||
|
||||
- Promise polyfill implementation separated from polyfilling logic.
|
||||
- `PromisePolyfill` is now available on the exported/global `m`.
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- core: Workaround for [Internet Explorer bug](https://www.tjvantoll.com/2013/08/30/bugs-with-document-activeelement-in-internet-explorer/) when running in an iframe
|
||||
|
||||
#### Note
|
||||
|
||||
- Stream references no longer magically coerce to their underlying values ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150), stream breaking change: `mithril-stream@2.0.0`)
|
||||
|
||||
---
|
||||
|
||||
### v1.1.6
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- core: render() function can no longer prevent from changing `document.activeElement` in lifecycle hooks ([#1988](https://github.com/MithrilJS/mithril.js/pull/1988), [@purplecode](https://github.com/purplecode))
|
||||
- core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [@octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922))
|
||||
- hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [@s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942))
|
||||
|
||||
#### Ospec improvements
|
||||
|
||||
- ospec v1.4.0
|
||||
- Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928))
|
||||
- Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@RodericDay](https://github.com/RodericDay))
|
||||
- ospec v2.0.0 (to be released)
|
||||
- Added support for custom reporters ([#2009](https://github.com/MithrilJS/mithril.js/pull/2020))
|
||||
- Make Ospec more [Flems](https://flems.io)-friendly ([#2034](https://github.com/MithrilJS/mithril.js/pull/2034))
|
||||
- Works either as a global or in CommonJS environments
|
||||
- the o.run() report is always printed asynchronously (it could be synchronous before if none of the tests were async).
|
||||
- Properly point to the assertion location of async errors [#2036](https://github.com/MithrilJS/mithril.js/issues/2036)
|
||||
- expose the default reporter as `o.report(results)`
|
||||
- Don't try to access the stack traces in IE9
|
||||
|
||||
---
|
||||
|
||||
### v1.1.5
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value [#1919](https://github.com/MithrilJS/mithril.js/issues/1919) ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924), [@tskillian](https://github.com/tskillian))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.4
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN))
|
||||
|
||||
#### Ospec improvements:
|
||||
|
||||
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager))
|
||||
|
||||
---
|
||||
|
||||
### 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))
|
||||
|
||||
---
|
||||
|
||||
#### 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/MithrilJS/mithril.js/issues/1752) / [#1753](https://github.com/MithrilJS/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/MithrilJS/mithril.js/issues/1764) / [#1769](https://github.com/MithrilJS/mithril.js/pull/1769)
|
||||
- documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake))
|
||||
|
||||
---
|
||||
|
||||
### v1.1.0
|
||||
|
||||
#### News
|
||||
|
||||
- support for ES6 class components
|
||||
- support for closure components
|
||||
- improvements in build and release automation
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- fix IE11 input[type] error - [#1610](https://github.com/MithrilJS/mithril.js/issues/1610)
|
||||
- apply [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) to unkeyed children case
|
||||
- fix abort detection [#1612](https://github.com/MithrilJS/mithril.js/issues/1612)
|
||||
- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/MithrilJS/mithril.js/issues/1593)
|
||||
|
||||
---
|
||||
|
||||
### v1.0.1
|
||||
|
||||
#### News
|
||||
|
||||
- performance improvements in IE [#1598](https://github.com/MithrilJS/mithril.js/pull/1598)
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
- prevent infinite loop in non-existent default route - [#1579](https://github.com/MithrilJS/mithril.js/issues/1579)
|
||||
- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/MithrilJS/mithril.js/issues/1609)
|
||||
|
||||
---
|
||||
|
||||
### Migrating from `v0.2.x`
|
||||
|
||||
`v1.x` is largely API-compatible with `v0.2.x`, but there are some breaking changes.
|
||||
|
||||
If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations.
|
||||
|
||||
- [`m.prop` removed](#mprop-removed)
|
||||
- [`m.component` removed](#mcomponent-removed)
|
||||
- [`config` function](#config-function)
|
||||
- [Changes in redraw behaviour](#changes-in-redraw-behaviour)
|
||||
- [No more redraw locks](#no-more-redraw-locks)
|
||||
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers)
|
||||
- [Synchronous redraw removed](#synchronous-redraw-removed)
|
||||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
|
||||
- [Component `controller` function](#component-controller-function)
|
||||
- [Component arguments](#component-arguments)
|
||||
- [`view()` parameters](#view-parameters)
|
||||
- [Passing components to `m()`](#passing-components-to-m)
|
||||
- [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute)
|
||||
- [`m.route.mode`](#mroutemode)
|
||||
- [`m.route` and anchor tags](#mroute-and-anchor-tags)
|
||||
- [Reading/writing the current route](#readingwriting-the-current-route)
|
||||
- [Accessing route params](#accessing-route-params)
|
||||
- [Building/Parsing query strings](#buildingparsing-query-strings)
|
||||
- [Preventing unmounting](#preventing-unmounting)
|
||||
- [Run code on component removal](#run-code-on-component-removal)
|
||||
- [`m.request`](#mrequest)
|
||||
- [`m.deferred` removed](#mdeferred-removed)
|
||||
- [`m.sync` removed](#msync-removed)
|
||||
- [`xlink` namespace required](#xlink-namespace-required)
|
||||
- [Nested arrays in views](#nested-arrays-in-views)
|
||||
- [`vnode` equality checks](#vnode-equality-checks)
|
||||
|
||||
---
|
||||
|
||||
## `m.prop` removed
|
||||
|
||||
In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md).
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
|
||||
var num = m.prop(1)
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
var prop = require("mithril/stream")
|
||||
|
||||
var num = prop(1)
|
||||
var doubled = num.map(function(n) {return n * 2})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.component` removed
|
||||
|
||||
In `v0.2.x` components could be created using either `m(component)` or `m.component(component)`. `v1.x` only supports `m(component)`.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
// These are equivalent
|
||||
m.component(component)
|
||||
m(component)
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m(component)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `config` function
|
||||
|
||||
In `v0.2.x` mithril provided a single lifecycle method, `config`. `v1.x` provides much more fine-grained control over the lifecycle of a vnode.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
config : function(element, isInitialized) {
|
||||
// runs on each redraw
|
||||
// isInitialized is a boolean representing if the node has been added to the DOM
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md).
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
// Called before the DOM node is created
|
||||
oninit : function(vnode) { /*...*/ },
|
||||
// Called after the DOM node is created
|
||||
oncreate : function(vnode) { /*...*/ },
|
||||
// Called before the node is updated, return false to cancel
|
||||
onbeforeupdate : function(vnode, old) { /*...*/ },
|
||||
// Called after the node is updated
|
||||
onupdate : function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, return a Promise that resolves when
|
||||
// ready for the node to be removed from the DOM
|
||||
onbeforeremove : function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, but after onbeforeremove calls done()
|
||||
onremove : function(vnode) { /*...*/ }
|
||||
})
|
||||
```
|
||||
|
||||
If available the DOM-Element of the vnode can be accessed at `vnode.dom`.
|
||||
|
||||
---
|
||||
|
||||
## Changes in redraw behaviour
|
||||
|
||||
Mithril's rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ:
|
||||
|
||||
### No more redraw locks
|
||||
|
||||
In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v1.x. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change.
|
||||
|
||||
### Cancelling redraw from event handlers
|
||||
|
||||
`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`.
|
||||
|
||||
#### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick : function(e) {
|
||||
m.redraw.strategy("none")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### `v1.x`
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick : function(e) {
|
||||
e.redraw = false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Synchronous redraw removed
|
||||
|
||||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed.
|
||||
|
||||
#### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.redraw(true) // redraws immediately & synchronously
|
||||
```
|
||||
|
||||
#### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.redraw() // schedules a redraw on the next requestAnimationFrame tick
|
||||
```
|
||||
|
||||
### `m.startComputation`/`m.endComputation` removed
|
||||
|
||||
They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x.
|
||||
|
||||
---
|
||||
|
||||
## Component `controller` function
|
||||
|
||||
In `v1.x` there is no more `controller` property in components, use `oninit` instead.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
controller : function() {
|
||||
var ctrl = this
|
||||
|
||||
ctrl.fooga = 1
|
||||
},
|
||||
|
||||
view : function(ctrl) {
|
||||
return m("p", ctrl.fooga)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
oninit : function(vnode) {
|
||||
vnode.state.fooga = 1
|
||||
},
|
||||
|
||||
view : function(vnode) {
|
||||
return m("p", vnode.state.fooga)
|
||||
}
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
m.mount(document.body, {
|
||||
oninit : function(vnode) {
|
||||
var state = this // this is bound to vnode.state by default
|
||||
|
||||
state.fooga = 1
|
||||
},
|
||||
|
||||
view : function(vnode) {
|
||||
var state = this // this is bound to vnode.state by default
|
||||
|
||||
return m("p", state.fooga)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component arguments
|
||||
|
||||
Arguments to a component in `v1.x` must be an object, simple values like `String`/`Number`/`Boolean` will be treated as text children. Arguments are accessed within the component by reading them from the `vnode.attrs` object.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var component = {
|
||||
controller : function(options) {
|
||||
// options.fooga === 1
|
||||
},
|
||||
|
||||
view : function(ctrl, options) {
|
||||
// options.fooga == 1
|
||||
}
|
||||
}
|
||||
|
||||
m("div", m.component(component, { fooga : 1 }))
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var component = {
|
||||
oninit : function(vnode) {
|
||||
// vnode.attrs.fooga === 1
|
||||
},
|
||||
|
||||
view : function(vnode) {
|
||||
// vnode.attrs.fooga == 1
|
||||
}
|
||||
}
|
||||
|
||||
m("div", m(component, { fooga : 1 }))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `view()` parameters
|
||||
|
||||
In `v0.2.x` view functions are passed a reference to the `controller` instance and (optionally) any options passed to the component. In `v1.x` they are passed **only** the `vnode`, exactly like the `controller` function.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
controller : function() {},
|
||||
|
||||
view : function(ctrl, options) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
oninit : function(vnode) {
|
||||
// ...
|
||||
},
|
||||
|
||||
view : function(vnode) {
|
||||
// Use vnode.state instead of ctrl
|
||||
// Use vnode.attrs instead of options
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passing components to `m()`
|
||||
|
||||
In `v0.2.x` you could pass components as the second argument of `m()` w/o any wrapping required. To help with consistency in `v1.x` they must always be wrapped with a `m()` invocation.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m("div", component)
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m("div", m(component))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passing vnodes to `m.mount()` and `m.route()`
|
||||
|
||||
In `v0.2.x`, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object.
|
||||
|
||||
In `v1.x`, components are required instead in both cases.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.mount(element, m('i', 'hello'))
|
||||
m.mount(element, m(Component, attrs))
|
||||
|
||||
m.route(element, '/', {
|
||||
'/': m('b', 'bye')
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.mount(element, {view: function () {return m('i', 'hello')}})
|
||||
m.mount(element, {view: function () {return m(Component, attrs)}})
|
||||
|
||||
m.route(element, '/', {
|
||||
'/': {view: function () {return m('b', 'bye')}}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.route.mode`
|
||||
|
||||
In `v0.2.x` the routing mode could be set by assigning a string of `"pathname"`, `"hash"`, or `"search"` to `m.route.mode`. In `v.1.x` it is replaced by `m.route.prefix(prefix)` where `prefix` can be `#`, `?`, or an empty string (for "pathname" mode). The new API also supports hashbang (`#!`), which is the default, and it supports non-root pathnames and arbitrary mode variations such as querybang (`?!`)
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.route.mode = "pathname"
|
||||
m.route.mode = "search"
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.route.prefix("")
|
||||
m.route.prefix("?")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.route()` and anchor tags
|
||||
|
||||
Handling clicks on anchor tags via the mithril router is similar to `v0.2.x` but uses a new lifecycle method and API.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m("a", {
|
||||
href : "/path",
|
||||
config : m.route
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m("a", {
|
||||
href : "/path",
|
||||
oncreate : m.route.link
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reading/writing the current route
|
||||
|
||||
In `v0.2.x` all interaction w/ the current route happened via `m.route()`. In `v1.x` this has been broken out into two functions.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route()
|
||||
|
||||
// Setting a new route
|
||||
m.route("/other/route")
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route.get()
|
||||
|
||||
// Setting a new route
|
||||
m.route.set("/other/route")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessing route params
|
||||
|
||||
In `v0.2.x` reading route params was entirely handled through `m.route.param()`. This API is still available in `v1.x`, and additionally any route params are passed as properties in the `attrs` object on the vnode.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
controller : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function() {
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr" : {
|
||||
oninit : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view : function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building/Parsing query strings
|
||||
|
||||
`v0.2.x` used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In `v1.x` these have been broken out and attached to the root `m`.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var qs = m.route.buildQueryString({ a : 1 });
|
||||
|
||||
var obj = m.route.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var qs = m.buildQueryString({ a : 1 });
|
||||
|
||||
var obj = m.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preventing unmounting
|
||||
|
||||
It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
if (condition) e.preventDefault()
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("a[href=/]", {config: m.route})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("a", {onclick: function() {if (!condition) m.route.set("/")}})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run code on component removal
|
||||
|
||||
Components no longer call `this.onunload` when they are being removed. They now use the standardized lifecycle hook `onremove`.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
onremove : function() {
|
||||
// ...
|
||||
}
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## m.request
|
||||
|
||||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options.
|
||||
|
||||
In addition, requests no longer have `m.startComputation`/`m.endComputation` semantics. Instead, redraws are always triggered when a request promise chain completes (unless `background:true` is set).
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var data = m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
initialValue: [],
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data())
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var data = []
|
||||
m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
})
|
||||
.then(function (responseBody) {
|
||||
data = responseBody
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data) // note: not a getter-setter
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve the request promise, and the `deserialize` callback is ignored.
|
||||
|
||||
---
|
||||
|
||||
## `m.deferred` removed
|
||||
|
||||
`v0.2.x` used its own custom asynchronous contract object, exposed as `m.deferred`, which was used as the basis for `m.request`. `v1.x` uses Promises instead, and implements a [polyfill](promises.md) in non-supporting environments. In situations where you would have used `m.deferred`, you should use Promises instead.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
var greetAsync = function() {
|
||||
var deferred = m.deferred()
|
||||
setTimeout(function() {
|
||||
deferred.resolve("hello")
|
||||
}, 1000)
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
.then(function(value) {return value + " world"})
|
||||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
var greetAsync = function() {
|
||||
return new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
.then(function(value) {return value + " world"})
|
||||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.sync` removed
|
||||
|
||||
Since `v1.x` uses standards-compliant Promises, `m.sync` is redundant. Use `Promise.all` instead.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m.sync([
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
Promise.all([
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `xlink` namespace required
|
||||
|
||||
In `v0.2.x`, the `xlink` namespace was the only supported attribute namespace, and it was supported via special casing behavior. Now namespace parsing is fully supported, and namespaced attributes should explicitly declare their namespace.
|
||||
|
||||
### `v0.2.x`
|
||||
|
||||
```javascript
|
||||
m("svg",
|
||||
// the `href` attribute is namespaced automatically
|
||||
m("image[href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
### `v1.x`
|
||||
|
||||
```javascript
|
||||
m("svg",
|
||||
// User-specified namespace on the `href` attribute
|
||||
m("image[xlink:href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nested arrays in views
|
||||
|
||||
Arrays now represent [fragments](fragment.md), which are structurally significant in v1.x virtual DOM. Whereas nested arrays in v0.2.x would be flattened into one continuous list of virtual nodes for the purposes of diffing, v1.x preserves the array structure - the children of any given array are not considered siblings of those of adjacent arrays.
|
||||
|
||||
---
|
||||
|
||||
## `vnode` equality checks
|
||||
|
||||
If a vnode is strictly equal to the vnode occupying its place in the last draw, v1.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views).
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ 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]
|
||||
available at [https://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
[homepage]: https://contributor-covenant.org
|
||||
[version]: https://contributor-covenant.org/version/1/4/
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ Special thanks to:
|
|||
|
||||
Other people who also deserve recognition:
|
||||
|
||||
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html)
|
||||
- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](https://arthurclemens.github.io/mithril-template-converter/index.html)
|
||||
- Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint)
|
||||
- the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
Here are some examples of Mithril in action
|
||||
|
||||
- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html)
|
||||
- [Animation](https://raw.githack.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html)
|
||||
- [Community Added Examples](https://how-to-mithril.js.org)
|
||||
- [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)
|
||||
|
||||
- [DBMonster](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html)
|
||||
- [Markdown Editor](https://raw.githack.com/MithrilJS/mithril.js/master/examples/editor/index.html)
|
||||
- SVG: [Clock](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](https://raw.githack.com/MithrilJS/mithril.js/master/examples/svg/tiger.html)
|
||||
- [ThreadItJS](https://raw.githack.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html)
|
||||
- [TodoMVC](https://raw.githack.com/MithrilJS/mithril.js/master/examples/todomvc/index.html)
|
||||
|
|
|
|||
|
|
@ -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/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:
|
||||
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](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](https://raw.githack.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/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:
|
||||
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](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](https://raw.githack.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/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:
|
||||
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](https://raw.githack.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](https://raw.githack.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
|
||||
------ | -------
|
||||
|
|
|
|||
|
|
@ -122,8 +122,8 @@ m("section.container")
|
|||
m("input[type=text][placeholder=Name]")
|
||||
// <input type="text" placeholder="Name" />
|
||||
|
||||
m("a#exit.external[href='http://example.com']", "Leave")
|
||||
// <a id="exit" class="external" href="http://example.com">Leave</a>
|
||||
m("a#exit.external[href='https://example.com']", "Leave")
|
||||
// <a id="exit" class="external" href="https://example.com">Leave</a>
|
||||
```
|
||||
|
||||
If you omit the tag name, Mithril assumes a `div` tag.
|
||||
|
|
@ -215,7 +215,7 @@ m("a-scene", [
|
|||
])
|
||||
```
|
||||
|
||||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](http://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property.
|
||||
And yes, this translates to both attributes and properties, and it works just like they would in the DOM. Using [Brick's `brick-deck`](https://brick.mozilla.io/docs/brick-deck) as an example, they have a `selected-index` attribute with a corresponding `selectedIndex` getter/setter property.
|
||||
|
||||
```javascript
|
||||
m("brick-deck[selected-index=0]", [/* ... */]) // lowercase
|
||||
|
|
@ -232,7 +232,7 @@ For custom elements, it doesn't auto-stringify properties, in case they are obje
|
|||
m("my-special-element", {
|
||||
whitelist: [
|
||||
"https://example.com",
|
||||
"http://neverssl.com",
|
||||
"https://neverssl.com",
|
||||
"https://google.com",
|
||||
],
|
||||
})
|
||||
|
|
@ -462,7 +462,7 @@ You cannot use JavaScript statements such as `if` or `for` within JavaScript exp
|
|||
|
||||
In Mithril, well-formed HTML is valid JSX. Little effort other than copy-pasting is required to integrate an independently produced HTML file into a project using JSX.
|
||||
|
||||
When using hyperscript, it's necessary to convert HTML to hyperscript syntax before the code can be run. To facilitate this, you can [use the HTML-to-Mithril-template converter](http://arthurclemens.github.io/mithril-template-converter/index.html).
|
||||
When using hyperscript, it's necessary to convert HTML to hyperscript syntax before the code can be run. To facilitate this, you can [use the HTML-to-Mithril-template converter](https://arthurclemens.github.io/mithril-template-converter/index.html).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ Let's create an HTML file to follow along:
|
|||
|
||||
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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://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!
|
||||
|
|
@ -96,7 +96,7 @@ As you can see, you use the same code to both create and update HTML. Mithril au
|
|||
|
||||
#### 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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
|
@ -135,7 +135,7 @@ 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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://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).
|
||||
|
|
@ -206,7 +206,7 @@ If you're wondering about performance, it turns out Mithril is very fast at rend
|
|||
|
||||
#### 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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
|
@ -238,13 +238,13 @@ m.route(root, "/splash", {
|
|||
|
||||
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`.
|
||||
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 `https://localhost`, then you get redirected to `https://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.
|
||||
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 `https://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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
|
@ -253,7 +253,7 @@ Also, as you would expect, clicking on the link on the splash page takes you to
|
|||
|
||||
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.
|
||||
Let's change our click counter to make it save data on a server. For the server, we'll use [REM](https://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)), `body` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work)
|
||||
|
||||
|
|
@ -291,7 +291,7 @@ 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>
|
||||
<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="https://codepen.io/tivac">@tivac</a>) on <a href="https://codepen.io">CodePen</a>.</p>
|
||||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Installation
|
||||
|
||||
- [CDN](#cdn)
|
||||
- [NPM](#npm)
|
||||
- [npm](#npm)
|
||||
- [Quick start with Webpack](#quick-start-with-webpack)
|
||||
- [TypeScript](#typescript)
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ If you're new to JavaScript or just want a very simple setup to get your feet we
|
|||
|
||||
---
|
||||
|
||||
### NPM
|
||||
### npm
|
||||
|
||||
```bash
|
||||
$ npm install mithril --save
|
||||
|
|
@ -83,11 +83,11 @@ $ npm start
|
|||
|
||||
#### Step by step
|
||||
|
||||
For production-level projects, the recommended way of installing Mithril is to use NPM.
|
||||
For production-level projects, the recommended way of installing Mithril is to use npm.
|
||||
|
||||
NPM (Node package manager) is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node.js](https://nodejs.org); NPM will be automatically installed as well.
|
||||
npm is the default package manager that is bundled w/ Node.js. It is widely used as the package manager for both client-side and server-side libraries in the JavaScript ecosystem. Download and install [Node](https://nodejs.org); npm is bundled with that and installed alongside it.
|
||||
|
||||
To use Mithril via NPM, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`.
|
||||
To use Mithril via npm, go to your project folder, and run `npm init --yes` from the command line. This will create a file called `package.json`.
|
||||
|
||||
```bash
|
||||
npm init --yes
|
||||
|
|
@ -113,11 +113,11 @@ m.render(document.body, "hello world")
|
|||
|
||||
Modularization is the practice of separating the code into files. Doing so makes it easier to find code, understand what code relies on what code, and test.
|
||||
|
||||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](http://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](http://rollupjs.org/) or [Babel](https://babeljs.io/).
|
||||
CommonJS is a de-facto standard for modularizing JavaScript code, and it's used by Node.js, as well as tools like [Browserify](https://browserify.org/) and [Webpack](https://webpack.js.org/). It's a robust, battle-tested precursor to ES6 modules. Although the syntax for ES6 modules is specified in Ecmascript 6, the actual module loading mechanism is not. If you wish to use ES6 modules despite the non-standardized status of module loading, you can use tools like [Rollup](https://rollupjs.org/) or [Babel](https://babeljs.io/).
|
||||
|
||||
Most browser today do not natively support modularization systems (CommonJS or ES6), so modularized code must be bundled into a single JavaScript file before running in a client-side application.
|
||||
|
||||
A popular way for creating a bundle is to setup an NPM script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line:
|
||||
A popular way for creating a bundle is to setup an npm script for [Webpack](https://webpack.js.org/). To install Webpack, run this from the command line:
|
||||
|
||||
```bash
|
||||
npm install webpack webpack-cli --save-dev
|
||||
|
|
@ -140,7 +140,7 @@ The `-d` flag tells webpack to use development mode, which produces source maps
|
|||
|
||||
The `--watch` flag tells webpack to watch the file system and automatically recreate `app.js` if file changes are detected.
|
||||
|
||||
Now you can run the script via `npm start` in your command line window. This looks up the `webpack` command in the NPM path, reads `index.js` and creates a file called `app.js` which includes both Mithril and the `hello world` code above. If you want to run the `webpack` command directly from the command line, you need to either add `node_modules/.bin` to your PATH, or install webpack globally via `npm install webpack -g`. It's, however, recommended that you always install webpack locally and use npm scripts, to ensure builds are reproducible in different computers.
|
||||
Now you can run the script via `npm start` in your command line window. This looks up the `webpack` command in the npm path, reads `index.js` and creates a file called `app.js` which includes both Mithril and the `hello world` code above. If you want to run the `webpack` command directly from the command line, you need to either add `node_modules/.bin` to your PATH, or install webpack globally via `npm install webpack -g`. It's, however, recommended that you always install webpack locally and use npm scripts, to ensure builds are reproducible in different computers.
|
||||
|
||||
```
|
||||
npm start
|
||||
|
|
@ -159,7 +159,7 @@ Now that you have created a bundle, you can then reference the `bin/app.js` file
|
|||
</html>
|
||||
```
|
||||
|
||||
As you've seen above, importing a module in CommonJS is done via the `require` function. You can reference NPM modules by their library names (e.g. `require("mithril")` or `require("jquery")`), and you can reference your own modules via relative paths minus the file extension (e.g. if you have a file called `mycomponent.js` in the same folder as the file you're importing to, you can import it by calling `require("./mycomponent")`).
|
||||
As you've seen above, importing a module in CommonJS is done via the `require` function. You can reference npm modules by their library names (e.g. `require("mithril")` or `require("jquery")`), and you can reference your own modules via relative paths minus the file extension (e.g. if you have a file called `mycomponent.js` in the same folder as the file you're importing to, you can import it by calling `require("./mycomponent")`).
|
||||
|
||||
To export a module, assign what you want to export to the special `module.exports` object:
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ Note that in this example, we're using `m.mount`, which wires up the component t
|
|||
|
||||
If you open bin/app.js, you'll notice that the Webpack bundle is not minified, so this file is not ideal for a live application. To generate a minified file, open `package.json` and add a new npm script:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"scripts": {
|
||||
|
|
@ -236,21 +236,6 @@ npm start
|
|||
|
||||
The source file `index.js` will be compiled (bundled) and a browser window opens showing the result. Any changes in the source files will instantly get recompiled and the browser will refresh reflecting the changes.
|
||||
|
||||
#### Mithril bundler
|
||||
|
||||
Mithril comes with a bundler tool of its own. It is sufficient for ES5-based projects that have no other dependencies other than Mithril, but it's currently considered experimental for projects that require other NPM dependencies. It produces smaller bundles than webpack, but you should not use it in production yet.
|
||||
|
||||
If you want to try it and give feedback, you can open `package.json` and change the npm script for webpack to this:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "my-project",
|
||||
"scripts": {
|
||||
"build": "bundle src/index.js --output bin/app.js --watch"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Vanilla
|
||||
|
||||
If you don't have the ability to run a bundler script due to company security policies, there's an options to not use a module system at all:
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ When using JSX, it's possible to interpolate JavaScript expressions within JSX t
|
|||
|
||||
```jsx
|
||||
var greeting = "Hello"
|
||||
var url = "http://google.com"
|
||||
var url = "https://google.com"
|
||||
var link = <a href={url}>{greeting}!</a>
|
||||
// yields <a href="http://google.com">Hello!</a>
|
||||
// yields <a href="https://google.com">Hello!</a>
|
||||
```
|
||||
|
||||
Components can be used by using a convention of uppercasing the first letter of the component name:
|
||||
|
|
@ -56,7 +56,7 @@ m.render(document.body, <MyComponent />)
|
|||
|
||||
The simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin.
|
||||
|
||||
Babel requires NPM, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once NPM is installed, create a project folder and run this command:
|
||||
Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command:
|
||||
|
||||
```bash
|
||||
npm init -y
|
||||
|
|
@ -340,4 +340,4 @@ function SummaryView() {
|
|||
|
||||
In Mithril, well-formed HTML is generally valid JSX. Little more than just pasting raw HTML is required for things to just work. About the only things you'd normally have to do are change unquoted property values like `attr=value` to `attr="value"` and change void elements like `<input>` to `<input />`, this being due to JSX being based on XML and not HTML.
|
||||
|
||||
When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a [community-created HTML-to-Mithril-template converter](http://arthurclemens.github.io/mithril-template-converter/index.html) to do much of it for you.
|
||||
When using hyperscript, you often need to translate HTML to hyperscript syntax to use it. To help speed up this process along, you can use a [community-created HTML-to-Mithril-template converter](https://arthurclemens.github.io/mithril-template-converter/index.html) to do much of it for you.
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ function traverseDirectory(pathname, callback) {
|
|||
|
||||
//run
|
||||
traverseDirectory("./docs", function(pathname) {
|
||||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|node_modules/)) {
|
||||
if (pathname.indexOf(".md") > -1 && !pathname.match(/change-log|migration-|node_modules/)) {
|
||||
fs.readFile(pathname, "utf8", function(err, data) {
|
||||
if (err) console.log(err)
|
||||
else lint(pathname, data)
|
||||
|
|
|
|||
935
docs/migration-v02x.md
Normal file
935
docs/migration-v02x.md
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
# Migrating from v0.2.x
|
||||
|
||||
v1.x and v2.x are largely API-compatible with v0.2.x, but there are some breaking changes. Migrating to v2.x is nearly identical, so the notes below apply mostly to both.
|
||||
|
||||
If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations.
|
||||
|
||||
- [`m.prop` removed](#mprop-removed)
|
||||
- [`m.component` removed](#mcomponent-removed)
|
||||
- [`m.withAttr` removed](#mwithattr-removed)
|
||||
- [`m.version` removed](#mversion-removed)
|
||||
- [`config` function](#config-function)
|
||||
- [Changes in redraw behaviour](#changes-in-redraw-behaviour)
|
||||
- [No more redraw locks](#no-more-redraw-locks)
|
||||
- [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers)
|
||||
- [Synchronous redraw changed](#synchronous-redraw)
|
||||
- [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed)
|
||||
- [Component `controller` function](#component-controller-function)
|
||||
- [Component arguments](#component-arguments)
|
||||
- [Component vnode children](#component-children)
|
||||
- [DOM vnode children](#dom-vnode-children)
|
||||
- [Keys](#keys)
|
||||
- [`view()` parameters](#view-parameters)
|
||||
- [Passing components to `m()`](#passing-components-to-m)
|
||||
- [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute)
|
||||
- [`m.route.mode`](#mroutemode)
|
||||
- [`m.route()` and anchor tags](#mroute-and-anchor-tags)
|
||||
- [Path templates](#path-templates)
|
||||
- [Reading/writing the current route](#readingwriting-the-current-route)
|
||||
- [Accessing route params](#accessing-route-params)
|
||||
- [Building/Parsing query strings](#buildingparsing-query-strings)
|
||||
- [Preventing unmounting](#preventing-unmounting)
|
||||
- [Run code on component removal](#run-code-on-component-removal)
|
||||
- [`m.request`](#mrequest)
|
||||
- [Default `responseType` for `m.request`](#default-responsetype-for-mrequest)
|
||||
- [`m.deferred` removed](#mdeferred-removed)
|
||||
- [`m.sync` removed](#msync-removed)
|
||||
- [`xlink` namespace required](#xlink-namespace-required)
|
||||
- [Nested arrays in views](#nested-arrays-in-views)
|
||||
- [`vnode` equality checks](#vnode-equality-checks)
|
||||
|
||||
---
|
||||
|
||||
## `m.prop` removed
|
||||
|
||||
In v2.x, `m.prop()` was converted into now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md).
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
|
||||
var num = m.prop(1)
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var m = require("mithril")
|
||||
var prop = require("mithril/stream")
|
||||
|
||||
var num = prop(1)
|
||||
var doubled = num.map(function(n) { return n * 2 })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.component` removed
|
||||
|
||||
In v0.2.x components could be created using either `m(Component)` or `m.component(Component)`. v2.x only support `m(Component)`.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
// These are equivalent
|
||||
m.component(Component)
|
||||
m(Component)
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m(Component)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.withAttr` removed
|
||||
|
||||
In v0.2.x event listeners could use `oninput: m.withAttr("value", func)` and similar. In v2.x, just read them directly from the event's target. It synergized well with `m.prop`, but since that was removed in favor of an out of core solution and v1.x didn't see similar broad, idiomatic usage of streams, `m.withAttr` lost most of its usefulness.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var value = m.prop("")
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value(),
|
||||
oninput: m.withAttr("value", value),
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var value = ""
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value,
|
||||
oninput: function (ev) { value = ev.target.value },
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.version` removed
|
||||
|
||||
It served little use in general, and you can always add it back yourself. You should prefer feature detection for knowing what features are available, and the v2.x API is designed to better enable this.
|
||||
|
||||
---
|
||||
|
||||
## `config` function
|
||||
|
||||
In v0.2.x mithril provided a single lifecycle method, `config`. v2.x provide much more fine-grained control over the lifecycle of a vnode.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
config: function(element, isInitialized) {
|
||||
// runs on each redraw
|
||||
// isInitialized is a boolean representing if the node has been added to the DOM
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md).
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
// Called before the DOM node is created
|
||||
oninit: function(vnode) { /*...*/ },
|
||||
// Called after the DOM node is created
|
||||
oncreate: function(vnode) { /*...*/ },
|
||||
// Called before the node is updated, return false to cancel
|
||||
onbeforeupdate: function(vnode, old) { /*...*/ },
|
||||
// Called after the node is updated
|
||||
onupdate: function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, return a Promise that resolves when
|
||||
// ready for the node to be removed from the DOM
|
||||
onbeforeremove: function(vnode) { /*...*/ },
|
||||
// Called before the node is removed, but after onbeforeremove calls done()
|
||||
onremove: function(vnode) { /*...*/ }
|
||||
})
|
||||
```
|
||||
|
||||
If available the DOM-Element of the vnode can be accessed at `vnode.dom`.
|
||||
|
||||
---
|
||||
|
||||
## Changes in redraw behaviour
|
||||
|
||||
Mithril's rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ:
|
||||
|
||||
### No more redraw locks
|
||||
|
||||
In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v2.x without replacement. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change.
|
||||
|
||||
### Cancelling redraw from event handlers
|
||||
|
||||
`m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`.
|
||||
|
||||
#### v0.2.x
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick: function(e) {
|
||||
m.redraw.strategy("none")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### v2.x
|
||||
|
||||
```javascript
|
||||
m("div", {
|
||||
onclick: function(e) {
|
||||
e.redraw = false
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Synchronous redraw changed
|
||||
|
||||
In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. In v2.x, this functionality was split into two different methods for clarity.
|
||||
|
||||
#### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.redraw(true) // redraws immediately & synchronously
|
||||
```
|
||||
|
||||
#### v2.x
|
||||
|
||||
```javascript
|
||||
m.redraw() // schedules a redraw on the next requestAnimationFrame tick
|
||||
m.redraw.sync() // invokes a redraw immediately and waits for it to complete
|
||||
```
|
||||
|
||||
### `m.startComputation`/`m.endComputation` removed
|
||||
|
||||
They are considered anti-patterns and have a number of problematic edge cases, so they were removed without replacement in v2.x.
|
||||
|
||||
---
|
||||
|
||||
## Component `controller` function
|
||||
|
||||
In v2.x, there is no more `controller` property in components - use `oninit` instead.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
controller: function() {
|
||||
var ctrl = this
|
||||
|
||||
ctrl.fooga = 1
|
||||
},
|
||||
|
||||
view: function(ctrl) {
|
||||
return m("p", ctrl.fooga)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
oninit: function(vnode) {
|
||||
vnode.state.fooga = 1
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
return m("p", vnode.state.fooga)
|
||||
}
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
m.mount(document.body, {
|
||||
// this is bound to vnode.state by default
|
||||
oninit: function(vnode) {
|
||||
this.fooga = 1
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
return m("p", this.fooga)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component arguments
|
||||
|
||||
Arguments to a component in v2.x must be an object, simple values like `String`/`Number`/`Boolean` will be treated as text children. Arguments are accessed within the component by reading them from the `vnode.attrs` object.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function(options) {
|
||||
// options.fooga === 1
|
||||
},
|
||||
|
||||
view: function(ctrl, options) {
|
||||
// options.fooga === 1
|
||||
}
|
||||
}
|
||||
|
||||
m("div", m.component(Component, { fooga: 1 }))
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
oninit: function(vnode) {
|
||||
// vnode.attrs.fooga === 1
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
// vnode.attrs.fooga === 1
|
||||
}
|
||||
}
|
||||
|
||||
m("div", m(Component, { fooga: 1 }))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component vnode children
|
||||
|
||||
In v0.2.x, component vnode children were not normalized, just passed as extra arguments, and they were not flattened, either. (Internally, it was just returning a partially applied component that was diffed based on the component being partially applied.) In v2.x, component vnode children are passed via `vnode.children` as a resolved array of children, but like v0.2.x, the individual children themselves are not normalized, nor is the children array flattened.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function(value, renderProp) {
|
||||
// value === "value"
|
||||
// typeof renderProp === "function"
|
||||
},
|
||||
|
||||
view: function(ctrl, value, renderProp) {
|
||||
// value === "value"
|
||||
// typeof renderProp === "function"
|
||||
}
|
||||
}
|
||||
|
||||
m("div", m.component(Component, "value", function(key) { return "child" }))
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
oninit: function(vnode) {
|
||||
// vnode.children[0] === "value"
|
||||
// typeof vnode.children[1] === "function"
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
// vnode.children[0] === "value"
|
||||
// typeof vnode.children[1] === "function"
|
||||
},
|
||||
}
|
||||
|
||||
m("div", m(Component, "value", function(key) { return "child" }))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DOM vnode children
|
||||
|
||||
In v0.2.x, the children of DOM nodes were represented literally with no normalization aside from using the children directly if only a single array child is present. It returned a structure more like this, with the strings represented literally.
|
||||
|
||||
```js
|
||||
m("div", "value", ["nested"])
|
||||
|
||||
// Becomes:
|
||||
{
|
||||
tag: "div",
|
||||
attrs: {},
|
||||
children: [
|
||||
"value",
|
||||
["nested"],
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
In v2.x, children of DOM vnodes are normalized to objects of a single consistent structure.
|
||||
|
||||
```js
|
||||
m("div", "value", ["nested"])
|
||||
|
||||
// Becomes roughly:
|
||||
{
|
||||
tag: "div",
|
||||
attrs: null,
|
||||
children: [
|
||||
{tag: "#", children: "value"},
|
||||
{tag: "[", children: [
|
||||
{tag: "#", children: "nested"},
|
||||
]},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If only a single text child is present on a DOM vnode, it instead sets `text` to that value.
|
||||
|
||||
```js
|
||||
m("div", "value")
|
||||
|
||||
// Becomes roughly:
|
||||
{
|
||||
tag: "div",
|
||||
attrs: null,
|
||||
text: "",
|
||||
children: undefined,
|
||||
}
|
||||
```
|
||||
|
||||
See [the vnode docs](vnodes.md) for more details on the v2.x vnode structure and how things are normalized.
|
||||
|
||||
*Most of the v2.x vnode properties here are omitted for brevity.*
|
||||
|
||||
---
|
||||
|
||||
## Keys
|
||||
|
||||
In v0.2.x, you could mix keyed and unkeyed vnodes freely.
|
||||
|
||||
In v2.x, children lists of both fragments and elements must be either all keyed or all unkeyed. Holes are considered unkeyed for the purposes of this check, too - it no longer ignores them.
|
||||
|
||||
If you need to work around it, use the idiom of a fragment containing a single vnode, like `[m("div", {key: whatever})]`.
|
||||
|
||||
---
|
||||
|
||||
## `view()` parameters
|
||||
|
||||
In v0.2.x view functions are passed a reference to the `controller` instance and (optionally) any options passed to the component. In v2.x they are passed **only** the `vnode`, exactly like the `controller` function.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
controller: function() {},
|
||||
|
||||
view: function(ctrl, options) {
|
||||
// ...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.mount(document.body, {
|
||||
oninit: function(vnode) {
|
||||
// ...
|
||||
},
|
||||
|
||||
view: function(vnode) {
|
||||
// Use vnode.state instead of ctrl
|
||||
// Use vnode.attrs instead of options
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passing components to `m()`
|
||||
|
||||
In v0.2.x you could pass components as the second argument of `m()` w/o any wrapping required. To help with consistency in v2.x they must always be wrapped with a `m()` invocation.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m("div", Component)
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m("div", m(Component))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Passing vnodes to `m.mount()` and `m.route()`
|
||||
|
||||
In v0.2.x, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object.
|
||||
|
||||
In v2.x, components are required instead in both cases.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.mount(element, m('i', 'hello'))
|
||||
m.mount(element, m(Component, attrs))
|
||||
|
||||
m.route(element, '/', {
|
||||
'/': m('b', 'bye')
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.mount(element, {view: function () {return m('i', 'hello')}})
|
||||
m.mount(element, {view: function () {return m(Component, attrs)}})
|
||||
|
||||
m.route(element, '/', {
|
||||
'/': {view: function () {return m('b', 'bye')}}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.route.mode`
|
||||
|
||||
In v0.2.x the routing mode could be set by assigning a string of `"pathname"`, `"hash"`, or `"search"` to `m.route.mode`. In `v.1.x` it is replaced by `m.route.prefix = prefix` where `prefix` can any prefix. If it starts with `#`, it works in "hash" mode, `?` for "search" mode, and any other character (or the empty string) for "pathname" mode. It also supports combinations of the above like `m.route.prefix = "/path/#!"` or `?#`.
|
||||
|
||||
The default was changed to also use a `#!` (hashbang) prefix instead of just `#`. So if you were using the default behavior and want to retain your existing URLs, specify `m.route.prefix = "#"` before initializing the routes.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.route.mode = "hash"
|
||||
m.route.mode = "pathname"
|
||||
m.route.mode = "search"
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
// Direct equivalents
|
||||
m.route.prefix = "#"
|
||||
m.route.prefix = ""
|
||||
m.route.prefix = "?"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.route()` and anchor tags
|
||||
|
||||
Handling routable links now uses a special built-in component instead of an attribute. If you were using this on `<button>`s and the like, you can specify that tag name using a `selector: "button"` attribute.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m("a", {
|
||||
href: "/path",
|
||||
config: m.route
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
// When clicked this link will load the "/path" route instead of navigating
|
||||
m(m.route.Link, {
|
||||
href: "/path",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Path templates
|
||||
|
||||
In v1.x, there were three separate path template syntaxes that, although they were similar, had 2 separately designed syntaxes and 3 different implementations. It was defined in a fairly ad-hoc way, and parameters weren't generally escaped. Now, everything is either encoded if it's `:key`, raw if it's `:key...`. If things are unexpectedly encoded, use `:path...`. It's that simple.
|
||||
|
||||
Concretely, here's how it affects each method:
|
||||
|
||||
### `m.request` URLs
|
||||
|
||||
Path components in v2.x are escaped automatically when interpolated and they read their values from `params`. In v0.2.x, `m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}})` would send its request with the URL set to `/user/a%2Fb/photos/c/d`. In v2.x, the corresponding `m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}})` would send its request to `/user/a%2Fb/photos/c%2Fd`. If you deliberately *want* to interpolate a key unescaped, use `:key...` instead.
|
||||
|
||||
Interpolations in inline query strings, like in `/api/search?q=:query`, are not performed in v2.x. Pass those via `params` with appropriate key names instead, without specifying it in the query string.
|
||||
|
||||
Do note that this applies to `m.jsonp` as well. When migrating from `m.request` + `dataType: "jsonp"` to `m.jsonp`, you also need to be aware of this.
|
||||
|
||||
### `m.route(route, params, shouldReplaceHistoryEntry)` paths
|
||||
|
||||
These permit interpolations now, and they work identically to that of `m.request`.
|
||||
|
||||
### `m.route` route patterns
|
||||
|
||||
Path keys of the form `:key...` return their URL decoded in v1.x, but return the raw URL in v2.x.
|
||||
|
||||
Previously, stuff like `:key.md` were erroneously accepted, with the resulting parameter's value set to `keymd: "..."`. This is no longer the case - the `.md` is part of the pattern now, not the name.
|
||||
|
||||
---
|
||||
|
||||
## Reading/writing the current route
|
||||
|
||||
In v0.2.x all interaction w/ the current route happened via `m.route()`. In v2.x this has been broken out into two functions.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route()
|
||||
|
||||
// Setting a new route
|
||||
m.route("/other/route")
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
// Getting the current route
|
||||
m.route.get()
|
||||
|
||||
// Setting a new route
|
||||
m.route.set("/other/route")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Accessing route params
|
||||
|
||||
In v0.2.x reading route params was entirely handled through `m.route.param()`. This API is still available in v2.x, and additionally any route params are passed as properties in the `attrs` object on the vnode.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr": {
|
||||
controller: function() {
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view: function() {
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.route(document.body, "/booga", {
|
||||
"/:attr": {
|
||||
oninit: function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
},
|
||||
view: function(vnode) {
|
||||
vnode.attrs.attr // "booga"
|
||||
m.route.param("attr") // "booga"
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Building/Parsing query strings
|
||||
|
||||
v0.2.x used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In v2.x these have been broken out and moved to the root `m`.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var qs = m.route.buildQueryString({ a: 1 });
|
||||
|
||||
var obj = m.route.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var qs = m.buildQueryString({ a: 1 });
|
||||
|
||||
var obj = m.parseQueryString("a=1");
|
||||
```
|
||||
|
||||
Also, in v2.x, `{key: undefined}` is serialized as `key=undefined` by `m.buildQueryString` and methods that use it like `m.request`. In v0.2.x, the key was omitted and this carried over to `m.request`. If you were previously relying on this, change your code to omit the keys from the object entirely. It may be worth using a simple utility to strip all keys from an object whose values are `undefined` if you can't easily do that and you need to retain v0.2.x behavior.
|
||||
|
||||
```javascript
|
||||
// Call whenever you need to omit `undefined` parameters from an object.
|
||||
function omitUndefineds(object) {
|
||||
var result = {}
|
||||
|
||||
for (var key in object) {
|
||||
if ({}.hasOwnProperty.call(object, key)) {
|
||||
var value = object[key]
|
||||
if (Array.isArray(value)) {
|
||||
result[key] = value.map(omitUndefineds)
|
||||
} else if (value != null && typeof value === "object") {
|
||||
result[key] = omitUndefineds(value)
|
||||
} else if (value !== undefined) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preventing unmounting
|
||||
|
||||
It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
if (condition) e.preventDefault()
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
return m("a[href=/]", {config: m.route})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
view: function() {
|
||||
return m("a", {
|
||||
onclick: function() { if (!condition) m.route.set("/") },
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run code on component removal
|
||||
|
||||
Components no longer call `this.onunload` when they are being removed. They now use the standardized lifecycle hook `onremove`.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
controller: function() {
|
||||
this.onunload = function(e) {
|
||||
// ...
|
||||
}
|
||||
},
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Component = {
|
||||
onremove: function() {
|
||||
// ...
|
||||
}
|
||||
view: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.request`
|
||||
|
||||
Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options.
|
||||
|
||||
In addition, requests no longer have `m.startComputation`/`m.endComputation` semantics. Instead, redraws are always triggered when a request promise chain completes (unless `background: true` is set).
|
||||
|
||||
The `data` parameter has now been split into `params`, query parameters interpolated into the URL and appended to the request, and `body`, the body to send in the underlying XHR.
|
||||
|
||||
In v0.2.x, you would use a `dataType: "jsonp"` to initiate a JSONP request. In v2.x, you now use [`m.jsonp`](jsonp.md), which carries mostly the same API as `m.request` without the XHR-related parts.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var data = m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
initialValue: [],
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data())
|
||||
}, 1000)
|
||||
|
||||
m.request({
|
||||
method: "POST",
|
||||
url: "https://api.github.com/",
|
||||
data: someJson,
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var data = []
|
||||
m.request({
|
||||
method: "GET",
|
||||
url: "https://api.github.com/",
|
||||
})
|
||||
.then(function (responseBody) {
|
||||
data = responseBody
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data) // note: not a getter-setter
|
||||
}, 1000)
|
||||
|
||||
m.request({
|
||||
method: "POST",
|
||||
url: "https://api.github.com/",
|
||||
body: someJson,
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
var data = []
|
||||
m.request("https://api.github.com/")
|
||||
.then(function (responseBody) {
|
||||
data = responseBody
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
console.log(data) // note: not a getter-setter
|
||||
}, 1000)
|
||||
|
||||
m.request("https://api.github.com/", {
|
||||
method: "POST",
|
||||
body: someJson,
|
||||
})
|
||||
```
|
||||
|
||||
Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve the request promise, and the `deserialize` callback is ignored.
|
||||
|
||||
---
|
||||
|
||||
## `m.request` headers
|
||||
|
||||
In v0.2.x, Mithril didn't set any headers on requests by default. Now, it sets up to 2 headers:
|
||||
|
||||
- `Content-Type: application/json; charset=utf-8` for requests with JSON bodies that are `!= null`
|
||||
- `Accept: application/json, text/*` for requests expecting JSON responses
|
||||
|
||||
The first of the two headers, `Content-Type`, will trigger a CORS prefetch as it [is not a CORS-safelisted request header](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) due to the specified content type, and that could introduce new errors depending on how CORS is configured on your server. If you run into issues with this, you may need to override that header in question by passing `headers: {"Content-Type": "text/plain"}`. (The `Accept` header doesn't trigger anything, so you don't need to override that.)
|
||||
|
||||
*The only content types that the Fetch spec lets avoid CORS prefetch checks are `application/x-www-form-urlencoded`, `multipart/form-data`, and `text/plain`. It doesn't allow anything else, and it intentionally disallows JSON.*
|
||||
|
||||
---
|
||||
|
||||
## `m.deferred` removed
|
||||
|
||||
v0.2.x used its own custom asynchronous contract object, exposed as `m.deferred`, which was used as the basis for `m.request`. v2.x uses Promises instead, and implements a [polyfill](promises.md) in non-supporting environments. In situations where you would have used `m.deferred`, you should use Promises instead.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
var greetAsync = function() {
|
||||
var deferred = m.deferred()
|
||||
setTimeout(function() {
|
||||
deferred.resolve("hello")
|
||||
}, 1000)
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
.then(function(value) {return value + " world"})
|
||||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var greetAsync = function() {
|
||||
return new Promise(function(resolve){
|
||||
setTimeout(function() {
|
||||
resolve("hello")
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
greetAsync()
|
||||
.then(function(value) {return value + " world"})
|
||||
.then(function(value) {console.log(value)}) //logs "hello world" after 1 second
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.sync` removed
|
||||
|
||||
Since v2.x uses standards-compliant Promises, `m.sync` is redundant. Use `Promise.all` instead.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m.sync([
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
Promise.all([
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
|
||||
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
|
||||
])
|
||||
.then(function (users) {
|
||||
console.log("Contributors:", users[0].name, "and", users[1].name)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `xlink` namespace required
|
||||
|
||||
In v0.2.x, the `xlink` namespace was the only supported attribute namespace, and it was supported via special casing behavior. Now namespace parsing is fully supported, and namespaced attributes should explicitly declare their namespace.
|
||||
|
||||
### v0.2.x
|
||||
|
||||
```javascript
|
||||
m("svg",
|
||||
// the `href` attribute is namespaced automatically
|
||||
m("image[href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m("svg",
|
||||
// User-specified namespace on the `href` attribute
|
||||
m("image[xlink:href='image.gif']")
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nested arrays in views
|
||||
|
||||
Arrays now represent [fragments](fragment.md), which are structurally significant in v2.x virtual DOM. Whereas nested arrays in v0.2.x would be flattened into one continuous list of virtual nodes for the purposes of diffing, v2.x preserves the array structure - the children of any given array are not considered siblings of those of adjacent arrays.
|
||||
|
||||
---
|
||||
|
||||
## `vnode` equality checks
|
||||
|
||||
If a vnode is strictly equal to the vnode occupying its place in the last draw, v2.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views).
|
||||
362
docs/migration-v1x.md
Normal file
362
docs/migration-v1x.md
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
# Migrating from v1.x
|
||||
|
||||
v2.x is almost entirely API-compatible with v1.x, but there are some breaking changes.
|
||||
|
||||
- [Assigning to `vnode.state`](#assigning-to-vnodestate)
|
||||
- [Changes to route anchors](#changes-to-route-anchors)
|
||||
- [Changes to `m.request` errors](#changes-to-mrequest-errors)
|
||||
- [`m.withAttr` removed](#mwithattr-removed)
|
||||
- [`m.route.prefix`](#mrouteprefix)
|
||||
- [`m.request`/`m.jsonp` params and body](#mrequest-params-and-body)
|
||||
- [Path templates](#path-templates)
|
||||
- [Lifecycle call order](#lifecycle-call-order)
|
||||
- [`m.redraw` synchronicity](#mredraw)
|
||||
- [Selector attribute precedence](#selector-attribute-precedence)
|
||||
- [Children normalization](#children-normalization)
|
||||
- [Default `responseType` for `m.request`](#default-responsetype-for-mrequest)
|
||||
- [`m.request` headers](#mrequest-headers)
|
||||
- [Query parameters in hash strings in routes](#query-parameters-in-hash-strings-in-routes)
|
||||
- [Keys](#keys)
|
||||
- [`m.version` remvoed](#mversion-removed)
|
||||
|
||||
---
|
||||
|
||||
## Assigning to `vnode.state`
|
||||
|
||||
In v1.x, you could manipulate `vnode.state` and assign anything you wanted. In v2.x, an error will be thrown if it changes. Migration may vary, but most cases, it's as simple as changing references `vnode.state` to `vnode.state.foo`, picking an appropriate name for `foo` (like maybe `count` if it's a counter's current value).
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
var Counter = {
|
||||
oninit: function(vnode) { vnode.state = 0 },
|
||||
view: function(vnode) {
|
||||
return m(".counter", [
|
||||
m("button", {onclick: function() { vnode.state-- }}, "-")
|
||||
vnode.state,
|
||||
m("button", {onclick: function() { vnode.state++ }}, "+")
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Counter = {
|
||||
oninit: function(vnode) { vnode.state.count = 0 },
|
||||
view: function(vnode) {
|
||||
return m(".counter", [
|
||||
m("button", {onclick: function() { vnode.state.count-- }}, "-")
|
||||
vnode.state.count,
|
||||
m("button", {onclick: function() { vnode.state.count++ }}, "+")
|
||||
])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*When v1.0 first released, class and closure components didn't exist, so it just pulled what it needed from `vnode.tag`. This implementation detail is what allowed you to do it, and some began to rely on it. It was also implied as possible in some places within the docs. Now, things are different, and this makes it a little easier to manage from an implementation standpoint as there's only one reference to state, not two.*
|
||||
|
||||
---
|
||||
|
||||
## Changes to route anchors
|
||||
|
||||
In v1.x, you previously used `oncreate: m.route.link` and, if the link could change, `onupdate: m.route.link` as well, each as lifecycle hooks on the vnode that could be routed with. In v2.x, you now use an [`m.route.Link` component](route.md#mroutelink). The selector can be specified via a `selector:` attribute in case you were using anything other than `m("a", ...)`, options can be specified via `options:`, you can disable it via `disabled:`, and other attributes can be specified inline including `href:` (required). The `selector:` itself can contain be any selector valid as the first argument for `m`, and the attributes `[href=...]` and `[disabled]` can be specified in the selector as well as the normal options.
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
m("a", {
|
||||
href: "/path",
|
||||
oncreate: m.route.link,
|
||||
})
|
||||
|
||||
m("button", {
|
||||
href: "/path",
|
||||
oncreate: m.route.link,
|
||||
})
|
||||
|
||||
m("button.btn[href=/path]", {
|
||||
oncreate: m.route.link,
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m(m.route.Link, {
|
||||
href: "/path",
|
||||
})
|
||||
|
||||
m(m.route.Link, {
|
||||
selector: "button",
|
||||
href: "/path",
|
||||
})
|
||||
|
||||
m(m.route.Link, {
|
||||
selector: "button.btn[href=/path]",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changes to `m.request` errors
|
||||
|
||||
In v1.x, `m.request` parsed errors from JSON calls and assigned the resulting parsed object's properties to the response. So, if you received a response with status 403 and a body of `{"code": "backoff", "timeout": 1000}`, the error would have two extra properties: `err.code = "backoff"` and `err.timeout = 1000`.
|
||||
|
||||
In v2.x, the response is assigned to a `response` property on the result instead, and a `code` property contains the resulting status code. So if you received a response with status 403 and a body of `{"code": "backoff", "timeout": 1000}`, the error would have assigned to it two properties: `err.response = {code: "backoff", timeout: 1000}` and `err.code = 403`.
|
||||
|
||||
---
|
||||
|
||||
## `m.withAttr` removed
|
||||
|
||||
In v1.x, event listeners could use `oninput: m.withAttr("value", func)` and similar. In v2.x, just read them directly from the event's target. It synergized well with streams, but since the idiom of `m.withAttr("value", stream)` was not *nearly* as common as `m.withAttr("value", prop)`, `m.withAttr` lost most of its usefulness and so it was removed.
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
var value = ""
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value(),
|
||||
oninput: m.withAttr("value", function(v) { value = v }),
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
var value = m.stream("")
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value(),
|
||||
oninput: m.withAttr("value", value),
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var value = ""
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value,
|
||||
oninput: function (ev) { value = ev.target.value },
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
var value = m.stream("")
|
||||
|
||||
// In your view
|
||||
m("input[type=text]", {
|
||||
value: value(),
|
||||
oninput: function (ev) { value(ev.target.value) },
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.route.prefix`
|
||||
|
||||
In v1.x, `m.route.prefix` was a function called via `m.route.prefix(prefix)`. It's now a property you set to via `m.route.prefix = prefix`
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
m.route.prefix("/root")
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.route.prefix = "/root"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.request`/`m.jsonp` params and body
|
||||
|
||||
The `data` and `useBody` were refactored into `params`, query parameters interpolated into the URL and appended to the request, and `body`, the body to send in the underlying XHR. This gives you much better control over the actual request sent and allows you to both interpolate into query parameters with `POST` requests and create `GET` requests with bodies.
|
||||
|
||||
`m.jsonp`, having no meaningful "body", just uses `params`, so renaming `data` to `params` is sufficient for that method.
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
m.request("https://example.com/api/user/:id", {
|
||||
method: "GET",
|
||||
data: {id: user.id}
|
||||
})
|
||||
|
||||
m.request("https://example.com/api/user/create", {
|
||||
method: "POST",
|
||||
data: userData
|
||||
})
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
m.request("https://example.com/api/user/:id", {
|
||||
method: "GET",
|
||||
params: {id: user.id}
|
||||
})
|
||||
|
||||
m.request("https://example.com/api/user/create", {
|
||||
method: "POST",
|
||||
body: userData
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Path templates
|
||||
|
||||
In v1.x, there were three separate path template syntaxes that, although they were similar, had 2 separately designed syntaxes and 3 different implementations. It was defined in a fairly ad-hoc way, and parameters weren't generally escaped. Now, everything is either encoded if it's `:key`, raw if it's `:key...`. If things are unexpectedly encoded, use `:path...`. It's that simple.
|
||||
|
||||
Concretely, here's how it affects each method:
|
||||
|
||||
### `m.request` and `m.jsonp` URLs, `m.route.set` paths
|
||||
|
||||
Path components in v2.x are escaped automatically when interpolated. Suppose you invoke `m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})`. Previously, if `user` was `{name: "a/b", id: "c/d"}`, this would set the route to `/user/a%2Fb/photos/c/d`, but it will now set it to `/user/a%2Fb/photos/c%2Fd`. If you deliberately *want* to interpolate a key unescaped, use `:key...` instead.
|
||||
|
||||
Keys in v2.x cannot contain any instances of `.` or `-`. In v1.x, they could contain anything other than `/`.
|
||||
|
||||
Interpolations in inline query strings, like in `/api/search?q=:query`, are not performed in v2.x. Pass those via `params` with appropriate key names instead, without specifying it in the query string.
|
||||
|
||||
### `m.route` route patterns
|
||||
|
||||
Path keys of the form `:key...` return their URL decoded in v1.x, but return the raw URL in v2.x.
|
||||
|
||||
Previously, stuff like `:key.md` were erroneously accepted, with the resulting parameter's value set to `keymd: "..."`. This is no longer the case - the `.md` is part of the pattern now, not the name.
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle call order
|
||||
|
||||
In v1.x, attribute lifecycle hooks on component vnodes were called *before* the component's own lifecycle hooks in all cases. In v2.x, this is the case only for `onbeforeupdate`. So you may need to adjust your code accordingly.
|
||||
|
||||
### v1.x
|
||||
|
||||
```javascript
|
||||
var Comp = {
|
||||
oncreate: function() {
|
||||
console.log("Component oncreate")
|
||||
},
|
||||
view: function() {
|
||||
return m("div")
|
||||
},
|
||||
}
|
||||
|
||||
m.mount(document.body, {
|
||||
view: function() {
|
||||
return m(Comp, {
|
||||
oncreate: function() {
|
||||
console.log("Attrs oncreate")
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Logs:
|
||||
// Attrs oncreate
|
||||
// Component oncreate
|
||||
```
|
||||
|
||||
### v2.x
|
||||
|
||||
```javascript
|
||||
var Comp = {
|
||||
oncreate: function() {
|
||||
console.log("Component oncreate")
|
||||
},
|
||||
view: function() {
|
||||
return m("div")
|
||||
},
|
||||
}
|
||||
|
||||
m.mount(document.body, {
|
||||
view: function() {
|
||||
return m(Comp, {
|
||||
oncreate: function() {
|
||||
console.log("Attrs oncreate")
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Logs:
|
||||
// Component oncreate
|
||||
// Attrs oncreate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `m.redraw` synchronicity
|
||||
|
||||
`m.redraw()` in v2.x is always async. You can specifically request a synchronous redraw via `m.redraw.sync()` provided no redraw is currently occurring.
|
||||
|
||||
---
|
||||
|
||||
## Selector attribute precedence
|
||||
|
||||
In v1.x, selector attributes took precedence over attributes specified in the attributes object. For instance, `m("[a=b]", {a: "c"}).attrs` returned `{a: "b"}`.
|
||||
|
||||
In v2.x, attributes specified in the attributes object take precedence over selector attributes. For instance, `m("[a=b]", {a: "c"}).attrs` returns `{a: "c"}`.
|
||||
|
||||
Note that this is technically reverting to v0.2.x behavior.
|
||||
|
||||
---
|
||||
|
||||
## Children normalization
|
||||
|
||||
In v1.x, component vnode children were normalized like other vnodes. In v2.x, this is no longer the case and you will need to plan accordingly. This does not affect the normalization done on render.
|
||||
|
||||
---
|
||||
|
||||
## `m.request` headers
|
||||
|
||||
In v1.x, Mithril set these two headers on all non-`GET` requests, but only when `useBody` was set to `true` (the default) and the other conditions listed hold:
|
||||
|
||||
- `Content-Type: application/json; charset=utf-8` for requests with JSON bodies
|
||||
- `Accept: application/json, text/*` for requests expecting JSON responses
|
||||
|
||||
In v2.x, Mithril sets the first for all requests with JSON bodies that are `!= null` and omits it by default otherwise, and this is done independent of which method is chosen, including on `GET` requests.
|
||||
|
||||
The first of the two headers, `Content-Type`, will trigger a CORS prefetch as it [is not a CORS-safelisted request header](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) due to the specified content type, and that could introduce new errors depending on how CORS is configured on your server. If you run into issues with this, you may need to override that header in question by passing `headers: {"Content-Type": "text/plain"}`. (The `Accept` header doesn't trigger anything, so you don't need to override that.)
|
||||
|
||||
*The only content types that the Fetch spec lets avoid CORS prefetch checks are `application/x-www-form-urlencoded`, `multipart/form-data`, and `text/plain`. It doesn't allow anything else, and it intentionally disallows JSON.*
|
||||
|
||||
---
|
||||
|
||||
## Query parameters in hash strings in routes
|
||||
|
||||
In v1.x, you could specify query parameters for routes in both the query string and hash string, so `m.route.set("/route?foo=1&bar=2")`, `m.route.set("/route?foo=1#bar=2")`, and `m.route.set("/route#foo=1&bar=2")` were all equivalent and the attributes extracted from them would have been `{foo: "1", bar: "2"}`.
|
||||
|
||||
In v2.x, the contents of hash strings are ignored but preserved. So the attributes extracted from each would be this:
|
||||
|
||||
- `m.route.set("/route?foo=1&bar=2")` → `{foo: "1", bar: "2"}`
|
||||
- `m.route.set("/route?foo=1#bar=2")` → `{foo: "1"}`
|
||||
- `m.route.set("/route#foo=1&bar=2")` → `{}`
|
||||
|
||||
The reason for doing this is because URLs like `https://example.com/#!/route#key` are technically invalid per the [URL spec](https://url.spec.whatwg.org/#url-code-points) and were even invalid per the [RFC that preceded it](https://tools.ietf.org/html/rfc3986#appendix-A), and it's only a quirk of the HTML spec that they're allowed. (The HTML spec should've required IDs and location fragments to be valid URL fragments from the start instead if it wanted to follow spec.)
|
||||
|
||||
Or in short, stop using invalid URLs!
|
||||
|
||||
---
|
||||
|
||||
## Keys
|
||||
|
||||
In v1.x, you could mix keyed and unkeyed vnodes freely. If the first node is keyed, a keyed diff is performed, assuming every element has a key and just ignoring holes as it goes. Otherwise, an iterative diff is performed, and if a node has a key, it would be checked that it didn't change at the same time tags and similar are checked.
|
||||
|
||||
In v2.x, children lists of both fragments and elements must be either all keyed or all unkeyed. Holes are considered unkeyed for the purposes of this check, too - it no longer ignores them.
|
||||
|
||||
If you need to work around it, use the idiom of a fragment containing a single vnode, like `[m("div", {key: whatever})]`.
|
||||
|
||||
---
|
||||
|
||||
## `m.version` removed
|
||||
|
||||
It served little use in general, and you can always add it back yourself. You should prefer feature detection for knowing what features are available, and the v2.x API is designed to better enable this.
|
||||
|
|
@ -26,4 +26,5 @@
|
|||
- Misc
|
||||
- [Framework comparison](framework-comparison.md)
|
||||
- [Change log/Migration](change-log.md)
|
||||
- [v1 Documentation](archive/v1.1.6)
|
||||
- [v1 Documentation](https://mithril.js.org/archive/v1.1.6/)
|
||||
- [v0.2 Documentation](https://mithril.js.org/archive/v0.2.5/)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ var Home = {
|
|||
}
|
||||
|
||||
m.route(document.body, "/home", {
|
||||
"/home": Home, // defines `http://localhost/#!/home`
|
||||
"/home": Home, // defines `https://localhost/#!/home`
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -297,9 +297,9 @@ Routing without page refreshes is made partially possible by the [`history.pushS
|
|||
|
||||
The routing strategy dictates how a library might actually implement routing. There are three general strategies that can be used to implement a SPA routing system, and each has different caveats:
|
||||
|
||||
- `m.route.prefix = '#!'` (default) – Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `http://localhost/#!/page1`
|
||||
- `m.route.prefix = '?'` – Using the querystring. A URL using this strategy typically looks like `http://localhost/?/page1`
|
||||
- `m.route.prefix = ''` – Using the pathname. A URL using this strategy typically looks like `http://localhost/page1`
|
||||
- `m.route.prefix = '#!'` (default) – Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `https://localhost/#!/page1`
|
||||
- `m.route.prefix = '?'` – Using the querystring. A URL using this strategy typically looks like `https://localhost/?/page1`
|
||||
- `m.route.prefix = ''` – Using the pathname. A URL using this strategy typically looks like `https://localhost/page1`
|
||||
|
||||
Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState`, because it can fall back to using `onhashchange`. Use this strategy if you want to keep the hashes purely local.
|
||||
|
||||
|
|
@ -503,8 +503,8 @@ m.route.prefix = "?"
|
|||
m.route.prefix = "#"
|
||||
|
||||
// set to pathname strategy on a non-root URL
|
||||
// e.g. if the app lives under `http://localhost/my-app` and something else
|
||||
// lives under `http://localhost`
|
||||
// e.g. if the app lives under `https://localhost/my-app` and something else
|
||||
// lives under `https://localhost`
|
||||
m.route.prefix = "/my-app"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ The `<!doctype html>` line indicates this is an HTML 5 document. The first `char
|
|||
|
||||
We could create the entire application in a single JavaScript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`.
|
||||
|
||||
There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command:
|
||||
There are many ways to setup a bundler tool, but most are distributed via npm. In fact, most modern JavaScript libraries and tools are distributed that way, including Mithril. To download npm, [install Node.js](https://nodejs.org/en/); npm is installed automatically with it. Once you have Node.js and npm installed, open the command line and run this command:
|
||||
|
||||
```bash
|
||||
npm init -y
|
||||
```
|
||||
|
||||
If NPM is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file.
|
||||
If npm is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ var User = {
|
|||
module.exports = User
|
||||
```
|
||||
|
||||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
|
||||
Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](https://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint.
|
||||
|
||||
*Note: third-party cookies may have to be enabled for the REM endpoint to work.*
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ module.exports = {
|
|||
By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and since its syntax is just JavaScript, it's possible to leverage a lot of JavaScript tooling ecosystem. For example:
|
||||
|
||||
- You can use [Babel](es6.md) to transpile ES6+ to ES5 for IE and to transpile [JSX](jsx.md) (an inline HTML-like syntax extension) to appropriate hyperscript calls.
|
||||
- You can use [ESLint](http://eslint.org/) for easy linting with no special plugins.
|
||||
- You can use [ESLint](https://eslint.org/) for easy linting with no special plugins.
|
||||
- You can use [Terser](https://github.com/terser-js/terser) or [UglifyJS](https://github.com/mishoo/UglifyJS2) (ES5 only) to minify your code easily.
|
||||
- You can use [Istanbul](https://github.com/istanbuljs/nyc) for code coverage.
|
||||
- You can use [TypeScript](https://www.typescriptlang.org/) for easy code analysis. (There are [community-supported type definitions available](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mithril), so you don't need to roll your own.)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ data.title = "' onerror='alert(1)"
|
|||
data.title = "' onmouseover='alert(1)"
|
||||
|
||||
// An attack that does not use JavaScript
|
||||
data.description = "<a href='http://evil.com/login-page-that-steals-passwords.html'>Click here to read more</a>"
|
||||
data.description = "<a href='https://evil.com/login-page-that-steals-passwords.html'>Click here to read more</a>"
|
||||
```
|
||||
|
||||
There are countless non-obvious ways of creating malicious code, so it is highly recommended that you use a [whitelist](https://en.wikipedia.org/wiki/Whitelist) of permitted HTML tags, attributes and attribute values, as opposed to a [blacklist](https://en.wikipedia.org/wiki/Blacklisting) to sanitize the user input. It's also highly recommended that you use a proper HTML parser, instead of regular expressions for sanitization, because regular expressions are extremely difficult to test for all edge cases.
|
||||
|
|
@ -134,7 +134,7 @@ Here's the example snippet for the [Facebook Like button](https://developers.fac
|
|||
|
||||
<!-- Your like button code -->
|
||||
<div class="fb-like"
|
||||
data-href="http://www.your-domain.com/your-page.html"
|
||||
data-href="https://www.your-domain.com/your-page.html"
|
||||
data-layout="standard"
|
||||
data-action="like"
|
||||
data-show-faces="true">
|
||||
|
|
@ -157,7 +157,7 @@ var FacebookLikeButton = {
|
|||
view: function() {
|
||||
return [
|
||||
m("#fb-root"),
|
||||
m("#fb-like[data-href=http://www.your-domain.com/your-page.html][data-layout=standard][data-action=like][data-show-faces=true]")
|
||||
m("#fb-like[data-href=https://www.your-domain.com/your-page.html][data-layout=standard][data-action=like][data-show-faces=true]")
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<div id="root"></div>
|
||||
<script src="../../mithril.js"></script>
|
||||
<script>
|
||||
//http://codepen.io/RobinVr/pen/kEoqc
|
||||
//https://codepen.io/RobinVr/pen/kEoqc
|
||||
var ring = m(".top",
|
||||
m(".middle",
|
||||
m("svg[preserveAspectRatio='xMidYMid meet'][version='1.1'][viewBox='0 0 584 586'][xmlns='http://www.w3.org/2000/svg'][xmlns:sketch='http://www.bohemiancoding.com/sketch/ns'][xmlns:xlink='http://www.w3.org/1999/xlink']", [
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ var Header = {
|
|||
m("p.head_links", [
|
||||
m("a[href='https://github.com/koglerjs/threaditjs/tree/master/examples/mithril']", "Source"),
|
||||
" | ",
|
||||
m("a[href='http://threaditjs.com']", "ThreaditJS Home"),
|
||||
m("a[href='https://threaditjs.com']", "ThreaditJS Home"),
|
||||
]),
|
||||
m("h2", [
|
||||
m(m.route.Link, {href: "/"}, "ThreaditJS: Mithril"),
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Mithril • ThreadIt.js</title>
|
||||
<link rel="stylesheet" href="http://threaditjs.com/reset.css"/>
|
||||
<link rel="stylesheet" href="http://threaditjs.com/shared.css"/>
|
||||
<link rel="stylesheet" href="https://threaditjs.com/reset.css"/>
|
||||
<link rel="stylesheet" href="https://threaditjs.com/shared.css"/>
|
||||
<link rel="stylesheet" href="colors.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<section id="todoapp"></section>
|
||||
<footer id="info">
|
||||
<p>Double-click to edit a todo</p>
|
||||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
|
||||
<p>Part of <a href="https://todomvc.com">TodoMVC</a></p>
|
||||
</footer>
|
||||
<script src="../../mithril.js"></script>
|
||||
<script src="todomvc.js"></script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
ospec [](https://www.npmjs.com/package/ospec) [](https://www.npmjs.com/package/ospec)
|
||||
ospec [](https://www.npmjs.com/package/ospec) [](https://www.npmjs.com/package/ospec)
|
||||
=====
|
||||
|
||||
[About](#about) | [Usage](#usage) | [CLI](#command-line-interface) | [API](#api) | [Goals](#goals)
|
||||
|
|
@ -371,7 +371,7 @@ ospec '**/*.test.js' --ignore 'folder1/**' --require esm ./my-file.js
|
|||
|
||||
### Run ospec directly from the command line:
|
||||
|
||||
ospec comes with an executable named `ospec`. NPM auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe.
|
||||
ospec comes with an executable named `ospec`. npm auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe.
|
||||
|
||||
ospec doesn't work when installed globally (`npm install -g`). Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mithril-stream [](https://www.npmjs.com/package/mithril-stream) [](https://www.npmjs.com/package/mithril-stream)
|
||||
mithril-stream [](https://www.npmjs.com/package/mithril-stream) [](https://www.npmjs.com/package/mithril-stream)
|
||||
==============
|
||||
|
||||
Mithril's `m.stream` as a standalone module.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module.exports = function(options) {
|
|||
var spymap = []
|
||||
|
||||
// This way I'm not also implementing a partial `URL` polyfill. Based on the
|
||||
// regexp at http://urlregex.com/, but adapted to allow relative URLs and
|
||||
// regexp at https://urlregex.com/, but adapted to allow relative URLs and
|
||||
// care only about HTTP(S) URLs.
|
||||
var urlHash = "#[?!/+=&;%@.\\w_-]*"
|
||||
var urlQuery = "\\?[!/+=&;%@.\\w_-]*"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue