Качество пользовательского интерфейса играет важную роль в качестве UX. В любом приложении, созданном с использованием шаблона MV*, представление отвечает за части пользовательского интерфейса приложения.

Для многих из нас обычный способ реализации представления включает в себя ручное управление DOM, когда данные необходимо отобразить. Хотя все это хорошо, в этой статье мы познакомимся с возможностями использования представлений, управляемых данными, именно так, как это реализовано в Vue.js. Попутно мы создадим простое приложение Todo, которое позволит добавлять задачи в список, помечать их как завершенные, а также отображать различные представления.

Давайте начнем.

Приложение, которое мы хотим создать, будет выглядеть так:

В предыдущей статье я обсуждал, как Vue.js реализует модель MVVM. Мы начнем со структуры каталогов следующим образом:

/
|- /libs
|    |- /css
|    |   |- bStrap.css
|    |   |- normalize.css
|    |- /js
|        |- jQ-2.js
|        |- vue.js
|- app.js
|- index.html
NB:
jQ-2 == jQuery 2.1.4
bStrap == Bootstrap 3.3.6
vue == vue 1.0.10

Далее мы создаем базовую разметку:

Имея на руках базовую разметку, мы обращаем внимание на основное приложение, расположенное в app.js. Напомним, что Vue.js реализует MVVM, а центральной конструкцией приложения Vue.js является виртуальная машина. Vue VM — это экземпляр функции глобального конструктора Vue.

Виртуальная машина Vue имеет 3 основных атрибута или свойства.

  1. Элемент DOM, определяемый свойством el. Это свойство представляет представление, к которому будет привязана виртуальная машина.
  2. Объект модели, который содержит данные, которые виртуальная машина будет использовать для обеспечения функциональности приложения и передачи данных в представление для рендеринга. Это свойство определяется атрибутом «данные» виртуальной машины.
  3. Свойство объекта «методы», которое содержит обработчики событий, которые виртуальная машина использует для обработки действий пользовательского интерфейса, переданных ей из представления.

Еще одним важным свойством виртуальной машины является «вычисляемый» объект, который содержит данные, динамически вычисляемые виртуальной машиной для использования в представлении. Назначение свойства «вычислено» — ограничить объем логики, используемой в представлениях, одними выражениями. Это очень полезная функция.

Таким образом, наша виртуальная машина Vue выглядит следующим образом:

Если мы запустим приложение как есть, мы получим сообщение об ошибке (в консоли) от Vue, говорящее нам, что элемент «#todo» не существует. Итак, мы обновляем index.html следующим образом:

Теперь, когда мы запускаем приложение, мы получаем пустую страницу без ошибок. Что первично — виртуальная машина или представление? Ответ таков: мы немного строим и немного тестируем, гарантируя, что и виртуальная машина, и представление полностью синхронизированы.

Следующим шагом мы определяем наши свойства данных. Нам понадобятся следующие свойства (здесь я обманываю :-)):

  • «todoItem», представляющий один элемент списка дел. Это будет строка.
  • массив, содержащий незавершенные и новые задачи, назовем его «оставшимся».
  • массив для хранения выполненных задач. Назовем его «завершенным».
  • свойство, которое представляет коллекцию/список имен/идентификаторов, отображаемых в данный момент. Назовем его «listName».
  • И не в последнюю очередь свойство, которое представляет содержимое данных отображаемой коллекции/списка.

Вышеупомянутое выглядит так:

Если мы запустим это, ничего не произойдет. Почему? Потому что представление не было обновлено. Давайте сделаем это дальше.

<!DOCTYPE html>
<head>
 <meta charset=”utf-8">
 <title>VUE Todo App | Telios WebDev</title>
 <link rel=”stylesheet” href=”libs/css/normalize.css”>
 <link rel=”stylesheet” href=”libs/css/bStrap.css”>
<script src=”libs/js/jQ-2.js”></script>
 <script src=”libs/js/vue.js”></script>
</head>
<body>
 <div class=”container”>
 <div class=”row”>
 <div class=”col-md-6 col-md-offset-3" id=”todo”>
 <h1 class=”text-center”>Vue Todo App</h1>
 <div class=”border” id=”todoList”>
 <div class=”input-group”>
 <input type=”text” class=”form-control” v-model=”todoItem”
 @keyup.enter.stop.prevent=”addTodo”>
 <button class=”btn btn-success btn-default” id=”addTodo”
 @click.stop.prevent=”addTodo”>Add Todo</button>
 </div>
 <div class=”border” id=”todoListItems”>
 <h3 v-show=”showListName”>{{listName}} Items</h3>
 <p v-for=”item in displayList”
 @dblClick.stop.prevent=”editItem(item, listName)”>
 <input type=”checkbox” @click.stop.prevent=”markComplete(item, $event)”>
 <span>{{item}}</span>
 <button @click.stop.prevent=”removeItem(item, listName)”>X</button>
 </p>
 </div>
 </div>
 <pre>{{$data | json}}</pre>
 </div>
 </div>
 </div>
 <script src=”app.js”></script>
