├── .gitignore
├── tweet.png
├── tweet2.png
├── complete-app.gif
├── loginButtons.png
├── empty-chat-app.png
├── first-message.gif
├── anonymous-chat2x.gif
├── first-message-2x.gif
├── meteor-data-flow.gif
├── ios-simulator-small.png
├── full-stack-db-driver.png
├── chat-tutorial-part-5.md
├── chat-tutorial-part-1.md
├── chat-tutorial-part-3.md
├── chat-tutorial-part-2.md
└── chat-tutorial-part-4.md
/.gitignore:
--------------------------------------------------------------------------------
1 | unused/*
2 |
--------------------------------------------------------------------------------
/tweet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/tweet.png
--------------------------------------------------------------------------------
/tweet2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/tweet2.png
--------------------------------------------------------------------------------
/complete-app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/complete-app.gif
--------------------------------------------------------------------------------
/loginButtons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/loginButtons.png
--------------------------------------------------------------------------------
/empty-chat-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/empty-chat-app.png
--------------------------------------------------------------------------------
/first-message.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/first-message.gif
--------------------------------------------------------------------------------
/anonymous-chat2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/anonymous-chat2x.gif
--------------------------------------------------------------------------------
/first-message-2x.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/first-message-2x.gif
--------------------------------------------------------------------------------
/meteor-data-flow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/meteor-data-flow.gif
--------------------------------------------------------------------------------
/ios-simulator-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/ios-simulator-small.png
--------------------------------------------------------------------------------
/full-stack-db-driver.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/meteor/chat-tutorial/HEAD/full-stack-db-driver.png
--------------------------------------------------------------------------------
/chat-tutorial-part-5.md:
--------------------------------------------------------------------------------
1 | # Meet Meteor Part Five: Deploying and Building for Mobile
2 |
3 | This is the last of a 5 part series:
4 |
5 | 1. [Install Meteor and create your app](chat-tutorial-part-1.md)
6 | 2. [Implement basic chat](chat-tutorial-part-2.md)
7 | 3. [Add user accounts](chat-tutorial-part-3.md)
8 | 4. [Refine the UI of the app](chat-tutorial-part-4.md)
9 | 5. Deploy the app and create a mobile version
10 |
11 | ## Let's show the world: Deploying
12 |
13 | We've made a chat application with user accounts, a scrollable window, and visible notification when new messages come while we are scrolling through the chat history. This app is ready to show to the world! In other frameworks, it would be time to figure out how to bundle up the app and get it on a server somewhere. We would also have to decide whether it was worth the expense and hassle to do this. With Meteor, it's free and dead-simple. From your `chat/` directory (and choosing a unique name instead of "app-name"):
14 |
15 | ```bash
16 | meteor deploy app-name
17 | # choose a unique name instead of 'app-name'
18 | ```
19 |
20 | This will deploy your app to a server provided by Meteor at the address `app-name.meteor.com`. This is a free service with no time limit that Meteor provides to all of its users. This service works fine for small apps, and it includes MongoDB. The Meteor tool will inform you of progress as the app is bundled and deployed, and it will inform you when your app is serving. Once it's finished, check out your chat app online. You can now share this with other people and chat from across the world!
21 |
22 | ## Deploying to other servers
23 |
24 | If you outgrow the free server or need more control of your environment, you can deploy your app to your own server. You can deploy to any server that supports Node.js and Fibers.
25 |
26 | ```bash
27 | meteor build meteor-chat
28 | ```
29 |
30 | It will output a directory that contains a tarball of the needed files as well as instructions on how to deploy the app.
31 |
32 | ## Mobile builds
33 |
34 | Although web apps often work great on phones, it is often desirable to have a true native app so you can sell it in app stores, get native access to mobile I/O like the camera and geolocation, and also just so you'll have a place on people's home screen. Meteor supports building for iOS (if you have a mac) and Android (if you have Mac/Linux - no Windows yet) using Cordova.
35 |
36 | You can see the current platforms by typing
37 |
38 | ```bash
39 | meteor list-platforms
40 | # browser
41 | # server
42 | ```
43 |
44 | You can add either iOS or android:
45 |
46 | ```bash
47 | meteor add-platform ios
48 | meteor add-platform android
49 | ```
50 |
51 | Android development will require you to install the correct Android SDK. You can find instructions on the Meteor wiki for [Mac](https://github.com/meteor/meteor/wiki/Mobile-Development-Install:-Android-on-Mac) and [Linux](https://github.com/meteor/meteor/wiki/Mobile-Development-Install:-Android-on-Linux).
52 |
53 | Once you have added the platform(s) that you wish to use, you can see them by running `meteor list-platforms` again. You can also run your app in the simulator for the platform you chose:
54 |
55 | ```bash
56 | meteor run ios
57 | # or
58 | meteor run android
59 | ```
60 |
61 | 
62 |
63 | Or you can even run it on a connected device (requires an Apple developer's account for iOS):
64 |
65 | ```bash
66 | meteor run ios-device -p 3000
67 | # or
68 | meteor run android-device -p 3000
69 | ```
70 |
71 | Finally, when you run `meteor build myapp`, the directory that gets created will now contain the Android `apk` file and/or an Xcode project directory.
72 |
73 | ## Conclusion
74 |
75 | In this series, we've been through every phase of an app's lifecycle, from project creation to deployment, including mobile as well as browser. We added packages, took advantage of Meteor's reactivity, and easily manipulated data while differentiating users and securing the app. Some of the features we saw:
76 |
77 | * Rapid installation, project creation, and deployment
78 | * Hot code push
79 | * Multi-platform support
80 | * Full-stack packages
81 | * Reactive rendering
82 | * Database synchronization between server and multiple clients
83 | * Intuitive code that can run with the same API on the client and server
84 | * Minimal boilerplate code
85 |
86 | We also were able to go deep enough to create a more subtle user experience around viewing messages and notification when new messages arrived. All of this was done with less than 100 lines of JavaScript.
87 |
88 | If you would like to learn more about Meteor, there is much more to learn on the [Meteor resources page](https://www.meteor.com/tools/resources). The Meteor community is very active on the [Meteor forums](https://forums.meteor.com/) and you can get technical help at [Stack Overflow](http://stackoverflow.com/questions/tagged/meteor). Finally, you can meet fellow Meteorites at [meetups around the world](http://www.meetup.com/pro/meteor/). I hope this gave you a feel for what is possible with Meteor. Now you are ready to begin building your own creations. Enjoy!
89 |
--------------------------------------------------------------------------------
/chat-tutorial-part-1.md:
--------------------------------------------------------------------------------
1 | # Meet Meteor: Build a chat app in <100 lines of JavaScript - Part One
2 |
3 | [Meteor](http://www.meteor.com) has been generating a level of excitement that we haven't seen from web developers in years.
4 |
5 | 
6 |
7 | 
8 |
9 | In this tutorial, we will see what the excitement is about while exploring the core features and building a functional chat application with very little code. This tutorial will consist of 5 parts:
10 |
11 | 1. Install Meteor and create your app
12 | 2. [Implement basic chat](chat-tutorial-part-2.md)
13 | 3. [Add user accounts](chat-tutorial-part-3.md)
14 | 4. [Refine the UI of the app](chat-tutorial-part-4.md)
15 | 5. [Deploy the app and create a mobile version](chat-tutorial-part-5.md)
16 |
17 | ## What is Meteor?
18 | Meteor is a full-stack platform that includes:
19 |
20 | * a CLI tool for creating, modifying, debugging, and deploying projects
21 | * an all-JavaScript codebase with support for ES2015/ES6 across all platforms (via Babel)
22 | * a build system that coordinates code for the server, the browser, and mobile clients for iOS and Android
23 | * integrated features that are essential for modern apps, such as websockets, live database query streaming, and reactive user interfaces.
24 |
25 | Meteor shows itself best in action, though, so let's jump straight into the code.
26 |
27 | ## Install Meteor and create a project
28 | You can install Meteor in a couple of minutes. You can use [the Windows installer](https://install.meteor.com/windows), or just paste the following into your terminal on Mac and Linux:
29 |
30 | ```bash
31 | curl https://install.meteor.com/ | sh
32 | ```
33 |
34 | This will install the Meteor CLI tool. You are now ready to create a project. Run these commands in the terminal:
35 |
36 | ```bash
37 | meteor create chat # => chat created. To run...
38 | cd chat
39 | meteor remove insecure # => insecure removed from your project
40 | meteor remove autopublish # => autopublish removed from your project
41 | ```
42 |
43 | This has scaffolded a small app called `chat`. We also removed a couple of packages, `insecure` and `autopublish`. These packages are designed to make prototyping simpler, but we won't use them here.
44 |
45 | Now, in the terminal(while still in the `chat/` directory), type:
46 |
47 | ```
48 | meteor
49 | ```
50 |
51 | This tells Meteor to run your app on a development server. As Meteor will tell you in the output, you can view the app by opening your browser to [localhost:3000](http://localhost:3000). You can try clicking the button in the app.
52 |
53 | Let's replace the code in this app (you can leave the server running). Open the chat directory in your code editor. You will see the app is constructed of 3 files: chat.html, chat.js, and chat.css.
54 |
55 | * Delete all the code in chat.html, chat.js, and chat.css so that they are all empty files.
56 |
57 | ## Add the CSS
58 |
59 | Let's go ahead and paste in some css code so the app will look good (we'll ignore the css from here on):
60 |
61 | * Copy the CSS from [here](https://github.com/rdickert/chat-tutorial-code/blob/master/chat1.css), paste it into chat.css, and save.
62 |
63 | ## Structure the HTML
64 | Now add the following to chat.html (make sure you keep comments, enclosed in `` in html – we'll use those as markers to add more code later):
65 |
66 | ```html
67 |
68 | Meteor Chat
69 |
70 |
71 |
72 |
73 |
74 |
Meteor Chat
75 |
76 |
77 |
78 |
83 |
84 |
85 |
86 |
87 | ```
88 |
89 | We now have a skeleton. If everything went well, you should see an empty chat app:
90 |
91 | 
92 |
93 | ## Hot code push
94 |
95 | You may have noticed that when you hit save, the browser updated without restarting the server. This Meteor feature is called **hot code push**. When Meteor detects a file change, it rebuilds your app and pushes it to all connected clients.
96 |
97 | Go ahead and try changing the name or some of the styling of the app. This facilitates a very interactive kind of development. This is possible with some other frameworks by using a tool like LiveReload, but with Meteor, it comes built-in.
98 |
99 | Not only is hot code push great for development, but it also works in production, allowing you to push updates to connected users live – and Meteor has a mechanism for preserving their state so they don't lose their place in the app. This is a great example of how the Meteor platform looks at the entire development and deployment process, not just code framework features.
100 |
101 | ## Conclusion: Ready to rock
102 |
103 | We've installed Meteor and scaffolded our chat app. Notice how little effort this took. Because Meteor has simple installation, easy app creation, an included development server, and hot code push, we are already focusing on our app rather than spending time installing Node.js, MongoDB, a templating system, or other components. In [part two](chat-tutorial-part-2.md), we'll complete the main functionality of the app: rendering messages to all connected clients.
104 |
105 |
106 |
--------------------------------------------------------------------------------
/chat-tutorial-part-3.md:
--------------------------------------------------------------------------------
1 | # Meet Meteor Part Three: User Accounts
2 |
3 | This is part 3 of a 5 part series:
4 |
5 | 1. [Install Meteor and create your app](chat-tutorial-part-1.md)
6 | 2. [Implement basic chat](chat-tutorial-part-2.md)
7 | 3. Add user accounts
8 | 4. [Refine the UI of the app](chat-tutorial-part-4.md)
9 | 5. [Deploy the app and create a mobile version](chat-tutorial-part-5.md)
10 |
11 | ## Adding accounts
12 |
13 | We have a working chat mechanism, thanks to Meteor's built-in websocket support, full-stack database driver, and reactive rendering. But chat requires user accounts so we can identify who's talking. Many frameworks would require a full tutorial just on this topic, and if we want to add OAuth login via Facebook, Google, etc, that can be a week's project. Let's see if we can do a little better than that.
14 |
15 | To add front- and back-end accounts functionality, go to your `chat/` directory and type the following:
16 |
17 | ```bash
18 | meteor add accounts-ui
19 | meteor add accounts-password
20 | meteor add accounts-facebook
21 | # or you can type meteor add accounts-ui accounts-password accounts-facebook on one line
22 | ```
23 |
24 | The packages you just added using the Meteor tool provide a complete end-to-end accounts system:
25 |
26 | * `accounts-ui` provides front-end UI elements for account management. It automatically includes another package, `accounts-base`, that adds core client and server infrastructure. In other apps, you might choose to build your own UI on the base accounts functionality, or perhaps to use a different UI package that is themed with CSS framework like Bootstrap, Ionic, or Semantic UI. [You can see some of these options on Atmosphere,](https://atmospherejs.com/meteor/accounts-ui?q=accounts) Meteor's package package repository.
27 | * `accounts-password` provides functionality for pure username/password accounts that are stored on your server.
28 | * `accounts-facebook` provides OAuth functionality for Facebook. There are several other OAuth sites supported (including Github, Google, Twitter, and Weibo), and you can also extend it yourself.
29 |
30 | ## Add a login widget
31 |
32 | We'll need some UI so we can log in to the app. Meteor's accounts system provides a simple widget we can include with `{{> loginButtons}}`. Add that one-liner right after the h1 tag in the header:
33 |
34 | ```html
35 |
36 |
Meteor Chat
37 | {{> loginButtons}}
38 |
39 | ```
40 |
41 | When you hit save, you'll see the widget. You can click on it to expand it.
42 |
43 | 
44 |
45 | There's our Facebook button in red. If you click on it, you'll see that it does a one-time configuration. Once you enter the required information from your Facebook developer account, anyone with a Facebook account will be able to log in. Adding OAuth in other frameworks can be a week's work, but here we did it in few minutes.
46 |
47 | We'll stick with simple password-based authentication for the rest of this tutorial.
48 |
49 | ## Configure accounts
50 |
51 | If you look at the login widget, it asks for the user's email address. For us it would be simpler to have them choose a username. Let's add configuration under the line `/*account config*/` in chat-demo.js.
52 |
53 | ```javascript
54 | /*account config*/
55 | Accounts.ui.config({
56 | passwordSignupFields: "USERNAME_ONLY"
57 | });
58 | ```
59 |
60 | When you hit save with the widget open, you'll see hot code push in action, changing from an email login to a username-based one. Note how the widget stayed open, maintaining the state of the app even though we swapped out the underlying code. Don't log in just yet.
61 |
62 | ## Secure the app and track the username
63 |
64 | Next, let's update our method. Let's add the username to the message, and let's disable adding messages if you are not logged in. You'll need to add these lines under `/* add authentication here */`
65 |
66 | ```javascript
67 | if (! Meteor.userId()) {
68 | throw new Meteor.Error("not-authorized");
69 | }
70 | ```
71 |
72 | Then change `username: "anonymous"` to return the username through the accounts system. Your final method code will look like this:
73 |
74 | ```javascript
75 | Meteor.methods({
76 | sendMessage: function (message) {
77 | if (! Meteor.userId()) {
78 | throw new Meteor.Error("not-authorized");
79 | }
80 |
81 | Messages.insert({
82 | messageText: message,
83 | createdAt: new Date(),
84 | username: Meteor.user().username // <-add real username
85 | });
86 | }
87 | });
88 | ```
89 |
90 | ## Hide the messages form when logged out
91 |
92 | You have now secured your method. Try putting in new messages from the app and from the browser console. If the console is open, you will see the "not authorized" error. This is the behavior we want, but there is a UX problem: Showing the message input field to an unauthenticated user is bad because it will silently fail when they try to use it. It would be better to hide the input. Let's do that. Go back to the HTML. The accounts package gives us access to another helper called `{{currentUser}}` which will return their account ID if they are logged in but will be undefined if not. We can use this to our advantage by wrapping the input form in an `#if` helper. Update your `footer` element with the `#if`/`/if` helper pair so it looks like this:
93 |
94 | ```javascript
95 |
102 | ```
103 |
104 | When you hit save (if you are still logged out), the input field should be gone. Now create an account using the widget. The input field reappears reactively. We didn't have to write code to show/hide, add classes, or otherwise manipulate the DOM. We just declaratively told Meteor how to render the view, and Meteor changed the underlying DOM for us.
105 |
106 | Now that you are logged in, add a message. If everything is correct, you will see your message logged with the correct username. Logging out should cause the input to disappear as well.
107 |
108 | ##Conclusion: This is a real chat app
109 |
110 | Our app now handles user authentication and controls who can add messages. It is secured against anonymous use, and the interface updates reactively based on the login status of the user. With a few packages, we got integrated accounts behaviors for the server and the client, including Oauth integration, and we even got a visual component, the login widget, for free. This is what a full-stack package system can do. There are other [account UIs available on Atmosphere](https://atmospherejs.com/?q=account%20ui) to give you more control of the look and feel of your app, or you can create your own.
111 |
112 | We could stop here, but let's go a little deeper into the UI. It's great that we got something very rudimentary, but how does Meteor handle more subtle UI interactions? The [fourth installment](chat-tutorial-part-4.md) of this series will take you into slightly more advanced UI work.
113 |
--------------------------------------------------------------------------------
/chat-tutorial-part-2.md:
--------------------------------------------------------------------------------
1 | ## Meet Meteor Part Two: Core chat functionality
2 |
3 | This is part 2 of a 5 part series:
4 |
5 | 1. [Install Meteor and create your app](chat-tutorial-part-1.md)
6 | 2. Implement basic chat
7 | 3. [Add user accounts](chat-tutorial-part-3.md)
8 | 4. [Refine the UI of the app](chat-tutorial-part-4.md)
9 | 5. [Deploy the app and create a mobile version](chat-tutorial-part-5.md)
10 |
11 | If you completed part 1, you have a scaffold ready to go. Let's fill that in and get messages moving between all connected clients.
12 |
13 | ## Render messages in HMTL
14 |
15 | Let's tell the app how to render chat messages. We are using Meteor's Blaze view layer (you can use [Angular](), [React](), and other view layers if you prefer for future apps). Blaze uses [Handlebars](http://handlebarsjs.com/) syntax, which uses curly braces to add dynamic elements to our HTML. We will use the `#each` helper to render the `message` template one time for each message. In chat.html, replace `` with:
16 |
17 | ```html
18 |
23 | ```
24 |
25 | Then add the `message` template at the bottom of the file:
26 |
27 | ```html
28 |
29 |
30 |
{{username}}
31 |
{{messageText}}
32 |
33 |
34 | ```
35 | This template will be able to access the `username` and `text` attributes of the database document we use as its context.
36 |
37 | ## Add the database and client cache
38 | Meteor comes with a full-stack reactive database driver. MongoDB is installed on the server, and Meteor provides an in-memory client data cache called Minimongo on the browser. Minimongo can be queried with the same API as MongoDB, allowing lightning-fast local access to a subset of the master database. Minimongo's data is kept in sync with the master database in real-time using Meteor's Distributed Data Protocol (DDP) over websockets. On the server, data changes are observed real-time using Meteor's Livequery.
39 |
40 | 
41 |
42 | Setting all this up would be a huge undertaking if you did it yourself, but Meteor has it covered. Here is all the code you need to set it up. Put the following into chat.js (again `/* comments */` will show us where to add new code as we build up the functionality of the app).
43 |
44 | ```javascript
45 | Messages = new Mongo.Collection("msgs");
46 |
47 | Meteor.methods({
48 | sendMessage: function (messageText) {
49 | /* add authentication here */
50 |
51 | Messages.insert({
52 | messageText: messageText,
53 | createdAt: new Date(),
54 | username: "anonymous"
55 | });
56 | }
57 | });
58 |
59 | if (Meteor.isServer) {
60 | // This code only runs on the server
61 | Meteor.publish("messages", function () {
62 | return Messages.find({}, {sort: {createdAt: -1}, limit: 5});
63 | });
64 | }
65 |
66 | /* scrolling code */
67 |
68 | if (Meteor.isClient) {
69 | // This code only runs on the client
70 | Meteor.subscribe("messages");
71 |
72 | /* helper code */
73 |
74 | /*chat window scrolling*/
75 |
76 | /*events*/
77 |
78 | /*account config*/
79 | }
80 | ```
81 |
82 | One thing you may notice about the code is that we are able to specify where code will run with `if (Meteor.isClient` and `if (Meteor.isServer)`[^client-server-folders]. That means the the first line, `Messages = new Mongo.Collection("msgs");` runs on both client and server. On the server, it creates a MongoDB collection called `Messages` (referenced as `msgs` internally by MongoDB), and on the client, it makes a similarly named collection with Minimongo.
83 |
84 | The way we will track messages is by storing them in the Messages collection. The next line creates a Meteor method called `sendMessage`. This also runs on both the client (storing the message locally) and the server (adding it to the master database). We use the MongoDB `insert` method to do this. We mark when the message was created with the current time (`new Date()`) and log the user as "anonymous" for now. Meteor will manage the network connection for us.
85 |
86 | To synchronize the server's database with the client cache, we connect them with a publication. You can see this in starting in the `if (Meteor.isServer)` block. On the server, we create a publication called `"messages"`, and on the client, we subscribe to it. Meteor will synchronize these over websockets using DDP. The contents of the publication are set by a [standard MongoDB query](https://docs.mongodb.org/manual/tutorial/query-documents/) that returns the 5 most recent messages. (Choosing a small number is a "cheat" so we don't have to deal with message window scrolling yet.)
87 |
88 | The publication is very much like setting up an HTTP GET endpoint, but there is a key difference. When data changes the results of a query, all subscribed clients will automatically get the updated results. We set up the endpoints with a simple pub/sub relationship, and Meteor sets up the websocket and manages synchronization of the data using DDP. DDP provides a clear protocol and connection point, not just for the Meteor client, but also for any client that follows the protocol. DDP can therefore act as a **REST for websockets**. There are DDP clients in Node, Python, and other languages, as well as native iOS and Android packages.
89 |
90 |
91 | ## Set up the the recentMessages helper
92 |
93 | In our HTML, we told the template to iterate over an array of messages using `{{ #each recentMessages }}`. We still need to provide the array of messages from the database. Replace `/* helper code */` with the following inside the `if (Meteor.isClient)` block in the .js file:
94 |
95 | ```javascript
96 | Template.body.helpers({
97 | recentMessages: function () {
98 | return Messages.find({}, {sort: {createdAt: 1}});
99 | },
100 | /* unread message helper */
101 | });
102 | ```
103 |
104 | `Template.body.helpers` is what we use to define the helper functions on the main body of the app (you can replace `body` with a template name to set up helpers for other templates). `recentMessages` is a function that just returns a MongoDB query result.[^cursor-vs-array] This time it's on the client, so this is on Minimongo, and it will be sorted in forward chronological order so that messages read from top to bottom. Notice how Minimongo allows us to continue working with the database with exactly the same API as on the server, allowing code to be shared or moved between client and server with no changes. When people talk about *isomorphic JavaScript*, this is what they mean, but this is a level deeper, because it's the same API, not just the same language. In most frameworks, that database query would look different on the client (using Angular, Backbone, etc.) and the server (using native MongoDB or an ORM like Mongoose).
105 |
106 | ## Post your first message
107 |
108 | Believe it or not, our app is now able to render text messages. [Go into the browser console](http://webmasters.stackexchange.com/questions/8525/how-to-open-the-javascript-console-in-different-browsers) and call the `sendMessage` method with the following:
109 |
110 | ```
111 | Meteor.call('sendMessage', 'hello from the browser console')
112 | ```
113 |
114 | 
115 |
116 | You will see your message rendered in the app. Go ahead and add another message or two if you like. We didn't write any code to update the browser, so how did this work? When we called the method, it ran on the browser first, doing an insert on Minimongo. The templates we set up to render the messages were automatically wired by Meteor to watch for changes to their data source (the `recentMessages` helper). When Minimongo updated itself, that changed the result of the `recentMessages` helper, which caused the template to re-render. This ability to rerender when data changes is called **reactivity**. Not only does this save you from writing a bunch of code, but it allows you to create your app more *declaratively* – that is, you write the HTML templates as if you were rendering the app the very first time, and Meteor will update it to reflect changes in the data. Meteor is actually very careful about what it updates, changing the minimum number of things necessary. Because it doesn't rerender the entire template, user input fields and DOM changes from things like jQuery plugins will not be overwritten, avoiding a common problem in reactive frameworks.
117 |
118 | It is worth noting that Meteor is watching the database, not just responding to framework hooks. You can prove this by going into the MongoDB console. In the terminal from within your project (and with the server running), type `meteor mongo`. The meteor tool will put you into MongoDB's server console. You can now add another message with the following (remember, internally, MongoDB calls our collection `msgs`:
119 |
120 | ```
121 | db.msgs.insert({text: 'hello from Mongo', createdAt: new Date, username: 'mongo console'})
122 | ```
123 | You should see this appear immediately in your chat window. Meteor is able to watch Mongo by tailing its oplog, a change log that is used for replication within MongoDB clusters. This means that you can use the database for an integration point between a meteor app and and an existing app. If another app can write to MongoDB, Meteor can watch those changes and report the results in real-time.
124 |
125 | ## React to user Input
126 |
127 | Let's make the app post messages typed into the UI. Our HTML `` is inside a form, so we can respond to `submit` events. Meteor's syntax for events is similar to Backbone's. Replace `/*events*/` in the JavaScript file:
128 |
129 | ```
130 | Template.body.events({
131 | "submit .new-message": function (event) {
132 | var text = event.target.text.value;
133 |
134 | Meteor.call("sendMessage", text);
135 |
136 | event.target.text.value = "";
137 | event.preventDefault();
138 | },
139 |
140 | /* scroll event */
141 |
142 | });
143 | ```
144 |
145 | When it receives a `submit` event on the input (`class="new-message"`), it will retrieve the text, send it with the `sendMessage` method just like we did from the browser console, and blank out the input, using `event.preventDefault()` to keep the page from refreshing. Go ahead and add a message in the app. Sure enough, you can now add messages.
146 |
147 | ##Now for the cool part
148 | Let's open a second browser and point it to [localhost:3000](http://localhost:3000/). Put it side-by-side with the first browser. Now type a message in one of the browsers. You'll see that it appears in other as well.
149 | 
150 |
151 | With Meteor, we got the "hard part" of the chat app for free! We didn't have to write any code to send chat messages down to the other client. Meteor handles that with Livequery and pub/sub.
152 |
153 |
154 | ##Meteor data flow
155 |
156 | 
157 |
158 | Let's review how this app works. When you hit enter to send your message, Meteor does the insert locally on minimongo, which reactively updates the DOM right away based on your templates. This is called "optimistic UI," because it is assuming that the change at the client will be accepted by the server. This makes the app feel very snappy and responsive because we don't have to wait for the server round-trip to display the result. At the same time, it sends the method to the server and runs an insert against the server database. The server database is the "single source of truth." In most cases, there will be no change. But in a chat app, it's possible that someone else will have sent a message just before ours, so the server will insert that above ours. The final version is then pushed down to the client via the subscription. Minimongo will discard its optimistic result and accept the server's result, updating the message window if necessary.
159 |
160 | ## Conclusion: A working (anonymous) chat app
161 |
162 | We already have the working core of a chat app. Here's what we did:
163 |
164 | * installed Meteor and created a project in a few minutes
165 | * wrote declarative templates
166 | * set up real-time database connectivity using pub/sub
167 | * allowed database updates with Meteor methods
168 | * responded to UI events
169 |
170 | Also take note of what we didn't have to do:
171 |
172 | * set up the server database
173 | * create a client-side cache
174 | * establish websockets
175 | * write code to monitor the database
176 | * write code to mutate the DOM
177 |
178 | Not bad for a few lines of code! The main thing missing is that messages are anonymous, which severely limits our chat app's usefulness. We need people to be able to log in so we can keep track of who's who. We can also prevent anonymous messages altogether to show the basics of securing the app. We'll cover these in [part three of the tutorial](chat-tutorial-part-3.md).
179 |
180 |
181 | [^client-server-folders]: You can specify that entire files only load on the client or server by placing them in folders called `client/` or`server/` in your project root.
182 |
183 | [^cursor-vs-array]: Note that in this code we return a *cursor*, not an array. To get the array of documents, we could have called the `fetch()` method: `return Messages.find({}, {sort: {createdAt: 1}}).fetch();`. A [cursor](http://docs.meteor.com/#/full/mongo_cursor) is an object that defines a result set that can be accessed via `fetch`, `map`, or `forEach` (or quantified with `count`). By handing Meteor the cursor instead of the final array, we allow Meteor to make fine-grained updates to our app. Meteor will automatically fetch/update data as needed.
--------------------------------------------------------------------------------
/chat-tutorial-part-4.md:
--------------------------------------------------------------------------------
1 | # Meet Meteor Part Four: Better UI
2 |
3 | This is part 4 of a 5 part series:
4 |
5 | 1. [Install Meteor and create your app](chat-tutorial-part-1.md)
6 | 2. [Implement basic chat](chat-tutorial-part-2.md)
7 | 3. [Add user accounts](chat-tutorial-part-3.md)
8 | 4. Refine the UI of the app
9 | 5. [Deploy the app and create a mobile version](chat-tutorial-part-5.md)
10 |
11 | ## UX Issues
12 | We've gotten an amazingly long way with in our chat app with a small amount of code. We have used Meteor's core reactivity and database synchronization features to render messages in real-time to all clients, and we've used Meteor's accounts system to keep track of users with very little effort. We've definitely cheated on the chat window rendering, though. Right now, only 5 chats are stored locally at all. After that number, when another message is added, the oldest one is deleted from the client cache, even though those messages still exist on the server. While this is a great demonstration of how the client cache can be a subset of the master database, a real chat app should allow you to scroll through old messages. This brings with it some UI complexity, however. In addressing these, we will learn about reactive variables and see a couple of more advanced hooks that allow us to trigger actions at key moments in an app's lifecycle. This will allow us to explore what it takes to move a small bit of functionality out of the "toy" phase to behave in a way that is sophisticated enough to satisfy real users.
13 |
14 | ## View the message history
15 | Let's go ahead and remove the limit on the amount of messages synced to the client. Let's simplify the `publish` function (in the `if (Meteor.isServer)` code block) to return all messages:
16 |
17 | ```
18 | Meteor.publish("messages", function () {
19 | return Messages.find();
20 | });
21 | ```
22 |
23 | ## Make a scrollToBottom() function
24 | Once you've done this, it becomes obvious why we were cheating: The message window shows the top of the list, which shows the oldest messages. You have to scroll down to see the latest. Not only that, but if you add a new message, you'll have to scroll to see that one too. We can solve this by adding a function to scroll the window to the bottom any time a message is rendered.
25 |
26 | First, let's make a function that will scroll the window to the bottom using jQuery. We will also add a boolean called `autoScrollingIsActive` which we will use later. You can add this just below the publish code, replacing `/* scrolling code */` as shown:
27 |
28 | ```javascript
29 | if (Meteor.isServer) {
30 | // This code only runs on the server
31 | Meteor.publish("messages", function () {
32 | return Messages.find();
33 | });
34 | }
35 |
36 | var autoScrollingIsActive = false;
37 | /* reactive var here */
38 | scrollToBottom = function scrollToBottom (duration) {
39 | var messageWindow = $(".message-window");
40 | var scrollHeight = messageWindow.prop("scrollHeight");
41 | messageWindow.stop().animate({scrollTop: scrollHeight}, duration || 0);
42 | };
43 | ```
44 |
45 | You can play with this function in the browser console. With the window scrolled away from the bottom (add messages if you need to), try `scrollToBottom()` to change it instantly or `scrollToBottom(500)` to see it scroll smoothly. The `.stop()` in the jQuery will reset the animation if it's called multiple times so that it doesn't cause scrolling problems.
46 |
47 | ## Trigger auto-scrolling on startup
48 | So we have the tool we need. Let's make it so the app will be scrolled correctly when you first open it. Meteor provides a callback to `subscribe` called `onReady` that allows us to call a function when all the data is received from the server. Let's use that to snap the message window to the bottom (we don't want an animation here). We'll also set `autoScrollingIsActive` to true – again, we'll use that shortly. Replace `Meteor.subscribe("messages");` so it looks like the following (a few surrounding lines are shown):
49 |
50 | ```javascript
51 | if (Meteor.isClient) {
52 | // This code only runs on the client
53 | Meteor.subscribe("messages", {
54 | onReady: function () {
55 | scrollToBottom();
56 | autoScrollingIsActive = true;
57 | }
58 | });
59 |
60 | Template.body.helpers({
61 | ```
62 |
63 | ## Trigger auto-scrolling when a message arrives
64 | So now we need to trigger `scrollToBottom()` when new messages come in. Meteor provides the `onRendered()` hook to execute code whenever a template has been placed into the DOM - which will happen whenever a new message is added. Let's use that to call `scrollToBottom()`. Replace `/*chat window scrolling*/` with:
65 |
66 | ```javascript
67 | Template.message.onRendered(function () {
68 | if (autoScrollingIsActive) {
69 | scrollToBottom(250);
70 | }
71 | });
72 | ```
73 |
74 | We do this only `if (autoScrollingIsActive)` so that it doesn't scroll on the initial page load but only when new messages come in. And this seems to solve our problem. It will advance all client chat windows when new messages come in. It also adds an animation that is both pleasant and provides continuity when new chats roll in.
75 |
76 | ## Allowing the user to view history
77 | Consider, though, why we put in the scrollable window in the first place. The whole point is to be able to look at the message history. What if you are reading something from a while ago and someone else posts a new message? Sorry, you are forcibly scrolled back to current messages. This will anger users. You can test this by opening a second window in incognito mode or using a different browser to log in as a second user (otherwise both windows will always be the same user, and you won't be able to test our new features). See how a message from one user forces the other user to the most recent message.
78 |
79 | It turns out that the rules we need are a little more involved than it first seemed:
80 |
81 | 1. When we are at the bottom, we want the chat window to advance automatically.
82 | 2. When we are viewing the chat history (not at the bottom), we want the chat window to do nothing when a new message comes in.
83 | 3. If we scroll back to the bottom, though, the app should again advance automatically.
84 |
85 | It would seem that we need some kind of way to detect that we are at the bottom to turn automatic scrolling on or off. It turns out that we can do that by watching scroll events on our chat window. Place this event handler under `/* scroll event */`
86 |
87 | ```javascript
88 | "scroll .message-window": function () {
89 | var howClose = 80; // # pixels leeway to be considered "at Bottom"
90 | var messageWindow = $(".message-window");
91 | var scrollHeight = messageWindow.prop("scrollHeight");
92 | var scrollBottom = messageWindow.prop("scrollTop") + messageWindow.height();
93 | var atBottom = scrollBottom > (scrollHeight - howClose);
94 | autoScrollingIsActive = atBottom ? true : false;
95 | },
96 | ```
97 |
98 | This code uses jQuery to calculate how close to the bottom we are, and `autoScrollingIsActive` will be set to true/false depending of whether we are within 80 pixels of the bottom.
99 |
100 | Great, now we have reasonable scrolling behavior. `autoScrollingIsActive` will become active any time we scroll close enough to the bottom, and it will turn off when we scroll away. Give it a try.
101 |
102 | One more thing: The window should scroll down any time you post a message yourself, even if you are currently viewing your history. Let's fix that quickly by adding a single call to `scrollToBottom()` in the `submit` event handler:
103 |
104 | ```javascript
105 | "submit .new-message": function(event) {
106 | var messageText = event.target.text.value;
107 |
108 | Meteor.call("sendMessage", messageText);
109 | scrollToBottom(250); // <--add this line
110 |
111 | event.target.text.value = "";
112 | event.preventDefault();
113 | },
114 | ```
115 | This is starting to work much better.
116 |
117 |
118 | ## Notifying users when a message comes in
119 |
120 | There is one other UX problem when you are looking at the chat history: There is no way to tell that new messages have arrived, so you may miss important messages.
121 |
122 | Let's implement a "new messages" button that will appear only when you are in the chat history and have a new message come in. It should have the following behaviors:
123 |
124 | 1. It appears when you are in your chat history (not at the bottom) and a new message comes in.
125 | 2. It should scroll the window to the bottom and disappear when you click it.
126 | 3. It should go away on its own if you scroll to the bottom yourself.
127 |
128 | ## Store local state in a reactive variable
129 |
130 | To implement this, we'll use a reactive variable from the `reactive-var` package. A reactive variable is an object with `.get()` and `.set()` methods that is able to drive reactive change in the UI when it changes. Unlike database collections, each browser window gets its own copy of the reactive variable, so state will not be transmitted between clients or even between tabs in the same browser. Let's build the button and see how this works. We'll also make the button fade in and out for a nice effect. To do the fading, we'll add an animation package called Momentum. In the terminal in your `chat/` directory:
131 |
132 | ```bash
133 | meteor add reactive-var
134 | meteor add percolate:momentum
135 | ```
136 |
137 | This will give you the helpers we'll use in the HTML to fade the button in and out. Let's add the button with helpers now in the HTML file by replacing the `` comment with:
138 |
139 | ```html
140 | {{#momentum plugin="fade"}}
141 | {{#if thereAreUnreadMessages}}
142 |
143 | {{/if}}
144 | {{/momentum}}
145 | ```
146 |
147 | In the JavaScript, let's create a reactive variable to store this the state of this button. Replace `/* reactive var here */` with:
148 |
149 | ```javascript
150 | thereAreUnreadMessages = new ReactiveVar(false);
151 | ```
152 |
153 | We temporarily made `thereAreUnreadMessages` a global (no `var` at the beginning) so you can access it from the console – you can add a `var` later, if you want.
154 |
155 | We need to expose our reactive variable with the helper function `thereAreUnreadMessages`. Replace `/* unread message helper */` with:
156 |
157 | ```javascript
158 | thereAreUnreadMessages: function () {
159 | return thereAreUnreadMessages.get();
160 | }
161 | ```
162 |
163 | (Make sure the `recentMessages` helper function body (above the new helper) has a comma at the end so you don't get an error.)
164 |
165 | Go to the browser console and type:
166 |
167 | ```
168 | thereAreUnreadMessages.set(true)
169 | ```
170 |
171 | Look, the button fades in! You can remove it with `thereAreUnreadMessages.set(false)`. This is another example of reactive rendering: You simply declaratively state that the button should be present if the variable is true, and Meteor will rerender the DOM as necessary when that variable changes.
172 |
173 | ## Implement the button's behaviors
174 | First, let's make the button appear at the appropriate time. The time to check is when a new message comes in, which will trigger the `onRendered()` callback we already have. If autoscrolling is not active, we are in message history and might have unread messages. To prevent unwanted appearances of the button we should also make sure that the user is logged in and the the new message wasn't written by the user but by someone else. Replace the `onRendered` block with the following:
175 |
176 | ```javascript
177 | Template.message.onRendered(function () {
178 | if (autoScrollingIsActive) {
179 | scrollToBottom(250);
180 | } else {
181 | if (Meteor.user() && this.data.username !== Meteor.user().username) {
182 | thereAreUnreadMessages.set(true);
183 | }
184 | }
185 | });
186 | ```
187 |
188 | The button should go away if we scroll to the bottom by ourselves. This is easily done by setting `thereAreUnreadMessages` to false if we are at the bottom. Add lines to the `scroll` event to make it match the following:
189 |
190 | ```javascript
191 | "scroll .message-window": function () {
192 | var howClose = 80; // # pixels leeway to be considered "at Bottom"
193 | var messageWindow = $(".message-window");
194 | var scrollHeight = messageWindow.prop("scrollHeight");
195 | var scrollBottom = messageWindow.prop("scrollTop") + messageWindow.height();
196 | var atBottom = scrollBottom > (scrollHeight - howClose);
197 | autoScrollingIsActive = atBottom ? true : false;
198 | if (atBottom) { // <--new
199 | thereAreUnreadMessages.set(false);
200 | }
201 | },
202 | ```
203 |
204 |
205 | ## Make the button a button
206 | Making the button actually scroll the window is very simple. Just add this event directly under the `scroll event`:
207 |
208 | ```javascript
209 | "click .more-messages": function () {
210 | scrollToBottom(500);
211 | thereAreUnreadMessages.set(false);
212 | }
213 | ```
214 |
215 |
216 | ## Conclusion: This feels like a real chat app
217 | Try out the app now, again with two users. The button will appear when we are in history and a new message from another user comes in. If we press it, we will be scrolled to the bottom and it will fade out. If we scroll to the bottom ourselves, it will go away on its own. Nice!
218 |
219 | 
220 |
221 | It turned out that chat scrolling behavior is quite rich. We were able to make the app handle users' UX needs quite easily by using the `onRendered()` hook, some jQuery, a reactive variable, and an animation package. This represents some typical tasks you might have to work through to make a great UI.
222 |
223 | Although we only have one chat room, this is otherwise a pretty functional chat app – and we still have less than 100 lines of JavaScript. You have experienced most of the major phases of building a Meteor app. All that's left is to deploy it. We'll cover that as well as mobile integration in [the final installment](chat-tutorial-part-5.md) of this tutorial.
--------------------------------------------------------------------------------