Merge branch 'devel' into meteor3-svelte-docs-try2-devel-branch
@@ -44,6 +44,10 @@ export default defineConfig({
|
||||
text: "Meteor.js 3 + Solid",
|
||||
link: "/tutorials/solid/index",
|
||||
},
|
||||
{
|
||||
text: "Meteor.js 3 + Blaze",
|
||||
link: "/tutorials/blaze/index",
|
||||
},
|
||||
{
|
||||
text: "Meteor.js 3 + Svelte",
|
||||
link: "/tutorials/svelte/index",
|
||||
@@ -480,10 +484,14 @@ export default defineConfig({
|
||||
link: "/tutorials/vue/meteorjs3-vue3-vue-meteor-tracker",
|
||||
text: "Meteor + Vue + vue-meteor-tracker",
|
||||
},
|
||||
{
|
||||
{
|
||||
text: "Meteor.js 3 + Solid",
|
||||
link: "/tutorials/solid/index",
|
||||
},
|
||||
{
|
||||
text: "Meteor.js 3 + Blaze",
|
||||
link: "/tutorials/blaze/index",
|
||||
},
|
||||
{
|
||||
text: "Meteor.js 3 + Svelte",
|
||||
link: "/tutorials/svelte/index",
|
||||
|
||||
190
v3-docs/docs/tutorials/blaze/1.creating-the-app.md
Normal file
@@ -0,0 +1,190 @@
|
||||
## 1: Creating the app
|
||||
|
||||
### 1.1: Install Meteor
|
||||
|
||||
First, we need to install Meteor by following this [installation guide](https://docs.meteor.com/about/install.html).
|
||||
|
||||
### 1.2: Create Meteor Project
|
||||
|
||||
The easiest way to setup Meteor with Blaze is by using the command `meteor create` with the option `--blaze` and your project name:
|
||||
|
||||
```shell
|
||||
meteor create --blaze simple-todos-blaze
|
||||
```
|
||||
|
||||
Meteor will create all the necessary files for you.
|
||||
|
||||
The files located in the `client` directory are setting up your client side (web), you can see for example `client/main.html` where Meteor is rendering your App main component into the HTML.
|
||||
|
||||
Also, check the `server` directory where Meteor is setting up the server side (Node.js), you can see the `server/main.js` which would be a good place to initialize your MongoDB database with some data. You don't need to install MongoDB as Meteor provides an embedded version of it ready for you to use.
|
||||
|
||||
You can now run your Meteor app using:
|
||||
|
||||
```shell
|
||||
meteor
|
||||
```
|
||||
|
||||
Don't worry, Meteor will keep your app in sync with all your changes from now on.
|
||||
|
||||
Take a quick look at all the files created by Meteor, you don't need to understand them now but it's good to know where they are.
|
||||
|
||||
Your Blaze code will be located inside the `imports/ui` directory, and the `App.html` and `App.js` files will be the root component of your Blaze To-do app. We haven't made those yet but will soon.
|
||||
|
||||
### 1.3: Create Task Component
|
||||
|
||||
To start working on our todo list app, let’s replace the code of the default starter app with the code below. From there, we’ll talk about what it does.
|
||||
|
||||
First, let’s remove the body from our HTML entry-point (leaving just the `<head>` tag):
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [client/main.html]
|
||||
<head>
|
||||
<title>Simple todo</title>
|
||||
</head>
|
||||
```
|
||||
:::
|
||||
|
||||
Create a new directory named `imports` inside the `simple-todos-blaze` folder. In the `imports` folder, create another directory with the name `ui` and add an `App.html` file inside of it with the content below:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
<body>
|
||||
{{> mainContainer }}
|
||||
</body>
|
||||
|
||||
<template name="mainContainer">
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Todo List</h1>
|
||||
</header>
|
||||
|
||||
<ul>
|
||||
{{#each tasks}}
|
||||
{{> task}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="task">
|
||||
<li>{{text}}</li>
|
||||
</template>
|
||||
```
|
||||
:::
|
||||
|
||||
We just created two templates, the `mainContainer`, which will be rendered in the body of our app, and it will show a header and a list of tasks that will render each item using the `task` template. Now, we need some data to present sample tasks on this page.
|
||||
|
||||
### 1.4: Create Sample Tasks
|
||||
|
||||
Create a new file called `App.js` in your `ui` folder.
|
||||
|
||||
Inside your entry-point `main.js` file, remove all the previous content and just add the code below to import the new file `imports/ui/App.js`:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [client/main.js]
|
||||
import '../imports/ui/App.js';
|
||||
```
|
||||
:::
|
||||
|
||||
As you are not connecting to your server and database yet, let’s define some sample data, which we will use shortly to render a list of tasks. Add the code below to the `App.js` file:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
import './App.html';
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks: [
|
||||
{ text: 'This is task 1' },
|
||||
{ text: 'This is task 2' },
|
||||
{ text: 'This is task 3' },
|
||||
],
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Adding a helper to the `mainContainer` template, you are able to define the array of tasks. When the app starts, the client-side entry-point will import the `App.js` file, which will also import the `App.html` template we created in the previous step.
|
||||
|
||||
At this point meteor should be running on port 3000 so you can visit the running app and see your list with three tasks displayed at [http://localhost:3000/](http://localhost:3000/) - but if meteor is not running, go to your terminal and move to the top directory of your project and type `meteor` then press return to launch the app.
|
||||
|
||||
All right! Let’s find out what all these bits of code are doing!
|
||||
|
||||
### 1.5: Rendering Data
|
||||
|
||||
<img width="688px" src="/tutorials/blaze/assets/mermaid-diagram-blaze-rendering.png"/>
|
||||
|
||||
Meteor parses HTML files and identifies three top-level tags: `<head>`, `<body>`, and `<template>`.
|
||||
|
||||
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.
|
||||
|
||||
Everything inside `<template>` tags is compiled into Meteor templates, which can be included inside HTML with <span v-pre>{{> templateName}}</span> or referenced in your JavaScript with `Template.templateName`.
|
||||
|
||||
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.
|
||||
|
||||
All of the code in your HTML files will be compiled with [Meteor’s Spacebars compiler](http://blazejs.org/api/spacebars.html). Spacebars uses statements surrounded by double curly braces such as <span v-pre>{{#each}}</span> and <span v-pre>{{#if}}</span> to let you add logic and data to your views.
|
||||
|
||||
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.mainContainer` that returns an array. Inside the template tag of the HTML, we can use <span v-pre>{{#each tasks}}</span> 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 <span v-pre>{{text}}</span>.
|
||||
|
||||
### 1.6: Mobile Look
|
||||
|
||||
Let’s see how your app is looking on mobile. You can simulate a mobile environment by `right clicking` your app in the browser (we are assuming you are using Google Chrome, as it is the most popular browser) and then `inspect`, this will open a new window inside your browser called `Dev Tools`. In the `Dev Tools` you have a small icon showing a Mobile device and a Tablet:
|
||||
|
||||
<img width="500px" src="/tutorials/blaze/assets/step01-dev-tools-mobile-toggle.png"/>
|
||||
|
||||
Click on it and then select the phone that you want to simulate and in the top nav bar.
|
||||
|
||||
> You can also check your app in your personal cellphone. To do so, connect to your App using your local IP in the navigation browser of your mobile browser.
|
||||
>
|
||||
> This command should print your local IP for you on Unix systems
|
||||
> `ifconfig | grep "inet " | grep -Fv 127.0.0.1 | awk '{print $2}'`
|
||||
>
|
||||
> On Microsoft Windows try this in a command prompt
|
||||
> `ipconfig | findstr "IPv4 Address"`
|
||||
|
||||
You should see the following:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step01-mobile-without-meta-tags.png"/>
|
||||
|
||||
As you can see, everything is small, as we are not adjusting the view port for mobile devices. You can fix this and other similar issues by adding these lines to your `client/main.html` file, inside the `head` tag, after the `title`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [client/main.html]
|
||||
...
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge"/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, height=device-height, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Now your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step01-mobile-with-meta-tags.png"/>
|
||||
|
||||
|
||||
### 1.7: Hot Module Replacement
|
||||
|
||||
By default, when using Blaze with Meteor, a package called [hot-module-replacement](https://docs.meteor.com/packages/hot-module-replacement) is already added for you. This package updates the javascript modules in a running app that were modified during a rebuild. Reduces the feedback cycle while developing, so you can view and test changes quicker (it even updates the app before the build has finished). You are also not going to lose the state, your app code will be updated, and your state will be the same.
|
||||
|
||||
> You can read more about packages [here](https://docs.meteor.com/packages/).
|
||||
|
||||
You should also add the package [dev-error-overlay](https://atmospherejs.com/meteor/dev-error-overlay) at this point, so you can see the errors in your web browser.
|
||||
|
||||
```shell
|
||||
meteor add dev-error-overlay
|
||||
```
|
||||
|
||||
You can try to make some mistakes and then you are going to see the errors in the browser and not only in the console.
|
||||
|
||||
In the next step we are going to work with our MongoDB database to be able to store our tasks.
|
||||
156
v3-docs/docs/tutorials/blaze/2.collections.md
Normal file
@@ -0,0 +1,156 @@
|
||||
## 2: Collections
|
||||
|
||||
Meteor already sets up MongoDB for you. In order to use our database, we need to create a _collection_, which is where we will store our _documents_, in our case our `tasks`.
|
||||
|
||||
> You can read more about collections [here](https://v3-docs.meteor.com/api/collections.html).
|
||||
|
||||
In this step we will implement all the necessary code to have a basic collection for our tasks up and running.
|
||||
|
||||
### 2.1: Create Tasks Collection {#create-tasks-collection}
|
||||
|
||||
Create a new directory in `imports/api` if it doesn't exist already. We can create a new collection to store our tasks by creating a new file at `imports/api/TasksCollection.js` which instantiates a new Mongo collection and exports it.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/api/TasksCollection.js]
|
||||
import { Mongo } from "meteor/mongo";
|
||||
|
||||
export const TasksCollection = new Mongo.Collection("tasks");
|
||||
```
|
||||
:::
|
||||
|
||||
Notice that we stored the file in the `imports/api` directory, which is a place to store API-related code, like publications and methods. You can name this folder as you want, this is just a choice.
|
||||
|
||||
> You can read more about app structure and imports/exports [here](http://guide.meteor.com/structure.html).
|
||||
|
||||
### 2.2: Initialize Tasks Collection {#initialize-tasks-collection}
|
||||
|
||||
For our collection to work, you need to import it in the server so it sets some plumbing up.
|
||||
|
||||
You can either use `import "/imports/api/TasksCollection"` or `import { TasksCollection } from "/imports/api/TasksCollection"` if you are going to use on the same file, but make sure it is imported.
|
||||
|
||||
Now it is easy to check if there is data or not in our collection, otherwise, we can insert some sample data easily as well.
|
||||
|
||||
You don't need to keep the old content of `server/main.js`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [server/main.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "/imports/api/TasksCollection";
|
||||
|
||||
const insertTask = (taskText) =>
|
||||
TasksCollection.insertAsync({ text: taskText });
|
||||
|
||||
Meteor.startup(async () => {
|
||||
if ((await TasksCollection.find().countAsync()) === 0) {
|
||||
[
|
||||
"First Task",
|
||||
"Second Task",
|
||||
"Third Task",
|
||||
"Fourth Task",
|
||||
"Fifth Task",
|
||||
"Sixth Task",
|
||||
"Seventh Task",
|
||||
].forEach(insertTask);
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
So you are importing the `TasksCollection` and adding a few tasks to it iterating over an array of strings and for each string calling a function to insert this string as our `text` field in our `task` document.
|
||||
|
||||
### 2.3: Render Tasks Collection {#render-tasks-collection}
|
||||
|
||||
Now comes the fun part, you will render the tasks with Blaze. That will be pretty simple.
|
||||
|
||||
In your `App.js` file, import the `TasksCollection` file and, instead of returning a static array, return the tasks saved in the database:
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [imports/ui/App.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
import { TasksCollection } from "../api/TasksCollection";
|
||||
import './App.html';
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
return TasksCollection.find({});
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
But wait! Something is missing. If you run your app now, you'll see that you don't render any tasks.
|
||||
|
||||
That's because we need to publish our data to the client.
|
||||
|
||||
> For more information on Publications/Subscriptions, please check our [docs](https://v3-docs.meteor.com/api/meteor.html#pubsub).
|
||||
|
||||
Meteor doesn't need REST calls. It instead relies on synchronizing the MongoDB on the server with a MiniMongoDB on the client. It does this by first publishing collections on the server and then subscribing to them on the client.
|
||||
|
||||
First, create a publication for our tasks:
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [imports/api/TasksPublications.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "./TasksCollection";
|
||||
|
||||
Meteor.publish("tasks", function () {
|
||||
return TasksCollection.find();
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Now, we need to import this file in our server:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [server/main.js]
|
||||
...
|
||||
import { TasksCollection } from '/imports/api/TasksCollection';
|
||||
|
||||
import "../imports/api/TasksPublications"; // [!code highlight]
|
||||
|
||||
const insertTask = taskText => TasksCollection.insertAsync({ text: taskText });
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
The only thing left is subscribe to this publication:
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.onCreated(function mainContainerOnCreated() {
|
||||
Meteor.subscribe('tasks');
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
See how your app should look like now:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/collections-tasks-list.png"/>
|
||||
|
||||
You can change your data on MongoDB in the server and your app will react and re-render for you.
|
||||
|
||||
You can connect to your MongoDB running `meteor mongo` in the terminal from your app folder or using a Mongo UI client, like [NoSQLBooster](https://nosqlbooster.com/downloads). Your embedded MongoDB is running in port `3001`.
|
||||
|
||||
See how to connect:
|
||||
|
||||
<img width="500px" src="/tutorials/blaze/assets/collections-connect-db.png"/>
|
||||
|
||||
See your database:
|
||||
|
||||
<img width="500px" src="/tutorials/blaze/assets/collections-see-database.png"/>
|
||||
|
||||
You can double-click your collection to see the documents stored on it:
|
||||
|
||||
<img width="500px" src="/tutorials/blaze/assets/collections-documents.png"/>
|
||||
|
||||
In the next step, we are going to create tasks using a form.
|
||||
205
v3-docs/docs/tutorials/blaze/3.forms-and-events.md
Normal file
@@ -0,0 +1,205 @@
|
||||
## 3: Forms and Events
|
||||
|
||||
All apps need to allow the user to perform some sort of interaction with the data that is stored. In our case, the first type of interaction is to insert new tasks. Without it, our To-Do app wouldn't be very helpful.
|
||||
|
||||
One of the main ways in which a user can insert or edit data on a website is through forms. In most cases, it is a good idea to use the `<form>` tag since it gives semantic meaning to the elements inside it.
|
||||
|
||||
### 3.1: Create Task Form
|
||||
|
||||
First, we need to create a simple form component to encapsulate our logic.
|
||||
|
||||
Create a new template named `form` inside the `App.html` file, and inside of the new template, we’ll add an input field and a button:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
|
||||
<template name="form">
|
||||
<form class="task-form">
|
||||
<input type="text" name="text" placeholder="Type to add new tasks" />
|
||||
<button type="submit">Add Task</button>
|
||||
</form>
|
||||
</template>
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.2: Update the mainContainer template element
|
||||
|
||||
Then we can simply add this to our `mainContainer` template above your list of tasks:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
|
||||
<template name="mainContainer">
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Todo List</h1>
|
||||
</header>
|
||||
|
||||
{{> form }}
|
||||
|
||||
<ul>
|
||||
{{#each tasks}}
|
||||
{{> task}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
We are rendering the `form` template that we created in the previous step, and we are iterating over each of the `tasks` and rendering them using the `task` template.
|
||||
|
||||
### 3.3: Update the Stylesheet
|
||||
|
||||
You also can style it as you wish. For now, we only need some margin at the top so the form doesn't seem off the mark. Add the CSS class `.task-form`, this needs to be the same name in your `class` attribute in the form component.
|
||||
|
||||
::: code-group
|
||||
|
||||
```css [client/main.css]
|
||||
.task-form {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 3.4: Add Submit Handler
|
||||
|
||||
Now let's create a function to handle the form submit and insert a new task into the database. To do it, we will need to implement a Meteor Method.
|
||||
|
||||
Methods are essentially RPC calls to the server that let you perform operations on the server side securely. You can read more about Meteor Methods [here](https://guide.meteor.com/methods.html).
|
||||
|
||||
To create your methods, you can create a file called `TasksMethods.js`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [imports/api/TasksMethods.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "./TasksCollection";
|
||||
|
||||
Meteor.methods({
|
||||
"tasks.insert"(doc) {
|
||||
return TasksCollection.insertAsync(doc);
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Remember to import your method on the `main.js` server file, delete the `insertTask` function, and invoke the new meteor method inside the `forEach` block.
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [server/main.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "/imports/api/TasksCollection";
|
||||
import "../imports/api/TasksPublications";
|
||||
import "../imports/api/TasksMethods"; // [!code highlight]
|
||||
|
||||
Meteor.startup(async () => {
|
||||
if ((await TasksCollection.find().countAsync()) === 0) {
|
||||
[
|
||||
"First Task",
|
||||
"Second Task",
|
||||
"Third Task",
|
||||
"Fourth Task",
|
||||
"Fifth Task",
|
||||
"Sixth Task",
|
||||
"Seventh Task",
|
||||
].forEach((taskName) => {
|
||||
Meteor.callAsync("tasks.insert", { // [!code highlight]
|
||||
text: taskName, // [!code highlight]
|
||||
createdAt: new Date(), // [!code highlight]
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Inside the `forEach` function, we are adding a task to the `tasks` collection by calling `Meteor.callAsync()`. The first argument is the name of the method we want to call, and the second argument is the text of the task.
|
||||
|
||||
Also, insert a date `createdAt` in your `task` document so you know when each task was created.
|
||||
|
||||
Now we need to import `TasksMethods.js` and add a listener to the `submit` event on the form:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
import { TasksCollection } from "../api/TasksCollection";
|
||||
import '/imports/api/TasksMethods.js'; // this import in this client UI allows for optimistic execution
|
||||
import './App.html';
|
||||
|
||||
Template.mainContainer.onCreated(function mainContainerOnCreated() {
|
||||
Meteor.subscribe('tasks');
|
||||
});
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
return TasksCollection.find({});
|
||||
},
|
||||
});
|
||||
|
||||
Template.form.events({
|
||||
"submit .task-form"(event) {
|
||||
// Prevent default browser form submit
|
||||
event.preventDefault();
|
||||
|
||||
// Get value from form element
|
||||
const target = event.target;
|
||||
const text = target.text.value;
|
||||
|
||||
// Insert a task into the collection
|
||||
Meteor.callAsync("tasks.insert", {
|
||||
text,
|
||||
createdAt: new Date(), // current time
|
||||
});
|
||||
|
||||
// Clear form
|
||||
target.text.value = '';
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
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 called when the event happens.
|
||||
|
||||
In our case above, we listen to the `submit` event on any element that matches the CSS selector `.task-form`. When this event is triggered by the user pressing enter inside the input field or the submit button, our event handler function is called.
|
||||
|
||||
The event handler gets an argument called `event` that has some information about the triggered event. 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 the other properties of the event object by adding a `console.log(event)` and inspecting the object in your browser console.
|
||||
|
||||
Just like on the server side, we are adding a task to the `tasks` collection by calling `Meteor.callAsync("tasks.insert")`. It will first execute on the client optimistically using minimongo while simultaneously making the remote procedure call on the server. If the server call fails, minimongo will rollback the change on the client. This gives the speediest user experience. It's a bit like [rollback netcode](https://glossary.infil.net/?t=Rollback%20Netcode) in fighting video games.
|
||||
|
||||
Finally, in the last line of the event handler, we need to clear the input to prepare for another new task.
|
||||
|
||||
### 3.5: Show Newest Tasks First
|
||||
|
||||
Now you just need to make a change that will make users happy: we need to show the newest tasks first. We can accomplish this quite quickly by sorting our [Mongo](https://guide.meteor.com/collections.html#mongo-collections) query.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
return TasksCollection.find({}, { sort: { createdAt: -1 } });
|
||||
},
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step03-form-new-task.png"/>
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step03-new-task-on-list.png"/>
|
||||
|
||||
In the next step, we are going to update your tasks state and provide a way for users to remove tasks.
|
||||
170
v3-docs/docs/tutorials/blaze/4.update-and-remove.md
Normal file
@@ -0,0 +1,170 @@
|
||||
## 4: Update and Remove
|
||||
|
||||
Up until now, you have only inserted documents into our collection. Let's take a look at how you can update and remove them by interacting with the user interface.
|
||||
|
||||
### 4.1: Add Checkbox
|
||||
|
||||
First, you need to add a `checkbox` element to your `Task` component.
|
||||
|
||||
Next, let’s create a new file for our `task` template in `imports/ui/Task.html`, so we can start to separate the logic in our app.
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/Task.html]
|
||||
<template name="task">
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" checked="{{isChecked}}" class="toggle-checked" />
|
||||
<span>{{text}}</span>
|
||||
</label>
|
||||
</li>
|
||||
</template>
|
||||
```
|
||||
:::
|
||||
|
||||
Don’t forget to remove the template named `task` in `imports/ui/App.html`.
|
||||
|
||||
You must also add the following import:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
import './Task';
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
### 4.2: Toggle Checkbox
|
||||
|
||||
Now you can update your task document by toggling its `isChecked` field.
|
||||
|
||||
First, create a new method called `tasks.toggleChecked` to update the `isChecked` property.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/api/TasksMethods.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "./TasksCollection";
|
||||
|
||||
Meteor.methods({
|
||||
..
|
||||
"tasks.toggleChecked"({ _id, isChecked }) {
|
||||
return TasksCollection.updateAsync(_id, {
|
||||
$set: { isChecked: !isChecked },
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Create a new file called `Task.js` so we can have our handlers to the `task` template:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/Task.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
import { TasksCollection } from "../api/TasksCollection";
|
||||
import '/imports/api/TasksMethods.js'; // this import in this client UI allows for optimistic execution
|
||||
import './Task.html';
|
||||
|
||||
|
||||
Template.task.events({
|
||||
'click .toggle-checked'() {
|
||||
// Set the checked property to the opposite of its current value
|
||||
let taskID = this._id;
|
||||
let checkedValue = Boolean(this.isChecked);
|
||||
Meteor.callAsync("tasks.toggleChecked", { _id: taskID, isChecked: checkedValue });
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Toggling checkboxes should now persist in the DB even if you refresh the web browser.
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step04-checkbox.png"/>
|
||||
|
||||
If your computer is fast enough, it's possible that when it sets up the default tasks a few will have the same date. That will cause them to non-deterministically "jump around" in the UI as you toggle checkboxes and the UI reactively updates. To make it stable, you can add a secondary sort on the `_id` of the task:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
return TasksCollection.find({}, { sort: { createdAt: -1, _id: -1 } });
|
||||
},
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
|
||||
### 4.3: Remove tasks
|
||||
|
||||
You can remove tasks with just a few lines of code.
|
||||
|
||||
First, add a button after text `label` in your `Task` component.
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/Task.html]
|
||||
<template name="task">
|
||||
<li>
|
||||
<label>
|
||||
<input type="checkbox" checked="{{isChecked}}" class="toggle-checked" />
|
||||
<span>{{text}}</span>
|
||||
</label>
|
||||
|
||||
<button class="delete">×</button>
|
||||
</li>
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Next you need to have a function to delete the task. For that, let's create a new method called `tasks.delete`:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/api/TasksMethods.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "./TasksCollection";
|
||||
|
||||
Meteor.methods({
|
||||
..
|
||||
"tasks.delete"({ _id }) {
|
||||
return TasksCollection.removeAsync(_id);
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Now add the removal logic in the `Task.js`. It will just be a new event to the `task` template that is activated when the user clicks on a delete button (i.e. any button with the class `delete`):
|
||||
|
||||
::: code-group
|
||||
|
||||
```javascript [imports/ui/Task.js]
|
||||
...
|
||||
|
||||
Template.task.events({
|
||||
...,
|
||||
'click .delete'() {
|
||||
let taskID = this._id;
|
||||
Meteor.callAsync("tasks.delete", { _id: taskID });
|
||||
},
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step04-delete-button.png"/>
|
||||
|
||||
### 4.4: Getting data in event handlers
|
||||
|
||||
In a collection, every inserted document has a unique `_id` field that can refer to that specific document. Inside the event handlers, `this` refers to an individual task object. We can get the `_id` of the current task with `this._id` and any other field available on the client-side. Once we have the `_id`, we can use, update, and remove the relevant task. That’s how our code will update or remove a task.
|
||||
|
||||
In the next step, we are going to improve the look of your app using CSS with Flexbox.
|
||||
|
||||
188
v3-docs/docs/tutorials/blaze/5.styles.md
Normal file
@@ -0,0 +1,188 @@
|
||||
## 5: Styles
|
||||
|
||||
### 5.1: CSS
|
||||
|
||||
Our user interface up until this point has looked quite ugly. Let's add some basic styling which will serve as the foundation for a more professional looking app.
|
||||
|
||||
Replace the content of our `client/main.css` file with the one below, the idea is to have an app bar at the top, and a scrollable content including:
|
||||
|
||||
- form to add new tasks;
|
||||
- list of tasks.
|
||||
|
||||
::: code-group
|
||||
|
||||
```css [client/main.css]
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background-color: #315481;
|
||||
background-image: linear-gradient(to bottom, #315481, #918e82 100%);
|
||||
background-attachment: fixed;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
color: white;
|
||||
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.main::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
header {
|
||||
background: #d2edf4;
|
||||
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
|
||||
padding: 20px 15px 15px 15px;
|
||||
position: relative;
|
||||
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.app-bar h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.task-form {
|
||||
display: flex;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.task-form > input {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 6px;
|
||||
background: transparent;
|
||||
border: 1px solid #aaa;
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.task-form > input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.task-form > button {
|
||||
min-width: 100px;
|
||||
height: 95%;
|
||||
background-color: #315481;
|
||||
}
|
||||
|
||||
.tasks {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.tasks > li {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
border-bottom: #eee solid 1px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tasks > li > label {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.tasks > li > button {
|
||||
justify-self: flex-end;
|
||||
background-color: #ff3046;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
> If you want to learn more about this stylesheet check this article about [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/), and also this free [video tutorial](https://flexbox.io/) about it from [Wes Bos](https://twitter.com/wesbos).
|
||||
>
|
||||
> Flexbox is an excellent tool to distribute and align elements in your UI.
|
||||
|
||||
### 5.2: Applying styles
|
||||
|
||||
Now you need to add some elements around your components. You are going to add a `class` to your main div in the `App`, also a `header` element with a few `divs` around your `h1`, and a main `div` around your form and list. Check below how it should be, pay attention to the name of the classes, they need to be the same as in the CSS file:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
<template name="mainContainer">
|
||||
<div class="app">
|
||||
<header>
|
||||
<div class="app-bar">
|
||||
<div class="app-header">
|
||||
<h1>📝️ Todo List</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main">
|
||||
{{> form }}
|
||||
|
||||
<ul class="tasks">
|
||||
{{#each tasks}}
|
||||
{{> task}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step05-styles.png"/>
|
||||
|
||||
In the next step, we are going to make this task list more interactive, for example, providing a way to filter tasks.
|
||||
204
v3-docs/docs/tutorials/blaze/6.filter-tasks.md
Normal file
@@ -0,0 +1,204 @@
|
||||
## 6: Filter tasks
|
||||
|
||||
In this step, you will filter your tasks by status and show the number of pending tasks.
|
||||
|
||||
### 6.1: ReactiveDict
|
||||
|
||||
First, you will add a button to show or hide the completed tasks from the list.
|
||||
|
||||
To keep the state, we will use the `ReactiveDict`, a reactive dictionary which enables us to store an arbitrary set of key-value pairs. Use it to manage the internal state in your components, i.e. the currently selected item in a list. To know more about how `ReactiveDict` works, you can click on this [link](https://docs.meteor.com/api/reactive-dict.html), and there you will find everything you need to know and everything you can do with it.
|
||||
|
||||
We need to install the `reactive-dict` package in our app. Simply run the command below on your app root directory:
|
||||
|
||||
```shell
|
||||
meteor add reactive-dict
|
||||
```
|
||||
|
||||
Next, we need to set up a new `ReactiveDict` and attach it to the `mainContainer` template instance (as this is where we’ll store the button’s state) when it is first created. The best place to create our variables is inside the callback onCreated of the template that we want to persist our data. This callback is called as soon as the template renders on the screen:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveDict } from 'meteor/reactive-dict';
|
||||
import { TasksCollection } from "../api/TasksCollection";
|
||||
import '/imports/api/TasksMethods.js'; // this import in this client UI allows for optimistic execution
|
||||
import './App.html';
|
||||
import './Task';
|
||||
|
||||
Template.mainContainer.onCreated(function mainContainerOnCreated() {
|
||||
this.state = new ReactiveDict();
|
||||
|
||||
Meteor.subscribe('tasks');
|
||||
});
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Then, we need an event handler to update the `ReactiveDict` variable when the button is clicked. An event handler takes two arguments, the second of which is the same template instance in the onCreated callback. Also, create a new constant called `HIDE_COMPLETED_STRING` below the imports, that will be used throughout the code as the name of the variable we are persisting:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
const HIDE_COMPLETED_STRING = "hideCompleted";
|
||||
|
||||
...
|
||||
|
||||
Template.mainContainer.events({
|
||||
"click #hide-completed-button"(event, instance) {
|
||||
const currentHideCompleted = instance.state.get(HIDE_COMPLETED_STRING);
|
||||
instance.state.set(HIDE_COMPLETED_STRING, !currentHideCompleted);
|
||||
}
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
The button in the UI to toggle our state will look something like this:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
|
||||
<div class="main">
|
||||
{{> form }}
|
||||
|
||||
<div class="filter">
|
||||
<button id="hide-completed-button">
|
||||
{{#if hideCompleted}}
|
||||
Show All
|
||||
{{else}}
|
||||
Hide Completed
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul class="tasks">
|
||||
{{#each tasks}}
|
||||
{{> task}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
You may notice we’re using `if` (a conditional test) for the first time, and it’s pretty straightforward. You can learn more about the conditional test, `if`, [here](https://guide.meteor.com/v1.3/blaze.html#builtin-block-helpers). We’re also using a helper called `hideCompleted` that we didn’t create yet, but we will shortly.
|
||||
|
||||
### 6.2: Button style
|
||||
|
||||
You should add some style to your button so it does not look gray and without a good contrast. You can use the styles below as a reference:
|
||||
|
||||
::: code-group
|
||||
|
||||
```css [client/main.css]
|
||||
.filter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.filter > button {
|
||||
background-color: #62807e;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 6.3: Filter Tasks
|
||||
|
||||
Now, we need to update `Template.mainContainer.helpers`. The code below verifies if the variable `hideCompleted` is set to true and if yes, we filter our query to get non completed tasks. We also have a new helper called `hideCompleted` that will help us in the UI where we want to know if we’re filtering or not:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
const instance = Template.instance();
|
||||
const hideCompleted = instance.state.get(HIDE_COMPLETED_STRING);
|
||||
|
||||
const hideCompletedFilter = { isChecked: { $ne: true } };
|
||||
|
||||
return TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
|
||||
sort: { createdAt: -1, _id: -1 },
|
||||
}).fetch();
|
||||
},
|
||||
hideCompleted() {
|
||||
return Template.instance().state.get(HIDE_COMPLETED_STRING);
|
||||
},
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 6.4: Meteor Dev Tools Extension
|
||||
|
||||
You can install an extension to visualize the data in your Mini Mongo.
|
||||
|
||||
[Meteor DevTools Evolved](https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf) will help you to debug your app as you can see what data is on Mini Mongo.
|
||||
|
||||
<img width="800px" src="/tutorials/blaze/assets/step06-extension.png"/>
|
||||
|
||||
You can also see all the messages that Meteor is sending and receiving from the server, this is useful for you to learn more about how Meteor works.
|
||||
|
||||
<img width="800px" src="/tutorials/blaze/assets/step06-ddp-messages.png"/>
|
||||
|
||||
Install it in your Google Chrome browser using this [link](https://chrome.google.com/webstore/detail/meteor-devtools-evolved/ibniinmoafhgbifjojidlagmggecmpgf).
|
||||
|
||||
### 6.5: Pending tasks
|
||||
|
||||
Update the App component in order to show the number of pending tasks in the app bar.
|
||||
|
||||
You should avoid adding zero to your app bar when there are no pending tasks.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
...,
|
||||
incompleteCount() {
|
||||
const incompleteTasksCount = TasksCollection.find({ isChecked: { $ne: true } }).count();
|
||||
return incompleteTasksCount ? `(${incompleteTasksCount})` : '';
|
||||
},
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
|
||||
<template name="mainContainer">
|
||||
<div class="app">
|
||||
<header>
|
||||
<div class="app-bar">
|
||||
<div class="app-header">
|
||||
<h1>📝️ To Do List {{incompleteCount}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step06-all.png"/>
|
||||
<img width="200px" src="/tutorials/blaze/assets/step06-filtered.png"/>
|
||||
|
||||
In the next step we are going to include user access in your app.
|
||||
438
v3-docs/docs/tutorials/blaze/7.adding-user-accounts.md
Normal file
@@ -0,0 +1,438 @@
|
||||
## 7: Adding User Accounts
|
||||
|
||||
### 7.1: Password Authentication
|
||||
|
||||
Meteor already comes with a basic authentication and account management system out of the box, so you only need to add the `accounts-password` to enable username and password authentication:
|
||||
|
||||
```shell
|
||||
meteor add accounts-password
|
||||
```
|
||||
|
||||
> There are many more authentication methods supported. You can read more about the accounts system [here](https://v3-docs.meteor.com/api/accounts.html).
|
||||
|
||||
We also recommend you to install `bcrypt` node module, otherwise, you are going to see a warning saying that you are using a pure-Javascript implementation of it.
|
||||
|
||||
```shell
|
||||
meteor npm install --save bcrypt
|
||||
```
|
||||
|
||||
> You should always use `meteor npm` instead of only `npm` so you always use the `npm` version pinned by Meteor, this helps you to avoid problems due to different versions of npm installing different modules.
|
||||
|
||||
### 7.2: Create User Account
|
||||
|
||||
Now you can create a default user for our app, we are going to use `meteorite` as username, we just create a new user on server startup if we didn't find it in the database.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [server/main.js]
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { TasksCollection } from '/imports/api/TasksCollection';
|
||||
import "../imports/api/TasksMethods";
|
||||
|
||||
const SEED_USERNAME = 'meteorite';
|
||||
const SEED_PASSWORD = 'password';
|
||||
|
||||
Meteor.startup(async () => {
|
||||
if (!(await Accounts.findUserByUsername(SEED_USERNAME))) {
|
||||
await Accounts.createUser({
|
||||
username: SEED_USERNAME,
|
||||
password: SEED_PASSWORD,
|
||||
});
|
||||
}
|
||||
|
||||
...
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
You should not see anything different in your app UI yet.
|
||||
|
||||
### 7.3: Login Form
|
||||
|
||||
You need to provide a way for the users to input the credentials and authenticate, for that we need a form.
|
||||
|
||||
Our login form will be simple, with just two fields (username and password) and a button. You should use `Meteor.loginWithPassword(username, password)`; to authenticate your user with the provided inputs.
|
||||
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/Login.html]
|
||||
<template name="login">
|
||||
<form class="login-form">
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
name="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password">Password</label>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
```
|
||||
:::
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/Login.js]
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Template } from 'meteor/templating';
|
||||
import './Login.html';
|
||||
|
||||
Template.login.events({
|
||||
'submit .login-form'(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const target = e.target;
|
||||
|
||||
const username = target.username.value;
|
||||
const password = target.password.value;
|
||||
|
||||
Meteor.loginWithPassword(username, password);
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Be sure also to import the login form in `App.js`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveDict } from 'meteor/reactive-dict';
|
||||
import { TasksCollection } from "../api/TasksCollection";
|
||||
import '/imports/api/TasksMethods.js'; // this import in this client UI allows for optimistic execution
|
||||
import './App.html';
|
||||
import './Task';
|
||||
import "./Login.js";
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Ok, now you have a form, let's use it.
|
||||
|
||||
### 7.4: Require Authentication
|
||||
|
||||
Our app should only allow an authenticated user to access its task management features.
|
||||
|
||||
We can accomplish that by rendering the `Login` from the template when we don’t have an authenticated user. Otherwise, we return the form, filter, and list component.
|
||||
|
||||
To achieve this, we will use a conditional test inside our main div on `App.html`:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
<div class="main">
|
||||
{{#if isUserLoggedIn}}
|
||||
|
||||
{{> form }}
|
||||
|
||||
<div class="filter">
|
||||
<button id="hide-completed-button">
|
||||
{{#if hideCompleted}}
|
||||
Show All
|
||||
{{else}}
|
||||
Hide Completed
|
||||
{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ul class="tasks">
|
||||
{{#each tasks}}
|
||||
{{> task}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
{{> login }}
|
||||
{{/if}}
|
||||
</div>
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
As you can see, if the user is logged in, we render the whole app (`isUserLoggedIn`). Otherwise, we render the Login template. Let’s now create our helper `isUserLoggedIn`:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
const getUser = () => Meteor.user();
|
||||
const isUserLoggedInChecker = () => Boolean(getUser());
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
...,
|
||||
isUserLoggedIn() {
|
||||
return isUserLoggedInChecker();
|
||||
},
|
||||
});
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
### 7.5: Login Form style
|
||||
|
||||
Ok, let's style the login form now:
|
||||
|
||||
::: code-group
|
||||
|
||||
```css [client/main.css]
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-form > div {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.login-form > div > label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-form > div > input {
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 6px;
|
||||
background: transparent;
|
||||
border: 1px solid #aaa;
|
||||
width: 100%;
|
||||
font-size: 1em;
|
||||
margin-right: 16px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.login-form > div > input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.login-form > div > button {
|
||||
background-color: #62807e;
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
Now your login form should be centralized and beautiful.
|
||||
|
||||
### 7.6: Server startup
|
||||
|
||||
Every task should have an owner from now on. So go to your database, as you learn before, and remove all the tasks from there:
|
||||
|
||||
`db.tasks.remove({});`
|
||||
|
||||
Change your `server/main.js` to add the seed tasks using your `meteorite` user as owner.
|
||||
|
||||
Make sure you restart the server after this change so `Meteor.startup` block will run again. This is probably going to happen automatically anyway as you are going to make changes in the server side code.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [server/main.js]
|
||||
...
|
||||
|
||||
const user = await Accounts.findUserByUsername(SEED_USERNAME);
|
||||
|
||||
if ((await TasksCollection.find().countAsync()) === 0) {
|
||||
[
|
||||
"First Task",
|
||||
"Second Task",
|
||||
"Third Task",
|
||||
"Fourth Task",
|
||||
"Fifth Task",
|
||||
"Sixth Task",
|
||||
"Seventh Task",
|
||||
].forEach((taskName) => {
|
||||
Meteor.callAsync("tasks.insert", {
|
||||
text: taskName,
|
||||
createdAt: new Date(),
|
||||
userId: user._id
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
See that we are using a new field called `userId` with our user `_id` field, we are also setting `createdAt` field.
|
||||
|
||||
### 7.7: Task owner
|
||||
|
||||
First, let's change our publication to publish the tasks only for the currently logged user. This is important for security, as you send only data that belongs to that user.
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [/imports/api/TasksPublications.js]
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { TasksCollection } from "./TasksCollection";
|
||||
|
||||
Meteor.publish("tasks", function () {
|
||||
let result = this.ready();
|
||||
const userId = this.userId;
|
||||
if (userId) {
|
||||
result = TasksCollection.find({ userId });
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Now let's check if we have a `user` before trying to fetch any data:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
Template.mainContainer.helpers({
|
||||
tasks() {
|
||||
let result = [];
|
||||
if (isUserLoggedInChecker()) {
|
||||
const instance = Template.instance();
|
||||
const hideCompleted = instance.state.get(HIDE_COMPLETED_STRING);
|
||||
|
||||
const hideCompletedFilter = { isChecked: { $ne: true } };
|
||||
|
||||
result = TasksCollection.find(hideCompleted ? hideCompletedFilter : {}, {
|
||||
sort: { createdAt: -1, _id: -1 },
|
||||
}).fetch();
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
hideCompleted() {
|
||||
return Template.instance().state.get(HIDE_COMPLETED_STRING);
|
||||
},
|
||||
incompleteCount() {
|
||||
result = '';
|
||||
if (isUserLoggedInChecker()) {
|
||||
const incompleteTasksCount = TasksCollection.find({ isChecked: { $ne: true } }).count();
|
||||
result = incompleteTasksCount ? `(${incompleteTasksCount})` : '';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
isUserLoggedIn() {
|
||||
return isUserLoggedInChecker();
|
||||
},
|
||||
});
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Also, update the `tasks.insert` method to include the field `userId` when creating a new task:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/api/TasksMethods.js]
|
||||
...
|
||||
Meteor.methods({
|
||||
"tasks.insert"(doc) {
|
||||
const insertDoc = { ...doc };
|
||||
if (!('userId' in insertDoc)) {
|
||||
insertDoc.userId = this.userId;
|
||||
}
|
||||
return TasksCollection.insertAsync(insertDoc);
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 7.8: Log out
|
||||
|
||||
We can also organize our tasks by showing the owner’s username below our app bar. Let’s add a new `div` where the user can click and log out from the app:
|
||||
|
||||
::: code-group
|
||||
|
||||
```html [imports/ui/App.html]
|
||||
...
|
||||
<div class="main">
|
||||
{{#if isUserLoggedIn}}
|
||||
<div class="user">
|
||||
{{getUser.username}} 🚪
|
||||
</div>
|
||||
|
||||
{{> form }}
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Now, let’s create the `getUser` helper and implement the event that will log out the user when they click on this `div`. Logging out is done by calling the function `Meteor.logout()`:
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [imports/ui/App.js]
|
||||
...
|
||||
|
||||
Template.mainContainer.events({
|
||||
...,
|
||||
'click .user'() {
|
||||
Meteor.logout();
|
||||
},
|
||||
});
|
||||
|
||||
...
|
||||
|
||||
Template.mainContainer.helpers({
|
||||
...,
|
||||
getUser() {
|
||||
return getUser();
|
||||
},
|
||||
});
|
||||
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
Remember to style your username as well.
|
||||
|
||||
::: code-group
|
||||
|
||||
```css [client/main.css]
|
||||
.user {
|
||||
display: flex;
|
||||
|
||||
align-self: flex-end;
|
||||
|
||||
margin: 8px 16px 0;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Phew! You have done quite a lot in this step. Authenticated the user, set the user in the tasks, and provided a way for the user to log out.
|
||||
|
||||
Your app should look like this:
|
||||
|
||||
<img width="200px" src="/tutorials/blaze/assets/step07-login.png"/>
|
||||
<img width="200px" src="/tutorials/blaze/assets/step07-logout.png"/>
|
||||
|
||||
In the next step, we are going to learn how to deploy your app!
|
||||
98
v3-docs/docs/tutorials/blaze/8.deploying.md
Normal file
@@ -0,0 +1,98 @@
|
||||
## 8: Deploying
|
||||
|
||||
Deploying a Meteor application is similar to deploying any other Node.js app that uses websockets. You can find deployment options in [our guide](https://guide.meteor.com/deployment), including Meteor Up, Docker, and our recommended method, Galaxy.
|
||||
|
||||
In this tutorial, we will deploy our app on [Galaxy](https://galaxycloud.app/), which is our own cloud solution. Galaxy offers a free plan, so you can deploy and test your app. Pretty cool, right?
|
||||
|
||||
|
||||
### 8.1: Create your account
|
||||
|
||||
|
||||
You need a Meteor account to deploy your apps. If you don’t have one yet, you can [sign up here](https://cloud.meteor.com/?isSignUp=true).
|
||||
With this account, you can access our package manager, [Atmosphere](https://atmospherejs.com/), [Forums](https://forums.meteor.com/) and more.
|
||||
|
||||
|
||||
|
||||
### 8.2: Set up MongoDB (Optional)
|
||||
|
||||
|
||||
As your app uses MongoDB the first step is to set up a MongoDB database, Galaxy offers MongoDB hosting on a free plan for testing purposes, and you can also request for a production ready database that allows you to scale.
|
||||
|
||||
In any MongoDB provider you will have a MongoDB URL which you must use. If you use the free option provided by Galaxy, the initial setup is done for you.
|
||||
|
||||
Galaxy MongoDB URL will be like this: `mongodb://username:<password>@org-dbname-01.mongodb.galaxy-cloud.io` .
|
||||
> You can read more about Galaxy MongoDB [here](https://galaxy-support.meteor.com/en/article/mongodb-general-1syd5af/).
|
||||
|
||||
### 8.3: Set up settings
|
||||
|
||||
|
||||
If you are not using the free option, then you need to create a settings file. It’s a JSON file that Meteor apps can read configurations from. Create this file in a new folder called `private` in the root of your project. It is important to notice that `private` is a special folder that is not going to be published to the client side of your app.
|
||||
|
||||
Make sure you replace `Your MongoDB URL` by your own MongoDB URL :)
|
||||
|
||||
|
||||
::: code-group
|
||||
```json [private/settings.json]
|
||||
{
|
||||
"galaxy.meteor.com": {
|
||||
"env": {
|
||||
"MONGO_URL": "Your MongoDB URL"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
### 8.4: Deploy it
|
||||
|
||||
|
||||
Now you are ready to deploy, run `meteor npm install` before deploying to make sure all your dependencies are installed.
|
||||
|
||||
You also need to choose a subdomain to publish your app. We are going to use the main domain `meteorapp.com` that is free and included on any Galaxy plan.
|
||||
|
||||
In this example we are going to use `blaze-meteor-3.meteorapp.com` but make sure you select a different one, otherwise you are going to receive an error.
|
||||
|
||||
|
||||
> You can learn how to use custom domains on Galaxy [here](https://galaxy-support.meteor.com/en/article/domains-16cijgc/). Custom domains are available starting with the Essentials plan.
|
||||
|
||||
Run the deployment command:
|
||||
|
||||
```shell
|
||||
meteor deploy blaze-meteor-3.meteorapp.com --free --mongo
|
||||
```
|
||||
|
||||
> If you are not using the free hosting with MongoDB on Galaxy, then remove the `--mongo` flag from the deploy script and add `--settings private/settings.json` with the proper setting for your app.
|
||||
|
||||
Make sure you replace `blaze-meteor-3` by a custom name that you want as subdomain. You will see a log like this:
|
||||
|
||||
```shell
|
||||
meteor deploy blaze-meteor-3.meteorapp.com --settings private/settings.json
|
||||
Talking to Galaxy servers at https://us-east-1.galaxy-deploy.meteor.com
|
||||
Preparing to build your app...
|
||||
Preparing to upload your app...
|
||||
Uploaded app bundle for new app at blaze-meteor-3.meteorapp.com.
|
||||
Galaxy is building the app into a native image.
|
||||
Waiting for deployment updates from Galaxy...
|
||||
Building app image...
|
||||
Deploying app...
|
||||
You have successfully deployed the first version of your app.
|
||||
For details, visit https://galaxy.meteor.com/app/blaze-meteor-3.meteorapp.com
|
||||
```
|
||||
|
||||
|
||||
This process usually takes just a few minutes, but it depends on your internet speed as it’s going to send your app bundle to Galaxy servers.
|
||||
|
||||
> Galaxy builds a new Docker image that contains your app bundle and then deploy containers using it, [read more](https://galaxy-support.meteor.com/en/article/container-environment-lfd6kh/).
|
||||
You can check your logs on Galaxy, including the part that Galaxy is building your Docker image and deploying it.
|
||||
|
||||
### 8.5: Access the app and enjoy
|
||||
|
||||
|
||||
Now you should be able to access your Galaxy dashboard at `https://galaxy.meteor.com/app/blaze-meteor-3.meteorapp.com`.
|
||||
|
||||
You can also access your app on Galaxy 2.0 which is currently in beta at `https://galaxy-beta.meteor.com/<your-username>/us-east-1/apps/<your-app-name>.meteorapp.com`. Remember to use your own subdomain instead of `blaze-meteor-3`.
|
||||
|
||||
You can access the app at [blaze-meteor-3.meteorapp.com](https://blaze-meteor-3.meteorapp.com/)! Just use your subdomain to access yours!
|
||||
|
||||
> We deployed to Galaxy running in the US (us-east-1), we also have Galaxy running in other regions in the world, check the list [here](https://galaxy-support.meteor.com/en/article/regions-1vucejm/).
|
||||
This is huge, you have your app running on Galaxy, ready to be used by anyone in the world!
|
||||
17
v3-docs/docs/tutorials/blaze/9.next-steps.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## 9: Next Steps
|
||||
|
||||
You have completed the tutorial!
|
||||
|
||||
By now, you should have a good understanding of working with Meteor and Blaze.
|
||||
|
||||
::: info
|
||||
You can find the final version of this app in our [GitHub repository](https://github.com/meteor/meteor3-blaze).
|
||||
:::
|
||||
|
||||
Here are some options for what you can do next:
|
||||
|
||||
- Check out the complete [documentation](https://v3-docs.meteor.com/) to learn more about Meteor 3.
|
||||
- Read the [Galaxy Guide](https://galaxy-support.meteor.com/en/article/deploy-to-galaxy-18gd6e2/) to learn more about deploying your app.
|
||||
- Join our community on the [Meteor Forums](https://forums.meteor.com/) and the [Meteor Lounge on Discord](https://discord.gg/hZkTCaVjmT) to ask questions and share your experiences.
|
||||
|
||||
We can't wait to see what you build next!
|
||||
BIN
v3-docs/docs/tutorials/blaze/assets/collections-connect-db.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/collections-documents.png
Normal file
|
After Width: | Height: | Size: 189 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/collections-see-database.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/collections-tasks-list.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 17 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step03-form-new-task.png
Normal file
|
After Width: | Height: | Size: 178 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step03-new-task-on-list.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step04-checkbox.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step04-delete-button.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step05-styles.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step06-all.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step06-ddp-messages.png
Normal file
|
After Width: | Height: | Size: 418 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step06-extension.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step06-filtered.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step07-login.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
v3-docs/docs/tutorials/blaze/assets/step07-logout.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
25
v3-docs/docs/tutorials/blaze/index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Meteor.js 3 + Blaze
|
||||
|
||||
In this tutorial, we will create a simple To-Do app using [Blaze](https://www.blazejs.org/) and Meteor 3.0. Meteor works well with other frameworks like [React](https://react.dev/), [Vue 3](https://vuejs.org/), [Solid](https://www.solidjs.com/), and [Svelte](https://svelte.dev/).
|
||||
|
||||
Blaze was created as part of Meteor when it launched in 2011, React was created by Facebook in 2013. Both have been used successfully by large production apps. Blaze is the easiest to learn and has the most full-stack Meteor packages, but React is more developed and has a larger community. Vue, Solid and Svelte are newer UI frameworks that some teams prefer. Choose what you like but learning Blaze is always a good idea because you may want to use Blaze UI packages with your app even if you are using something like React. The [accounts-ui](https://docs.meteor.com/packages/accounts-ui) package is a good example.
|
||||
|
||||
Blaze is a powerful library for creating user interfaces by writing reactive HTML templates using an easy-to-learn Handlebars-like template syntax. If you are new and not sure what UI framework to use, Blaze is a great place to start. Compared to using a combination of traditional templates and jQuery, Blaze eliminates the need for all the “update logic” in your app that listens for data changes and manipulates the DOM. Instead, familiar template directives like <span v-pre>{{#if}}</span> and <span v-pre>{{#each}}</span> integrates with [Tracker’s](https://docs.meteor.com/api/tracker.html) “transparent reactivity” and [Minimongo’s](https://docs.meteor.com/api/collections.html) database cursors so that the DOM updates automatically.
|
||||
|
||||
To start building your Blaze app, you'll need a code editor. If you're unsure which one to choose, [Visual Studio Code](https://code.visualstudio.com/) is a good option.
|
||||
|
||||
Let’s begin building your app!
|
||||
|
||||
# Table of Contents
|
||||
|
||||
[[toc]]
|
||||
|
||||
<!-- @include: ./1.creating-the-app.md-->
|
||||
<!-- @include: ./2.collections.md-->
|
||||
<!-- @include: ./3.forms-and-events.md-->
|
||||
<!-- @include: ./4.update-and-remove.md-->
|
||||
<!-- @include: ./5.styles.md-->
|
||||
<!-- @include: ./6.filter-tasks.md-->
|
||||
<!-- @include: ./7.adding-user-accounts.md-->
|
||||
<!-- @include: ./8.deploying.md-->
|
||||
<!-- @include: ./9.next-steps.md-->
|
||||