</body>

Если вы запустите это, это должно выглядеть следующим образом:

Итак, давайте посмотрим, что мы сделали до сих пор.

<input type=”text” class=”form-control” v-model=”todoItem”
 @keyup.enter=”addTodo”>
 <button class=”btn btn-success btn-default” id=”addTodo”
 @click=”addTodo”>Add Todo</button>

Приведенный выше код создает элемент ввода для добавления новых задач в коллекцию. Обратите внимание на использование директивы v-model. Это обеспечивает двустороннюю привязку данных между элементом ввода и свойством todoItem виртуальной машины. Эти типы привязок являются важной функцией библиотек и сред MVVM, которая позволяет создавать представления, управляемые данными, т. е. представления, отображаемые для отражения состояния данных базовой виртуальной машины, а не в результате прямых манипуляций с DOM.

Обратите также внимание на директиву '@keyup.enter', которая является сокращением для директивы 'v-on:keyup' для привязки события 'keyup' (исключительно для клавиши 'enter'), которое происходит в элементе ввода, к методу 'addTodo' ВМ. Мы также используем директиву @click для привязки события нажатия кнопки «Добавить задачу» к тому же обработчику.

Далее обратите внимание на модификаторы директивы «.stop.prevent», которые позволяют нам предотвращать действие по умолчанию, а также отменять/останавливать распространение событий. Если бы вы посмотрели на свою консоль, вы бы увидели, что Vue отображает сообщение об ошибке, предупреждающее нас о том, что функция addTodo не определена. Мы справимся с этим в ближайшее время.

Следующий:

<h3 v-show=”showListName”>{{listName}} Items</h3>
 <p v-for=”item in displayList”
 @dblClick.stop.prevent=”editItem(item, listName)”>
 <input type=”checkbox” @click.stop.prevent=”markComplete(item, $event)”>
 <span>{{item}}</span>
 <button @click.stop.prevent=”removeItem(item, listName)”>X</button>
 </p>

В приведенном выше фрагменте мы видим привязку директивы v-show к свойству showListName на виртуальной машине. Я уверен, вы заметили, что в модели данных виртуальной машины такого свойства нет, и вы правы. Это свойство является примером «вычисляемого свойства». По сути, это приводит к отображению тега «h3», если свойство «showListName» истинно.

Далее идет текстовая интерполяция «{{listName}}, которая просто заменяет это строковым значением свойства listName модели данных виртуальной машины. После этого у нас есть «p», который содержит «ввод», «диапазон» и «кнопку», которые выполняют разные действия. Сам «p» имеет директиву «v-for», которая заставляет его отображать содержимое списка, идентифицированного «displayList» на виртуальной машине. «displayList» в настоящее время «нулевой».

«p» также имеет директиву «@dblClick» с модификаторами, которые привязаны к обработчику событий «editItem» виртуальной машины. Обработчику передаются 2 аргумента «item & listName». «Вход» — это флажок, который при нажатии запускает обработчик «markComplete», также передавая ему 2 аргумента. «Span» просто отображает элемент в коллекции «displayList», а кнопка привязана к обработчику «removeItem» и передает ему 2 аргумента.

<pre>{{$data | json}}</pre>

Приведенный выше фрагмент просто позволяет нам увидеть содержимое модели данных виртуальной машины.

На данном этапе представление опережает виртуальную машину, оно имеет привязки для нескольких методов и свойств, которые виртуальная машина еще не предоставила. Итак, мы будем играть в догонялки следующим образом;

//Create the VM
var todoApp = new Vue({
//define the DOM View element
 el: ‘#todo’,
//define the Data Model
 data: {
 todoItem: ‘’,
 remaining: [],
 completed: [],
 listName: ‘’,
 displayList: null
 },
//define Computed properties
 computed: {
 showListName: function () {
 if(this.all.length) {
 return true;
 }
 return false;
 },
 all: function () {
 return this.remaining.concat(this.completed);
 },
 remainingItems: function () {
 return this.remaining.length;
 },
 completedItems: function () {
 return this.completed.length;
 },
 allItems: function () {
 return this.all.length;
 }
 },
//define View methods
 methods: {
 addTodo: function () {
 this.remaining.push(this.todoItem);
 this.listName = ‘Remaining’;
 this.displayList = this.remaining;
 return this.todoItem = ‘’;
 },
 markComplete : function (item, e) {
 var index = this.remaining.indexOf(item);
 if(e.target.checked === true) {
 this.completed.push(item);
 return this.remaining.splice(index, 1);
 }
 return console.log(item +’ was marked incomplete’);
 },
 markAllItemsComplete: function () {
 console.log(‘mark all complete button clicked’);
 var self = this;
 this.remaining.forEach(function (todo) {
 self.completed.push(todo);
 });
 this.remaining = [];
 },
 showRemaining: function () {
 this.listName = ‘Remaining’;
 this.displayList = this.remaining;
 return console.log(‘show remaining was clicked’);
 },
 showCompleted: function () {
 this.listName = ‘Completed’;
 this.displayList = this.completed;
 return console.log(‘show completed was clicked’);
 },
 showAll: function () {
 this.listName = ‘All’;
 this.displayList = this.all;
 return console.log(‘show all was clicked’);
 },
 editItem: function (item, listName) {
 console.log(‘edit was clicked on ‘, item);
 if(!this.todoItem) {
 this.removeItem(item, listName);
 this.todoItem = item;
 return
 }
 return alert(‘You must finish editing the current item’);
 },
 removeItem: function (item, listName) {
 var
 list = listName.toLowerCase(),
 index = this[list].indexOf(item);
 return this[list].splice(index, 1);
 }
 }
});

