Сегодня мы создадим приложение — список задач с Vue.js, а также рассмотрим и другие удобные инструменты для создания современных веб-приложений.
Убедитесь, что у вас установлена Vue CLI! Если нет, то установите её командой
$ npm install --global vue-cli
С Vue CLI идут несколько шаблонов готовых приложений:
webpack - Полнофункциональная установка Webpack + Vue-loader с hot reload, linting, testing & CSS extraction.
webpack-simple - Упрощённая установка Webpack + Vue-loader.
browserify - Полнофункциональная установка Browserify + vueify с hot-reload, linting & unit testing.
browserify-simple - Упрощённая установка Browserify + vueify.
simple - Простенькая установка Vue всего в одном файле HTML
Создадим приложение:
$ vue init webpack todo-app
Вас спросят название для проекта, описание, автора и сборку Vue. Мы будем работать не устанавливая Vue-router.
Установим зависимости:
$ cd my-project
$ npm install
Для запуска приложения выполните команду:
$ npm run dev
Откроется браузер на странице localhost:8080
Для стилизации приложения будем использовать Semantic.
Структура компонента
Каждое приложение Vue имеет компонент верхнего уровня, у нас таким компонентом будет TodoList.
У нас уже есть готовый компонент Hello (сгенерированный Vue CLI) в папке src/App.vue, осталось создать недостающие компоненты нижнего уровня. Создадим простенький компонент TodoList.vue:
<template>
<div>
<ul>
<li> Todo A </li>
<li> Todo B </li>
<li> Todo C </li>
</ul>
</div>
</template>
<script type = "text/javascript" >
export default {
};
</script>
<style>
</style>
Это обычная заготовка, которую мы заполним позднее.
Импортируем наш компонент в главный экземпляр Vue, чтобы можно было с ним работать. Откройте файл src/App.vue и отредактируйте его:
<script>
import TodoList from './components/TodoList';
export default {
components: {
// добавим ссылку на компонент TodoList
TodoList,
},
};
</script>
Для обработки компонента необходимо его включить в HTML:
<template>
<div>
// Обработка компонента TodoList
// он будет здесь:
<todo-list></todo-list>
</div>
</template>
Теперь наше приложение выглядит так: Мы должны передавать данные главному компоненту для вывода списка задач. Задачи будут иметь три свойства: Название, Проект и Выполнено.
Добавим данных в наш компонент:
export default {
name: 'app',
components: {
TodoList,
},
// data function avails data to the template
data() {
return {
todos: [{
title: 'Todo A',
project: 'Project A',
done: false,
}, {
title: 'Todo B',
project: 'Project B',
done: true,
}, {
title: 'Todo C',
project: 'Project C',
done: false,
}, {
title: 'Todo D',
project: 'Project D',
done: false,
}],
};
},
};
Нам нужно передать данные из главного компонента в TodoList и для этого мы будем использовать директиву v-bind. Она принимает аргумент (он отделяется двоеточием от имени директивы), в нашем случае это будет todos; это показывает директиве v-bind связать элемент todos с тем, что идёт в нём.
<todo-list v-bind:todos="todos"></todo-list>
Теперь нужно доработать компонент TodoList для доступа к этим данным. Добавим свойство к классу компонента:
export default {
props: ['todos'],
}
Обработка данных
Пройдёмся в цикле по списку задач в шаблоне TodoList и выведем количество завершённых и незавершённых задач:
<template>
<div>
// JavaScript expressions in Vue are enclosed in double curly brackets.
<p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
<p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
<div class='ui centered card' v-for="todo in todos">
<div class='content'>
<div class='header'>
{{ todo.title }}
</div>
<div class='meta'>
{{ todo.project }}
</div>
<div class='extra content'>
<span class='right floated edit icon'>
<i class='edit icon'></i>
</span>
</div>
</div>
<div class='ui bottom attached green basic button' v-show="todo.done">
Completed
</div>
<div class='ui bottom attached red basic button' v-show="!todo.done">
Complete
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: ['todos'],
};
</script>
Редактирование задач
Разделим шаблон задачи для улучшения читабельности кода. Создайте новый компонент Todo.vue в папке src/components и перенесите в него шаблон задачи:
<template>
<div class='ui centered card'>
<div class='content'>
<div class='header'>
{{ todo.title }}
</div>
<div class='meta'>
{{ todo.project }}
</div>
<div class='extra content'>
<span class='right floated edit icon'>
<i class='edit icon'></i>
</span>
</div>
</div>
<div class='ui bottom attached green basic button' v-show="todo.done">
Completed
</div>
<div class='ui bottom attached red basic button' v-show="!todo.done">
Complete
</div>
</div>
</template>
<script type="text/javascript">
export default {
props: ['todo'],
};
</script>
Переработаем компонент TodoList для работы с компонентом Todo:
<template>
<div>
<p>Completed Tasks: {{todos.filter(todo => {return todo.done === true}).length}}</p>
<p>Pending Tasks: {{todos.filter(todo => {return todo.done === false}).length}}</p>
// we are now passing the data to the todo component to render the todo list
<todo v-for="todo in todos" :todo.sync="todo"></todo>
</div>
</template>
<script type = "text/javascript" >
import Todo from './Todo';
export default {
props: ['todos'],
components: {
Todo,
},
};
</script>
Добавим свойство isEditing в класс компонента Todo, это свойство покажет редактируется ли в данный момент задача или нет. Повесим обработчик события на элемент Edit и будем показывать форму редактирования, при этом изменим значение свойства isEditing в true. Осталось добавить форму и установить начальное значение свойства. Наш шаблон теперь выглядит так:
<template>
<div class='ui centered card'>
// Todo shown when we are not in editing mode.
<div class="content" v-show="!isEditing">
<div class='header'>
{{ todo.title }}
</div>
<div class='meta'>
{{ todo.project }}
</div>
<div class='extra content'>
<span class='right floated edit icon' v-on:click="showForm">
<i class='edit icon'></i>
</span>
</div>
</div>
// form is visible when we are in editing mode
<div class="content" v-show="isEditing">
<div class='ui form'>
<div class='field'>
<label>Title</label>
<input type='text' v-model="todo.title" >
</div>
<div class='field'>
<label>Project</label>
<input type='text' v-model="todo.project" >
</div>
<div class='ui two button attached buttons'>
<button class='ui basic blue button' v-on:click="hideForm">
Close X
</button>
</div>
</div>
</div>
<div class='ui bottom attached green basic button' v-show="!isEditing &&todo.done" disabled>
Completed
</div>
<div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">
Pending
</div>
</div>
</template>
</template>
У нас есть метод showForm, открывающий форму правки, теперь добавим метод hideForm для её закрытия при нажатии на кнопку отмены:
<script>
export default {
props: ['todo'],
data() {
return {
isEditing: false,
};
},
methods: {
showForm() {
this.isEditing = true;
},
hideForm() {
this.isEditing = false;
},
},
};
</script>
Данные в форме уже привязаны к списку задач и их редактирование автоматически сохраняет обновлённые данные. По нажатию кнопки Close увидим обновлённую задачу:
Удаление задачи
Добавим иконку удаления задачи:
<template>
<span class='right floated edit icon' v-on:click="showForm">
<i class='edit icon'></i>
</span>
/* add the trash icon in below the edit icon in the template */
<span class='right floated trash icon' v-on:click="deleteTodo(todo)">
<i class='trash icon'></i>
</span>
</template>
Добавим метод в класс компонента, вызываемый по событию delete-todo, и передающий компоненту текущую задачу к удалению.
<span class='right floated trash icon' v-on:click="deleteTodo(todo)">
// Todo component
methods: {
deleteTodo(todo) {
this.$emit('delete-todo', todo);
},
},
Теперь нужно добавить обработчик события удаления в родительский компонент (TodoList):
// TodoList component
methods: {
deleteTodo(todo) {
const todoIndex = this.todos.indexOf(todo);
this.todos.splice(todoIndex, 1);
},
},
Передаём метод deleteTodo в компонент Todo:
// шаблон TodoList
<todo v-on:remove-todo="deleteTodo" v-for="todo in todos" :todo.sync="todo"></todo>
Добавление новой задачи
Для создания новой задачи сделаем новый компонент CreateTodo в папке src/components. Он выведет кнопку со знаком плюс, она при нажатии откроет форму добавления задачи:
<template>
<div class='ui basic content center aligned segment'>
<button class='ui basic button icon' v-on:click="openForm" v-show="!isCreating">
<i class='plus icon'></i>
</button>
<div class='ui centered card' v-show="isCreating">
<div class='content'>
<div class='ui form'>
<div class='field'>
<label>Title</label>
<input v-model="titleText" type='text' ref='title' defaultValue="">
</div>
<div class='field'>
<label>Project</label>
<input type='text' ref='project' defaultValue="">
</div>
<div class='ui two button attached buttons'>
<button class='ui basic blue button' v-on:click="sendForm()">
Create
</button>
<button class='ui basic red button' v-on:click="closeForm">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
titleText: '',
projectText: '',
isCreating: false,
};
},
methods: {
openForm() {
this.isCreating = true;
},
closeForm() {
this.isCreating = false;
},
sendForm() {
if (this.titleText.length > 0 && this.projectText.length > 0) {
const title = this.titleText;
const project = this.projectText;
this.$emit('create-todo', {
title,
project,
done: false,
});
this.newTodoText = '';
}
this.isCreating = false;
},
},
};
</script>
Добавим новый компонент в главный компонент:
// главный компонент в App.vue
components: {
TodoList,
CreateTodo,
},
А также добавим метод для создания новой задачи:
// в App.vue
methods: {
addTodo(title) {
this.todos.push({
title,
done: false,
});
},
},
Вызов компонента CreateTodo из шаблона App.vue:
<create-form v-on:add-todo="addTodo">
Выполнение задачи
Осталось добавить метод для установки отметки о выполнении задачи.
// компонент Todo
methods: {
completeTodo(todo) {
this.$emit('complete-todo', todo);
},
}
Обработчик события будет добавлен в TodoList:
methods: {
completeTodo(todo) {
const todoIndex = this.todos.indexOf(todo);
this.todos[todoIndex].done = true;
},
},
И добавим вызов метода в шаблоне:
<todo v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" v-for="todo in todos" :todo.sync="todo"></todo>
Готово! Ниже по ссылкам можно увидеть полный код приложения или пощупать демонстрационную версию.
Полный код примера. Демо-версия.
По материалам «Build a To-Do App with Vue.js 2» by Jeremy Kithome