├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── content ├── .DS_Store ├── angular │ ├── metadata.js │ ├── step02.md │ ├── step03.md │ ├── step04.md │ ├── step05.md │ ├── step06.md │ ├── step07.md │ ├── step08.md │ ├── step09.md │ ├── step10.md │ ├── step11.md │ └── step12.md ├── blaze │ ├── metadata.js │ ├── step02.md │ ├── step03.md │ ├── step04.md │ ├── step05.md │ ├── step07.md │ ├── step08.md │ ├── step09.md │ ├── step10.md │ ├── step11.md │ └── step12.md ├── shared │ ├── adding-css.js │ ├── adding-css.md │ ├── explanations.md │ ├── nextSteps.md │ ├── step01.md │ └── step06.md ├── step00.html ├── svelte │ ├── metadata.js │ ├── step02.md │ ├── step03.md │ ├── step04.md │ ├── step05.md │ ├── step07.md │ ├── step08.md │ ├── step09.md │ ├── step10.md │ ├── step11.md │ └── step12.md └── vue │ ├── metadata.js │ ├── step02.md │ ├── step03.md │ ├── step04.md │ ├── step05.md │ ├── step07.md │ ├── step08.md │ ├── step09.md │ ├── step10.md │ ├── step11.md │ └── step12.md ├── generated ├── angular.multi.patch ├── blaze.multi.patch ├── svelte.multi.patch └── vue.multi.patch ├── package-lock.json ├── package.js └── scripts └── process-repo.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "repos/blaze"] 2 | path = repos/blaze 3 | url = git@github.com:meteor/simple-todos.git 4 | [submodule "repos/angular"] 5 | path = repos/angular 6 | url = git@github.com:meteor/simple-todos-angular.git 7 | [submodule "repos/vue"] 8 | path = repos/vue 9 | url = git@github.com:meteor/simple-todos-vue.git 10 | [submodule "repos/svelte"] 11 | path = repos/svelte 12 | url = git@github.com:meteor/simple-todos-svelte.git 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Meteor Development Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | We no longer use this repository, we have now different repos for each technology: 2 | 3 | - https://github.com/meteor/react-tutorial 4 | - https://github.com/meteor/blaze-tutorial 5 | - https://github.com/meteor/vue-tutorial 6 | - https://github.com/meteor/svelte-tutorial 7 | - https://github.com/meteor/angular-tutorial 8 | 9 | # Official Meteor tutorials 10 | 11 | This repository contains the content and view code for the official Meteor tutorials at [meteor.com](https://www.meteor.com/tutorials/blaze/creating-an-app). 12 | 13 | Feel free to submit a pull request to improve the content! 14 | 15 | ### Tutorial content 16 | 17 | 1. [Blaze tutorial](https://www.meteor.com/tutorials/blaze/creating-an-app): [`/content/blaze`](https://github.com/meteor/tutorials/tree/master/content/blaze) 18 | 2. [Angular tutorial](https://www.meteor.com/tutorials/angular/creating-an-app): [`/content/angular`](https://github.com/meteor/tutorials/tree/master/content/angular) 19 | 3. [React tutorial - NEW FORMAT](https://react-tutorial.meteor.com): [`/content/react`](https://github.com/meteor/react-tutorial/) 20 | 4. [Vue tutorial](https://www.meteor.com/tutorials/vue/creating-an-app): [`/content/vue`](https://github.com/meteor/tutorials/tree/master/content/vue) 21 | 5. [Svelte tutorial](https://www.meteor.com/tutorials/svelte/creating-an-app): [`/content/svelte`](https://github.com/meteor/tutorials/tree/master/content/svelte) 22 | 23 | ### Tutorial step-by-step repositories 24 | 25 | We also maintain all of the tutorials as step-by-step git repositories here: 26 | 27 | 1. [Blaze](https://github.com/meteor/simple-todos) 28 | 2. [Angular](https://github.com/meteor/simple-todos-angular) 29 | 3. [React - NEW FORMAT](https://github.com/meteor/react-tutorial/) 30 | 4. [Vue](https://github.com/meteor/simple-todos-vue) 31 | 5. [Svelte](https://github.com/meteor/simple-todos-svelte) 32 | 33 | ### Tutorial viewer 34 | 35 | If you are editing the tutorials, use this simple app to view them: https://github.com/meteor/tutorial-viewer 36 | 37 | ## Tutorial workflow 38 | 39 | ### Editing the prose 40 | 41 | Just edit the markdown files in `/content/`. 42 | 43 | ### Editing code snippets 44 | 45 | The code snippets are generated from the step-by-step git repositories which are git submodules in `/repos`. Each code snippet is its own commit. Commit messages follow the following format: 46 | 47 | ``` 48 | Step 3.1: Add some feature 49 | ``` 50 | 51 | You might also want to make sure that all of your files end with a newline so that you don't get an annoying "No newline at end of file" diff. 52 | 53 | After using `git rebase -i --root` to massage the repository into the desired state, run the script to update the generated files: 54 | 55 | ```sh 56 | ./scripts/process-repo.rb blaze 57 | ``` 58 | 59 | The commit with this message can then be included in the content with the following code snippet: 60 | 61 | ```html 62 | {{> DiffBox step="3.1" tutorialName="simple-todos"}} 63 | ``` 64 | 65 | You should replace `simple-todos` with the correct tutorial name (defined by calling `DiffBox.registerTutorial`). 66 | 67 | You're done! Make sure to commit the changes to all of the generated files. 68 | 69 | ## Repository layout 70 | 71 | This repository is a Meteor package; it's currently not published, but you can clone it and use it as a local package in an app. 72 | 73 | The different parts of the repository have quite different responsibilities, but they are somewhat tightly coupled so it doesn't make sense to split them into separate packages at this point. 74 | 75 | 3. `/content/` The actual tutorial prose content, in Markdown format. 76 | 4. `/generated/` (don't edit manually) This directory contains Git patch files generated from the step-by-step repos. 77 | 5. `/repos/` This directory contains git submodules of all three step-by-step tutorial repositories. 78 | 6. `/scripts/` This contains a script that is used to update `/generated/` from the repositories in `/repos/`. 79 | 80 | 81 | ## Updating Sub Modules 82 | 83 | 1. `git submodule update --init --recursive` 84 | 2. `meteor` 85 | -------------------------------------------------------------------------------- /content/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meteor/tutorials/3fd1f6917fb5dcece6255517ecd1d0b29f12cc48/content/.DS_Store -------------------------------------------------------------------------------- /content/angular/metadata.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | DiffBox.registerTutorial("simple-todos-angular", { 3 | gitHubRepoName: "meteor/simple-todos-angular", 4 | patchFilename: "generated/angular.multi.patch" 5 | }); 6 | } 7 | 8 | TutorialRegistry.registerTutorial("angular", { 9 | title: "Simple Todos Angular", 10 | subtitle: "Learn how to use Meteor and Angular together", 11 | tutorialSourceLink: "github.com/meteor/tutorials/content/angular", 12 | steps: [ 13 | { 14 | title: 'Creating an app', 15 | slug: "creating-an-app", 16 | template: 'sharedStep01' 17 | }, 18 | { 19 | title: 'Templates', 20 | slug: "templates", 21 | template: 'angular-step02' 22 | }, 23 | { 24 | title: 'Collections', 25 | slug: "collections", 26 | template: 'angular-step03' 27 | }, 28 | { 29 | title: 'Forms and events', 30 | slug: "forms-and-events", 31 | template: 'angular-step04' 32 | }, 33 | { 34 | title: 'Update and remove', 35 | slug: "update-and-remove", 36 | template: 'angular-step05' 37 | }, 38 | { 39 | title: 'Running on mobile', 40 | slug: "running-on-mobile", 41 | template: 'angular-step06' 42 | }, 43 | { 44 | title: 'Filtering Collections', 45 | slug: "filtering-collections", 46 | template: 'angular-step07' 47 | }, 48 | { 49 | title: 'Adding user accounts', 50 | slug: "adding-user-accounts", 51 | template: 'angular-step08' 52 | }, 53 | { 54 | title: 'Security with methods', 55 | slug: "security-with-methods", 56 | template: 'angular-step09' 57 | }, 58 | { 59 | title: 'Publish and subscribe', 60 | slug: "publish-and-subscribe", 61 | template: 'angular-step10' 62 | }, 63 | { 64 | title: 'Testing', 65 | slug: "testing", 66 | template: 'angular-step11' 67 | }, 68 | { 69 | title: 'Next steps', 70 | slug: "next-steps", 71 | template: 'angular-step12' 72 | } 73 | ] 74 | }); 75 | -------------------------------------------------------------------------------- /content/angular/step02.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step02"}} 2 | # Defining views with templates 3 | 4 | To use Angular in our app, we first need to remove the default UI package of Meteor, called `Blaze`. 5 | 6 | We remove it by running: 7 | 8 | meteor remove blaze-html-templates 9 | 10 | Now we need to replace it with UI package for angular: 11 | 12 | meteor add angular-templates 13 | 14 | To start working with [angular-meteor](http://angular-meteor.com/), let's add some NPM packages. 15 | 16 | meteor npm install --save angular angular-meteor 17 | 18 | > Note: `meteor npm` supports the same features as `npm`, though the difference can be important. Consult the [`meteor npm` documentation](https://docs.meteor.com/commandline.html#meteornpm) for more information. 19 | 20 | To start working on our todos list app, let's replace the code of the default starter app with the code below. Then we'll talk about what it does. 21 | 22 | {{> DiffBox tutorialName="simple-todos-angular" step="2.2"}} 23 | 24 | {{> DiffBox tutorialName="simple-todos-angular" step="2.3"}} 25 | 26 | Now we need to create a new directory called `imports`, a specially-named directory which will behave differently than other directories in the project. Files outside the `imports` directory will be loaded automatically when the Meteor server starts, while files inside the `imports` directory will only load when an `import` statement is used to load them. 27 | 28 | After creating the `imports` directory, we will create two new files inside it. 29 | 30 | A template for the todosList component: 31 | 32 | {{> DiffBox tutorialName="simple-todos-angular" step="2.4"}} 33 | 34 | And some functionality: 35 | 36 | {{> DiffBox tutorialName="simple-todos-angular" step="2.5"}} 37 | 38 | We can now implement it into the application. 39 | 40 | First, we have to put component into a template: 41 | 42 | {{> DiffBox tutorialName="simple-todos-angular" step="2.6"}} 43 | 44 | Then add module to the application: 45 | 46 | {{> DiffBox tutorialName="simple-todos-angular" step="2.7"}} 47 | 48 | You can read more about how imports work and how to structure your code in the [Application Structure article](http://guide.meteor.com/structure.html) of the Meteor Guide. 49 | 50 | In our browser, the app should look pretty much like this: 51 | 52 | > #### Todo List 53 | > - This is task 1 54 | > - This is task 2 55 | > - This is task 3 56 | 57 | Now let's find out what all these bits of code are doing! 58 | 59 | ### HTML files in Meteor define templates 60 | 61 | Meteor parses all of the regular .HTML files in your app folder and identifies three top-level tags: **<head>**, **<body>**, and **<template>**. 62 | 63 | Everything inside any <head> tags is added to the `head` section of the HTML sent to the client, and everything inside <body> tags is added to the `body` section, just like in a regular HTML file. 64 | 65 | The [angular-meteor package](http://angular-meteor.com/) parses all of the `html` files in your app folder and puts them in Angular's template cache with the id of their full path. 66 | 67 | So, for example, when a file named `my-angular-template.html` is placed in the `client` folder, it will be available for `ng-include` or `ui-router` with the name `client/my-angular-template.html`. 68 | 69 | ### Adding logic and data to templates 70 | 71 | All of the code in your `html` files is compiled with Angular. Angular binds the data into our templates just like any other Angular app. 72 | 73 | In the next step, we will see how we can use the $meteor service to bind our scope data to a database collection. 74 | 75 | {{> DiffBox tutorialName="simple-todos-angular" step="2.8"}} 76 | 77 | {{/template}} 78 | -------------------------------------------------------------------------------- /content/angular/step03.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step03"}} 2 | 3 | # Storing tasks in a collection 4 | 5 | {{> step03CollectionsIntro tutorialName="simple-todos"}} 6 | 7 | Let's update our client-side JavaScript code to get our tasks from a collection instead of a static array: 8 | 9 | {{> DiffBox tutorialName="simple-todos-angular" step="3.3"}} 10 | 11 | When you make these changes to the code, you'll notice that the tasks that used to be in the todo list have disappeared. That's because our database is currently empty — we need to insert some tasks! 12 | 13 | {{> step03InsertingTasksFromConsole}} 14 | 15 | {{/template}} 16 | -------------------------------------------------------------------------------- /content/angular/step04.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step04"}} 2 | 3 | # Adding tasks with a form 4 | 5 | In this step, we'll add an input field for users to add tasks to the list. 6 | 7 | First, let's add a form to our HTML: 8 | 9 | {{> DiffBox tutorialName="simple-todos-angular" step="4.1"}} 10 | 11 | Here's the JavaScript code we need to add to listen to the `submit` event on the form: 12 | 13 | {{> DiffBox tutorialName="simple-todos-angular" step="4.2"}} 14 | 15 | Now your app has a new input field. To add a task, just type into the input field and hit enter. If you open a new browser window and open the app again, you'll see that the list is automatically synchronized between all clients. 16 | 17 | ### Attaching events to templates 18 | 19 | As you can see, this is just a regular Angular application. 20 | 21 | In our case above, we are listening to the `submit` event on our form to call the `addTask` controller function. 22 | 23 | ### Inserting into a collection 24 | 25 | Inside our controller function, we are adding a task to the `tasks` collection by simply calling `Tasks.insert()`. We can assign any properties to the task object, such as the time created, since we don't ever have to define a schema for the collection. 26 | 27 | Being able to insert anything into the database from the client isn't very secure, but it's okay for now. In step 9 we'll learn how we can make our app secure and restrict how data is inserted into the database. 28 | 29 | ### Sorting our tasks 30 | 31 | Currently, our code displays all new tasks at the bottom of the list. That's not very good for a task list, because we want to see the newest tasks first. 32 | 33 | We can solve this by sorting the results using the `createdAt` field that is automatically added by our new code. 34 | Until now you probably used Angular sort filter to do so. you can still use that here, but we are going to use a different method because it is better for real world use cases. 35 | 36 | Replace the `Tasks` collection variable with a function inside our `tasks` helper. 37 | The function will return the result of calling the `find` function with the `sort` parameter on our `Tasks` collection, like that: 38 | 39 | {{> DiffBox tutorialName="simple-todos-angular" step="4.3"}} 40 | 41 | To better understand the difference between using the sort filter and the collection options, check out the advanced tutorial about [search, sort and pagination](https://angular-meteor.com/tutorials/socially/angular1/search-sort-pagination-and-reactive-vars). 42 | 43 | In the next step, we'll add some very important todo list functions: checking off and deleting tasks. 44 | {{/template}} 45 | -------------------------------------------------------------------------------- /content/angular/step05.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step05"}} 2 | 3 | # Checking off and deleting tasks 4 | 5 | Until now, we have only interacted with a collection by inserting documents. Now, we will learn how to update and remove them. 6 | 7 | Let's add two elements to our `todosList` component, a checkbox and a delete button: 8 | 9 | {{> DiffBox tutorialName="simple-todos-angular" step="5.1"}} 10 | 11 | {{> DiffBox tutorialName="simple-todos-angular" step="5.2"}} 12 | 13 | ### Update 14 | 15 | As you can see we added two directives. 16 | 17 | To watch `checked` state of each task. 18 | 19 | ```html 20 | ng-checked="task.checked" 21 | ``` 22 | 23 | And to change current state by calling `setChecked` method of the controller. 24 | 25 | ```html 26 | ng-click="$ctrl.setChecked(task)" 27 | ``` 28 | 29 | ### Delete 30 | 31 | We can delete task by clicking `removeTask` method. 32 | 33 | ### Classes 34 | 35 | If you try checking off some tasks after adding all of the above code, you will see that checked off tasks have a line through them. 36 | 37 | Here we bind the checked state of a task to a class with `ng-class`: 38 | 39 | ```html 40 |
  • 41 | ``` 42 | 43 | With this code, if the `checked` property of a task is `true`, the `checked` class is added to our list item. Using this class, we can make checked-off tasks look different in our CSS. 44 | {{/template}} 45 | -------------------------------------------------------------------------------- /content/angular/step06.md: -------------------------------------------------------------------------------- 1 | {{#template name="angularSpecialPart06"}} 2 | ### Setting up Angular for mobile 3 | 4 | Angular needs the main document to be ready so it can bootstrap, but different devices have different events for `ready`. 5 | 6 | To solve this, we need to change the way we bootstrap our Angular app. 7 | Remove the current bootstrap by removing `ng-app` from the `` tag: 8 | 9 | {{> DiffBox tutorialName="simple-todos-angular" step="6.1"}} 10 | 11 | Then add the following code right after `Meteor.isClient`: 12 | 13 | {{> DiffBox tutorialName="simple-todos-angular" step="6.2"}} 14 | 15 | {{/template}} 16 | 17 | {{#template name="angular-step06"}} 18 | {{> sharedStep06 specialContent="angularSpecialPart06"}} 19 | {{/template}} 20 | -------------------------------------------------------------------------------- /content/angular/step07.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step07"}} 2 | 3 | # Filtering collections 4 | 5 | In this step, we'll add a client-side data filtering feature to our app, so that users can check a box to see only incomplete tasks. 6 | 7 | We're going to learn how to use Mongo's filtering API. 8 | 9 | First, we need to add a checkbox to our HTML: 10 | 11 | {{> DiffBox tutorialName="simple-todos-angular" step="7.1"}} 12 | 13 | This checkbox binds to the controller's `hideCompleted` variable. 14 | 15 | Now, we need to update our `tasks` query each time `hideCompleted` changes. 16 | 17 | ### Filtering collection syntax 18 | 19 | The query that returns all tasks (the current query looks like that: 20 | 21 | ```js 22 | Tasks.find({}, { sort: { createdAt: -1 } }) 23 | ``` 24 | 25 | and the query to return only the not completed todos looks like that: 26 | 27 | ```js 28 | Tasks.find({ checked: {$ne: true} }, { sort: { createdAt: -1 } }) 29 | ``` 30 | 31 | ### Add reactivity 32 | 33 | We somehow want to update the query every time `hideComplete` changes. 34 | 35 | Let's implement some reactivity into `tasks` helper: 36 | 37 | {{> DiffBox tutorialName="simple-todos-angular" step="7.2"}} 38 | 39 | As you can see, we used `getReactively()` method. You can read more about it in the following chapter. 40 | 41 | ### Connecting Angular bindings to Meteor's reactivity 42 | 43 | To make Meteor understand Angular bindings and the other way around, we use [$scope.getReactively](https://angular-meteor.com/api/angular-meteor/1.3.11/get-reactively) function that turns Angular 44 | scope variables into [Meteor reactive variables](http://docs.meteor.com/#/full/reactivevar_pkg). 45 | 46 | Now if you check the box, the task list will only show tasks that haven't been completed. 47 | 48 | > To learn more about the [getReactively](https://angular-meteor.com/api/angular-meteor/1.3.11/get-reactively) feature 49 | > you can try the [advanced tutorial](http://angular-meteor.com/tutorial/step_12). 50 | 51 | ### One more feature: Showing a count of incomplete tasks 52 | 53 | Now that we have written a query that filters out completed tasks, we can use the same query to display a count of the tasks that haven't been checked off. To do this we need to add a scope function and change one line of the HTML. 54 | 55 | {{> DiffBox tutorialName="simple-todos-angular" step="7.3"}} 56 | 57 | {{> DiffBox tutorialName="simple-todos-angular" step="7.4"}} 58 | 59 | {{/template}} 60 | -------------------------------------------------------------------------------- /content/angular/step08.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step08"}} 2 | 3 | # Adding user accounts 4 | 5 | Meteor comes with an accounts system and a drop-in login user interface that lets you add multi-user functionality to your app in minutes. 6 | 7 | To enable the accounts system and UI, we need to add the relevant packages. In your app directory, run the following command: 8 | 9 | ```bash 10 | meteor add accounts-password dotansimha:accounts-ui-angular 11 | ``` 12 | 13 | `accounts-password` is a package that includes all the logic for password based authentication. 14 | 15 | `dotansimha:accounts-ui-angular` includes the `` directive that contains all the HTML and CSS we need for user authentication forms. 16 | 17 | Now let's add dependency to `account.ui` module in our module definition: 18 | 19 | {{> DiffBox tutorialName="simple-todos-angular" step="8.2"}} 20 | 21 | In the HTML, right under the checkbox, include the following code to add a login dropdown: 22 | 23 | {{> DiffBox tutorialName="simple-todos-angular" step="8.3"}} 24 | 25 | Then, in your JavaScript, add the following code to configure the accounts UI to use usernames instead of email addresses: 26 | 27 | {{> DiffBox tutorialName="simple-todos-angular" step="8.4"}} 28 | 29 | We'll need to import that configuration from our *client-side JavaScript entrypoint* also: 30 | 31 | {{> DiffBox tutorialName="simple-todos-angular" step="8.5"}} 32 | 33 | Now users can create accounts and log into your app! This is very nice, but logging in and out isn't very useful yet. Let's add two functions: 34 | 35 | 1. Only display the new task input field to logged in users 36 | 2. Show which user created each task 37 | 38 | To do this, we will add two new fields to the `tasks` collection: 39 | 40 | 1. `owner` - the `_id` of the user that created the task. 41 | 2. `username` - the `username` of the user that created the task. We will save the username directly in the task object so that we don't have to look up the user every time we display the task. 42 | 43 | First, let's add some code to save these fields into the `addTask` function: 44 | 45 | {{> DiffBox tutorialName="simple-todos-angular" step="8.6"}} 46 | 47 | We want somehow to know about logged in user. We can use helper function! Let's call it `currentUser`: 48 | 49 | {{> DiffBox tutorialName="simple-todos-angular" step="8.7"}} 50 | 51 | Then, in our HTML, add an `ng-show` directive to only show the form when there is a logged in user: 52 | 53 | {{> DiffBox tutorialName="simple-todos-angular" step="8.8"}} 54 | 55 | Finally, add a statement to display the `username` field on each task right before the text: 56 | 57 | {{> DiffBox tutorialName="simple-todos-angular" step="8.9"}} 58 | 59 | Now, users can log in and we can track which user each task belongs to. Let's look at some of the concepts we just discovered in more detail. 60 | 61 | ### Automatic accounts UI 62 | 63 | If our app has the `accounts-ui` package, all we have to do to add a login dropdown is include the `loginButtons` template with [meteor-include](http://angular-meteor.com/api/meteor-include) directive. 64 | This dropdown detects which login methods have been added to the app and displays the appropriate controls. In our case, the only enabled login method is `accounts-password`, so the dropdown displays a password field. If you are adventurous, you can add the `accounts-facebook` package to enable Facebook login in your app - the Facebook button will automatically appear in the dropdown. 65 | 66 | ### Getting information about the logged-in user 67 | 68 | You can use `Meteor.userId()` to get the current user's `_id`, or `Meteor.user()` to get the whole user document. 69 | 70 | ### Custom templates 71 | 72 | You can choose not to use the `accounts-ui` package template and create your own Angular login templates. 73 | You can read more about it in the [chapter about angular-material](http://angular-meteor.com/tutorial/step_18) in the advanced tutorial. 74 | 75 | In the next step, we will learn how to make our app more secure by doing all of our data validation on the server instead of the client. 76 | {{/template}} 77 | -------------------------------------------------------------------------------- /content/angular/step09.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step09"}} 2 | 3 | # Security with methods 4 | 5 | Before this step, any user of the app could edit any part of the database. This might be okay for very small internal apps or demos, but any real application needs to control permissions for its data. In Meteor, the best way to do this is by declaring _methods_. Instead of the client code directly calling `insert`, `update`, and `remove`, it will instead call methods that will check if the user is authorized to complete the action and then make any changes to the database on the client's behalf. 6 | 7 | ### Removing `insecure` 8 | 9 | Every newly created Meteor project has the `insecure` package added by default. This is the package that allows us to edit the database from the client. It's useful when prototyping, but now we are taking off the training wheels. To remove this package, go to your app directory and run: 10 | 11 | ```bash 12 | meteor remove insecure 13 | ``` 14 | 15 | If you try to use the app after removing this package, you will notice that none of the inputs or buttons work anymore. This is because all client-side database permissions have been revoked. Now we need to rewrite some parts of our app to use methods. 16 | 17 | ### Defining methods 18 | 19 | First, we need to define some methods. We need one method for each database operation we want to perform on the client. Methods should be defined in code that is executed on the client and the server - we will discuss this a bit later in the section titled _Optimistic UI_. 20 | 21 | {{> DiffBox tutorialName="simple-todos-angular" step="9.2"}} 22 | 23 | Now that we have defined our methods, we need to update the places we were operating on the collection to use the methods instead. 24 | 25 | {{> DiffBox tutorialName="simple-todos-angular" step="9.3"}} 26 | 27 | {{> DiffBox tutorialName="simple-todos-angular" step="9.4"}} 28 | 29 | Now all of our inputs and buttons will start working again. What did we gain from all of this work? 30 | 31 | 1. When we insert tasks into the database, we can now securely verify that the user is logged in, that the `createdAt` field is correct, and that the `owner` and `username` fields are correct and the user isn't impersonating anyone. 32 | 2. We can add extra validation logic to `setChecked` and `removeTask` in later steps when users can make tasks private. 33 | 3. Our client code is now more separated from our database logic. Instead of a lot of stuff happening inside our event handlers, we now have methods that can be called from anywhere. 34 | 35 | {{> step09OptimisticUI}} 36 | 37 | {{/template}} 38 | -------------------------------------------------------------------------------- /content/angular/step10.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step10"}} 2 | 3 | # Filtering data with publish and subscribe 4 | 5 | Now that we have moved all of our app's sensitive code into methods, we need to learn about the other half of Meteor's security story. Until now, we have worked assuming the entire database is present on the client, meaning if we call `Tasks.find()` we will get every task in the collection. That's not good if users of our application want to store privacy-sensitive data. We need a way of controlling which data Meteor sends to the client-side database. 6 | 7 | Just like with `insecure` in the last step, all new Meteor apps start with the `autopublish` package. Let's remove it and see what happens: 8 | 9 | ```bash 10 | meteor remove autopublish 11 | ``` 12 | 13 | When the app refreshes, the task list will be empty. Without the `autopublish` package, we will have to specify explicitly what the server sends to the client. The functions in Meteor that do this are `Meteor.publish` and [$scope.subscribe](http://angular-meteor.com/api/subscribe). 14 | 15 | Let's add them now. 16 | 17 | {{> DiffBox tutorialName="simple-todos-angular" step="10.2"}} 18 | 19 | {{> DiffBox tutorialName="simple-todos-angular" step="10.3"}} 20 | 21 | Once you have added this code, all of the tasks will reappear. 22 | 23 | Calling `Meteor.publish` on the server registers a _publication_ named `"tasks"`. When [$scope.subscribe](http://angular-meteor.com/api/subscribe) is called on the client with the publication name, the client _subscribes_ to all the data from that publication, which in this case is all of the tasks in the database. To truly see the power of the publish/subscribe model, let's implement a feature that allows users to mark tasks as "private" so that no other users can see them. 24 | 25 | ### Implementing private tasks 26 | 27 | First, let's add another property to tasks called "private" and a button for users to mark a task as private. This button should only show up for the owner of a task. It will display the current state of the item. 28 | 29 | {{> DiffBox tutorialName="simple-todos-angular" step="10.4"}} 30 | 31 | {{> DiffBox tutorialName="simple-todos-angular" step="10.5"}} 32 | 33 | We need to modify our JavaScript code in two places: 34 | 35 | {{> DiffBox tutorialName="simple-todos-angular" step="10.6"}} 36 | 37 | {{> DiffBox tutorialName="simple-todos-angular" step="10.7"}} 38 | 39 | ### Selectively publishing tasks based on privacy status 40 | 41 | Now that we have a way of setting which tasks are private, we should modify our 42 | publication function to only send the tasks that a user is authorized to see: 43 | 44 | {{> DiffBox tutorialName="simple-todos-angular" step="10.8"}} 45 | 46 | To test that this functionality works, you can use your browser's private browsing mode to log in as a different user. Put the two windows side by side and mark a task private to confirm that the other user can't see it. Now make it public again and it will reappear! 47 | 48 | ### Extra method security 49 | 50 | In order to finish up our private task feature, we need to add checks to our `deleteTask` and `setChecked` methods to make sure only the task owner can delete or check off a private task: 51 | 52 | {{> DiffBox tutorialName="simple-todos-angular" step="10.9"}} 53 | 54 | > Notice that with this code anyone can delete any public task. With some small modifications to the code, you should be able to make it so that only the owner can delete their tasks. 55 | 56 | We're done with our private task feature! Now our app is secure from attackers trying to view or modify someone's private tasks. 57 | 58 | {{/template}} 59 | -------------------------------------------------------------------------------- /content/angular/step11.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step11"}} 2 | 3 | # Testing 4 | 5 | Now we've created a few features for our application, let's add a test to ensure that we don't regress and that it works the way we expect. 6 | 7 | We'll write a test that exercises one of our Methods (which form the "write" part of our app's API), and verifies it works correctly. 8 | 9 | To do so, we'll add a [test driver](http://guide.meteor.com/testing.html#test-driver) for the [Mocha](https://mochajs.org) JavaScript test framework: 10 | 11 | ```bash 12 | meteor add practicalmeteor:mocha 13 | ``` 14 | 15 | > **New in Meteor 1.7+**: While the `meteor test ...` command does still work as specified below, the release of Meteor 1.7 includes a new feature which allows you to specify the location of the test module in `package.json` with a property named `"testModule"` within the `"meteor"` object, which you can read more about [in the changelog](https://docs.meteor.com/changelog.html#v1720180528). In order to get the expected behavior, such that the `meteor test ...` command only uses files whose names match the format `*.test[s].*` or `*.spec[s].*`, you should either remove the line `"testModule": "tests/main.js"` from your `package.json` file, or change it to an appropriate value, before running `meteor test ...`. 16 | 17 | We can now run our app in "test mode" by calling out a special command and specifying to use the driver (you'll need to stop the regular app from running, or specify an alternate port with `--port XYZ`): 18 | 19 | ```bash 20 | meteor test --driver-package practicalmeteor:mocha 21 | ``` 22 | 23 | If you do so, you should see an empty test results page in your browser window. 24 | 25 | Let's add a simple test (that doesn't do anything yet): 26 | 27 | {{> DiffBox tutorialName="simple-todos-angular" step="11.2"}} 28 | 29 | In any test we need to ensure the database is in the state we expect before beginning. We can use Mocha's `beforeEach` construct to do that easily: 30 | 31 | {{> DiffBox tutorialName="simple-todos-angular" step="11.3"}} 32 | 33 | Here we create a single task that's associated with a random `userId` that'll be different for each test run. 34 | 35 | Now we can write the test to call the `task.remove` method "as" that user and verify the task is deleted: 36 | 37 | {{> DiffBox tutorialName="simple-todos-angular" step="11.4"}} 38 | 39 | There's a lot more you can do in a Meteor test! You can read more about it in the Meteor Guide [article on testing](http://guide.meteor.com/testing.html). 40 | 41 | # Testing the component 42 | 43 | We need one thing to start writing tests of our angular component. 44 | 45 | ```bash 46 | meteor npm install --save-dev angular-mocks 47 | ``` 48 | 49 | Let's prepare some background for tests: 50 | 51 | {{> DiffBox tutorialName="simple-todos-angular" step="11.6"}} 52 | 53 | Add test to check incomplete tasks count: 54 | 55 | {{> DiffBox tutorialName="simple-todos-angular" step="11.7"}} 56 | 57 | Now we can move to testing our controller: 58 | 59 | {{> DiffBox tutorialName="simple-todos-angular" step="11.8"}} 60 | 61 | As you remember, we use meteor method to insert new tasks. Let's check if it works: 62 | 63 | {{> DiffBox tutorialName="simple-todos-angular" step="11.9"}} 64 | 65 | It should also reseting form: 66 | 67 | {{> DiffBox tutorialName="simple-todos-angular" step="11.10"}} 68 | 69 | {{/template}} 70 | -------------------------------------------------------------------------------- /content/angular/step12.md: -------------------------------------------------------------------------------- 1 | {{#template name="angular-step12"}} 2 | 3 | # What's next? 4 | 5 | Congratulations on your newly built Meteor app! 6 | 7 | Your app currently supports collaborating on a single todo list. To see how you 8 | could add more functionality, check out the [full angular-meteor tutorial](http://angular-meteor.com/). 9 | 10 | {{> step12NextSteps}} 11 | 12 | {{/template}} 13 | -------------------------------------------------------------------------------- /content/blaze/metadata.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | DiffBox.registerTutorial("simple-todos", { 3 | gitHubRepoName: "meteor/simple-todos", 4 | patchFilename: "generated/blaze.multi.patch" 5 | }); 6 | } 7 | 8 | TutorialRegistry.registerTutorial("blaze", { 9 | title: "Simple Todos", 10 | subtitle: "Build a simple todo list app with Meteor", 11 | tutorialSourceLink: "github.com/meteor/tutorials/content/blaze", 12 | steps: [ 13 | { 14 | title: 'Creating an app', 15 | slug: "creating-an-app", 16 | template: 'sharedStep01' 17 | }, 18 | { 19 | title: 'Templates', 20 | slug: "templates", 21 | template: 'blaze-step02' 22 | }, 23 | { 24 | title: 'Collections', 25 | slug: "collections", 26 | template: 'blaze-step03' 27 | }, 28 | { 29 | title: 'Forms and events', 30 | slug: "forms-and-events", 31 | template: 'blaze-step04' 32 | }, 33 | { 34 | title: 'Update and remove', 35 | slug: "update-and-remove", 36 | template: 'blaze-step05' 37 | }, 38 | { 39 | title: 'Running on mobile', 40 | slug: "running-on-mobile", 41 | template: 'sharedStep06' 42 | }, 43 | { 44 | title: 'Temporary UI state', 45 | slug: "temporary-ui-state", 46 | template: 'blaze-step07' 47 | }, 48 | { 49 | title: 'Adding user accounts', 50 | slug: "adding-user-accounts", 51 | template: 'blaze-step08' 52 | }, 53 | { 54 | title: 'Security with methods', 55 | slug: "security-with-methods", 56 | template: 'blaze-step09' 57 | }, 58 | { 59 | title: 'Publish and subscribe', 60 | slug: "publish-and-subscribe", 61 | template: 'blaze-step10' 62 | }, 63 | { 64 | title: 'Testing', 65 | slug: "testing", 66 | template: 'blaze-step11' 67 | }, 68 | { 69 | title: 'Next steps', 70 | slug: "next-steps", 71 | template: 'blaze-step12' 72 | } 73 | ] 74 | }); 75 | -------------------------------------------------------------------------------- /content/blaze/step02.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step02"}} 2 | # Defining views with templates 3 | 4 | To start working on our todo list app, let's replace the code of the default starter app with the code below. Then we'll talk about what it does. 5 | 6 | First, let's remove the body from our HTML entry-point (leaving just the `` tag): 7 | 8 | {{> DiffBox tutorialName="simple-todos" step="2.1"}} 9 | 10 | Create a new directory with the name `imports` inside `simple-todos` folder. Then we create some new files in the `imports/` directory: 11 | 12 | {{> DiffBox tutorialName="simple-todos" step="2.2"}} 13 | 14 | {{> DiffBox tutorialName="simple-todos" step="2.3"}} 15 | 16 | Inside our front-end JavaScript entry-point file, `client/main.js`, we'll _remove_ the rest of the code and import `imports/ui/body.js`: 17 | 18 | {{> DiffBox tutorialName="simple-todos" step="2.4"}} 19 | 20 | You can read more about how imports work and how to structure your code in the [Application Structure article](http://guide.meteor.com/structure.html) of the Meteor Guide. 21 | 22 | In our browser, the app will now look much like this: 23 | 24 | > #### Todo List 25 | > - This is task 1 26 | > - This is task 2 27 | > - This is task 3 28 | 29 | Now let's find out what all these bits of code are doing! 30 | 31 | ### HTML files in Meteor define templates 32 | 33 | Meteor parses HTML files and identifies three top-level tags: **<head>**, **<body>**, and **<template>**. 34 | 35 | Everything inside any <head> tags is added to the `head` section of the HTML sent to the client, and everything inside <body> tags is added to the `body` section, just like in a regular HTML file. 36 | 37 | Everything inside <template> tags is compiled into Meteor _templates_, which can be included inside HTML with `{{dstache}}> templateName}}` or referenced in your JavaScript with `Template.templateName`. 38 | 39 | Also, the `body` section can be referenced in your JavaScript with `Template.body`. Think of it as a special "parent" template, that can include the other child templates. 40 | 41 | ### Adding logic and data to templates 42 | 43 | All of the code in your HTML files is compiled with [Meteor's Spacebars compiler](http://blazejs.org/api/spacebars.html). Spacebars uses statements surrounded by double curly braces such as `{{dstache}}#each}}` and `{{dstache}}#if}}` to let you add logic and data to your views. 44 | 45 | You can pass data into templates from your JavaScript code by defining _helpers_. In the code above, we defined a helper called `tasks` on `Template.body` that returns an array. Inside the body tag of the HTML, we can use `{{dstache}}#each tasks}}` to iterate over the array and insert a `task` template for each value. Inside the `#each` block, we can display the `text` property of each array item using `{{dstache}}text}}`. 46 | 47 | In the next step, we will see how we can use helpers to make our templates display dynamic data from a database collection. 48 | 49 | ### Styling with CSS 50 | 51 | To have a better experience while following the tutorial we suggest you copy-paste the following CSS code into your app: 52 | 53 | {{> DiffBox tutorialName="simple-todos" step="2.5"}} 54 | 55 | {{/template}} 56 | -------------------------------------------------------------------------------- /content/blaze/step03.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step03"}} 2 | 3 | # Storing tasks in a collection 4 | 5 | {{> step03CollectionsIntro tutorialName="simple-todos"}} 6 | 7 | Let's update our client-side JavaScript code to get our tasks from a collection instead of a static array: 8 | 9 | {{> DiffBox tutorialName="simple-todos" step="3.3"}} 10 | 11 | When you make these changes to the code, you'll notice that the tasks that used to be in the todo list have disappeared. That's because our database is currently empty — we need to insert some tasks! 12 | 13 | {{> step03InsertingTasksFromConsole}} 14 | 15 | {{/template}} 16 | -------------------------------------------------------------------------------- /content/blaze/step04.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step04"}} 2 | 3 | # Adding tasks with a form 4 | 5 | In this step, we'll add an input field for users to add tasks to the list. 6 | 7 | First, let's add a form to our HTML: 8 | 9 | {{> DiffBox tutorialName="simple-todos" step="4.1"}} 10 | 11 | Here's the JavaScript code we need to add to listen to the `submit` event on the form: 12 | 13 | {{> DiffBox tutorialName="simple-todos" step="4.2"}} 14 | 15 | Now your app has a new input field. To add a task, just type into the input field and hit enter. If you open a new browser window and open the app again, you'll see that the list is automatically synchronized between all clients. 16 | 17 | ### Attaching events to templates 18 | 19 | Event listeners are added to templates in much the same way as helpers are: by calling `Template.templateName.events(...)` with a dictionary. The keys describe the event to listen for, and the values are _event handlers_ that are called when the event happens. 20 | 21 | In our case above, we are listening to the `submit` event on any element that matches the CSS selector `.new-task`. When this event is triggered by the user pressing enter inside the input field, our event handler function is called. 22 | 23 | The event handler gets an argument called `event` that has some information about the event that was triggered. In this case `event.target` is our form element, and we can get the value of our input with `event.target.text.value`. You can see all of the other properties of the `event` object by adding a `console.log(event)` and inspecting the object in your browser console. 24 | 25 | Finally, in the last line of the event handler, we clear the input to prepare for another new task. 26 | 27 | ### Inserting into a collection 28 | 29 | Inside the event handler, we are adding a task to the `tasks` collection by calling `Tasks.insert()`. We can assign any properties to the task object, such as the time created, since we don't ever have to define a schema for the collection. 30 | 31 | Being able to insert anything into the database from the client isn't very secure, but it's okay for now. In step 10 we'll learn how we can make our app secure and restrict how data is inserted into the database. 32 | 33 | ### Sorting our tasks 34 | 35 | Currently, our code displays all new tasks at the bottom of the list. That's not very good for a task list, because we want to see the newest tasks first. 36 | 37 | We can solve this by sorting the results using the `createdAt` field that is automatically added by our new code. Just add a sort option to the `find` call inside the `tasks` helper: 38 | 39 | {{> DiffBox tutorialName="simple-todos" step="4.3"}} 40 | 41 | In the next step, we'll add some very important todo list functions: checking off and deleting tasks. 42 | {{/template}} 43 | -------------------------------------------------------------------------------- /content/blaze/step05.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step05"}} 2 | 3 | # Checking off and deleting tasks 4 | 5 | Until now, we have only interacted with a collection by inserting documents. Now, we will learn how to update and remove them. 6 | 7 | Let's work on our `task` template---starting by moving it to its own file, with some new features, a checkbox and a delete button: 8 | 9 | {{> DiffBox tutorialName="simple-todos" step="5.1"}} 10 | 11 | **5.2** Remove the previous `task` template 12 | 13 | Since we've added a new `task` template here, we need to remove the old `task` template we had before. Open the `imports/ui/body.html` file and remove the entire `` section from the end. 14 | 15 | Now we've added UI elements, but they don't do anything yet! We should add some event handlers: 16 | 17 | {{> DiffBox tutorialName="simple-todos" step="5.3"}} 18 | 19 | The `body` template uses the `task` template, so we need to import it as well: 20 | 21 | {{> DiffBox tutorialName="simple-todos" step="5.4"}} 22 | 23 | 24 | ### Getting data in event handlers 25 | 26 | Inside the event handlers, `this` refers to an individual task object. In a collection, every inserted document has a unique `_id` field that can be used to refer to that specific document. We can get the `_id` of the current task with `this._id`. Once we have the `_id`, we can use `update` and `remove` to modify the relevant task. 27 | 28 | ### Update 29 | 30 | The `update` function on a collection takes two arguments. The first is a selector that identifies a subset of the collection, and the second is an update parameter that specifies what should be done to the matched objects. 31 | 32 | In this case, the selector is just the `_id` of the relevant task. The update parameter uses `$set` to toggle the `checked` field, which will represent whether the task has been completed. 33 | 34 | ### Remove 35 | 36 | The `remove` function takes one argument, a selector that determines which item to remove from the collection. 37 | 38 | ### Using object properties or helpers to add/remove classes 39 | 40 | If you try checking off some tasks after adding all of the above code, you will see that checked off tasks have a line through them. This is enabled by the following snippet: 41 | 42 | ```html 43 |
  • 44 | ``` 45 | 46 | With this code, if the `checked` property of a task is `true`, the `checked` class is added to our list item. Using this class, we can make checked-off tasks look different in our CSS. 47 | {{/template}} 48 | -------------------------------------------------------------------------------- /content/blaze/step07.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step07"}} 2 | 3 | # Storing temporary UI state in a Reactive Dictionary 4 | 5 | In this step, we'll add a client-side data filtering feature to our app, so that users can check a box to only see incomplete tasks. We're going to learn how to use a [`ReactiveDict`](https://atmospherejs.com/meteor/reactive-dict) to store temporary reactive state on the client. A `ReactiveDict` is like a normal JS object with keys and values, but with built-in reactivity. 6 | 7 | First, we need to add a checkbox to our HTML: 8 | 9 | {{> DiffBox tutorialName="simple-todos" step="7.1"}} 10 | 11 | Now we need to add the `reactive-dict` package: 12 | 13 | ```bash 14 | meteor add reactive-dict 15 | ``` 16 | 17 | Then we need to set up a new `ReactiveDict` and attach it to the body template instance (as this is where we'll store the checkbox's state) when it is first created: 18 | 19 | {{> DiffBox tutorialName="simple-todos" step="7.3"}} 20 | 21 | Then, we need an event handler to update the `ReactiveDict` variable when the checkbox 22 | is checked or unchecked. An event handler takes two arguments, the second of which is the same template instance which was `this` in the `onCreated` callback: 23 | 24 | {{> DiffBox tutorialName="simple-todos" step="7.4"}} 25 | 26 | Now, we need to update `Template.body.helpers`. The code below has a new if 27 | block to filter the tasks if the checkbox is checked: 28 | 29 | {{> DiffBox tutorialName="simple-todos" step="7.5"}} 30 | 31 | Now if you check the box, the task list will only show tasks that haven't been completed. 32 | 33 | ### ReactiveDicts are reactive data stores for the client 34 | 35 | Until now, we have stored all of our state in collections, and the view updated automatically when we modified the data inside these collections. This is because Mongo.Collection is recognized by Meteor as a _reactive data source_, meaning Meteor knows when the data inside has changed. `ReactiveDict` is the same way, but is not synced with the server like collections are. This makes a `ReactiveDict` a convenient place to store temporary UI state like the checkbox above. Just like with collections, we don't have to write any extra code for the template to update when the `ReactiveDict` variable changes — just calling `instance.state.get(...)` inside the helper is enough. 36 | 37 | You can read more about patterns for writing components in the [Blaze article](http://guide.meteor.com/blaze.html) of the Meteor Guide. 38 | 39 | 40 | ### One more feature: Showing a count of incomplete tasks 41 | 42 | Now that we have written a query that filters out completed tasks, we can use the same query to display a count of the tasks that haven't been checked off. To do this we need to add a helper and change one line of the HTML. 43 | 44 | {{> DiffBox tutorialName="simple-todos" step="7.6"}} 45 | 46 | {{> DiffBox tutorialName="simple-todos" step="7.7"}} 47 | 48 | {{/template}} 49 | -------------------------------------------------------------------------------- /content/blaze/step08.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step08"}} 2 | 3 | # Adding user accounts 4 | 5 | Meteor comes with an accounts system and a drop-in login user interface that lets you add multi-user functionality to your app in minutes. 6 | 7 | To enable the accounts system and UI, we need to add the relevant packages. In your app directory, run the following command: 8 | 9 | ```bash 10 | meteor add accounts-ui accounts-password 11 | ``` 12 | 13 | In the HTML, right under the checkbox, include the following code to add a login dropdown: 14 | 15 | {{> DiffBox tutorialName="simple-todos" step="8.2"}} 16 | 17 | Then, in your JavaScript, add the following code to configure the accounts UI to use usernames instead of email addresses: 18 | 19 | {{> DiffBox tutorialName="simple-todos" step="8.3"}} 20 | 21 | We'll need to import that configuration from our *client-side JavaScript entrypoint* also: 22 | 23 | {{> DiffBox tutorialName="simple-todos" step="8.4"}} 24 | 25 | Now users can create accounts and log into your app! This is very nice, but logging in and out isn't very useful yet. Let's add two functions: 26 | 27 | 1. Only display the new task input field to logged in users 28 | 2. Show which user created each task 29 | 30 | To do this, we will add two new fields to the `tasks` collection: 31 | 32 | 1. `owner` - the `_id` of the user that created the task. 33 | 2. `username` - the `username` of the user that created the task. We will save the username directly in the task object so that we don't have to look up the user every time we display the task. 34 | 35 | First, let's add some code to save these fields into the `submit .new-task` event handler: 36 | 37 | {{> DiffBox tutorialName="simple-todos" step="8.5"}} 38 | 39 | Then, in our HTML, add an `#if` block helper to only show the form when there is a logged in user: 40 | 41 | {{> DiffBox tutorialName="simple-todos" step="8.6"}} 42 | 43 | Finally, add a Spacebars statement to display the `username` field on each task right before the text: 44 | 45 | {{> DiffBox tutorialName="simple-todos" step="8.7"}} 46 | 47 | Now, users can log in and we can track which user each task belongs to. Let's look at some of the concepts we just discovered in more detail. 48 | 49 | ### Automatic accounts UI 50 | 51 | If our app has the `accounts-ui` package, all we have to do to add a login dropdown is include the `loginButtons` template with `{{dstache}}> loginButtons}}`. This dropdown detects which login methods have been added to the app and displays the appropriate controls. In our case, the only enabled login method is `accounts-password`, so the dropdown displays a password field. If you are adventurous, you can add the `accounts-facebook` package to enable Facebook login in your app - the Facebook button will automatically appear in the dropdown. 52 | 53 | ### Getting information about the logged-in user 54 | 55 | In your HTML, you can use the built-in `{{dstache}}currentUser}}` helper to check if a user is logged in and get information about them. For example, `{{dstache}}currentUser.username}}` will display the logged in user's username. 56 | 57 | In your JavaScript code, you can use `Meteor.userId()` to get the current user's `_id`, or `Meteor.user()` to get the whole user document. 58 | 59 | In the next step, we will learn how to make our app more secure by doing all of our data validation on the server instead of the client. 60 | {{/template}} 61 | 62 | You can read more about using accounts in Meteor in the [Accounts article](http://guide.meteor.com/accounts.html) of the Meteor Guide. 63 | 64 | -------------------------------------------------------------------------------- /content/blaze/step09.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step09"}} 2 | 3 | # Security with methods 4 | 5 | Before this step, any user of the app could edit any part of the database. This might be okay for very small internal apps or demos, but any real application needs to control permissions for its data. In Meteor, the best way to do this is by declaring _methods_. Instead of the client code directly calling `insert`, `update`, and `remove`, it will instead call methods that will check if the user is authorized to complete the action and then make any changes to the database on the client's behalf. 6 | 7 | ### Removing `insecure` 8 | 9 | Every newly created Meteor project has the `insecure` package added by default. This is the package that allows us to edit the database from the client. It's useful when prototyping, but now we are taking off the training wheels. To remove this package, go to your app directory and run: 10 | 11 | ```bash 12 | meteor remove insecure 13 | ``` 14 | 15 | If you try to use the app after removing this package, you will notice that none of the inputs or buttons work anymore. This is because all client-side database permissions have been revoked. Now we need to rewrite some parts of our app to use methods. 16 | 17 | ### Defining methods 18 | 19 | First, we need to define some methods. We need one method for each database operation we want to perform on the client. Methods should be defined in code that is executed on the client and the server - we will discuss this a bit later in the section titled _Optimistic UI_. 20 | 21 | {{> DiffBox tutorialName="simple-todos" step="9.2"}} 22 | 23 | Now that we have defined our methods, we need to update the places we were operating on the collection to use the methods instead: 24 | 25 | {{> DiffBox tutorialName="simple-todos" step="9.3"}} 26 | 27 | We do the same on this file, but also remove the `import` of the `Tasks` collection since it's no longer necessary: 28 | 29 | {{> DiffBox tutorialName="simple-todos" step="9.4"}} 30 | 31 | Now all of our inputs and buttons will start working again. What did we gain from all of this work? 32 | 33 | 1. When we insert tasks into the database, we can now securely verify that the user is logged in, that the `createdAt` field is correct, and that the `owner` and `username` fields are correct and the user isn't impersonating anyone. 34 | 2. We can add extra validation logic to `setChecked` and `remove` in later steps when users can make tasks private. 35 | 3. Our client code is now more separated from our database logic. Instead of a lot of stuff happening inside our event handlers, we now have methods that can be called from anywhere. 36 | 37 | {{> step09OptimisticUI}} 38 | 39 | 40 | {{/template}} 41 | -------------------------------------------------------------------------------- /content/blaze/step10.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step10"}} 2 | 3 | # Filtering data with publish and subscribe 4 | 5 | Now that we have moved all of our app's sensitive code into methods, we need to learn about the other half of Meteor's security story. Until now, we have worked assuming the entire database is present on the client, meaning if we call `Tasks.find()` we will get every task in the collection. That's not good if users of our application want to store privacy-sensitive data. We need a way of controlling which data Meteor sends to the client-side database. 6 | 7 | Just like with `insecure` in the last step, all new Meteor apps start with the `autopublish` package. Let's remove it and see what happens: 8 | 9 | ```bash 10 | meteor remove autopublish 11 | ``` 12 | 13 | When the app refreshes, the task list will be empty. Without the `autopublish` package, we will have to specify explicitly what the server sends to the client. The functions in Meteor that do this are `Meteor.publish` and `Meteor.subscribe`. 14 | 15 | First lets add a publication for all tasks: 16 | 17 | {{> DiffBox tutorialName="simple-todos" step="10.2"}} 18 | 19 | And then let's subscribe to that publication when the `body` template is created: 20 | 21 | {{> DiffBox tutorialName="simple-todos" step="10.3"}} 22 | 23 | Once you have added this code, all of the tasks will reappear. 24 | 25 | Calling `Meteor.publish` on the server registers a _publication_ named `"tasks"`. When `Meteor.subscribe` is called on the client with the publication name, the client _subscribes_ to all the data from that publication, which in this case is all of the tasks in the database. To truly see the power of the publish/subscribe model, let's implement a feature that allows users to mark tasks as "private" so that no other users can see them. 26 | 27 | You can read more about publications in the [Data Loading article](http://guide.meteor.com/data-loading.html) of the Meteor Guide. 28 | 29 | ### Implementing private tasks 30 | 31 | First, let's add another property to tasks called "private" and a button for users to mark a task as private. This button should only show up for the owner of a task. It will display the current state of the item. 32 | 33 | {{> DiffBox tutorialName="simple-todos" step="10.4"}} 34 | 35 | Let's make sure our task has a special class if it is marked private: 36 | 37 | {{> DiffBox tutorialName="simple-todos" step="10.5"}} 38 | 39 | We need to modify our JavaScript code in three places: 40 | 41 | {{> DiffBox tutorialName="simple-todos" step="10.6"}} 42 | 43 | {{> DiffBox tutorialName="simple-todos" step="10.7"}} 44 | 45 | {{> DiffBox tutorialName="simple-todos" step="10.8"}} 46 | 47 | ### Selectively publishing tasks based on privacy status 48 | 49 | Now that we have a way of setting which tasks are private, we should modify our 50 | publication function to only send the tasks that a user is authorized to see: 51 | 52 | {{> DiffBox tutorialName="simple-todos" step="10.9"}} 53 | 54 | To test that this functionality works, you can use your browser's private browsing mode to log in as a different user. Put the two windows side by side and mark a task private to confirm that the other user can't see it. Now make it public again and it will reappear! 55 | 56 | ### Extra method security 57 | 58 | In order to finish up our private task feature, we need to add checks to our `deleteTask` and `setChecked` methods to make sure only the task owner can delete or check off a private task: 59 | 60 | {{> DiffBox tutorialName="simple-todos" step="10.10"}} 61 | 62 | > Notice that with this code anyone can delete any public task. With some small modifications to the code, you should be able to make it so that only the owner can delete their tasks. 63 | 64 | We're done with our private task feature! Now our app is secure from attackers trying to view or modify someone's private tasks. 65 | 66 | {{/template}} 67 | -------------------------------------------------------------------------------- /content/blaze/step11.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step11"}} 2 | 3 | # Testing 4 | 5 | Now that we've created a few features for our application, let's add a test to ensure that we don't regress and that it works the way we expect. 6 | 7 | We'll write a test that exercises one of our Methods (which form the "write" part of our app's API), and verifies it works correctly. 8 | 9 | To do so, we'll add a [test driver](http://guide.meteor.com/testing.html#test-driver) for the [Mocha](https://mochajs.org) JavaScript test framework, along with a test assertion library: 10 | 11 | ```bash 12 | meteor add meteortesting:mocha 13 | meteor npm install --save-dev chai 14 | ``` 15 | 16 | We can now run our app in "test mode" by calling out a special command and specifying to use the driver (you'll need to stop the regular app from running, or specify an alternate port with `--port XYZ`): 17 | 18 | ```bash 19 | TEST_WATCH=1 meteor test --driver-package meteortesting:mocha 20 | ``` 21 | 22 | If you do so, you should see test results for the two tests included by default in the `tests/main.js` file 23 | 24 | Let's add a new file in the `imports/api` folder named `tasks.test.js`. This file will be the home for all tests we make related to testing the applications's `tasks` api. 25 | 26 | Once the file is created we can then add a new test case to the file. 27 | 28 | {{> DiffBox tutorialName="simple-todos" step="11.2"}} 29 | 30 | The `imports/api/tasks.test.js` file will need to be imported in the `tests/main.js` file because the `tests/main.js` file serves as the entry point for the meteor test command 31 | 32 | {{> DiffBox tutorialName="simple-todos" step="11.3"}} 33 | 34 | Now that we are able to run all three of our test cases, we need to continue building the test case in the `imports/api/tasks.test.js` file. 35 | 36 | We first need to ensure the database is in the state we expect before our test case starts to run. To do so we can use Mocha's `beforeEach` construct. 37 | 38 | {{> DiffBox tutorialName="simple-todos" step="11.4"}} 39 | 40 | Here we create a single task that's associated with a random `userId` that'll be different for each test run. 41 | 42 | Now we can have our test case call the `tasks.remove` method "as" that user and verify the task is deleted: 43 | 44 | {{> DiffBox tutorialName="simple-todos" step="11.5"}} 45 | 46 | Running the test command again will allw you to see all three test cases are now passing. 47 | 48 | ```bash 49 | TEST_WATCH=1 meteor test --driver-package meteortesting:mocha 50 | ``` 51 | 52 | There's a lot more you can do in a Meteor test! You can read more about it in the Meteor Guide [article on testing](http://guide.meteor.com/testing.html). 53 | 54 | {{/template}} 55 | -------------------------------------------------------------------------------- /content/blaze/step12.md: -------------------------------------------------------------------------------- 1 | {{#template name="blaze-step12"}} 2 | 3 | # What's next? 4 | 5 | Congratulations on your newly built Meteor app! 6 | 7 | Your app currently supports collaborating on a single todo list. To see how you 8 | could add more functionality, check out the Todos example — a more 9 | complete app that can handle sharing multiple lists. Also, try Local Market, a 10 | cross-platform customer engagement app that shows off native hardware 11 | functionality and social features. 12 | 13 | ```bash 14 | meteor create --example todos 15 | meteor create --example localmarket 16 | ``` 17 | 18 | {{> step12NextSteps}} 19 | 20 | {{/template}} 21 | -------------------------------------------------------------------------------- /content/shared/adding-css.js: -------------------------------------------------------------------------------- 1 | Template.addingCSS.helpers({ 2 | cssHeading (cssFileName) { 3 | return `Replace ${cssFileName} with this code`; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /content/shared/adding-css.md: -------------------------------------------------------------------------------- 1 | {{#template name="addingCSS"}} 2 | 3 | ### Adding CSS 4 | 5 | Before we go any further, let's make our app look nice by adding some CSS. 6 | 7 | Since this tutorial is focused on working with HTML and JavaScript, just copy all the CSS code below into `{{cssFileName}}`. This is all the CSS code you will need until the end of the tutorial. The app will still work without the CSS, but it will look much nicer if you add it. 8 | 9 | {{#codeBox cssHeading cssFileName}} 10 | ```css 11 | /* CSS declarations go here */ 12 | body { 13 | font-family: sans-serif; 14 | background-color: #315481; 15 | background-image: linear-gradient(to bottom, #315481, #918e82 100%); 16 | background-attachment: fixed; 17 | 18 | position: absolute; 19 | top: 0; 20 | bottom: 0; 21 | left: 0; 22 | right: 0; 23 | 24 | padding: 0; 25 | margin: 0; 26 | 27 | font-size: 14px; 28 | } 29 | 30 | .container { 31 | max-width: 600px; 32 | margin: 0 auto; 33 | min-height: 100%; 34 | background: white; 35 | } 36 | 37 | header { 38 | background: #d2edf4; 39 | background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); 40 | padding: 20px 15px 15px 15px; 41 | position: relative; 42 | } 43 | 44 | #login-buttons { 45 | display: block; 46 | } 47 | 48 | h1 { 49 | font-size: 1.5em; 50 | margin: 0; 51 | margin-bottom: 10px; 52 | display: inline-block; 53 | margin-right: 1em; 54 | } 55 | 56 | form { 57 | margin-top: 10px; 58 | margin-bottom: -10px; 59 | position: relative; 60 | } 61 | 62 | .new-task input { 63 | box-sizing: border-box; 64 | padding: 10px 0; 65 | background: transparent; 66 | border: none; 67 | width: 100%; 68 | padding-right: 80px; 69 | font-size: 1em; 70 | } 71 | 72 | .new-task input:focus{ 73 | outline: 0; 74 | } 75 | 76 | ul { 77 | margin: 0; 78 | padding: 0; 79 | background: white; 80 | } 81 | 82 | .delete { 83 | float: right; 84 | font-weight: bold; 85 | background: none; 86 | font-size: 1em; 87 | border: none; 88 | position: relative; 89 | } 90 | 91 | li { 92 | position: relative; 93 | list-style: none; 94 | padding: 15px; 95 | border-bottom: #eee solid 1px; 96 | } 97 | 98 | li .text { 99 | margin-left: 10px; 100 | } 101 | 102 | li.checked { 103 | color: #888; 104 | } 105 | 106 | li.checked .text { 107 | text-decoration: line-through; 108 | } 109 | 110 | li.private { 111 | background: #eee; 112 | border-color: #ddd; 113 | } 114 | 115 | header .hide-completed { 116 | float: right; 117 | } 118 | 119 | .toggle-private { 120 | margin-left: 5px; 121 | } 122 | 123 | @media (max-width: 600px) { 124 | li { 125 | padding: 12px 15px; 126 | } 127 | 128 | .search { 129 | width: 150px; 130 | clear: both; 131 | } 132 | 133 | .new-task input { 134 | padding-bottom: 5px; 135 | } 136 | } 137 | ``` 138 | {{/codeBox}} 139 | 140 | {{/template}} 141 | -------------------------------------------------------------------------------- /content/shared/explanations.md: -------------------------------------------------------------------------------- 1 | {{#template name="step03CollectionsIntro"}} 2 | 3 | Collections are Meteor's way of storing persistent data. The special thing about collections in Meteor is that they can be accessed from both the server and the client, making it easy to write view logic without having to write a lot of server code. They also update themselves automatically, so a view component backed by a collection will automatically display the most up-to-date data. 4 | 5 | You can read more about collections in the [Collections article](http://guide.meteor.com/collections.html) of the Meteor Guide. 6 | 7 | Creating a new collection is as easy as calling `new Mongo.Collection("my-collection")` in your JavaScript. On the server, this sets up a MongoDB collection called `my-collection`; on the client, it creates a cache connected to the server collection. We'll learn more about the client/server divide in step 12, but for now we can write our code with the assumption that the entire database is present on the client. 8 | 9 | To create the collection, we define a new **`imports/api/tasks.js`** module that creates a Mongo collection and exports it: 10 | 11 | {{> DiffBox tutorialName=tutorialName step="3.1"}} 12 | 13 | Notice that we place this file in a new `imports/api` directory. This is a sensible place to store API-related files for the application. We will start by putting "collections" here and later we will add "publications" that read from them and "methods" that write to them. You can read more about how to structure your code in the [Application Structure article](http://guide.meteor.com/structure.html) of the Meteor Guide. 14 | 15 | We now need to import the `imports/api/tasks.js` module into `server/main.js`: 16 | 17 | {{> DiffBox tutorialName=tutorialName step="3.2"}} 18 | 19 | Importing `imports/api/tasks.js` on the server creates the MongoDB collection and sets up the plumbing to get the data to the client. 20 | 21 | {{/template}} 22 | 23 | {{#template name="step03InsertingTasksFromConsole"}} 24 | 25 | ### Inserting tasks from the server-side database console 26 | 27 | Items inside collections are called _documents_. Let's use the server database console to insert some documents into our collection. While your app is running, in a new terminal tab, go to your app directory and type: 28 | 29 | ```bash 30 | meteor mongo 31 | ``` 32 | 33 | This opens a console into your app's local development database. Into the prompt, type: 34 | 35 | ```js 36 | db.tasks.insert({ text: "Hello world!", createdAt: new Date() }); 37 | ``` 38 | 39 | In your web browser, you will see the UI of your app immediately update to show the new task. You can see that we didn't have to write any code to connect the server-side database to our front-end code—it just happened automatically. 40 | 41 | Insert a few more tasks from the database console with different text. In the next step, we'll see how to add functionality to our app's UI so that we can add tasks without using the database console. 42 | 43 | {{/template}} 44 | 45 | {{#template name="step09OptimisticUI"}} 46 | 47 | ### Optimistic UI 48 | 49 | So why do we want to define our methods on the client and on the server? We do this to enable a feature we call _optimistic UI_. 50 | 51 | When you call a method on the client using `Meteor.call`, two things happen in parallel: 52 | 53 | 1. The client sends a request to the server to run the method in a secure environment, just like an AJAX request would work 54 | 2. A simulation of the method runs directly on the client to attempt to predict the outcome of the server call using the available information 55 | 56 | What this means is that a newly created task actually appears on the screen _before_ the result comes back from the server. 57 | 58 | If the result from the server comes back and is consistent with the simulation on the client, everything remains as is. If the result on the server is different from the result of the simulation on the client, the UI is patched to reflect the actual state of the server. 59 | 60 | You can read more about methods and optimistic UI in the [Methods article](http://guide.meteor.com/methods.html) of the Meteor Guide, and our [blog post about optimistic UI](https://blog.meteor.com/optimistic-ui-with-meteor-67b5a78c3fcf). 61 | 62 | {{/template}} 63 | -------------------------------------------------------------------------------- /content/shared/nextSteps.md: -------------------------------------------------------------------------------- 1 | {{#template name="step12NextSteps"}} 2 | 3 | Here are some options for where you can go next: 4 | 5 | 1. Read the [Meteor Guide](http://guide.meteor.com) to learn about best practices and useful community packages 6 | 7 | 2. Check out the [complete documentation](https://docs.meteor.com) 8 | 9 | 3. Explore additional Meteor content such as the [Intermediate Meteor](https://www.youtube.com/watch?v=BI8IslJHSag&list=PLLnpHn493BHFYZUSK62aVycgcAouqBt7V) video series 10 | 11 | 4. Run your app on the cloud [today](https://www.meteor.com/hosting). Try Galaxy (Meteor Hosting) for Free! [Sign up](https://www.meteor.com/hosting) for Galaxy Hosting today and get 4 GB free trial for the first 30 days! 12 | 13 | {{/template}} 14 | -------------------------------------------------------------------------------- /content/shared/step01.md: -------------------------------------------------------------------------------- 1 | {{#template name="sharedStep01"}} 2 | 3 | # Creating your first app 4 | 5 | In this tutorial, we are going to create a simple app to manage a 'to do' list and collaborate with others on those tasks. By the end, you should have a basic understanding of Meteor and its project structure. 6 | 7 | To create the app, open your terminal and type: 8 | 9 | ```bash 10 | meteor create simple-todos --blaze 11 | ``` 12 | 13 | This will create a new folder called `simple-todos` with all of the files that a Meteor app needs: 14 | 15 | ```bash 16 | client/main.js # a JavaScript entry point loaded on the client 17 | client/main.html # an HTML file that defines view templates 18 | client/main.css # a CSS file to define your app's styles 19 | server/main.js # a JavaScript entry point loaded on the server 20 | test/main.js # a JavaScript entry point when running tests 21 | package.json # a control file for installing npm packages 22 | package-lock.json # describes the npm dependency tree 23 | node_modules/ # packages installed by npm 24 | .meteor/ # internal Meteor files 25 | .gitignore # a control file for git 26 | ``` 27 | 28 | To run the newly created app: 29 | 30 | ```bash 31 | cd simple-todos 32 | meteor 33 | ``` 34 | 35 | Open your web browser and go to `http://localhost:3000` to see the app running. 36 | 37 | You can play around with this default app for a bit before we continue. For example, try editing the text in `

    ` inside `client/main.html` using your favorite text editor. When you save the file, the page in your browser will automatically update with the new content. We call this _hot code push_. 38 | 39 | Now that you have some experience editing the files in your Meteor app, let's start working on a simple todo list application. If you find a bug or error in the tutorial, please file an issue or submit a pull request [on GitHub](https://github.com/meteor/tutorials). 40 | {{/template}} 41 | -------------------------------------------------------------------------------- /content/shared/step06.md: -------------------------------------------------------------------------------- 1 | {{#template name="sharedStep06"}} 2 | 3 | # Running your app on Android or iOS 4 | 5 | > Currently, Meteor on Windows does not support mobile builds. If you are using Meteor on Windows, you should skip this step. 6 | 7 | So far, we've been building our app and testing only in a web browser, but Meteor has been designed to work across different platforms - your simple todo list website can become an iOS or Android app in just a few commands. 8 | 9 | Meteor makes it easy to set up all of the tools required to build mobile apps, but downloading all of the programs can take a while - for Android the download is about 300MB and for iOS you need to install Xcode which is about 2GB. If you don't want to wait to download these tools, feel free to skip to the next step. 10 | 11 | {{#if specialContent}} 12 | {{> Template.dynamic template=specialContent}} 13 | {{/if}} 14 | 15 | ### Running on an iOS simulator (Mac Only) 16 | 17 | If you have a Mac, you can run your app inside the iOS simulator. 18 | 19 | Go to your app folder and type: 20 | 21 | ```bash 22 | meteor install-sdk ios 23 | ``` 24 | 25 | This will run you through the setup necessary to build an iOS app from your project. When you're done, type: 26 | 27 | ```bash 28 | meteor add-platform ios 29 | meteor run ios 30 | ``` 31 | 32 | You will see the iOS simulator pop up with your app running inside. 33 | 34 | ### Running on an Android emulator 35 | 36 | In the terminal, go to your app folder and type: 37 | 38 | ```bash 39 | meteor install-sdk android 40 | ``` 41 | 42 | This will help you install all of the necessary tools to build an Android app from your project. When you are done installing everything, type: 43 | 44 | ```bash 45 | meteor add-platform android 46 | ``` 47 | 48 | After you agree to the license terms, type: 49 | 50 | ```bash 51 | meteor run android 52 | ``` 53 | 54 | After some initialization, you will see an Android emulator pop up, running your app inside a native Android wrapper. The emulator can be somewhat slow, so if you want to see what it's really like using your app, you should run it on an actual device. 55 | 56 | ### Running on an Android device 57 | 58 | First, complete all of the steps above to set up the Android tools on your system. Then, make sure you have [USB Debugging enabled on your phone](http://developer.android.com/tools/device.html#developer-device-options) and the phone is plugged into your computer with a USB cable. Also, you must quit the Android emulator before running on a device. 59 | 60 | Then, run the following command: 61 | 62 | ```bash 63 | meteor run android-device 64 | ``` 65 | 66 | The app will be built and installed on your device. 67 | 68 | ### Running on an iPhone or iPad (Mac Only; requires Apple developer account) 69 | 70 | If you have an Apple developer account, you can also run your app on an iOS device. Run the following command: 71 | 72 | ```bash 73 | meteor run ios-device 74 | ``` 75 | 76 | This will open Xcode with a project for your iOS app. You can use Xcode to then launch the app on any device or simulator that Xcode supports. 77 | 78 | Now that we have seen how easy it is to run our app on mobile, let's get to adding some more features. 79 | 80 | {{/template}} 81 | -------------------------------------------------------------------------------- /content/step00.html: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /content/svelte/metadata.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | DiffBox.registerTutorial("simple-todos-svelte", { 3 | gitHubRepoName: "meteor/simple-todos-svelte", 4 | patchFilename: "generated/svelte.multi.patch" 5 | }); 6 | } 7 | 8 | TutorialRegistry.registerTutorial("svelte", { 9 | title: "Simple Todos Svelte", 10 | subtitle: "Learn how to use Meteor and Svelte together", 11 | tutorialSourceLink: "github.com/meteor/tutorials/content/svelte", 12 | steps: [ 13 | { 14 | title: 'Creating an app', 15 | slug: "creating-an-app", 16 | template: 'sharedStep01' 17 | }, 18 | { 19 | title: 'Templates', 20 | slug: "templates", 21 | template: 'svelte-step02' 22 | }, 23 | { 24 | title: 'Collections', 25 | slug: "collections", 26 | template: 'svelte-step03' 27 | }, 28 | { 29 | title: 'Forms and events', 30 | slug: "forms-and-events", 31 | template: 'svelte-step04' 32 | }, 33 | { 34 | title: 'Update and remove', 35 | slug: "update-and-remove", 36 | template: 'svelte-step05' 37 | }, 38 | { 39 | title: 'Running on mobile', 40 | slug: "running-on-mobile", 41 | template: 'sharedStep06' 42 | }, 43 | { 44 | title: 'Filtering Collections', 45 | slug: "filtering-collections", 46 | template: 'svelte-step07' 47 | }, 48 | { 49 | title: 'Adding user accounts', 50 | slug: "adding-user-accounts", 51 | template: 'svelte-step08' 52 | }, 53 | { 54 | title: 'Security with methods', 55 | slug: "security-with-methods", 56 | template: 'svelte-step09' 57 | }, 58 | { 59 | title: 'Publish and subscribe', 60 | slug: "publish-and-subscribe", 61 | template: 'svelte-step10' 62 | }, 63 | { 64 | title: 'Testing', 65 | slug: "testing", 66 | template: 'svelte-step11' 67 | }, 68 | { 69 | title: 'Next steps', 70 | slug: "next-steps", 71 | template: 'svelte-step12' 72 | } 73 | ] 74 | }); 75 | -------------------------------------------------------------------------------- /content/svelte/step02.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step02"}} 2 | # In a hurry? 3 | 4 | If you don't have time to follow this detailed tutorial right now, you have two options. 5 | 6 | To skip to the finished application explained by this tutorial, run the following commands: 7 | 8 | ```sh 9 | git clone git@github.com:meteor/simple-todos-svelte.git 10 | cd simple-todos-svelte 11 | meteor npm install 12 | meteor 13 | ``` 14 | 15 | If you are an experienced Svelte developer, most of this application will be self-explanatory, though you can always refer back to this tutorial to understand the details. 16 | 17 | # Working with Svelte 18 | 19 | The rest of this tutorial will take you on a step-by-step journey through the details of using Svelte with Meteor. 20 | 21 | ### Install Svelte dependencies 22 | 23 | To start working with Svelte as our view library, we first need to add the relevant npm package. 24 | 25 | Open a new terminal in the same directory as your running app, and run the following commands: 26 | 27 | ```sh 28 | meteor npm install --save svelte 29 | ``` 30 | 31 | > Note: `meteor npm` supports the same features as `npm`, though the difference can be important. Consult the [`meteor npm` documentation](https://docs.meteor.com/commandline.html#meteornpm) for more information. 32 | 33 | We also need to add the svelte:compiler Meteor package to allow us to create files with the .svelte extension, which makes it possible to create Svelte components in the single file format, and the rdb:svelte-meteor-data Meteor package which allows us to consume Meteor's reactive data sources inside of our Svelte components. 34 | 35 | ```sh 36 | meteor add svelte:compiler rdb:svelte-meteor-data 37 | ``` 38 | 39 | ### Replace `blaze-html-templates` with `static-html` 40 | 41 | By default, new Meteor applications use Blaze as their templating engine, but the application we're building in this tutorial does not need the `blaze-html-templates` Meteor package. 42 | 43 | First remove the `blaze-html-templates` package: 44 | 45 | ```sh 46 | meteor remove blaze-html-templates 47 | ``` 48 | 49 | Now, to ensure `.html` files are processed as static HTML, add the `static-html` package: 50 | 51 | ```sh 52 | meteor add static-html 53 | ``` 54 | 55 | The `static-html` package is not specifically for building a Svelte application. It simply turns `` and `` fragments found in `.html` files into raw HTML that will be served from the Meteor web server. Later, your Svelte application will render its components into this HTML. 56 | 57 | Note that both `blaze-html-templates` and `static-html` are _Meteor packages_, rather than npm packages, because they need to register _compiler plugins_ that determine how `.html` files are processed. Controlling compilation is one of several key features that make Meteor packages more powerful than npm packages. 58 | 59 | ### Replace the starter code 60 | 61 | To get started, let's replace the code of the default starter app. Then we'll talk about what it does. 62 | 63 | First, replace the content of the **`client/main.html`** file: 64 | 65 | {{> DiffBox tutorialName="simple-todos-svelte" step="2.2"}} 66 | 67 | Second, replace the contents of the **`client/main.js`** module: 68 | 69 | {{> DiffBox tutorialName="simple-todos-svelte" step="2.3"}} 70 | 71 | Now we need to create a new directory called `imports`, with a directory called `ui` inside of it. There will soon be other directories besides `ui` within `imports`, but we'll start with `ui`. 72 | 73 | > Note: in previous versions of Meteor, the `imports` directory was special because files outside the `imports` directory were loaded automatically when the application started, whereas files inside the `imports` directory were only loaded when imported using an `import` declaration or a `require` statement. As of Meteor 1.7, the entry point for both client and server JavaScript is determined by the `meteor.mainModule` section in `package.json`. In other words, as far as JavaScript code is concerned, the entire application now behaves as if it was inside an `imports` directory, so you don't need to worry as much about the `imports` directory now. 74 | 75 | You can read more about how imports work and how to structure your code in the [Application Structure article](http://guide.meteor.com/structure.html) of the Meteor Guide. 76 | 77 | To continue converting your app to use Svelte, copy the following code into **`imports/ui/App.svelte`**: 78 | 79 | {{> DiffBox tutorialName="simple-todos-svelte" step="2.4"}} 80 | 81 | Now copy the following code into **`imports/ui/Task.svelte`**: 82 | 83 | {{> DiffBox tutorialName="simple-todos-svelte" step="2.5"}} 84 | 85 | We just added three things to our app: 86 | 87 | 1. An `App` Svelte component in `imports/ui/App.svelte` 88 | 2. A `Task` Svelte component in `imports/ui/Task.svelte` 89 | 3. Some initialization code (in our `client/main.js` client JavaScript entry point), in a `Meteor.startup` block, which knows how to call code when the page is loaded and ready. This code creates a Svelte component that will be mounted using the `#app` html element. 90 | 91 | ### Define view components with Svelte 92 | 93 | In Svelte, single file components are created with the .svelte file extension and are comprised of three sections, the script section, the markup section and the style section. Within the script section you will write Javascript that runs when the component instance is created. The Svelte component format is fully explained in the [Svelte Guide](https://svelte.dev/docs#Component_format) 94 | 95 | ### Check the result 96 | 97 | In our browser, the app should **roughly** look like the following (though much less pretty): 98 | 99 | > #### Todo List 100 | > 101 | > - This is task 1 102 | > - This is task 2 103 | > - This is task 3 104 | 105 | If your app doesn't look like this, use the GitHub link at the top right corner of each code snippet to see the entire file, and make sure your code matches the example. 106 | 107 | ### Add CSS styles to your app 108 | 109 | In order to make your todo list more visually appealing, copy the following code into **`client/main.css`**: 110 | 111 | {{> DiffBox tutorialName="simple-todos-svelte" step="2.6"}} 112 | 113 | Now that you've added the CSS, the app should look a lot nicer. Check your browser to see that the new styles have loaded. 114 | 115 | {{/template}} 116 | -------------------------------------------------------------------------------- /content/svelte/step03.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step03"}} 2 | 3 | # Storing tasks in a collection 4 | 5 | {{> step03CollectionsIntro tutorialName="simple-todos-svelte"}} 6 | 7 | Let's update our client-side JavaScript code to get our tasks from a collection instead of a static array: 8 | 9 | {{> DiffBox tutorialName="simple-todos-svelte" step="3.3"}} 10 | 11 | When you make these changes to the code, you'll notice that the tasks that used to be in the todo list have disappeared. That's because our database is currently empty — we need to insert some tasks! 12 | 13 | {{> step03InsertingTasksFromConsole}} 14 | 15 | {{/template}} 16 | -------------------------------------------------------------------------------- /content/svelte/step04.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step04"}} 2 | 3 | # Adding tasks with a form 4 | 5 | In this step, we'll add an input field for users to add new tasks to the list. 6 | 7 | First, let's add a form and a form input to our `App` component's markup section. Then we will add a `newTask` property to the `App` component's script section: 8 | 9 | {{> DiffBox step="4.1" tutorialName="simple-todos-svelte"}} 10 | 11 | This form's input tag will have a `bind:value` attribute added to it and this will bind the input's value to the `newTask` property. 12 | 13 | Next, the `handleSubmit` method will be added to the `App` component's script section. In order to execute the `handleSubmit` function on our form's submit event we will add the `on:submit|preventDefault` attribute to the form tag: 14 | 15 | {{> DiffBox step="4.2" tutorialName="simple-todos-svelte"}} 16 | 17 | To add a new task, just type into the input field and hit enter. If you open a new browser window and open the app again, you'll see that the list is automatically synchronized between all clients. 18 | 19 | ### Inserting into a collection 20 | 21 | Inside the event handler, we are adding a task to the `tasks` collection by calling `Tasks.insert()`. We can assign any properties to the task object, such as the time created, since we don't ever have to define a schema for the collection. 22 | 23 | Being able to insert anything into the database from the client isn't very secure, but it's okay for now. In step 10 we'll learn how we can make our app secure and restrict how data is inserted into the database. 24 | 25 | ### Sorting our tasks 26 | 27 | Currently, our code displays all new tasks at the bottom of the list. That's not very good for a task list, because we want to see the newest tasks first. 28 | 29 | We can solve this by sorting the results using the `createdAt` field that is automatically added by our new code. Just add a sort option to the `Tasks.find` call inside the `App` component: 30 | 31 | {{> DiffBox step="4.3" tutorialName="simple-todos-svelte"}} 32 | 33 | Let's go back to the browser and make sure this worked: any new tasks that you add should appear at the top of the list, rather than at the bottom. 34 | 35 | In the next step, we'll add some very important todo list features: checking off and deleting tasks. 36 | {{/template}} 37 | -------------------------------------------------------------------------------- /content/svelte/step05.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step05"}} 2 | 3 | # Checking off and deleting tasks 4 | 5 | Until now, we have only interacted with a collection by inserting documents. Now, we will learn how to update and remove them. 6 | 7 | Let's add two new elements to our `Task` component, a checkbox and a delete button, with event handlers for both: 8 | 9 | {{> DiffBox step="5.1" tutorialName="simple-todos-svelte"}} 10 | 11 | ### Update 12 | 13 | In the code above, we call `Tasks.update` to check off a task. 14 | 15 | The `update` function on a collection takes two arguments. The first is a selector that identifies a subset of the collection, and the second is an update parameter that specifies what should be done to the matched objects. 16 | 17 | In this case, the selector is just the `_id` of the relevant task. The update parameter uses `$set` to toggle the `checked` field, which will represent whether the task has been completed. 18 | 19 | ### Remove 20 | 21 | The code from above uses `Tasks.remove` to delete a task. The `remove` function takes one argument, a selector that determines which item to remove from the collection. 22 | 23 | {{/template}} -------------------------------------------------------------------------------- /content/svelte/step07.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step07"}} 2 | 3 | # Storing temporary UI data in Svelte components data field 4 | 5 | In this step, we'll add a client-side data filtering feature to our app, so that users can check a box to only see incomplete tasks. We're going to learn how to use Svelte's component state to store temporary information that is only used on the client. 6 | 7 | First, we need to add a checkbox to our `App` component: 8 | 9 | {{> DiffBox step="7.1" tutorialName="simple-todos-svelte"}} 10 | 11 | You can see that it reads from `hideCompleted`. We'll need to initialize the value of `hideCompleted` to false: 12 | 13 | {{> DiffBox step="7.2" tutorialName="simple-todos-svelte"}} 14 | 15 | The `hideCompleted` component propert will reactively update the contents of the `task` array and filter out tasks that have been checked if neeeded: 16 | 17 | {{> DiffBox step="7.3" tutorialName="simple-todos-svelte"}} 18 | 19 | Now if you check the box, the task list will only show tasks that haven't been completed. 20 | 21 | 22 | ### One more feature: Showing a count of incomplete tasks 23 | 24 | Now that we have written a query that filters out completed tasks, we can use the same query to display a count of the tasks that haven't been checked off. Since we already have the data in the client-side collection, adding this extra count doesn't involve asking the server for anything. 25 | 26 | {{> DiffBox step="7.4" tutorialName="simple-todos-svelte"}} 27 | 28 | {{> DiffBox step="7.5" tutorialName="simple-todos-svelte"}} 29 | 30 | {{/template}} 31 | -------------------------------------------------------------------------------- /content/svelte/step08.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step08"}} 2 | 3 | # Adding user accounts 4 | 5 | Meteor comes with an accounts system and a drop-in login user interface that lets you add multi-user functionality to your app in minutes. 6 | 7 | > Currently, this UI component uses Blaze, Meteor's default UI engine. 8 | 9 | To make use of the accounts system and UI, we need to add the relevant packages. In your app directory, run the following command: 10 | 11 | ```bash 12 | meteor add accounts-ui accounts-password 13 | ``` 14 | 15 | ### Wrapping a Blaze component in Svelte 16 | 17 | To use the login Blaze template from the `accounts-ui` package inside of a Svelte component, we need to first add the `svelte:blaze-integration` Meteor package: 18 | 19 | ```sh 20 | meteor add svelte:blaze-integration 21 | ``` 22 | 23 | Now we can include the `loginButtons` template in the `App` component: 24 | 25 | {{> DiffBox step="8.3" tutorialName="simple-todos-svelte"}} 26 | 27 | We will then need to add a new file to configure the accounts package `imports/startup/accounts-config.js`. 28 | 29 | Then, add the following code to configure the accounts UI to use usernames instead of email addresses: 30 | 31 | {{> DiffBox step="8.4" tutorialName="simple-todos-svelte"}} 32 | 33 | We also need to import that configuration file into our client side entrypoint: 34 | 35 | {{> DiffBox step="8.5" tutorialName="simple-todos-svelte"}} 36 | 37 | ### Adding user-related functionality 38 | 39 | Now users can create accounts and log into your app! This is very nice, but logging in and out isn't very useful yet. Let's add two features: 40 | 41 | 1. Only display the new task input field to logged in users 42 | 2. Show which user created each task 43 | 44 | To do this, we will add two new fields to the `tasks` collection: 45 | 46 | 1. `owner` - the `_id` of the user that created the task. 47 | 2. `username` - the `username` of the user that created the task. We will save the username directly in the task object so that we don't have to look up the user every time we display the task. 48 | 49 | First, let's add some code to save these fields into the `handleSubmit` event handler: 50 | 51 | {{> DiffBox step="8.6" tutorialName="simple-todos-svelte"}} 52 | 53 | Modify the `App` component to get the information about the currently logged in user: 54 | 55 | {{> DiffBox step="8.7" tutorialName="simple-todos-svelte"}} 56 | 57 | Then, we can wrap our form in a `{#if expression}{/if}` directive to conditionally render our form only when there is a logged in user: 58 | 59 | {{> DiffBox step="8.8" tutorialName="simple-todos-svelte"}} 60 | 61 | Finally, add a statement to display the `username` field on each task right before the text: 62 | 63 | {{> DiffBox step="8.9" tutorialName="simple-todos-svelte"}} 64 | 65 | In your browser, add some tasks and notice that your username shows up. Old tasks that we added before this step won't have usernames attached; you can just delete them. 66 | 67 | Now, users can log in and we can track which user each task belongs to. Let's look at some of the concepts we just discovered in more detail. 68 | 69 | ### Automatic accounts UI 70 | 71 | If our app has the `accounts-ui` package, all we have to do to add a login dropdown is render the included UI component. This dropdown detects which login methods have been added to the app and displays the appropriate controls. In our case, the only enabled login method is `accounts-password`, so the dropdown displays a password field. If you are adventurous, you can add the `accounts-facebook` package to enable Facebook login in your app - the Facebook button will automatically appear in the dropdown. 72 | 73 | ### Getting information about the logged-in user 74 | 75 | You can use `Meteor.user()` to check if a user is logged in and get information about them. For example, `Meteor.user().username` contains the logged in user's username. You can also use `Meteor.userId()` to just get the current user's `_id`. 76 | 77 | In the next step, we will learn how to make our app more secure by doing data validation on the server. 78 | {{/template}} 79 | -------------------------------------------------------------------------------- /content/svelte/step09.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step09"}} 2 | 3 | # Security with methods 4 | 5 | Before this step, any user of the app could edit any part of the database. This might be okay for very small internal apps or demos, but any real application needs to control permissions for its data. In Meteor, the best way to do this is by declaring _methods_. Instead of the client code directly calling `insert`, `update`, and `remove`, it will instead call methods that will check if the user is authorized to complete the action and then make any changes to the database on the client's behalf. 6 | 7 | ### Removing `insecure` 8 | 9 | Every newly created Meteor project has the `insecure` package added by default. This is the package that allows us to edit the database from the client. It's useful when prototyping, but now we are taking off the training wheels. To remove this package, go to your app directory and run: 10 | 11 | ```bash 12 | meteor remove insecure 13 | ``` 14 | 15 | If you try to use the app after removing this package, you will notice that none of the inputs or buttons work anymore. This is because all client-side database permissions have been revoked. Now we need to rewrite some parts of our app to use methods. 16 | 17 | ### Defining methods 18 | 19 | First, we need to define some methods. We need one method for each database operation we want to perform on the client. Methods should be defined in code that is executed on the client and the server - we will discuss this a bit later in the section titled _Optimistic UI_. 20 | 21 | {{> DiffBox step="9.2" tutorialName="simple-todos-svelte"}} 22 | 23 | Now that we have defined our methods, we need to update the places we were operating on the collection to use the methods instead: 24 | 25 | {{> DiffBox step="9.3" tutorialName="simple-todos-svelte"}} 26 | 27 | {{> DiffBox step="9.4" tutorialName="simple-todos-svelte"}} 28 | 29 | Now all of our inputs and buttons will start working again. What did we gain from all of this work? 30 | 31 | 1. When we insert tasks into the database, we can now securely verify that the user is logged in, that the `createdAt` field is correct, and that the `owner` and `username` fields are correct and the user isn't impersonating anyone. 32 | 2. We can add extra validation logic to `setChecked` and `deleteTask` in later steps when users can make tasks private. 33 | 3. Our client code is now more separated from our database logic. Instead of a lot of stuff happening inside our event handlers, we now have methods that can be called from anywhere. 34 | 35 | {{> step09OptimisticUI}} 36 | 37 | {{/template}} 38 | -------------------------------------------------------------------------------- /content/svelte/step10.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step10"}} 2 | 3 | # Filtering data with publish and subscribe 4 | 5 | Now that we have moved all of our app's sensitive code into methods, we need to learn about the other half of Meteor's security story. Until now, we have worked assuming the entire database is present on the client, meaning if we call `Tasks.find()` we will get every task in the collection. That's not good if users of our application want to store privacy-sensitive data. We need a way of controlling which data Meteor sends to the client-side database. 6 | 7 | Just like with `insecure` in the last step, all new Meteor apps start with the `autopublish` package, which automatically synchronizes all of the database contents to the client. Let's remove it and see what happens: 8 | 9 | ```bash 10 | meteor remove autopublish 11 | ``` 12 | 13 | When the app refreshes, the task list will be empty. Without the `autopublish` package, we will have to specify explicitly what the server sends to the client. The functions in Meteor that do this are `Meteor.publish` and `Meteor.subscribe`. 14 | 15 | First lets add a publication for all the tasks we have in our Mongo collection: 16 | 17 | {{> DiffBox step="10.2" tutorialName="simple-todos-svelte"}} 18 | 19 | Calling `Meteor.publish` on the server registers a _publication_ named `"tasks"`. 20 | 21 | And then let's subscribe to that publication in the `App` component's `onMount` event handler. This event handler is called when the component has been added to the DOM. 22 | 23 | {{> DiffBox step="10.3" tutorialName="simple-todos-svelte"}} 24 | 25 | Once you have added this code, all of the tasks will reappear. 26 | 27 | To truly see the power of the publish/subscribe model, let's implement a feature that allows users to mark tasks as "private" so that no other users can see them. 28 | 29 | ### Adding a button to make tasks private 30 | 31 | Let's add another property to `Task` component called `showPrivateButton` and a button for users to mark a task as private. This button should only show up for the owner of a task. We want the label to indicate the current status: public or private. 32 | 33 | First, we need to add a new method that we can call to set a task's private status: 34 | 35 | {{> DiffBox step="10.4" tutorialName="simple-todos-svelte"}} 36 | 37 | Now, we need to update the `Task` component to include a new `showPrivateButton` field: 38 | 39 | {{> DiffBox step="10.5" tutorialName="simple-todos-svelte"}} 40 | 41 | The `Task` component will need to determine whether to show the button when a user logs in and logs out of 42 | the application: 43 | 44 | {{> DiffBox step="10.6" tutorialName="simple-todos-svelte"}} 45 | 46 | Now let's add the button: 47 | 48 | {{> DiffBox step="10.7" tutorialName="simple-todos-svelte"}} 49 | 50 | We need to define the event handler called by the button: 51 | 52 | {{> DiffBox step="10.8" tutorialName="simple-todos-svelte"}} 53 | 54 | One last thing, let's update the class of the `
  • ` element in the `Task` component to reflect it's privacy status. 55 | 56 | {{> DiffBox step="10.9" tutorialName="simple-todos-svelte"}} 57 | 58 | ### Selectively publishing tasks based on privacy status 59 | 60 | Now that we have a way of setting which tasks are private, we should modify our 61 | publication function to only send the tasks that a user is authorized to see: 62 | 63 | {{> DiffBox step="10.10" tutorialName="simple-todos-svelte"}} 64 | 65 | To test that this functionality works, you can use your browser's private browsing mode to log in as a different user. Put the two windows side by side and mark a task private to confirm that the other user can't see it. Now make it public again and it will reappear! 66 | 67 | ### Extra method security 68 | 69 | In order to finish up our private task feature, we need to add checks to our `deleteTask` and `setChecked` methods to make sure only the task owner can delete or check off a private task: 70 | 71 | {{> DiffBox step="10.11" tutorialName="simple-todos-svelte"}} 72 | 73 | > Notice that with this code anyone can delete any public task. With some small modifications to the code, you should be able to make it so that only the owner can delete their tasks. 74 | 75 | We're done with our private task feature! Now our app is secure from attackers trying to view or modify someone's private tasks. 76 | 77 | {{/template}} 78 | -------------------------------------------------------------------------------- /content/svelte/step11.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step11"}} 2 | 3 | # Testing 4 | 5 | Now that we've created a few features for our application, let's run a few tests to ensure that everything works the way we expect. 6 | 7 | ### Install Meteor and npm dependencies 8 | 9 | First, we will add a [test driver](http://guide.meteor.com/testing.html#test-driver) for the [Mocha](https://mochajs.org) JavaScript test framework, along with a test assertion library: 10 | 11 | ```bash 12 | meteor add meteortesting:mocha 13 | meteor npm install --save-dev chai 14 | ``` 15 | ### Run `meteor test` with a driver package 16 | 17 | With these installed we can now run our app in "test mode" by running `meteor test` and specifying a test driver package (first you'll need to stop the regular app from running): 18 | 19 | ```bash 20 | TEST_WATCH=1 meteor test --driver-package meteortesting:mocha 21 | ``` 22 | 23 | After running that command, you should see the test results for the two tests included by default in the `tests/main.js` file from when we ran the `meteor create` command 24 | 25 | ```bash 26 | -------------------------------- 27 | --- RUNNING APP SERVER TESTS --- 28 | -------------------------------- 29 | ``` 30 | 31 | followed by 32 | 33 | ```bash 34 | simple-todos-svelte 35 | ✓ package.json has correct name 36 | ✓ server is not client 37 | 38 | 2 passing (10ms) 39 | ``` 40 | 41 | ### Understand existing tests 42 | 43 | Every new Meteor application includes a **`tests/main.js`** module containing several example tests using the `describe`, `it`, and `assert` style popularized by testing frameworks like [Mocha](https://mochajs.org/#getting-started): 44 | 45 | ```js 46 | import assert from "assert"; 47 | 48 | describe("simple-todos-svelte", function() { 49 | it("package.json has correct name", async function() { 50 | const { name } = await import("../package.json"); 51 | assert.strictEqual(name, "simple-todos-svelte"); 52 | }); 53 | 54 | if (Meteor.isClient) { 55 | it("client is not server", function() { 56 | assert.strictEqual(Meteor.isServer, false); 57 | }); 58 | } 59 | 60 | if (Meteor.isServer) { 61 | it("server is not client", function() { 62 | assert.strictEqual(Meteor.isClient, false); 63 | }); 64 | } 65 | }); 66 | ``` 67 | 68 | This module serves as the entry point for all your application tests, and it is defined as such in the `package.json` file. Under the `meteor` field in the `package.json` file is the `testModule` field and it is this field that refers to the entry point for the `meteor test` command. If you would like to, you can continue adding new tests to this module, using `Meteor.isServer` and `Meteor.isClient` to determine which tests run will run in which environment. 69 | 70 | ### Import additional test modules 71 | 72 | However, if you would prefer to split your tests across multiple modules, you can do that too. We will demonstrate this approach now. Let's add a new test module called **`imports/api/tasks.tests.js`**: 73 | 74 | {{> DiffBox tutorialName="simple-todos-svelte" step="11.2"}} 75 | 76 | In any test we make we need to ensure the database is in the state we expect before beginning. We can use Mocha's `beforeEach` construct to do test setup tasks: 77 | 78 | {{> DiffBox tutorialName="simple-todos-svelte" step="11.3"}} 79 | 80 | Here we create a single task that's associated with a random `userId` that'll be different for each test run. 81 | 82 | Now we can write the test to call the `tasks.remove` method "as" that user and verify the task is deleted: 83 | 84 | {{> DiffBox tutorialName="simple-todos-svelte" step="11.4"}} 85 | 86 | The only remaining step is to import this new test module into the main `tests/main.js` module: 87 | 88 | {{> DiffBox tutorialName="simple-todos-svelte" step="11.5"}} 89 | 90 | ### Run `meteor test` again 91 | 92 | If you run the test command again (or if you left it running from before) 93 | 94 | ```bash 95 | TEST_WATCH=1 meteor test --driver-package meteortesting:mocha 96 | ``` 97 | 98 | you should now see the output from the new test module we just added: 99 | 100 | ```bash 101 | Tasks 102 | methods 103 | ✓ can delete owned task 104 | 105 | simple-todos-svelte 106 | ✓ package.json has correct name 107 | ✓ server is not client 108 | 109 | 3 passing (120ms) 110 | ``` 111 | 112 | ### Other useful testing commands 113 | 114 | To make it easier to type this command, you may want to add a shorthand to the [`"scripts"` section](https://docs.npmjs.com/misc/scripts) of your `package.json` file. 115 | 116 | In fact, new Meteor apps come with a few preconfigured npm scripts, which you are welcome to use or modify. 117 | 118 | The standard `npm test` (or `meteor npm test`) command runs the following command: 119 | 120 | ```bash 121 | meteor test --once --driver-package meteortesting:mocha 122 | ``` 123 | 124 | This command is suitable for running in a continuous integration (CI) environment such as [Travis CI](https://travis-ci.org) or [CircleCI](https://circleci.com), since it runs only your server-side tests and then exits with 0 if all the tests passed. 125 | 126 | If you would like to run your tests while developing your application (and re-run them whenever the development server restarts), consider using `meteor npm run test-app`, which is equivalent to 127 | 128 | ```bash 129 | TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha 130 | ``` 131 | 132 | This is almost the same as the earlier command, except that it also loads your application code as normal (due to `--full-app`), allowing you to interact with your app in the browser while running both client and server tests. 133 | 134 | ### Further reading 135 | 136 | There's a lot more you can do with Meteor tests! You can read more about it in the Meteor Guide [article on testing](http://guide.meteor.com/testing.html). 137 | 138 | {{/template}} 139 | -------------------------------------------------------------------------------- /content/svelte/step12.md: -------------------------------------------------------------------------------- 1 | {{#template name="svelte-step12"}} 2 | 3 | # What's next? 4 | 5 | Congratulations on your newly built Meteor app! 6 | 7 | {{> step12NextSteps}} 8 | 9 | {{/template}} 10 | -------------------------------------------------------------------------------- /content/vue/metadata.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | DiffBox.registerTutorial("simple-todos-vue", { 3 | gitHubRepoName: "meteor/simple-todos-vue", 4 | patchFilename: "generated/vue.multi.patch" 5 | }); 6 | } 7 | 8 | TutorialRegistry.registerTutorial("vue", { 9 | title: "Simple Todos Vue", 10 | subtitle: "Learn how to use Meteor and Vue together", 11 | tutorialSourceLink: "github.com/meteor/tutorials/content/vue", 12 | steps: [ 13 | { 14 | title: "Creating an app", 15 | slug: "creating-an-app", 16 | template: "sharedStep01" 17 | }, 18 | { 19 | title: 'Components', 20 | slug: "components", 21 | template: 'vue-step02' 22 | }, 23 | { 24 | title: 'Collections', 25 | slug: "collections", 26 | template: 'vue-step03' 27 | }, 28 | { 29 | title: 'Forms and events', 30 | slug: "forms-and-events", 31 | template: 'vue-step04' 32 | }, 33 | { 34 | title: 'Update and remove', 35 | slug: "update-and-remove", 36 | template: 'vue-step05' 37 | }, 38 | { 39 | title: 'Running on mobile', 40 | slug: "running-on-mobile", 41 | template: "sharedStep06" 42 | }, 43 | { 44 | title: 'Temporary UI state', 45 | slug: "temporary-ui-state", 46 | template: 'vue-step07' 47 | }, 48 | { 49 | title: 'Adding user accounts', 50 | slug: "adding-user-accounts", 51 | template: 'vue-step08' 52 | }, 53 | { 54 | title: 'Security with methods', 55 | slug: "security-with-methods", 56 | template: 'vue-step09' 57 | }, 58 | { 59 | title: 'Publish and subscribe', 60 | slug: "publish-and-subscribe", 61 | template: 'vue-step10' 62 | }, 63 | { 64 | title: 'Testing', 65 | slug: "testing", 66 | template: 'vue-step11' 67 | }, 68 | { 69 | title: 'Next steps', 70 | slug: "next-steps", 71 | template: 'vue-step12' 72 | } 73 | ] 74 | }); 75 | -------------------------------------------------------------------------------- /content/vue/step02.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step02"}} 2 | 3 | # In a hurry? 4 | 5 | If you don't have time to follow this detailed tutorial right now, you have two options. 6 | 7 | You can skip to the finished application explained by this tutorial, by running the following commands: 8 | 9 | ```sh 10 | git clone git@github.com:meteor/simple-todos-vue.git 11 | cd simple-todos-vue 12 | meteor npm install 13 | meteor 14 | ``` 15 | 16 | If you are an experienced Vue developer, most of this application will be self-explanatory, though you can always refer back to this tutorial to understand the details. 17 | 18 | # Working with Vue 19 | 20 | The rest of this tutorial will take you on a step-by-step journey through the details of using Vue with Meteor. 21 | 22 | ### Install Vue dependencies 23 | 24 | To start working with Vue as our view library, we first need to add the relevant npm package. 25 | 26 | Open a new terminal in the same directory as your running app, and run the following command: 27 | 28 | ```sh 29 | meteor npm install --save vue 30 | ``` 31 | 32 | > Note: `meteor npm` supports the same features as `npm`, though the difference can be important. Consult the [`meteor npm` documentation](https://docs.meteor.com/commandline.html#meteornpm) for more information. 33 | 34 | We also need to add the akryum:vue-component Meteor package to allow us to create files with the .vue extension, which makes it possible to create Vue components in the single file format. 35 | 36 | ```sh 37 | meteor add akryum:vue-component 38 | ``` 39 | 40 | ### Replace `blaze-html-templates` with `static-html` 41 | 42 | By default, new Meteor applications use Blaze as their templating engine and because we created our Meteor application with the `meteor create` command the `blaze-html-templates` package was included by default. While it's possible for a Meteor application to use Blaze and Vue simultaneously and we will demonstrate how to do this later, the application we're building in this tutorial does not need the `blaze-html-templates` Meteor package. 43 | 44 | First we will remove the `blaze-html-templates` Meteor package: 45 | 46 | ```sh 47 | meteor remove blaze-html-templates 48 | ``` 49 | 50 | Now, to ensure `.html` files are processed as static HTML, we will add the `static-html` Meteor package: 51 | 52 | ```sh 53 | meteor add static-html 54 | ``` 55 | 56 | The `static-html` package is not specific to Vue. It simply turns `` and `` fragments found in `.html` files into raw HTML that will be served from the Meteor web server. Later, your Vue application will render its components into this HTML. 57 | 58 | Note that both `blaze-html-templates` and `static-html` are _Meteor packages_, rather than npm packages, because they need to register _compiler plugins_ that determine how `.html` files are processed. Controlling compilation is one of several key features that make Meteor packages more powerful than npm packages. 59 | 60 | ### Replace the starter code 61 | 62 | To get started, let's replace the code of the default starter app. Then we'll talk about what it does. 63 | 64 | First, replace the content of the **`client/main.html`** file: 65 | 66 | {{> DiffBox tutorialName="simple-todos-vue" step="2.2"}} 67 | 68 | Second, replace the contents of the **`client/main.js`** module: 69 | 70 | {{> DiffBox tutorialName="simple-todos-vue" step="2.3"}} 71 | 72 | Now we need to create a new directory called `imports`, with a directory called `ui` inside of it. There will soon be other directories besides `ui` within `imports`, but we'll start with `ui`. 73 | 74 | > Note: in previous versions of Meteor, the `imports` directory was special because files outside the `imports` directory were loaded automatically when the application started, whereas files inside the `imports` directory were only loaded when imported using an `import` declaration or a `require` statement. As of Meteor 1.7, the entry point for both client and server JavaScript is determined by the `meteor.mainModule` section in `package.json`. In other words, as far as JavaScript code is concerned, the entire application now behaves as if it was inside an `imports` directory, so you don't need to worry as much about the `imports` directory now. 75 | 76 | You can read more about how imports work and how to structure your code in the [Application Structure article](http://guide.meteor.com/structure.html) of the Meteor Guide. 77 | 78 | To continue converting your app to use Vue, copy the following code into **`imports/ui/App.vue`**: 79 | 80 | {{> DiffBox tutorialName="simple-todos-vue" step="2.4"}} 81 | 82 | Now copy the following code into **`imports/ui/Task.vue`**: 83 | 84 | {{> DiffBox tutorialName="simple-todos-vue" step="2.5"}} 85 | 86 | We just added three things to our app: 87 | 88 | 1. An `App` Vue component in `imports/ui/App.vue` 89 | 2. A `Task` Vue component in `imports/ui/Task.vue` 90 | 3. Some initialization code (in our `client/main.js` client JavaScript entrypoint), in a `Meteor.startup` block, which knows how to call code when the page is loaded and ready. This code creates the root Vue instance and passes in the options object to it's contructor. This root Vue instance will be mounted using the `#app` html element. 91 | 92 | ### Define view components with Vue 93 | 94 | In Vue, single file components are created with the .vue file extension and are comprised of three sections, the script section, the template section and the style section. Within the script section you will define a Vue instance that supports the template section. This Vue instace, like the root Vue instance created in `main.js`, is fully documented in the [Vue.js Guide](https://vuejs.org/v2/guide/instance.html) 95 | 96 | ### Check the result 97 | 98 | In our browser, the app should **roughly** look like the following (though much less pretty): 99 | 100 | > #### Todo List 101 | > 102 | > - This is task 1 103 | > - This is task 2 104 | > - This is task 3 105 | 106 | If your app doesn't look like this, use the GitHub link at the top right corner of each code snippet to see the entire file, and make sure your code matches the example. 107 | 108 | ### Add CSS styles to your app 109 | 110 | In order to make your todo list more visually appealing, copy the following code into **`client/main.css`**: 111 | 112 | {{> DiffBox tutorialName="simple-todos-vue" step="2.6"}} 113 | 114 | Now that you've added the CSS, the app should look a lot nicer. Check your browser to see that the new styles have loaded. 115 | 116 | {{/template}} 117 | -------------------------------------------------------------------------------- /content/vue/step03.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step03"}} 2 | 3 | # Storing tasks in a collection 4 | 5 | {{> step03CollectionsIntro tutorialName="simple-todos-vue"}} 6 | 7 | ### 3.3 Using data from a collection inside a Vue component 8 | 9 | To use data from a Meteor collection inside a Vue component, we can use a package called `vue-meteor-tracker`, which allows us to create a "data container" to feed Meteor's reactive data into Vue's component hierarchy. 10 | 11 | ```bash 12 | meteor npm install --save vue-meteor-tracker 13 | ``` 14 | In your Vue component, we can now add a `meteor` object: 15 | 16 | ``` 17 | export default { 18 | meteor: { 19 | // Meteor specific options 20 | } 21 | } 22 | ``` 23 | 24 | {{> DiffBox step="3.4" tutorialName="simple-todos-vue"}} 25 | 26 | {{> DiffBox step="3.5" tutorialName="simple-todos-vue"}} 27 | 28 | Add a field for each reactive Meteor data source that you need access to, in this case we want access to the Tasks Mongo Collection. 29 | 30 | ``` 31 | export default { 32 | meteor: { 33 | tasks() { 34 | return Tasks.find({}).fetch(); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | When you make these changes to the code, you'll notice that the tasks that used to be in the todo list have disappeared. That's because our database is currently empty—so we need to insert some tasks! 41 | 42 | {{> step03InsertingTasksFromConsole}} 43 | 44 | {{/template}} 45 | -------------------------------------------------------------------------------- /content/vue/step04.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step04"}} 2 | 3 | # Adding tasks with a form 4 | 5 | In this step, we'll add an input field for users to add new tasks to the list. 6 | 7 | First, let's add a form to our `App` component's template section and a `newTask` field to the `data` object in our Vue instance: 8 | 9 | {{> DiffBox step="4.1" tutorialName="simple-todos-vue"}} 10 | 11 | This form will have an input element added to it that has a `v-model` attribute. The `newTask` data field will now be bound via two-way binding to the input element's value. 12 | 13 | You can also see that the `form` element has a `@submit.prevent` attribute that references a method called `handleSubmit` that we will later be defined in the component's `methods` object. In Vue, this is how you listen to browser events, like the submit event on the form. 14 | 15 | We can now add that `handleSubmit` method to our `App` component's `methods` object: 16 | 17 | {{> DiffBox step="4.2" tutorialName="simple-todos-vue"}} 18 | 19 | Now your app has a new input field. To add a new task, just type into the input field and hit enter. If you open a new browser window and open the app again, you'll see that the list is automatically synchronized between all clients. 20 | 21 | ### Listening for events in Vue 22 | 23 | As you can see, in Vue you handle DOM events by directly referencing a method on the component. Inside the event handler, you can reference elements from the component by giving them a `v-model` property and adding a field to the data property. Read more about the different kinds of events Vue supports, and how the event system works, in the [Vue.js Guide](https://vuejs.org/v2/guide/#Handling-User-Input). 24 | 25 | ### Inserting into a collection 26 | 27 | Inside the event handler, we are adding a task to the `tasks` collection by calling `Tasks.insert()`. We can assign any properties to the task object, such as the time created, since we don't ever have to define a schema for the collection. 28 | 29 | Being able to insert anything into the database from the client isn't very secure, but it's okay for now. In step 10 we'll learn how we can make our app secure and restrict how data is inserted into the database. 30 | 31 | ### Sorting our tasks 32 | 33 | Currently, our code displays all new tasks at the bottom of the list. That's not very good for a task list, because we want to see the newest tasks first. 34 | 35 | We can solve this by sorting the tasks using the `createdAt` field that is automatically added by our new code. Just add a sort option to the `Tasks.find` call inside the `App` component: 36 | 37 | {{> DiffBox step="4.3" tutorialName="simple-todos-vue"}} 38 | 39 | Let's go back to the browser and make sure this worked: any new tasks that you add should appear at the top of the list, rather than at the bottom. 40 | 41 | In the next step, we'll add some very important todo list features: checking off and deleting tasks. 42 | {{/template}} 43 | -------------------------------------------------------------------------------- /content/vue/step05.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step05"}} 2 | 3 | # Checking off and deleting tasks 4 | 5 | Until now, we have only interacted with a collection by inserting documents. Now, we will learn how to update and remove them. 6 | 7 | Let's add two new elements to our `task` component, a checkbox and a delete button, with event handlers for both: 8 | 9 | {{> DiffBox step="5.1" tutorialName="simple-todos-vue"}} 10 | 11 | ### Update 12 | 13 | In the code above, we call `Tasks.update` to check off a task. 14 | 15 | The `update` function on a collection takes two arguments. The first is a selector that identifies a subset of the collection, and the second is an update parameter that specifies what should be done to the matched objects. 16 | 17 | In this case, the selector is just the `_id` of the relevant task. The update parameter uses `$set` to toggle the `checked` field, which will represent whether the task has been completed. 18 | 19 | ### Remove 20 | 21 | The code from above uses `Tasks.remove` to delete a task. The `remove` function takes one argument, a selector that determines which item to remove from the collection. 22 | 23 | {{/template}} 24 | -------------------------------------------------------------------------------- /content/vue/step07.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step07"}} 2 | 3 | # Storing temporary UI data in Vue components data field 4 | 5 | In this step, we'll add a client-side data filtering feature to our app, so that users can check a box to only see incomplete tasks. We're going to learn how to use Vue's component state to store temporary information that is only used on the client. 6 | 7 | First, we need to add a checkbox to our `App` component: 8 | 9 | {{> DiffBox step="7.1" tutorialName="simple-todos-vue"}} 10 | 11 | You can see that it reads from `this.hideCompleted`. We'll need to initialize the value of `this.hideCompleted` in the component's data object: 12 | 13 | {{> DiffBox step="7.2" tutorialName="simple-todos-vue"}} 14 | 15 | We can update `this.hideCompleted` from an event handler directly, which will then cause the component to re-render: 16 | 17 | {{> DiffBox step="7.3" tutorialName="simple-todos-vue"}} 18 | 19 | Now, we need to update the list of tasks to filter out completed tasks when `this.hideCompleted` is true: 20 | 21 | {{> DiffBox step="7.4" tutorialName="simple-todos-vue"}} 22 | 23 | Now if you check the box, the task list will only show tasks that haven't been completed. 24 | 25 | ### One more feature: Showing a count of incomplete tasks 26 | 27 | Now that we have written a query that filters out completed tasks, we can use the same query to display a count of the tasks that haven't been checked off. To do this we need to fetch a count in the meteor object of the Vue instace. Since we already have the data in the client-side collection, adding this extra count doesn't involve asking the server for anything. 28 | 29 | {{> DiffBox step="7.5" tutorialName="simple-todos-vue"}} 30 | 31 | {{> DiffBox step="7.6" tutorialName="simple-todos-vue"}} 32 | 33 | {{/template}} 34 | -------------------------------------------------------------------------------- /content/vue/step08.md: -------------------------------------------------------------------------------- 1 | {{#template name="vue-step08"}} 2 | 3 | # Adding user accounts 4 | 5 | Meteor comes with an accounts system and a drop-in login user interface that lets you add multi-user functionality to your app in minutes. 6 | 7 | > Currently, this UI component uses Blaze, Meteor's default UI engine. 8 | 9 | To make use of the accounts system and UI, we need to add the relevant packages. In your app directory, run the following command: 10 | 11 | ```bash 12 | meteor add accounts-ui accounts-password 13 | ``` 14 | 15 | ### Wrapping a Blaze component in Vue 16 | 17 | To use the Blaze UI component from the `accounts-ui` package inside of a Vue component, we need to make use of the Vue and Blaze Integration package. So we will first need to install this package. 18 | 19 | ```sh 20 | meteor add vuejs:blaze-integration 21 | ``` 22 | 23 | Let's include the `loginButtons` template in the App component by adding the following markup ``: 24 | 25 | {{> DiffBox step="8.3" tutorialName="simple-todos-vue"}} 26 | 27 | We will then need to add a new file to configure the accounts package `imports/startup/accounts-config.js`. 28 | 29 | Then, add the following code to configure the accounts UI to use usernames instead of email addresses: 30 | 31 | {{> DiffBox step="8.4" tutorialName="simple-todos-vue"}} 32 | 33 | We also need to import that configuration file into our client side entrypoint: 34 | 35 | {{> DiffBox step="8.5" tutorialName="simple-todos-vue"}} 36 | 37 | ### Adding user-related functionality 38 | 39 | Now users can create accounts and log into your app! This is very nice, but logging in and out isn't very useful yet. Let's add two features: 40 | 41 | 1. Only display the new task input field to logged in users 42 | 2. Show which user created each task 43 | 44 | To do this, we will add two new fields to the `tasks` collection: 45 | 46 | 1. `owner` - the `_id` of the user that created the task. 47 | 2. `username` - the `username` of the user that created the task. We will save the username directly in the task object so that we don't have to look up the user every time we display the task. 48 | 49 | First, let's add some code to save these fields into the `handleSubmit` event handler: 50 | 51 | {{> DiffBox step="8.6" tutorialName="simple-todos-vue"}} 52 | 53 | Modify the data container to get information about the currently logged in user: 54 | 55 | {{> DiffBox step="8.7" tutorialName="simple-todos-vue"}} 56 | 57 | Then, we can wrap our form in a `