Запуск приложения сейчас не будет иметь ошибок. Мы можем добавить задачи, пометить их как завершенные и удалить, как показано ниже:

Обратите внимание, что в списке «завершено» есть 2 элемента, а в списке «осталось» — один элемент. Я уверен, что вы можете начать видеть силу «представлений, управляемых данными».

Давайте посмотрим, что мы уже сделали в основном приложении.

//define Computed properties
 computed: {
 showListName: function () {
 if(this.all.length) {
 return true;
 }
 return false;
 },
 all: function () {
 return this.remaining.concat(this.completed);
 },
 remainingItems: function () {
 return this.remaining.length;
 },
 completedItems: function () {
 return this.completed.length;
 },
 allItems: function () {
 return this.all.length;
 }
 },

Мы определили «вычисляемые» свойства, которые предоставляют значения, которые представление использует для форматирования данных, представляемых пользователю. Наконец мы видим свойство «showListName», которое является «истинным», когда в коллекции «все» есть что-то. Сама коллекция «все» представляет собой слияние «оставшейся» коллекции и «завершенной» коллекции. Затем есть 3 свойства, которые возвращают числовые значения — «remainingItems», «completedItems» и «allItems». Они просто возвращают длину или количество элементов в соответствующих коллекциях.

Далее идут обработчики событий для виртуальной машины.

//define View methods
 methods: {
 addTodo: function () {
 this.remaining.push(this.todoItem);
 this.listName = ‘Remaining’;
 this.displayList = this.remaining;
 return this.todoItem = ‘’;
 },
 markComplete : function (item, e) {
 var index = this.remaining.indexOf(item);
 if(e.target.checked === true) {
 this.completed.push(item);
 return this.remaining.splice(index, 1);
 }
 return console.log(item +’ was marked incomplete’);
 },
 markAllItemsComplete: function () {
 console.log(‘mark all complete button clicked’);
 var self = this;
 this.remaining.forEach(function (todo) {
 self.completed.push(todo);
 });
 this.remaining = [];
 },
 showRemaining: function () {
 this.listName = ‘Remaining’;
 this.displayList = this.remaining;
 return console.log(‘show remaining was clicked’);
 },
 showCompleted: function () {
 this.listName = ‘Completed’;
 this.displayList = this.completed;
 return console.log(‘show completed was clicked’);
 },
 showAll: function () {
 this.listName = ‘All’;
 this.displayList = this.all;
 return console.log(‘show all was clicked’);
 },
 editItem: function (item, listName) {
 console.log(‘edit was clicked on ‘, item);
 if(!this.todoItem) {
 this.removeItem(item, listName);
 this.todoItem = item;
 return
 }
 return alert(‘You must finish editing the current item’);
 },
 removeItem: function (item, listName) {
 var
 list = listName.toLowerCase(),
 index = this[list].indexOf(item);
 return this[list].splice(index, 1);
 }
 }

Я предполагаю, что их использование не требует пояснений, но обратите внимание, что ни в одном из этих обработчиков нет прямой манипуляции с DOM. Все обработчики получают аргументы от представления, где это необходимо, а затем манипулируют данными внутри виртуальной машины, изменение данных/состояния виртуальной машины синхронизируется с представлением, и, таким образом, у нас есть полностью реактивное представление, управляемое данными. Отношения ВМ.

На данный момент виртуальная машина намного опережает представление, поэтому мы снова играем в догонялки.

<!DOCTYPE html>
<head>
 <meta charset=”utf-8">
 <title>VUE Todo App | Telios WebDev</title>
 <link rel=”stylesheet” href=”libs/css/normalize.css”>
 <link rel=”stylesheet” href=”libs/css/bStrap.css”>
