Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/suzukiryuichiro/vue-todo
https://github.com/suzukiryuichiro/vue-todo
Last synced: 1 day ago
JSON representation
- Host: GitHub
- URL: https://github.com/suzukiryuichiro/vue-todo
- Owner: SuzukiRyuichiro
- Created: 2021-06-25T02:04:19.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2021-06-25T09:20:42.000Z (over 3 years ago)
- Last Synced: 2024-11-09T09:39:45.781Z (about 2 months ago)
- Language: Vue
- Size: 124 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
### JavaScript Workshop
# Introduction to Vue.js
Learn how to build this simple Todo app.
**REGISTER:**
> https://info.lewagon.com/tokyo-vuejs
Note: the slide version of this workshop is available [here](https://slides.trouni.com/?src=trouni/workshop-vuejs-todo).
---
## Setup
**TODO:**
Clone the git repository for this workshop and get into the project folder.
```sh
git clone https://github.com/trouni/workshop-vuejs-todo.gitcd workshop-vuejs-todo
```**OR** [download the ZIP file](https://github.com/trouni/workshop-vuejs-todo/archive/refs/heads/main.zip) and unzip the archive to your projects folder or desktop.
**TODO:**
Run `yarn install` to install the project's dependencies, then `yarn serve` to launch a local server.
```sh
yarn install # Installs dependenciesyarn serve # Compiles and hot-reloads for development
```You should now be able to view your app on http://localhost:8080.
### Optional Setup
Not required, but you can also install these useful tools:
- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) for syntax highlighting, snippets, _etc._ in VS Code, or [this package](https://medium.com/@kentaguilar/install-vue-syntax-highlighting-via-package-control-on-sublime-text-2-bfb977f444e7) for Sublime Text.
- [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd), a Chrome extension to debug your Vue app from the browser.---
## Intro to Vue Components
### Component File Structure
```html
// Data & logic
/* Styling */
```
β οΈ The `` should only have a **single** root element.
### Creating your first components
Create a new `/components/TasksList.vue` file with the following code:
```html
This is my first Vue.js component
```
Then import the component in your `App.vue` file.
```vue
Task Manager
import TasksList from './components/TasksList.vue'
export default {
name: 'App',
components: {
TasksList
}
}```
Let's replace the text with a static card:
```html
Create a card component
Create a new TaskCard.vue file in the components folder, then import it in TasksList.vue
```
### Styling components
You can add styling rules for each component in the `` section. Try it out!
You can use this styling I prepared for the card, but we should first move the task card into its own component. Move the previous code into a `TaskCard.vue` and add this styling:
```vue
<!-- TaskCard.vue --><style lang="scss">
.task-card {
display: flex;
min-height: 6rem;
text-align: left;
background-color: saturate(rgba(#41b883, 0.03), 30%);
border-bottom: solid 1px rgba(#35495e, 0.1);
border-left: solid 8px #41b883;
transition: all 0.3s;
p { font-size: 0.9rem; }
&:hover {
transform: scale(1.02);
background-color: white;
box-shadow: 2px 3px 10px rgba(black, 0.1);
}
& > div:first-child {
margin: 0 1rem;
padding: 1rem 0;
display: flex;
flex-grow: 1;
flex-direction: column;
justify-content: center;
}
&.done {
border-left: solid 8px rgba(#35495e, 0.3);
background-color: rgba(#35495e, 0.08);
h3, p { text-decoration: line-through; opacity: 0.3; }
&:hover { transform: unset; box-shadow: unset; }
}
}```
## Making dynamic components
### Defining the State of a Component with `data`
Let's define some `data` and use the mustaches `{{ }}` to interpolate the values in our HTML template.
**π‘Tip**: Install the awesome [Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd), a Chrome extension to inspect data (and more) in your Vue app.
```vue
{{ title }}
{{ description }}
{{ done ? "β " : "βοΈ" }}
export default {
data() {
return {
title: "Make the card component dynamic",
description:
"Learn about using the data option and passing data to child components using props",
done: false
};
},
};```
**We want our component to be reusable:**
Let's assign the `title` and `description` dynamically.
### Passing Data to Child Components with `props`
> Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Props are basically special data attributes **that come from the parent component**.
Let's replace the `data` option with `props`:
```js
// TaskCard.vueexport default {
props: {
title: String,
description: String,
done: { type: Boolean, default: false }
},
};
```We can now pass the `title` and `description` from the parent component:
```html
<!-- TasksList.vue <template> --><TaskCard
title="Make the card component dynamic"
description="Learn about using the data option and passing data to child components using props"
done="true"
/>
```## Vue Directives
Add some static tasks to help us implement our app:
```js
// TasksList.vue <script>data() {
return {
tasks: [
{
title: "Create a card component",
description:
"Create a new TaskCard.vue file in the components folder, then import it in TasksList",
done: true,
},
{
title: "Make the card component dynamic",
description:
"Learn about using the data option and passing data to child components using props",
done: true,
},
{
title: "Bind the attributes to the data",
description:
"Use the v-bind directive to bind the title and description to our data",
done: false,
},
],
};
},
```### Binding Attributes to Props and Data with `v-bind`
We want to use the values from our `tasks` in the `title` and `description` attributes of our `TaskCard` component, but mustaches cannot be used inside HTML attributes.
Instead, use a `v-bind` directive (shorthand `:`).
```html
<!-- TasksList.vue <template> --><TaskCard
v-bind:title="tasks[0].title"
v-bind:description="tasks[0].description"
v-bind:done="tasks[0].done"
/>
```### Binding HTML Classes
We can bind the class attribute to add a `done` class to the card when the task is completed.
```html
<!-- TaskCard.vue <template> --><div :class="['task-card', { done }]">
```### Iterate with `v-for`
Let's now display all of our tasks by iterating over the `tasks` array using the `v-for` directive.
```html
<!-- TasksList.vue <template> --><TaskCard
v-for="(task, index) in tasks"
:key="index"
:title="task.title"
:description="task.description"
:done="task.done"
/>
```### Conditional Rendering with `v-if` / `v-else`
Let's display a small message when we have no tasks in our app.
```html
<!-- TasksList.vue <template> --><div v-if="tasks.length > 0" class="tasks-list">
<TaskCard
...
/>
</div>
<p v-else>You don't have any tasks yet...</p>
```### Adding Behavior with `methods`
Implement an `addTask()` method to push a new task inside of the `tasks` array.
```js
// TasksList.vue <script>// ...
methods: {
addTask(title, description, done = false) {
this.tasks.unshift({ title, description, done });
},
},
// ...
```### Listen for events with `v-on`
Let's add a button to test that our method works and make it listen to `click` events using `v-on` (shorthand `@`).
```html
<!-- TasksList.vue <template> --><button
class="btn round-icon"
v-on:click="addTask('My new task', 'My new description')"
>οΌ</button>
```### Capturing user input with `v-model`
We still need to be able to enter the `title` and `description` ourselves. Let's add some inputs to our `TasksList`.
```html
<!-- TasksList.vue <template> --><div class="task-card new-task">
<div>
<input
type="text"
placeholder="What would you like to do?"
/>
<textarea
placeholder="Add some details about your task..."
></textarea>
</div>
</div>
```Add the following styles to your `TasksList` component:
```vue
<!-- TasksList.vue --><style lang="scss">
@keyframes expand-vertical { from { min-height: 0; height: 0; } to { min-height: 6rem; } }
.task-card.new-task {
animation: expand-vertical 0.2s;
overflow: hidden;
background-color: white;
border-left: solid 5px #35495e;
&, &:hover { transform: scale(1.1); box-shadow: 2px 3px 10px rgba(black, 0.2); }
& + .tasks-list { pointer-events: none; }
input { font-size: 1.17rem; font-weight: bold; width: 100%; }
textarea { width: 100%; font-size: 0.9rem; resize: none; }
}
</style>
```We can bind the input fields with data attributes in our component.
```vue
<!-- TasksList.vue --><template>
<!-- ... -->
<input
type="text"
placeholder="What would you like to do?"
v-model="newTitle"
/>
<textarea
placeholder="Add some details about your task..."
v-model="newDescription"
></textarea>
<!-- ... -->
</template><script>
export default {
data() {
return {
tasks: [],
newTitle: "",
newDescription: "",
};
},
};```
### Update the `v-on` directive
```vue
οΌ
// ...
methods: {
addTask(title, description, done = false) {
this.tasks.unshift({ title, description, done });
},
resetForm() {
this.newTitle = ""
this.newDescription = ""
}
},
// ...```
### Manipulate the DOM by combining `v-on` and `v-if`/`v-show`
Let's only show the inputs after we click on the `+` button.
```vue
{{ newFormVisible ? "β" : "οΌ" }}
You don't have any tasks yet...
// ...
data() {
return {
// ...
newFormVisible: false,
};
},methods: {
addTask(title, description, done = false) {
// ...
this.newFormVisible = false;
},
// ...
```## Bonus
### Persist Data with `localStorage`
We can easily store the `tasks` array directly in the user's browser:
```js
// TasksList.vue <script>// ...
data() {
return {
tasks: JSON.parse(localStorage.getItem("tasks")) || []
};
},watch: {
tasks() {
localStorage.setItem("tasks", JSON.stringify(this.tasks));
},
},
// ...
```### Send Data to Parent Components using Custom Events
To pass data _upstream_ from a child to a parent component:
1. the child component emits a custom event using `$emit('custom-event')`
2. the parent component listens for that event using `v-on:custom-event`Let's move the input fields and style from `TasksList` to a new component `NewTask`.
```vue
<!-- NewTask.vue --><template>
<div class="task-card new-task"
@keyup.enter="submitTask(newTitle, newDescription), resetForm()">
<div>
<input
type="text"
placeholder="What would you like to do?"
v-model="newTitle"
/>
<textarea
placeholder="Add some details about your task..."
v-model="newDescription"
></textarea>
</div>
</div>
</template><style lang="scss">
@keyframes expand-vertical { from { min-height: 0; height: 0; } to { min-height: 6rem; } }
.task-card.new-task {
animation: expand-vertical 0.2s;
overflow: hidden;
background-color: white;
border-left: solid 5px #35495e;
&, &:hover { transform: scale(1.1); box-shadow: 2px 3px 10px rgba(black, 0.2); }
& + .tasks-list { pointer-events: none; }
input { font-size: 1.17rem; font-weight: bold; width: 100%; }
textarea { width: 100%; font-size: 0.9rem; resize: none; }
}
</style>
```In this component, let's replace the `addTask` method with a `submitTask` method:
```vue
<!-- NewTask.vue --><template>
<div class="task-card new-task"
@keyup.enter="submitTask(newTitle, newDescription), resetForm()">
<!-- ... -->
</template><script>
// ...
methods: {
submitTask(title, description) {
// Problem is that the `tasks` array is in the parent component `TasksList`. We need to submit the title and description "upstream".
},
// ...
},```
We can send a custom event upstream using `$emit`:
```js
// NewTask.vue// ...
methods: {
submitTask(title, description) {
this.$emit("add-task", title, description);
},
},
// ...
```The parent component `TasksList` listens for the custom `add-task` event, and calls the `addTask` method when the event is triggered.
```html
<!-- TasksList.vue <template> --><NewTask @add-task="addTask" />
```### Mark Tasks as Done
Implement this `toggleTask` method in the `TasksList` component:
```js
// TasksList.vue <script>methods: {
// ...
toggleTask(taskIndex) {
const taskToUpdate = this.tasks[taskIndex];
taskToUpdate.done = !taskToUpdate.done;
this.$set(this.tasks, taskIndex, taskToUpdate);
},
}
```Note that Vue2 requires us to use `this.$set` rather than `this.tasks[taskIndex] = taskToUpdate` to make sure [Vue reacts to the changes in the array](https://vuejs.org/v2/guide/reactivity.html#For-Arrays).
Create this new UI component `Checkbox`:
```vue
<!-- Checkbox.vue --><template>
<div :class="['checkbox', { checked }]"></div>
</template><script>
export default {
props: {
checked: Boolean,
},
};$check-color: #41b883;
.checkbox {
display: flex;
align-self: center;
border: solid 2px $check-color;
border-radius: 50%;
width: 2rem;
height: 2rem;
margin: 0 1rem;
cursor: pointer;
&:hover { background-color: rgba($check-color, 0.2); transform: scale(1.1); }
&.checked { background-color: $check-color; }
&.checked:after {
content: "";
display: inline-block;
transform: rotate(45deg) translate(-35%, 40%);
height: 1rem;
width: 1rem;
margin-left: 60%;
border-bottom: 5px solid white;
border-right: 5px solid white;
}
}```
Modify your `TaskCard` component to use the `Checkbox` and emit a custom event on `click`.
```vue
{{ title }}
{{ description }}
import Checkbox from "./Checkbox";
export default {
components: {
Checkbox,
},props: {
title: String,
description: String,
done: Boolean,
taskIndex: Number,
},
};```
### The Finished App
You can view the finished code for the app [here](https://github.com/trouni/workshop-vuejs-todo/tree/solution).
## What to look at next?
- [Computed Properties](https://v3.vuejs.org/api/options-data.html#computed)
- [Vue Lifecycle Hooks](https://v3.vuejs.org/api/options-lifecycle-hooks.html)
- [Component Slots](https://v3.vuejs.org/api/options-data.html#computed)
- [Vue Router](https://router.vuejs.org/)---
## Happy coding!
Workshop/tutorial by **Trouni Tiet**\
[LinkedIn](https://linkedin.com/trouni) | [GitHub](https://github.com/trouni)