mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'publish-packages' into library-refactor
Conflicts: packages/domutils/package.js packages/handlebars/package.js packages/htmljs/package.js packages/liverange/package.js packages/spark/package.js packages/universal-events/package.js tools/bundler.js tools/help.txt tools/packages.js tools/run-app.js tools/run-mongo.js tools/skel/.meteor/packages
This commit is contained in:
11
.mailmap
11
.mailmap
@@ -8,7 +8,10 @@
|
||||
# For any emails that show up in the shortlog that aren't in one of
|
||||
# these lists, figure out their GitHub username and add them.
|
||||
|
||||
GITHUB: aldeed <eric@dairystatedesigns.com>
|
||||
GITHUB: AlexeyMK <alexey@alexeymk.com>
|
||||
GITHUB: apendua <apendua@gmail.com>
|
||||
GITHUB: arbesfeld <arbesfeld@gmail.com>
|
||||
GITHUB: DenisGorbachev <Denis.Gorbachev@faster-than-wind.ru>
|
||||
GITHUB: EOT <eot@gmx.at>
|
||||
GITHUB: FooBarWidget <honglilai@gmail.com>
|
||||
@@ -17,13 +20,17 @@ GITHUB: OyoKooN <nathan@sxnlabs.com>
|
||||
GITHUB: RobertLowe <robert@iblargz.com>
|
||||
GITHUB: ansman <nicklas@ansman.se>
|
||||
GITHUB: awwx <andrew.wilcox@gmail.com>
|
||||
GITHUB: cmather <mather.chris@gmail.com>
|
||||
GITHUB: codeinthehole <david.winterbottom@gmail.com>
|
||||
GITHUB: dandv <ddascalescu+github@gmail.com>
|
||||
GITHUB: davegonzalez <gonzalez.dalex@gmail.com>
|
||||
GITHUB: emgee3 <hello@gravitronic.com>
|
||||
GITHUB: icellan <icellan@icellan.com>
|
||||
GITHUB: jacott <geoffjacobsen@gmail.com>
|
||||
GITHUB: jfhamlin <jfhamlin@gmail.com>
|
||||
GITHUB: justinsb <justin@fathomdb.com>
|
||||
GITHUB: marcandre <github@marc-andre.ca>
|
||||
GITHUB: mart-jansink <m.jansink@gmail.com>
|
||||
GITHUB: meawoppl <meawoppl@gmail.com>
|
||||
GITHUB: michaelbishop <michael@michaelsplace.net>
|
||||
GITHUB: mitar <mitar.git@tnode.com>
|
||||
@@ -31,7 +38,9 @@ GITHUB: mitar <mitar.github@tnode.com>
|
||||
GITHUB: mizzao <mizzao@gmail.com>
|
||||
GITHUB: mquandalle <maxime.quandalle@gmail.com>
|
||||
GITHUB: nathan-muir <ndmuir@gmail.com>
|
||||
GITHUB: Neftedollar <oildollar@gmail.com>
|
||||
GITHUB: paulswartz <paulswartz@gmail.com>
|
||||
GITHUB: Pent <jon@empire5design.com>
|
||||
GITHUB: queso <joshua.owens@gmail.com>
|
||||
GITHUB: rdickert <robert.dickert@gmail.com>
|
||||
GITHUB: rgould <rwgould@gmail.com>
|
||||
@@ -48,6 +57,7 @@ METEOR: dgreensp <dgreenspan@alum.mit.edu>
|
||||
METEOR: estark37 <emily@meteor.com>
|
||||
METEOR: estark37 <estark37@gmail.com>
|
||||
METEOR: glasser <glasser@meteor.com>
|
||||
METEOR: glasser <glasser@davidglasser.net>
|
||||
METEOR: gschmidt <geoff@geoffschmidt.com>
|
||||
METEOR: karayu <lele.yu@gmail.com>
|
||||
METEOR: n1mmy <nim@meteor.com>
|
||||
@@ -55,3 +65,4 @@ METEOR: sixolet <naomi@meteor.com>
|
||||
METEOR: Slava <slava@meteor.com>
|
||||
METEOR: stubailo <sashko@mit.edu>
|
||||
METEOR: ekatek <ekate@meteor.com>
|
||||
|
||||
|
||||
183
History.md
183
History.md
@@ -1,7 +1,190 @@
|
||||
## v.NEXT
|
||||
|
||||
#### Meteor Accounts
|
||||
|
||||
* Log out a user's other sessions when they change their password.
|
||||
|
||||
* Store pending OAuth login results in the database instead of
|
||||
in-memory, so that an OAuth flow succeeds even if different requests
|
||||
go to different server processes.
|
||||
|
||||
* When validateLoginAttempt callbacks return false, don't override a more
|
||||
specific error message.
|
||||
|
||||
* Add `Random.secret()` for generating security-critical secrets like
|
||||
login tokens.
|
||||
|
||||
* `Meteor.logoutOtherClients` now calls the user callback when other
|
||||
login tokens have actually been removed from the database, not when
|
||||
they have been marked for eventual removal. Fixes #1915.
|
||||
|
||||
* Rename `Oauth` to `OAuth`. `Oauth` is now an alias for backwards
|
||||
compatibility.
|
||||
|
||||
* Add `oauth-encryption` package for encrypting sensitive account
|
||||
credentials in the database.
|
||||
|
||||
* A validate login hook can now override the exception thrown from
|
||||
`beginPasswordExchange` like it can for other login methods.
|
||||
|
||||
* Remove an expensive observe over all users in the `accounts-base`
|
||||
package.
|
||||
|
||||
|
||||
#### Blaze
|
||||
|
||||
* Blaze no longer renders javascript: URLs in attribute values by
|
||||
default, to help prevent cross-site scripting bugs. Use
|
||||
`UI._allowJavascriptUrls()` to allow them.
|
||||
|
||||
* Fix `UI.toHTML` on templates containing `{{#with}}`
|
||||
|
||||
* Fix {{#with}} over a data context that is mutated
|
||||
|
||||
* Properly clean up autoruns on `UI.toHTML`
|
||||
|
||||
* Add support for `{{!-- block comments --}}` in Spacebars. Block comments may
|
||||
contain `}}`, so they are more useful than `{{! normal comments}}` for
|
||||
commenting out sections of Spacebars templates.
|
||||
XXX shouldn't this be in spacebars/README.md?
|
||||
|
||||
* Kill TBODY special case in DomRange (XXX 45ac9b1a6d needs a better
|
||||
description)
|
||||
|
||||
* Do not ignore jquery-event extra parameters (? XXX b2193f5)
|
||||
|
||||
|
||||
#### DDP and Mongo
|
||||
|
||||
* DDP heartbeats XXX
|
||||
|
||||
* Generalize the mechanism by which client-side inserts generated IDs to support
|
||||
latency compensation of generation of multiple random values. For example,
|
||||
calling `insert` inside a method body will now return consistent IDs on the
|
||||
client and the server. Code that wants a random stream that is consistent
|
||||
between method stub and real method execution can get one with
|
||||
`DDP.randomStream`.
|
||||
|
||||
* The oplog observe driver handles errors communicating with Mongo better and
|
||||
knows to re-poll all queries during Mongo failovers.
|
||||
|
||||
* Fix bugs involving mutating DDP method arguments.
|
||||
|
||||
|
||||
#### meteor command-line tool
|
||||
|
||||
* Move boilerplate HTML from tools to webapp. Changes internal
|
||||
Webapp.addHtmlAttributeHook API incompatibly.
|
||||
|
||||
* Add `meteor list-sites` command for listing the sites that you have
|
||||
deployed to meteor.com with your Meteor developer account.
|
||||
|
||||
* Third-party template languages can request that their generated source loads
|
||||
before other JavaScript files, just like *.html files, by passing the
|
||||
isTemplate option to Plugin.registerSourceHandler.
|
||||
|
||||
* You can specify a particular interface for the dev mode runner to bind to with
|
||||
`meteor -p host:port`.
|
||||
|
||||
* Don't include proprietary tar tags in bundle tarballs.
|
||||
|
||||
* Convert relative urls to absolute url when merging CSS files
|
||||
|
||||
|
||||
#### Upgraded dependencies
|
||||
|
||||
* Node.js from 0.10.25 to 0.10.26.
|
||||
* MongoDB driver from 1.3.19 to 1.4.1
|
||||
* stylus: 0.42.3 (from 0.42.2)
|
||||
* showdown: XXX (from XXX)
|
||||
* css-parse: an unreleased version (from 1.7.0)
|
||||
* css-stringify: an unreleased version (from 1.4.1)
|
||||
|
||||
|
||||
Patches contributed by GitHub users aldeed, apendua, arbesfeld, awwx, dandv,
|
||||
davegonzalez, emgee3, justinsb, mquandalle, Neftedollar, Pent, sdarnell,
|
||||
and timhaines.
|
||||
|
||||
|
||||
## v0.8.0.1
|
||||
|
||||
* Fix security flaw in OAuth1 implementation. Clients can no longer
|
||||
choose the callback_url for OAuth1 logins.
|
||||
|
||||
|
||||
## v0.8.0
|
||||
|
||||
Meteor 0.8.0 introduces Blaze, a total rewrite of our live templating engine,
|
||||
replacing Spark. Advantages of Blaze include:
|
||||
|
||||
* Better interoperability with jQuery plugins and other techniques which
|
||||
directly manipulate the DOM
|
||||
* More fine-grained updates: only the specific elements or attributes that
|
||||
change are touched rather than the entire template
|
||||
* A fully documented templating language
|
||||
* No need for the confusing `{{#constant}}`, `{{#isolate}}`, and `preserve`
|
||||
directives
|
||||
* Uses standard jQuery delegation (`.on`) instead of our custom implementation
|
||||
* Blaze supports live SVG templates that work just like HTML templates
|
||||
|
||||
See
|
||||
[the Using Blaze wiki page](https://github.com/meteor/meteor/wiki/Using-Blaze)
|
||||
for full details on upgrading your app to 0.8.0. This includes:
|
||||
|
||||
* The `Template.foo.rendered` callback is now only called once when the template
|
||||
is rendered, rather than repeatedly as it is "re-rendered", because templates
|
||||
now directly update changed data instead of fully re-rendering.
|
||||
|
||||
* The `accounts-ui` login buttons are now invoked as a `{{> loginButtons}}`
|
||||
rather than as `{{loginButtons}}`.
|
||||
|
||||
* Previous versions of Meteor used a heavily modified version of the Handlebars
|
||||
templating language. In 0.8.0, we've given it its own name: Spacebars!
|
||||
Spacebars has an
|
||||
[explicit specification](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md)
|
||||
instead of being defined as a series of changes to Handlebars. There are some
|
||||
incompatibilities with our previous Handlebars fork, such as a
|
||||
[different way of specifying dynamic element attributes](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md#in-attribute-values)
|
||||
and a
|
||||
[new way of defining custom block helpers](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md#custom-block-helpers).
|
||||
|
||||
* Your template files must consist of
|
||||
[well-formed HTML](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md#html-dialect). Invalid
|
||||
HTML is now a compilation failure. (There is a current limitation in our HTML
|
||||
parser such that it does not support
|
||||
[omitting end tags](http://www.w3.org/TR/html5/syntax.html#syntax-tag-omission)
|
||||
on elements such as `<P>` and `<LI>`.)
|
||||
|
||||
* `Template.foo` is no longer a function. It is instead a
|
||||
"component". Components render to an intermediate representation of an HTML
|
||||
tree, not a string, so there is no longer an easy way to render a component to
|
||||
a static HTML string.
|
||||
|
||||
* `Meteor.render` and `Spark.render` have been removed. Use `UI.render` and
|
||||
`UI.insert` instead.
|
||||
|
||||
* The `<body>` tag now defines a template just like the `<template>` tag, which
|
||||
can have helpers and event handlers. Define them directly on the object
|
||||
`UI.body`.
|
||||
|
||||
* Previous versions of Meteor shipped with a synthesized `tap` event,
|
||||
implementing a zero-delay click event on mobile browsers. Unfortunately, this
|
||||
event never worked very well. We're eliminating it. Instead, use one of the
|
||||
excellent third party solutions.
|
||||
|
||||
* The `madewith` package (which supported adding a badge to your website
|
||||
displaying its score from http://madewith.meteor.com/) has been removed, as it
|
||||
is not compatible with the new version of that site.
|
||||
|
||||
* The internal `spark`, `liverange`, `universal-events`, and `domutils` packages
|
||||
have been removed.
|
||||
|
||||
* The `Handlebars` namespace has been deprecated. `Handlebars.SafeString` is
|
||||
now `Spacebars.SafeString`, and `Handlebars.registerHelper` is now
|
||||
`UI.registerHelper`.
|
||||
|
||||
Patches contributed by GitHub users cmather and mart-jansink.
|
||||
|
||||
|
||||
## v0.7.2
|
||||
|
||||
|
||||
@@ -554,6 +554,14 @@ bcrypt: https://github.com/ncb000gt/node.bcrypt.js
|
||||
Copyright (c) 2010 Nicholas Campbell
|
||||
|
||||
|
||||
----------
|
||||
html5: https://github.com/aredridel/html5
|
||||
----------
|
||||
|
||||
Copyright (c) 2010 Aria Stewart <aredridel@nbtsc.org>
|
||||
|
||||
|
||||
|
||||
==============
|
||||
Apache License
|
||||
==============
|
||||
|
||||
@@ -12,4 +12,3 @@ jquery-waypoints
|
||||
less
|
||||
spiderable
|
||||
appcache
|
||||
handlebars
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.2
|
||||
0.8.0.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="api">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h1 id="api">The Meteor API</h1>
|
||||
|
||||
@@ -455,7 +455,7 @@ the server. The return value is an object with the following fields:
|
||||
|
||||
Instead of using callbacks to notify you on changes, this is
|
||||
a [reactive](#reactivity) data source. You can use it in a
|
||||
[template](#templates) or [computation](#deps_autorun)
|
||||
[template](#livehtmltemplates) or [computation](#deps_autorun)
|
||||
to get realtime updates.
|
||||
|
||||
{{> api_box reconnect}}
|
||||
@@ -1109,15 +1109,6 @@ the matching documents.
|
||||
|
||||
{{> api_box cursor_count}}
|
||||
|
||||
// Display a count of posts matching certain criteria. Automatically
|
||||
// keep it updated as the database changes.
|
||||
var frag = Meteor.render(function () {
|
||||
var highScoring = Posts.find({score: {$gt: 10}});
|
||||
return "<p>There are " + highScoring.count() + " posts with " +
|
||||
"scores greater than 10</p>";
|
||||
});
|
||||
document.body.appendChild(frag);
|
||||
|
||||
Unlike the other functions, `count` registers a dependency only on the
|
||||
number of matching documents. (Updates that just change or reorder the
|
||||
documents in the result set will not trigger a recomputation.)
|
||||
@@ -1142,31 +1133,31 @@ applicable. If you only need to receive the fields that changed, see
|
||||
<dt><span class="name">added(document)</span> <span class="or">or</span></dt>
|
||||
<dt><span class="name">addedAt(document, atIndex, before)</span></dt>
|
||||
<dd>
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
A new document `document` entered the result set. The new document
|
||||
appears at position `atIndex`. It is immediately before the document
|
||||
whose `_id` is `before`. `before` will be `null` if the new document
|
||||
is at the end of the results.
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</dd>
|
||||
|
||||
<dt><span class="name">changed(newDocument, oldDocument)
|
||||
<span class="or">or</span></span></dt>
|
||||
<dt><span class="name">changedAt(newDocument, oldDocument, atIndex)</span></dt>
|
||||
<dd>
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
The contents of a document were previously `oldDocument` and are now
|
||||
`newDocument`. The position of the changed document is `atIndex`.
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</dd>
|
||||
|
||||
<dt><span class="name">removed(oldDocument)</span>
|
||||
<span class="or">or</span></dt>
|
||||
<dt><span class="name">removedAt(oldDocument, atIndex)</span></dt>
|
||||
<dd>
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
The document `oldDocument` is no longer in the result set. It used to be at position `atIndex`.
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</dd>
|
||||
|
||||
{{#dtdd "movedTo(document, fromIndex, toIndex, before)"}}
|
||||
@@ -1207,12 +1198,12 @@ result set, not the entire contents of the document that changed.
|
||||
<span class="or">or</span></dt>
|
||||
<dt><span class="name">addedBefore(id, fields, before)</span></dt>
|
||||
<dd>
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
A new document entered the result set. It has the `id` and `fields`
|
||||
specified. `fields` contains all fields of the document excluding the
|
||||
`_id` field. The new document is before the document identified by
|
||||
`before`, or at the end if `before` is `null`.
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</dd>
|
||||
|
||||
{{#dtdd "changed(id, fields)"}}
|
||||
@@ -1287,7 +1278,7 @@ that needs to deal with `_id` fields that may be either strings or `ObjectID`s,
|
||||
method, since Meteor currently constructs them fully randomly.
|
||||
{{/note}}
|
||||
|
||||
{{#api_box_inline selectors}}
|
||||
{{#api_box selectors}}
|
||||
|
||||
The simplest selectors are just a string or
|
||||
[`Meteor.Collection.ObjectID`](#collection_object_id). These selectors match the
|
||||
@@ -1319,9 +1310,9 @@ But they can also contain more complicated tests:
|
||||
See the [complete
|
||||
documentation](http://docs.mongodb.org/manual/reference/operator/).
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
{{#api_box_inline modifiers}}
|
||||
{{#api_box modifiers}}
|
||||
|
||||
A modifier is an object that describes how to update a document in
|
||||
place by changing some of its fields. Some examples:
|
||||
@@ -1344,9 +1335,9 @@ supported by [validated updates](#allow).)
|
||||
See the [full list of
|
||||
modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations).
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
{{#api_box_inline sortspecifiers}}
|
||||
{{#api_box sortspecifiers}}
|
||||
|
||||
Sorts may be specified using your choice of several syntaxes:
|
||||
|
||||
@@ -1361,9 +1352,9 @@ The last form will only work if your JavaScript implementation
|
||||
preserves the order of keys in objects. Most do, most of the time, but
|
||||
it's up to you to be sure.
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
{{#api_box_inline fieldspecifiers}}
|
||||
{{#api_box fieldspecifiers}}
|
||||
|
||||
Queries can specify a particular set of fields to include or exclude from the
|
||||
result object.
|
||||
@@ -1406,7 +1397,7 @@ A more advanced example:
|
||||
See <a href="http://docs.mongodb.org/manual/tutorial/project-fields-from-query-results/#projection">
|
||||
the MongoDB docs</a> for details of the nested field rules and array behavior.
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
<h2 id="session"><span>Session</span></h2>
|
||||
|
||||
@@ -1419,7 +1410,7 @@ you call [`Session.get`](#session_get)`("currentList")`
|
||||
from inside a template, the template will automatically be rerendered
|
||||
whenever [`Session.set`](#session_set)`("currentList", x)` is called.
|
||||
|
||||
{{> api_box set}}
|
||||
{{> api_box session_set}}
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1436,21 +1427,28 @@ Example:
|
||||
This is useful in initialization code, to avoid re-initializing a session
|
||||
variable every time a new version of your app is loaded.
|
||||
|
||||
{{> api_box get}}
|
||||
{{> api_box session_get}}
|
||||
|
||||
Example:
|
||||
|
||||
Session.set("enemy", "Eastasia");
|
||||
var frag = Meteor.render(function () {
|
||||
return "<p>We've always been at war with " +
|
||||
Session.get("enemy") + "</p>";
|
||||
// in main.html
|
||||
{{lt}}template name="main">
|
||||
<p>We've always been at war with {{dstache}}theEnemy}}.</p>
|
||||
{{lt}}/template>
|
||||
|
||||
// in main.js
|
||||
Template.main.helpers({
|
||||
theEnemy: function () {
|
||||
return Session.get("enemy");
|
||||
}
|
||||
});
|
||||
|
||||
Session.set("enemy", "Eastasia");
|
||||
// Page will say "We've always been at war with Eastasia"
|
||||
document.body.append(frag);
|
||||
|
||||
// Page will change to say "We've always been at war with Eurasia"
|
||||
Session.set("enemy", "Eurasia");
|
||||
// Page will change to say "We've always been at war with Eurasia"
|
||||
|
||||
|
||||
{{> api_box equals}}
|
||||
|
||||
@@ -1464,7 +1462,7 @@ If value is a scalar, then these two expressions do the same thing:
|
||||
|
||||
Example:
|
||||
|
||||
<template name="postsView">
|
||||
{{lt}}template name="postsView">
|
||||
{{dstache}}! Show a dynamically updating list of items. Let the user click on an
|
||||
item to select it. The selected item is given a CSS class so it
|
||||
can be rendered differently. }}
|
||||
@@ -1472,11 +1470,11 @@ Example:
|
||||
{{dstache}}#each posts}}
|
||||
{{dstache}}> postItem }}
|
||||
{{dstache}}/each}}
|
||||
</{{! }}template>
|
||||
{{lt}}/template>
|
||||
|
||||
<template name="postItem">
|
||||
{{lt}}template name="postItem">
|
||||
<div class="{{dstache}}postClass}}">{{dstache}}title}}</div>
|
||||
</{{! }}template>
|
||||
{{lt}}/template>
|
||||
|
||||
///// in JS file
|
||||
Template.postsView.posts = function() {
|
||||
@@ -1712,6 +1710,11 @@ example, to support GitHub login, run `$ meteor add accounts-github` and use the
|
||||
Session.set('errorMessage', err.reason || 'Unknown error');
|
||||
});
|
||||
|
||||
Login service configuration is sent from the server to the client over DDP when
|
||||
your app starts up; you may not call the login function until the configuration
|
||||
is loaded. The function `Accounts.loginServicesConfigured()` is a reactive data
|
||||
source that will return true once the login service is configured; you should
|
||||
not make login buttons visible or active until it is true.
|
||||
|
||||
|
||||
{{> api_box currentUser}}
|
||||
@@ -1850,10 +1853,18 @@ proceed. If the callback returns a falsy value or throws an
|
||||
exception, the login is aborted. Throwing a `Meteor.Error` will
|
||||
report the error reason to the user.
|
||||
|
||||
All registered validate login callbacks are called, even if one of the
|
||||
callbacks aborts the login. The later callbacks will see the
|
||||
`allowed` field set to `false` since the login will now not be
|
||||
successful.
|
||||
All registered validate login callbacks are called, even if one of the callbacks
|
||||
aborts the login. The later callbacks will see the `allowed` field set to
|
||||
`false` since the login will now not be successful. This allows later callbacks
|
||||
to override an error from a previous callback; for example, you could override
|
||||
the "Incorrect password" error with a different message.
|
||||
|
||||
Validate login callbacks that aren't explicitly trying to override a previous
|
||||
error generally have no need to run if the attempt has already been determined
|
||||
to fail, and should start with
|
||||
|
||||
if (!attempt.allowed)
|
||||
return false;
|
||||
|
||||
|
||||
{{> api_box accounts_onLogin}}
|
||||
@@ -2011,11 +2022,19 @@ Example:
|
||||
|
||||
<h2 id="templates_api"><span>Templates</span></h2>
|
||||
|
||||
A template that you declare as `<{{! }}template name="foo"> ... </{{!
|
||||
}}template>` can be accessed as the function `Template.foo`, which
|
||||
returns a string of HTML when called.
|
||||
|
||||
The same template may occur many times on the page, and these
|
||||
When you write a template as `<{{! }}template name="foo"> ... <{{!
|
||||
}}/template>` in an HTML file in your app, Meteor generates a
|
||||
"component object" named `Template.foo`.
|
||||
|
||||
{{#note}}
|
||||
Meteor's component API is currently in flux. This section documents a few
|
||||
features of the component object that are useful for writing apps; a future
|
||||
release will elaborate more about how components work and about how to build
|
||||
components that aren't just template.
|
||||
{{/note}}
|
||||
|
||||
The same template may occur many times on a page, and these
|
||||
occurrences are called template instances. Template instances have a
|
||||
life cycle of being created, put into the document, and later taken
|
||||
out of the document and destroyed. Meteor manages these stages for
|
||||
@@ -2024,61 +2043,6 @@ or replaced and should be cleaned up. You can associate data with a
|
||||
template instance, and you can access its DOM nodes when it is in the
|
||||
document.
|
||||
|
||||
Additionally, Meteor will maintain a template instance and its state
|
||||
even if its surrounding HTML is re-rendered into new DOM nodes. As
|
||||
long as the structure of template invocations is the same, Meteor will
|
||||
not consider any instances to have been created or destroyed. You can
|
||||
request that the same DOM nodes be retained as well using `preserve`
|
||||
and `constant`.
|
||||
|
||||
There are a number of callbacks and directives that you can specify on
|
||||
a named template and that apply to all instances of the template.
|
||||
They are described below.
|
||||
|
||||
{{> api_box template_call}}
|
||||
|
||||
When called inside a template helper, the body of `Meteor.render`, or
|
||||
other settings where reactive HTML is being generated, the resulting
|
||||
HTML is annotated so that it renders as reactive DOM elements.
|
||||
Otherwise, the HTML is unadorned and static.
|
||||
|
||||
|
||||
{{> api_box template_rendered}}
|
||||
|
||||
This callback is called once when an instance of Template.*myTemplate* is
|
||||
rendered into DOM nodes and put into the document for the first time, and again
|
||||
each time any part of the template is re-rendered.
|
||||
|
||||
In the body of the callback, `this` is a [template
|
||||
instance](#template_inst) object that is unique to this occurrence of
|
||||
the template and persists across re-renderings. Use the `created` and
|
||||
`destroyed` callbacks to perform initialization or clean-up on the
|
||||
object.
|
||||
|
||||
{{> api_box template_created}}
|
||||
|
||||
This callback is called when an invocation of *myTemplate* represents
|
||||
a new occurrence of the template and not a re-rendering of an existing
|
||||
template instance. Inside the callback, `this` is the new [template
|
||||
instance](#template_inst) object. Properties you set on this object
|
||||
will be visible from the `rendered` and `destroyed` callbacks and from
|
||||
event handlers.
|
||||
|
||||
This callback fires once and is the first callback to fire. Every
|
||||
`created` has a corresponding `destroyed`; that is, if you get a
|
||||
`created` callback with a certain template instance object in `this`,
|
||||
you will eventually get a `destroyed` callback for the same object.
|
||||
|
||||
{{> api_box template_destroyed}}
|
||||
|
||||
This callback is called when an occurrence of a template is taken off
|
||||
the page for any reason and not replaced with a re-rendering. Inside
|
||||
the callback, `this` is the [template instance](#template_inst) object
|
||||
being destroyed.
|
||||
|
||||
This callback is most useful for cleaning up or undoing any external
|
||||
effects of `created`. It fires once and is the last callback to fire.
|
||||
|
||||
|
||||
{{> api_box template_events}}
|
||||
|
||||
@@ -2101,104 +2065,83 @@ Example:
|
||||
}
|
||||
});
|
||||
|
||||
In Handlebars, this helper would then be invoked as `{{dstache}}foo}}`.
|
||||
Now you can invoke this helper with `{{dstache}}foo}}` in the template defined
|
||||
with `<{{! }}template name="myTemplate">`.
|
||||
|
||||
The following syntax is equivalent, but won't work for reserved property
|
||||
names:
|
||||
The following syntax is a shorthand for when you only have one helper to define:
|
||||
|
||||
Template.myTemplate.foo = function () {
|
||||
return Session.get("foo");
|
||||
};
|
||||
|
||||
{{> api_box template_preserve}}
|
||||
To create a helper that can be used in any template, use
|
||||
[`UI.registerHelper`](#ui_registerhelper).
|
||||
|
||||
You can "preserve" a DOM element during re-rendering, leaving the
|
||||
existing element in place in the document while replacing the
|
||||
surrounding HTML. This means that re-rendering a template need not
|
||||
disturb text fields, iframes, and other sensitive elements it
|
||||
contains. The elements to preserve must be present both as nodes in
|
||||
the old DOM and as tags in the new HTML. Meteor will patch the DOM
|
||||
around the preserved elements.
|
||||
|
||||
{{> api_box template_rendered}}
|
||||
|
||||
This callback is called once when an instance of Template.*myTemplate* is
|
||||
rendered into DOM nodes and put into the document for the first time.
|
||||
|
||||
In the body of the callback, `this` is a [template
|
||||
instance](#template_inst) object that is unique to this occurrence of
|
||||
the template and persists across re-renderings. Use the `created` and
|
||||
`destroyed` callbacks to perform initialization or clean-up on the
|
||||
object.
|
||||
|
||||
Because your template has been rendered, you can use functions like
|
||||
`this.findAll` which look at its DOM nodes.
|
||||
|
||||
{{> api_box template_created}}
|
||||
|
||||
This callback is called before your template's logic is evaluated for the first
|
||||
time. Inside the callback, `this` is the new [template
|
||||
instance](#template_inst) object. Properties you set on this object will be
|
||||
visible from the `rendered` and `destroyed` callbacks and from event handlers.
|
||||
|
||||
This callback fires once and is the first callback to fire. Every
|
||||
`created` has a corresponding `destroyed`; that is, if you get a
|
||||
`created` callback with a certain template instance object in `this`,
|
||||
you will eventually get a `destroyed` callback for the same object.
|
||||
|
||||
{{#note}}
|
||||
By default, new Meteor apps automatically include the
|
||||
`preserve-inputs` package. This preserves all elements of type
|
||||
`input`, `textarea`, `button`, `select`, and `option` that have unique
|
||||
`id` attributes or that have `name` attributes that are unique within
|
||||
an enclosing element with an `id` attribute. To turn off this default
|
||||
behavior, simply remove the `preserve-inputs` package.
|
||||
The `created` callback is not currently very useful. In a later release, the
|
||||
template instance object (or something like it) will be visible from helper
|
||||
functions, and `created` will be a useful way to set up values that are read
|
||||
from helpers. For now, you probably just want to use `rendered`.
|
||||
{{/note}}
|
||||
|
||||
Preservation is useful in a variety of cases where replacing a DOM
|
||||
element with an identical or modified element would not have the same
|
||||
effect as retaining the original element. These include:
|
||||
{{> api_box template_destroyed}}
|
||||
|
||||
* Input text fields and other form controls
|
||||
* Elements with CSS animations
|
||||
* Iframes
|
||||
* Nodes with references kept in JavaScript code
|
||||
This callback is called when an occurrence of a template is taken off
|
||||
the page for any reason and not replaced with a re-rendering. Inside
|
||||
the callback, `this` is the [template instance](#template_inst) object
|
||||
being destroyed.
|
||||
|
||||
If you want to preserve a whole region of the DOM, an element and its
|
||||
children, or nodes not rendered by Meteor, use a [constant
|
||||
region](#constant) instead.
|
||||
|
||||
To preserve nodes, pass a list of selectors, each of which should match
|
||||
at most one element in the template. When the template is re-rendered,
|
||||
the selector is run on the old DOM and the new DOM, and Meteor will
|
||||
reuse the old element in place while working in any HTML changes around
|
||||
it.
|
||||
|
||||
A second form of `preserve` takes a labeling function for each selector
|
||||
and allows the selectors to match multiple nodes. The node-labeling
|
||||
function takes a node and returns a label string that is unique for each
|
||||
node, or `false` to exclude the node from preservation.
|
||||
|
||||
For example, to preserve all `<input>` elements with ids in template 'foo', use:
|
||||
|
||||
Template.foo.preserve({
|
||||
'input[id]': function (node) { return node.id; }
|
||||
});
|
||||
|
||||
Selectors are interpreted as rooted at the top level of the template.
|
||||
Each occurrence of the template operates independently, so the selectors
|
||||
do not have to be unique on the entire page, only within one occurrence
|
||||
of the template. Selectors will match nodes even if they are in
|
||||
sub-templates.
|
||||
|
||||
Preserving a node does *not* preserve its attributes or contents. They
|
||||
will be updated to reflect the new HTML. Text in input fields is not
|
||||
preserved unless the input field has focus, in which case the cursor and
|
||||
selection are left intact. Iframes retain their navigation state and
|
||||
animations continue to run as long as their parameters haven't changed.
|
||||
|
||||
There are some cases where nodes can not be preserved because of
|
||||
constraints inherent in the DOM API. For example, an element's tag name
|
||||
can't be changed, and it can't be moved relative to its parent or other
|
||||
preserved nodes. For this reason, nodes that are re-ordered or
|
||||
re-parented by an update will not be preserved.
|
||||
|
||||
{{#note}}
|
||||
Previous versions of Meteor had an implicit page-wide `preserve`
|
||||
directive that labeled nodes by their "id" and "name" attributes.
|
||||
This has been removed in favor of the explicit, opt-in mechanism.
|
||||
{{/note}}
|
||||
This callback is most useful for cleaning up or undoing any external effects of
|
||||
`created` or `rendered`. It fires once and is the last callback to fire.
|
||||
|
||||
|
||||
<h2 id="template_inst"><span>Template instances</span></h2>
|
||||
|
||||
A template instance object represents an occurrence of a template in
|
||||
the document. It can be used to access the DOM and it can be
|
||||
assigned properties that persist across page re-renderings.
|
||||
assigned properties that persist as the template is reactively updated.
|
||||
|
||||
Template instance objects are found as the value of `this` in the
|
||||
`created`, `rendered`, and `destroyed` template callbacks and as an
|
||||
`created`, `rendered`, and `destroyed` template callbacks, and as an
|
||||
argument to event handlers.
|
||||
|
||||
In addition to the properties and functions described below, you can
|
||||
assign additional properties of your choice to the object. Property names
|
||||
starting with `_` are guaranteed to be available for your use. Use
|
||||
the `created` and `destroyed` callbacks to perform initialization or
|
||||
clean-up on the object.
|
||||
{{#note}}
|
||||
You cannot currently access the template instance object from helpers.
|
||||
We plan to refactor how template instances work and make them more
|
||||
universally accessible.
|
||||
{{/note}}
|
||||
|
||||
In addition to the properties and functions described below, you can assign
|
||||
additional properties of your choice to the object. Use the
|
||||
[`created`](#template_created) and [`destroyed`](#template_destroyed) callbacks
|
||||
to perform initialization or clean-up on the object.
|
||||
|
||||
You can only access `findAll`, `find`, `firstNode`, and `lastNode`
|
||||
from the `rendered` callback and event handlers, not from `created`
|
||||
@@ -2207,7 +2150,11 @@ in the DOM.
|
||||
|
||||
{{> api_box template_findAll}}
|
||||
|
||||
Returns an array of DOM elements matching `selector`.
|
||||
Returns a [jQuery object](http://api.jquery.com/Types/#jQuery) of DOM elements
|
||||
matching `selector`. This object is similar to an array but has other methods
|
||||
defined by the jQuery library.
|
||||
|
||||
You can also call this function as `this.$(selector)`.
|
||||
|
||||
The template instance serves as the document root for the selector. Only
|
||||
elements inside the template and its sub-templates can match parts of
|
||||
@@ -2239,88 +2186,50 @@ the template. It is updated each time the template is re-rendered.
|
||||
Access is read-only and non-reactive.
|
||||
|
||||
|
||||
{{> api_box render}}
|
||||
<h2 id="ui"><span>Template utilities</span></h2>
|
||||
|
||||
`Meteor.render` creates a `DocumentFragment` (a sequence of DOM nodes)
|
||||
that automatically updates in realtime. Most Meteor apps don't need to
|
||||
call this directly; they use templates and Meteor handles the rendering.
|
||||
The `UI` namespace contains several utilities that are helpful when writing your
|
||||
user interface. `UI.registerHelper` allows you to define helper functions which
|
||||
you can use in every template. `UI.render`, `UI.renderWithData`, and
|
||||
`UI.insert` allow you to manually render templates and insert them directly into
|
||||
any part of the DOM for finer control than just using template inclusions.
|
||||
|
||||
Pass in `htmlFunc`, a function that returns an HTML
|
||||
string. `Meteor.render` calls the function and turns the output into
|
||||
DOM nodes. Meanwhile, it tracks the data that was used when `htmlFunc`
|
||||
ran, and automatically wires up callbacks so that whenever any of the
|
||||
data changes, `htmlFunc` is re-run and the DOM nodes are updated in
|
||||
place.
|
||||
{{> api_box ui_registerhelper}}
|
||||
|
||||
You may insert the returned `DocumentFragment` directly into the DOM
|
||||
wherever you would like it to appear. The inserted nodes will continue
|
||||
to update until they are taken off the screen. Then they will be
|
||||
automatically cleaned up. For more details about clean-up, see
|
||||
[`Deps.flush`](#deps_flush).
|
||||
{{> api_box ui_body}}
|
||||
|
||||
`Meteor.render` tracks the data dependencies of `htmlFunc` by running
|
||||
it in a reactive computation, so it can respond to changes in any reactive
|
||||
data sources used by that function. For more information, or to learn
|
||||
how to make your own reactive data sources, see
|
||||
[Reactivity](#reactivity).
|
||||
You can define helpers and event maps on `UI.body` just like on any
|
||||
`Template.myTemplate` object.
|
||||
|
||||
Example:
|
||||
{{> api_box ui_render}}
|
||||
|
||||
// Client side: show the number of players online.
|
||||
var frag = Meteor.render(function () {
|
||||
return "<p>There are " + Players.find({online: true}).count() +
|
||||
" players online.</p>";
|
||||
});
|
||||
document.body.appendChild(frag);
|
||||
This returns an "instantiated component" object, which can be passed to
|
||||
[`UI.insert`](#ui_insert). The template's [`created`](#template_created) callback
|
||||
will be invoked. The component will continue to be updated reactively as the
|
||||
data used changes.
|
||||
|
||||
// Server side: find all players that have been idle for a while,
|
||||
// and mark them as offline. The count on the screen will
|
||||
// automatically update on all clients.
|
||||
Players.update({idleTime: {$gt: 30}}, {$set: {online: false}});
|
||||
{{#warning}}
|
||||
Future releases will provide a richer API for "instantiated components"
|
||||
(probably unifying them with "template instances"). For now, all you can
|
||||
do with them is pass them to `UI.insert`.
|
||||
|
||||
{{> api_box renderList}}
|
||||
Most users will not need to manually render components or manually insert them
|
||||
into the DOM at all. As of 0.8.0, if you call `UI.render` and never insert
|
||||
the result into the DOM, the logic to keep the instantiated component updated
|
||||
will continue running in your browser forever. Additionally, if you remove any
|
||||
part of your DOM using any mechanism other than jQuery, the logic to keep that
|
||||
part of the the DOM updated will continue running. To avoid these issues,
|
||||
either avoid directly updating the DOM or ensure that any removals go through
|
||||
jQuery.
|
||||
{{/warning}}
|
||||
|
||||
Creates a `DocumentFragment` that automatically updates as the results
|
||||
of a database query change. Most Meteor apps use `{{dstache}}#each}}` in
|
||||
a template instead of calling this directly.
|
||||
{{> api_box ui_renderwithdata}}
|
||||
|
||||
`renderList` is more efficient than using `Meteor.render` to render HTML
|
||||
for a list of documents. For example, if a new document is created in
|
||||
the database that matches the query, a new item will be rendered and
|
||||
inserted at the appropriate place in the DOM without re-rendering the
|
||||
other elements. Similarly, if a document changes position in a sorted
|
||||
query, the DOM nodes will simply be moved and not re-rendered.
|
||||
|
||||
`docFunc` is called as needed to generate HTML for each document. If
|
||||
you provide `elseFunc`, then whenever the query returns no results, it
|
||||
will be called to render alternate content. You might use this to show
|
||||
a message like "No records match your query."
|
||||
|
||||
Each call to `docFunc` or `elseFunc` is run in its own reactive
|
||||
computation so that if it has other external data dependencies, it will be
|
||||
individually re-run when the data changes.
|
||||
|
||||
Example:
|
||||
|
||||
// List the titles of all of the posts that have the tag
|
||||
// "frontpage". Keep the list updated as new posts are made, as tags
|
||||
// change, etc. Display the selected post differently.
|
||||
var frag = Meteor.renderList(
|
||||
Posts.find({tags: "frontpage"}),
|
||||
function(post) {
|
||||
var style = Session.equals("selectedId", post._id) ? "selected" : "";
|
||||
// A real app would need to quote/sanitize post.name
|
||||
return '<div class="' + style + '">' + post.name + '</div>';
|
||||
});
|
||||
document.body.appendChild(frag);
|
||||
|
||||
// Select a post. This will cause only the selected item and the
|
||||
// previously selected item to update.
|
||||
var somePost = Posts.findOne({tags: "frontpage"});
|
||||
Session.set("selectedId", somePost._id);
|
||||
{{> api_box ui_insert}}
|
||||
|
||||
|
||||
{{#api_box_inline eventmaps}}
|
||||
|
||||
{{#api_box eventmaps}}
|
||||
|
||||
Several functions take event maps. An event map is an object where
|
||||
the properties specify a set of events to handle, and the values are
|
||||
@@ -2348,8 +2257,8 @@ information about the event, and `template`, a [template
|
||||
instance](#template_inst) for the template where the handler is
|
||||
defined. The handler also receives some additional context data in
|
||||
`this`, depending on the context of the current element handling the
|
||||
event. In a Handlebars template, an element's context is the
|
||||
Handlebars data context where that element occurs, which is set by
|
||||
event. In a template, an element's context is the
|
||||
data context where that element occurs, which is set by
|
||||
block helpers such as `#with` and `#each`.
|
||||
|
||||
Example:
|
||||
@@ -2481,72 +2390,15 @@ catching typing in text fields, while `keydown` and `keyup` can be
|
||||
used for arrow keys or modifier keys.
|
||||
{{/dtdd}}
|
||||
|
||||
{{#dtdd "<code>tap</code>"}} Tap on an element. On touch-enabled
|
||||
devices, this is a replacement to `click` that fires immediately.
|
||||
These events are synthesized from `touchmove` and `touchend`.
|
||||
{{/dtdd}}
|
||||
|
||||
</dl>
|
||||
|
||||
Other DOM events are available as well, but for the events above,
|
||||
Meteor has taken some care to ensure that they work uniformly in all
|
||||
browsers.
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
|
||||
|
||||
{{#api_box_inline constant}}
|
||||
|
||||
You can mark a region of a template as "constant" and not subject to
|
||||
re-rendering using the
|
||||
`{{dstache}}#constant}}...{{dstache}}/constant}}` block helper.
|
||||
Content inside the `#constant` block helper is preserved exactly as-is
|
||||
even if the enclosing template is re-rendered. Changes to other parts
|
||||
of the template are patched in around it in the same manner as
|
||||
`preserve`. Unlike individual node preservation, a constant region
|
||||
retains not only the identities of its nodes but also their attributes
|
||||
and contents. The contents of the block will only be evaluated once
|
||||
per occurrence of the enclosing template.
|
||||
|
||||
Constant regions allow non-Meteor content to be embedded in a Meteor
|
||||
template. Many third-party widgets create and manage their own DOM
|
||||
nodes programmatically. Typically, you put an empty element in your
|
||||
template, which the widget or library will then populate with
|
||||
children. Normally, when Meteor re-renders the enclosing template it
|
||||
would remove the new children, since the template says it should be
|
||||
empty. If the container is wrapped in a `#constant` block, however, it
|
||||
is left alone; whatever content is currently in the DOM remains.
|
||||
|
||||
{{#note}}
|
||||
Constant regions are intended for embedding non-Meteor content.
|
||||
Event handlers and reactive dependencies don't currently work
|
||||
correctly inside constant regions.
|
||||
{{/note}}
|
||||
|
||||
|
||||
{{/api_box_inline}}
|
||||
|
||||
{{#api_box_inline isolate}}
|
||||
|
||||
Each template runs as its own reactive computation. When the template
|
||||
accesses a reactive data source, such as by calling `Session.get` or
|
||||
making a database query, this establishes a data dependency that will
|
||||
cause the whole template to be re-rendered when the data changes.
|
||||
This means that the amount of re-rendering for a particular change
|
||||
is affected by how you've divided your HTML into templates.
|
||||
|
||||
Typically, the exact extent of re-rendering is not crucial, but if you
|
||||
want more control, such as for performance reasons, you can use the
|
||||
`{{dstache}}#isolate}}...{{dstache}}/isolate}}` helper. Data
|
||||
dependencies established inside an `#isolate` block are localized to
|
||||
the block and will not in themselves cause the parent template to be
|
||||
re-rendered. This block helper essentially conveys the reactivity
|
||||
benefits you would get by pulling the content out into a new
|
||||
sub-template.
|
||||
|
||||
{{/api_box_inline}}
|
||||
|
||||
<h2 id="match"><span>Match</span></h2>
|
||||
|
||||
Meteor methods and publish functions take arbitrary [EJSON](#ejson) types as
|
||||
@@ -2581,7 +2433,7 @@ server logs but not revealed to the client.
|
||||
|
||||
{{> api_box match_test}}
|
||||
|
||||
{{#api_box_inline matchpatterns}}
|
||||
{{#api_box matchpatterns}}
|
||||
|
||||
The following patterns can be used as pattern arguments to `check` and `Match.test`:
|
||||
|
||||
@@ -2661,7 +2513,7 @@ from the call to `check` or `Match.test`. Examples:
|
||||
{{/dtdd}}
|
||||
</dl>
|
||||
|
||||
{{/api_box_inline}}
|
||||
{{/api_box}}
|
||||
|
||||
<h2 id="timers"><span>Timers</span></h2>
|
||||
|
||||
@@ -2780,14 +2632,6 @@ same flush until there is no more work to be done. Callbacks
|
||||
registered with [`Deps.afterFlush`](#deps_afterflush) are called
|
||||
after processing outstanding invalidations.
|
||||
|
||||
Any auto-updating DOM elements that are found to not be in the
|
||||
document during a flush may be cleaned up by Meteor (meaning that
|
||||
Meteor will stop tracking and updating the elements, so that the
|
||||
browser's garbage collector can delete them). So, if you manually
|
||||
call `flush`, you need to make sure that any auto-updating elements
|
||||
that you have created by calling [`Meteor.render`](#meteor_render)
|
||||
have already been inserted in the main DOM tree.
|
||||
|
||||
It is illegal to call `flush` from inside a `flush` or from a running
|
||||
computation.
|
||||
|
||||
@@ -3243,11 +3087,11 @@ by spammers.)
|
||||
'Hello from Meteor!',
|
||||
'This is a test of Email.send.');
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
|
||||
<h2 id="assets"><span>Assets</span></h2>
|
||||
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
`Assets` allows server code in a Meteor application to access static server
|
||||
assets, which are located in the `private` subdirectory of an application's
|
||||
tree.
|
||||
@@ -3261,7 +3105,7 @@ directory called `nested` with a file called `data.txt` inside it, then server
|
||||
code can read `data.txt` by running:
|
||||
|
||||
var data = Assets.getText('nested/data.txt');
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
|
||||
</template>
|
||||
|
||||
@@ -3272,6 +3116,7 @@ code can read `data.txt` by running:
|
||||
|
||||
<template name="api_box">
|
||||
<div class="api {{bare}}">
|
||||
|
||||
<h3 id="{{id}}">
|
||||
<a class="name selflink" href="#{{id}}">{{{name}}}</a>
|
||||
{{#if locus}}
|
||||
@@ -3280,7 +3125,7 @@ code can read `data.txt` by running:
|
||||
</h3>
|
||||
|
||||
<div class="desc">
|
||||
{{#each descr}}{{#better_markdown}}{{{this}}}{{/better_markdown}}{{/each}}
|
||||
{{#each descr}}{{#markdown}}{{{this}}}{{/markdown}}{{/each}}
|
||||
</div>
|
||||
|
||||
{{#if args}}
|
||||
@@ -3293,8 +3138,8 @@ code can read `data.txt` by running:
|
||||
{{> api_box_args options}}
|
||||
{{/if}}
|
||||
|
||||
{{#if body}}
|
||||
{{#better_markdown}}{{{body}}}{{/better_markdown}}
|
||||
{{#if UI.contentBlock}}
|
||||
{{#markdown}}{{> UI.contentBlock}}{{/markdown}}
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
@@ -3314,12 +3159,12 @@ code can read `data.txt` by running:
|
||||
{{{type}}}
|
||||
{{/if}}
|
||||
</span></dt>
|
||||
<dd>{{#better_markdown}}{{{descr}}}{{/better_markdown}}</dd>
|
||||
<dd>{{#markdown}}{{{descr}}}{{/markdown}}</dd>
|
||||
{{/each}}
|
||||
</dl>
|
||||
</template>
|
||||
|
||||
|
||||
<template name="api_section_helper">
|
||||
<template name="api_section">
|
||||
<h2 id="{{id}}"><a href="#{{id}}" class="selflink"><span>{{name}}</span></a></h2>
|
||||
</template>
|
||||
|
||||
@@ -58,12 +58,18 @@ Template.api.settings = {
|
||||
id: "meteor_settings",
|
||||
name: "Meteor.settings",
|
||||
locus: "Anywhere",
|
||||
descr: ["`Meteor.settings` contains deployment-specific configuration options. " +
|
||||
"You can initialize settings by passing the `--settings` option (which takes a file containing JSON data) to " +
|
||||
"`meteor run` or `meteor deploy`, " +
|
||||
"or by setting your server process's `METEOR_SETTINGS` environment variable to a JSON string. " +
|
||||
"If you don't provide any settings, `Meteor.settings` will be an empty object. If the settings object contains a key named `public`, then " +
|
||||
"`Meteor.settings.public` will be available on the client as well as the server. All other properties of `Meteor.settings` are only defined on the server."]
|
||||
descr: ["`Meteor.settings` contains deployment-specific configuration " +
|
||||
"options. You can initialize settings by passing the `--settings` " +
|
||||
"option (which takes the name of a file containing JSON data) to " +
|
||||
"`meteor run` or `meteor deploy`. When running your server " +
|
||||
"directly (e.g. from a bundle), you instead specify settings by " +
|
||||
"putting the JSON directly into the `METEOR_SETTINGS` environment " +
|
||||
"variable. " +
|
||||
"If you don't provide any settings, `Meteor.settings` will be an " +
|
||||
"empty object. If the settings object contains a key named " +
|
||||
"`public`, then `Meteor.settings.public` will be available on the " +
|
||||
"client as well as the server. All other properties of " +
|
||||
"`Meteor.settings` are only defined on the server."]
|
||||
};
|
||||
|
||||
Template.api.release = {
|
||||
@@ -805,7 +811,7 @@ Template.api.cursor_observe_changes = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.id = {
|
||||
Template.api.random_id = {
|
||||
id: "meteor_id",
|
||||
name: "Random.id()",
|
||||
locus: "Anywhere",
|
||||
@@ -994,54 +1000,12 @@ Template.api.dependency_hasdependents = {
|
||||
// writeFence
|
||||
// invalidationCrossbar
|
||||
|
||||
Template.api.render = {
|
||||
id: "meteor_render",
|
||||
name: "Meteor.render(htmlFunc)",
|
||||
locus: "Client",
|
||||
descr: ["Create DOM nodes that automatically update themselves as data changes."],
|
||||
args: [
|
||||
{name: "htmlFunc",
|
||||
type: "Function returning a string of HTML",
|
||||
descr: "Function that generates HTML to be rendered. Called immediately and re-run whenever data changes. May also be a string of HTML instead of a function."}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.renderList = {
|
||||
id: "meteor_renderlist",
|
||||
name: "Meteor.renderList(observable, docFunc, [elseFunc])",
|
||||
locus: "Client",
|
||||
descr: ["Create DOM nodes that automatically update themselves based on the results of a database query."],
|
||||
args: [
|
||||
{name: "observable",
|
||||
type: "Cursor",
|
||||
type_link: "meteor_collection_cursor",
|
||||
descr: "Query cursor to observe as a reactive source of ordered documents."},
|
||||
{name: "docFunc",
|
||||
type: "Function taking a document and returning HTML",
|
||||
descr: "Render function to be called for each document."},
|
||||
{name: "elseFunc",
|
||||
type: "Function returning HTML",
|
||||
descr: "Optional. Render function to be called when query is empty."}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
Template.api.eventmaps = {
|
||||
id: "eventmaps",
|
||||
name: "Event Maps"
|
||||
};
|
||||
|
||||
Template.api.constant = {
|
||||
id: "constant",
|
||||
name: "Constant regions"
|
||||
};
|
||||
|
||||
Template.api.isolate = {
|
||||
id: "isolate",
|
||||
name: "Reactivity isolation"
|
||||
};
|
||||
|
||||
|
||||
|
||||
Template.api.user = {
|
||||
id: "meteor_user",
|
||||
@@ -1053,7 +1017,7 @@ Template.api.user = {
|
||||
Template.api.currentUser = {
|
||||
id: "template_currentuser",
|
||||
name: "{{currentUser}}",
|
||||
locus: "Handlebars templates",
|
||||
locus: "Templates",
|
||||
descr: ["Calls [Meteor.user()](#meteor_user). Use `{{#if currentUser}}` to check whether the user is logged in."]
|
||||
};
|
||||
|
||||
@@ -1082,7 +1046,7 @@ Template.api.loggingIn = {
|
||||
Template.api.loggingInTemplate = {
|
||||
id: "template_loggingin",
|
||||
name: "{{loggingIn}}",
|
||||
locus: "Handlebars templates",
|
||||
locus: "Templates",
|
||||
descr: ["Calls [Meteor.loggingIn()](#meteor_loggingin)."]
|
||||
};
|
||||
|
||||
@@ -1200,6 +1164,11 @@ Template.api.accounts_config = {
|
||||
name: "loginExpirationInDays",
|
||||
type: "Number",
|
||||
descr: "The number of days from when a user logs in until their token expires and they are logged out. Defaults to 90. Set to `null` to disable login expiration."
|
||||
},
|
||||
{
|
||||
name: "oauthSecretKey",
|
||||
type: "String",
|
||||
descr: "When using the `oauth-encryption` package, the 16 byte key using to encrypt sensitive account credentials in the database, encoded in base64. This option may only be specifed on the server. See packages/oauth-encryption/README.md for details."
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1208,7 +1177,7 @@ Template.api.accounts_ui_config = {
|
||||
id: "accounts_ui_config",
|
||||
name: "Accounts.ui.config(options)",
|
||||
locus: "Client",
|
||||
descr: ["Configure the behavior of [`{{loginButtons}}`](#accountsui)."],
|
||||
descr: ["Configure the behavior of [`{{> loginButtons}}`](#accountsui)."],
|
||||
options: [
|
||||
{
|
||||
name: "requestPermissions",
|
||||
@@ -1653,7 +1622,8 @@ Template.api.bindEnvironment = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.set = {
|
||||
// Can't name this '.set', since that's a method on components.
|
||||
Template.api.session_set = {
|
||||
id: "session_set",
|
||||
name: "Session.set(key, value)",
|
||||
locus: "Client",
|
||||
@@ -1683,7 +1653,8 @@ Template.api.setDefault = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.get = {
|
||||
// Can't name this '.get', since that's a method on components.
|
||||
Template.api.session_get = {
|
||||
id: "session_get",
|
||||
name: "Session.get(key)",
|
||||
locus: "Client",
|
||||
@@ -1718,7 +1689,7 @@ Template.api.httpcall = {
|
||||
args: [
|
||||
{name: "method",
|
||||
type: "String",
|
||||
descr: 'The HTTP method to use: "`GET`", "`POST`", "`PUT`", or "`DELETE`".'},
|
||||
descr: 'The [HTTP method](http://en.wikipedia.org/wiki/HTTP_method) to use, such as "`GET`", "`POST`", or "`HEAD`".'},
|
||||
{name: "url",
|
||||
type: "String",
|
||||
descr: 'The URL to retrieve.'},
|
||||
@@ -1753,7 +1724,7 @@ Template.api.httpcall = {
|
||||
descr: "Maximum time in milliseconds to wait for the request before failing. There is no timeout by default."},
|
||||
{name: "followRedirects",
|
||||
type: "Boolean",
|
||||
descr: "If true, transparently follow HTTP redirects. Cannot be set to false on the client."}
|
||||
descr: "If `true`, transparently follow HTTP redirects. Cannot be set to `false` on the client. Default `true`."}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1786,19 +1757,6 @@ Template.api.http_del = {
|
||||
};
|
||||
|
||||
|
||||
// XXX move these up to right place
|
||||
Template.api.template_call = {
|
||||
id: "template_call",
|
||||
name: "Template.<em>myTemplate</em>([data])",
|
||||
locus: "Client",
|
||||
descr: ["Call a template function by name to produce HTML."],
|
||||
args: [
|
||||
{name: "data",
|
||||
type: "Object",
|
||||
descr: 'Optional. The data context object with which to call the template.'}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.template_rendered = {
|
||||
id: "template_rendered",
|
||||
name: "Template.<em>myTemplate</em>.rendered = function ( ) { ... }",
|
||||
@@ -1845,18 +1803,6 @@ Template.api.template_helpers = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.template_preserve = {
|
||||
id: "template_preserve",
|
||||
name: "Template.<em>myTemplate</em>.preserve(selectors)",
|
||||
locus: "Client",
|
||||
descr: ["Specify rules for preserving individual DOM elements on re-render."],
|
||||
args: [
|
||||
{name: "selectors",
|
||||
type: "Array or Object",
|
||||
descr: "Array of CSS selectors that each match at most one element, such as `['.thing1', '.thing2']`, or, alternatively, a dictionary of selectors and node-labeling functions (see below)."}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.template_findAll = {
|
||||
id: "template_findAll",
|
||||
name: "<em>this</em>.findAll(selector)",
|
||||
@@ -1902,6 +1848,80 @@ Template.api.template_data = {
|
||||
descr: ["The data context of this instance's latest invocation."]
|
||||
};
|
||||
|
||||
|
||||
Template.api.ui_registerhelper = {
|
||||
id: "ui_registerhelper",
|
||||
name: "UI.registerHelper(name, function)",
|
||||
locus: "Client",
|
||||
descr: ["Defines a [helper function](#template_helpers) which can be used from all templates."],
|
||||
args: [
|
||||
{name: "name",
|
||||
type: "String",
|
||||
descr: "The name of the helper function you are defining."
|
||||
},
|
||||
{name: "function",
|
||||
type: "Function",
|
||||
descr: "The helper function itself."
|
||||
}]
|
||||
};
|
||||
|
||||
Template.api.ui_body = {
|
||||
id: "ui_body",
|
||||
name: "UI.body",
|
||||
locus: "Client",
|
||||
descr: ["The [component object](#templates_api) representing your `<body>` tag."]
|
||||
};
|
||||
|
||||
Template.api.ui_render = {
|
||||
id: "ui_render",
|
||||
name: "UI.render(Template.<em>myTemplate</em>)",
|
||||
locus: "Client",
|
||||
descr: ["Executes a template's logic."],
|
||||
args: [
|
||||
{name: "template",
|
||||
type: "Template",
|
||||
descr: "The particular template to evaluate."
|
||||
}]
|
||||
};
|
||||
|
||||
Template.api.ui_renderwithdata = {
|
||||
id: "ui_renderwithdata",
|
||||
name: "UI.renderWithData(Template.<em>myTemplate</em>, data)",
|
||||
locus: "Client",
|
||||
descr: ["Executes a template's logic with a data context. Otherwise identical to `UI.render`."],
|
||||
args: [
|
||||
{name: "template",
|
||||
type: "Template",
|
||||
descr: "The particular template to evaluate."
|
||||
},
|
||||
{name: "data",
|
||||
type: "Object",
|
||||
descr: "The data context that will be used when evaluating the template."
|
||||
}]
|
||||
};
|
||||
|
||||
Template.api.ui_insert = {
|
||||
id: "ui_insert",
|
||||
name: "UI.insert(instantiatedComponent, parentNode[, nextNode])",
|
||||
locus: "Client",
|
||||
descr: ["Inserts an instantiated component into the DOM and calls its [`rendered`](#template_rendered) callback."],
|
||||
args: [
|
||||
{name: "instantiatedComponent",
|
||||
type: "Instantiated component object",
|
||||
descr: "The return value from `UI.render` or `UI.renderWithData`."
|
||||
},
|
||||
{name: "parentNode",
|
||||
type: "DOM Node",
|
||||
descr: "The node that will be the parent of the rendered template."
|
||||
},
|
||||
{name: "nextNode",
|
||||
type: "DOM Node",
|
||||
descr: "If provided, must be a child of <em>parentNode</em>; the template will be inserted before this node. If not provided, the template will be inserted as the last child."
|
||||
}]
|
||||
};
|
||||
|
||||
|
||||
|
||||
var rfc = function (descr) {
|
||||
return '[RFC5322](http://tools.ietf.org/html/rfc5322) ' + descr;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template name="commandline">
|
||||
<div>
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
{{#api_section "commandline"}}Command line{{/api_section}}
|
||||
|
||||
@@ -188,6 +188,6 @@ instead of deploying to Meteor's servers. You will have to deal with
|
||||
logging, monitoring, backups, load-balancing, etc, all of which we
|
||||
handle for you if you use `meteor deploy`.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,8 +11,7 @@ when writing those apps.
|
||||
{{> structure }}
|
||||
{{> data }}
|
||||
{{> reactivity }}
|
||||
{{> livehtml }}
|
||||
{{> templates }}
|
||||
{{> livehtmltemplates }}
|
||||
{{> packages_concept }}
|
||||
{{> namespacing }}
|
||||
{{> deploying }}
|
||||
@@ -21,7 +20,7 @@ when writing those apps.
|
||||
|
||||
<template name="whatismeteor">
|
||||
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="whatismeteor">What is Meteor?</h2>
|
||||
|
||||
@@ -69,11 +68,11 @@ In Meteor 1.0, the `meteor` build tool will have full support for
|
||||
Atmosphere.
|
||||
{{/note}}
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
<template name="structure">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="structuringyourapp">Structuring your application</h2>
|
||||
|
||||
@@ -139,7 +138,7 @@ initial page load.
|
||||
Template sections, on the other hand, are converted into JavaScript
|
||||
functions, available under the `Template` namespace. It's
|
||||
a really convenient way to ship HTML templates to the client.
|
||||
See the [templates](#templates) section for more.
|
||||
See the [templates](#livehtmltemplates) section for more.
|
||||
|
||||
Lastly, the Meteor server will serve any files under the `public`
|
||||
directory, just like in a Rails or Django project. This is the place
|
||||
@@ -167,11 +166,11 @@ application are loaded according to these rules:
|
||||
* Finally, all files that match `main.*` are moved after everything else
|
||||
(preserving their order).
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
<template name="data">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="dataandsecurity">Data and security</h2>
|
||||
|
||||
@@ -347,11 +346,11 @@ and publish functions validate all of their arguments. Just run
|
||||
method or publish function which skips `check`ing any of its arguments will fail
|
||||
with an exception.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
<template name="reactivity">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="reactivity">Reactivity</h2>
|
||||
|
||||
@@ -387,9 +386,9 @@ error-prone logic.
|
||||
|
||||
These Meteor functions run your code as a reactive computation:
|
||||
|
||||
* [Templates](#templates)
|
||||
* [`Meteor.render`](#meteor_render) and [`Meteor.renderList`](#meteor_renderlist)
|
||||
* [Templates](#livehtmltemplates)
|
||||
* [`Deps.autorun`](#deps_autorun)
|
||||
* [`UI.render`](#ui_render) and [`UI.renderWithData`](#ui_renderwithdata)
|
||||
|
||||
And the reactive data sources that can trigger changes are:
|
||||
|
||||
@@ -414,150 +413,98 @@ Meteor's
|
||||
is a package called [`Deps`](#deps) that is fairly short and straightforward.
|
||||
You can use it yourself to implement new reactive data sources.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
<template name="livehtml">
|
||||
{{#better_markdown}}
|
||||
<template name="livehtmltemplates">
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="livehtml">Live HTML</h2>
|
||||
<h2 id="livehtmltemplates">Live HTML templates</h2>
|
||||
|
||||
HTML templating is central to web applications. With Meteor's live
|
||||
HTML templating is central to web applications. With Blaze, Meteor's live
|
||||
page update technology, you can render your HTML _reactively_, meaning
|
||||
that it will update automatically to track changes in the data used to
|
||||
generate it.
|
||||
|
||||
This optional feature works with any HTML templating library, or even
|
||||
with HTML you generate manually from JavaScript. Here's an example:
|
||||
Meteor makes it easy to use your favorite HTML templating language along with
|
||||
Meteor's live page update technology. Just write your template as you normally
|
||||
would, and Meteor will take care of making it update in realtime.
|
||||
|
||||
var fragment = Meteor.render(
|
||||
function () {
|
||||
var name = Session.get("name") || "Anonymous";
|
||||
return "<div>Hello, " + name + "</div>";
|
||||
});
|
||||
document.body.appendChild(fragment);
|
||||
Meteor ships with a templating language called
|
||||
[Spacebars](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md),
|
||||
inspired by [Handlebars](http://handlebarsjs.com/). It shares some of the
|
||||
spirit and syntax of Handlebars, but it has been tailored to produce reactive
|
||||
Meteor templates when compiled.
|
||||
|
||||
Session.set("name", "Bob"); // page updates automatically!
|
||||
{{#note}}
|
||||
Today, the only templating system that ships with Meteor is Spacebars, though
|
||||
our community has created packages for other languages such as
|
||||
[Jade](https://atmospherejs.com/package/jade).
|
||||
{{/note}}
|
||||
|
||||
[`Meteor.render`](#meteor_render) takes a rendering function, that is, a
|
||||
function that returns some HTML as a string. It returns an auto-updating
|
||||
`DocumentFragment`. When there is a change to data used by the rendering
|
||||
function, it is re-run. The DOM nodes in the `DocumentFragment` then
|
||||
update themselves in-place, no matter where they were inserted on the
|
||||
page. It's completely automatic. [`Meteor.render`](#meteor_render) uses
|
||||
a [reactive computation](#reactivity) to discover what data is used by the
|
||||
rendering function.
|
||||
|
||||
Most of the time, though, you won't call these functions directly
|
||||
— you'll just use your favorite templating package, such as
|
||||
Handlebars or Jade. The `render` and `renderList` functions are intended
|
||||
for people that are implementing new templating systems.
|
||||
|
||||
Meteor normally batches up any needed updates and executes them only
|
||||
when your code isn't running. That way, you can be sure that the DOM
|
||||
won't change out from underneath you. Sometimes you want the opposite
|
||||
behavior. For example, if you've just inserted a record in the
|
||||
database, you might want to force the DOM to update so you can find
|
||||
the new elements using a library like jQuery. In that case, call
|
||||
[`Deps.flush`](#deps_flush) to bring the DOM up to date
|
||||
immediately.
|
||||
|
||||
When live-updating DOM elements are taken off the screen, they are automatically
|
||||
cleaned up — their callbacks are torn down, any associated database
|
||||
queries are stopped, and they stop updating. For this reason, you never have to
|
||||
worry about the [zombie
|
||||
templates](http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/)
|
||||
that plague hand-written update logic. To protect your elements from cleanup,
|
||||
just make sure that they are on-screen before your code returns to the event loop,
|
||||
or before any call you make to [`Deps.flush`](#deps_flush).
|
||||
|
||||
Another thorny problem in hand-written applications is element
|
||||
preservation. Suppose the user is typing text into an `<input>`
|
||||
element, and then the area of the page that includes that element is
|
||||
redrawn. The user could be in for a bumpy ride, as the focus, the
|
||||
cursor position, the partially entered text, and the accented
|
||||
character input state will be lost when the `<input>` is recreated.
|
||||
|
||||
This is another problem that Meteor solves for you. You can specify
|
||||
elements to preserve when templates are re-rendered with the
|
||||
[`preserve`](#template_preserve) directive on the template. Meteor will
|
||||
preserve these elements even when their enclosing template is
|
||||
rerendered, but will still update their children and copy over any
|
||||
attribute changes.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
<template name="templates">
|
||||
{{#better_markdown}}
|
||||
|
||||
<h2 id="templates">Templates</h2>
|
||||
|
||||
Meteor makes it easy to use your favorite HTML templating language,
|
||||
such as Handlebars or Jade, along with Meteor's live page update
|
||||
technology. Just write your template as you normally would, and Meteor
|
||||
will take care of making it update in realtime.
|
||||
|
||||
To use this feature, create a file in your project with the `.html`
|
||||
To define templates, create a file in your project with the `.html`
|
||||
extension. In the file, make a `<template>` tag and give it a
|
||||
`name` attribute. Put the template contents inside the tag. Meteor
|
||||
will precompile the template, ship it down to the client, and make it
|
||||
available as a function on the global `Template` object.
|
||||
available as on the global `Template` object.
|
||||
|
||||
{{#note}}
|
||||
Today, the only templating system that has been packaged for Meteor is
|
||||
Handlebars. Let us know what templating systems you'd like to use with
|
||||
Meteor. Meanwhile, see the [Handlebars
|
||||
documentation](http://www.handlebarsjs.com/) and [Meteor Handlebars
|
||||
extensions](https://github.com/meteor/meteor/wiki/Handlebars).
|
||||
{{/note}}
|
||||
|
||||
A template with a `name` of `hello` is rendered by calling the
|
||||
function `Template.hello`, passing any data for the template:
|
||||
|
||||
<!-- in myapp.html -->
|
||||
<template name="hello">
|
||||
<div class="greeting">Hello there, {{dstache}}first}} {{dstache}}last}}!</div>
|
||||
</{{! }}template>
|
||||
|
||||
// in the JavaScript console
|
||||
> Template.hello({first: "Alyssa", last: "Hacker"});
|
||||
=> "<div class="greeting">Hello there, Alyssa Hacker!</div>"
|
||||
|
||||
This returns a string. To use the template along with the [`Live
|
||||
HTML`](#livehtml) system, and get DOM elements that update
|
||||
automatically in place, use [`Meteor.render`](#meteor_render):
|
||||
|
||||
Meteor.render(function () {
|
||||
return Template.hello({first: "Alyssa", last: "Hacker"});
|
||||
})
|
||||
=> automatically updating DOM elements
|
||||
When you app is loaded, it automatically renders the special template called
|
||||
`<body>`, which is written using the `<body>` element instead of a
|
||||
`<template>`. You insert a template inside another template by using the
|
||||
`{{dstache}}> inclusion}}` operator.
|
||||
|
||||
The easiest way to get data into templates is by defining helper
|
||||
functions in JavaScript. Just add the helper functions directly on the
|
||||
`Template.[template name]` object. For example, in this template:
|
||||
`Template.`*templateName* object. Putting it all together:
|
||||
|
||||
|
||||
<!-- in myapp.html -->
|
||||
<body>
|
||||
<h1>Today's weather!</h1>
|
||||
{{dstache}}> forecast}}
|
||||
</body>
|
||||
|
||||
<template name="forecast">
|
||||
<div>It'll be {{dstache}}prediction}} tonight</div>
|
||||
{{lt}}/template>
|
||||
|
||||
// in client/myapp.js: reactive helper function
|
||||
Template.forecast.prediction = function () {
|
||||
return Session.get("weather");
|
||||
};
|
||||
|
||||
// in the JavaScript console
|
||||
> Session.set("weather", "cloudy");
|
||||
> document.body.innerHTML
|
||||
=> "<h1>Today's weather!</h1> <div>It'll be cloudy tonight</div>"
|
||||
|
||||
> Session.set("weather", "cool and dry");
|
||||
> document.body.innerHTML
|
||||
=> "<h1>Today's weather!</h1> <div>It'll be cool and dry tonight</div>"
|
||||
|
||||
|
||||
To iterate over an array or database cursor, use `{{dstache}}#each}}`:
|
||||
|
||||
<!-- in myapp.html -->
|
||||
<template name="players">
|
||||
{{dstache}}#each topScorers}}
|
||||
<div>{{dstache}}name}}</div>
|
||||
{{dstache}}/each}}
|
||||
</{{! }}template>
|
||||
|
||||
instead of passing in `topScorers` as data when we call the
|
||||
template function, we could define a function on `Template.players`:
|
||||
{{lt}}/template>
|
||||
|
||||
// in myapp.js
|
||||
Template.players.topScorers = function () {
|
||||
return Users.find({score: {$gt: 100}}, {sort: {score: -1}});
|
||||
};
|
||||
|
||||
In this case, the data is coming from a database query. When the
|
||||
database cursor is passed to `#each`, it will wire up all of the
|
||||
database cursor is passed to `{{dstache}}#each}}`, it will wire up all of the
|
||||
machinery to efficiently add and move DOM nodes as new results enter
|
||||
the query.
|
||||
|
||||
Helpers can take arguments, and they receive the current template context data
|
||||
in `this`. Note that some block helpers change the current context (notably
|
||||
`each` and `with`):
|
||||
`{{dstache}}#each}}` and `{{dstache}}#with}}`):
|
||||
|
||||
// in a JavaScript file
|
||||
Template.players.leagueIs = function (league) {
|
||||
@@ -574,15 +521,7 @@ in `this`. Note that some block helpers change the current context (notably
|
||||
<div>Senior: {{dstache}}name}}</div>
|
||||
{{dstache}}/if}}
|
||||
{{dstache}}/each}}
|
||||
</{{! }}template>
|
||||
|
||||
{{#note}}
|
||||
Handlebars note: `{{dstache}}#if leagueIs "junior"}}` is
|
||||
allowed because of a Meteor extension that allows nesting a helper
|
||||
in a block helper. (Both `if` and `leagueIs` are
|
||||
technically helpers, and stock Handlebars would not invoke
|
||||
`leagueIs` here.)
|
||||
{{/note}}
|
||||
{{lt}}/template>
|
||||
|
||||
Helpers can also be used to pass in constant data.
|
||||
|
||||
@@ -599,13 +538,13 @@ the data context of the element that triggered the event.
|
||||
{{dstache}}#each player}}
|
||||
{{dstache}}> playerScore}}
|
||||
{{dstache}}/each}}
|
||||
</{{! }}template>
|
||||
{{lt}}/template>
|
||||
|
||||
<template name="playerScore">
|
||||
<div>{{dstache}}name}}: {{dstache}}score}}
|
||||
<span class="givePoints">Give points</span>
|
||||
</div>
|
||||
</{{! }}template>
|
||||
{{lt}}/template>
|
||||
|
||||
<!-- myapp.js -->
|
||||
Template.playerScore.events({
|
||||
@@ -614,35 +553,14 @@ the data context of the element that triggered the event.
|
||||
}
|
||||
});
|
||||
|
||||
Putting it all together, here's an example of how you can inject
|
||||
arbitrary data into your templates, and have them update automatically
|
||||
whenever that data changes. See [Live HTML](#livehtml) for further
|
||||
discussion.
|
||||
For more details about Spacebars, read [the Spacebars
|
||||
README](https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md).
|
||||
|
||||
<!-- in myapp.html -->
|
||||
<template name="forecast">
|
||||
<div>It'll be {{dstache}}prediction}} tonight</div>
|
||||
</{{! }}template>
|
||||
|
||||
<!-- in myapp.js -->
|
||||
// JavaScript: reactive helper function
|
||||
Template.forecast.prediction = function () {
|
||||
return Session.get("weather");
|
||||
};
|
||||
|
||||
<!-- in the console -->
|
||||
> Session.set("weather", "cloudy");
|
||||
> document.body.appendChild(Meteor.render(Template.forecast));
|
||||
In DOM: <div>It'll be cloudy tonight</div>
|
||||
|
||||
> Session.set("weather", "cool and dry");
|
||||
In DOM: <div>It'll be cool and dry tonight</div>
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
<template name="packages_concept">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="usingpackages">Using packages</h2>
|
||||
|
||||
@@ -679,12 +597,12 @@ are willing to brave the fact that the Meteor package format is not
|
||||
documented yet and will change significantly before Meteor 1.0. See
|
||||
[Writing Packages](#writingpackages).
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
|
||||
<template name="namespacing">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="namespacing">Namespacing</h2>
|
||||
|
||||
@@ -773,12 +691,12 @@ certainly shouldn't depend on this quirk, and in the future Meteor may
|
||||
check for it and throw an error if you do.
|
||||
{{/note}}
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
|
||||
<template name="deploying">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="deploying">Deploying</h2>
|
||||
|
||||
@@ -823,7 +741,7 @@ To get started, run
|
||||
This command will generate a fully-contained Node.js application in the form of
|
||||
a tarball. To run this application, you need to provide Node.js 0.10 and a
|
||||
MongoDB server. (The current release of Meteor has been tested with Node
|
||||
0.10.25; older versions contain a serious bug that can cause production servers
|
||||
0.10.26; older versions contain a serious bug that can cause production servers
|
||||
to stall.) You can then run the application by invoking node, specifying the
|
||||
HTTP port for the application to listen on, and the MongoDB endpoint. If
|
||||
you don't already have a MongoDB server, we can recommend our friends at
|
||||
@@ -845,12 +763,12 @@ have `npm` available, and run the following:
|
||||
$ npm install fibers@1.0.1
|
||||
{{/warning}}
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
|
||||
<template name="packages_writing">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h2 id="writingpackages">Writing packages</h2>
|
||||
|
||||
@@ -949,5 +867,5 @@ quick tips:
|
||||
machine architecture, but if not your built Meteor package will be
|
||||
portable.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -173,7 +173,7 @@ a:hover {
|
||||
/** Main pane **/
|
||||
|
||||
#main {
|
||||
margin: 10px;
|
||||
margin: 10px 10px 10px 60px;
|
||||
line-height: 1.3;
|
||||
color: #333333;
|
||||
}
|
||||
@@ -407,10 +407,34 @@ dl.callbacks {
|
||||
|
||||
/** layout control **/
|
||||
|
||||
/* default to no sidebar */
|
||||
#nav {
|
||||
#menu-ico {
|
||||
font-size: 30px;
|
||||
float: right;
|
||||
position: fixed;
|
||||
top: 3px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
#menu-ico.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* default to no sidebar */
|
||||
#nav {
|
||||
display: block;
|
||||
background: #FFF;
|
||||
position: fixed;
|
||||
width: 260px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -220px;
|
||||
}
|
||||
|
||||
#nav.show {
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.github-ribbon {
|
||||
display: none;
|
||||
}
|
||||
@@ -419,37 +443,44 @@ pre {
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* ipad portrait or better */
|
||||
#main {
|
||||
width: 440px;
|
||||
height: 100%;
|
||||
margin-left: 260px; /* nav width + padding */
|
||||
padding: 30px;
|
||||
}
|
||||
#nav {
|
||||
display: block;
|
||||
width: 200px;
|
||||
position: fixed;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.main-headline {
|
||||
display: none;
|
||||
}
|
||||
/* ipad portrait or better */
|
||||
#main {
|
||||
width: 440px;
|
||||
height: 100%;
|
||||
margin-left: 260px; /* nav width + padding */
|
||||
padding: 30px;
|
||||
}
|
||||
#nav {
|
||||
display: block;
|
||||
width: 200px;
|
||||
position: fixed;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.main-headline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#menu-ico {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
/* ipad landscape and desktop */
|
||||
#main {
|
||||
width: 610px;
|
||||
margin-left: 330px; /* nav width + padding */
|
||||
}
|
||||
#nav {
|
||||
width: 270px;
|
||||
}
|
||||
.github-ribbon {
|
||||
display: block;
|
||||
}
|
||||
/* ipad landscape and desktop */
|
||||
#main {
|
||||
width: 610px;
|
||||
margin-left: 330px; /* nav width + padding */
|
||||
}
|
||||
#nav {
|
||||
width: 270px;
|
||||
}
|
||||
.github-ribbon {
|
||||
display: block;
|
||||
}
|
||||
#menu-ico {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="nav">
|
||||
<div id="nav" class="hide">
|
||||
{{> nav }}
|
||||
</div>
|
||||
<div id="main">
|
||||
@@ -23,18 +23,42 @@
|
||||
</body>
|
||||
|
||||
<template name="nav">
|
||||
<div id="nav-inner">
|
||||
{{#each sections}}
|
||||
{{#if type "spacer"}}
|
||||
<div class="spacer"> </div>
|
||||
<div id="menu-ico"><a href="#">☰</a></div>
|
||||
<div id="nav-inner">
|
||||
{{#each sections}}
|
||||
{{#if type "spacer"}}
|
||||
<div class="spacer"> </div>
|
||||
{{/if}}
|
||||
{{#if type "section"}}
|
||||
{{#nav_section}}
|
||||
<a href="#{{id}}" class="{{maybe_current}} {{style}}">
|
||||
{{#if prefix}}{{prefix}}.{{/if}}{{#if instance}}<i>{{instance}}</i>.{{/if}}{{name}}
|
||||
</a>
|
||||
{{/nav_section}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="nav_section">
|
||||
{{#if depthIs 1}}
|
||||
<h1>{{> UI.contentBlock}}</h1>
|
||||
{{/if}}
|
||||
{{#if type "section"}}
|
||||
<h{{depth}}><a href="#{{id}}" class="{{maybe_current}} {{style}}">
|
||||
{{#if prefix}}{{prefix}}.{{/if}}{{#if instance}}<i>{{instance}}</i>.{{/if}}{{name}}
|
||||
</a></h{{depth}}>
|
||||
{{#if depthIs 2}}
|
||||
<h2>{{> UI.contentBlock}}</h2>
|
||||
{{/if}}
|
||||
{{#if depthIs 3}}
|
||||
<h3>{{> UI.contentBlock}}</h3>
|
||||
{{/if}}
|
||||
{{#if depthIs 4}}
|
||||
<h4>{{> UI.contentBlock}}</h4>
|
||||
{{/if}}
|
||||
{{#if depthIs 5}}
|
||||
<h5>{{> UI.contentBlock}}</h5>
|
||||
{{/if}}
|
||||
{{#if depthIs 6}}
|
||||
<h6>{{> UI.contentBlock}}</h6>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- This only is displayed on narrow displays (eg phone) -->
|
||||
@@ -43,24 +67,35 @@
|
||||
</template>
|
||||
|
||||
|
||||
<template name="dtdd_helper">
|
||||
<dt><span class="name">{{{name}}}</span>
|
||||
{{#if type}}<span class="type">{{type}}</span>{{/if}}
|
||||
</dt>
|
||||
<dd>{{#better_markdown}}{{{descr}}}{{/better_markdown}}</dd>
|
||||
<template name="dtdd">
|
||||
{{! can be used with either one argument (which ends up in the data
|
||||
context, or with both name and type }}
|
||||
|
||||
{{#if name}}
|
||||
<dt><span class="name">{{{name}}}</span>
|
||||
{{#if type}}<span class="type">{{type}}</span>{{/if}}
|
||||
</dt>
|
||||
{{else}}
|
||||
<dt><span class="name">{{{.}}}</span></dt>
|
||||
{{/if}}
|
||||
<dd>{{#markdown}}{{> UI.contentBlock}}{{/markdown}}</dd>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<template name="warning_helper">
|
||||
<div class="warning">
|
||||
{{#better_markdown}}{{{this}}}{{/better_markdown}}
|
||||
</div>
|
||||
<!-- this is used within {{#markdown}}. <div> must be unindented.
|
||||
http://daringfireball.net/projects/markdown/syntax#html -->
|
||||
<template name="warning">
|
||||
<div class="warning">
|
||||
{{#markdown}}{{> UI.contentBlock}}{{/markdown}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="note_helper">
|
||||
<div class="note">
|
||||
{{#better_markdown}}{{{this}}}{{/better_markdown}}
|
||||
</div>
|
||||
<!-- this is used within {{#markdown}}. <div> must be unindented.
|
||||
http://daringfireball.net/projects/markdown/syntax#html -->
|
||||
<template name="note">
|
||||
<div class="note">
|
||||
{{#markdown}}{{> UI.contentBlock}}{{/markdown}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ Template.headline.release = function () {
|
||||
return Meteor.release || "(checkout)";
|
||||
};
|
||||
|
||||
|
||||
Meteor.startup(function () {
|
||||
// XXX this is broken by the new multi-page layout. Also, it was
|
||||
// broken before the multi-page layout because it had illegible
|
||||
@@ -89,8 +88,18 @@ Meteor.startup(function () {
|
||||
|
||||
// Make external links open in a new tab.
|
||||
$('a:not([href^="#"])').attr('target', '_blank');
|
||||
|
||||
// Hide menu by tapping on background
|
||||
$('#main').on('click', function () {
|
||||
hideMenu();
|
||||
});
|
||||
});
|
||||
|
||||
var hideMenu = function () {
|
||||
$('#nav').removeClass('show');
|
||||
$('#menu-ico').removeClass('hidden');
|
||||
};
|
||||
|
||||
var toc = [
|
||||
{name: "Meteor " + Template.headline.release(), id: "top"}, [
|
||||
"Quick start",
|
||||
@@ -102,8 +111,7 @@ var toc = [
|
||||
"Structuring your app",
|
||||
"Data and security",
|
||||
"Reactivity",
|
||||
"Live HTML",
|
||||
"Templates",
|
||||
"Live HTML templates",
|
||||
"Using packages",
|
||||
"Namespacing",
|
||||
"Deploying",
|
||||
@@ -214,7 +222,7 @@ var toc = [
|
||||
"Accounts.onCreateUser",
|
||||
"Accounts.validateLoginAttempt",
|
||||
"Accounts.onLogin",
|
||||
{name: "Accounts.onLoginFailure", id: "accounts_onlogin"},
|
||||
{name: "Accounts.onLoginFailure", id: "accounts_onlogin"}
|
||||
],
|
||||
|
||||
{name: "Passwords", id: "accounts_passwords"}, [
|
||||
@@ -233,13 +241,12 @@ var toc = [
|
||||
],
|
||||
|
||||
{name: "Templates", id: "templates_api"}, [
|
||||
{prefix: "Template", instance: "myTemplate", id: "template_call"}, [
|
||||
{name: "rendered", id: "template_rendered"},
|
||||
{name: "created", id: "template_created"},
|
||||
{name: "destroyed", id: "template_destroyed"},
|
||||
{prefix: "Template", instance: "myTemplate", id: "templates_api"}, [
|
||||
{name: "events", id: "template_events"},
|
||||
{name: "helpers", id: "template_helpers"},
|
||||
{name: "preserve", id: "template_preserve"}
|
||||
{name: "rendered", id: "template_rendered"},
|
||||
{name: "created", id: "template_created"},
|
||||
{name: "destroyed", id: "template_destroyed"}
|
||||
],
|
||||
{name: "Template instances", id: "template_inst"}, [
|
||||
{instance: "this", name: "findAll", id: "template_findAll"},
|
||||
@@ -248,12 +255,15 @@ var toc = [
|
||||
{instance: "this", name: "lastNode", id: "template_lastNode"},
|
||||
{instance: "this", name: "data", id: "template_data"}
|
||||
],
|
||||
"Meteor.render",
|
||||
"Meteor.renderList",
|
||||
"UI", [
|
||||
"UI.registerHelper",
|
||||
"UI.body",
|
||||
"UI.render",
|
||||
"UI.renderWithData",
|
||||
"UI.insert"
|
||||
],
|
||||
{type: "spacer"},
|
||||
{name: "Event maps", style: "noncode"},
|
||||
{name: "Constant regions", style: "noncode", id: "constant"},
|
||||
{name: "Reactivity isolation", style: "noncode", id: "isolate"}
|
||||
{name: "Event maps", style: "noncode"}
|
||||
],
|
||||
|
||||
"Match", [
|
||||
@@ -348,6 +358,7 @@ var toc = [
|
||||
"force-ssl",
|
||||
"jquery",
|
||||
"less",
|
||||
"oauth-encryption",
|
||||
"random",
|
||||
"spiderable",
|
||||
"stylus",
|
||||
@@ -380,6 +391,10 @@ Template.nav.sections = function () {
|
||||
var ret = [];
|
||||
var walk = function (items, depth) {
|
||||
_.each(items, function (item) {
|
||||
// Work around (eg) accidental trailing commas leading to spurious holes
|
||||
// in IE8.
|
||||
if (!item)
|
||||
return;
|
||||
if (item instanceof Array)
|
||||
walk(item, depth + 1);
|
||||
else {
|
||||
@@ -401,159 +416,38 @@ Template.nav.sections = function () {
|
||||
|
||||
Template.nav.type = function (what) {
|
||||
return this.type === what;
|
||||
}
|
||||
};
|
||||
|
||||
Template.nav.maybe_current = function () {
|
||||
return Session.equals("section", this.id) ? "current" : "";
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('warning', function(fn) {
|
||||
return Template.warning_helper(fn(this));
|
||||
});
|
||||
Template.nav_section.depthIs = function (n) {
|
||||
return this.depth === n;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('note', function(fn) {
|
||||
return Template.note_helper(fn(this));
|
||||
});
|
||||
|
||||
// "name" argument may be provided as part of options.hash instead.
|
||||
Handlebars.registerHelper('dtdd', function(name, options) {
|
||||
if (options && options.hash) {
|
||||
// {{#dtdd name}}
|
||||
options.hash.name = name;
|
||||
} else {
|
||||
// {{#dtdd name="foo" type="bar"}}
|
||||
options = name;
|
||||
// Show hidden TOC when menu icon is tapped
|
||||
Template.nav.events({
|
||||
'click #menu-ico' : function () {
|
||||
$('#nav').addClass('show');
|
||||
$('#menu-ico').addClass('hidden');
|
||||
},
|
||||
// Hide TOC when selecting an item
|
||||
'click a' : function () {
|
||||
hideMenu();
|
||||
}
|
||||
|
||||
return Template.dtdd_helper({descr: options.fn(this),
|
||||
name: options.hash.name,
|
||||
type: options.hash.type});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('better_markdown', function(fn) {
|
||||
var converter = new Showdown.converter();
|
||||
var input = fn(this);
|
||||
|
||||
///////
|
||||
// Make Markdown *actually* skip over block-level elements when
|
||||
// processing a string.
|
||||
//
|
||||
// Official Markdown doesn't descend into
|
||||
// block elements written out as HTML (divs, tables, etc.), BUT
|
||||
// it doesn't skip them properly either. It assumes they are
|
||||
// either pretty-printed with their contents indented, or, failing
|
||||
// that, it just scans for a close tag with the same name, and takes
|
||||
// it regardless of whether it is the right one. As a hack to work
|
||||
// around Markdown's hacks, we find the block-level elements
|
||||
// using a proper recursive method and rewrite them to be indented
|
||||
// with the final close tag on its own line.
|
||||
///////
|
||||
|
||||
// Open-block tag should be at beginning of line,
|
||||
// and not, say, in a string literal in example code, or in a pre block.
|
||||
// Tag must be followed by a non-word-char so that we match whole tag, not
|
||||
// eg P for PRE. All regexes we wish to use when scanning must have
|
||||
// 'g' flag so that they respect (and set) lastIndex.
|
||||
// Assume all tags are lowercase.
|
||||
var rOpenBlockTag = /^\s{0,2}<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)(?=\W)/mg;
|
||||
var rTag = /<(\/?\w+)/g;
|
||||
var idx = 0;
|
||||
var newParts = [];
|
||||
var blockBuf = [];
|
||||
// helper function to execute regex `r` starting at idx and putting
|
||||
// the end index back into idx; accumulate the intervening string
|
||||
// into an array; and return the regex's first capturing group.
|
||||
var rcall = function(r, inBlock) {
|
||||
var lastIndex = idx;
|
||||
r.lastIndex = lastIndex;
|
||||
var match = r.exec(input);
|
||||
var result = null;
|
||||
if (! match) {
|
||||
idx = input.length;
|
||||
} else {
|
||||
idx = r.lastIndex;
|
||||
result = match[1];
|
||||
}
|
||||
(inBlock ? blockBuf : newParts).push(input.substring(lastIndex, idx));
|
||||
return result;
|
||||
};
|
||||
|
||||
// This is a tower of terrible hacks.
|
||||
// Replace Spark annotations <$...> ... </$...> with HTML comments, and
|
||||
// space out the comments on their own lines. This keeps them from
|
||||
// interfering with Markdown's paragraph parsing.
|
||||
// Really, running Markdown multiple times on the same string is just a
|
||||
// bad idea.
|
||||
input = input.replace(/<(\/?\$.*?)>/g, '<!--$1-->');
|
||||
input = input.replace(/<!--.*?-->/g, '\n\n$&\n\n');
|
||||
|
||||
var hashedBlocks = {};
|
||||
var numHashedBlocks = 0;
|
||||
|
||||
var nestedTags = [];
|
||||
while (idx < input.length) {
|
||||
var blockTag = rcall(rOpenBlockTag, false);
|
||||
if (blockTag) {
|
||||
nestedTags.push(blockTag);
|
||||
while (nestedTags.length) {
|
||||
var tag = rcall(rTag, true);
|
||||
if (! tag) {
|
||||
throw new Error("Expected </"+nestedTags[nestedTags.length-1]+
|
||||
"> but found end of string");
|
||||
} else if (tag.charAt(0) === '/') {
|
||||
// close tag
|
||||
var tagToPop = tag.substring(1);
|
||||
var tagPopped = nestedTags.pop();
|
||||
if (tagPopped !== tagToPop)
|
||||
throw new Error(("Mismatched close tag, expected </"+tagPopped+
|
||||
"> but found </"+tagToPop+">: "+
|
||||
input.substr(idx-50,50)+"{HERE}"+
|
||||
input.substr(idx,50)).replace(/\n/g,'\\n'));
|
||||
} else {
|
||||
// open tag
|
||||
nestedTags.push(tag);
|
||||
}
|
||||
}
|
||||
var newBlock = blockBuf.join('');
|
||||
var openTagFinish = newBlock.indexOf('>') + 1;
|
||||
var closeTagLoc = newBlock.lastIndexOf('<');
|
||||
|
||||
var key = ++numHashedBlocks;
|
||||
hashedBlocks[key] = newBlock.slice(openTagFinish, closeTagLoc);
|
||||
newParts.push(newBlock.slice(0, openTagFinish),
|
||||
'!!!!HTML:'+key+'!!!!',
|
||||
newBlock.slice(closeTagLoc));
|
||||
blockBuf.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var newInput = newParts.join('');
|
||||
var output = converter.makeHtml(newInput);
|
||||
|
||||
output = output.replace(/!!!!HTML:(.*?)!!!!/g, function(z, a) {
|
||||
return hashedBlocks[a];
|
||||
});
|
||||
|
||||
output = output.replace(/<!--(\/?\$.*?)-->/g, '<$1>');
|
||||
|
||||
return output;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('dstache', function() {
|
||||
UI.registerHelper('dstache', function() {
|
||||
return '{{';
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('tstache', function() {
|
||||
UI.registerHelper('tstache', function() {
|
||||
return '{{{';
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('api_section', function(id, nameFn) {
|
||||
return Template.api_section_helper(
|
||||
{name: nameFn(this), id:id}, true);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('api_box_inline', function(box, fn) {
|
||||
return Template.api_box(_.extend(box, {body: fn(this)}), true);
|
||||
UI.registerHelper('lt', function () {
|
||||
return '<';
|
||||
});
|
||||
|
||||
Template.api_box.bare = function() {
|
||||
@@ -562,7 +456,7 @@ Template.api_box.bare = function() {
|
||||
(this.options && this.options.length)) ? "" : "bareapi";
|
||||
};
|
||||
|
||||
var check_links = function() {
|
||||
check_links = function() {
|
||||
var body = document.body.innerHTML;
|
||||
|
||||
var id_set = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="packages">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
<h1 id="packages">Packages</h1>
|
||||
|
||||
@@ -28,11 +28,12 @@ and removed with:
|
||||
{{> pkg_force_ssl}}
|
||||
{{> pkg_jquery}}
|
||||
{{> pkg_less}}
|
||||
{{> pkg_oauth_encryption}}
|
||||
{{> pkg_random}}
|
||||
{{> pkg_spiderable}}
|
||||
{{> pkg_stylus}}
|
||||
{{> pkg_showdown}}
|
||||
{{> pkg_underscore}}
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_accounts_ui">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `accounts-ui`
|
||||
|
||||
A turn-key user interface for Meteor Accounts.
|
||||
@@ -9,16 +9,16 @@ To add Accounts and a set of login controls to an application, add the
|
||||
`accounts-password`, `accounts-facebook`, `accounts-github`,
|
||||
`accounts-google`, `accounts-twitter`, or `accounts-weibo`.
|
||||
|
||||
Then simply add the `{{dstache}}loginButtons}}` helper to an HTML file. This
|
||||
Then simply add the `{{dstache}}> loginButtons}}` helper to an HTML file. This
|
||||
will place a login widget on the page. If there is only one provider configured
|
||||
and it is an external service, this will add a login/logout button. If you use
|
||||
`accounts-password` or use multiple external login services, this will add
|
||||
a "Sign in" link which opens a dropdown menu with login options. If you plan to
|
||||
position the login dropdown in the right edge of the screen, use
|
||||
`{{dstache}}loginButtons align="right"}}` in order to get the dropdown to lay
|
||||
`{{dstache}}> loginButtons align="right"}}` in order to get the dropdown to lay
|
||||
itself out without expanding off the edge of the screen.
|
||||
|
||||
To configure the behavior of `{{dstache}}loginButtons}}`, use
|
||||
To configure the behavior of `{{dstache}}> loginButtons}}`, use
|
||||
[`Accounts.ui.config`](#accounts_ui_config).
|
||||
|
||||
`accounts-ui` also includes modal popup dialogs to handle links from
|
||||
@@ -29,5 +29,5 @@ when the URLs are loaded.
|
||||
|
||||
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_amplify">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
## `amplify`
|
||||
|
||||
@@ -10,5 +10,5 @@ components, and several useful utility functions.
|
||||
Amplify defines a global namespace `amplify` on the client only. It does
|
||||
not run on the server.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_appcache">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
## `appcache`
|
||||
|
||||
@@ -89,5 +89,5 @@ cache, see the
|
||||
[AppCache page](https://github.com/meteor/meteor/wiki/AppCache)
|
||||
in the Meteor wiki.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_audit_argument_checks">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
## `audit-argument-checks`
|
||||
|
||||
@@ -14,5 +14,5 @@ Methods and publish functions that do not need to validate their arguments can
|
||||
simply run `check(arguments, [Match.Any])` to satisfy the
|
||||
`audit-argument-checks` coverage checker.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_backbone">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `backbone`
|
||||
|
||||
[Backbone](http://documentcloud.github.com/backbone/) is a popular client-side MVC framework for managing complex
|
||||
@@ -7,5 +7,5 @@ data in the browser. In addition to the MVC and DOM-binding
|
||||
functionality, it also provides an API for HTML5 pushState and
|
||||
client-side URL routing.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_bootstrap">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `bootstrap`
|
||||
|
||||
[Twitter's Bootstrap](http://twitter.github.com/bootstrap/) package is a front-end toolkit for faster, more
|
||||
@@ -9,5 +9,5 @@ interactions including typography, forms, buttons, tables, grids, and
|
||||
navigation.
|
||||
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_browser_policy">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `browser-policy`
|
||||
|
||||
The `browser-policy` package lets you set security-related policies that will be
|
||||
@@ -170,5 +170,5 @@ sites can frame your site, while
|
||||
sites can be loaded inside frames on your site.
|
||||
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_coffeescript">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `coffeescript`
|
||||
|
||||
[CoffeeScript](http://coffeescript.org/) is a little language that
|
||||
@@ -48,5 +48,5 @@ Heavy CoffeeScript users, please let us know how this arrangement
|
||||
works for you, whether `share` is helpful for you, and anything else
|
||||
you'd like to see changed.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_d3">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
## `d3`
|
||||
|
||||
@@ -13,5 +13,5 @@ to DOM manipulation.
|
||||
The `d3` package adds the D3 library to the client JavaScript
|
||||
bundle. It has no effect on the server.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_force_ssl">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `force-ssl`
|
||||
|
||||
This package causes Meteor to redirect insecure connections (HTTP) to a
|
||||
@@ -19,5 +19,5 @@ Applications deployed to `meteor.com` subdomains with
|
||||
`meteor deploy` are automatically served via HTTPS using Meteor's
|
||||
certificate.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_jquery">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
|
||||
## `jquery`
|
||||
|
||||
@@ -17,5 +17,5 @@ plugins as separate packages. These include:
|
||||
* [`jquery-layout`](http://layout.jquery-dev.net/)
|
||||
* [`jquery-waypoints`](http://imakewebthings.com/jquery-waypoints/)
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_less">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `less`
|
||||
|
||||
[LESS](http://lesscss.org/) extends CSS with dynamic behavior such as variables, mixins,
|
||||
@@ -15,5 +15,5 @@ If you want to `@import` a file, give it the extension `.import.less`
|
||||
to prevent Meteor from processing it independently.
|
||||
{{/note}}
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
9
docs/client/packages/oauth-encryption.html
Normal file
9
docs/client/packages/oauth-encryption.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<template name="pkg_oauth_encryption">
|
||||
{{#markdown}}
|
||||
## `oauth-encryption`
|
||||
|
||||
Encrypts sensitive account credential information stored in the
|
||||
database. See packages/oauth-encryption/README.md for details.
|
||||
|
||||
{{/markdown}}
|
||||
</template>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_random">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `random`
|
||||
|
||||
The `random` package provides several functions for generating random
|
||||
@@ -10,9 +10,18 @@ servers that don't have enough entropy to seed the cryptographically strong
|
||||
generator).
|
||||
|
||||
<dl class="callbacks">
|
||||
{{#dtdd "Random.id()"}}
|
||||
Returns a unique identifier, such as `"Jjwjg6gouWLXhMGKW"`, that is likely to
|
||||
be unique in the whole world.
|
||||
{{#dtdd "Random.id([n])"}}
|
||||
Returns a unique identifier, such as `"Jjwjg6gouWLXhMGKW"`, that is
|
||||
likely to be unique in the whole world. The optional argument `n`
|
||||
specifies the length of the identifier in characters and defaults to 17.
|
||||
{{/dtdd}}
|
||||
|
||||
{{#dtdd "Random.secret([n])"}}
|
||||
Returns a random string of printable characters with 6 bits of
|
||||
entropy per character. The optional argument `n` specifies the length of
|
||||
the secret string and defaults to 43 characters, or 256 bits of
|
||||
entropy. Use `Random.secret` for security-critical secrets that are
|
||||
intended for machine, rather than human, consumption.
|
||||
{{/dtdd}}
|
||||
|
||||
{{#dtdd "Random.fraction()"}}
|
||||
@@ -28,5 +37,5 @@ Returns a random string of `n` hexadecimal digits.
|
||||
{{/dtdd}}
|
||||
</dl>
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_spiderable">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `spiderable`
|
||||
|
||||
|
||||
@@ -33,6 +33,14 @@ If you deploy your application with `meteor bundle`, you must install
|
||||
`$PATH`. If you use `meteor deploy` this is already taken care of.
|
||||
{{/warning}}
|
||||
|
||||
{{#warning}}
|
||||
When running your page, `spiderable` will wait for all publications
|
||||
to be ready. Make sure that all of your [`publish functions`](#meteor_publish)
|
||||
either return a cursor (or an array of cursors), or eventually call
|
||||
[`this.ready()`](#publish_ready). Otherwise, the `phantomjs` executions
|
||||
will fail.
|
||||
{{/warning}}
|
||||
|
||||
{{/better_markdown}}
|
||||
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_stylus">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `stylus`
|
||||
|
||||
[Stylus](http://learnboost.github.com/stylus/) is a CSS pre-processor with a simple syntax and expressive
|
||||
@@ -21,5 +21,5 @@ to prevent Meteor from processing it independently.
|
||||
|
||||
See <http://visionmedia.github.com/nib> for documentation of the nib extensions of Stylus.
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template name="pkg_underscore">
|
||||
{{#better_markdown}}
|
||||
{{#markdown}}
|
||||
## `underscore`
|
||||
|
||||
[Underscore](http://underscorejs.org/) is a utility-belt library for
|
||||
@@ -30,5 +30,5 @@ are treated as objects if they have no prototype (specifically, if
|
||||
{{/warning}}
|
||||
|
||||
|
||||
{{/better_markdown}}
|
||||
{{/markdown}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// While galaxy apps are on their own special meteor releases, override
|
||||
// Meteor.release here.
|
||||
if (Meteor.isClient) {
|
||||
Meteor.release = Meteor.release ? "0.7.2" : undefined;
|
||||
Meteor.release = Meteor.release ? "0.8.0.1" : undefined;
|
||||
}
|
||||
|
||||
1
examples/clock/.meteor/.gitignore
vendored
Normal file
1
examples/clock/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
9
examples/clock/.meteor/packages
Normal file
9
examples/clock/.meteor/packages
Normal file
@@ -0,0 +1,9 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
underscore
|
||||
1
examples/clock/.meteor/release
Normal file
1
examples/clock/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
0.8.0.1
|
||||
37
examples/clock/client/clock.html
Normal file
37
examples/clock/client/clock.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<head>
|
||||
<title>SVG Clock Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="400" height="400"
|
||||
viewBox="-110 -110 220 220">
|
||||
|
||||
<!-- bounding circle -->
|
||||
<circle style="stroke: black; fill: #eee;"
|
||||
cx="0" cy="0" r="100"/>
|
||||
|
||||
<!-- hour, minute and second hands -->
|
||||
{{#with handData}}
|
||||
<line {{radial hourDegrees 0 .55}}
|
||||
style="stroke-width: 6px;
|
||||
stroke: green;" />
|
||||
<line {{radial minuteDegrees 0 .85}}
|
||||
style="stroke-width: 4px;
|
||||
stroke: blue;" />
|
||||
<line {{radial secondDegrees 0 .95}}
|
||||
style="stroke-width: 2px;
|
||||
stroke: red;" />
|
||||
{{/with}}
|
||||
|
||||
<!-- tick marks -->
|
||||
{{#each hours}}
|
||||
<line {{radial degrees 0.9 1}}
|
||||
style="stroke-width: 3px;
|
||||
stroke: black;" />
|
||||
{{/each}}
|
||||
</svg>
|
||||
</body>
|
||||
|
||||
<!-- Adapted from David Basoko's SVG clock demo -->
|
||||
|
||||
33
examples/clock/client/clock.js
Normal file
33
examples/clock/client/clock.js
Normal file
@@ -0,0 +1,33 @@
|
||||
Meteor.setInterval(function () {
|
||||
Session.set('time', new Date);
|
||||
}, 1000);
|
||||
|
||||
UI.body.helpers({
|
||||
|
||||
hours: _.range(0, 12),
|
||||
|
||||
degrees: function () {
|
||||
return 30 * this;
|
||||
},
|
||||
|
||||
handData: function () {
|
||||
var time = Session.get('time') || new Date;
|
||||
return { hourDegrees: time.getHours() * 30,
|
||||
minuteDegrees: time.getMinutes() * 6,
|
||||
secondDegrees: time.getSeconds() * 6 };
|
||||
},
|
||||
|
||||
radial: function (angleDegrees,
|
||||
startFraction,
|
||||
endFraction) {
|
||||
var r = 100;
|
||||
var radians = (angleDegrees-90) / 180 * Math.PI;
|
||||
|
||||
return {
|
||||
x1: r * startFraction * Math.cos(radians),
|
||||
y1: r * startFraction * Math.sin(radians),
|
||||
x2: r * endFraction * Math.cos(radians),
|
||||
y2: r * endFraction * Math.sin(radians)
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -6,4 +6,3 @@
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.2
|
||||
0.8.0.1
|
||||
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
|
||||
#outer {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.player {
|
||||
|
||||
1
examples/other/domrange-grid/.meteor/.gitignore
vendored
Normal file
1
examples/other/domrange-grid/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
10
examples/other/domrange-grid/.meteor/packages
Normal file
10
examples/other/domrange-grid/.meteor/packages
Normal file
@@ -0,0 +1,10 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
ui
|
||||
1
examples/other/domrange-grid/.meteor/release
Normal file
1
examples/other/domrange-grid/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
none
|
||||
18
examples/other/domrange-grid/domrange-grid.css
Normal file
18
examples/other/domrange-grid/domrange-grid.css
Normal file
@@ -0,0 +1,18 @@
|
||||
/* CSS declarations go here */
|
||||
|
||||
|
||||
* { margin: 0; padding: 0 }
|
||||
|
||||
#grid td {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.color0 { background: #eee; }
|
||||
.color1 { background: #d8f; }
|
||||
.color2 { background: #8c3; }
|
||||
.color3 { background: #39d; }
|
||||
.color4 { background: #d96; }
|
||||
.color5 { background: #a95; }
|
||||
7
examples/other/domrange-grid/domrange-grid.html
Normal file
7
examples/other/domrange-grid/domrange-grid.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<head>
|
||||
<title>domrange-grid</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
105
examples/other/domrange-grid/domrange-grid.js
Normal file
105
examples/other/domrange-grid/domrange-grid.js
Normal file
@@ -0,0 +1,105 @@
|
||||
if (Meteor.isClient) {
|
||||
Meteor.startup(function () {
|
||||
var N = 10;
|
||||
var numColors = 6;
|
||||
var colors = [];
|
||||
for(var z = 0; z < numColors; z++)
|
||||
colors[z] = z;
|
||||
|
||||
var guid = 1;
|
||||
|
||||
var table = $('<table id="grid"></table>');
|
||||
$(table).appendTo("body");
|
||||
var rows = [];
|
||||
var tableContent = new UI.DomRange;
|
||||
var makeCell = function (row) {
|
||||
var cells = row.cells;
|
||||
var tr = row.dom.elements()[0];
|
||||
var cell = {color: Random.choice(colors),
|
||||
guid: String(guid++)};
|
||||
cell.dom = new UI.DomRange(cell);
|
||||
cells.push(cell);
|
||||
cell.dom.add(cell.guid, $('<td class="color' +
|
||||
cell.color + '">' +
|
||||
cell.color + '</td>'));
|
||||
row.content.add(cell.guid, cell);
|
||||
};
|
||||
var makeRow = function () {
|
||||
var row = {cells: [], guid: String(guid++),
|
||||
content: new UI.DomRange};
|
||||
row.dom = new UI.DomRange(row);
|
||||
rows.push(row);
|
||||
tableContent.add(row.guid, row);
|
||||
var tr = $("<tr></tr>")[0];
|
||||
row.dom.add(tr);
|
||||
UI.DomRange.insert(row.content, tr);
|
||||
var cells = row.cells;
|
||||
for(var c = 0; c < N; c++)
|
||||
makeCell(row);
|
||||
};
|
||||
for (var r = 0; r < N; r++)
|
||||
makeRow();
|
||||
|
||||
UI.DomRange.insert(tableContent, table[0]);
|
||||
|
||||
$(document).on('keydown', function (evt) {
|
||||
var deltaN = 0;
|
||||
var deltaC = 0;
|
||||
if (evt.which === 38) {
|
||||
deltaN = 1; // up
|
||||
} else if (evt.which === 40) {
|
||||
deltaN = -1; // down
|
||||
} else if (evt.which === 37) {
|
||||
deltaC = -1; // left
|
||||
} else if (evt.which === 39) {
|
||||
deltaC = 1; // right
|
||||
} else if (evt.which === 32) {
|
||||
// spacebar
|
||||
var row0 = rows.shift();
|
||||
rows.push(row0);
|
||||
tableContent.moveBefore(row0.guid, null);
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
var cell0 = row.cells.shift();
|
||||
row.cells.push(cell0);
|
||||
row.content.moveBefore(cell0.guid, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (deltaN === 1) {
|
||||
N += 1;
|
||||
for (var i = 0; i < N - 1; i++)
|
||||
// lengthen old rows
|
||||
makeCell(rows[i]);
|
||||
makeRow();
|
||||
} else if (deltaN === -1) {
|
||||
if (N === 0)
|
||||
return;
|
||||
N -= 1;
|
||||
tableContent.remove(rows[N].guid);
|
||||
rows.length = N;
|
||||
for (var i = 0; i < N; i++) {
|
||||
var row = rows[i];
|
||||
row.content.remove(row.cells[N].guid);
|
||||
rows[i].cells.length = N;
|
||||
}
|
||||
}
|
||||
|
||||
if (deltaC) {
|
||||
for (var r = 0; r < N; r++) {
|
||||
var row = rows[r];
|
||||
for (var c = 0; c < N; c++) {
|
||||
var cell = row.cells[c];
|
||||
var td = cell.dom.elements()[0];
|
||||
var color =
|
||||
(cell.color =
|
||||
(cell.color + deltaC + numColors)
|
||||
% numColors);
|
||||
td.innerHTML = color;
|
||||
td.className = 'color' + color;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<li>Logged out — otherwise</li>
|
||||
</ul>
|
||||
<p>If you reload the page while logged in, you'll start in the "logging in" state and see the "Loading..." message until the data loads. Because logging in doesn't complete until all subscriptions have been rerun and finished loading, and the app only serves data when you're logged in, the "logging in" state encompasses loading the initial data for all subscriptions and is the only loading screen we need.</p>
|
||||
<p>To configure this app for Google auth, the easiest way is to add the <code>accounts-ui</code> package, add <code>{{loginButtons}}</code> to the end of the body, and use the configuration wizard.</p>
|
||||
<p>To configure this app for Google auth, the easiest way is to add the <code>accounts-ui</code> package, add <code>{{> loginButtons}}</code> to the end of the body, and use the configuration wizard.</p>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ X={{x}}<br>
|
||||
<br>
|
||||
{{count}} circles<br>
|
||||
<input type="button" value="Add" class="add">
|
||||
<input type="button" value="Remove" class="remove" {{{disabled}}}>
|
||||
<input type="button" value="Remove" class="remove" {{disabled}}>
|
||||
<input type="button" value="Scram" class="scram">
|
||||
<input type="button" value="Clear" class="clear">
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@ if (typeof Session.get("spinForward") !== 'boolean') {
|
||||
Template.preserveDemo.preserve([ '.spinner', '.spinforward' ]);
|
||||
|
||||
Template.preserveDemo.spinForwardChecked = function () {
|
||||
return Session.get('spinForward') ? 'checked="checked"' : '';
|
||||
return Session.get('spinForward') ? 'checked' : '';
|
||||
};
|
||||
|
||||
Template.preserveDemo.spinAnim = function () {
|
||||
@@ -69,7 +69,7 @@ Template.preserveDemo.events({
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Template.constantDemo.checked = function (which) {
|
||||
return Session.get('mapchecked' + which) ? 'checked="checked"' : '';
|
||||
return Session.get('mapchecked' + which) ? 'checked' : '';
|
||||
};
|
||||
|
||||
Template.constantDemo.show = function (which) {
|
||||
@@ -193,7 +193,7 @@ Template.circles.count = function () {
|
||||
|
||||
Template.circles.disabled = function () {
|
||||
return Session.get("selectedCircle:" + this.group) ?
|
||||
'' : 'disabled="disabled"';
|
||||
'' : 'disabled';
|
||||
};
|
||||
|
||||
Template.circles.created = function () {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
preserve-inputs
|
||||
accounts-ui
|
||||
accounts-password
|
||||
d3
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.2
|
||||
0.8.0.1
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
<div class="span5">
|
||||
<div style="float: right">
|
||||
{{loginButtons align="right"}}
|
||||
{{> loginButtons align="right"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,13 +54,11 @@
|
||||
|
||||
<template name="map">
|
||||
<div class="map">
|
||||
{{#constant}}
|
||||
<svg width="500" height="500">
|
||||
<circle class="callout" cx=-100 cy=-100></circle>
|
||||
<g class="circles"></g>
|
||||
<g class="labels"></g>
|
||||
</svg>
|
||||
{{/constant}}
|
||||
<svg width="500" height="500">
|
||||
<circle class="callout" cx=-100 cy=-100></circle>
|
||||
<g class="circles"></g>
|
||||
<g class="labels"></g>
|
||||
</svg>
|
||||
<div>
|
||||
<small class="attribution muted">©
|
||||
<a href="http://www.openstreetmap.org/?lat=37.78212&lon=-122.40146&zoom=15&layers=M"
|
||||
|
||||
@@ -8,5 +8,4 @@ underscore
|
||||
backbone
|
||||
spiderable
|
||||
jquery
|
||||
preserve-inputs
|
||||
insecure
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.2
|
||||
0.8.0.1
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{{else}}
|
||||
<div class="destroy"></div>
|
||||
<div class="display">
|
||||
<input class="check" name="markdone" type="checkbox" {{{done_checkbox}}} />
|
||||
<input class="check" name="markdone" type="checkbox" checked={{done}} />
|
||||
<div class="todo-text">{{text}}</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -188,10 +188,6 @@ Template.todo_item.done_class = function () {
|
||||
return this.done ? 'done' : '';
|
||||
};
|
||||
|
||||
Template.todo_item.done_checkbox = function () {
|
||||
return this.done ? 'checked="checked"' : '';
|
||||
};
|
||||
|
||||
Template.todo_item.editing = function () {
|
||||
return Session.equals('editing_itemname', this._id);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
</body>
|
||||
|
||||
<template name="radio">
|
||||
<span class="radio"><input id="{{key}}:{{value}}" {{{maybeChecked}}} type="radio" name="{{key}}" value="{{value}}" />{{! no whitespace}}<label for="{{key}}:{{value}}">{{label}}</label></span>
|
||||
<span class="radio">
|
||||
<input id="{{key}}:{{value}}" {{maybeChecked}} type="radio" name="{{key}}" value="{{value}}" />
|
||||
<label for="{{key}}:{{value}}">{{label}}</label>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template name="button">
|
||||
@@ -18,92 +21,92 @@
|
||||
<div id="controlpane">
|
||||
<div class="group">
|
||||
<h3>Dropdown align edge:</h3>
|
||||
{{radio "alignRight" "false" "Left"}}
|
||||
{{radio "alignRight" "true" "Right"}}
|
||||
{{> radio key="alignRight" value="false" label="Left"}}
|
||||
{{> radio key="alignRight" value="true" label="Right"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Positioning:</h3>
|
||||
{{radio "positioning" "relative" "Relative"}}
|
||||
{{radio "positioning" "absolute" "Absolute"}}
|
||||
{{radio "positioning" "floatRight" "Float:right"}}
|
||||
{{radio "positioning" "inline" "Inline"}}
|
||||
{{> radio key="positioning" value="relative" label="Relative"}}
|
||||
{{> radio key="positioning" value="absolute" label="Absolute"}}
|
||||
{{> radio key="positioning" value="floatRight" label="Float:right"}}
|
||||
{{> radio key="positioning" value="inline" label="Inline"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>How many third-party services?</h3>
|
||||
{{radio "numServices" "0" "0"}}
|
||||
{{radio "numServices" "1" "1"}}
|
||||
{{radio "numServices" "2" "2"}}
|
||||
{{radio "numServices" "3" "3"}}
|
||||
{{> radio key="numServices" value="0" label="0"}}
|
||||
{{> radio key="numServices" value="1" label="1"}}
|
||||
{{> radio key="numServices" value="2" label="2"}}
|
||||
{{> radio key="numServices" value="3" label="3"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Has password accounts?</h3>
|
||||
{{radio "hasPasswords" "false" "No"}}
|
||||
{{radio "hasPasswords" "true" "Yes"}}
|
||||
{{> radio key="hasPasswords" value="false" label="No"}}
|
||||
{{> radio key="hasPasswords" value="true" label="Yes"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Password sign-up fields:</h3>
|
||||
{{radio "signupFields" "EMAIL_ONLY" "Email"}}
|
||||
{{radio "signupFields" "USERNAME_ONLY" "Username"}}
|
||||
{{radio "signupFields" "USERNAME_AND_EMAIL" "Username & Email"}}
|
||||
{{radio "signupFields" "USERNAME_AND_OPTIONAL_EMAIL" "Username & Optional Email"}}
|
||||
{{> radio key="signupFields" value="EMAIL_ONLY" label="Email"}}
|
||||
{{> radio key="signupFields" value="USERNAME_ONLY" label="Username"}}
|
||||
{{> radio key="signupFields" value="USERNAME_AND_EMAIL" label="Username & Email"}}
|
||||
{{> radio key="signupFields" value="USERNAME_AND_OPTIONAL_EMAIL" label="Username & Optional Email"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Fake-Configure:</h3>
|
||||
{{button "fakeConfig" "facebook" "Facebook"}}
|
||||
{{button "fakeConfig" "github" "GitHub"}}
|
||||
{{button "fakeConfig" "google" "Google"}}
|
||||
{{> button key="fakeConfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="fakeConfig" value="github" label="GitHub"}}
|
||||
{{> button key="fakeConfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Show Configure Dialog:</h3>
|
||||
{{button "showConfig" "facebook" "Facebook"}}
|
||||
{{button "showConfig" "github" "GitHub"}}
|
||||
{{button "showConfig" "google" "Google"}}
|
||||
{{> button key="showConfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="showConfig" value="github" label="GitHub"}}
|
||||
{{> button key="showConfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Unconfigure:</h3>
|
||||
{{button "unconfig" "facebook" "Facebook"}}
|
||||
{{button "unconfig" "github" "GitHub"}}
|
||||
{{button "unconfig" "google" "Google"}}
|
||||
{{> button key="unconfig" value="facebook" label="Facebook"}}
|
||||
{{> button key="unconfig" value="github" label="GitHub"}}
|
||||
{{> button key="unconfig" value="google" label="Google"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Messages:</h3>
|
||||
{{button "messages" "error" "Error"}}
|
||||
{{button "messages" "info" "Info"}}
|
||||
{{button "messages" "clear" "Clear"}}
|
||||
{{> button key="messages" value="error" label="Error"}}
|
||||
{{> button key="messages" value="info" label="Info"}}
|
||||
{{> button key="messages" value="clear" label="Clear"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Signing in/out</h3>
|
||||
{{button "sign" "in" "Fake sign-in"}}
|
||||
{{button "sign" "out" "Sign out"}}
|
||||
{{> button key="sign" value="in" label="Fake sign-in"}}
|
||||
{{> button key="sign" value="out" label="Sign out"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logged-out Views</h3>
|
||||
{{button "lov" "signIn" "Sign In"}}
|
||||
{{button "lov" "createAccount" "Create Account"}}
|
||||
{{button "lov" "forgotPassword" "Forgot Password"}}
|
||||
{{> button key="lov" value="signIn" label="Sign In"}}
|
||||
{{> button key="lov" value="createAccount" label="Create Account"}}
|
||||
{{> button key="lov" value="forgotPassword" label="Forgot Password"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logged-in Views</h3>
|
||||
{{button "liv" "accountButtons" "Account Buttons"}}
|
||||
{{button "liv" "changePassword" "Change Password"}}
|
||||
{{button "liv" "messageOnly" "Message Only"}}
|
||||
{{> button key="liv" value="accountButtons" label="Account Buttons"}}
|
||||
{{> button key="liv" value="changePassword" label="Change Password"}}
|
||||
{{> button key="liv" value="messageOnly" label="Message Only"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Other Modals</h3>
|
||||
{{button "modals" "resetPassword" "Reset Password"}}
|
||||
{{button "modals" "enrollAccount" "Enroll Account"}}
|
||||
{{button "modals" "justVerifiedEmail" "Verified Email"}}
|
||||
{{> button key="modals" value="resetPassword" label="Reset Password"}}
|
||||
{{> button key="modals" value="enrollAccount" label="Enroll Account"}}
|
||||
{{> button key="modals" value="justVerifiedEmail" label="Verified Email"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Logging-in Spinner</h3>
|
||||
{{radio "fakeLoggingIn" "false" "Off"}}
|
||||
{{radio "fakeLoggingIn" "true" "Pretend loggingIn=true"}}
|
||||
{{> radio key="fakeLoggingIn" value="false" label="Off"}}
|
||||
{{> radio key="fakeLoggingIn" value="true" label="Pretend loggingIn=true"}}
|
||||
</div>
|
||||
<div class="group">
|
||||
<h3>Background Color</h3>
|
||||
{{radio "bgcolor" "white" "White"}}
|
||||
{{radio "bgcolor" "black" "Black"}}
|
||||
{{radio "bgcolor" "red" "Red"}}
|
||||
{{> radio key="bgcolor" value="white" label="White"}}
|
||||
{{> radio key="bgcolor" value="black" label="Black"}}
|
||||
{{> radio key="bgcolor" value="red" label="Red"}}
|
||||
</div>
|
||||
</div>
|
||||
{{#with settings}}
|
||||
@@ -112,7 +115,7 @@
|
||||
{{#if match "positioning:inline"}}
|
||||
Here is a place to sign in, yay!
|
||||
{{/if}}
|
||||
{{loginButtons align=dropdownAlign}}
|
||||
{{> loginButtons align=dropdownAlign}}
|
||||
{{#if match "positioning:inline"}}
|
||||
Isn't that great?
|
||||
{{/if}}
|
||||
|
||||
@@ -87,20 +87,10 @@ if (Meteor.isClient) {
|
||||
Template.radio.maybeChecked = function () {
|
||||
var curValue = Session.get('settings')[this.key];
|
||||
if (castValue(this.value) === curValue)
|
||||
return 'checked="checked"';
|
||||
return 'checked';
|
||||
return '';
|
||||
};
|
||||
|
||||
Template.page.radio = function (key, value, label) {
|
||||
return new Handlebars.SafeString(
|
||||
Template.radio({key: key, value: value, label: label}));
|
||||
};
|
||||
|
||||
Template.page.button = function (key, value, label) {
|
||||
return new Handlebars.SafeString(
|
||||
Template.button({key: key, value: value, label: label}));
|
||||
};
|
||||
|
||||
Template.page.match = function (kv) {
|
||||
kv = keyValueFromId(kv);
|
||||
if (! kv)
|
||||
@@ -218,4 +208,4 @@ if (Meteor.isClient) {
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1
examples/unfinished/atoms/.meteor/.gitignore
vendored
Normal file
1
examples/unfinished/atoms/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
9
examples/unfinished/atoms/.meteor/packages
Normal file
9
examples/unfinished/atoms/.meteor/packages
Normal file
@@ -0,0 +1,9 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
underscore
|
||||
1
examples/unfinished/atoms/.meteor/release
Normal file
1
examples/unfinished/atoms/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
none
|
||||
12
examples/unfinished/atoms/atoms.css
Normal file
12
examples/unfinished/atoms/atoms.css
Normal file
@@ -0,0 +1,12 @@
|
||||
g[class=atom] circle {
|
||||
stroke: black;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
g[class=atom] text {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 24px;
|
||||
fill: black;
|
||||
font-weight: bold;
|
||||
text-anchor: middle;
|
||||
}
|
||||
28
examples/unfinished/atoms/atoms.html
Normal file
28
examples/unfinished/atoms/atoms.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<head>
|
||||
<title>Atoms</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="atoms">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 500 500">
|
||||
{{#atom x=100 y=100 color="#ffff00"}}O{{/atom}}
|
||||
{{> hydrogen x=150 y=100}}
|
||||
{{#giantatom x=250 y=100 color="#ff9999"}}My{{/giantatom}}
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template name="atom">
|
||||
<g class="atom">
|
||||
<circle style="fill: {{#if color}}{{color}}{{else}}white{{/if}}" cx={{x}} cy={{y}} r={{#if r}}{{r}}{{else}}20{{/if}} />
|
||||
<text x={{x}} y={{textY}}>{{> UI.contentBlock}}</text>
|
||||
</g>
|
||||
</template>
|
||||
|
||||
<template name="hydrogen">
|
||||
{{#atom x=x y=y r=r color="#00ffff"}}H{{/atom}}
|
||||
</template>
|
||||
|
||||
<template name="giantatom">
|
||||
{{#atom x=x y=y r=50 color=color}}{{> UI.contentBlock}}{{/atom}}
|
||||
</template>
|
||||
5
examples/unfinished/atoms/atoms.js
Normal file
5
examples/unfinished/atoms/atoms.js
Normal file
@@ -0,0 +1,5 @@
|
||||
if (Meteor.isClient) {
|
||||
Template.atom.textY = function () {
|
||||
return this.y + 8;
|
||||
};
|
||||
}
|
||||
1
examples/unfinished/movers/.meteor/.gitignore
vendored
Normal file
1
examples/unfinished/movers/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
10
examples/unfinished/movers/.meteor/packages
Normal file
10
examples/unfinished/movers/.meteor/packages
Normal file
@@ -0,0 +1,10 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
less
|
||||
1
examples/unfinished/movers/.meteor/release
Normal file
1
examples/unfinished/movers/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
none
|
||||
2252
examples/unfinished/movers/client/jquery-ui-sortable.js
vendored
Executable file
2252
examples/unfinished/movers/client/jquery-ui-sortable.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
16
examples/unfinished/movers/movers.html
Normal file
16
examples/unfinished/movers/movers.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<head>
|
||||
<title>movers</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> main}}
|
||||
</body>
|
||||
|
||||
<template name="main">
|
||||
<div class="container">
|
||||
<div class="item red">Red</div>
|
||||
<div class="item green">Green</div>
|
||||
<div class="item blue">Blue</div>
|
||||
<div class="item yellow">Yellow</div>
|
||||
</div>
|
||||
</template>
|
||||
92
examples/unfinished/movers/movers.js
Normal file
92
examples/unfinished/movers/movers.js
Normal file
@@ -0,0 +1,92 @@
|
||||
if (Meteor.isClient) {
|
||||
var moveCount = 0;
|
||||
var MOVE_INTERVAL = 3000;
|
||||
var MOVE_DURATION = 2000;
|
||||
|
||||
doMove = function () {
|
||||
moveCount++;
|
||||
if (moveCount % 2 === 1) {
|
||||
animateToBefore($('.green'), $('.yellow'));
|
||||
animateToBefore($('.red'), null);
|
||||
animateToBefore($('.blue'), null);
|
||||
} else {
|
||||
animateToBefore($('.red'), null);
|
||||
animateToBefore($('.green'), null);
|
||||
animateToBefore($('.blue'), null);
|
||||
animateToBefore($('.yellow'), null);
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.startup(function () {
|
||||
doMove();
|
||||
window.setInterval(doMove, MOVE_INTERVAL);
|
||||
});
|
||||
|
||||
|
||||
animateToBefore = function ($n, $newNext) {
|
||||
// we don't use jQuery's `.css()` for these because we want the
|
||||
// element's own style, not the computed style
|
||||
var oldTop = $n[0].style.top;
|
||||
var oldPosition = $n[0].style.position;
|
||||
var oldZIndex = $n[0].style.zIndex;
|
||||
var oldMarginBottom = $n[0].style.marginBottom;
|
||||
|
||||
var outerHeight = $n.outerHeight(); // not margin
|
||||
var marginBottom = parseInt($n.css('margin-bottom'));
|
||||
|
||||
// TODO: test interesting elements like table rows, etc.
|
||||
var placeholder = $(document.createElement($n[0].nodeName));
|
||||
var placeholderHeight = outerHeight + marginBottom;
|
||||
placeholder.css('height', placeholderHeight);
|
||||
// insert placeholder
|
||||
$n.before(placeholder);
|
||||
|
||||
// move node
|
||||
if ($newNext)
|
||||
$newNext.before($n);
|
||||
else
|
||||
$n.parent().append($n);
|
||||
|
||||
// XXX would tracking "left" as well as "top" magically get us
|
||||
// horizontal re-ordering?
|
||||
$n.css({marginBottom: -outerHeight,
|
||||
position: 'relative',
|
||||
zIndex: 2,
|
||||
top: 0});
|
||||
var vOffset = placeholder.offset().top - $n.offset().top;
|
||||
$n.css('top', vOffset);
|
||||
|
||||
$({t:0}).animate({t:1}, {
|
||||
duration: MOVE_DURATION,
|
||||
step: function (t, fx) {
|
||||
var curPlaceholderHeight = Math.round(placeholderHeight * (1-t));
|
||||
var curMarginBottom = marginBottom - curPlaceholderHeight;
|
||||
var curTop = (-curPlaceholderHeight +
|
||||
Math.round((1-t) * (vOffset + placeholderHeight)));
|
||||
$n.css({marginBottom: curMarginBottom,
|
||||
top: curTop});
|
||||
placeholder.css('height', curPlaceholderHeight);
|
||||
},
|
||||
progress: function (a, t) {
|
||||
// if (t >= 0.5) {
|
||||
// console.log(a);
|
||||
// a.stop();
|
||||
// }
|
||||
},
|
||||
complete: function () {
|
||||
placeholder.remove();
|
||||
$n[0].style.top = oldTop;
|
||||
$n[0].style.position = oldPosition;
|
||||
$n[0].style.zIndex = oldZIndex;
|
||||
$n[0].style.marginBottom = oldMarginBottom;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(function () {
|
||||
// code to run on server at startup
|
||||
});
|
||||
}
|
||||
12
examples/unfinished/movers/movers.less
Normal file
12
examples/unfinished/movers/movers.less
Normal file
@@ -0,0 +1,12 @@
|
||||
.item {
|
||||
border: 1px solid black;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
}
|
||||
.red { background: #fcc; }
|
||||
.blue { background: #ccf; }
|
||||
.green { background: #cfc; }
|
||||
.yellow { background: #ffc; }
|
||||
1
examples/unfinished/reorderable-list/.meteor/.gitignore
vendored
Normal file
1
examples/unfinished/reorderable-list/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
8
examples/unfinished/reorderable-list/.meteor/packages
Normal file
8
examples/unfinished/reorderable-list/.meteor/packages
Normal file
@@ -0,0 +1,8 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
1
examples/unfinished/reorderable-list/.meteor/release
Normal file
1
examples/unfinished/reorderable-list/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
none
|
||||
2252
examples/unfinished/reorderable-list/client/jquery-ui-sortable.js
vendored
Executable file
2252
examples/unfinished/reorderable-list/client/jquery-ui-sortable.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
10
examples/unfinished/reorderable-list/client/shark.css
Normal file
10
examples/unfinished/reorderable-list/client/shark.css
Normal file
@@ -0,0 +1,10 @@
|
||||
#list div {
|
||||
padding: 10px;
|
||||
height: 19px;
|
||||
border: 1px solid #bbb;
|
||||
margin: 8px;
|
||||
font-weight: bold;
|
||||
cursor: move;
|
||||
width: 400px;
|
||||
background: #eee; }
|
||||
|
||||
9
examples/unfinished/reorderable-list/client/shark.html
Normal file
9
examples/unfinished/reorderable-list/client/shark.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<body>
|
||||
<div id="list">
|
||||
{{#each items}}
|
||||
<div class="item">
|
||||
{{text}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</body>
|
||||
30
examples/unfinished/reorderable-list/client/shark.js
Normal file
30
examples/unfinished/reorderable-list/client/shark.js
Normal file
@@ -0,0 +1,30 @@
|
||||
UI.body.items = Items.find({}, { sort: { rank: 1 } });
|
||||
|
||||
SimpleRationalRanks = {
|
||||
beforeFirst: function (firstRank) { return firstRank - 1; },
|
||||
between: function (beforeRank, afterRank) { return (beforeRank + afterRank) / 2; },
|
||||
afterLast: function (lastRank) { return lastRank + 1; }
|
||||
};
|
||||
|
||||
UI.body.rendered = function () {
|
||||
$(this.find('#list')).sortable({ // uses the 'sortable' interaction from jquery ui
|
||||
stop: function (event, ui) { // fired when an item is dropped
|
||||
var el = ui.item.get(0), before = ui.item.prev().get(0), after = ui.item.next().get(0);
|
||||
|
||||
var newRank;
|
||||
if (!before) { // moving to the top of the list
|
||||
newRank = SimpleRationalRanks.beforeFirst(UI.getElementData(after).rank);
|
||||
|
||||
} else if (!after) { // moving to the bottom of the list
|
||||
newRank = SimpleRationalRanks.afterLast(UI.getElementData(before).rank);
|
||||
|
||||
} else {
|
||||
newRank = SimpleRationalRanks.between(
|
||||
UI.getElementData(before).rank,
|
||||
UI.getElementData(after).rank);
|
||||
}
|
||||
|
||||
Items.update(UI.getElementData(el)._id, {$set: {rank: newRank}});
|
||||
}
|
||||
});
|
||||
};
|
||||
9
examples/unfinished/reorderable-list/lib/items.js
Normal file
9
examples/unfinished/reorderable-list/lib/items.js
Normal file
@@ -0,0 +1,9 @@
|
||||
Items = new Meteor.Collection("items");
|
||||
|
||||
if (Meteor.isServer) {
|
||||
if (Items.find().count() === 0) {
|
||||
_.each(
|
||||
["violet", "unicorn", "flask", "jar", "leitmotif", "rearrange", "right", "ethereal"],
|
||||
function (text, index) { Items.insert({text: text, rank: index}); });
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,3 @@
|
||||
standard-app-packages
|
||||
insecure
|
||||
jquery
|
||||
preserve-inputs
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.2
|
||||
0.8.0.1
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
<div>
|
||||
<button id="startgame" class="startgame" {{{disabled}}}>
|
||||
<button id="startgame" class="startgame" {{disabled}}>
|
||||
{{#if count}} It's on! {{else}} Play solo {{/if}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -24,9 +24,9 @@ var set_selected_positions = function (word) {
|
||||
}
|
||||
|
||||
for (var pos = 0; pos < 16; pos++) {
|
||||
if (last_in_a_path.indexOf(pos) !== -1)
|
||||
if (_.indexOf(last_in_a_path, pos) !== -1)
|
||||
Session.set('selected_' + pos, 'last_in_path');
|
||||
else if (in_a_path.indexOf(pos) !== -1)
|
||||
else if (_.indexOf(in_a_path, pos) !== -1)
|
||||
Session.set('selected_' + pos, 'in_path');
|
||||
else
|
||||
Session.set('selected_' + pos, false);
|
||||
@@ -68,13 +68,14 @@ Template.lobby.disabled = function () {
|
||||
var me = player();
|
||||
if (me && me.name)
|
||||
return '';
|
||||
return 'disabled="disabled"';
|
||||
return 'disabled';
|
||||
};
|
||||
|
||||
var trim = function (string) { return string.replace(/^\s+|\s+$/g, ''); };
|
||||
|
||||
Template.lobby.events({
|
||||
'keyup input#myname': function (evt) {
|
||||
var name = $('#lobby input#myname').val().trim();
|
||||
var name = trim($('#lobby input#myname').val());
|
||||
Players.update(Session.get('player_id'), {$set: {name: name}});
|
||||
},
|
||||
'click button.startgame': function () {
|
||||
@@ -115,7 +116,9 @@ Template.board.clock = function () {
|
||||
Template.board.events({
|
||||
'click .square': function (evt) {
|
||||
var textbox = $('#scratchpad input');
|
||||
textbox.val(textbox.val() + evt.target.innerHTML);
|
||||
// Note: Getting the letter out of the DOM is kind of a hack
|
||||
var letter = evt.target.textContent || evt.target.innerText;
|
||||
textbox.val(textbox.val() + letter);
|
||||
textbox.focus();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ paths_for_word = function (board, word) {
|
||||
|
||||
for (var i = 0; i < positions_to_try.length; i++) {
|
||||
var pos = positions_to_try[i];
|
||||
if (board[pos] === word[0] && path.indexOf(pos) === -1)
|
||||
if (board[pos] === word[0] && _.indexOf(path, pos) === -1)
|
||||
check_path(word.slice(1), // cdr of word
|
||||
path.concat([pos]), // append matching loc to path
|
||||
ADJACENCIES[pos]); // only look at surrounding tiles
|
||||
|
||||
2
meteor
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUNDLE_VERSION=0.3.33
|
||||
BUNDLE_VERSION=0.3.34
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -215,38 +215,40 @@ Meteor.logout = function (callback) {
|
||||
};
|
||||
|
||||
Meteor.logoutOtherClients = function (callback) {
|
||||
// Call the `logoutOtherClients` method. Store the login token that we get
|
||||
// back and use it to log in again. The server is not supposed to close
|
||||
// connections on the old token for 10 seconds, so we should have time to
|
||||
// store our new token and log in with it before being disconnected. If we get
|
||||
// disconnected, then we'll immediately reconnect with the new token. If for
|
||||
// some reason we get disconnected before storing the new token, then the
|
||||
// worst that will happen is that we'll have a flicker from trying to log in
|
||||
// with the old token before storing and logging in with the new one.
|
||||
Accounts.connection.apply('logoutOtherClients', [], { wait: true },
|
||||
function (error, result) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
} else {
|
||||
var userId = Meteor.userId();
|
||||
storeLoginToken(userId, result.token, result.tokenExpires);
|
||||
// If the server hasn't disconnected us yet by deleting our
|
||||
// old token, then logging in now with the new valid token
|
||||
// will prevent us from getting disconnected. If the server
|
||||
// has already disconnected us due to our old invalid token,
|
||||
// then we would have already tried and failed to login with
|
||||
// the old token on reconnect, and we have to make sure a
|
||||
// login method gets sent here with the new token.
|
||||
Meteor.loginWithToken(result.token, function (err) {
|
||||
if (err &&
|
||||
storedLoginToken() &&
|
||||
storedLoginToken().token === result.token) {
|
||||
makeClientLoggedOut();
|
||||
}
|
||||
callback && callback(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
// We need to make two method calls: one to replace our current token,
|
||||
// and another to remove all tokens except the current one. We want to
|
||||
// call these two methods one after the other, without any other
|
||||
// methods running between them. For example, we don't want `logout`
|
||||
// to be called in between our two method calls (otherwise the second
|
||||
// method call would return an error). Another example: we don't want
|
||||
// logout to be called before the callback for `getNewToken`;
|
||||
// otherwise we would momentarily log the user out and then write a
|
||||
// new token to localStorage.
|
||||
//
|
||||
// To accomplish this, we make both calls as wait methods, and queue
|
||||
// them one after the other, without spinning off the event loop in
|
||||
// between. Even though we queue `removeOtherTokens` before
|
||||
// `getNewToken`, we won't actually send the `removeOtherTokens` call
|
||||
// until the `getNewToken` callback has finished running, because they
|
||||
// are both wait methods.
|
||||
Accounts.connection.apply(
|
||||
'getNewToken',
|
||||
[],
|
||||
{ wait: true },
|
||||
function (err, result) {
|
||||
if (! err) {
|
||||
storeLoginToken(Meteor.userId(), result.token, result.tokenExpires);
|
||||
}
|
||||
}
|
||||
);
|
||||
Accounts.connection.apply(
|
||||
'removeOtherTokens',
|
||||
[],
|
||||
{ wait: true },
|
||||
function (err) {
|
||||
callback && callback(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -269,13 +271,13 @@ Accounts.loginServicesConfigured = function () {
|
||||
/// HANDLEBARS HELPERS
|
||||
///
|
||||
|
||||
// If we're using Handlebars, register the {{currentUser}} and
|
||||
// {{loggingIn}} global helpers.
|
||||
if (Package.handlebars) {
|
||||
Package.handlebars.Handlebars.registerHelper('currentUser', function () {
|
||||
// If our app has a UI, register the {{currentUser}} and {{loggingIn}}
|
||||
// global helpers.
|
||||
if (Package.ui) {
|
||||
Package.ui.UI.registerHelper('currentUser', function () {
|
||||
return Meteor.user();
|
||||
});
|
||||
Package.handlebars.Handlebars.registerHelper('loggingIn', function () {
|
||||
Package.ui.UI.registerHelper('loggingIn', function () {
|
||||
return Meteor.loggingIn();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,6 +53,18 @@ Accounts.config = function(options) {
|
||||
"server; some configuration options may not take effect.");
|
||||
}
|
||||
|
||||
// We need to validate the oauthSecretKey option at the time
|
||||
// Accounts.config is called. We also deliberately don't store the
|
||||
// oauthSecretKey in Accounts._options.
|
||||
if (_.has(options, "oauthSecretKey")) {
|
||||
if (Meteor.isClient)
|
||||
throw new Error("The oauthSecretKey option may only be specified on the server");
|
||||
if (! Package["oauth-encryption"])
|
||||
throw new Error("The oauth-encryption package must be loaded to set oauthSecretKey");
|
||||
Package["oauth-encryption"].OAuthEncryption.loadKey(options.oauthSecretKey);
|
||||
options = _.omit(options, "oauthSecretKey");
|
||||
}
|
||||
|
||||
// validate option keys
|
||||
var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation",
|
||||
"restrictCreationByEmailDomain", "loginExpirationInDays"];
|
||||
|
||||
@@ -73,12 +73,19 @@ var validateLogin = function (connection, attempt) {
|
||||
}
|
||||
catch (e) {
|
||||
attempt.allowed = false;
|
||||
// XXX this means the last thrown error overrides previous error
|
||||
// messages. Maybe this is surprising to users and we should make
|
||||
// overriding errors more explicit. (see
|
||||
// https://github.com/meteor/meteor/issues/1960)
|
||||
attempt.error = e;
|
||||
return true;
|
||||
}
|
||||
if (! ret) {
|
||||
attempt.allowed = false;
|
||||
attempt.error = new Meteor.Error(403, "Login forbidden");
|
||||
// don't override a specific error provided by a previous
|
||||
// validator or the initial attempt (eg "incorrect password").
|
||||
if (!attempt.error)
|
||||
attempt.error = new Meteor.Error(403, "Login forbidden");
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -302,6 +309,9 @@ Accounts._reportLoginFailure = function (methodInvocation, methodName, methodArg
|
||||
|
||||
validateLogin(methodInvocation.connection, attempt);
|
||||
failedLogin(methodInvocation.connection, attempt);
|
||||
// validateLogin may mutate attempt to set a new error message. Return
|
||||
// the modified version.
|
||||
return attempt;
|
||||
};
|
||||
|
||||
|
||||
@@ -424,6 +434,17 @@ Meteor.methods({
|
||||
// use. Tests set Accounts._noConnectionCloseDelayForTest to delete tokens
|
||||
// immediately instead of using a delay.
|
||||
//
|
||||
// XXX COMPAT WITH 0.7.2
|
||||
// This single `logoutOtherClients` method has been replaced with two
|
||||
// methods, one that you call to get a new token, and another that you
|
||||
// call to remove all tokens except your own. The new design allows
|
||||
// clients to know when other clients have actually been logged
|
||||
// out. (The `logoutOtherClients` method guarantees the caller that
|
||||
// the other clients will be logged out at some point, but makes no
|
||||
// guarantees about when.) This method is left in for backwards
|
||||
// compatibility, especially since application code might be calling
|
||||
// this method directly.
|
||||
//
|
||||
// @returns {Object} Object with token and tokenExpires keys.
|
||||
logoutOtherClients: function () {
|
||||
var self = this;
|
||||
@@ -441,7 +462,7 @@ Meteor.methods({
|
||||
var tokens = user.services.resume.loginTokens;
|
||||
var newToken = Accounts._generateStampedLoginToken();
|
||||
var userId = self.userId;
|
||||
Meteor.users.update(self.userId, {
|
||||
Meteor.users.update(userId, {
|
||||
$set: {
|
||||
"services.resume.loginTokensToDelete": tokens,
|
||||
"services.resume.haveLoginTokensToDelete": true
|
||||
@@ -462,8 +483,60 @@ Meteor.methods({
|
||||
tokenExpires: Accounts._tokenExpiration(newToken.when)
|
||||
};
|
||||
} else {
|
||||
throw new Error("You are not logged in.");
|
||||
throw new Meteor.Error("You are not logged in.");
|
||||
}
|
||||
},
|
||||
|
||||
// Generates a new login token with the same expiration as the
|
||||
// connection's current token and saves it to the database. Associates
|
||||
// the connection with this new token and returns it. Throws an error
|
||||
// if called on a connection that isn't logged in.
|
||||
//
|
||||
// @returns Object
|
||||
// If successful, returns { token: <new token>, id: <user id>,
|
||||
// tokenExpires: <expiration date> }.
|
||||
getNewToken: function () {
|
||||
var self = this;
|
||||
var user = Meteor.users.findOne(self.userId, {
|
||||
fields: { "services.resume.loginTokens": 1 }
|
||||
});
|
||||
if (! self.userId || ! user) {
|
||||
throw new Meteor.Error("You are not logged in.");
|
||||
}
|
||||
// Be careful not to generate a new token that has a later
|
||||
// expiration than the curren token. Otherwise, a bad guy with a
|
||||
// stolen token could use this method to stop his stolen token from
|
||||
// ever expiring.
|
||||
var currentHashedToken = Accounts._getLoginToken(self.connection.id);
|
||||
var currentStampedToken = _.find(
|
||||
user.services.resume.loginTokens,
|
||||
function (stampedToken) {
|
||||
return stampedToken.hashedToken === currentHashedToken;
|
||||
}
|
||||
);
|
||||
if (! currentStampedToken) { // safety belt: this should never happen
|
||||
throw new Meteor.Error("Invalid login token");
|
||||
}
|
||||
var newStampedToken = Accounts._generateStampedLoginToken();
|
||||
newStampedToken.when = currentStampedToken.when;
|
||||
Accounts._insertLoginToken(self.userId, newStampedToken);
|
||||
return loginUser(self, self.userId, newStampedToken);
|
||||
},
|
||||
|
||||
// Removes all tokens except the token associated with the current
|
||||
// connection. Throws an error if the connection is not logged
|
||||
// in. Returns nothing on success.
|
||||
removeOtherTokens: function () {
|
||||
var self = this;
|
||||
if (! self.userId) {
|
||||
throw new Meteor.Error("You are not logged in.");
|
||||
}
|
||||
var currentToken = Accounts._getLoginToken(self.connection.id);
|
||||
Meteor.users.update(self.userId, {
|
||||
$pull: {
|
||||
"services.resume.loginTokens": { hashedToken: { $ne: currentToken } }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -753,7 +826,7 @@ Accounts.registerLoginHandler("resume", function(options) {
|
||||
// (Also used by Meteor Accounts server and tests).
|
||||
//
|
||||
Accounts._generateStampedLoginToken = function () {
|
||||
return {token: Random.id(), when: (new Date)};
|
||||
return {token: Random.secret(), when: (new Date)};
|
||||
};
|
||||
|
||||
///
|
||||
@@ -815,6 +888,67 @@ maybeStopExpireTokensInterval = function () {
|
||||
expireTokenInterval = Meteor.setInterval(expireTokens,
|
||||
EXPIRE_TOKENS_INTERVAL_MS);
|
||||
|
||||
|
||||
///
|
||||
/// OAuth Encryption Support
|
||||
///
|
||||
|
||||
var OAuthEncryption = Package["oauth-encryption"] && Package["oauth-encryption"].OAuthEncryption;
|
||||
|
||||
|
||||
var usingOAuthEncryption = function () {
|
||||
return OAuthEncryption && OAuthEncryption.keyIsLoaded();
|
||||
};
|
||||
|
||||
|
||||
// OAuth service data is temporarily stored in the pending credentials
|
||||
// collection during the oauth authentication process. Sensitive data
|
||||
// such as access tokens are encrypted without the user id because
|
||||
// we don't know the user id yet. We re-encrypt these fields with the
|
||||
// user id included when storing the service data permanently in
|
||||
// the users collection.
|
||||
//
|
||||
var pinEncryptedFieldsToUser = function (serviceData, userId) {
|
||||
_.each(_.keys(serviceData), function (key) {
|
||||
var value = serviceData[key];
|
||||
if (OAuthEncryption && OAuthEncryption.isSealed(value))
|
||||
value = OAuthEncryption.seal(OAuthEncryption.open(value), userId);
|
||||
serviceData[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Encrypt unencrypted login service secrets when oauth-encryption is
|
||||
// added.
|
||||
//
|
||||
// XXX For the oauthSecretKey to be available here at startup, the
|
||||
// developer must call Accounts.config({oauthSecretKey: ...}) at load
|
||||
// time, instead of in a Meteor.startup block, because the startup
|
||||
// block in the app code will run after this accounts-base startup
|
||||
// block. Perhaps we need a post-startup callback?
|
||||
|
||||
Meteor.startup(function () {
|
||||
if (!usingOAuthEncryption())
|
||||
return;
|
||||
|
||||
var ServiceConfiguration =
|
||||
Package['service-configuration'].ServiceConfiguration;
|
||||
|
||||
ServiceConfiguration.configurations.find( {$and: [
|
||||
{ secret: {$exists: true} },
|
||||
{ "secret.algorithm": {$exists: false} }
|
||||
] } ).
|
||||
forEach(function (config) {
|
||||
ServiceConfiguration.configurations.update(
|
||||
config._id,
|
||||
{ $set: {
|
||||
secret: OAuthEncryption.seal(config.secret)
|
||||
} }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
///
|
||||
/// CREATE USER HOOKS
|
||||
///
|
||||
@@ -851,6 +985,11 @@ Accounts.insertUserDoc = function (options, user) {
|
||||
// collections)
|
||||
user = _.extend({createdAt: new Date(), _id: Random.id()}, user);
|
||||
|
||||
if (user.services)
|
||||
_.each(user.services, function (serviceData) {
|
||||
pinEncryptedFieldsToUser(serviceData, user._id);
|
||||
});
|
||||
|
||||
var fullUser;
|
||||
if (onCreateUserHook) {
|
||||
fullUser = onCreateUserHook(options, user);
|
||||
@@ -986,6 +1125,8 @@ Accounts.updateOrCreateUserFromExternalService = function(
|
||||
var user = Meteor.users.findOne(selector);
|
||||
|
||||
if (user) {
|
||||
pinEncryptedFieldsToUser(serviceData, user._id);
|
||||
|
||||
// We *don't* process options (eg, profile) for update, but we do replace
|
||||
// the serviceData (eg, so that we keep an unexpired access token and
|
||||
// don't cache old email addresses in serviceData.email).
|
||||
@@ -1122,6 +1263,10 @@ Meteor.methods({
|
||||
Package['service-configuration'].ServiceConfiguration;
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, "Service " + options.service + " already configured");
|
||||
|
||||
if (_.has(options, "secret") && usingOAuthEncryption())
|
||||
options.secret = OAuthEncryption.seal(options.secret);
|
||||
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
Meteor.methods({
|
||||
getCurrentLoginToken: function () {
|
||||
return Accounts._getLoginToken(this.connection.id);
|
||||
}
|
||||
});
|
||||
|
||||
// XXX it'd be cool to also test that the right thing happens if options
|
||||
// *are* validated, but Accounts._options is global state which makes this hard
|
||||
// (impossible?)
|
||||
@@ -297,3 +303,73 @@ Tinytest.addAsync(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.add(
|
||||
'accounts - get new token',
|
||||
function (test) {
|
||||
// Test that the `getNewToken` method returns us a valid token, with
|
||||
// the same expiration as our original token.
|
||||
var userId = Accounts.insertUserDoc({}, { username: Random.id() });
|
||||
var stampedToken = Accounts._generateStampedLoginToken();
|
||||
Accounts._insertLoginToken(userId, stampedToken);
|
||||
var conn = DDP.connect(Meteor.absoluteUrl());
|
||||
conn.call('login', { resume: stampedToken.token });
|
||||
test.equal(conn.call('getCurrentLoginToken'),
|
||||
Accounts._hashLoginToken(stampedToken.token));
|
||||
|
||||
var newTokenResult = conn.call('getNewToken');
|
||||
test.equal(newTokenResult.tokenExpires,
|
||||
Accounts._tokenExpiration(stampedToken.when));
|
||||
test.equal(conn.call('getCurrentLoginToken'),
|
||||
Accounts._hashLoginToken(newTokenResult.token));
|
||||
conn.disconnect();
|
||||
|
||||
// A second connection should be able to log in with the new token
|
||||
// we got.
|
||||
var secondConn = DDP.connect(Meteor.absoluteUrl());
|
||||
secondConn.call('login', { resume: newTokenResult.token });
|
||||
secondConn.disconnect();
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.addAsync(
|
||||
'accounts - remove other tokens',
|
||||
function (test, onComplete) {
|
||||
// Test that the `removeOtherTokens` method removes all tokens other
|
||||
// than the caller's token, thereby logging out and closing other
|
||||
// connections.
|
||||
var userId = Accounts.insertUserDoc({}, { username: Random.id() });
|
||||
var stampedTokens = [];
|
||||
var conns = [];
|
||||
|
||||
_.times(2, function (i) {
|
||||
stampedTokens.push(Accounts._generateStampedLoginToken());
|
||||
Accounts._insertLoginToken(userId, stampedTokens[i]);
|
||||
var conn = DDP.connect(Meteor.absoluteUrl());
|
||||
conn.call('login', { resume: stampedTokens[i].token });
|
||||
test.equal(conn.call('getCurrentLoginToken'),
|
||||
Accounts._hashLoginToken(stampedTokens[i].token));
|
||||
conns.push(conn);
|
||||
});
|
||||
|
||||
conns[0].call('removeOtherTokens');
|
||||
simplePoll(
|
||||
function () {
|
||||
var tokens = _.map(conns, function (conn) {
|
||||
return conn.call('getCurrentLoginToken');
|
||||
});
|
||||
return ! tokens[1] &&
|
||||
tokens[0] === Accounts._hashLoginToken(stampedTokens[0].token);
|
||||
},
|
||||
function () { // success
|
||||
_.each(conns, function (conn) {
|
||||
conn.disconnect();
|
||||
});
|
||||
onComplete();
|
||||
},
|
||||
function () { // timed out
|
||||
throw new Error("accounts - remove other tokens timed out");
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -25,14 +25,16 @@ Package.on_use(function (api) {
|
||||
// we'd probably want to abstract this away
|
||||
api.use('mongo-livedata', ['client', 'server']);
|
||||
|
||||
// If handlebars happens to be loaded, we'll define some helpers like
|
||||
// If the 'ui' package is loaded, we'll define some helpers like
|
||||
// {{currentUser}}. If not, no biggie.
|
||||
api.use('handlebars', 'client', {weak: true});
|
||||
api.use('ui', 'client', {weak: true});
|
||||
|
||||
// Allow us to detect 'autopublish', and publish some Meteor.users fields if
|
||||
// it's loaded.
|
||||
api.use('autopublish', 'server', {weak: true});
|
||||
|
||||
api.use('oauth-encryption', 'server', {weak: true});
|
||||
|
||||
api.export('Accounts');
|
||||
|
||||
api.add_files('accounts_common.js', ['client', 'server']);
|
||||
@@ -53,5 +55,6 @@ Package.on_test(function (api) {
|
||||
api.use('tinytest');
|
||||
api.use('random');
|
||||
api.use('test-helpers');
|
||||
api.use('oauth-encryption');
|
||||
api.add_files('accounts_tests.js', 'server');
|
||||
});
|
||||
|
||||
@@ -6,7 +6,9 @@ Accounts.registerLoginHandler(function (options) {
|
||||
|
||||
check(options.oauth, {credentialToken: String});
|
||||
|
||||
if (!Oauth.hasCredential(options.oauth.credentialToken)) {
|
||||
var result = OAuth.retrieveCredential(options.oauth.credentialToken);
|
||||
|
||||
if (!result) {
|
||||
// OAuth credentialToken is not recognized, which could be either
|
||||
// because the popup was closed by the user before completion, or
|
||||
// some sort of error where the oauth provider didn't talk to our
|
||||
@@ -26,7 +28,7 @@ Accounts.registerLoginHandler(function (options) {
|
||||
Accounts.LoginCancelledError.numericError,
|
||||
"No matching login attempt found") };
|
||||
}
|
||||
var result = Oauth.retrieveCredential(options.oauth.credentialToken);
|
||||
|
||||
if (result instanceof Error)
|
||||
// We tried to login, but there was a fatal error. Report it back
|
||||
// to the user.
|
||||
|
||||
@@ -73,12 +73,13 @@ Meteor.methods({beginPasswordExchange: function (request) {
|
||||
// the second step method ('login') is called. If a user calls
|
||||
// 'beginPasswordExchange' but then never calls the second step
|
||||
// 'login' method, no login hook will fire.
|
||||
Accounts._reportLoginFailure(self, 'beginPasswordExchange', arguments, {
|
||||
// The validate login hooks can mutate the exception to be thrown.
|
||||
var attempt = Accounts._reportLoginFailure(self, 'beginPasswordExchange', arguments, {
|
||||
type: 'password',
|
||||
error: err,
|
||||
userId: user && user._id
|
||||
});
|
||||
throw err;
|
||||
throw attempt.error;
|
||||
}
|
||||
|
||||
// Save results so we can verify them later.
|
||||
@@ -263,7 +264,7 @@ Accounts.sendResetPasswordEmail = function (userId, email) {
|
||||
if (!email || !_.contains(_.pluck(user.emails || [], 'address'), email))
|
||||
throw new Error("No such email for user.");
|
||||
|
||||
var token = Random.id();
|
||||
var token = Random.secret();
|
||||
var when = new Date();
|
||||
Meteor.users.update(userId, {$set: {
|
||||
"services.password.reset": {
|
||||
@@ -312,7 +313,7 @@ Accounts.sendEnrollmentEmail = function (userId, email) {
|
||||
throw new Error("No such email for user.");
|
||||
|
||||
|
||||
var token = Random.id();
|
||||
var token = Random.secret();
|
||||
var when = new Date();
|
||||
Meteor.users.update(userId, {$set: {
|
||||
"services.password.reset": {
|
||||
@@ -435,7 +436,7 @@ Accounts.sendVerificationEmail = function (userId, address) {
|
||||
|
||||
|
||||
var tokenRecord = {
|
||||
token: Random.id(),
|
||||
token: Random.secret(),
|
||||
address: address,
|
||||
when: new Date()};
|
||||
Meteor.users.update(
|
||||
|
||||
@@ -33,7 +33,12 @@ if (Meteor.isClient) (function () {
|
||||
}, 10 * 1000, 100);
|
||||
};
|
||||
var invalidateLoginsStep = function (test, expect) {
|
||||
Meteor.call("testInvalidateLogins", true, expect(function (error) {
|
||||
Meteor.call("testInvalidateLogins", 'fail', expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
};
|
||||
var hideActualLoginErrorStep = function (test, expect) {
|
||||
Meteor.call("testInvalidateLogins", 'hide', expect(function (error) {
|
||||
test.isFalse(error);
|
||||
}));
|
||||
};
|
||||
@@ -468,13 +473,59 @@ if (Meteor.isClient) (function () {
|
||||
test.equal(Meteor.userId(), null);
|
||||
}));
|
||||
},
|
||||
logoutStep,
|
||||
function (test, expect) {
|
||||
var self = this;
|
||||
// Test that Meteor.logoutOtherClients logs out a second
|
||||
// authentcated connection while leaving Accounts.connection
|
||||
// logged in.
|
||||
var secondConn = DDP.connect(Meteor.absoluteUrl());
|
||||
var token;
|
||||
|
||||
var expectSecondConnLoggedOut = expect(function (err, result) {
|
||||
test.isTrue(err);
|
||||
});
|
||||
|
||||
var expectAccountsConnLoggedIn = expect(function (err, result) {
|
||||
test.isFalse(err);
|
||||
});
|
||||
|
||||
var expectSecondConnLoggedIn = expect(function (err, result) {
|
||||
test.equal(result.token, token);
|
||||
test.isFalse(err);
|
||||
Meteor.logoutOtherClients(function (err) {
|
||||
test.isFalse(err);
|
||||
secondConn.call('login', { resume: token },
|
||||
expectSecondConnLoggedOut);
|
||||
Accounts.connection.call('login', {
|
||||
resume: Accounts._storedLoginToken()
|
||||
}, expectAccountsConnLoggedIn);
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.loginWithPassword(
|
||||
self.username,
|
||||
self.password,
|
||||
expect(function (err) {
|
||||
test.isFalse(err);
|
||||
token = Accounts._storedLoginToken();
|
||||
test.isTrue(token);
|
||||
secondConn.call('login', { resume: token },
|
||||
expectSecondConnLoggedIn);
|
||||
})
|
||||
);
|
||||
},
|
||||
logoutStep,
|
||||
|
||||
// The tests below this point are for the deprecated
|
||||
// `logoutOtherClients` method.
|
||||
|
||||
function (test, expect) {
|
||||
var self = this;
|
||||
|
||||
// Test that Meteor.logoutOtherClients logs out a second authenticated
|
||||
// connection while leaving Accounts.connection logged in.
|
||||
var token;
|
||||
var userId;
|
||||
self.secondConn = DDP.connect(Meteor.absoluteUrl());
|
||||
|
||||
var expectLoginError = expect(function (err) {
|
||||
@@ -502,7 +553,6 @@ if (Meteor.isClient) (function () {
|
||||
token = Accounts._storedLoginToken();
|
||||
self.beforeLogoutOthersToken = token;
|
||||
test.isTrue(token);
|
||||
userId = Meteor.userId();
|
||||
self.secondConn.call("login", { resume: token },
|
||||
expectSecondConnLoggedIn);
|
||||
}));
|
||||
@@ -536,41 +586,9 @@ if (Meteor.isClient) (function () {
|
||||
);
|
||||
},
|
||||
logoutStep,
|
||||
function (test, expect) {
|
||||
var self = this;
|
||||
// Test that, when we call logoutOtherClients, if the server disconnects
|
||||
// us before the logoutOtherClients callback runs, then we still end up
|
||||
// logged in.
|
||||
var expectServerLoggedIn = expect(function (err, result) {
|
||||
test.isFalse(err);
|
||||
test.isTrue(Meteor.userId());
|
||||
test.equal(result, Meteor.userId());
|
||||
});
|
||||
|
||||
Meteor.loginWithPassword(
|
||||
self.username,
|
||||
self.password,
|
||||
expect(function (err) {
|
||||
test.isFalse(err);
|
||||
test.isTrue(Meteor.userId());
|
||||
|
||||
// The test is only useful if things interleave in the following order:
|
||||
// - logoutOtherClients runs on the server
|
||||
// - onReconnect fires and sends a login method with the old token,
|
||||
// which results in an error
|
||||
// - logoutOtherClients callback runs and stores the new token and
|
||||
// logs in with it
|
||||
// In practice they seem to interleave this way, but I'm not sure how
|
||||
// to make sure that they do.
|
||||
|
||||
Meteor.logoutOtherClients(function (err) {
|
||||
test.isFalse(err);
|
||||
Meteor.call("getUserId", expectServerLoggedIn);
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
logoutStep,
|
||||
function (test, expect) {
|
||||
var self = this;
|
||||
// Test that deleting a user logs out that user's connections.
|
||||
@@ -603,6 +621,28 @@ if (Meteor.isClient) (function () {
|
||||
})
|
||||
);
|
||||
},
|
||||
validateLoginsStep,
|
||||
function (test, expect) {
|
||||
Meteor.loginWithPassword(
|
||||
"no such user",
|
||||
"some password",
|
||||
expect(function (error) {
|
||||
test.isTrue(error);
|
||||
test.equal(error.reason, 'User not found');
|
||||
})
|
||||
);
|
||||
},
|
||||
hideActualLoginErrorStep,
|
||||
function (test, expect) {
|
||||
Meteor.loginWithPassword(
|
||||
"no such user",
|
||||
"some password",
|
||||
expect(function (error) {
|
||||
test.isTrue(error);
|
||||
test.equal(error.reason, 'hide actual error');
|
||||
})
|
||||
);
|
||||
},
|
||||
validateLoginsStep
|
||||
]);
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ Accounts.onCreateUser(function (options, user) {
|
||||
});
|
||||
|
||||
|
||||
// connection id -> true
|
||||
// connection id -> action
|
||||
var invalidateLogins = {};
|
||||
|
||||
|
||||
Meteor.methods({
|
||||
testInvalidateLogins: function (flag) {
|
||||
if (flag)
|
||||
invalidateLogins[this.connection.id] = true;
|
||||
testInvalidateLogins: function (action) {
|
||||
if (action)
|
||||
invalidateLogins[this.connection.id] = action;
|
||||
else
|
||||
delete invalidateLogins[this.connection.id];
|
||||
}
|
||||
@@ -30,9 +30,19 @@ Meteor.methods({
|
||||
|
||||
|
||||
Accounts.validateLoginAttempt(function (attempt) {
|
||||
return ! (attempt &&
|
||||
attempt.connection &&
|
||||
invalidateLogins[attempt.connection.id]);
|
||||
var action =
|
||||
attempt &&
|
||||
attempt.connection &&
|
||||
invalidateLogins[attempt.connection.id];
|
||||
|
||||
if (! action)
|
||||
return true;
|
||||
else if (action === 'fail')
|
||||
return false;
|
||||
else if (action === 'hide')
|
||||
throw new Meteor.Error(403, 'hide actual error');
|
||||
else
|
||||
throw new Error('unknown action: ' + action);
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
<!--
|
||||
NOTE: You shouldn't use these templates directly. Instead, use the global
|
||||
{{loginButtons}} template. For positioning on the right side of your app,
|
||||
try {{loginButtons align="right"}}
|
||||
-->
|
||||
|
||||
<template name="_loginButtons">
|
||||
<template name="loginButtons">
|
||||
<div id="login-buttons" class="login-buttons-dropdown-align-{{align}}">
|
||||
{{#if currentUser}}
|
||||
{{#if loggingIn}}
|
||||
@@ -86,6 +80,6 @@
|
||||
</div>
|
||||
{{else}}
|
||||
{{! just add some padding }}
|
||||
<div class="login-buttons-padding" />
|
||||
<div class="login-buttons-padding"></div>
|
||||
{{/unless}}
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
// for convenience
|
||||
var loginButtonsSession = Accounts._loginButtonsSession;
|
||||
|
||||
Handlebars.registerHelper(
|
||||
"loginButtons",
|
||||
function (options) {
|
||||
if (options.hash.align === "right")
|
||||
return new Handlebars.SafeString(Template._loginButtons({align: "right"}));
|
||||
else
|
||||
return new Handlebars.SafeString(Template._loginButtons({align: "left"}));
|
||||
});
|
||||
|
||||
// shared between dropdown and single mode
|
||||
Template._loginButtons.events({
|
||||
Template.loginButtons.events({
|
||||
'click #login-buttons-logout': function() {
|
||||
Meteor.logout(function () {
|
||||
loginButtonsSession.closeDropdown();
|
||||
@@ -19,8 +10,8 @@ Template._loginButtons.events({
|
||||
}
|
||||
});
|
||||
|
||||
Template._loginButtons.preserve({
|
||||
'input[id]': Spark._labelFromIdOrName
|
||||
UI.registerHelper('loginButtons', function () {
|
||||
throw new Error("Use {{> loginButtons}} instead of {{loginButtons}}");
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
<template name="_configureLoginServiceDialog">
|
||||
{{#if visible}}
|
||||
<div id="configure-login-service-dialog" class="accounts-dialog accounts-centered-dialog">
|
||||
{{{configurationSteps}}}
|
||||
{{> configurationSteps}}
|
||||
|
||||
<p>
|
||||
Now, copy over some details.
|
||||
@@ -97,12 +97,10 @@
|
||||
</div>
|
||||
<a class="accounts-close configure-login-service-dismiss-button">×</a>
|
||||
|
||||
{{#isolate}}
|
||||
<div class="login-button login-button-configure {{#if saveDisabled}}login-button-disabled{{/if}}"
|
||||
<div class="login-button login-button-configure {{#if saveDisabled}}login-button-disabled{{/if}}"
|
||||
id="configure-login-service-dialog-save-configuration">
|
||||
Save Configuration
|
||||
</div>
|
||||
{{/isolate}}
|
||||
Save Configuration
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -231,7 +231,7 @@ Template._configureLoginServiceDialog.visible = function () {
|
||||
|
||||
Template._configureLoginServiceDialog.configurationSteps = function () {
|
||||
// renders the appropriate template
|
||||
return configureLoginServiceDialogTemplateForService()();
|
||||
return configureLoginServiceDialogTemplateForService();
|
||||
};
|
||||
|
||||
Template._configureLoginServiceDialog.saveDisabled = function () {
|
||||
|
||||
@@ -3,7 +3,7 @@ var loginButtonsSession = Accounts._loginButtonsSession;
|
||||
|
||||
// events shared between loginButtonsLoggedOutDropdown and
|
||||
// loginButtonsLoggedInDropdown
|
||||
Template._loginButtons.events({
|
||||
Template.loginButtons.events({
|
||||
'click #login-name-link, click #login-sign-in-link': function () {
|
||||
loginButtonsSession.set('dropdownVisible', true);
|
||||
Deps.flush();
|
||||
@@ -94,7 +94,9 @@ Template._loginButtonsLoggedOutDropdown.events({
|
||||
document.getElementById('login-username').value = usernameOrEmail;
|
||||
else
|
||||
document.getElementById('login-email').value = usernameOrEmail;
|
||||
// "login-password" is preserved, since password fields aren't updated by Spark.
|
||||
|
||||
if (password !== null)
|
||||
document.getElementById('login-password').value = password;
|
||||
|
||||
// Force redrawing the `login-dropdown-list` element because of
|
||||
// a bizarre Chrome bug in which part of the DIV is not redrawn
|
||||
@@ -134,6 +136,8 @@ Template._loginButtonsLoggedOutDropdown.events({
|
||||
var username = trimmedElementValueById('login-username');
|
||||
var email = trimmedElementValueById('login-email')
|
||||
|| trimmedElementValueById('forgot-password-email'); // Ughh. Standardize on names?
|
||||
// notably not trimmed. a password could (?) start or end with a space
|
||||
var password = elementValueById('login-password');
|
||||
|
||||
loginButtonsSession.set('inSignupFlow', false);
|
||||
loginButtonsSession.set('inForgotPasswordFlow', false);
|
||||
@@ -144,9 +148,12 @@ Template._loginButtonsLoggedOutDropdown.events({
|
||||
document.getElementById('login-username').value = username;
|
||||
if (document.getElementById('login-email'))
|
||||
document.getElementById('login-email').value = email;
|
||||
// "login-password" is preserved, since password fields aren't updated by Spark.
|
||||
|
||||
if (document.getElementById('login-username-or-email'))
|
||||
document.getElementById('login-username-or-email').value = email || username;
|
||||
|
||||
if (password !== null)
|
||||
document.getElementById('login-password').value = password;
|
||||
},
|
||||
'keypress #login-username, keypress #login-email, keypress #login-username-or-email, keypress #login-password, keypress #login-password-again': function (event) {
|
||||
if (event.keyCode === 13)
|
||||
|
||||
@@ -7,8 +7,7 @@ Package.describe({
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use(['deps', 'service-configuration', 'accounts-base',
|
||||
'underscore', 'templating',
|
||||
'handlebars', 'spark', 'session'], 'client');
|
||||
'underscore', 'templating', 'session'], 'client');
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply('accounts-base', ['client', 'server']);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ var browserEnabled = function(request) {
|
||||
|
||||
WebApp.addHtmlAttributeHook(function (request) {
|
||||
if (browserEnabled(request))
|
||||
return 'manifest="/app.manifest"';
|
||||
return { manifest: "/app.manifest" };
|
||||
else
|
||||
return null;
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user