CanJS — это коллекция интерфейсных библиотек, облегчающих создание сложных и инновационных веб-приложений, которые можно поддерживать в течение длительного периода времени. Он разбит на десятки отдельных пакетов, так что вы можете выбирать, что вам нужно в вашем приложении, не увязая в огромной зависимости размером более 100 КБ.
CanJS поддерживает архитектуру MVVM (Model-View-ViewModel) со следующими пакетами ключей:
- can-компонент для пользовательских элементов
- Can-Connect для общения с API
- можно определить для наблюдаемых
- can-stache для шаблонов, похожих на руль
В этом руководстве мы собираемся создать приложение со списком текущих дел, которое использует список проблем хранилища GitHub в качестве источника. Наше приложение будет обновляться в режиме реального времени благодаря API-интерфейсу GitHub Webhook, и мы сможем упорядочить проблемы благодаря упорядоченному взаимодействию пользовательского интерфейса jQuery .
Вы можете найти готовый исходный код для этого приложения на GitHub . Вот как будет выглядеть финальное приложение:
Если вы хотите поднять свои навыки работы с JavaScript на новый уровень, зарегистрируйтесь в SitePoint Premium и ознакомьтесь с нашей последней книгой « Современный JavaScript»
MVVM в CanJS
Прежде чем мы начнем наш проект для этого урока, давайте углубимся в то, что MVVM означает в приложении CanJS.
Модели данных
«Модель» в MVVM предназначена для вашей модели данных: представление данных в вашем приложении. Наше приложение имеет дело с отдельными проблемами и списком проблем, так что это типы данных, которые мы имеем в нашей модели.
В CanJS мы используем can-define / list / list и can-define / map / map для представления массивов и объектов соответственно. Это наблюдаемые типы данных, которые будут автоматически обновлять View или ViewModel (в MVVM) при их изменении.
Например, наше приложение будет иметь тип проблемы:
import DefineMap from 'can-define/map/map'; const Issue = DefineMap.extend('Issue', { id: 'number', title: 'string', sort_position: 'number', body: 'string' });
Каждый экземпляр Issue
будет иметь четыре свойства: id
, title
, sort_position
и body
. Когда значение установлено, can-define/map/map
преобразует это значение в тип, указанный выше, если только значение не равно null
или undefined
. Например, установка id
в строку "1"
даст свойству id
числовое значение 1
, в то время как установка его в null
фактически сделает его null
.
Мы определим тип для массивов таких вопросов:
import DefineList from 'can-define/list/list'; Issue.List = DefineList.extend('IssueList', { '#': Issue });
Свойство # в can-define/list/list
преобразует любой элемент в списке в указанный тип, поэтому любой элемент в Issue.List
будет экземпляром Issue
.
Посмотреть шаблоны
«Представление» в веб-приложении — это пользовательский интерфейс HTML, с которым взаимодействуют пользователи. CanJS может отображать HTML с несколькими различными синтаксисами шаблонов, в том числе can-stache , который похож на усы и Handlebars .
Вот простой пример шаблона can-stache
:
<ol> {{#each issues}} <li> {{title}} </li> {{/each}} </ol>
В приведенном выше примере мы используем {{#each}} для перебора списка issues
, а затем показываем title
каждой проблемы с {{title}}
. Любые изменения в списке issues
или заголовках проблем приведут к обновлению DOM (например, li
будет добавлен в DOM, если в список будет добавлена новая проблема).
Посмотреть модели
ViewModel в MVVM — это связующий код между моделью и представлением. Любая логика, которая не может содержаться в модели, но необходима для представления, обеспечивается ViewModel.
В CanJS шаблон can-stache
отображается с помощью ViewModel. Вот действительно простой пример:
import stache from 'can-stache'; const renderer = stache('{{greeting}} world'); const viewModel = {greeting: 'Hello'}; const fragment = renderer(viewModel); console.log(fragment.textContent);// Logs “Hello world”
Компоненты
Концепция, которая связывает все эти вещи вместе, является компонентом (или пользовательским элементом). Компоненты полезны для группировки функциональности и обеспечения возможности многократного использования во всем приложении.
В CanJS компонент can состоит из представления (файл can-stache
), модели представления ( can-define/map/map
) и (необязательно) объекта, который может прослушивать события JavaScript.
import Component from 'can-component'; import DefineMap from 'can-define/map/map'; import stache from 'can-stache'; const HelloWorldViewModel = DefineMap.extend('HelloWorldVM', { greeting: {value: 'Hello'}, showExclamation: {value: true} }); Component.extend({ tag: 'hello-world', view: stache('{{greeting}} world{{#if showExclamation}}!{{/if}}'), ViewModel: HelloWorldViewModel, events: { '{element} click': () => { this.viewModel.showExclamation = !this.viewModel.showExclamation; } } }); const template = stache('hello-world'); document.body.appendChild(template);
В приведенном выше примере наш шаблон будет отображать «Hello world!» Или просто «Hello world» (без восклицательного знака), в зависимости от того, нажал ли пользователь наш пользовательский элемент.
Эти четыре понятия — все, что вам нужно знать, чтобы создать приложение CanJS! Наш пример приложения будет использовать эти четыре идеи для создания полноценного приложения MVVM.
Предварительные условия для этого урока
Прежде чем начать, установите последнюю версию Node.js. Мы будем использовать npm для установки внутреннего сервера, который будет обрабатывать связь с API GitHub.
Кроме того, если у вас еще нет учетной записи GitHub, зарегистрируйте ее .
Настройте наш местный проект
Давайте начнем с создания нового каталога для нашего проекта и переключения на этот новый каталог:
mkdir canjs-github cd canjs-github
Теперь давайте создадим файлы, которые нам понадобятся для нашего проекта:
touch app.css app.js index.html
Мы будем использовать app.css
для наших стилей, app.js
для нашего JavaScript и index.html
для пользовательского интерфейса (UI).
CanJS Hello World
Давайте получим кодирование! Сначала мы добавим это в наш файл index.html
:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>CanJS GitHub Issues To-Do List</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="app.css"> </head> <body> <script type="text/stache" id="app-template"> <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <h1 class="page-header text-center"> {{pageTitle}} </h1> </div> </div> </div> </script> <script type="text/stache" id="github-issues-template"> </script> <script src="https://unpkg.com/jquery@3/dist/jquery.min.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script src="https://unpkg.com/can@3/dist/global/can.all.js"></script> <script src="/socket.io/socket.io.js"></script> <script src="app.js"></script> </body> </html>
У этого есть куча разных частей, поэтому давайте разберем это:
- Два элемента
link
вhead
— это таблицы стилей для нашего проекта. Мы используем Bootstrap для некоторых базовых стилей, и у нас будет несколько настроек вapp.css
- Первый элемент
script
(сid="app-template"
) содержит корневой шаблон для нашего приложения - Второй элемент
script
(сid="github-issues-template"
) будет содержать шаблон для компонентаgithub-issues
мы создадим позже в этом уроке. - Элементы
script
в конце страницы загружают наши зависимости: jQuery, пользовательский интерфейс jQuery, CanJS, Socket.io и код нашего приложения.
В нашем приложении мы будем использовать пользовательский интерфейс jQuery (который зависит от jQuery ) для сортировки проблем с помощью перетаскивания. Мы включили can.all.js
чтобы иметь доступ ко всем модулям CanJS ; обычно вы хотите использовать загрузчик модулей, такой как StealJS или веб-пакет , но это выходит за рамки этой статьи. Мы будем использовать Socket.io для получения событий от GitHub для обновления нашего приложения в режиме реального времени.
app.css
давайте добавим несколько стилей в наш файл app.css
:
form { margin: 1em 0 2em 0; } .list-group .drag-background { background-color: #dff0d8; } .text-overflow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
Наконец, давайте добавим немного кода в наш файл app.js
:
var AppViewModel = can.DefineMap.extend('AppVM', { pageTitle: { type: "string", value: "GitHub Issues", } }); var appVM = new AppViewModel(); var template = can.stache.from('app-template'); var appFragment = template(appVM); document.body.appendChild(appFragment);
Давайте разберем JavaScript:
-
can.DefineMap
используется для объявления пользовательских наблюдаемых типов объектов -
AppViewModel
— это наблюдаемый тип объекта, который будет служить корневой моделью представления для нашего приложения. -
pageTitle
— это свойство всех экземпляровAppViewModel
которое по умолчанию имеет значениеGitHub Issues
-
appVM
— это новый экземпляр модели представления нашего приложения -
can.stache.from
преобразует содержимое тегаscript
в функцию, которая отображает шаблон -
appFragment
— это фрагмент документа визуализированного шаблона с даннымиappVM
-
document.body.appendChild
берет узел DOM и добавляет его в тело HTML
Примечание. Сценарий
can.all.js
на нашей странице создает глобальную переменную can, которую мы можем использовать для доступа к любому модулю CanJS. Например, модульcan-stache
доступен для нашего скрипта какcan.stache
.
Если вы откроете index.html
в своем браузере, вы увидите что-то вроде этого:
В консоли есть одна ошибка, потому что мы еще не настроили наш сервер Socket.io в реальном времени. Давайте сделаем это дальше.
Настройте наш сервер
GitHub Webhooks API может отправлять уведомления сервера, когда что-то меняется в репозитории. Вместо того, чтобы тратить время на написание серверного кода, я создал модуль npm для github-issue-server, который будет:
- Настройте сервер ngrok для получения событий GitHub Webhook
- Делайте аутентифицированные запросы к API GitHub, когда мы создаем проблемы в нашем пользовательском интерфейсе
- Используйте Socket.io для общения в реальном времени с нашим пользовательским интерфейсом
- Служите файлы в нашем каталоге проекта
- Добавить свойство
sort_position
к каждой проблеме - Сохраните список наших проблем и их
sort_position
в локальный файлissues.json
Чтобы сервер мог взаимодействовать с GitHub с помощью аутентифицированных запросов, нам нужно создать личный токен доступа :
- Перейдите на github.com/settings/tokens/new.
- Введите описание токена (я назвал мой «Список задач CanJS GitHub Issue»)
- Выберите область
public_repo
- Нажмите на Создать токен
- На следующей странице щелкните значок буфера обмена Copy Token рядом с токеном.
Теперь мы можем установить сервер. Мы будем использовать npm для создания package.json и установки github-issue-server
:
npm init -y npm install github-issue-server
Чтобы запустить наш сервер, выполните следующую команду, заменив ACCESS_TOKEN на токен личного доступа, который вы скопировали с GitHub:
node node_modules/github-issue-server/ ACCESS_TOKEN
Ваш сервер запустится и скажет что-то вроде:
Started up server, available at: http://localhost:8080/ Started up ngrok server, webhook available at: https://829s1522.ngrok.io/api/webhook
ngrok
сервера ngrok
будет иметь другой поддомен, который является уникальным для вас.
Теперь, если мы ngrok.io
адреса localhost
или ngrok.io
в нашем браузере, мы увидим ту же домашнюю страницу, что и раньше, за исключением того, что в нашей консоли не будет ошибок:
Создать компонент GitHub Issues
В CanJS компонент — это пользовательский элемент, который имеет представление (шаблон stache) и модель представления (которая соединяет вашу модель данных с представлением). Компоненты полезны для группировки функций и их многократного использования во всем приложении.
Давайте создадим компонент GitHub- github-issues
который будет использоваться для составления списка всех наших GitHub-проблем и добавления новых!
Сначала мы добавим это в начало нашего файла app.js
:
var GitHubIssuesVM = can.DefineMap.extend('GitHubIssuesVM', { pageTitle: 'string' }); can.Component.extend({ tag: 'github-issues', view: can.stache.from('github-issues-template'), ViewModel: GitHubIssuesVM });
GitHubIssuesVM
определяется как модель представления для нашего компонента. Каждый экземпляр компонента будет иметь свое собственное свойство pageTitle
которое будет отображаться в представлении HTML.
Во-вторых, давайте определим шаблон для элемента github-issues
:
<script type="text/stache" id="github-issues-template"> <h1 class="page-header text-center"> {{pageTitle}} </h1> </script>
Обратите внимание на синтаксис {{pageTitle}}
, который отображает pageTitle
в нашей модели представления в шаблон.
Наконец, давайте заменим заголовок, который мы имеем в нашем HTML:
<h1 class="page-header text-center"> {{pageTitle}} </h1>
… С нашим новым пользовательским элементом:
<github-issues {page-title}="pageTitle" />
В приведенном выше коде мы передаем свойство pageTitle
из view-модели нашего приложения в компонент github-issues
. Синтаксис {page-title}
— это односторонняя привязка родительского шаблона к дочернему компоненту, что означает, что любые изменения в родительском элементе будут распространяться на дочерний, но никакие изменения в дочернем элементе не влияют на родительский. CanJS поддерживает как одностороннюю, так и двустороннюю привязку данных. Мы рассмотрим примеры двусторонней привязки данных позже.
Наша страница должна выглядеть точно так же, как и раньше, за исключением того, что теперь она имеет такую структуру HTML:
Настройте репозиторий GitHub
Наше приложение собирается сделать список дел из проблем в репозитории GitHub (репо), поэтому нам нужно настроить репозиторий GitHub для нашего приложения.
Если у вас уже есть репо, которое вы хотите использовать, отлично! В противном случае создайте его сейчас .
Теперь, когда у нас есть репо, перейдите на его страницу настроек , нажмите Webhooks , затем нажмите Add webhook . После аутентификации вы можете заполнить форму:
- Скопируйте
ngrok
сервераngrok
с локального сервера в поле URL -адрес полезной нагрузки (адрес выглядит примерно так:https://829s1522.ngrok.io/api/webhook
). - Выберите
application/json
в качестве типа контента. - Нажмите Позвольте мне выбрать отдельные события и установите флажок Проблемы
- gfgf
- Нажмите кнопку Добавить веб-крючок , чтобы завершить процесс
Теперь, когда список проблем в вашем репо меняется, ваш локальный сервер будет получать эти события Webhook. Давайте проверим это!
Создайте проблему в своем репозитории GitHub, перейдя на вкладку « Проблемы » в GitHub. Если вы создадите проблему под названием «Тестовая проблема», вы увидите следующее сообщение в интерфейсе командной строки:
Получено «открытое» действие от GitHub для выпуска «Тестовый выпуск»
Перечислите проблемы GitHub
Теперь, когда у нас есть некоторые проблемы в нашем репозитории GitHub, давайте покажем эти проблемы в нашем пользовательском интерфейсе!
Сначала мы создадим наблюдаемый тип проблемы, который будет моделью для наших данных о проблеме. Добавьте это в начало вашего файла app.js
:
var Issue = can.DefineMap.extend('Issue', { seal: false }, { id: 'number', title: 'string', sort_position: 'number', body: 'string' });
Каждый экземпляр Issue
будет иметь свойства id
, title
, sort_position
и body
. Поскольку проблемы GitHub имеют множество других свойств, отличных от тех, которые мы здесь моделируем, мы установим печать на false
чтобы ошибки не возникали, когда другие свойства поступают через GitHub API.
Во-вторых, давайте создадим тип can.DefineList
для массивов проблем:
Issue.List = can.DefineList.extend('IssueList', { '#': Issue });
В-третьих, мы настроим can-set.Algebra, чтобы can-connect
знал о двух специальных свойствах: id
— это уникальный идентификатор для каждой проблемы, и мы будем использовать sort
с Issue.getList
для извлечения проблем в определенном порядке.
Issue.algebra = new can.set.Algebra( can.set.props.id('id'), can.set.props.sort('sort') );
Наконец, мы подключим типы Issue
и Issue.List
к нашей конечной точке сервера. Убедитесь, что вы заменили GITHUB_ORG / GITHUB_REPO информацией для своего репо:
Issue.connection = can.connect.superMap({ url: '/api/github/repos/GITHUB_ORG/GITHUB_REPO/issues', Map: Issue, List: Issue.List, name: 'issue', algebra: Issue.algebra });
Когда мы вызываем can.connect.superMap , некоторые методы CRUD (создание, чтение, обновление и удаление) добавляются к нашему объекту Issue
. В эти методы включен getList , который можно вызвать для получения списка всех экземпляров для этого типа.
В нашем приложении мы будем использовать Issue.getList
для получения всех проблем с нашего сервера. Давайте обновим наш GitHubIssuesVM
чтобы он issuesPromise
свойство issuesPromise
:
var GitHubIssuesVM = can.DefineMap.extend('GitHubIssuesVM', { issuesPromise: { value: function() { return Issue.getList({ sort: 'sort_position' }); } }, issues: { get: function(lastValue, setValue) { if (lastValue) { return lastValue; } this.issuesPromise.then(setValue); } }, pageTitle: 'string' });
Свойство issuesPromise
— это Обещание, возвращаемое Issue.getList
; мы указываем sort_position
в качестве свойства sort
поэтому список остается отсортированным по этому свойству. Свойство issues
будет значением Promise после его разрешения.
Теперь давайте github-issues-template
в index.html
:
<div class="list-group"> {{#if issuesPromise.isPending}} <div class="list-group-item list-group-item-info"> <h4>Loading…</h4> </div> {{/if}} {{#if issuesPromise.isRejected}} <div class="list-group-item list-group-item-danger"> <h4>Error</h4> <p>{{issuesPromise.reason}}</p> </div> {{/if}} {{#if issuesPromise.isResolved}} {{#if issues.length}} <ol class="list-unstyled"> {{#each issues}} <li class="list-group-item"> <h4 class="list-group-item-heading"> {{title}} <span class="text-muted">#{{number}}</span> </h4> <p class="list-group-item-text text-overflow"> {{body}} </p> </li> {{/each}} </ol> {{else}} <div class="list-group-item list-group-item-info"> <h4>No issues</h4> </div> {{/if}} {{/if}} </div>
В шаблонах can-stache
мы можем использовать {{#if}} для условных выражений , поэтому у нас есть три основных блока для определения того, будет ли в Promise для нашего списка проблем указано значениеPending , isRejected или isResolved . В случае isResolved
мы будем перебирать массив проблем с {{#each}} , или мы покажем сообщение, которые не являются проблемами.
Теперь, когда вы перезагрузите страницу, вы увидите тот же список проблем!
Создание проблем GitHub
Давайте добавим форму для создания нового выпуска с заголовком и описанием. Затем мы создадим новую проблему через API GitHub.
Во-первых, давайте добавим форму под h1
в нашем github-issues-template
template в index.html
:
<form ($submit)="send()"> <div class="form-group"> <label for="title" class="sr-only">Issue title</label> <input class="form-control" id="title" placeholder="Issue title" type="text" {($value)}="title" /> </div> <div class="form-group"> <label for="body" class="sr-only">Issue description</label> <textarea class="form-control" id="body" placeholder="Issue description" {($value)}="body"></textarea> </div> <button class="btn btn-primary" type="submit">Submit issue</button> </form>
Приведенный выше фрагмент кода использует несколько функций CanJS, о которых мы не говорили:
-
($submit)
— это прослушиватель событий DOM, который будет вызывать функциюsend()
в нашей модели представления приsend()
формы -
{($value)}="title"
и{($value)}="body"
являются двусторонними значениями : при измененииvalue
input
модель представления будет обновляться, и наоборот
Во-вторых, давайте обновим GitHubIssuesVM
в app.js
чтобы иметь три новых свойства:
var GitHubIssuesVM = can.DefineMap.extend('GitHubIssuesVM', { issuesPromise: { value: function() { return Issue.getList({ sort: 'sort_position' }); } }, issues: { get: function(lastValue, setValue) { if (lastValue) { return lastValue; } this.issuesPromise.then(setValue); } }, pageTitle: 'string', title: 'string', body: 'string', send: function() { var firstIssue = (this.issues) ? this.issues[0] : null; var sortPosition = (firstIssue) ? (Number.MIN_SAFE_INTEGER + firstIssue.sort_position) / 2 : 0; new Issue({ title: this.title, body: this.body, sort_position: sortPosition }).save().then(function() { this.title = this.body = ''; }.bind(this)); } });
Помимо свойств body
и title
для новой проблемы, мы добавили метод send()
который создает новую проблему. Он принимает список issues
чтобы он мог вычислить sort_position
для новой проблемы: мы хотим, чтобы он был до первой проблемы. Как только у нас есть все значения для новой проблемы, мы вызываем new Issue()
чтобы создать ее, .save()
чтобы отправить ее на наш сервер, а затем ждем разрешения Promise; если это удастся, мы сбрасываем title
и body
чтобы очистить форму!
Наконец, давайте обновим компонент github-issues
app.js
в app.js
чтобы иметь новый объект events
:
can.Component.extend({ tag: 'github-issues', view: can.stache.from('github-issues-template'), ViewModel: GitHubIssuesVM, events: { '{element} form submit': function(element, event) { event.preventDefault(); } } });
Свойство events can-component
используется для прослушивания события отправки формы, которое должно быть запущено. Мы не хотим, чтобы страница перезагружалась, когда пользователь отправляет форму, поэтому мы вызываем protectDefault (), чтобы отменить поведение отправки формы по умолчанию.
Теперь мы можем добавить проблему и увидеть ее в пользовательском интерфейсе GitHub! Более того, проблема появляется в нижней части нашего списка проблем, что довольно фантастично благодаря алгебре множеств!
Добавление обновления в реальном времени
Наше приложение может отправлять новые проблемы в GitHub, но изменения из GitHub не обновляют наше приложение. Давайте добавим обновление в реальном времени с Socket.IO !
В app.js
давайте добавим следующий код после того, как мы установили Issue.connection
:
var socket = io(); socket.on('issue created', function(issue) { Issue.connection.createInstance(issue); }); socket.on('issue removed', function(issue) { Issue.connection.destroyInstance(issue); }); socket.on('issue updated', function(issue) { Issue.connection.updateInstance(issue); });
Наш локальный сервер генерирует три разных события, когда проблемы создаются, удаляются или обновляются. Затем наши прослушиватели событий вызывают createInstance , destroyInstance или updateInstance, чтобы изменить модель данных Issue
. Поскольку каждый экземпляр Issue
является наблюдаемым и Issue.List
является наблюдаемым, CanJS автоматически обновит любые части нашего приложения, которые ссылаются на что-либо в модели Issue
!
Когда мы перезагрузим страницу и внесем изменения через пользовательский интерфейс GitHub, мы увидим те же самые изменения в нашем пользовательском интерфейсе!
Изменение порядка вопросов
Теперь давайте добавим некоторые функциональные возможности перетаскивания для нас, чтобы организовать наши проблемы! Наш локальный сервер настроен так, чтобы каждый раз при изменении порядка в списке проблем issues.json
файл issues.json
каталоге нашего проекта, поэтому все, что нам нужно сделать, — это обновить наше приложение, чтобы иметь некоторые элементы управления для переупорядочения проблем и некоторую логику для назначения им новых sort_position
.
После кода Socket.IO, который мы добавили в разделе выше, давайте добавим следующее:
can.view.callbacks.attr('sortable-issues', function(element) { $(element).sortable({ containment: 'parent', handle: '.grab-handle', revert: true, start: function(event, ui) { var draggedElement = ui.item; draggedElement.addClass('drag-background'); }, stop: function(event, ui) { var draggedElement = ui.item; draggedElement.removeClass('drag-background'); }, update: function(event, ui) { var draggedElement = ui.item[0]; var draggedIssue = can.data.get.call(draggedElement, 'issue'); var nextSibling = draggedElement.nextElementSibling; var previousSibling = draggedElement.previousElementSibling; var nextIssue = (nextSibling) ? can.data.get.call(nextSibling, 'issue') : {sort_position: Number.MAX_SAFE_INTEGER}; var previousIssue = (previousSibling) ? can.data.get.call(previousSibling, 'issue') : {sort_position: Number.MIN_SAFE_INTEGER}; draggedIssue.sort_position = (nextIssue.sort_position + previousIssue.sort_position) / 2; draggedIssue.save(); } }); });
Уф! Давайте разберемся с этим:
- can.view.callbacks предназначен для регистрации обратного вызова всякий раз, когда новый атрибут или элемент добавляется в DOM. В нашем коде наша функция будет вызываться всякий раз, когда к элементу добавляется атрибут
sortable-issues
. - Мы используем сортируемое взаимодействие jQuery UI для перетаскивания элементов DOM. Мы настроили его с помощью параметров сдерживания , обработки и возврата .
- Всякий раз, когда пользователь начинает перетаскивать проблему, запускается функция запуска , которая добавляет класс в элемент DOM.
- Всякий раз, когда пользователь удаляет проблему, вызывается функция stop , которая удаляет класс, который мы добавили при
start
. - Обновление будет вызвано после полной остановки сортировки и обновления DOM. Наша функция получает данные модели проблемы для проблемы, которая была перетащена, а также для проблем, которые возникают непосредственно до и после, поэтому она может пересчитать
sort_position
,sort_position
между двумя проблемами. После того, как мы присваиваем свойствоsort_position
, мы вызываем save (), чтобы поместить обновленные данные о проблеме на наш локальный сервер.
Теперь давайте обновим <ol>
проблем в index.html
:
<ol class="list-unstyled" sortable-issues> {{#each issues}} <li class="list-group-item" {{data('issue', this)}}> {{^is issues.length 1}} <span class="glyphicon glyphicon-move grab-handle pull-right text-muted" aria-hidden="true"></span> {{/is}} <h4 class="list-group-item-heading"> {{title}} <span class="text-muted">#{{number}}</span> </h4> <p class="list-group-item-text text-overflow"> {{body}} </p> </li> {{/each}} </ol>
Мы добавили несколько новых вещей:
- Атрибут
app.js
вызовет обратный вызов, который мы определили вapp.js
, как только список окажется в DOM. -
{{data('issue', this)}}
присоединит данные о проблеме к элементу DOM, чтобы мы могли получить их в нашемsortable-issues
. - Раздел
{{^is issues.length 1}}
добавит ручку захвата, чтобы переместить проблему, если в списке более одной проблемы.
Теперь, когда мы перезагрузим страницу, мы увидим ручку захвата по каждой проблеме, и мы можем подобрать их, чтобы изменить порядок проблем!
Дальнейшее чтение
Нам удалось создать список дел в реальном времени для проблем GitHub с CanJS! Если у меня возникнет желание узнать больше о CanJS, ознакомьтесь с некоторыми из руководств, приведенных ниже на CanJS.com :
Спасибо, что нашли время, чтобы пройти этот урок. Если вам нужна помощь, пожалуйста, не бойтесь задавать вопросы о Gitter , на форумах CanJS , писать мне в Twitter или оставлять комментарии ниже!
Эта статья была рецензирована Камило Рейесом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!