Skip to main content

Пишем «To-do» приложение на Vue 2

Vue — это простой и минимальный Javascript фреймворк, который может использоваться для создания мощных веб-приложений. Vue — это хорошоая альтернатива другим JavaScript фреймворкам, таким как AngularJS. Урок расчитан на людей у которых есть небольшие знания Html, Css и Javascript.

В этом уроке я покажу как создать свое небольшое так называется «TO-DO», приложение! Давайте начнем!

#Инструменты

Для начала нам понадобится установить интерфейс для командной строки VUE-CLI. Сам по себе он поможет нам быстро и легко собирать проект на VUE и выпускать его в продакшн за считанные секунды, облегчая жизнь и сокращая работы на многие часы. Внутри данного интерфейс вы сможете найти уже готовые и настроенные пакеты для масштабирования вашего проекта, в него входят

 
		// webpack - Набор программ который будут собирать проект, чистить, тестировать 
		и вытаскивать наш CSS

		// webpack-simple - Говый Webpack + Vue Loader для быстрого прототипирования

		//  simple - Простейшая настройка Vue в одном HTML-файл

 

#Установка

И так открываем нашу консоль (Необходим заранее установленный Node.js и Npm) и пишем следующую команду

 
	// устанавливаем vue-cli
	$ npm install --global vue-cli

 

Так же в этом уроке вы сможете научится использовать одностраничные компоненты VUE, которые намного удобнее стандартных вызовов. Так же я научу Вас как отправлять данные из родителского компонента в дочерний и обратно. А так же писать «реюзабальные» компоненты. Пристигните кресла мы начинаем.

# Создание приложения

Набираем в консоли:

 
	// Создание нового проекта использую WebPack
	$ vue init webpack todo-app

 

Затем вам будет предложенно несколько опций:
— Ввести название приложения и автора
— Нам не нужен Vue Router для нашего приложения потому-что оно у нас будет одностраничным
— Включим проверку кода через Linting
— Включаем Тесты которые предлагает нам консоль

Затем в консоле входим в папку с проектом и устанавлиаем все npm модули с помощью этих комманд и когда все закончится запускаем наш сервер:

 
	// Входим в папку с приложением
	$  cd название-приложения

	// Запускаем установку Npm-modules
	$ npm install

	// Запускаем наш сервер
	$ npm run dev

 

После этого скрипт автоматически запустит бразуер для Вас и вы увидите стандартное окно приложения VUE

# Структура Компонентов

Каждое приложение построенное на Vue имеет главный компонент , который служит основой для всего приложения. В нашем он у нас так же имеется и внутрь мы вложим, другой компонент TodoList. VUE-CLI уже создало для нас главный компонент и найти его можно по этому пути и выглядит он вот так:

	 
		// src/App.vue
	

		<template>
		  <div id="app">
		    <img src="./assets/logo.png">
		    <hello></hello>
		  </div>
		</template>

		<script>
			import Hello from './components/Hello'

			export default {
			  name: 'app',
			  components: {
			    Hello
			  }
			}
		</script>

		<style>
			#app {
			  font-family: 'Avenir', Helvetica, Arial, sans-serif;
			  -webkit-font-smoothing: antialiased;
			  -moz-osx-font-smoothing: grayscale;
			  text-align: center;
			  color: #2c3e50;
			  margin-top: 60px;
			}
		</style>



	

# Создание Компонента

VUE-CLI автоматически для нас создало компонент под название Hello.vue найти его можно в папке «src/components/» (заметьте одностраничные компоненты VUE имеют расширения файла .vue), вместо него мы создадим новый компонент TodoList.vue и напишем в нем такой-вот код:

	 
		// src/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 делятся на 3 части:
— Template — здесь мы пишем на HTML код
— Script — здесь мы сохраняем наши данные и выполняем какие-то базовые функции на JS
— Style — для написания нашего CSS — кода

# Импорт Компонента

Теперь давайте импортируем наш компонент в главный компонент, для этого воспользуемся синтаксисом импорта ES6, перейдем в наш главный компонент «src/App.vue» и перед добавим перед «export default» и удалим не нужный нам Hello.vue компонент;

	 
	// Добавим эту строчку
	import TodoList from './components/TodoList'  
	// Удалим эту строчку
	import Hello from './components/Hello'  

	

И в теге script у нас получится такой вот код:

	 
import TodoList from './components/TodoList.vue'

export default {
  name: 'app',
  components: {
    TodoList
  }
}

	

Затем в теге template уберем все и добавим наш компонент:

	 
<template>
  <div>
    <todo-list></todo-list>
  </div>
</template>
	

Заметьте название компонента описаны в так называемом «CamelCase», TodoList = todo-list, подробнее можете ознакомиться тут: ВерблюжийРегистр

Если открыть браузер можно увидеть вот это:

# Добавление данных компонента

Нам нужно будет предоставить данные основному компоненту, который будет использоваться для отображения списка задач. У наших задач будет три свойства; Название, Задача и Выполнена (выполнена да или нет). Компоненты используют данные в своих шаблонах, используя функцию данных. Эта функция возвращает объект со свойствами, предназначенными для шаблона. Давайте обновим наш компонент.

	 
export default {
  name: 'app',
  components: {
    TodoList
  },
  // Все данные использованные в данной функции можно будет вызывать в шаблоне
  data() {
    return {
      todos: [{
        title: 'Выучить Vue.js',
        project: 'Vue.js',
        done: false
      }, {
        title: 'Выучить React.js',
        project: 'React.js',
        done: true,
      }, {
        title: 'Выучить Angular 2',
        project: 'Angular 2',
        done: false
      }, {
        title: 'Выучить Angular',
        project: 'Angular',
        done: false
      }]
    }
  }
}
	

Теперь нам нужно отправить данные из этого компонента в наш TodoList компонент. Для этого в Vue предусмотрена директива v-bind. Директива принимает аргумент в виде масcива наших задач, все это выглядит вот так:

	 
<todo-list v-bind:todos="todos"></todo-list>
	

Удобно правда? Но данные все еще не доступны в нашем TodoList компоненте, давайте это исправим, для этого в нашем TodoList компоненте объявим свойство которые он должен будет принять:

	 

export default {  
    props: ['todos']
}
	

Теперь наши задачи доступны в TodoList компоненте.

# Вывод задач на странице

Теперь нам требуется вывести список задач на странице, для того что бы наши пользователи могли взаимодействовать с ним. Внутри нашего компонента TodoList в теге Template мы можем перебрать наш список задач, а также посчитать количество выполненных и незавершенных задач. Для перебора массивов в VUE внутри HTML предусмотрена директива v-for. Использовать его очень просто v-for=»todo in todos»; todos это наш массив который мы собираемся перебрать и вывести каждый его элемент (todo) как отдельный элемент на страницу. Вот как это выглядит на примере:


<template>
  <div>
    // Выражения  Javascript в Vue оборачиваются в двойные фигурные скобки 
    <p>Законченые задачи: {{todos.filter(todo => {return todo.done === true}).length}}</p>
    <p>Осталось Задач: {{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">
        Завершено
      </div>
      <div class='ui bottom attached red basic button' v-show="!todo.done">
        В процессе
      </div>
  </div>
</template>

<script type = "text/javascript" >

export default {
  props: ['todos']
}
</script>

	

# Рефактор компонента и редактирование «задачи» в списке задач

Конечно на этом этапе мы бы могли уже начать писать функции для обработки наших задач, но сейчас наш код немного грязный, а я бы хотел сделать его чуточку чище и удобнее для использования и редактирования в будущем, хорошей практикой в компонентном подходе считается разбивание больших компонентов на более мелкие, для обработки задач. В будущем когда наше приложение будет масштабироваться, нам будет легче использовать более мелкие компоненты. Давайте сделаем тоже самое и в нашем приложении.

Давайте создадим компонент и назовем его «Todo» внутри папки «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>

	

Абсолютно так-же как и в первый раз передадим данные в компонент «Todo» через директиву «v-bind», для этого вначале удалим ненужный участок кода который мы перенесли, объявим компонент «Todo» в HTML, а также в теге Script:


 
<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" v-bind:todo="todo"></todo>
  </div>
</template>

<script type = "text/javascript" >

import Todo from './Todo';

export default {
  props: ['todos'],
  components: {
    Todo
  }
}
</script>


Вы уже скорее всего заметили что «v-for» мы можем использовать не только со стандартными элементами HTML но и с компонентами, но будьте внимательны в VUE начиная с версии 2.2.0 и выше вы увидите ошибку, которая скажет вам о том что для каждого элемента необходим уникальный ключ, для решения этой проблемы достаточно отредактировать нашу директиву «TODO» таким образом:


 <todo  v-for="(todo, index) in todos"  v-bind:key="todo" v-bind:todo="todo">

Теперь давайте улучшим наше приложение визуально, для этого я воспользуюсь css-фреймворком «semantic ui» Вы можете использовать любой который Вам больше нравится. Подключу я его в файле index.html:


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>todo-app</title>
    
    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.css">
    <script
      src="https://code.jquery.com/jquery-3.1.1.min.js"
      integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
      crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.7/semantic.min.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
 

Теперь приложение выглядит намного приятнее:

Давайте добавим новое свойство isEditing к компоненту Todo. Это нам нужно для того чтобы при выборе для редактирование опеределеной задачи, ее состояние изменилось на «редактируемое» и появилась форма для ее изменения, в остальное время форма будет скрыта. На кнопку мы повесим обработчик событий с методом ShowForm который будет изменять значение свойство isEditing c false (по умолчанию) на true и это событие покажет форму редактирования. Но для начала давайте создадим эту самую форму в компоненте «TODO»:


<template>
  <div class='ui centered card'>
    // Задача отображается если мы не находимся в режиме редактирования  (isEditing = false)
    <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>
    // Форма отображается если задача находится в режиме редактирования (isEditing = true)
    <div class="content" v-show="isEditing">
      <div class='ui form'>
        <div class='field'>
          <label>Задача</label>
          <input type='text' v-model="todo.title" >
        </div>
        <div class='field'>
          <label>Проэкт</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">
            Закрыть X
          </button>
        </div>
      </div>
    </div>
    <div class='ui bottom attached green basic button' v-show="!isEditing &&todo.done" disabled>
        Завершено
    </div>
    <div class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">
        В процессе
    </div>
  </div>
</template>

В дополнение к методу «showForm()» нужно добавить еще один метод «hideForm» который с помощью обработчика событий на кнопке «Закрыть» будет закрывать форму редактирования меняя значение свойства isEditing обратно на false:


<script>
export default {
  props: ['todo'],
  data () {
    return {
      isEditing: false,
    }
  },
  methods: {
    showForm() {
      this.isEditing = true;
    },
    hideForm() {
      this.isEditing = false;
    }
  }
}
</script>


Так как мы привязали значения формы к значениям задачи(todo) и данных получаемых с самого первого компонента, редактирование значений немедленно изменит задачу (todo). И если мы нажмем кнопку «Закрыть», то увидим измененную задачу. Магия!

# Удаление задачи

Давайте добавим к иконочке редактирования еще одну с корзинкой для удаления задачи и повесим на нее обработчик по клику который вызовет метод из родительского компонента TodoList который удалит нашу задачу:


    <span class='right floated edit icon' v-on:click="showForm">
      <i class='edit icon'></i>
    </span>
    /* Добавим иконку удаления прямо под иконкой с редактированием */
    <span class='right floated trash icon' v-on:click="deleteTodo(todo)">
      <i class='trash icon'></i>
    </span>

Для того чтобы вызвать метод из родительского компонента мы добавим прослушиватель событий в наш код который выглядит это примерно вот так (в компонент Todo):


//Todo Component 
methods: {
    deleteTodo (todo) {
      this.$emit('delete-todo', todo)
    }
  }

А обработчик событий мы добавим в компонент Todo-list:


// TodoList component
methods: {
    deleteTodo (todo) {
      const todoIndex = this.todos.indexOf(todo)
      this.todos.splice(todoIndex, 1)
    }
  }

И опять в компонент Todo-List на нашу директиву todo добавим вызов метода события при нажатия на кнопку удаления:


 <todo v-on:delete-todo="deleteTodo"  v-for="(todo, index) in todos"  v-bind:key="todo" v-bind:todo="todo">

# Добавление задачи

Для добавления задачи мы создадим новый компонент который будет добавлять задачу в общий массив всех задач. Код будет примерно следующий:


<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>Название</label>
            <input v-model="titleText" type='text' ref='title' defaultValue="">
          </div>
          <div class='field'>
            <label>Проект</label>
            <input v-model="projectText" type='text' ref='project' defaultValue="">
          </div>
          <div class='ui two button attached buttons'>
            <button class='ui basic blue button' v-on:click="sendForm()">
              Создать
            </button>
            <button class='ui basic red button' v-on:click="closeForm">
              Отменить
            </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 () {
      this.$emit('add-todo', {
        title: this.titleText,
        project: this.projectText,
        done: false
      })
      this.newTodoText = ''
      this.isCreating = false
    }
  }
}
</script>




После давайте импортируем новый компонент в главный компонент нашего приложения:


//App.vue
import TodoList from './components/TodoList.vue'
import CreateTodo from './components/CreateTodo.vue'
export default {
  name: 'app',
  components: {
    TodoList,
    CreateTodo
  },
  data () {
    return {
      todos: [{
        title: 'Выучить Vue.js',
        project: 'Vue.js',
        done: false
      }, {
        title: 'Выучить React.js',
        project: 'React.js',
        done: true
      }, {
        title: 'Выучить Angular 2',
        project: 'Angular 2',
        done: false
      }, {
        title: 'Выучить Angular',
        project: 'Angular',
        done: false
      }]
    }
  }
}

Так-же нам понадобится метод который будет отвечать за добавления задач:


// App.vue
 methods: {
    addTodo (title) {
      this.todos.push({
        title: title.title,
        project: title.project,
        done: false
      })
      console.log('todos is', this.todos);
    }
  }

И теперь наконец-то вызовем наш компонент для создания задач в главном компоненте:


// App.vue
<create-todo v-on:add-todo="addTodo">

Такая вот картинка у нас появилась, теперь по кнопке + мы получаем форму которая добавит новую задачу в список имеющихся:

# Задача выполнена

Теперь давайте добавим метод который отметит задачу выполненной и изменит текст на кнопке, добавим новый метод в Todo:


// Todo component
methods: {
      completeTodo(todo) {
        this.$emit('complete-todo', todo)
      }
}

Обработчик события добавим к компоненту TodoList для обработки события:


methods: {
    completeTodo(todo) {
      const todoIndex = this.todos.indexOf(todo);
      this.todos[todoIndex].done = true;
    },
  },

Чтобы передать метод из TodoList компоненту Todo, мы добавим его в атрибуты компонента Todo.


 <todo v-on:delete-todo="deleteTodo" v-on:complete-todo="completeTodo" v-for="todo in todos" :todo.sync="todo"></todo>

И самое последнее прикрепим функции на кнопку статуса и если мы нажмемња ожидается, то задача станет автоматически выполненной:


<div  v-on:click="completeTodo(todo)" class='ui bottom attached red basic button' v-show="!isEditing && !todo.done">
       Ожидается    
</div>

# Итоги

Мы научились как создавать приложение на Vue используя всего лишь командную строку, научились работать с файлами с расширением .vue, выучили структура компонента и как передавать между ними данные. Но это все статичные данные, в следующих уроках я покажу как сделать тоже самое но используя полноценный бэкенд.

Код урока вы можете найти на этом репозитории!

Ставьте лайки и спасибо за то что прочитали туториал, если вы нашли ошибку сообщите об этом через форму комментариев.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *