Merge remote-tracking branch 'origin/next' into next
This commit is contained in:
commit
dcbb82f7d2
9 changed files with 1008 additions and 259 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
A Javascript Framework for Building Brilliant Applications
|
A Javascript Framework for Building Brilliant Applications
|
||||||
|
|
||||||
See the [website](http://lhorie.github.io/mithril) for documentation
|
See the [website](http://mithril.js.org) for documentation
|
||||||
|
|
||||||
There's also a [blog](http://lhorie.github.io/mithril-blog) and a [mailing list](https://groups.google.com/forum/#!forum/mithriljs)
|
There's also a [blog](http://lhorie.github.io/mithril-blog) and a [mailing list](https://groups.google.com/forum/#!forum/mithriljs)
|
||||||
|
|
||||||
|
|
@ -74,6 +74,6 @@ m.module(document.getElementById("example"), app);
|
||||||
|
|
||||||
### Learn more
|
### Learn more
|
||||||
|
|
||||||
- [Tutorial](http://lhorie.github.io/mithril/getting-started.html)
|
- [Tutorial](http://mithril.js.org/getting-started.html)
|
||||||
- [Differences from Other Frameworks](http://lhorie.github.io/mithril/comparison.html)
|
- [Differences from Other Frameworks](http://mithril.js.org/comparison.html)
|
||||||
- [Benchmarks](http://lhorie.github.io/mithril/benchmarks.html)
|
- [Benchmarks](http://mithril.js.org/benchmarks.html)
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ var ContactsWidget = {
|
||||||
//ContactList no longer calls `Contact.save`
|
//ContactList no longer calls `Contact.save`
|
||||||
var ContactForm = {
|
var ContactForm = {
|
||||||
controller: function(args) {
|
controller: function(args) {
|
||||||
ctrl = this
|
var ctrl = this
|
||||||
ctrl.contact = m.prop(new Contact())
|
ctrl.contact = m.prop(new Contact())
|
||||||
ctrl.save = function(contact) {
|
ctrl.save = function(contact) {
|
||||||
Observable.trigger("saveContact", {contact: contact})
|
Observable.trigger("saveContact", {contact: contact})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
## Web Services
|
## Web Services
|
||||||
|
|
||||||
Mithril provides a high-level utility for working with web services, which allows writing asynchronous code relatively procedurally.
|
Mithril allows writing asynchronous code in a procedural way through a high-level utility for working with web services.
|
||||||
|
|
||||||
It provides a number of useful features out of the box:
|
This utility provides a number of useful features out of the box:
|
||||||
|
|
||||||
- The ability to get an early reference to a container that will hold the asynchronous response
|
- The ability to get an early reference to a container that will hold the asynchronous response
|
||||||
- The ability to queue operations to be performed after the asynchronous request completes
|
- The ability to queue operations to be performed after the asynchronous request completes
|
||||||
|
|
|
||||||
858
mithril.d.ts
vendored
858
mithril.d.ts
vendored
|
|
@ -1,166 +1,884 @@
|
||||||
//Mithril type definitions for Typescript
|
// Mithril type definitions for Typescript
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the module containing all the types/declarations/etc. for Mithril
|
||||||
|
*/
|
||||||
declare module _mithril {
|
declare module _mithril {
|
||||||
interface MithrilStatic {
|
interface MithrilStatic {
|
||||||
|
/**
|
||||||
|
* Creates a virtual element for use with m.render, m.mount, etc.
|
||||||
|
*
|
||||||
|
* @param selector A simple CSS selector. May include SVG tags. Nested
|
||||||
|
* selectors are not supported.
|
||||||
|
* @param attributes Attributes to add. Any DOM attribute may be used
|
||||||
|
* as an attribute, although innerHTML and the like may be overwritten
|
||||||
|
* silently.
|
||||||
|
* @param children Child elements, components, and text to add.
|
||||||
|
* @return A virtual element.
|
||||||
|
*
|
||||||
|
* @see m.render
|
||||||
|
* @see m.mount
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
selector: string,
|
||||||
|
attributes: MithrilAttributes,
|
||||||
|
...children: Array<string |
|
||||||
|
MithrilVirtualElement<T> |
|
||||||
|
MithrilComponent<T>>
|
||||||
|
): MithrilVirtualElement<T>;
|
||||||
|
|
||||||
<T extends MithrilController>(selector: string, attributes: MithrilAttributes, ...children: Array<string|MithrilVirtualElement|MithrilComponent<T>>): MithrilVirtualElement;
|
/**
|
||||||
<T extends MithrilController>(selector: string, ...children: Array<string|MithrilVirtualElement|MithrilComponent<T>>): MithrilVirtualElement;
|
* Creates a virtual element for use with m.render, m.mount, etc.
|
||||||
|
*
|
||||||
|
* @param selector A simple CSS selector. Nested selectors are not
|
||||||
|
* supported.
|
||||||
|
* @param children Child elements, components, and text to add.
|
||||||
|
* @return A virtual element.
|
||||||
|
*
|
||||||
|
* @see m.render
|
||||||
|
* @see m.mount
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
selector: string,
|
||||||
|
...children: Array<string |
|
||||||
|
MithrilVirtualElement<T> |
|
||||||
|
MithrilComponent<T>>
|
||||||
|
): MithrilVirtualElement<T>;
|
||||||
|
|
||||||
prop<T>(promise: MithrilPromise<T>) : MithrilPromiseProperty<T>;
|
/**
|
||||||
prop<T>(value: T): MithrilProperty<T>;
|
* Initializes a component for use with m.render, m.mount, etc.
|
||||||
prop(): MithrilProperty<Object>; // might be that this should be Property<any>
|
* Shorthand for m.component.
|
||||||
|
*
|
||||||
|
* @param selector A component.
|
||||||
|
* @param args Arguments to optionally pass to the component.
|
||||||
|
* @return A component.
|
||||||
|
*
|
||||||
|
* @see m.render
|
||||||
|
* @see m.mount
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
component: MithrilComponent<T>,
|
||||||
|
...args: any[]
|
||||||
|
): MithrilComponent<T>;
|
||||||
|
|
||||||
withAttr(property: string, callback: (value: any) => void): (e: MithrilEvent) => any;
|
/**
|
||||||
|
* Creates a getter-setter function that wraps a Mithril promise. Useful
|
||||||
|
* for uniform data access, m.withAttr, etc.
|
||||||
|
*
|
||||||
|
* @param promise A thennable to initialize the property with. It may
|
||||||
|
* optionally be a Mithril promise.
|
||||||
|
* @return A getter-setter function wrapping the promise.
|
||||||
|
*
|
||||||
|
* @see m.withAttr
|
||||||
|
*/
|
||||||
|
prop<T>(promise: Thennable<T>) : MithrilPromiseProperty<T>;
|
||||||
|
|
||||||
module<T extends MithrilController>(rootElement: Node, component: MithrilComponent<T>): T;
|
/**
|
||||||
module<T extends MithrilController>(rootElement: Node): T;
|
* Creates a getter-setter function that wraps a simple value. Useful
|
||||||
mount<T extends MithrilController>(rootElement: Node, component: MithrilComponent<T>): T;
|
* for uniform data access, m.withAttr, etc.
|
||||||
mount<T extends MithrilController>(rootElement: Node): T;
|
*
|
||||||
|
* @param value A value to initialize the property with
|
||||||
|
* @return A getter-setter function wrapping the value.
|
||||||
|
*
|
||||||
|
* @see m.withAttr
|
||||||
|
*/
|
||||||
|
prop<T>(value: T): MithrilBasicProperty<T>;
|
||||||
|
|
||||||
component<T extends MithrilController>(component: MithrilComponent<T>, ...args: Array<any>): MithrilComponent<T>
|
/**
|
||||||
|
* Creates a getter-setter function that wraps a simple value. Useful
|
||||||
|
* for uniform data access, m.withAttr, etc.
|
||||||
|
*
|
||||||
|
* @return A getter-setter function wrapping the value.
|
||||||
|
*
|
||||||
|
* @see m.withAttr
|
||||||
|
*/
|
||||||
|
prop<T>(): MithrilBasicProperty<T>;
|
||||||
|
|
||||||
trust(html: string): string;
|
/**
|
||||||
|
* Returns a event handler that can be bound to an element, firing with
|
||||||
|
* the specified property.
|
||||||
|
*
|
||||||
|
* @param property The property to get from the event.
|
||||||
|
* @param callback The handler to use the value from the event.
|
||||||
|
* @return A function suitable for listening to an event.
|
||||||
|
*/
|
||||||
|
withAttr(
|
||||||
|
property: string,
|
||||||
|
callback: (value: any) => void
|
||||||
|
): (e: Event) => any;
|
||||||
|
|
||||||
render(rootElement: Element|HTMLDocument): void;
|
/**
|
||||||
render(rootElement: Element|HTMLDocument, children: MithrilVirtualElement, forceRecreation?: boolean): void;
|
* @deprecated Use m.mount instead
|
||||||
render(rootElement: Element|HTMLDocument, children: MithrilVirtualElement[], forceRecreation?: boolean): void;
|
*/
|
||||||
|
module<T extends MithrilController>(
|
||||||
|
rootElement: Node,
|
||||||
|
component: MithrilComponent<T>
|
||||||
|
): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounts a component to a base DOM node.
|
||||||
|
*
|
||||||
|
* @param rootElement The base node.
|
||||||
|
* @param component The component to mount.
|
||||||
|
* @return An instance of the top-level component's controller
|
||||||
|
*/
|
||||||
|
mount<T extends MithrilController>(
|
||||||
|
rootElement: Node,
|
||||||
|
component: MithrilComponent<T>
|
||||||
|
): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a component for use with m.render, m.mount, etc.
|
||||||
|
*
|
||||||
|
* @param selector A component.
|
||||||
|
* @param args Arguments to optionally pass to the component.
|
||||||
|
* @return A component.
|
||||||
|
*
|
||||||
|
* @see m.render
|
||||||
|
* @see m.mount
|
||||||
|
* @see m
|
||||||
|
*/
|
||||||
|
component<T extends MithrilController>(
|
||||||
|
component: MithrilComponent<T>,
|
||||||
|
...args: any[]
|
||||||
|
): MithrilComponent<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trust this string of HTML.
|
||||||
|
*
|
||||||
|
* @param html The HTML to trust
|
||||||
|
* @return A String object instance with an added internal flag to mark
|
||||||
|
* it as trusted.
|
||||||
|
*/
|
||||||
|
trust(html: string): MithrilTrustedString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a virtual DOM tree.
|
||||||
|
*
|
||||||
|
* @param rootElement The base element/node to render the tree from.
|
||||||
|
* @param children One or more child nodes to add to the tree.
|
||||||
|
* @param forceRecreation If true, overwrite the entire tree without
|
||||||
|
* diffing against it.
|
||||||
|
*/
|
||||||
|
render<T extends MithrilController>(
|
||||||
|
rootElement: Element,
|
||||||
|
children: MithrilVirtualElement<T>|MithrilVirtualElement<T>[],
|
||||||
|
forceRecreation?: boolean
|
||||||
|
): void;
|
||||||
|
|
||||||
redraw: {
|
redraw: {
|
||||||
|
/**
|
||||||
|
* Force a redraw the active component. It redraws asynchronously by
|
||||||
|
* default to allow for simultaneous events to run before redrawing,
|
||||||
|
* such as the event combination keypress + input frequently used for
|
||||||
|
* input.
|
||||||
|
*
|
||||||
|
* @param force If true, redraw synchronously.
|
||||||
|
*/
|
||||||
(force?: boolean): void;
|
(force?: boolean): void;
|
||||||
strategy: MithrilProperty<string>;
|
|
||||||
|
strategy: {
|
||||||
|
/**
|
||||||
|
* Gets the current redraw strategy, which returns one of the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* "all" - recreates the DOM tree from scratch
|
||||||
|
* "diff" - recreates the DOM tree from scratch
|
||||||
|
* "none" - leaves the DOM tree intact
|
||||||
|
*
|
||||||
|
* This is useful for event handlers, which may want to cancel
|
||||||
|
* the next redraw if the event doesn't update the UI.
|
||||||
|
*
|
||||||
|
* @return The current strategy
|
||||||
|
*/
|
||||||
|
(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current redraw strategy. The parameter must be one of
|
||||||
|
* the following values:
|
||||||
|
*
|
||||||
|
* "all" - recreates the DOM tree from scratch
|
||||||
|
* "diff" - recreates the DOM tree from scratch
|
||||||
|
* "none" - leaves the DOM tree intact
|
||||||
|
*
|
||||||
|
* This is useful for event handlers, which may want to cancel
|
||||||
|
* the next redraw if the event doesn't update the UI.
|
||||||
|
*
|
||||||
|
* @param value The value to set
|
||||||
|
* @return The new strategy
|
||||||
|
*/
|
||||||
|
(value: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* Implementation detail - it's a MithrilBasicProperty instance
|
||||||
|
*/
|
||||||
|
toJSON(): string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
route: {
|
route: {
|
||||||
<T extends MithrilController>(rootElement: HTMLDocument, defaultRoute: string, routes: MithrilRoutes<T>): void;
|
/**
|
||||||
<T extends MithrilController>(rootElement: Element, defaultRoute: string, routes: MithrilRoutes<T>): void;
|
* Enable routing, mounting a controller based on the route. It
|
||||||
|
* automatically mounts the components for you, starting with the one
|
||||||
|
* specified by the default route.
|
||||||
|
*
|
||||||
|
* @param rootElement The element to mount the active controller to.
|
||||||
|
* @param defaultRoute The route to start with.
|
||||||
|
* @param routes A key-value mapping of pathname to controller.
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
rootElement: Element,
|
||||||
|
defaultRoute: string,
|
||||||
|
routes: MithrilRoutes<T>
|
||||||
|
): void;
|
||||||
|
|
||||||
(element: Element, isInitialized: boolean, context: Object, vdom: Object): void;
|
/**
|
||||||
|
* This allows m.route to be used as the `config` attribute for a
|
||||||
|
* virtual element, particularly useful for cases like this:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // Note that the '#' is not required in `href`, thanks to the
|
||||||
|
* `config` setting.
|
||||||
|
* m("a[href='/dashboard/alicesmith']", {config: m.route});
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
element: Element,
|
||||||
|
isInitialized: boolean,
|
||||||
|
context?: MithrilContext,
|
||||||
|
vdom?: MithrilVirtualElement<T>
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Programmatically redirect to another route.
|
||||||
|
*
|
||||||
|
* @param path The route to go to.
|
||||||
|
* @param params Parameters to pass as a query string.
|
||||||
|
* @param shouldReplaceHistory Whether to replace the current history
|
||||||
|
* instead of adding a new one.
|
||||||
|
*/
|
||||||
(path: string, params?: any, shouldReplaceHistory?: boolean): void;
|
(path: string, params?: any, shouldReplaceHistory?: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current route.
|
||||||
|
*
|
||||||
|
* @return The current route.
|
||||||
|
*/
|
||||||
(): string;
|
(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a route parameter.
|
||||||
|
*
|
||||||
|
* @param key The key to get.
|
||||||
|
* @return The value associated with the parameter key.
|
||||||
|
*/
|
||||||
param(key: string): string;
|
param(key: string): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current routing mode. This may be changed before calling
|
||||||
|
* m.route to change the part of the URL used to perform the routing.
|
||||||
|
*
|
||||||
|
* The value can be set to one of the following, defaulting to
|
||||||
|
* "hash":
|
||||||
|
*
|
||||||
|
* "search" - Uses the query string. This allows for named anchors to
|
||||||
|
* work on the page, but changes cause IE8 and lower to refresh the
|
||||||
|
* page.
|
||||||
|
*
|
||||||
|
* "hash" - Uses the hash. This is the only routing mode that does
|
||||||
|
* not cause page refreshes on any browser, but it does not support
|
||||||
|
* named anchors.
|
||||||
|
*
|
||||||
|
* "pathname" - Uses the URL pathname. This requires server-side
|
||||||
|
* setup to support bookmarking and page refreshes. It always causes
|
||||||
|
* page refreshes on IE8 and lower. Note that this requires that the
|
||||||
|
* application to be run from the root of the URL.
|
||||||
|
*/
|
||||||
mode: string;
|
mode: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize an object into a query string.
|
||||||
|
*
|
||||||
|
* @param data The data to serialize.
|
||||||
|
* @return The serialized string.
|
||||||
|
*/
|
||||||
buildQueryString(data: Object): String
|
buildQueryString(data: Object): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a query string into an object.
|
||||||
|
*
|
||||||
|
* @param data The data to parse.
|
||||||
|
* @return The parsed object data.
|
||||||
|
*/
|
||||||
parseQueryString(data: String): Object
|
parseQueryString(data: String): Object
|
||||||
}
|
}
|
||||||
|
|
||||||
request<T>(options: MithrilXHROptions): MithrilPromise<T>;
|
/**
|
||||||
|
* Send a request to a server to server. Note that the `url` option is
|
||||||
|
* required.
|
||||||
|
*
|
||||||
|
* @param options The options to use
|
||||||
|
* @return A promise to the returned data for "GET" requests, or a void
|
||||||
|
* promise for any other request type.
|
||||||
|
*
|
||||||
|
* @see MithrilXHROptions for the available options.
|
||||||
|
*/
|
||||||
|
request<T>(options: MithrilXHROptions<T>): MithrilPromise<T>;
|
||||||
|
|
||||||
deferred: {
|
deferred: {
|
||||||
onerror(e: Error): void;
|
/**
|
||||||
|
* Create a Mithril deferred object. It behaves synchronously if
|
||||||
|
* possible, an intentional deviation from Promises/A+. Note that
|
||||||
|
* deferreds are completely separate from the redrawing system, and
|
||||||
|
* never trigger a redraw on their own.
|
||||||
|
*
|
||||||
|
* @return A new Mithril deferred instance.
|
||||||
|
*
|
||||||
|
* @see m.deferred.onerror for the error callback called for Error
|
||||||
|
* subclasses
|
||||||
|
*/
|
||||||
<T>(): MithrilDeferred<T>;
|
<T>(): MithrilDeferred<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback for all uncaught native Error subclasses in deferreds.
|
||||||
|
* This defaults to synchronously rethrowing all errors, a deviation
|
||||||
|
* from Promises/A+, but the behavior is configurable. To restore
|
||||||
|
* Promises/A+-compatible behavior. simply set this to a no-op.
|
||||||
|
*/
|
||||||
|
onerror(e: Error): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
sync<T>(promises: MithrilPromise<T>[]): MithrilPromise<T[]>;
|
/**
|
||||||
|
* Takes a list of promises or thennables and returns a Mithril promise
|
||||||
|
* that resolves once all in the list are resolved, or rejects if any of
|
||||||
|
* them reject.
|
||||||
|
*
|
||||||
|
* @param promises A list of promises to try to resolve.
|
||||||
|
* @return A promise that resolves to all the promises if all resolve, or
|
||||||
|
* rejects with the error contained in the first rejection.
|
||||||
|
*/
|
||||||
|
sync<T>(promises: Thennable<T>[]): MithrilPromise<T[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this and endComputation if your views aren't redrawing after
|
||||||
|
* calls to third-party libraries. For integrating asynchronous code,
|
||||||
|
* this should be called before any asynchronous work is done. For
|
||||||
|
* synchronous code, this should be called at the beginning of the
|
||||||
|
* problematic segment. Note that these calls must be balanced, much like
|
||||||
|
* braces and parentheses. This is mostly used internally. Prefer
|
||||||
|
* m.redraw where possible, especially when making repeated calls.
|
||||||
|
*
|
||||||
|
* @see endComputation
|
||||||
|
* @see m.render
|
||||||
|
*/
|
||||||
startComputation(): void;
|
startComputation(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use startComputation and this if your views aren't redrawing after
|
||||||
|
* calls to third-party libraries. For integrating asynchronous code,
|
||||||
|
* this should be called after all asynchronous work completes. For
|
||||||
|
* synchronous code, this should be called at the end of the problematic
|
||||||
|
* segment. Note that these calls must be balanced, much like braces and
|
||||||
|
* parentheses. This is mostly used internally. Prefer m.redraw where
|
||||||
|
* possible, especially when making repeated calls.
|
||||||
|
*
|
||||||
|
* @see startComputation
|
||||||
|
* @see m.render
|
||||||
|
*/
|
||||||
endComputation(): void;
|
endComputation(): void;
|
||||||
|
|
||||||
// For test suite
|
/**
|
||||||
deps: {
|
* This overwrites the internal version of window used by Mithril.
|
||||||
(mockWindow: Window): Window;
|
* It's mostly useful for testing, and is also used internally by
|
||||||
factory: Object;
|
* Mithril to test itself. By default Mithril uses `window` for the
|
||||||
}
|
* dependency.
|
||||||
|
*
|
||||||
|
* @param mockWindow The mock to use for the window.
|
||||||
|
* @return The mock that was passed in.
|
||||||
|
*/
|
||||||
|
deps(mockWindow: Window): Window;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MithrilVirtualElement {
|
interface MithrilTrustedString extends String {
|
||||||
|
/** @private Implementation detail. Don't depend on it. */
|
||||||
|
$trusted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface for a virtual element. It's best to consider this immutable
|
||||||
|
* for most use cases.
|
||||||
|
*
|
||||||
|
* @see m
|
||||||
|
*/
|
||||||
|
interface MithrilVirtualElement<T extends MithrilController> {
|
||||||
|
/**
|
||||||
|
* A key to optionally associate with this element.
|
||||||
|
*/
|
||||||
key?: number;
|
key?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tag name of this element.
|
||||||
|
*/
|
||||||
tag?: string;
|
tag?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes of this element.
|
||||||
|
*/
|
||||||
attrs?: MithrilAttributes;
|
attrs?: MithrilAttributes;
|
||||||
children?: any[];
|
|
||||||
|
/**
|
||||||
|
* The children of this element.
|
||||||
|
*/
|
||||||
|
children?: Array<string|MithrilVirtualElement<T>|MithrilComponent<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration function for an element
|
/**
|
||||||
|
* An event passed by Mithril to unload event handlers.
|
||||||
|
*/
|
||||||
|
interface MithrilEvent {
|
||||||
|
/**
|
||||||
|
* Prevent the default behavior of scrolling the page and updating the
|
||||||
|
* URL on next route change.
|
||||||
|
*/
|
||||||
|
preventDefault(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context object for configuration functions.
|
||||||
|
*
|
||||||
|
* @see MithrilElementConfig
|
||||||
|
*/
|
||||||
|
interface MithrilContext {
|
||||||
|
/**
|
||||||
|
* A function to call when the node is unloaded. Useful for cleanup.
|
||||||
|
*/
|
||||||
|
onunload?(): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set true if the backing DOM node needs to be retained between route
|
||||||
|
* changes if possible. Set false if this node needs to be recreated
|
||||||
|
* every single time, regardless of how "different" it is.
|
||||||
|
*/
|
||||||
|
retain?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a callback function for a virtual element's config
|
||||||
|
* attribute. It's a low-level function useful for extra cleanup after
|
||||||
|
* removal from the tree, storing instances of third-party classes that
|
||||||
|
* need to be associated with the DOM, etc.
|
||||||
|
*
|
||||||
|
* @see MithrilAttributes
|
||||||
|
* @see MithrilContext
|
||||||
|
*/
|
||||||
interface MithrilElementConfig {
|
interface MithrilElementConfig {
|
||||||
(element: Element, isInitialized: boolean, context?: any, vdom?: MithrilVirtualElement): void;
|
/**
|
||||||
|
* A callback function for a virtual element's config attribute.
|
||||||
|
*
|
||||||
|
* @param element The associated DOM element.
|
||||||
|
* @param isInitialized Whether this is the first call for the virtual
|
||||||
|
* element or not.
|
||||||
|
* @param context The associated context for this element.
|
||||||
|
* @param vdom The associated virtual element.
|
||||||
|
*/
|
||||||
|
<T extends MithrilController>(
|
||||||
|
element: Element,
|
||||||
|
isInitialized: boolean,
|
||||||
|
context: MithrilContext,
|
||||||
|
vdom: MithrilVirtualElement<T>
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes on a virtual element
|
/**
|
||||||
|
* This represents the attributes available for configuring virtual elements,
|
||||||
|
* beyond the applicable DOM attributes.
|
||||||
|
*
|
||||||
|
* @see m
|
||||||
|
*/
|
||||||
interface MithrilAttributes {
|
interface MithrilAttributes {
|
||||||
title?: string;
|
/**
|
||||||
|
* The class name(s) for this virtual element, as a space-separated list.
|
||||||
|
*/
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class name(s) for this virtual element, as a space-separated list.
|
||||||
|
*/
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom, low-level configuration in case this element needs special
|
||||||
|
* cleanup after removal from the tree.
|
||||||
|
*
|
||||||
|
* @see MithrilElementConfig
|
||||||
|
*/
|
||||||
config?: MithrilElementConfig;
|
config?: MithrilElementConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines the subset of Event that Mithril needs
|
/**
|
||||||
interface MithrilEvent {
|
* The basis of a Mithril controller instance.
|
||||||
currentTarget: Element;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
interface MithrilController {
|
interface MithrilController {
|
||||||
onunload?(evt: Event): any;
|
/**
|
||||||
|
* An optional handler to call when the associated virtual element is
|
||||||
|
* destroyed.
|
||||||
|
*
|
||||||
|
* @param evt An associated event.
|
||||||
|
*/
|
||||||
|
onunload?(evt: MithrilEvent): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MithrilControllerFunction extends MithrilController {
|
/**
|
||||||
(): any;
|
* This represents a controller function.
|
||||||
|
*
|
||||||
|
* @see MithrilControllerConstructor
|
||||||
|
*/
|
||||||
|
interface MithrilControllerFunction<T extends MithrilController> {
|
||||||
|
(): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a controller constructor.
|
||||||
|
*
|
||||||
|
* @see MithrilControllerFunction
|
||||||
|
*/
|
||||||
|
interface MithrilControllerConstructor<T extends MithrilController> {
|
||||||
|
new(): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a view factory.
|
||||||
|
*/
|
||||||
interface MithrilView<T extends MithrilController> {
|
interface MithrilView<T extends MithrilController> {
|
||||||
(ctrl: T): string|MithrilVirtualElement;
|
/**
|
||||||
|
* Creates a view out of virtual elements.
|
||||||
|
*/
|
||||||
|
(ctrl: T): MithrilVirtualElement<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a Mithril component.
|
||||||
|
*
|
||||||
|
* @see m
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
interface MithrilComponent<T extends MithrilController> {
|
interface MithrilComponent<T extends MithrilController> {
|
||||||
controller: MithrilControllerFunction|{ new(): T };
|
/**
|
||||||
view: MithrilView<T>;
|
* The component's controller.
|
||||||
|
*
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
|
controller: MithrilControllerFunction<T> |
|
||||||
|
MithrilControllerConstructor<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a view out of virtual elements.
|
||||||
|
*
|
||||||
|
* @see m.component
|
||||||
|
*/
|
||||||
|
view(ctrl: T): MithrilVirtualElement<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the base interface for property getter-setters
|
||||||
|
*
|
||||||
|
* @see m.prop
|
||||||
|
*/
|
||||||
interface MithrilProperty<T> {
|
interface MithrilProperty<T> {
|
||||||
|
/**
|
||||||
|
* Gets the contained value.
|
||||||
|
*
|
||||||
|
* @return The contained value.
|
||||||
|
*/
|
||||||
(): T;
|
(): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the contained value.
|
||||||
|
*
|
||||||
|
* @param value The new value to set.
|
||||||
|
* @return The newly set value.
|
||||||
|
*/
|
||||||
(value: T): T;
|
(value: T): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a non-promise getter-setter functions.
|
||||||
|
*
|
||||||
|
* @see m.prop which returns objects that implement this interface.
|
||||||
|
*/
|
||||||
|
interface MithrilBasicProperty<T> extends MithrilProperty<T> {
|
||||||
|
/**
|
||||||
|
* Makes this serializable to JSON.
|
||||||
|
*/
|
||||||
toJSON(): T;
|
toJSON(): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MithrilPromiseProperty<T> extends MithrilPromise<T> {
|
/**
|
||||||
(): T;
|
* This represents a promise getter-setter function.
|
||||||
(value: T): T;
|
*
|
||||||
toJSON(): T;
|
* @see m.prop which returns objects that implement this interface.
|
||||||
|
*/
|
||||||
|
interface MithrilPromiseProperty<T> extends MithrilPromise<T>,
|
||||||
|
MithrilProperty<MithrilPromise<T>> {
|
||||||
|
/**
|
||||||
|
* Gets the contained promise.
|
||||||
|
*
|
||||||
|
* @return The contained value.
|
||||||
|
*/
|
||||||
|
(): MithrilPromise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the contained promise.
|
||||||
|
*
|
||||||
|
* @param value The new value to set.
|
||||||
|
* @return The newly set value.
|
||||||
|
*/
|
||||||
|
(value: MithrilPromise<T>): MithrilPromise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the contained wrapped value.
|
||||||
|
*
|
||||||
|
* @param value The new value to set.
|
||||||
|
* @return The newly set value.
|
||||||
|
*/
|
||||||
|
(value: T): MithrilPromise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a key-value mapping linking routes to components.
|
||||||
|
*/
|
||||||
interface MithrilRoutes<T extends MithrilController> {
|
interface MithrilRoutes<T extends MithrilController> {
|
||||||
|
/**
|
||||||
|
* The key represents the route. The value represents the corresponding
|
||||||
|
* component.
|
||||||
|
*/
|
||||||
[key: string]: MithrilComponent<T>;
|
[key: string]: MithrilComponent<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a Mithril deferred object.
|
||||||
|
*/
|
||||||
interface MithrilDeferred<T> {
|
interface MithrilDeferred<T> {
|
||||||
|
/**
|
||||||
|
* Resolve this deferred's promise with a value.
|
||||||
|
*
|
||||||
|
* @param value The value to resolve the promise with.
|
||||||
|
*/
|
||||||
resolve(value?: T): void;
|
resolve(value?: T): void;
|
||||||
reject(value?: any): void;
|
|
||||||
|
/**
|
||||||
|
* Reject this deferred with an error.
|
||||||
|
*
|
||||||
|
* @param value The reason for rejecting the promise.
|
||||||
|
*/
|
||||||
|
reject(reason?: any): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The backing promise.
|
||||||
|
*
|
||||||
|
* @see MithrilPromise
|
||||||
|
*/
|
||||||
promise: MithrilPromise<T>;
|
promise: MithrilPromise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a thennable success callback.
|
||||||
|
*/
|
||||||
interface MithrilSuccessCallback<T, U> {
|
interface MithrilSuccessCallback<T, U> {
|
||||||
(value: T): U;
|
(value: T): U | Thennable<U>;
|
||||||
(value: T): MithrilPromise<U>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MithrilErrorCallback<U> {
|
/**
|
||||||
(value: Error): U;
|
* This represents a thennable error callback.
|
||||||
(value: string): U;
|
*/
|
||||||
|
interface MithrilErrorCallback<T> {
|
||||||
|
(value: Error): T | Thennable<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MithrilPromise<T> {
|
/**
|
||||||
(): T;
|
* This represents a thennable.
|
||||||
(value: T): T;
|
*/
|
||||||
then<U>(success: (value: T) => U): MithrilPromise<U>;
|
interface Thennable<T> {
|
||||||
then<U>(success: (value: T) => MithrilPromise<U>): MithrilPromise<U>;
|
then<U>(success: (value: T) => U): Thennable<U>;
|
||||||
then<U,V>(success: (value: T) => U, error: (value: Error) => V): MithrilPromise<U>|MithrilPromise<V>;
|
then<U,V>(success: (value: T) => U, error: (value: Error) => V): Thennable<U>|Thennable<V>;
|
||||||
then<U,V>(success: (value: T) => MithrilPromise<U>, error: (value: Error) => V): MithrilPromise<U>|MithrilPromise<V>;
|
catch?: <U>(error: (value: Error) => U) => Thennable<U>;
|
||||||
}
|
}
|
||||||
interface MithrilXHROptions {
|
|
||||||
|
/**
|
||||||
|
* This represents a Mithril promise object.
|
||||||
|
*/
|
||||||
|
interface MithrilPromise<T> extends Thennable<T>, MithrilProperty<MithrilPromise<T>> {
|
||||||
|
/**
|
||||||
|
* Chain this promise with a simple success callback, propogating
|
||||||
|
* rejections.
|
||||||
|
*
|
||||||
|
* @param success The callback to call when the promise is resolved.
|
||||||
|
* @return The chained promise.
|
||||||
|
*/
|
||||||
|
then<U>(success: MithrilSuccessCallback<T,U>): MithrilPromise<U>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chain this promise with a success callback and error callback, without
|
||||||
|
* propogating rejections.
|
||||||
|
*
|
||||||
|
* @param success The callback to call when the promise is resolved.
|
||||||
|
* @param error The callback to call when the promise is rejected.
|
||||||
|
* @return The chained promise.
|
||||||
|
*/
|
||||||
|
then<U, V>(
|
||||||
|
success: MithrilSuccessCallback<T, U>,
|
||||||
|
error: MithrilErrorCallback<V>
|
||||||
|
): MithrilPromise<U> | MithrilPromise<V>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chain this promise with a single error callback, without propogating
|
||||||
|
* rejections.
|
||||||
|
*
|
||||||
|
* @param error The callback to call when the promise is rejected.
|
||||||
|
* @return The chained promise.
|
||||||
|
*/
|
||||||
|
catch<U>(error: MithrilErrorCallback<U>): MithrilPromise<T> |
|
||||||
|
MithrilPromise<U>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents the available options for configuring m.request.
|
||||||
|
*
|
||||||
|
* @see m.request
|
||||||
|
*/
|
||||||
|
interface MithrilXHROptions<T> {
|
||||||
|
/**
|
||||||
|
* This represents the HTTP method used, one of the following:
|
||||||
|
*
|
||||||
|
* - "GET" (default)
|
||||||
|
* - "POST"
|
||||||
|
* - "PUT"
|
||||||
|
* - "DELETE"
|
||||||
|
* - "HEAD"
|
||||||
|
* - "OPTIONS"
|
||||||
|
*/
|
||||||
method?: string;
|
method?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL to send the request to.
|
||||||
|
*/
|
||||||
url: string;
|
url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The username for HTTP authentication.
|
||||||
|
*/
|
||||||
user?: string;
|
user?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password for HTTP authentication.
|
||||||
|
*/
|
||||||
password?: string;
|
password?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data to be sent. It's automatically serialized in the right format
|
||||||
|
* depending on the method (with exception of HTML5 FormData), and put in
|
||||||
|
* the appropriate section of the request.
|
||||||
|
*/
|
||||||
data?: any;
|
data?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to run it in the background, i.e. true if it doesn't affect
|
||||||
|
* template rendering.
|
||||||
|
*/
|
||||||
background?: boolean;
|
background?: boolean;
|
||||||
unwrapSuccess?(data: any): any;
|
|
||||||
unwrapError?(data: any): any;
|
/**
|
||||||
|
* Set an initial value while the request is working, to populate the
|
||||||
|
* promise getter-setter.
|
||||||
|
*/
|
||||||
|
initialValue?: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional preprocessor function to unwrap a successful response, in
|
||||||
|
* case the response contains metadata wrapping the data.
|
||||||
|
*
|
||||||
|
* @param data The data to unwrap.
|
||||||
|
* @return The unwrapped result.
|
||||||
|
*/
|
||||||
|
unwrapSuccess?(data: any): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional preprocessor function to unwrap an unsuccessful response,
|
||||||
|
* in case the response contains metadata wrapping the data.
|
||||||
|
*
|
||||||
|
* @param data The data to unwrap.
|
||||||
|
* @return The unwrapped result.
|
||||||
|
*/
|
||||||
|
unwrapError?(data: any): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional function to serialize the data. This defaults to
|
||||||
|
* `JSON.stringify`.
|
||||||
|
*
|
||||||
|
* @param dataToSerialize The data to serialize.
|
||||||
|
* @return The serialized form as a string.
|
||||||
|
*/
|
||||||
serialize?(dataToSerialize: any): string;
|
serialize?(dataToSerialize: any): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional function to deserialize the data. This defaults to
|
||||||
|
* `JSON.parse`.
|
||||||
|
*
|
||||||
|
* @param dataToSerialize The data to parse.
|
||||||
|
* @return The parsed form.
|
||||||
|
*/
|
||||||
deserialize?(dataToDeserialize: string): any;
|
deserialize?(dataToDeserialize: string): any;
|
||||||
extract?(xhr: XMLHttpRequest, options: MithrilXHROptions): string;
|
|
||||||
type?(data: Object): void;
|
/**
|
||||||
config?(xhr: XMLHttpRequest, options: MithrilXHROptions): XMLHttpRequest;
|
* An optional function to extract the data from a raw XMLHttpRequest,
|
||||||
|
* useful if the relevant data is in a response header or the status
|
||||||
|
* field.
|
||||||
|
*
|
||||||
|
* @param xhr The associated XMLHttpRequest.
|
||||||
|
* @param options The options passed to this request.
|
||||||
|
* @return string The serialized format.
|
||||||
|
*/
|
||||||
|
extract?(xhr: XMLHttpRequest, options: MithrilXHROptions<T>): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parsed data, or its children if it's an array, will be passed to
|
||||||
|
* this class constructor if it's given, to parse it into classes.
|
||||||
|
*
|
||||||
|
* @param data The data to parse.
|
||||||
|
* @return The new instance for the list.
|
||||||
|
*/
|
||||||
|
type?: new (data: Object) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional function to run between `open` and `send`, useful for
|
||||||
|
* adding request headers or using XHR2 features such as the `upload`
|
||||||
|
* property. It is even possible to override the XHR altogether with a
|
||||||
|
* similar object, such as an XDomainRequest instance.
|
||||||
|
*
|
||||||
|
* @param xhr The associated XMLHttpRequest.
|
||||||
|
* @param options The options passed to this request.
|
||||||
|
* @return The new XMLHttpRequest, or nothing if the same one is kept.
|
||||||
|
*/
|
||||||
|
config?(xhr: XMLHttpRequest, options: MithrilXHROptions<T>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For JSONP requests, this must be the string "jsonp". Otherwise, it's
|
||||||
|
* ignored.
|
||||||
|
*/
|
||||||
dataType?: string;
|
dataType?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For JSONP requests, this is the query string key for the JSONP
|
||||||
|
* request. This is useful for APIs that don't use common conventions,
|
||||||
|
* such as `www.example.com/?jsonpCallback=doSomething`. It defaults to
|
||||||
|
* `callback` for JSONP requests, and is ignored for any other kind of
|
||||||
|
* request.
|
||||||
|
*/
|
||||||
|
callbackKey?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
277
mithril.js
277
mithril.js
|
|
@ -1,9 +1,20 @@
|
||||||
var m = (function app(window, undefined) {
|
var m = (function app(window, undefined) {
|
||||||
var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function";
|
function isFunction(object) {
|
||||||
|
return typeof object === "function";
|
||||||
|
}
|
||||||
|
function isObject(object) {
|
||||||
|
return type.call(object) === "[object Object]";
|
||||||
|
}
|
||||||
|
function isString(object) {
|
||||||
|
return type.call(object) === "[object String]";
|
||||||
|
}
|
||||||
|
var isArray = Array.isArray || function (object) {
|
||||||
|
return type.call(object) === "[object Array]";
|
||||||
|
};
|
||||||
var type = {}.toString;
|
var type = {}.toString;
|
||||||
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
|
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
|
||||||
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
|
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
|
||||||
var noop = function() {};
|
var noop = function () {};
|
||||||
|
|
||||||
// caching commonly used variables
|
// caching commonly used variables
|
||||||
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
|
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
|
||||||
|
|
@ -32,16 +43,18 @@ var m = (function app(window, undefined) {
|
||||||
* @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional)
|
* @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function m() {
|
function m(tag, pairs) {
|
||||||
var args = [].slice.call(arguments);
|
for (var args = [], i = 1; i < arguments.length; i++) {
|
||||||
if (type.call(args[0]) === OBJECT) return parameterize(args[0], args.slice(1));
|
args[i - 1] = arguments[i];
|
||||||
var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1] || "view" in args[1]) && !("subtree" in args[1]);
|
}
|
||||||
var attrs = hasAttrs ? args[1] : {};
|
if (isObject(tag)) return parameterize(tag, args);
|
||||||
|
var hasAttrs = pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs);
|
||||||
|
var attrs = hasAttrs ? pairs : {};
|
||||||
var classAttrName = "class" in attrs ? "class" : "className";
|
var classAttrName = "class" in attrs ? "class" : "className";
|
||||||
var cell = {tag: "div", attrs: {}};
|
var cell = {tag: "div", attrs: {}};
|
||||||
var match, classes = [];
|
var match, classes = [];
|
||||||
if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string");
|
if (!isString(tag)) throw new Error("selector in m(selector, attrs, children) should be a string");
|
||||||
while (match = parser.exec(args[0])) {
|
while (match = parser.exec(tag)) {
|
||||||
if (match[1] === "" && match[2]) cell.tag = match[2];
|
if (match[1] === "" && match[2]) cell.tag = match[2];
|
||||||
else if (match[1] === "#") cell.attrs.id = match[2];
|
else if (match[1] === "#") cell.attrs.id = match[2];
|
||||||
else if (match[1] === ".") classes.push(match[2]);
|
else if (match[1] === ".") classes.push(match[2]);
|
||||||
|
|
@ -51,8 +64,8 @@ var m = (function app(window, undefined) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var children = hasAttrs ? args.slice(2) : args.slice(1);
|
var children = hasAttrs ? args.slice(1) : args;
|
||||||
if (children.length === 1 && type.call(children[0]) === ARRAY) {
|
if (children.length === 1 && isArray(children[0])) {
|
||||||
cell.children = children[0];
|
cell.children = children[0];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -68,10 +81,36 @@ var m = (function app(window, undefined) {
|
||||||
else cell.attrs[attrName] = attrs[attrName];
|
else cell.attrs[attrName] = attrs[attrName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ");
|
if (classes.length) cell.attrs[classAttrName] = classes.join(" ");
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
function forEach(list, f) {
|
||||||
|
for (var i = 0; i < list.length && !f(list[i], i++);) {}
|
||||||
|
}
|
||||||
|
function forKeys(list, f) {
|
||||||
|
forEach(list, function (attrs, i) {
|
||||||
|
return (attrs = attrs && attrs.attrs) && attrs.key != null && f(attrs, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// This function was causing deopts in Chrome.
|
||||||
|
function dataToString(data) {
|
||||||
|
//data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version)
|
||||||
|
try {
|
||||||
|
if (data == null || data.toString() == null) data = "";
|
||||||
|
} catch (e) {
|
||||||
|
data = "";
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// This function was causing deopts in Chrome.
|
||||||
|
function injectTextNode(parentElement, first, index, data) {
|
||||||
|
try {
|
||||||
|
parentElement.insertBefore(first, parentElement.childNodes[index] || null);
|
||||||
|
first.nodeValue = data;
|
||||||
|
}
|
||||||
|
catch (e) {} //IE erroneously throws error when appending an empty text node after a null
|
||||||
|
}
|
||||||
function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) {
|
function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) {
|
||||||
//`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached`
|
//`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached`
|
||||||
//the diff algorithm can be summarized as this:
|
//the diff algorithm can be summarized as this:
|
||||||
|
|
@ -98,15 +137,13 @@ var m = (function app(window, undefined) {
|
||||||
//there's logic that relies on the assumption that null and undefined data are equivalent to empty strings
|
//there's logic that relies on the assumption that null and undefined data are equivalent to empty strings
|
||||||
//- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
|
//- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")}
|
||||||
//- it simplifies diffing code
|
//- it simplifies diffing code
|
||||||
//data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version)
|
data = dataToString(data);
|
||||||
try { if (data == null || data.toString() == null) data = ""; } catch (e) { data = ""; }
|
|
||||||
if (data.subtree === "retain") return cached;
|
if (data.subtree === "retain") return cached;
|
||||||
var cachedType = type.call(cached), dataType = type.call(data);
|
if (cached == null || type.call(cached) !== type.call(data)) {
|
||||||
if (cached == null || cachedType !== dataType) {
|
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
if (parentCache && parentCache.nodes) {
|
if (parentCache && parentCache.nodes) {
|
||||||
var offset = index - parentIndex;
|
var offset = index - parentIndex;
|
||||||
var end = offset + (dataType === ARRAY ? data : cached.nodes).length;
|
var end = offset + (isArray(data) ? data : cached.nodes).length;
|
||||||
clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end));
|
clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end));
|
||||||
}
|
}
|
||||||
else if (cached.nodes) clear(cached.nodes, cached);
|
else if (cached.nodes) clear(cached.nodes, cached);
|
||||||
|
|
@ -116,13 +153,12 @@ var m = (function app(window, undefined) {
|
||||||
cached.nodes = [];
|
cached.nodes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataType === ARRAY) {
|
if (isArray(data)) {
|
||||||
//recursively flatten array
|
//recursively flatten array
|
||||||
for (var i = 0, len = data.length; i < len; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
if (type.call(data[i]) === ARRAY) {
|
if (isArray(data[i])) {
|
||||||
data = data.concat.apply([], data);
|
data = data.concat.apply([], data);
|
||||||
i--; //check current index again and flatten until there are no more nested arrays at that index
|
i--; //check current index again and flatten until there are no more nested arrays at that index
|
||||||
len = data.length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,55 +171,51 @@ var m = (function app(window, undefined) {
|
||||||
//4) for each key, handle its corresponding action as marked in previous steps
|
//4) for each key, handle its corresponding action as marked in previous steps
|
||||||
var DELETION = 1, INSERTION = 2 , MOVE = 3;
|
var DELETION = 1, INSERTION = 2 , MOVE = 3;
|
||||||
var existing = {}, shouldMaintainIdentities = false;
|
var existing = {}, shouldMaintainIdentities = false;
|
||||||
for (var i = 0; i < cached.length; i++) {
|
forKeys(cached, function (attrs, i) {
|
||||||
if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) {
|
shouldMaintainIdentities = true;
|
||||||
shouldMaintainIdentities = true;
|
existing[cached[i].attrs.key] = {action: DELETION, index: i};
|
||||||
existing[cached[i].attrs.key] = {action: DELETION, index: i};
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var guid = 0;
|
var guid = 0;
|
||||||
for (var i = 0, len = data.length; i < len; i++) {
|
forKeys(data, function () {
|
||||||
if (data[i] && data[i].attrs && data[i].attrs.key != null) {
|
forEach(data, function (attrs) {
|
||||||
for (var j = 0, len = data.length; j < len; j++) {
|
if ((attrs = attrs && attrs.attrs) && attrs.key == null) {
|
||||||
if (data[j] && data[j].attrs && data[j].attrs.key == null) data[j].attrs.key = "__mithril__" + guid++;
|
attrs.key = "__mithril__" + guid++;
|
||||||
}
|
}
|
||||||
break;
|
});
|
||||||
}
|
return 1;
|
||||||
}
|
});
|
||||||
|
|
||||||
if (shouldMaintainIdentities) {
|
if (shouldMaintainIdentities) {
|
||||||
var keysDiffer = false;
|
var keysDiffer = data.length != cached.length;
|
||||||
if (data.length != cached.length) keysDiffer = true;
|
if (!keysDiffer) {
|
||||||
else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) {
|
forKeys(data, function (attrs, i) {
|
||||||
if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) {
|
var cachedCell = cached[i];
|
||||||
keysDiffer = true;
|
return keysDiffer = cachedCell && cachedCell.attrs && cachedCell.attrs.key != attrs.key;
|
||||||
break;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keysDiffer) {
|
if (keysDiffer) {
|
||||||
for (var i = 0, len = data.length; i < len; i++) {
|
forKeys(data, function (attrs, i) {
|
||||||
if (data[i] && data[i].attrs) {
|
var key = attrs.key;
|
||||||
if (data[i].attrs.key != null) {
|
if (existing[key]) {
|
||||||
var key = data[i].attrs.key;
|
existing[key] = {
|
||||||
if (!existing[key]) existing[key] = {action: INSERTION, index: i};
|
action: MOVE,
|
||||||
else existing[key] = {
|
index: i,
|
||||||
action: MOVE,
|
from: existing[key].index,
|
||||||
index: i,
|
element: cached.nodes[existing[key].index] || $document.createElement("div")
|
||||||
from: existing[key].index,
|
};
|
||||||
element: cached.nodes[existing[key].index] || $document.createElement("div")
|
} else {
|
||||||
};
|
existing[key] = {action: INSERTION, index: i};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
var actions = [];
|
var actions = [];
|
||||||
for (var prop in existing) actions.push(existing[prop]);
|
for (var prop in existing) actions.push(existing[prop]);
|
||||||
var changes = actions.sort(sortChanges);
|
var changes = actions.sort(sortChanges);
|
||||||
var newCached = new Array(cached.length);
|
var newCached = new Array(cached.length);
|
||||||
newCached.nodes = cached.nodes.slice();
|
newCached.nodes = cached.nodes.slice();
|
||||||
|
|
||||||
for (var i = 0, change; change = changes[i]; i++) {
|
forEach(changes, function (change) {
|
||||||
if (change.action === DELETION) {
|
if (change.action === DELETION) {
|
||||||
clear(cached[change.index].nodes, cached[change.index]);
|
clear(cached[change.index].nodes, cached[change.index]);
|
||||||
newCached.splice(change.index, 1);
|
newCached.splice(change.index, 1);
|
||||||
|
|
@ -203,43 +235,46 @@ var m = (function app(window, undefined) {
|
||||||
newCached[change.index] = cached[change.from];
|
newCached[change.index] = cached[change.from];
|
||||||
newCached.nodes[change.index] = change.element;
|
newCached.nodes[change.index] = change.element;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
cached = newCached;
|
cached = newCached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//end key algorithm
|
//end key algorithm
|
||||||
|
|
||||||
for (var i = 0, cacheCount = 0, len = data.length; i < len; i++) {
|
var cacheCount = 0;
|
||||||
|
forEach(data, function (entry) {
|
||||||
//diff each item in the array
|
//diff each item in the array
|
||||||
var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs);
|
var item = build(parentElement, parentTag, cached, index, entry, cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs);
|
||||||
if (item === undefined) continue;
|
if (item !== undefined) {
|
||||||
if (!item.nodes.intact) intact = false;
|
if (!item.nodes.intact) intact = false;
|
||||||
if (item.$trusted) {
|
if (item.$trusted) {
|
||||||
//fix offset of next element if item was a trusted string w/ more than one html element
|
//fix offset of next element if item was a trusted string w/ more than one html element
|
||||||
//the first clause in the regexp matches elements
|
//the first clause in the regexp matches elements
|
||||||
//the second clause (after the pipe) matches text nodes
|
//the second clause (after the pipe) matches text nodes
|
||||||
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length;
|
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length;
|
||||||
|
}
|
||||||
|
else subArrayCount += isArray(item) ? item.length : 1;
|
||||||
|
cached[cacheCount++] = item;
|
||||||
}
|
}
|
||||||
else subArrayCount += type.call(item) === ARRAY ? item.length : 1;
|
});
|
||||||
cached[cacheCount++] = item;
|
|
||||||
}
|
|
||||||
if (!intact) {
|
if (!intact) {
|
||||||
//diff the array itself
|
//diff the array itself
|
||||||
|
|
||||||
//update the list of DOM nodes by collecting the nodes from each item
|
//update the list of DOM nodes by collecting the nodes from each item
|
||||||
for (var i = 0, len = data.length; i < len; i++) {
|
forEach(data, function (_, i) {
|
||||||
if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes);
|
if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes);
|
||||||
}
|
});
|
||||||
//remove items from the end of the array if the new array is shorter than the old one
|
//remove items from the end of the array if the new array is shorter than the old one
|
||||||
//if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program
|
//if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program
|
||||||
for (var i = 0, node; node = cached.nodes[i]; i++) {
|
forEach(cached.nodes, function (node, i) {
|
||||||
if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]);
|
if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]);
|
||||||
}
|
});
|
||||||
if (data.length < cached.length) cached.length = data.length;
|
if (data.length < cached.length) cached.length = data.length;
|
||||||
cached.nodes = nodes;
|
cached.nodes = nodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (data != null && dataType === OBJECT) {
|
else if (data != null && isObject(data)) {
|
||||||
var views = [], controllers = [];
|
var views = [], controllers = [];
|
||||||
while (data.view) {
|
while (data.view) {
|
||||||
var view = data.view.$original || data.view;
|
var view = data.view.$original || data.view;
|
||||||
|
|
@ -265,14 +300,14 @@ var m = (function app(window, undefined) {
|
||||||
//if an element is different enough from the one in cache, recreate it
|
//if an element is different enough from the one in cache, recreate it
|
||||||
if (data.tag != cached.tag || dataAttrKeys.sort().join() != Object.keys(cached.attrs).sort().join() || data.attrs.id != cached.attrs.id || data.attrs.key != cached.attrs.key || (m.redraw.strategy() == "all" && (!cached.configContext || cached.configContext.retain !== true)) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) {
|
if (data.tag != cached.tag || dataAttrKeys.sort().join() != Object.keys(cached.attrs).sort().join() || data.attrs.id != cached.attrs.id || data.attrs.key != cached.attrs.key || (m.redraw.strategy() == "all" && (!cached.configContext || cached.configContext.retain !== true)) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) {
|
||||||
if (cached.nodes.length) clear(cached.nodes);
|
if (cached.nodes.length) clear(cached.nodes);
|
||||||
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload();
|
if (cached.configContext && isFunction(cached.configContext.onunload)) cached.configContext.onunload();
|
||||||
if (cached.controllers) {
|
if (cached.controllers) {
|
||||||
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
||||||
if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop});
|
if (isFunction(controller.onunload)) controller.onunload({preventDefault: noop});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type.call(data.tag) != STRING) return;
|
if (!isString(data.tag)) return;
|
||||||
|
|
||||||
var node, isNew = cached.nodes.length === 0;
|
var node, isNew = cached.nodes.length === 0;
|
||||||
if (data.attrs.xmlns) namespace = data.attrs.xmlns;
|
if (data.attrs.xmlns) namespace = data.attrs.xmlns;
|
||||||
|
|
@ -321,7 +356,7 @@ var m = (function app(window, undefined) {
|
||||||
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null);
|
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null);
|
||||||
}
|
}
|
||||||
//schedule configs to be called. They are called after `build` finishes running
|
//schedule configs to be called. They are called after `build` finishes running
|
||||||
if (typeof data.attrs["config"] === FUNCTION) {
|
if (isFunction(data.attrs["config"])) {
|
||||||
var context = cached.configContext = cached.configContext || {};
|
var context = cached.configContext = cached.configContext || {};
|
||||||
|
|
||||||
// bind
|
// bind
|
||||||
|
|
@ -333,7 +368,7 @@ var m = (function app(window, undefined) {
|
||||||
configs.push(callback(data, [node, !isNew, context, cached]));
|
configs.push(callback(data, [node, !isNew, context, cached]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (typeof data != FUNCTION) {
|
else if (!isFunction(data)) {
|
||||||
//handle text nodes
|
//handle text nodes
|
||||||
var nodes;
|
var nodes;
|
||||||
if (cached.nodes.length === 0) {
|
if (cached.nodes.length === 0) {
|
||||||
|
|
@ -364,11 +399,7 @@ var m = (function app(window, undefined) {
|
||||||
clear(cached.nodes, cached);
|
clear(cached.nodes, cached);
|
||||||
nodes = [$document.createTextNode(data)];
|
nodes = [$document.createTextNode(data)];
|
||||||
}
|
}
|
||||||
try {
|
injectTextNode(parentElement, nodes[0], index, data)
|
||||||
parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null);
|
|
||||||
nodes[0].nodeValue = data;
|
|
||||||
}
|
|
||||||
catch (e) {} //IE erroneously throws error when appending an empty text node after a null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -391,11 +422,11 @@ var m = (function app(window, undefined) {
|
||||||
//`config` isn't a real attributes, so ignore it
|
//`config` isn't a real attributes, so ignore it
|
||||||
if (attrName === "config" || attrName == "key") continue;
|
if (attrName === "config" || attrName == "key") continue;
|
||||||
//hook event handlers to the auto-redrawing system
|
//hook event handlers to the auto-redrawing system
|
||||||
else if (typeof dataAttr === FUNCTION && attrName.indexOf("on") === 0) {
|
else if (isFunction(dataAttr) && attrName.indexOf("on") === 0) {
|
||||||
node[attrName] = autoredraw(dataAttr, node);
|
node[attrName] = autoredraw(dataAttr, node);
|
||||||
}
|
}
|
||||||
//handle `style: {...}`
|
//handle `style: {...}`
|
||||||
else if (attrName === "style" && dataAttr != null && type.call(dataAttr) === OBJECT) {
|
else if (attrName === "style" && dataAttr != null && isObject(dataAttr)) {
|
||||||
for (var rule in dataAttr) {
|
for (var rule in dataAttr) {
|
||||||
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule];
|
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule];
|
||||||
}
|
}
|
||||||
|
|
@ -442,17 +473,17 @@ var m = (function app(window, undefined) {
|
||||||
if (nodes.length != 0) nodes.length = 0;
|
if (nodes.length != 0) nodes.length = 0;
|
||||||
}
|
}
|
||||||
function unload(cached) {
|
function unload(cached) {
|
||||||
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) {
|
if (cached.configContext && isFunction(cached.configContext.onunload)) {
|
||||||
cached.configContext.onunload();
|
cached.configContext.onunload();
|
||||||
cached.configContext.onunload = null;
|
cached.configContext.onunload = null;
|
||||||
}
|
}
|
||||||
if (cached.controllers) {
|
if (cached.controllers) {
|
||||||
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
||||||
if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop});
|
if (isFunction(controller.onunload)) controller.onunload({preventDefault: noop});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cached.children) {
|
if (cached.children) {
|
||||||
if (type.call(cached.children) === ARRAY) {
|
if (isArray(cached.children)) {
|
||||||
for (var i = 0, child; child = cached.children[i]; i++) unload(child);
|
for (var i = 0, child; child = cached.children[i]; i++) unload(child);
|
||||||
}
|
}
|
||||||
else if (cached.children.tag) unload(cached.children);
|
else if (cached.children.tag) unload(cached.children);
|
||||||
|
|
@ -544,7 +575,7 @@ var m = (function app(window, undefined) {
|
||||||
|
|
||||||
m.prop = function (store) {
|
m.prop = function (store) {
|
||||||
//note: using non-strict equality check here because we're checking if store is null OR undefined
|
//note: using non-strict equality check here because we're checking if store is null OR undefined
|
||||||
if (((store != null && type.call(store) === OBJECT) || typeof store === FUNCTION) && typeof store.then === FUNCTION) {
|
if ((store != null && isObject(store) || isFunction(store)) && isFunction(store.then)) {
|
||||||
return propify(store);
|
return propify(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,7 +590,7 @@ var m = (function app(window, undefined) {
|
||||||
};
|
};
|
||||||
if (component.controller) controller.prototype = component.controller.prototype
|
if (component.controller) controller.prototype = component.controller.prototype
|
||||||
var view = function(ctrl) {
|
var view = function(ctrl) {
|
||||||
if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1));
|
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
|
||||||
return component.view.apply(component, args ? [ctrl].concat(args) : [ctrl]);
|
return component.view.apply(component, args ? [ctrl].concat(args) : [ctrl]);
|
||||||
};
|
};
|
||||||
view.$original = component.view;
|
view.$original = component.view;
|
||||||
|
|
@ -568,7 +599,8 @@ var m = (function app(window, undefined) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
m.component = function(component) {
|
m.component = function(component) {
|
||||||
return parameterize(component, [].slice.call(arguments, 1));
|
for (var args = [], i = 1; i < arguments.length; i++) args[i - 1] = arguments[i];
|
||||||
|
return parameterize(component, args);
|
||||||
};
|
};
|
||||||
m.mount = m.module = function(root, component) {
|
m.mount = m.module = function(root, component) {
|
||||||
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
|
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
|
||||||
|
|
@ -589,7 +621,7 @@ var m = (function app(window, undefined) {
|
||||||
}
|
}
|
||||||
else unloaders = [];
|
else unloaders = [];
|
||||||
|
|
||||||
if (controllers[index] && typeof controllers[index].onunload === FUNCTION) {
|
if (controllers[index] && isFunction(controllers[index].onunload)) {
|
||||||
controllers[index].onunload(event);
|
controllers[index].onunload(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,7 +630,7 @@ var m = (function app(window, undefined) {
|
||||||
m.startComputation();
|
m.startComputation();
|
||||||
roots[index] = root;
|
roots[index] = root;
|
||||||
if (arguments.length > 2) component = subcomponent(component, [].slice.call(arguments, 2));
|
if (arguments.length > 2) component = subcomponent(component, [].slice.call(arguments, 2));
|
||||||
var currentComponent = topComponent = component = component || {controller: function() {}};
|
var currentComponent = topComponent = component = component || {controller: noop};
|
||||||
var constructor = component.controller || noop;
|
var constructor = component.controller || noop;
|
||||||
var controller = new constructor;
|
var controller = new constructor;
|
||||||
//controllers may call m.mount recursively (via m.route redirects, for example)
|
//controllers may call m.mount recursively (via m.route redirects, for example)
|
||||||
|
|
@ -619,7 +651,7 @@ var m = (function app(window, undefined) {
|
||||||
try {
|
try {
|
||||||
//lastRedrawId is a positive number if a second redraw is requested before the next animation frame
|
//lastRedrawId is a positive number if a second redraw is requested before the next animation frame
|
||||||
//lastRedrawID is null if it's the first redraw and not an event handler
|
//lastRedrawID is null if it's the first redraw and not an event handler
|
||||||
if (lastRedrawId && force !== true) {
|
if (lastRedrawId && !force) {
|
||||||
//when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout
|
//when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout
|
||||||
//when rAF: always reschedule redraw
|
//when rAF: always reschedule redraw
|
||||||
if ($requestAnimationFrame === window.requestAnimationFrame || new Date - lastRedrawCallTime > FRAME_BUDGET) {
|
if ($requestAnimationFrame === window.requestAnimationFrame || new Date - lastRedrawCallTime > FRAME_BUDGET) {
|
||||||
|
|
@ -683,18 +715,17 @@ var m = (function app(window, undefined) {
|
||||||
//routing
|
//routing
|
||||||
var modes = {pathname: "", hash: "#", search: "?"};
|
var modes = {pathname: "", hash: "#", search: "?"};
|
||||||
var redirect = noop, routeParams, currentRoute, isDefaultRoute = false;
|
var redirect = noop, routeParams, currentRoute, isDefaultRoute = false;
|
||||||
m.route = function() {
|
m.route = function(root, arg1, arg2, vdom) {
|
||||||
//m.route()
|
//m.route()
|
||||||
if (arguments.length === 0) return currentRoute;
|
if (arguments.length === 0) return currentRoute;
|
||||||
//m.route(el, defaultRoute, routes)
|
//m.route(el, defaultRoute, routes)
|
||||||
else if (arguments.length === 3 && type.call(arguments[1]) === STRING) {
|
else if (arguments.length === 3 && isString(arg1)) {
|
||||||
var root = arguments[0], defaultRoute = arguments[1], router = arguments[2];
|
|
||||||
redirect = function(source) {
|
redirect = function(source) {
|
||||||
var path = currentRoute = normalizeRoute(source);
|
var path = currentRoute = normalizeRoute(source);
|
||||||
if (!routeByValue(root, router, path)) {
|
if (!routeByValue(root, arg2, path)) {
|
||||||
if (isDefaultRoute) throw new Error("Ensure the default route matches one of the routes defined in m.route");
|
if (isDefaultRoute) throw new Error("Ensure the default route matches one of the routes defined in m.route");
|
||||||
isDefaultRoute = true;
|
isDefaultRoute = true;
|
||||||
m.route(defaultRoute, true);
|
m.route(arg1, true);
|
||||||
isDefaultRoute = false;
|
isDefaultRoute = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -710,32 +741,30 @@ var m = (function app(window, undefined) {
|
||||||
window[listener]();
|
window[listener]();
|
||||||
}
|
}
|
||||||
//config: m.route
|
//config: m.route
|
||||||
else if (arguments[0].addEventListener || arguments[0].attachEvent) {
|
else if (root.addEventListener || root.attachEvent) {
|
||||||
var element = arguments[0];
|
root.href = (m.route.mode !== 'pathname' ? $location.pathname : '') + modes[m.route.mode] + vdom.attrs.href;
|
||||||
var vdom = arguments[3];
|
if (root.addEventListener) {
|
||||||
element.href = (m.route.mode !== 'pathname' ? $location.pathname : '') + modes[m.route.mode] + vdom.attrs.href;
|
root.removeEventListener("click", routeUnobtrusive);
|
||||||
if (element.addEventListener) {
|
root.addEventListener("click", routeUnobtrusive);
|
||||||
element.removeEventListener("click", routeUnobtrusive);
|
|
||||||
element.addEventListener("click", routeUnobtrusive);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
element.detachEvent("onclick", routeUnobtrusive);
|
root.detachEvent("onclick", routeUnobtrusive);
|
||||||
element.attachEvent("onclick", routeUnobtrusive);
|
root.attachEvent("onclick", routeUnobtrusive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//m.route(route, params, shouldReplaceHistoryEntry)
|
//m.route(route, params, shouldReplaceHistoryEntry)
|
||||||
else if (type.call(arguments[0]) === STRING) {
|
else if (isString(root)) {
|
||||||
var oldRoute = currentRoute;
|
var oldRoute = currentRoute;
|
||||||
currentRoute = arguments[0];
|
currentRoute = root;
|
||||||
var args = arguments[1] || {};
|
arg1 || {};
|
||||||
var queryIndex = currentRoute.indexOf("?");
|
var queryIndex = currentRoute.indexOf("?");
|
||||||
var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {};
|
var params = queryIndex > -1 ? parseQueryString(currentRoute.slice(queryIndex + 1)) : {};
|
||||||
for (var i in args) params[i] = args[i];
|
for (var i in arg1) params[i] = arg1[i];
|
||||||
var querystring = buildQueryString(params);
|
var querystring = buildQueryString(params);
|
||||||
var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute;
|
var currentPath = queryIndex > -1 ? currentRoute.slice(0, queryIndex) : currentRoute;
|
||||||
if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;
|
if (querystring) currentRoute = currentPath + (currentPath.indexOf("?") === -1 ? "?" : "&") + querystring;
|
||||||
|
|
||||||
var shouldReplaceHistoryEntry = (arguments.length === 3 ? arguments[2] : arguments[1]) === true || oldRoute === arguments[0];
|
var shouldReplaceHistoryEntry = (arguments.length === 3 ? arg2 : arg1) === true || oldRoute === root;
|
||||||
|
|
||||||
if (window.history.pushState) {
|
if (window.history.pushState) {
|
||||||
computePreRedrawHook = setScroll;
|
computePreRedrawHook = setScroll;
|
||||||
|
|
@ -817,8 +846,8 @@ var m = (function app(window, undefined) {
|
||||||
var value = object[prop];
|
var value = object[prop];
|
||||||
var valueType = type.call(value);
|
var valueType = type.call(value);
|
||||||
var pair = (value === null) ? encodeURIComponent(key) :
|
var pair = (value === null) ? encodeURIComponent(key) :
|
||||||
valueType === OBJECT ? buildQueryString(value, key) :
|
isObject(value) ? buildQueryString(value, key) :
|
||||||
valueType === ARRAY ? value.reduce(function(memo, item) {
|
isArray(value) ? value.reduce(function(memo, item) {
|
||||||
if (!duplicates[key]) duplicates[key] = {};
|
if (!duplicates[key]) duplicates[key] = {};
|
||||||
if (!duplicates[key][item]) {
|
if (!duplicates[key][item]) {
|
||||||
duplicates[key][item] = true;
|
duplicates[key][item] = true;
|
||||||
|
|
@ -840,7 +869,7 @@ var m = (function app(window, undefined) {
|
||||||
var key = decodeURIComponent(pair[0]);
|
var key = decodeURIComponent(pair[0]);
|
||||||
var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null;
|
var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null;
|
||||||
if (params[key] != null) {
|
if (params[key] != null) {
|
||||||
if (type.call(params[key]) !== ARRAY) params[key] = [params[key]];
|
if (!isArray(params[key])) params[key] = [params[key]];
|
||||||
params[key].push(value);
|
params[key].push(value);
|
||||||
}
|
}
|
||||||
else params[key] = value;
|
else params[key] = value;
|
||||||
|
|
@ -922,7 +951,7 @@ var m = (function app(window, undefined) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function thennable(then, successCallback, failureCallback, notThennableCallback) {
|
function thennable(then, successCallback, failureCallback, notThennableCallback) {
|
||||||
if (((promiseValue != null && type.call(promiseValue) === OBJECT) || typeof promiseValue === FUNCTION) && typeof then === FUNCTION) {
|
if (((promiseValue != null && isObject(promiseValue)) || isFunction(promiseValue)) && isFunction(then)) {
|
||||||
try {
|
try {
|
||||||
// count protects against abuse calls from spec checker
|
// count protects against abuse calls from spec checker
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
@ -966,10 +995,10 @@ var m = (function app(window, undefined) {
|
||||||
fire();
|
fire();
|
||||||
}, function() {
|
}, function() {
|
||||||
try {
|
try {
|
||||||
if (state === RESOLVING && typeof successCallback === FUNCTION) {
|
if (state === RESOLVING && isFunction(successCallback)) {
|
||||||
promiseValue = successCallback(promiseValue);
|
promiseValue = successCallback(promiseValue);
|
||||||
}
|
}
|
||||||
else if (state === REJECTING && typeof failureCallback === FUNCTION) {
|
else if (state === REJECTING && isFunction(failureCallback)) {
|
||||||
promiseValue = failureCallback(promiseValue);
|
promiseValue = failureCallback(promiseValue);
|
||||||
state = RESOLVING;
|
state = RESOLVING;
|
||||||
}
|
}
|
||||||
|
|
@ -1086,13 +1115,13 @@ var m = (function app(window, undefined) {
|
||||||
if (options.deserialize === JSON.parse) {
|
if (options.deserialize === JSON.parse) {
|
||||||
xhr.setRequestHeader("Accept", "application/json, text/*");
|
xhr.setRequestHeader("Accept", "application/json, text/*");
|
||||||
}
|
}
|
||||||
if (typeof options.config === FUNCTION) {
|
if (isFunction(options.config)) {
|
||||||
var maybeXhr = options.config(xhr, options);
|
var maybeXhr = options.config(xhr, options);
|
||||||
if (maybeXhr != null) xhr = maybeXhr;
|
if (maybeXhr != null) xhr = maybeXhr;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = options.method === "GET" || !options.data ? "" : options.data;
|
var data = options.method === "GET" || !options.data ? "" : options.data;
|
||||||
if (data && (type.call(data) != STRING && data.constructor != window.FormData)) {
|
if (data && (!isString(data) && data.constructor != window.FormData)) {
|
||||||
throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";
|
throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";
|
||||||
}
|
}
|
||||||
xhr.send(data);
|
xhr.send(data);
|
||||||
|
|
@ -1138,8 +1167,10 @@ var m = (function app(window, undefined) {
|
||||||
var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity;
|
var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity;
|
||||||
var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target);
|
var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target);
|
||||||
if (e.type === "load") {
|
if (e.type === "load") {
|
||||||
if (type.call(response) === ARRAY && xhrOptions.type) {
|
if (isArray(response) && xhrOptions.type) {
|
||||||
for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]);
|
forEach(response, function (res, i) {
|
||||||
|
response[i] = new xhrOptions.type(res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (xhrOptions.type) response = new xhrOptions.type(response);
|
else if (xhrOptions.type) response = new xhrOptions.type(response);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
mithril.min.js
vendored
2
mithril.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue