Статьи

Как создать список задач GitHub в режиме реального времени с помощью CanJS

CanJS – это коллекция интерфейсных библиотек, облегчающих создание сложных и инновационных веб-приложений, которые можно поддерживать в течение длительного периода времени. Он разбит на десятки отдельных пакетов, так что вы можете выбирать, что вам нужно в вашем приложении, не увязая в огромной зависимости размером более 100 КБ.

CanJS поддерживает архитектуру MVVM (Model-View-ViewModel) со следующими пакетами ключей:

В этом руководстве мы собираемся создать приложение со списком текущих дел, которое использует список проблем хранилища GitHub в качестве источника. Наше приложение будет обновляться в режиме реального времени благодаря API-интерфейсу GitHub Webhook, и мы сможем упорядочить проблемы благодаря упорядоченному взаимодействию пользовательского интерфейса jQuery .

Вы можете найти готовый исходный код для этого приложения на GitHub . Вот как будет выглядеть финальное приложение:

Gif добавления проблем и их сортировки в нашем примере приложения

Если вы хотите поднять свои навыки работы с 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
  • Первый элемент scriptid="app-template" ) содержит корневой шаблон для нашего приложения
  • Второй элемент scriptid="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

В консоли есть одна ошибка, потому что мы еще не настроили наш сервер Socket.io в реальном времени. Давайте сделаем это дальше.

Настройте наш сервер

GitHub Webhooks API может отправлять уведомления сервера, когда что-то меняется в репозитории. Вместо того, чтобы тратить время на написание серверного кода, я создал модуль npm для github-issue-server, который будет:

  • Настройте сервер ngrok для получения событий GitHub Webhook
  • Делайте аутентифицированные запросы к API GitHub, когда мы создаем проблемы в нашем пользовательском интерфейсе
  • Используйте Socket.io для общения в реальном времени с нашим пользовательским интерфейсом
  • Служите файлы в нашем каталоге проекта
  • Добавить свойство sort_position к каждой проблеме
  • Сохраните список наших проблем и их sort_position в локальный файл issues.json

Чтобы сервер мог взаимодействовать с GitHub с помощью аутентифицированных запросов, нам нужно создать личный токен доступа :

  1. Перейдите на github.com/settings/tokens/new.
  2. Введите описание токена (я назвал мой «Список задач CanJS GitHub Issue»)
  3. Выберите область public_repo
  4. Нажмите на Создать токен
  5. На следующей странице щелкните значок буфера обмена 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 в нашем браузере, мы увидим ту же домашнюю страницу, что и раньше, за исключением того, что в нашей консоли не будет ошибок:

Снимок экрана нашего примера приложения без ошибок в консоли DevTools

Создать компонент 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:

Снимок экрана DOM с пользовательским элементом github-Issues

Настройте репозиторий 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

Создание проблем 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, о которых мы не говорили:

Во-вторых, давайте обновим 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! Более того, проблема появляется в нижней части нашего списка проблем, что довольно фантастично благодаря алгебре множеств!

Gif добавить проблему через наше приложение и показать ее в 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, мы увидим те же самые изменения в нашем пользовательском интерфейсе!

Gif добавления проблемы на GitHub.com и проблемы, обнаруживаемой в нашем примере приложения

Изменение порядка вопросов

Теперь давайте добавим некоторые функциональные возможности перетаскивания для нас, чтобы организовать наши проблемы! Наш локальный сервер настроен так, чтобы каждый раз при изменении порядка в списке проблем 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}} добавит ручку захвата, чтобы переместить проблему, если в списке более одной проблемы.

Теперь, когда мы перезагрузим страницу, мы увидим ручку захвата по каждой проблеме, и мы можем подобрать их, чтобы изменить порядок проблем!

Gif переупорядочения проблем в нашем примере приложения с перетаскиванием

Дальнейшее чтение

Нам удалось создать список дел в реальном времени для проблем GitHub с CanJS! Если у меня возникнет желание узнать больше о CanJS, ознакомьтесь с некоторыми из руководств, приведенных ниже на CanJS.com :

Спасибо, что нашли время, чтобы пройти этот урок. Если вам нужна помощь, пожалуйста, не бойтесь задавать вопросы о Gitter , на форумах CanJS , писать мне в Twitter или оставлять комментарии ниже!

Эта статья была рецензирована Камило Рейесом . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!