diff --git a/History.md b/History.md index 92e2b93f06c..0378f1b54dd 100644 --- a/History.md +++ b/History.md @@ -40,6 +40,9 @@ * Meteor now provides a compatible replacement for the DOM `localStorage` facility that works in IE7, in the `localstorage-polyfill` smart package. +* Meteor now packages the D3 library for manipulating documents based on data in + a smart package called `d3`. + * `Meteor.Collection` now takes its optional `manager` argument (used to associate a collection with a server you've connected to with `Meteor.connect`) as a named option. (The old call syntax continues to work diff --git a/LICENSE.txt b/LICENSE.txt index 5f5319ca246..201b3c924cf 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -356,6 +356,11 @@ Copyright (c) 2011: Tim Koschützki (tim@debuggable.com) Felix Geisendörfer (felix@debuggable.com) +---------- +node-form-data: https://github.com/felixge/node-form-data +---------- + +Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors ============== @@ -622,6 +627,11 @@ npmlog: https://github.com/isaacs/npmlog once: https://github.com/isaacs/once osenv: https://github.com/isaacs/osenv mute-stream: https://github.com/isaacs/mute-stream +couch-login: https://github.com/isaacs/couch-login +npmconf: https://github.com/isaacs/npmconf +read-installed: https://github.com/isaacs/read-installed +read-package-json: https://github.com/isaacs/read-package-json +promzard: https://github.com/isaacs/promzard ---------- Copyright (c) Isaac Z. Schlueter ("Author") @@ -752,6 +762,38 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISE OF THE POSSIBILITY OF SUCH DAMAGE. +---------- +D3: http://d3js.org/ +---------- + +Copyright (c) 2012, Michael Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ============= Public Domain @@ -841,6 +883,36 @@ By Isaac Z. Schlueter (http://blog.izs.me/) 0. You just DO WHAT THE FUCK YOU WANT TO. +---------- +node-stream-buffer: https://github.com/samcday/node-stream-buffer +---------- + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + + ---------- mongodb: http://www.mongodb.org/ ---------- diff --git a/docs/client/api.html b/docs/client/api.html index faf6db08707..712767f7c76 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -3,10 +3,10 @@

The Meteor API

-Your Javascript code can run in two environments: the *client* -(browser), and the *server* (a Node.js container on a server). For -each function in this API reference, we'll indicate if the function is -available just on the client, just on the server, or *Anywhere*. +Your Javascript code can run in two environments: the *client* (browser), and +the *server* (a [Node.js](http://nodejs.org/) container on a server). For each +function in this API reference, we'll indicate if the function is available just +on the client, just on the server, or *Anywhere*.

Meteor Core

@@ -47,7 +47,14 @@

Publish and subscribe

// server: publish the rooms collection, minus secret info. Meteor.publish("rooms", function () { - return Rooms.find({}, {fields: {secretInfo: false}}); + return Rooms.find({}, {fields: {secretInfo: 0}}); + }); + + // ... and publish secret info for rooms where the logged-in user + // is an admin. If the client subscribes to both streams, the records + // are merged together into the same documents in the Rooms collection. + Meteor.publish("adminSecretInfo", function () { + return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}}); }); Otherwise, the publish function can [`set`](#publish_set) and @@ -108,6 +115,11 @@

Publish and subscribe

will still work. {{/warning}} +{{> api_box subscription_userId}} + +This is constant. However, if the logged-in user changes, the publish +function is rerun with the new value. + {{> api_box subscription_set}} {{> api_box subscription_unset}} {{> api_box subscription_complete}} @@ -120,11 +132,6 @@

Publish and subscribe

{{> api_box subscription_stop}} -{{> api_box subscription_userId}} - -This is constant. However, if the logged-in user changes, the publish -function is rerun with the new value. - {{> api_box subscribe}} When you subscribe to a record set, it tells the server to send records @@ -150,9 +157,9 @@

Publish and subscribe

If all of the attributes in a document are removed, Meteor will remove the (now empty) document. If you want to publish empty -documents, just use a placeholder attribute. +documents, just use a placeholder attribute: - // Clicks.insert({exists: true}); + Clicks.insert({exists: true}); {{> api_box autosubscribe}} @@ -206,7 +213,7 @@

Methods

* `isSimulation`: a boolean value, true if this invocation is a stub. * `unblock`: when called, allows the next method from this client to begin running. -* `userId`: a function that returns the id of the current user. +* `userId`: the id of the current user. * `setUserId`: a function that associates the current client with a user. Calling `methods` on the client defines *stub* functions associated with @@ -225,9 +232,9 @@

Methods

{{> api_box method_invocation_userId}} -The user id is an arbitrary string — typically the id of the user -record in the database. You can set it with the `setUserId` function. If -you're using the Meteor accounts system then this is handled for you. +The user id is an arbitrary string — typically the id of the user record +in the database. You can set it with the `setUserId` function. If you're using +the [Meteor accounts system](#accounts_api) then this is handled for you. {{> api_box method_invocation_setUserId}} @@ -236,9 +243,9 @@

Methods

`userId` for future method calls received on this connection. Pass `null` to log out the connection. -If you are using the built-in Meteor accounts system then this should correspond -to the `_id` field of a document in the [`Meteor.users`](#meteor_users) -collection. +If you are using the [built-in Meteor accounts system](#accounts_api) then this +should correspond to the `_id` field of a document in the +[`Meteor.users`](#meteor_users) collection. `setUserId` is not retroactive. It affects the current method call and any future method calls on the connection. Any previous method calls on @@ -268,7 +275,7 @@

Methods

If a stub is available, it will also run the stub on the client. If you include a callback function as the last argument (which can't be -an argument to the method, since functions aren't serializeable), the +an argument to the method, since functions aren't serializable), the method will run asynchronously: it will return nothing in particular and will not throw an exception. When the method is complete (which may or may not happen before `Meteor.call` returns), the callback will be @@ -312,8 +319,9 @@

Methods

{{> api_box meteor_apply}} -`Meteor.apply` is just like `Meteor.call`, but it allows the -arguments to be passed as an array. +`Meteor.apply` is just like `Meteor.call`, except that the method arguments are +passed as an array rather than directly as arguments, and you can specify +options about how the client executes the method.

Server connections

@@ -382,7 +390,7 @@

Server connections

Get the current connection status. See [Meteor.status](#meteor_status). * `reconnect` - - See Meteor.reconnect. + See [Meteor.reconnect](#meteor_reconnect). * `onReconnect` - Set this to a function to be called as the first step of reconnecting. This function can call methods which will be executed before any other outstanding methods. For example, this can be used to re-establish @@ -444,7 +452,8 @@

Collections

* On the server, a collection with that name is created on a backend Mongo server. When you call methods on that collection on the server, -they translate directly into normal Mongo operations. +they translate directly into normal Mongo operations (after checking that +they match your [access control rules](#allow)). * On the client, a Minimongo instance is created. Minimongo is essentially an in-memory, non-persistent @@ -471,13 +480,8 @@

Collections

$ meteor remove autopublish -{{#warning}} -Currently the client is given full write access to the collection. They -can execute arbitrary Mongo update commands. Once we build -authentication, you will be able to limit the client's direct access to -insert, update, and remove. We are also considering validators and -other ORM-like functionality. -{{/warning}} +and instead call [`Meteor.publish`](#meteor_publish) to specify which parts of +your collection should be published to which users. // Create a collection called Posts and put a document in it. The // document will be immediately visible in the local copy of the @@ -546,8 +550,8 @@

Collections

Cursors are a reactive data source. The first time you retrieve a cursor's documents with `fetch`, `map`, or `forEach` inside a -reactive context (eg, [`Meteor.render`](#meteor_render), -[`Meteor.autosubscribe`](#meteor_autosubscribe), Meteor will register a +reactive context (eg, [`Meteor.render`](#meteor_render) or +[`Meteor.autosubscribe`](#meteor_autosubscribe)), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation. To disable this behavior, pass `{reactive: false}` as an option to @@ -589,10 +593,9 @@

Collections

{{> api_box update}} -Modify documents that match `selector` as -given by `modifier` (see modifier -documentation). By default, modify only one matching document. -If `multi` is true, modify all matching documents. +Modify documents that match `selector` as given by `modifier` (see [modifier +documentation](#modifiers)). By default, modify only one matching document. If +`multi` is true, modify all matching documents. Instead of a selector, you can pass a string, which will be interpreted as an `_id`. @@ -656,7 +659,7 @@

Collections

{{> api_box allow}} When a client calls `insert`, `update`, or `remove` on a collection, the -collection's `allow` and `deny` callbacks are called +collection's `allow` and [`deny`](#deny) callbacks are called on the server to determine if the write should be allowed. If at least one `allow` callback allows the write, and no `deny` callbacks deny the write, then the write is allowed to proceed. @@ -718,7 +721,7 @@

Collections

option. Set `fetch` to an array of the field names that should be retrieved. -Example: XXX test me! +Example: // Create a collection where users can only modify documents that // they own. Ownership is tracked by an 'owner' field on each @@ -772,12 +775,12 @@

Collections

collection. Meteor also has a special "insecure mode" for quickly prototyping new -applications. In insecure mode, if you haven't set up any `allow` or -`deny` rules on a collection, then all users have full write access to -the collection. This is the only effect of insecure mode. If you call -`allow` or `deny` at all, even `allow({})`, then access is checked just -like normal. __New Meteor projects start in insecure mode by default.__ To -turn it off just type `meteor remove insecure`. +applications. In insecure mode, if you haven't set up any `allow` or `deny` +rules on a collection, then all users have full write access to the +collection. This is the only effect of insecure mode. If you call `allow` or +`deny` at all on a collection, even `Posts.allow({})`, then access is checked +just like normal on that collection. __New Meteor projects start in insecure +mode by default.__ To turn it off just type `meteor remove insecure`. {{#note}} For `update` and `remove`, documents will be affected only if they match @@ -790,7 +793,7 @@

Collections

{{> api_box deny}} -This works just like `allow`, except it lets you +This works just like [`allow`](#allow), except it lets you make sure that certain writes are definitely denied, even if there is an `allow` rule that says that they should be permitted. @@ -943,7 +946,8 @@

Cursors

// Matches documents where fruit is one of three possibilities {fruit: {$in: ["peach", "plum", "pear"]}} -See the complete documentation. +See the [complete +documentation](http://www.mongodb.org/display/DOCS/Advanced+Queries). {{/api_box_inline}} @@ -959,14 +963,17 @@

Cursors

// 'supporters' array {$inc: {votes: 2}, $push: {supporters: "Traz"}} -But if a modifier doesn't contain any $-operators, then it is -instead interpreted as a literal document, and completely replaces -whatever was previously in the database. +But if a modifier doesn't contain any $-operators, then it is instead +interpreted as a literal document, and completely replaces whatever was +previously in the database. (Literal document modifiers are not currently +supported by [validated updates](#allow).) // Find the document with id "123", and completely replace it. Users.update({_id: "123"}, {name: "Alice", friends: ["Bob"]}); -See the full list of modifiers. +See the [full list of +modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations) +full list of modifiers. {{/api_box_inline}} @@ -1101,842 +1108,895 @@

Session

you need to use the `underscore` package and write `_.isEqual(Session.get(key), value)`. -

Templates

-A template that you declare as `<{{! }}template name="foo"> ... ` 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 -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 -you, including determining when a template instance has been removed -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. +

Accounts

-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`. +XXX intro text -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 user}} -{{> api_box template_call}} +Retreives the user record for the current user from +the [`Meteor.users`](#meteor_users) collection. -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. +On the client this will be a subset of the fields in the document, only +those that are published from the server are available on the client. By +default the server publishes `username`, `emails`, and +`profile`. See [`Meteor.users`](#meteor_users) for more on +the fields used in user documents. +If the user is logged in but the user's database record is not fully +loaded yet, this returns an object with only the `_id` field set. During +this period [`userLoaded`](#meteor_userloaded) will return +`false`. -{{> api_box template_rendered}} +{{> api_box userId}} -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. +{{> api_box users}} -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. +This collection contains one document per registered user. Here's an example +user document: -{{> api_box template_created}} + { + _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f", // Meteor.userId() + username: "cool_kid_13", // unique name + emails: [ + // each email address can only belong to one user. + { address: "cool@example.com", verified: true }, + { address: "another@different.com", verified: false } + ], + profile: { + // The profile is writable by the user by default. + name: "Joe Schmoe" + }, + services: { + facebook: { + id: "709050", // facebook id + accessToken: "AAACCgdX7G2...AbV9AZDZD" + }, + resume: { + loginTokens: [ + { token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd", + when: 1349761684048 } + ] + } + } + } -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. +A user document can contain any data you want to store about a user. Meteor +treats the following fields specially: + +- `username`: a unique String identifying the user. +- `emails`: an Array of Objects with keys `address` and `verified`; + an email address may belong to at most one user. `verified` is + a Boolean which is true if the user has [verified the + address](#accounts_verifyemail) with a token sent over email. +- `profile`: an Object which (by default) the user can create + and update with any data. +- `services`: an Object containing data used by particular + login services. For example, its `reset` field contains + tokens used by [forgot password](#accounts_forgotpassword) links, + and its `resume` field contains tokens used to keep you + logged in between sessions. + +Like all [Meteor.Collection](#collections)s, you can access all +documents on the server, but only those specifically published by the server are +available on the client. -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. +By default, the current user's `username`, `emails` and `profile` are +published to the client. You can publish additional fields for the +current user with: -{{> api_box template_destroyed}} + Meteor.publish("userData", function () { + return Meteor.users.find({_id: this.userId}, + {fields: {'other': 1, 'things': 1}}); + }); -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 the `autopublish` package is installed, the `username` and `profile` fields +for all users are published to all clients. To publish specific fields from all +users: -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. + Meteor.publish("allUserData", function () { + return Meteor.users.find({}, {fields: {'nested.things': 1}}); + }); + +Users are by default allowed to specify their own `profile` field with +[`Accounts.createUser`](#accounts_createuser) and modify it with +`Meteor.users.update`. To allow users to edit additional fields, use +[`Meteor.users.allow`](#allow). To forbid users from making any modifications to +their user document: + Meteor.users.deny({update: function () { return true; }}); -{{> api_box template_events}} -Declare event handers for instances of this template. Multiple calls add -new event handlers in addition to the existing ones. +{{> api_box userLoaded}} -See [Event Maps](#eventmaps) for a detailed description of the event -map format and how event handling works in Meteor. +There are some cases when the client knows the id of the logged in user +but has not yet received the user data from the server. For example, if +the user is logged in and reloads the page the user data will be +unavailable during initial page load. + +During these periods, `userLoaded` will return false +and [`user`](#meteor_user) will return an object with only +the `_id` key. {{#note}} -This syntax replaces the previous syntax: `Template.myTemplate.events = {...}`, -but for now, the old syntax still works. +We realize this is inconvenient. It is a temporary solution. In the +future we will either make it unnecessary or fold it into a more +general mechanism. {{/note}} +{{> api_box logout}} -{{> api_box template_helpers}} +{{> api_box loginWithPassword}} -Each template has a local dictionary of helpers that are made available to it, -and this call specifies helpers to add to the template's dictionary. +XXX needs link to passwords section and mention the package + +{{> api_box loginWithExternalService}} + +These functions initiate the login process with an external +service (eg: Facebook, Google, etc), using OAuth. When called they open a new pop-up +window that loads the provider's login page. Once the user has logged in +with the provider, the pop-up window is closed and the Meteor client +logs in to the Meteor server with the information provided by the external +service. + + +If the user has not already granted all the permissions requested they will be +prompted to grant access to their account in the pop-up dialog. Values for the +`requestPermissions` parameter differ for each login service: + +- Facebook: +- GitHub: +- Google: +- Twitter, Weibo: `requestPermissions` currently not supported + +XXX mention provider packages + +{{> api_box accounts_config}} +{{> api_box accounts_ui_config}} Example: - Template.myTemplate.helpers({ - foo: function () { - return Session.get("foo"); - } + Accounts.ui.config({ + requestPermissions: { + facebook: ['user_likes'], + github: ['user', 'repo'] + }, + passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL' }); -In Handlebars, this helper would then be invoked as `{{dstache}}foo}}`. - -The following syntax is equivalent, but won't work for reserved property -names: +{{> api_box accounts_validateNewUser}} - Template.myTemplate.foo = function () { - return Session.get("foo"); - }; +This can be called multiple times. If any of the functions return `false` or +throw an error, the new user creation is aborted. To set a specific error +message (which will be displayed by [`accounts-ui`](#accountsui)), throw a new +[`Meteor.Error`](#meteor_error). -{{> api_box template_preserve}} +Example: -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. + // Validate username, sending a specific error message on failure. + Accounts.validateNewUser(function (user) { + if (user.username && user.username.length >= 3) + return true; + throw new Meteor.Error(403, "Username must have at least 3 characters"); + }); + // Validate username, without a specific error message. + Accounts.validateNewUser(function (user) { + return user.username !== "root"; + }); -{{#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. -{{/note}} +{{> api_box accounts_onCreateUser}} -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: +Use this when you need to do more than simply accept or reject new user +creation. With this function you can programatically control the +contents of new user documents. -* Input text fields and other form controls -* Elements with CSS animations -* Iframes -* Nodes with references kept in JavaScript code +The function you pass will be called with two arguments: `options` and +`user`. The `options` argument comes +from [`Accounts.createUser`](#accounts_createuser) for +password-based users or from an external service login flow. `options` may come +from an untrusted client so make sure to validate any values you read from +it. The `user` argument is created on the server and contains a +proposed user object with all the automatically generated fields +required for the user to log in. -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. +The function should return the user document (either the one passed in or a +newly-created object) with whatever modifications are desired. The returned +document is inserted directly into the [`Meteor.users`](#meteor_users) collection. -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. +The default create user function simply copies `options.profile` into +the new user document. Calling `onCreateUser` overrides the default +hook. This can only be called once. -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. +Example: -For example, to preserve all `` elements with ids in template 'foo', use: + - Template.foo.preserve({ - 'input[id]': function (node) { return node.id; } + // Support for playing D&D: Roll 3d6 for dexterity + Accounts.onCreateUser(function(options, user) { + var d6 = function () { return Math.floor(Math.random() * 6) + 1; }; + user.dexterity = d6() + d6() + d6(); + // We still want the default hook's 'profile' behavior. + if (options.profile) + user.profile = options.profile; + return user; }); -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. +

Passwords

-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. +The `accounts-password` package implements a complete system for +password based authentication. In addition to the basic username and +password based sign-in process it also supports email based sign-in +including address verification and password recovery emails. -{{#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}} +Unlike most web applications, the Meteor client does not send the user's +password directly to the server. It uses the [Secure Remote Password +protocol](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) +to ensure the server never sees the user's plain-text password. This +helps protect against embarrassing password leaks if the server's +database is compromised. +To add password support to your application, run `$ meteor add +accounts-password`. You can construct your own user interface using the +functions below, or use the [`accounts-ui` package](#accountsui) to +include a turn-key user interface for password-based sign-in. -

Template instances

-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. +{{> api_box accounts_createUser}} -Template instance objects are found as the value of `this` in the -`created`, `rendered`, and `destroyed` template callbacks and as an -argument to event handlers. +On the client this function logs in as the newly created user on +successful completion. On the server, it returns the newly created user +id. -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. +On the client, you must pass `password` and one of `username` or `email` +— enough information for the user to be able to log in again +later. On the server, you can pass any subset of these options, but the +user will not be able to log in until it has an identifier and a +password. -You can only access `findAll`, `find`, `firstNode`, and `lastNode` -from the `rendered` callback and event handlers, not from `created` -and `destroyed`, because they require the template instance to be -in the DOM. +To create an account without a password on the server and still let the +user pick their own password, call `createUser` with the `email` option +and then +call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This +will send the user an email with a link to set their initial password. -{{> api_box template_findAll}} +By default the `profile` option is added directly to the new user document. To +override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser). -Returns an array of DOM elements matching `selector`. +This function is only used for creating users with passwords. The external +service login flows do not use this function. -The template instance serves as the document root for the selector. Only -elements inside the template and its sub-templates can match parts of -the selector. -{{> api_box template_find}} +{{> api_box accounts_changePassword}} -Returns one DOM element matching `selector`, or `null` if there are no -such elements. +{{> api_box accounts_forgotPassword}} -The template instance serves as the document root for the selector. Only -elements inside the template and its sub-templates can match parts of -the selector. +This triggers a call +to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) +on the server. Pass the token the user receives in this email +to [`Accounts.resetPassword`](#accounts_resetpassword) to +complete the password reset process. -{{> api_box template_firstNode}} +If you are using the [`accounts-ui` package](#pkg_accounts_ui), this is handled +automatically. Otherwise, it is your responsiblity to prompt the user for the +new password and call `resetPassword`. -The two nodes `firstNode` and `lastNode` indicate the extent of the -rendered template in the DOM. The rendered template includes these -nodes, their intervening siblings, and their descendents. These two -nodes are siblings (they have the same parent), and `lastNode` comes -after `firstNode`, or else they are the same node. +{{> api_box accounts_resetPassword}} -{{> api_box template_lastNode}} +This function accepts tokens generated +by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) +and +[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). -{{> api_box template_data}} +{{> api_box accounts_setPassword}} -This property provides access to the data context at the top level of -the template. It is updated each time the template is re-rendered. -Access is read-only and non-reactive. +{{> api_box accounts_verifyEmail}} +This function accepts tokens generated +by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It +sets the `emails.verified` field in the user record. -{{> api_box render}} +{{> api_box accounts_sendResetPasswordEmail}} -`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 token in this email should be passed +to [`Accounts.resetPassword`](#accounts_resetpassword). -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. +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). -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 -[`Meteor.flush`](#meteor_flush). +{{> api_box accounts_sendEnrollmentEmail}} -`Meteor.render` tracks the data dependencies of `htmlFunc` by running -it in a reactive context, 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). +The token in this email should be passed +to [`Accounts.resetPassword`](#accounts_resetpassword). -Example: +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). - // Show the number of users online. - var frag = Meteor.render(function () { - return "

There are " + Users.find({online: true}).count() + - " users online.

"; - }); - document.body.appendChild(frag); +{{> api_box accounts_sendVerificationEmail}} - // Find all users that have been idle for a while, and mark them as - // offline. The count on the screen will automatically update. - Users.update({idleTime: {$gt: 30}}, {online: false}); +The token in this email should be passed +to [`Accounts.verifyEmail`](#accounts_verifyemail). -{{> api_box renderList}} +To customize the contents of the email, see +[`Accounts.emailTemplates`](#accounts_emailtemplates). -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 accounts_emailTemplates}} -`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. +This is an `Object` with several fields that are used to generate text +for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and +`sendVerificationEmail`. -`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." +Override fields of the object by assigning to them: + +- `from`: A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From + address. By default email is from `no-reply@meteor.com`. If you wish to + receive email from users asking for help with their account, be sure to set + this to an email address that you can receive email at. +- `siteName`: The public name of your application. Defaults to the DNS name of + the application (eg: `awesome.meteor.com`). +- `resetPassword`: An `Object` with two fields: + - `resetPassword.subject`: A `Function` that takes a user object and returns + a `String` for the subject line of a reset password email. + - `resetPassword.text`: A `Function` that takes a user object and a url, and + returns the body text for a reset password email. +- `enrollAccount`: Same as `resetPassword`, but for initial password setup for + new accounts. +- `verifyEmail`: Same as `resetPassword`, but for verifying the users email + address. -Each call to `docFunc` or `elseFunc` is run in its own reactive -context 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 '
' + post.name + '
'; - }); - document.body.appendChild(frag); + Accounts.emailTemplates.siteName = "AwesomeSite"; + Accounts.emailTemplates.from = "AwesomeSite Admin "; + Accounts.emailTemplates.enrollAccount.subject = function (user) { + return "Welcome to Awesome Town, " + user.profile.name; + }; + Accounts.emailTemplates.enrollAccount.text = function (user, url) { + return "You have been selected to participate in building a better future!" + + " To activate your account, simply click the link below:\n\n" + + url; + }; - // 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); +

Templates

-{{#api_box_inline eventmaps}} +A template that you declare as `<{{! }}template name="foo"> ... ` can be accessed as the function `Template.foo`, which +returns a string of HTML when called. -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 -the handlers for those events. The property can be in one of several -forms: +The same template may occur many times on the 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 +you, including determining when a template instance has been removed +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. -
-{{#dtdd "eventtype"}} -Matches a particular type of event, such as 'click'. -{{/dtdd}} +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`. -{{#dtdd "eventtype selector"}} -Matches a particular type of event, but only when it appears on -an element that matches a certain CSS selector. -{{/dtdd}} +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. -{{#dtdd "event1, event2"}} -To handle more than one type of event with the same function, use a -comma-separated list. -{{/dtdd}} -
+{{> api_box template_call}} -The handler function receives two arguments: `event`, an object with -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 -block helpers such as `#with` and `#each`. +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. -Example: - { - // Fires when any element is clicked - 'click': function (event) { ... }, +{{> api_box template_rendered}} - // Fires when any element with the 'accept' class is clicked - 'click .accept': function (event) { ... }, +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. - // Fires when 'accept' is clicked, or a key is pressed - 'keydown, click .accept': function (event) { ... } - } +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. -Most events bubble up the document tree from their originating -element. For example, `'click p'` catches a click anywhere in a -paragraph, even if the click originated on a link, span, or some other -element inside the paragraph. The originating element of the event -is available as the `target` property, while the element that matched -the selector and is currently handling it is called `currentTarget`. +{{> api_box template_created}} - { - 'click p': function (event) { - var paragraph = event.currentTarget; // always a P - var clickedElement = event.target; // could be the P or a child element - } - } - -If a selector matches multiple elements that an event bubbles to, it -will be called multiple times, for example in the case of `'click -div'` or `'click *'`. If no selector is given, the handler -will only be called once, on the original target element. - -The following properties and methods are available on the event object -passed to handlers: +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. -
-{{#dtdd name="type" type="String"}} -The event's type, such as "click", "blur" or "keypress". -{{/dtdd}} +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. -{{#dtdd name="target" type="DOM Element"}} -The element that originated the event. -{{/dtdd}} +{{> api_box template_destroyed}} -{{#dtdd name="currentTarget" type="DOM Element"}} -The element currently handling the event. This is the element that -matched the selector in the event map. For events that bubble, it may -be `target` or an ancestor of `target`, and its value changes as the -event bubbles. -{{/dtdd}} +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. -{{#dtdd name="which" type="Number"}} -For mouse events, the number of the mouse button (1=left, 2=middle, 3=right). -For key events, a character or key code. -{{/dtdd}} +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. -{{#dtdd "stopPropagation()"}} -Prevent the event from propagating (bubbling) up to other elements. -Other event handlers matching the same element are still fired, in -this and other event maps. -{{/dtdd}} -{{#dtdd "stopImmediatePropagation()"}} -Prevent all additional event handlers from being run on this event, -including other handlers in this event map, handlers reached by -bubbling, and handlers in other event maps. -{{/dtdd}} +{{> api_box template_events}} -{{#dtdd "preventDefault()"}} -Prevents the action the browser would normally take in response to this -event, such as following a link or submitting a form. Further handlers -are still called, but cannot reverse the effect. -{{/dtdd}} +Declare event handers for instances of this template. Multiple calls add +new event handlers in addition to the existing ones. -{{#dtdd "isPropagationStopped()"}} -Returns whether `stopPropagation()` has been called for this event. -{{/dtdd}} +See [Event Maps](#eventmaps) for a detailed description of the event +map format and how event handling works in Meteor. -{{#dtdd "isImmediatePropagationStopped()"}} -Returns whether `stopImmediatePropagation()` has been called for this event. -{{/dtdd}} +{{#note}} +This syntax replaces the previous syntax: `Template.myTemplate.events = {...}`, +but for now, the old syntax still works. +{{/note}} -{{#dtdd "isDefaultPrevented()"}} -Returns whether `preventDefault()` has been called for this event. -{{/dtdd}} -
-Returning `false` from a handler is the same as calling -both `stopImmediatePropagation` and `preventDefault` on the event. +{{> api_box template_helpers}} -Event types and their uses include: +Each template has a local dictionary of helpers that are made available to it, +and this call specifies helpers to add to the template's dictionary. -
-{{#dtdd "click"}} -Mouse click on any element, including a link, button, form control, or div. -Use `preventDefault()` to prevent a clicked link from being followed. -Some ways of activating an element from the keyboard also fire `click`. -{{/dtdd}} +Example: -{{#dtdd "dblclick"}} -Double-click. -{{/dtdd}} + Template.myTemplate.helpers({ + foo: function () { + return Session.get("foo"); + } + }); -{{#dtdd "focus, blur"}} -A text input field or other form control gains or loses focus. You -can make any element focusable by giving it a `tabindex` property. -Browsers differ on whether links, checkboxes, and radio buttons are -natively focusable. These events do not bubble. -{{/dtdd}} +In Handlebars, this helper would then be invoked as `{{dstache}}foo}}`. -{{#dtdd "change"}} -A checkbox or radio button changes state. For text fields, use -`blur` or key events to respond to changes. -{{/dtdd}} +The following syntax is equivalent, but won't work for reserved property +names: -{{#dtdd "mouseenter, mouseleave"}} The pointer enters or -leaves the bounds of an element. These events do not bubble. -{{/dtdd}} + Template.myTemplate.foo = function () { + return Session.get("foo"); + }; -{{#dtdd "mousedown, mouseup"}} -The mouse button is newly down or up. -{{/dtdd}} +{{> api_box template_preserve}} -{{#dtdd "keydown, keypress, keyup"}} -The user presses a keyboard key. `keypress` is most useful for -catching typing in text fields, while `keydown` and `keyup` can be -used for arrow keys or modifier keys. -{{/dtdd}} -
+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. -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. +{{#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. +{{/note}} -{{/api_box_inline}} +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: +* Input text fields and other form controls +* Elements with CSS animations +* Iframes +* Nodes with references kept in JavaScript code +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. -{{#api_box_inline constant}} +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. -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. +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. -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. +For example, to preserve all `` elements with ids in template 'foo', use: -{{#note}} -Constant regions are intended for embedding non-Meteor content. -Event handlers and reactive dependencies don't currently work -correctly inside constant regions. -{{/note}} + 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. -{{/api_box_inline}} +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. -{{#api_box_inline isolate}} +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. -Each template runs in its own reactive context. 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. +{{#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}} -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}} +

Template instances

+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. +Template instance objects are found as the value of `this` in the +`created`, `rendered`, and `destroyed` template callbacks and as an +argument to event handlers. -

Accounts

+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. -XXX intro text +You can only access `findAll`, `find`, `firstNode`, and `lastNode` +from the `rendered` callback and event handlers, not from `created` +and `destroyed`, because they require the template instance to be +in the DOM. -{{> api_box user}} +{{> api_box template_findAll}} -Retreives the user record for the current user from -the [`Meteor.users`](#meteor_users) collection. +Returns an array of DOM elements matching `selector`. -On the client this will be a subset of the fields in the document, only -those that are published from the server are available on the client. By -default the server publishes `username`, `emails`, and -`profile`. See [`Meteor.users`](#meteor_users) for more on -the fields used user documents. +The template instance serves as the document root for the selector. Only +elements inside the template and its sub-templates can match parts of +the selector. -If the user is logged in but the user's database record is not fully -loaded yet, this returns an object with only the `_id` field set. During -this period [`userLoaded`](#meteor_userloaded) will return -`false`. +{{> api_box template_find}} -{{> api_box userId}} +Returns one DOM element matching `selector`, or `null` if there are no +such elements. -{{> api_box users}} +The template instance serves as the document root for the selector. Only +elements inside the template and its sub-templates can match parts of +the selector. -This collection contains one document per user. Example user document: +{{> api_box template_firstNode}} - { - _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f" // userId - username: "cool_kid_13", // unique name - emails: [ - // each email address can only belong to one user. - { address: "cool@example.com", verified: true }, - { address: "another@different.com", verified: false } - ], - profile: { - // The profile is writable by the user by default. - name: "Joe Schmoe" - }, - services: { - facebook: { - id: "709050", // facebook id - accessToken: "AAACCgdX7G2...AbV9AZDZD" - }, - resume: { - loginTokens: [ - { token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd", - when: 1349761684048 } - ] - } - } - } +The two nodes `firstNode` and `lastNode` indicate the extent of the +rendered template in the DOM. The rendered template includes these +nodes, their intervening siblings, and their descendents. These two +nodes are siblings (they have the same parent), and `lastNode` comes +after `firstNode`, or else they are the same node. +{{> api_box template_lastNode}} -Like all
Meteor.Collections, you can access all -documents on the server, but only those specifically published by the server are -available on the client. +{{> api_box template_data}} -By default, the current user's `username`, `emails` and `profile` are -published to the client. You can publish additional fields for the -current user with: +This property provides access to the data context at the top level of +the template. It is updated each time the template is re-rendered. +Access is read-only and non-reactive. - Meteor.publish("userData", function () { - return Meteor.users.find({_id: this.userId}, - {fields: {'other': 1, 'things': 1}}); - }); -If the `autopublish` package is installed, the `username` and `profile` fields -for all users are published to all clients. To publish specific fields from all -users: +{{> api_box render}} - Meteor.publish("allUserData", function () { - return Meteor.users.find({}, {fields: {'nested.things': 1}}); - }); +`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. -Users are by default allowed to specify their own `profile` field with -[`Accounts.createUser`](#accounts_createuser) and modify it with -`Meteor.users.update`. To allow users to edit additional fields, use -[`Meteor.users.allow`](#allow). To forbid users from making any modifications to -their user document: +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. - Meteor.users.deny({update: function () { return true; }}); +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 +[`Meteor.flush`](#meteor_flush). +`Meteor.render` tracks the data dependencies of `htmlFunc` by running +it in a reactive context, 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). -{{> api_box userLoaded}} +Example: -There are some cases when the client knows the id of the logged in user -but has not yet received the user data from the server. For example, if -the user is logged in and reloads the page the user data will be -unavailable during initial page load. + // Show the number of users online. + var frag = Meteor.render(function () { + return "

There are " + Users.find({online: true}).count() + + " users online.

"; + }); + document.body.appendChild(frag); -During these periods, `userLoaded` will return false -and [`user`](#meteor_user) will return an object with only -the `_id` key. + // Find all users that have been idle for a while, and mark them as + // offline. The count on the screen will automatically update. + Users.update({idleTime: {$gt: 30}}, {online: false}); -{{#note}} -We realize this is inconvenient. It is a temporary solution. In the -future we will either make it unnecessary or fold it into a more -general mechanism. -{{/note}} +{{> api_box renderList}} -{{> api_box logout}} +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 loginWithPassword}} +`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. -{{> api_box loginWithOAuth}} +`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." -These functions initiate the login process with a third party OAuth -provider (eg: Facebook, Google, etc). When called they open a new pop-up -window that loads the provider's login page. Once the user has logged in -with the provider, the pop-up window is closed and the Meteor client -logs in to the Meteor server with the information provided by the OAuth -provider. +Each call to `docFunc` or `elseFunc` is run in its own reactive +context so that if it has other external data dependencies, it will be +individually re-run when the data changes. -If the user has not already granted all the permissions requested they will be -prompted to grant access to their account in the pop-up dialog. Values for the -`requestPermissions` parameter differ for each login service: +Example: -- Facebook: http://developers.facebook.com/docs/authentication/permissions/ -- GitHub: http://developer.github.com/v3/oauth/#scopes -- Google: https://developers.google.com/accounts/docs/OAuth2Login#scopeparameter + // 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 '
' + post.name + '
'; + }); + document.body.appendChild(frag); -Currently, `loginWithTwitter` and `loginWithWeibo` do not support -`requestPermissions`. + // 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 accounts_createUser}} +{{#api_box_inline eventmaps}} -On the client this function logs in as the newly created user on -successful completion. On the server, it returns the newly created user -id. +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 +the handlers for those events. The property can be in one of several +forms: -On the client, you must pass `password` and one of `username` or `email` -— enough information for the user to be able to log in again -later. On the server, you can pass any subset of these options, but the -user will not be able to log in until it has an identifier and a -password. +
+{{#dtdd "eventtype"}} +Matches a particular type of event, such as 'click'. +{{/dtdd}} -To create an account without a password on the server and still let the -user pick their own password, call `createUser` with the `email` option -and then -call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This -will send the user an email with a link to set their initial password. +{{#dtdd "eventtype selector"}} +Matches a particular type of event, but only when it appears on +an element that matches a certain CSS selector. +{{/dtdd}} -By default the `profile` option is added directly to the new user document. To -override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser). +{{#dtdd "event1, event2"}} +To handle more than one type of event with the same function, use a +comma-separated list. +{{/dtdd}} +
-This function is only used for creating users with passwords. The OAuth -login flows do not use this function. +The handler function receives two arguments: `event`, an object with +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 +block helpers such as `#with` and `#each`. +Example: -{{> api_box accounts_changePassword}} + { + // Fires when any element is clicked + 'click': function (event) { ... }, -{{> api_box accounts_forgotPassword}} + // Fires when any element with the 'accept' class is clicked + 'click .accept': function (event) { ... }, -This triggers a call -to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) -on the server. Pass the token the user receives in this email -to [`Accounts.resetPassword`](#accounts_resetpassword) to -complete the password reset process. + // Fires when 'accept' is clicked, or a key is pressed + 'keydown, click .accept': function (event) { ... } + } -If you are using the `accounts-ui` - package, this is handled automatically. Otherwise, it is your -responsiblity to prompt the user for the new password and call `resetPassword`. +Most events bubble up the document tree from their originating +element. For example, `'click p'` catches a click anywhere in a +paragraph, even if the click originated on a link, span, or some other +element inside the paragraph. The originating element of the event +is available as the `target` property, while the element that matched +the selector and is currently handling it is called `currentTarget`. -- XXX token goes to `Accounts._resetPasswordToken`. + { + 'click p': function (event) { + var paragraph = event.currentTarget; // always a P + var clickedElement = event.target; // could be the P or a child element + } + } -{{> api_box accounts_resetPassword}} +If a selector matches multiple elements that an event bubbles to, it +will be called multiple times, for example in the case of `'click +div'` or `'click *'`. If no selector is given, the handler +will only be called once, on the original target element. -This function accepts tokens generated -by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail) -and -[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail) +The following properties and methods are available on the event object +passed to handlers: -{{> api_box accounts_setPassword}} +
+{{#dtdd name="type" type="String"}} +The event's type, such as "click", "blur" or "keypress". +{{/dtdd}} -{{> api_box accounts_verifyEmail}} +{{#dtdd name="target" type="DOM Element"}} +The element that originated the event. +{{/dtdd}} -This function accepts tokens generated -by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It -sets the `emails.verified` field in the user record. +{{#dtdd name="currentTarget" type="DOM Element"}} +The element currently handling the event. This is the element that +matched the selector in the event map. For events that bubble, it may +be `target` or an ancestor of `target`, and its value changes as the +event bubbles. +{{/dtdd}} -{{> api_box accounts_sendResetPasswordEmail}} +{{#dtdd name="which" type="Number"}} +For mouse events, the number of the mouse button (1=left, 2=middle, 3=right). +For key events, a character or key code. +{{/dtdd}} -The token in this email should be passed -to [`Accounts.resetPassword`](#accounts_resetpassword). +{{#dtdd "stopPropagation()"}} +Prevent the event from propagating (bubbling) up to other elements. +Other event handlers matching the same element are still fired, in +this and other event maps. +{{/dtdd}} -To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +{{#dtdd "stopImmediatePropagation()"}} +Prevent all additional event handlers from being run on this event, +including other handlers in this event map, handlers reached by +bubbling, and handlers in other event maps. +{{/dtdd}} -{{> api_box accounts_sendEnrollmentEmail}} +{{#dtdd "preventDefault()"}} +Prevents the action the browser would normally take in response to this +event, such as following a link or submitting a form. Further handlers +are still called, but cannot reverse the effect. +{{/dtdd}} -The token in this email should be passed -to [`Accounts.resetPassword`](#accounts_resetpassword). +{{#dtdd "isPropagationStopped()"}} +Returns whether `stopPropagation()` has been called for this event. +{{/dtdd}} -To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +{{#dtdd "isImmediatePropagationStopped()"}} +Returns whether `stopImmediatePropagation()` has been called for this event. +{{/dtdd}} -{{> api_box accounts_sendVerificationEmail}} +{{#dtdd "isDefaultPrevented()"}} +Returns whether `preventDefault()` has been called for this event. +{{/dtdd}} +
-The token in this email should be passed -to [`Accounts.verifyEmail`](#accounts_verifyemail). +Returning `false` from a handler is the same as calling +both `stopImmediatePropagation` and `preventDefault` on the event. -To customize the contents of the email, see -[`Accounts.emailTemplates`](#accounts_emailtemplates). +Event types and their uses include: -{{> api_box accounts_emailTemplates}} +
+{{#dtdd "click"}} +Mouse click on any element, including a link, button, form control, or div. +Use `preventDefault()` to prevent a clicked link from being followed. +Some ways of activating an element from the keyboard also fire `click`. +{{/dtdd}} -This is an `Object` with several fields that are used to generate text -for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and -`sendVerificationEmail`. +{{#dtdd "dblclick"}} +Double-click. +{{/dtdd}} -Override fields of the object by assigning to them: +{{#dtdd "focus, blur"}} +A text input field or other form control gains or loses focus. You +can make any element focusable by giving it a `tabindex` property. +Browsers differ on whether links, checkboxes, and radio buttons are +natively focusable. These events do not bubble. +{{/dtdd}} -- `from`: A `String` with an RFC5322 From address. By default email is from - `no-reply@meteor.com`. If you wish to receive email from users asking for help - with their account, be sure to set this to an email address that you can receive - email at. -- `siteName`: The public name of your application. Defaults to the DNS name of - the application (eg: `awesome.meteor.com`). -- `resetPassword`: An `Object` with two fields: - - `resetPassword.subject`: A `Function` that takes a user object and returns - a `String` for the subject line of a reset password email. - - `resetPassword.text`: A `Function` that takes a user object and a url, and - returns the body text for a reset password email. -- `enrollAccount`: Same as `resetPassword`, but for initial password setup for - new accounts. -- `verifyEmail`: Same as `resetPassword`, but for verifying the users email - address. +{{#dtdd "change"}} +A checkbox or radio button changes state. For text fields, use +`blur` or key events to respond to changes. +{{/dtdd}} +{{#dtdd "mouseenter, mouseleave"}} The pointer enters or +leaves the bounds of an element. These events do not bubble. +{{/dtdd}} -Example: +{{#dtdd "mousedown, mouseup"}} +The mouse button is newly down or up. +{{/dtdd}} - Accounts.emailTemplates.siteName = "AwesomeSite"; - Accounts.emailTemplates.from = "AwesomeSite Admin "; - Accounts.emailTemplates.enrollAccount.subject = function (user) { - return "Welcome to Awesome Town, " + user.profile.name; - }; - Accounts.emailTemplates.enrollAccount.text = function (user, url) { - return "You have been selected to participate in building better future!" - + " To activate your account, simply click the link below:\n\n" - + url; - }; +{{#dtdd "keydown, keypress, keyup"}} +The user presses a keyboard key. `keypress` is most useful for +catching typing in text fields, while `keydown` and `keyup` can be +used for arrow keys or modifier keys. +{{/dtdd}} +
+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 accounts_config}} -{{> api_box accounts_ui_config}} +{{/api_box_inline}} -{{> api_box accounts_validateNewUser}} -This can be called multiple times. If any of the functions return -`false` the new user creation is aborted. -Example: +{{#api_box_inline constant}} - // All users must have a username longer than 3 characters. - Accounts.validateNewUser(function (user) { - return user.username && user.username.length >= 3; - }); +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. -{{> api_box accounts_onCreateUser}} +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. -Use this when you need to do more than simply accept or reject new user -creation. With this function you can programatically control the -contents of new user documents. +{{#note}} +Constant regions are intended for embedding non-Meteor content. +Event handlers and reactive dependencies don't currently work +correctly inside constant regions. +{{/note}} -The function you pass will be called with two arguments: `options` and -`user`. The `options` argument comes -from [`Accounts.createUser`](#accounts_createuser) for -password-based users or from the OAuth login flow. `options` may come -from an untrusted client so make to validate any values you read from -it. The `user` argument is created on the server and contains a -proposed user object with all the automatically generated fields -required for the user to log in. -The function should return a new user object with whatever modifications -are desired. The returned object is inserted directly into -the [`Meteor.users`](#meteor_users) collection. +{{/api_box_inline}} -The default create user function simply copies `options.profile` into -the new user document. Calling `onCreateUser` overrides the default -hook. This can only be called once. +{{#api_box_inline isolate}} -Example: +Each template runs in its own reactive context. 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. - // Make members of the GitHub 'meteor' group into admins. - Accounts.onCreateUser(function(options, user) { - // http://developer.github.com/v3/orgs/members/#get-member - if (!Meteor.http.get( - "https://api.github.com/orgs/meteor/members/" + - user.services.github.username + - "?access_token=" + user.services.github.accessToken - ).error) { - user.admin = true; - } - return user; - }); +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}}

Timers

@@ -2297,9 +2357,9 @@

Email

environment variable should be of the form `smtp://USERNAME:PASSWORD@HOST:PORT/`. For apps deployed with `meteor deploy`, `MAIL_URL` defaults to an account (provided by -Mailgun) which allows -apps to send up to 200 emails per day; you may override this default by -assigning to `process.env.MAIL_URL` before your first call to `Email.send`. +[Mailgun](http://www.mailgun.com/)) which allows apps to send up to 200 emails +per day; you may override this default by assigning to `process.env.MAIL_URL` +before your first call to `Email.send`. If `MAIL_URL` is not set (eg, when running your application locally), `Email.send` outputs the message to standard output instead. diff --git a/docs/client/api.js b/docs/client/api.js index 476a85c95c8..0f339c1b9e7 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -741,7 +741,7 @@ Template.api.loginWithPassword = { { name: "password", type: "String", - descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with SRP." + descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with [SRP](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)." }, { name: "callback", @@ -752,11 +752,11 @@ Template.api.loginWithPassword = { }; -Template.api.loginWithOAuth = { - id: "meteor_loginwithoauth", - name: "Meteor.loginWithOAuthProvider([options], [callback])", +Template.api.loginWithExternalService = { + id: "meteor_loginwithexternalservice", + name: "Meteor.loginWithExternalService([options], [callback])", locus: "Client", - descr: ["Log the user in using an external OAuth service."], + descr: ["Log the user in using an external service."], args: [ { name: "callback", @@ -773,6 +773,76 @@ Template.api.loginWithOAuth = { ] }; + + +Template.api.accounts_config = { + id: "accounts_config", + name: "Accounts.config(options)", + locus: "Anywhere", + descr: ["Set global accounts options."], + options: [ + { + name: "sendVerificationEmail", + type: "Boolean", + descr: "New users with an email address will receive an address verification email." + }, + { + name: "forbidClientAccountCreation", + type: "Boolean", + descr: "[`createUser`](#accounts_createuser) requests from the client will be rejected." + } + ] +}; + +Template.api.accounts_ui_config = { + id: "accounts_ui_config", + name: "Accounts.ui.config(options)", + locus: "Client", + descr: ["Configure the behavior of [`{{loginButtons}}`](#accountsui)."], + options: [ + { + name: "requestPermissions", + type: "Object", + descr: "Which [permissions](#requestpermissions) to request from the user for each external service." + }, + { + name: "passwordSignupFields", + type: "String", + descr: "Which fields to display in the user creation form. One of '`USERNAME_AND_EMAIL`', '`USERNAME_AND_OPTIONAL_EMAIL`', '`USERNAME_ONLY`', or '`EMAIL_ONLY`' (default)." + } + ] +}; + +Template.api.accounts_validateNewUser = { + id: "accounts_validatenewuser", + name: "Accounts.validateNewUser(func)", + locus: "Server", + descr: ["Set restrictions on new user creation."], + args: [ + { + name: "func", + type: "Function", + descr: "Called whenever a new user is created. Takes the new user object, and returns true to allow the creation or false to abort." + } + ] +}; + +Template.api.accounts_onCreateUser = { + id: "accounts_oncreateuser", + name: "Accounts.onCreateUser(func)", + locus: "Server", + descr: ["Customize new user creation."], + args: [ + { + name: "func", + type: "Function", + descr: "Called whenever a new user is created. Return the new user object, or throw an `Error` to abort the creation." + } + ] +}; + + + Template.api.accounts_createUser = { id: "accounts_createuser", name: "Accounts.createUser(options, [callback])", @@ -985,75 +1055,6 @@ Template.api.accounts_emailTemplates = { -Template.api.accounts_config = { - id: "accounts_config", - name: "Accounts.config(options)", - locus: "Anywhere", - descr: ["Set global accounts options."], - options: [ - { - name: "sendVerificationEmail", - type: "Boolean", - descr: "New users with an email address will receive an address verification email." - }, - { - name: "forbidClientAccountCreation", - type: "Boolean", - descr: "[`createUser`](#accounts_createuser) requests from the client will be rejected." - } - ] -}; - -Template.api.accounts_ui_config = { - id: "accounts_ui_config", - name: "Accounts.ui.config(options)", - locus: "Client", - descr: ["Set Accounts UI options for the `loginButtons` template."], - options: [ - { - name: "requestPermissions", - type: "Object", - descr: "Which permissions to request from the user for each OAuth service. For example: `{facebook: ['user_likes'], github: ['user', 'repo']}`" - }, - { - name: "passwordSignupFields", - type: "String", - descr: "Which fields to display in the user creation form. One of '`USERNAME_AND_EMAIL`', '`USERNAME_AND_OPTIONAL_EMAIL`', '`USERNAME_ONLY`', or '`EMAIL_ONLY`' (default)." - } - ] -}; - -Template.api.accounts_validateNewUser = { - id: "accounts_validatenewuser", - name: "Accounts.validateNewUser(func)", - locus: "Server", - descr: ["Set restrictions on new user creation."], - args: [ - { - name: "func", - type: "Function", - descr: "Called whenever a new user is created. Takes the new user object, and returns true to allow the creation or false to abort." - } - ] -}; - -Template.api.accounts_onCreateUser = { - id: "accounts_oncreateuser", - name: "Accounts.onCreateUser(func)", - locus: "Server", - descr: ["Customize new user creation."], - args: [ - { - name: "func", - type: "Function", - descr: "Called whenever a new user is created. Return the new user object, or throw an `Error` to abort the creation." - } - ] -}; - - - - Template.api.setTimeout = { id: "meteor_settimeout", name: "Meteor.setTimeout", @@ -1402,8 +1403,7 @@ Template.api.template_data = { }; var rfc = function (descr) { - return ('RFC5322' - + ' ' + descr); + return '[RFC5322](http://tools.ietf.org/html/rfc5322) ' + descr; }; Template.api.email_send = { diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 06d4bca77f8..864a4025ac0 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -22,12 +22,12 @@

Concepts

Structuring your application

-A Meteor application is a mix of JavaScript that runs inside a -client web browser, JavaScript that runs on the Meteor server inside -a Node.js container, and all the supporting HTML fragments, CSS rules, -and static assets. Meteor automates the packaging and transmission -of these different components. And, it is quite flexible about how -you choose to structure those components in your file tree. +A Meteor application is a mix of JavaScript that runs inside a client web +browser, JavaScript that runs on the Meteor server inside a +[Node.js](http://nodejs.org/) container, and all the supporting HTML fragments, +CSS rules, and static assets. Meteor automates the packaging and transmission +of these different components. And, it is quite flexible about how you choose +to structure those components in your file tree. The only server asset is JavaScript. Meteor gathers all your JavaScript files, excluding anything under the `client` @@ -108,8 +108,7 @@

Data

Meteor's protocol for distributing document updates is database agnostic. By default, Meteor applications use the -familiar MongoDB API: +familiar [MongoDB API](http://www.mongodb.org/display/DOCS/Manual): servers store documents in MongoDB collections, and clients cache those documents in a client-side cache that implements the same Mongo API for queries and updates. @@ -149,14 +148,8 @@

Data

an alternative API. The `mongo-livedata` is a good starting point for such a project. -{{#note}} -A pre-release version of Meteor includes a user login system and a set of tools -for securing read and write access to data based on the logged-in user. For more -information, see the -Getting -Started with Auth wiki page. -{{/note}} +XXX should we mention security/auth at all here, or just expect folks to keep +reading until the accounts section? {{/better_markdown}} @@ -166,11 +159,10 @@

Data

Reactivity

-Meteor embraces the concept of - -reactive programming. This means that you can write your code in a -simple imperative style, and the result will be automatically -recalculated whenever data changes that your code depends on. +Meteor embraces the concept of [reactive +programming](http://en.wikipedia.org/wiki/Reactive_programming). This means that +you can write your code in a simple imperative style, and the result will be +automatically recalculated whenever data changes that your code depends on. Meteor.autosubscribe(function () { Meteor.subscribe("messages", Session.get("currentRoomId")); @@ -214,10 +206,11 @@

Reactivity

* [`Meteor.userId`](#meteor_userid) * [`Meteor.userLoaded`](#meteor_userloaded) -Meteor's implementation -of reactivity is short and sweet, about 50 lines of code. You can -hook into it yourself to add new reactive contexts or data sources, -using the [`Meteor.deps`](#meteor_deps) module. +Meteor's +[implementation](https://github.com/meteor/meteor/blob/master/packages/deps/deps.js) +of reactivity is short and sweet, about 50 lines of code. You can hook into it +yourself to add new reactive contexts or data sources, using the +[`Meteor.deps`](#meteor_deps) module. {{/better_markdown}} @@ -267,15 +260,14 @@

Live HTML

[`Meteor.flush`](#meteor_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 that plague hand-written update -logic. To protect your elements from cleanup, just make sure that they -on-screen before your code returns to the event loop, or before any -call you make to [`Meteor.flush`](#meteor_flush). +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 on-screen before your code returns to the event loop, +or before any call you make to [`Meteor.flush`](#meteor_flush). Another thorny problem in hand-written applications is element preservation. Suppose the user is typing text into an `` @@ -313,10 +305,9 @@

Templates

{{#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 -and Meteor -Handlebars extensions. +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 diff --git a/docs/client/docs.css b/docs/client/docs.css index b4661d17308..4f98beac79f 100644 --- a/docs/client/docs.css +++ b/docs/client/docs.css @@ -436,11 +436,11 @@ pre { @media (min-width: 1024px) { /* ipad landscape and desktop */ #main { - width: 600px; - margin-left: 310px; /* nav width + padding */ + width: 610px; + margin-left: 330px; /* nav width + padding */ } #nav { - width: 250px; + width: 270px; } .github-ribbon { display: block; diff --git a/docs/client/docs.js b/docs/client/docs.js index d9612497e0a..5190ee9ad16 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -48,18 +48,29 @@ Meteor.startup(function () { } }); - $('#main, #nav').delegate("a[href^='#']", 'click', function (evt) { - evt.preventDefault(); - var sel = $(this).attr('href'); + window.onhashchange = function () { + scrollToSection(location.hash); + }; + + var scrollToSection = function (section) { ignore_waypoints = true; - Session.set("section", sel.substr(1)); + Session.set("section", section.substr(1)); scroller().animate({ - scrollTop: $(sel).offset().top + scrollTop: $(section).offset().top }, 500, 'swing', function () { - window.location.hash = sel; + window.location.hash = section; ignore_waypoints = false; }); + }; + + $('#main, #nav').delegate("a[href^='#']", 'click', function (evt) { + evt.preventDefault(); + var sel = $(this).attr('href'); + scrollToSection(sel); }); + + // Make external links open in a new tab. + $('a:not([href^="#"])').attr('target', '_blank'); }); var toc = [ @@ -89,13 +100,13 @@ var toc = [ "Publish and subscribe", [ "Meteor.publish", [ + {instance: "this", name: "userId", id: "publish_userId"}, {instance: "this", name: "set", id: "publish_set"}, {instance: "this", name: "unset", id: "publish_unset"}, {instance: "this", name: "complete", id: "publish_complete"}, {instance: "this", name: "flush", id: "publish_flush"}, {instance: "this", name: "onStop", id: "publish_onstop"}, - {instance: "this", name: "stop", id: "publish_stop"}, - {instance: "this", name: "userId", id: "publish_userId"} + {instance: "this", name: "stop", id: "publish_stop"} ], "Meteor.subscribe", "Meteor.autosubscribe" @@ -141,7 +152,8 @@ var toc = [ {type: "spacer"}, {name: "Selectors", style: "noncode"}, {name: "Modifiers", style: "noncode"}, - {name: "Sort specifiers", style: "noncode"} + {name: "Sort specifiers", style: "noncode"}, + {name: "Field specifiers", style: "noncode"} ], "Session", [ @@ -150,6 +162,41 @@ var toc = [ "Session.equals" ], + {name: "Accounts", id: "accounts_api"}, [ + "Meteor.user", + "Meteor.userId", + "Meteor.users", + "Meteor.userLoaded", + "Meteor.logout", + "Meteor.loginWithPassword", + {name: "Meteor.loginWithFacebook", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithGithub", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithGoogle", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithTwitter", id: "meteor_loginwithexternalservice"}, + {name: "Meteor.loginWithWeibo", id: "meteor_loginwithexternalservice"}, + {type: "spacer"}, + + "Accounts.config", + "Accounts.ui.config", + "Accounts.validateNewUser", + "Accounts.onCreateUser" + ], + + {name: "Passwords", id: "accounts_passwords"}, [ + "Accounts.createUser", + "Accounts.changePassword", + "Accounts.forgotPassword", + "Accounts.resetPassword", + "Accounts.setPassword", + "Accounts.verifyEmail", + {type: "spacer"}, + + "Accounts.sendResetPasswordEmail", + "Accounts.sendEnrollmentEmail", + "Accounts.sendVerificationEmail", + "Accounts.emailTemplates" + ], + {name: "Templates", id: "templates_api"}, [ {prefix: "Template", instance: "myTemplate", id: "template_call"}, [ {name: "rendered", id: "template_rendered"}, @@ -174,41 +221,6 @@ var toc = [ {name: "Reactivity isolation", style: "noncode", id: "isolate"} ], - {name: "Accounts", id: "accounts_api"}, [ - "Meteor.user", - "Meteor.userId", - "Meteor.users", - "Meteor.userLoaded", - "Meteor.logout", - "Meteor.loginWithPassword", - {name: "Meteor.loginWithFacebook", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithGithub", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithGoogle", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithTwitter", id: "meteor_loginwithoauth"}, - {name: "Meteor.loginWithWeibo", id: "meteor_loginwithoauth"}, - {type: "spacer"}, - - "Accounts.createUser", - "Accounts.changePassword", - "Accounts.forgotPassword", - "Accounts.resetPassword", - "Accounts.setPassword", - "Accounts.verifyEmail", - {type: "spacer"}, - - "Accounts.sendResetPasswordEmail", - "Accounts.sendEnrollmentEmail", - "Accounts.sendVerificationEmail", - "Accounts.emailTemplates", - {type: "spacer"}, - - "Accounts.config", - "Accounts.ui.config", - "Accounts.validateNewUser", - "Accounts.onCreateUser" - ], - - "Timers", [ "Meteor.setTimeout", "Meteor.setInterval", diff --git a/docs/client/introduction.html b/docs/client/introduction.html index a097e2be235..ce4bc72bc5c 100644 --- a/docs/client/introduction.html +++ b/docs/client/introduction.html @@ -39,7 +39,8 @@ -The following works on all supported platforms. +The following works on all [supported +platforms](https://github.com/meteor/meteor/wiki/Supported-Platforms). Install Meteor: @@ -106,25 +107,24 @@

Seven Principles of Meteor

Developer Resources

-Fork me on GitHub +Fork me on GitHub If anything in Meteor catches your interest, we hope you'll get involved with the project!
Stack Overflow
-
The best place to ask (and answer!) technical questions is - on Stack - Overflow. Be sure to add the meteor tag to your - question. +
The best place to ask (and answer!) technical questions is on [Stack + Overflow](http://stackoverflow.com/questions/tagged/meteor). Be sure to add + the meteor tag to your question.
Mailing lists
- We have two mailing lists for Meteor. meteor-talk@googlegroups.com + We have two mailing lists for Meteor. meteor-talk@googlegroups.com is for general questions, requests for help, and new project announcements. - meteor-core@googlegroups.com + meteor-core@googlegroups.com is for discussing Meteor internals and proposed changes.
@@ -134,7 +134,7 @@

Developer Resources

can.
GitHub
-
The code is on GitHub. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.
+
The code is on GitHub. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.
{{/markdown}} diff --git a/docs/client/packages/accounts-ui.html b/docs/client/packages/accounts-ui.html index 7e7c4f01300..2c42ccfab7f 100644 --- a/docs/client/packages/accounts-ui.html +++ b/docs/client/packages/accounts-ui.html @@ -5,21 +5,25 @@ A turn-key user interface for Meteor Accounts. To add Accounts and a set of login controls to an application add the -`accounts-ui` package and one or more login provider package: +`accounts-ui` package and at least one login provider package: `accounts-password`, `accounts-facebook`, `accounts-github`, -`accounts-google`, `accounts-twitter`, and `accounts-weibo`. +`accounts-google`, `accounts-twitter`, or `accounts-weibo`. -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 -OAuth provider configured, this will add one login/logout button. If you -use `accounts-password` or have more than one OAuth provider, this will -add a 'Sign in' link which opens a dropdown menu with login options. To -make the login dropdown right aligned, use `{{dstache}}loginButtons align=right}}`. +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. To make the +login dropdown right aligned (useful if you position the login buttons +at the right edge of the screen), use `{{dstache}}loginButtons align=right}}`. + +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 [`sendResetPasswordEmail`](#accounts_sendresetpasswordemail), [`sendVerificationEmail`](#accounts_sendverificationemail), and [`sendEnrollmentEmail`](#accounts_sendenrollmentemail). These -do not have be manually placed in HTML, they are automatically activated +do not have be manually placed in HTML: they are automatically activated when the URLs are loaded. diff --git a/docs/client/packages/amplify.html b/docs/client/packages/amplify.html index 73a6d24ba36..1afe28df47e 100644 --- a/docs/client/packages/amplify.html +++ b/docs/client/packages/amplify.html @@ -10,6 +10,7 @@ Amplify defines a global namespace `amplify` on the client only. It does not run on the server. -For more information about Amplify, see http://amplifyjs.com/. +For more information about Amplify, see . + {{/better_markdown}} diff --git a/docs/client/packages/backbone.html b/docs/client/packages/backbone.html index 30675412c68..f12fc61b122 100644 --- a/docs/client/packages/backbone.html +++ b/docs/client/packages/backbone.html @@ -8,7 +8,7 @@ client-side URL routing. For more information about Backbone, see -http://documentcloud.github.com/backbone/. +. {{/better_markdown}} diff --git a/docs/client/packages/bootstrap.html b/docs/client/packages/bootstrap.html index aa2f4ccdfee..62a712298f6 100644 --- a/docs/client/packages/bootstrap.html +++ b/docs/client/packages/bootstrap.html @@ -9,7 +9,7 @@ navigation. For more information about Bootstrap, see -http://twitter.github.com/bootstrap/. +. {{/better_markdown}} diff --git a/docs/client/packages/coffeescript.html b/docs/client/packages/coffeescript.html index 6cd1cfc1654..3f24ce8c546 100644 --- a/docs/client/packages/coffeescript.html +++ b/docs/client/packages/coffeescript.html @@ -10,8 +10,7 @@ CoffeeScript is supported on both the client and the server. Files ending with `.coffee` are automatically compiled to JavaScript. -See http://jashkenas.github.com/coffee-script/ -for more information. +See for more information. {{/better_markdown}} diff --git a/docs/client/packages/d3.html b/docs/client/packages/d3.html index a7cf49a82bd..f361fa27c8e 100644 --- a/docs/client/packages/d3.html +++ b/docs/client/packages/d3.html @@ -3,7 +3,7 @@ ## `d3` -[D3.js](http://d3js.org/D3.js) is a JavaScript library for manipulating +[D3.js](http://d3js.org/) is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS. D3's emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, diff --git a/docs/client/packages/jquery.html b/docs/client/packages/jquery.html index f1026687549..989160cd2d6 100644 --- a/docs/client/packages/jquery.html +++ b/docs/client/packages/jquery.html @@ -3,7 +3,7 @@ ## `jquery` -jQuery is a fast and concise JavaScript +[jQuery](http://jquery.com/) is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. diff --git a/docs/client/packages/less.html b/docs/client/packages/less.html index 731eeb0fc27..da85fe95bb7 100644 --- a/docs/client/packages/less.html +++ b/docs/client/packages/less.html @@ -10,8 +10,7 @@ automatically compiled to CSS and the results are included in the client CSS bundle. -See http://lesscss.org/ for -documentation of the LESS language. +See for documentation of the LESS language. {{/better_markdown}} diff --git a/docs/client/packages/sass.html b/docs/client/packages/sass.html index 7db05566a71..51c917ecf42 100644 --- a/docs/client/packages/sass.html +++ b/docs/client/packages/sass.html @@ -7,10 +7,15 @@ application are automatically compiled to CSS and the results are included in the client CSS bundle. -See https://github.com/visionmedia/sass.js -for the JavaScript implementation of the Sass language -and http://sass-lang.com/ for the -original project. +See for the JavaScript implementation +of the Sass language and for the original project. + +{{#warning}} +The Sass JavaScript implementation used by Node is unmaintained and doesn't +implement the newest language syntax documented at . It +may be removed from a future version of Meteor; consider using [Less](#less) or +[Stylus](#stylus) instead. +{{/warning}} {{/better_markdown}} diff --git a/docs/client/packages/spiderable.html b/docs/client/packages/spiderable.html index 08c9d619bba..3bd785c4d43 100644 --- a/docs/client/packages/spiderable.html +++ b/docs/client/packages/spiderable.html @@ -3,37 +3,33 @@ ## `spiderable` -The `spiderable` package is a temporary solution to allow web search -engines to index a Meteor application. It uses the AJAX -Crawling specification published by Google to serve HTML to -compatible spiders (Google, Bing, Yandex, and more). +The `spiderable` package is a temporary solution to allow web search engines to +index a Meteor application. It uses the [AJAX Crawling +specification](https://developers.google.com/webmasters/ajax-crawling/) +published by Google to serve HTML to compatible spiders (Google, Bing, Yandex, +and more). -When a spider requests an HTML snapshot of a page the Meteor server runs -the client half of the application inside phantomjs, a headless browser, and -returns the full HTML generated by the client code. +When a spider requests an HTML snapshot of a page the Meteor server runs the +client half of the application inside [phantomjs](http://phantomjs.org/), a +headless browser, and returns the full HTML generated by the client code. {{#warning}} This is a temporary approach to allow Meteor applications to be searchable. Expect significant changes to this package. {{/warning}} -In order to have links between multiple pages on a site visible to -spiders, apps must use real links (eg ``) rather than -simply re-rendering portions of the page when an element is -clicked. Apps should render their content based on the URL of the page -and can use -HTML5 pushState -to alter the URL on the client without triggering a page reload. See the -Todos example -for a demonstration. +In order to have links between multiple pages on a site visible to spiders, apps +must use real links (eg ``) rather than simply re-rendering +portions of the page when an element is clicked. Apps should render their +content based on the URL of the page and can use [HTML5 +pushState](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history) +to alter the URL on the client without triggering a page reload. See the [Todos +example](http://meteor.com/examples/todos) for a demonstration. {{#warning}} If you deploy your application with `meteor bundle`, you must install -`phantomjs` (http://phantomjs.org) somewhere in your +`phantomjs` ([http://phantomjs.org](http://phantomjs.org/)) somewhere in your `$PATH`. If you use `meteor deploy` this is already taken care of. {{/warning}} diff --git a/docs/client/packages/stylus.html b/docs/client/packages/stylus.html index 0e683b19f60..4665873c378 100644 --- a/docs/client/packages/stylus.html +++ b/docs/client/packages/stylus.html @@ -14,10 +14,9 @@ your `.styl` files to enable cross-browser mixins such as `linear-gradient` and `border-radius`. -See http://learnboost.github.com/stylus -for documentation of the Stylus language, -and http://visionmedia.github.com/nib -for documentation of the nib extensions. +See for documentation of the Stylus +language, and for documentation of the nib +extensions. {{/better_markdown}} diff --git a/docs/client/packages/underscore.html b/docs/client/packages/underscore.html index 1b570d5a119..ae7f2ad88b2 100644 --- a/docs/client/packages/underscore.html +++ b/docs/client/packages/underscore.html @@ -9,8 +9,8 @@ The `underscore` package defines the `_` namespace on both the client and the server. -See http://documentcloud.github.com/underscore/ -for underscore API documentation. +See for underscore API +documentation. {{#warning}} Currently, underscore is included in all projects, as the Meteor diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js index f83aced0148..adeb7213463 100644 --- a/packages/accounts-base/accounts_common.js +++ b/packages/accounts-base/accounts_common.js @@ -12,12 +12,22 @@ if (!Accounts._options) { // - forbidClientAccountCreation {Boolean} // Do not allow clients to create accounts directly. Accounts.config = function(options) { - _.each(["sendVerificationEmail", "forbidClientAccountCreation"], function(key) { + // validate option keys + var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"]; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) { + throw new Error("Accounts.config: Invalid key: " + key); + } + }); + + // set values in Accounts._options + _.each(VALID_KEYS, function (key) { if (key in options) { - if (key in Accounts._options) + if (key in Accounts._options) { throw new Error("Can't set `" + key + "` more than once"); - else + } else { Accounts._options[key] = options[key]; + } } }); }; diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9e681fbc886..c75ca1e87b8 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -316,7 +316,7 @@ return true; }, - fields: ['_id'] // we only look at _id. + fetch: ['_id'] // we only look at _id. }); /// DEFAULT INDEXES ON USERS diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index b771e17de13..a60b8c0890a 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -1,3 +1,12 @@ +// 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?) +Tinytest.add('accounts - config validates keys', function (test) { + test.throws(function () { + Accounts.config({foo: "bar"}); + }); +}); + Tinytest.add('accounts - updateOrCreateUserFromExternalService', function (test) { var facebookId = Meteor.uuid(); var weiboId1 = Meteor.uuid(); diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index 047123e3a3b..c16db9680df 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -13,14 +13,13 @@ } var state = Meteor.uuid(); - var required_scope = ['user']; - var scope = _.union((options && options.requestPermissions) || [], required_scope); - var flat_scope = _.map(scope, encodeURIComponent).join('+'); + var scope = (options && options.requestPermissions) || []; + var flatScope = _.map(scope, encodeURIComponent).join('+'); var loginUrl = 'https://github.com/login/oauth/authorize' + '?client_id=' + config.clientId + - '&scope=' + flat_scope + + '&scope=' + flatScope + '&redirect_uri=' + Meteor.absoluteUrl('_oauth/github?close') + '&state=' + state; diff --git a/packages/accounts-password/email_tests.js b/packages/accounts-password/email_tests.js index c119bf0030d..4691266c47c 100644 --- a/packages/accounts-password/email_tests.js +++ b/packages/accounts-password/email_tests.js @@ -107,8 +107,8 @@ test.equal(Meteor.user().emails.length, 1); test.equal(Meteor.user().emails[0].address, email2); test.isFalse(Meteor.user().emails[0].verified); - // We should NOT be publishing verification tokens! - test.isFalse(_.has(Meteor.user(), 'emailVerificationTokens')); + // We should NOT be publishing things like verification tokens! + test.isFalse(_.has(Meteor.user(), 'services')); }, function (test, expect) { getVerifyEmailToken(email2, test, expect); diff --git a/packages/accounts-password/passwords_server.js b/packages/accounts-password/passwords_server.js index 6f0972efad8..5d907de04d1 100644 --- a/packages/accounts-password/passwords_server.js +++ b/packages/accounts-password/passwords_server.js @@ -138,13 +138,15 @@ if (!token) throw new Meteor.Error(400, "Need to pass token"); - var user = Meteor.users.findOne({'emailVerificationTokens.token': token}); + var user = Meteor.users.findOne( + {'services.email.verificationTokens.token': token}); if (!user) throw new Meteor.Error(403, "Verify email link expired"); - var tokenRecord = _.find(user.emailVerificationTokens, function (t) { - return t.token == token; - }); + var tokenRecord = _.find(user.services.email.verificationTokens, + function (t) { + return t.token == token; + }); if (!tokenRecord) throw new Meteor.Error(403, "Verify email link expired"); @@ -166,7 +168,7 @@ {_id: user._id, 'emails.address': tokenRecord.address}, {$set: {'emails.$.verified': true}, - $pull: {emailVerificationTokens: {token: token}}, + $pull: {'services.email.verificationTokens': {token: token}}, $push: {'services.resume.loginTokens': stampedLoginToken}}); this.setUserId(user._id); @@ -234,8 +236,9 @@ token: Meteor.uuid(), address: address, when: +(new Date)}; - Meteor.users.update({_id: userId}, - {$push: {emailVerificationTokens: tokenRecord}}); + Meteor.users.update( + {_id: userId}, + {$push: {'services.email.verificationTokens': tokenRecord}}); var verifyEmailUrl = Accounts.urls.verifyEmail(tokenRecord.token); Email.send({ @@ -442,7 +445,7 @@ // XXX allow an optional callback? if (callback) { - throw new Error("Meteor.createUser with callback not supported on the server yet."); + throw new Error("Accounts.createUser with callback not supported on the server yet."); } var userId = createUser(options).id; diff --git a/packages/accounts-ui-unstyled/accounts_ui.js b/packages/accounts-ui-unstyled/accounts_ui.js index 46c823b9b0f..770a77c9de7 100644 --- a/packages/accounts-ui-unstyled/accounts_ui.js +++ b/packages/accounts-ui-unstyled/accounts_ui.js @@ -9,6 +9,14 @@ if (!Accounts.ui._options) { Accounts.ui.config = function(options) { + // validate options keys + var VALID_KEYS = ['passwordSignupFields', 'requestPermissions']; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) + throw new Error("Accounts.ui.config: Invalid key: " + key); + }); + + // deal with `passwordSignupFields` if (options.passwordSignupFields) { if (_.contains([ "USERNAME_AND_EMAIL", @@ -17,20 +25,24 @@ Accounts.ui.config = function(options) { "EMAIL_ONLY" ], options.passwordSignupFields)) { if (Accounts.ui._options.passwordSignupFields) - throw new Error("Can't set `passwordSignupFields` more than once"); + throw new Error("Accounts.ui.config: Can't set `passwordSignupFields` more than once"); else Accounts.ui._options.passwordSignupFields = options.passwordSignupFields; } else { - throw new Error("Invalid option for `passwordSignupFields`: " + options.passwordSignupFields); + throw new Error("Accounts.ui.config: Invalid option for `passwordSignupFields`: " + options.passwordSignupFields); } } + // deal with `requestPermissions` if (options.requestPermissions) { _.each(options.requestPermissions, function (scope, service) { - if (Accounts.ui._options.requestPermissions[service]) - throw new Error("Can't set `requestPermissions` more than once for " + service); - else + if (Accounts.ui._options.requestPermissions[service]) { + throw new Error("Accounts.ui.config: Can't set `requestPermissions` more than once for " + service); + } else if (!(scope instanceof Array)) { + throw new Error("Accounts.ui.config: Value for `requestPermissions` must be an array"); + } else { Accounts.ui._options.requestPermissions[service] = scope; + } }); } }; diff --git a/packages/accounts-ui-unstyled/accounts_ui_tests.js b/packages/accounts-ui-unstyled/accounts_ui_tests.js new file mode 100644 index 00000000000..e65989f5d85 --- /dev/null +++ b/packages/accounts-ui-unstyled/accounts_ui_tests.js @@ -0,0 +1,17 @@ +// XXX it'd be cool to also test that the right thing happens if options +// *are* validated, but Accouns.ui._options is global state which makes this hard +// (impossible?) +Tinytest.add('accounts-ui - config validates keys', function (test) { + test.throws(function () { + Accounts.ui.config({foo: "bar"}); + }); + + test.throws(function () { + Accounts.ui.config({passwordSignupFields: "not a valid option"}); + }); + + test.throws(function () { + Accounts.ui.config({requestPermissions: {facebook: "not an array"}}); + }); +}); + diff --git a/packages/accounts-ui-unstyled/login_buttons_dropdown.js b/packages/accounts-ui-unstyled/login_buttons_dropdown.js index 2d4e5508912..121c4aaeeab 100644 --- a/packages/accounts-ui-unstyled/login_buttons_dropdown.js +++ b/packages/accounts-ui-unstyled/login_buttons_dropdown.js @@ -385,7 +385,7 @@ var signup = function () { loginButtonsSession.resetMessages(); - var options = {}; // to be passed to Meteor.createUser + var options = {}; // to be passed to Accounts.createUser var username = trimmedElementValueById('login-username'); if (username !== null) { diff --git a/packages/accounts-ui-unstyled/package.js b/packages/accounts-ui-unstyled/package.js index 4defc581ad4..ef41ac3bf02 100644 --- a/packages/accounts-ui-unstyled/package.js +++ b/packages/accounts-ui-unstyled/package.js @@ -21,3 +21,9 @@ Package.on_use(function (api) { 'login_buttons_dropdown.js', 'login_buttons_dialogs.js'], 'client'); }); + +Package.on_test(function (api) { + api.use('accounts-ui-unstyled'); + api.use('tinytest'); + api.add_files('accounts_ui_tests.js', 'client'); +}); diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index 15f92cde815..84a6c44e391 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -186,6 +186,43 @@ // if (Meteor.isServer) { + Tinytest.add("collection - allow and deny validate options", function (test) { + var collection = new Meteor.Collection(null); + + test.throws(function () { + collection.allow({invalidOption: true}); + }); + test.throws(function () { + collection.deny({invalidOption: true}); + }); + + _.each(['insert', 'update', 'remove', 'fetch'], function (key) { + var options = {}; + options[key] = true; + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + _.each(['insert', 'update', 'remove'], function (key) { + var options = {}; + options[key] = ['an array']; // this should be a function, not an array + test.throws(function () { + collection.allow(options); + }); + test.throws(function () { + collection.deny(options); + }); + }); + + test.throws(function () { + collection.allow({fetch: function () {}}); // this should be an array + }); + }); + Tinytest.add("collection - calling allow restricts", function (test) { var collection = new Meteor.Collection(null); test.equal(collection._restricted, false); diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 7ebfdc81365..4bb3ec4bc73 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -184,7 +184,7 @@ _.each(["insert", "update", "remove"], function (name) { // down. callback = function (err) { if (err) - Meteor._debug(name + " failed: " + err.error + " -- " + err.reason); + Meteor._debug(name + " failed: " + (err.reason || err.stack)); }; } @@ -281,19 +281,34 @@ Meteor.Collection.prototype._ensureIndex = function (index, options) { (function () { var addValidator = function(allowOrDeny, options) { + // validate keys + var VALID_KEYS = ['insert', 'update', 'remove', 'fetch']; + _.each(_.keys(options), function (key) { + if (!_.contains(VALID_KEYS, key)) + throw new Error(allowOrDeny + ": Invalid key: " + key); + }); + var self = this; self._restricted = true; _.each(['insert', 'update', 'remove'], function (name) { - if (options[name]) + if (options[name]) { + if (!(options[name] instanceof Function)) { + throw new Error(allowOrDeny + ": Value for `" + name + "` must be a function"); + } self._validators[name][allowOrDeny].push(options[name]); + } }); // Only update the fetch fields if we're passed things that affect // fetching. This way allow({}) and allow({insert: f}) don't result in // setting fetchAllFields - if (options.update || options.remove || options.fetch) + if (options.update || options.remove || options.fetch) { + if (options.fetch && !(options.fetch instanceof Array)) { + throw new Error(allowOrDeny + ": Value for `fetch` must be an array"); + } self._updateFetch(options.fetch); + } }; Meteor.Collection.prototype.allow = function(options) {