Merge branch 'next' of github.com:lhorie/mithril.js into next

This commit is contained in:
Leo Horie 2016-01-27 11:22:26 -05:00
commit 6b7ac83dde
12 changed files with 63 additions and 41 deletions

View file

@ -10,3 +10,4 @@ docs/layout/lib
# TODO: These are temporary, and need to be eventually enabled.
mithril.js
tests

View file

@ -18,7 +18,7 @@ Mithril is a client-side MVC framework - a tool to organize code in a way that i
### Light-weight
- Only 7kb gzipped, no dependencies
- Only 7.8 kB gzipped, no dependencies
- Small API, small learning curve
### Robust

View file

@ -73,6 +73,7 @@ window.templateConverter = (function () {
each(Object.keys(el.attrs).sort(), function (attrName) {
if (attrName === "style") return
if(el.attrs[attrName]===undefined) return;
virtual += "[" + attrName + "='"
virtual += el.attrs[attrName].replace(/'/g, "\\'") + "']"
})

View file

@ -6,7 +6,7 @@ This page aims to provide a comparison between Mithril and some of the most wide
### Code Size
One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is around 7kb gzipped and has no dependencies on other libraries.
One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is around 7.8 kB gzipped and has no dependencies on other libraries.
Note that while a small gzipped size can look appealing, that number is often used to "hide the weight" of the uncompressed code: remember that the decompressed Javascript still needs to be parsed and evaluated on every page load, and this cost (which can be in the dozens of milliseconds range for some frameworks in some browsers) cannot be cached.

View file

@ -4,7 +4,7 @@
Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called **M**odel), a UI layer (called **V**iew), and a glue layer (called **C**ontroller)
Mithril is around 7kb gzipped thanks to its [small, focused, API](mithril.md). It provides a templating engine with a virtual DOM diff implementation for performant rendering, utilities for high-level modelling via functional composition, as well as support for routing and componentization.
Mithril is around 7.8 kB gzipped thanks to its [small, focused, API](mithril.md). It provides a templating engine with a virtual DOM diff implementation for performant rendering, utilities for high-level modelling via functional composition, as well as support for routing and componentization.
The goal of the framework is to make application code discoverable, readable and maintainable, and hopefully help you become an even better developer.

View file

@ -50,7 +50,7 @@
<div class="feature col(4,4,12)">
<h2>Light-weight</h2>
<ul>
<li>Only 7kb gzipped, no dependencies</li>
<li>Only 7.8 kB gzipped, no dependencies</li>
<li>Small API, small learning curve</li>
</ul>
</div>

View file

@ -43,7 +43,7 @@ var MyComponent = {
m.mount(document.body, MyComponent) // renders <h1>Hello</h1> into <body>
```
The optional `controller` function creates an object that may be used in the following recommended ways:
The optional `controller` function creates an object that may be used in the following recommended ways:
- It can contain methods meant to be called by a `view`.
- It can call model methods directly or from methods inside the resulting object.
@ -165,8 +165,8 @@ var MyComponent = {
m.render(document.body, [
//the two lines below are equivalent
m(component, {data: "world"}),
m.component(component, {data: "world"})
m(MyComponent, {data: "world"}),
m.component(MyComponent, {data: "world"})
])
```
@ -211,7 +211,7 @@ var App = {
view: function() {
return m(".app", [
m("h1", "My App"),
//nested component
m.component(MyComponent, {message: "Hello"})
])
@ -246,7 +246,7 @@ var App = {
return m(".app", [
//pressing the button reverses the list
m("button[type=button]", {onclick: function() {ctrl.data.reverse()}}, "My App"),
ctrl.data.map(function(item) {
//the key ensures the components aren't recreated from scratch, if they merely exchanged places
return m.component(MyComponent, {message: "Hello " + item, key: item})
@ -327,12 +327,12 @@ var ctrl = new TemperatureConverter.controller();
assert(ctrl.kelvinToCelsius(273.15) == 0)
//test the template
var tpl = TemperatureConverter.view(null, {value: 273.15})
var tpl = TemperatureConverter.view(ctrl, {value: 273.15})
assert(tpl.children[1] == 0)
//test with real DOM
var testRoot = document.createElement("div")
m.render(testRoot, TemperatureConverter.view(null, {value: 273.15}))
m.render(testRoot, TemperatureConverter.view(ctrl, {value: 273.15}))
assert(testRoot.innerHTML.indexOf("celsius:0") > -1)
```
@ -451,7 +451,7 @@ var component = {
var unsaved = m.prop(false)
return {
unsaved: unsaved,
onunload: function(e) {
if (unsaved()) {
e.preventDefault()
@ -538,7 +538,7 @@ There are a few other technical caveats when nesting components:
2. Nested components cannot change `m.redraw.strategy` from the controller constructor (but they can from event handlers). It's recommended that you use the [`ctx.retain`](mithril.md#persisting-dom-elements-across-route-changes) flag instead of changing the redraw strategy in controller constructors.
3. The root DOM element in a component's view must not be changed during the lifecycle of the component, otherwise undefined behavior will occur. In other words, don't do this:
```javascript
var MyComponent = {
view: function() {
@ -590,7 +590,7 @@ where:
- **Component component**
A component is supposed to be an Object with two keys: `controller` and `view`. Each of these should point to a Javascript function. If a contoller is not specified, Mithril will automatically create an empty controller function.
A component is supposed to be an Object with two keys: `controller` and `view`. Each of these should point to a Javascript function. If a controller is not specified, Mithril will automatically create an empty controller function.
- **Object attributes**
@ -603,4 +603,3 @@ where:
- **returns Component parameterizedComponent**
A component with arguments bound

12
mithril.d.ts vendored
View file

@ -250,7 +250,7 @@ declare module _mithril {
<T extends MithrilController>(
rootElement: Element,
defaultRoute: string,
routes: MithrilRoutes<T>
routes: MithrilRoutes
): void;
/**
@ -534,6 +534,12 @@ declare module _mithril {
* @see MithrilElementConfig
*/
config?: MithrilElementConfig;
/**
* Any other virtual element properties including attributes and
* event handlers
*/
[property: string]: any;
}
/**
@ -668,12 +674,12 @@ declare module _mithril {
/**
* This represents a key-value mapping linking routes to components.
*/
interface MithrilRoutes<T extends MithrilController> {
interface MithrilRoutes {
/**
* The key represents the route. The value represents the corresponding
* component.
*/
[key: string]: MithrilComponent<T>;
[key: string]: MithrilComponent<MithrilController>;
}
/**

View file

@ -553,7 +553,7 @@ var m = (function app(window, undefined) {
if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) {
cachedAttrs[attrName] = dataAttr;
try {
//`config` isn't a real attributes, so ignore it
//`config` isn't a real attribute, so ignore it
if (attrName === "config" || attrName === "key") continue;
//hook event handlers to the auto-redrawing system
else if (isFunction(dataAttr) && attrName.slice(0, 2) === "on") {
@ -578,7 +578,11 @@ var m = (function app(window, undefined) {
//- when using CSS selectors (e.g. `m("[style='']")`), style is used as a string, but it's an object in js
else if (attrName in node && attrName !== "list" && attrName !== "style" && attrName !== "form" && attrName !== "type" && attrName !== "width" && attrName !== "height") {
//#348 don't set the value if not needed otherwise cursor placement breaks in Chrome
if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr;
try {
if (tag !== "input" || node[attrName] !== dataAttr) node[attrName] = dataAttr;
} catch (e) {
node.setAttribute(attrName, dataAttr);
}
}
else node.setAttribute(attrName, dataAttr);
}
@ -621,6 +625,13 @@ var m = (function app(window, undefined) {
else if (cached.children.tag) unload(cached.children);
}
}
function appendTextFragment(parentElement, data) {
try {
parentElement.appendChild($document.createRange().createContextualFragment(data));
} catch (e) {
parentElement.insertAdjacentHTML("beforeend", data);
}
}
function injectHTML(parentElement, index, data) {
var nextSibling = parentElement.childNodes[index];
if (nextSibling) {
@ -634,10 +645,7 @@ var m = (function app(window, undefined) {
else nextSibling.insertAdjacentHTML("beforebegin", data);
}
else {
if (window.Range && window.Range.prototype.createContextualFragment) {
parentElement.appendChild($document.createRange().createContextualFragment(data));
}
else parentElement.insertAdjacentHTML("beforeend", data);
appendTextFragment(parentElement, data);
}
var nodes = [];
while (parentElement.childNodes[index] !== nextSibling) {

View file

@ -333,12 +333,11 @@ describe("m.mount()", function () {
})
}))
list.pop()
list = []
refresh(true)
// TODO: These fail.
// expect(spies[1]).to.have.been.called
// expect(spies[2]).to.have.been.called
expect(spies[1]).to.have.been.called
expect(spies[2]).to.have.been.called
expect(spies[3]).to.have.been.called
})
@ -374,17 +373,15 @@ describe("m.mount()", function () {
})
}))
list.pop()
list = []
refresh(true)
// TODO: These fail.
// expect(spies1[1]).to.have.been.called
// expect(spies1[2]).to.have.been.called
expect(spies1[1]).to.have.been.called
expect(spies1[2]).to.have.been.called
expect(spies1[3]).to.have.been.called
// TODO: These fail.
// expect(spies2[1]).to.have.been.called
// expect(spies2[2]).to.have.been.called
expect(spies2[1]).to.have.been.called
expect(spies2[2]).to.have.been.called
expect(spies2[3]).to.have.been.called
})

View file

@ -20,19 +20,20 @@ describe("m.trust()", function () {
// FIXME: implement document.createRange().createContextualFragment() in the
// mock window for these tests
dom(function () {
xit("isn't escaped in m.render()", function () {
it("isn't escaped in m.render()", function () {
var root = document.createElement("div")
m.render(root, m("div", "a", m.trust("&amp;"), "b"))
expect(root.childNodes[0].innerHTML).to.equal("a&amp;b")
})
xit("works with mixed trusted content in div", function () {
it("works with mixed trusted content in div", function () {
var root = document.createElement("div")
m.render(root, [m.trust("<p>1</p><p>2</p>"), m("i", "foo")])
expect(root.childNodes[2].tagName).to.equal("I")
})
xit("works with mixed trusted content in text nodes", function () {
it("works with mixed trusted content in text nodes", function () {
var root = document.createElement("div")
m.render(root, [
m.trust("<p>1</p>123<p>2</p>"),
@ -41,9 +42,7 @@ describe("m.trust()", function () {
expect(root.childNodes[3].tagName).to.equal("I")
})
// FIXME: this is a bug (trusted string's contents rendered as just
// textual contents)
xit("works with mixed trusted content in td", function () {
it("works with mixed trusted content in td", function () {
var root = document.createElement("table")
root.appendChild(root = document.createElement("tr"))
@ -54,5 +53,16 @@ describe("m.trust()", function () {
expect(root.childNodes[2].tagName).to.equal("TD")
})
it("works with trusted content in div", function () {
var root = document.createElement("div")
m.render(root, m('div', [
m('p', '&copy;'),
m('p', m.trust('&copy;')),
m.trust('&copy;'),
]))
expect(root.innerHTML).to.equal("<div><p>&amp;copy;</p><p>©</p>©</div>")
})
})
})

0
tests/trust-test.html Normal file
View file