<script src=”libs/js/jQ-2.js”></script>
 <script src=”libs/js/vue.js”></script>
<style>
 .border {
 border: 1px solid black;
 border-radius: 10px;
 }
 #todo {
 margin-top: 60px;
 }
 #todo li {
 display: inline-block;
 margin: 0 auto;
 padding-left: 10px;
 }
 .input-group {
 position: relative;
 }
 #addTodo {
 float: right;
 }
 #markAllComplete {
 float: left;
 }
 #todoList {
 padding: 10px;
 }
 #todoList input, #todoList button {
 margin-top: 5px;
 }
 #todoListItems {
 margin-top: 10px;
 }
 #todoListItems button {
 float: right;
 background: none;
 border: none;
 color: red;
 }
 #todoListItems p {
 padding: 10px;
 font-size: 18px;
 }
 #todoListItems input[type=”checkbox”] {
 padding: 5px;
 }
 #todoListItems h3 {
 text-align: center;
 }
 </style>
</head>
<body>
 <div class=”container”>
 <div class=”row”>
 <div class=”col-md-6 col-md-offset-3" id=”todo”>
 <h1 class=”text-center”>Vue Todo App</h1>
 <div class=”border” id=”todoList”>
 <div class=”input-group”>
 <input type=”text” class=”form-control” v-model=”todoItem”
 @keyup.enter=”addTodo”>
 <button class=”btn btn-success btn-default” id=”addTodo”
 @click=”addTodo”>Add Todo</button>
 <button v-show=”remainingItems” class=”btn btn-info btn-default”
 @click=”markAllItemsComplete”
 id=”markAllComplete”>Mark All Complete</button>
 </div>
 <br>
 <ul>
 <li><a href=””
 @click.stop.prevent=”showAll”>All Items: {{allItems}}</a></li>
 <li><a href=””
 @click.stop.prevent=”showRemaining”>Remaning Items: {{remainingItems}}</a></li>
 <li><a href=””
 @click.stop.prevent=”showCompleted”>Completed Items: {{completedItems}}</a></li>
 </ul>
 <div class=”border” id=”todoListItems”>
 <h3 v-show=”showListName”>{{listName}} Items</h3>
 <p v-for=”item in displayList”
 @dblClick.stop.prevent=”editItem(item, listName)”>
 <input type=”checkbox” @click.stop.prevent=”markComplete(item, $event)”>
 <span>{{item}}</span>
 <button @click.stop.prevent=”removeItem(item, listName)”>X</button>
 </p>
 </div>
 </div>
 <pre>{{$data | json}}</pre>
 </div>
 </div>
 </div>
 <script src=”app.js”></script>
</body>

Мы добавляем немного CSS, чтобы приложение выглядело лучше, и у нас должно быть следующее представление по умолчанию:

Итак, что мы имеем?

<button v-show=”remainingItems” class=”btn btn-info btn-default”
 @click=”markAllItemsComplete”
 id=”markAllComplete”>Mark All Complete</button>

Фрагмент выше добавляет кнопку «Отметить все как завершенную» в представление, кнопка отображается только в том случае, если в «оставшейся» коллекции есть что-либо. Это реализуется директивой v-show="remainingItems", которая является "истинной" только в том случае, если вычисляемое свойство "remaingItems" истинно.

Следующий:

<ul>
 <li><a href=””
 @click.stop.prevent=”showAll”>All Items: {{allItems}}</a></li>
 <li><a href=””
 @click.stop.prevent=”showRemaining”>Remaning Items: {{remainingItems}}</a></li>
 <li><a href=””
 @click.stop.prevent=”showCompleted”>Completed Items: {{completedItems}}</a></li>
 </ul>

Приведенный выше код отображается как счетчик навигации, который выполняет две функции.

  1. Отображает количество элементов в различных коллекциях, привязываясь к соответствующему «вычисляемому» свойству, например. {{все элементы}}.
  2. Когда ссылка нажата, например. «Все элементы» запускает обработчик событий на виртуальной машине, который изменяет значение «displayList». Например, нажатие на «Все элементы» приводит к тому, что для «displayList» устанавливается коллекция «все», и это то, что отображается. связанные коды:
View:
<li><a href=””
 @click.stop.prevent=”showAll”>All Items: {{allItems}}</a></li>
VM:
all: function () {//computed property
 return this.remaining.concat(this.completed);
 },
allItems: function () {//computed property
 return this.all.length;
 }
showAll: function () {//VM method i.e. event handler
 this.listName = ‘All’;
 this.displayList = this.all;
 return console.log(‘show all was clicked’);
 },

В предыдущем фрагменте показаны взаимосвязи между виртуальной машиной и представлением, которые делают возможным представление, управляемое данными.

Примеры представлений:

Я верю, что эта статья была полезной. Спасибо за чтение, пожалуйста, оставьте комментарии ниже.