Статьи

Ember.js: идеальная платформа для веб-приложений

Ember.js — это продуманный внешний интерфейс JavaScript, который в последнее время вызывает большой интерес. Эта статья познакомит вас с некоторыми ключевыми понятиями фреймворка при создании простого приложения, чтобы показать базовый пример того, что он способен производить.

В качестве примера приложения мы будем использовать Dice Roller, включая возможность бросать некоторые кости и просматривать историю всех бросков костей, которые были выполнены до настоящего времени. Полностью рабочая версия этого приложения доступна от Github

Среда Ember.js объединяет множество современных концепций и технологий JavaScript в один пакет, включая, но не ограничиваясь:

  • Использование транспортера инструмента Babel , для поддержки ES2016 во всем.
  • Поддержка тестирования на уровне Unit, Integration и Acceptance в стандартной комплектации на базе Testem и QTest .
  • Создание активов с использованием Broccoli.js .
  • Поддержка перезагрузки в реальном времени, для сокращения времени цикла разработки.
  • Создание шаблонов с использованием синтаксиса разметки Handlebars .
  • URL Routing — первая разработка, обеспечивающая полную поддержку глубоких ссылок.
  • Полный слой данных, построенный на основе JSON API , но подключаемый для любого доступа к API, который вам необходим.
Понять Ember за 30 минут
Приложения с Ember JavaScript Framework

Для работы с Ember.js предполагается, что у вас установлена ​​последняя версия Node.js и npm. Если нет, то их можно загрузить и установить с веб-сайта Node.js.

Следует также упомянуть, что Ember — это просто оболочка внешнего интерфейса. У него есть несколько способов взаимодействия с бэкэндом по вашему выбору, но этот бэкэнд никоим образом не обрабатывается самим Ember.

Представляем ember-cli

Ember.js logo

Большая часть возможностей Ember.js заключается в его интерфейсе командной строки (CLI). Этот инструмент, известный как ember-cli, поддерживает большую часть жизненного цикла разработки приложения Ember.js, начиная с создания приложения, добавляя в него функциональность вплоть до запуска тестовых наборов и запуска реального проекта в режиме разработки.

Практически все, что вы делаете во время разработки приложения Ember.js, будет включать этот инструмент на каком-то уровне, поэтому важно понять, как его лучше всего использовать. Мы будем использовать это всюду по этой статье.

Первое, что нам нужно сделать, это убедиться, что Ember.js CLI правильно установлен и обновлен. Это делается путем установки из npm следующим образом:

$ npm install -g ember-cli 

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

 $ ember --version ember-cli: 2.15.0-beta.1 node: 8.2.1 os: darwin x64 

Создание вашего первого приложения Ember.js

После установки ember-cli вы готовы приступить к созданию приложения. Это первое место, где мы будем использовать инструмент CLI Ember.js — он создает всю структуру приложения, настраивая все готово к работе.

 $ ember new dice-roller installing app create .editorconfig create .ember-cli create .eslintrc.js create .travis.yml create .watchmanconfig create README.md create app/app.js create app/components/.gitkeep create app/controllers/.gitkeep create app/helpers/.gitkeep create app/index.html create app/models/.gitkeep create app/resolver.js create app/router.js create app/routes/.gitkeep create app/styles/app.css create app/templates/application.hbs create app/templates/components/.gitkeep create config/environment.js create config/targets.js create ember-cli-build.js create .gitignore create package.json create public/crossdomain.xml create public/robots.txt create testem.js create tests/.eslintrc.js create tests/helpers/destroy-app.js create tests/helpers/module-for-acceptance.js create tests/helpers/resolver.js create tests/helpers/start-app.js create tests/index.html create tests/integration/.gitkeep create tests/test-helper.js create tests/unit/.gitkeep create vendor/.gitkeep NPM: Installed dependencies Successfully initialized git. $ 

Это привело к созданию целого приложения, которое готово к запуску. Он даже настроил Git в качестве источника контроля для отслеживания вашей работы.

Примечание: если вы хотите, вы можете отключить интеграцию с Git, и вы можете предпочесть пряжу над npm. Помощь для инструмента описывает это и многое другое.

Теперь посмотрим, как это выглядит. Запуск приложения Ember для целей разработки — еще раз — также выполняется с помощью ember-cli:

 $ cd dice-roller $ ember serve Livereload server on http://localhost:49153 'instrument' is imported from external module 'ember-data/-debug' but never used Warning: ignoring input sourcemap for vendor/ember/ember.debug.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-2fXNPqjl.tmp/vendor/ember/ember.debug.map' Warning: ignoring input sourcemap for vendor/ember/ember-testing.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-Xwpjztar.tmp/vendor/ember/ember-testing.map' Build successful (5835ms) – Serving on http://localhost:4200/ Slowest Nodes (totalTime => 5% ) | Total (avg) ----------------------------------------------+--------------------- Babel (16) | 4625ms (289 ms) Rollup (1) | 445ms 

Теперь мы готовы к работе. Приложение работает на http: // localhost: 4200 и выглядит так:
Экран приложения по умолчанию

Также работает служба LiveReload, которая автоматически отслеживает изменения в файловой системе. Это означает, что у вас может быть невероятно быстрое время выполнения работ при настройке дизайна вашего сайта.

Давай попробуем?

Начальная страница уже говорит нам, что делать, так что давайте пойдем и изменим главную страницу и посмотрим, что произойдет. Мы собираемся изменить файл app/templates/application.hbs чтобы он выглядел следующим образом.

 This is my new application. {{outlet}} 

Примечание. Тег {{outlet}} является частью работы Routing в Ember. Мы рассмотрим это позже.

Первое, на что нужно обратить внимание, это вывод ember-cli, который должен выглядеть следующим образом:

 file changed templates/application.hbs Build successful (67ms) – Serving on http://localhost:4200/ Slowest Nodes (totalTime => 5% ) | Total (avg) ----------------------------------------------+--------------------- SourceMapConcat: Concat: App (1) | 9ms SourceMapConcat: Concat: Vendor /asset... (1) | 8ms SimpleConcatConcat: Concat: Vendor Sty... (1) | 4ms Funnel (7) | 4ms (0 ms) 

Это говорит нам о том, что мы заметили, что мы изменили шаблон, перестроили и перезапустили все. У нас было нулевое участие в этой части.

Теперь давайте посмотрим на браузер. Если у вас установлен и запущен LiveReload, вам даже не понадобится обновлять браузер, чтобы его можно было поднять, в противном случае вам потребуется перезагрузить текущую страницу.

Первая смена

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

Кроме того, мы получаем полностью настроенный набор тестов, готовый к запуску. Неудивительно, что это также выполняется с помощью инструмента Ember:

 $ ember test ⠸ Building'instrument' is imported from external module 'ember-data/-debug' but never used ⠴ BuildingWarning: ignoring input sourcemap for vendor/ember/ember.debug.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-S8aQFGaz.tmp/vendor/ember/ember.debug.map' ⠇ BuildingWarning: ignoring input sourcemap for vendor/ember/ember-testing.js because ENOENT: no such file or directory, open '/Users/coxg/source/me/writing/repos/dice-roller/tmp/source_map_concat-input_base_path-wO8OLEE2.tmp/vendor/ember/ember-testing.map' cleaning up... Built project successfully. Stored in "/Users/coxg/source/me/writing/repos/dice-roller/tmp/class-tests_dist-PUnMT5zL.tmp". ok 1 PhantomJS 2.1 - ESLint | app: app.js ok 2 PhantomJS 2.1 - ESLint | app: resolver.js ok 3 PhantomJS 2.1 - ESLint | app: router.js ok 4 PhantomJS 2.1 - ESLint | tests: helpers/destroy-app.js ok 5 PhantomJS 2.1 - ESLint | tests: helpers/module-for-acceptance.js ok 6 PhantomJS 2.1 - ESLint | tests: helpers/resolver.js ok 7 PhantomJS 2.1 - ESLint | tests: helpers/start-app.js ok 8 PhantomJS 2.1 - ESLint | tests: test-helper.js 1..8 # tests 8 # pass 8 # skip 0 # fail 0 # ok 

Обратите внимание, что вывод говорит о PhantomJS . Это связано с тем, что имеется полная поддержка интеграционных тестов, которые выполняются в браузере, и по умолчанию они выполняются без использования браузера PhantomJS. При желании имеется полная поддержка их запуска в других браузерах, и при настройке непрерывной интеграции (CI) стоит сделать это, чтобы убедиться, что ваше приложение работает корректно во всех поддерживаемых браузерах.

Как структурировано приложение Ember.js

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

На самом верхнем уровне вы заметите следующие файлы и каталоги:

  • README.md — это стандартный файл readme, описывающий приложение
  • package.json — это стандартный файл конфигурации npm, описывающий ваше приложение. Это используется в первую очередь для правильной установки зависимостей.
  • ember-cli-build.js — это конфигурация инструмента Ember CLI для поддержки нашей сборки.
  • testem.js — это конфигурация для тестовой среды. Это позволяет вам, помимо прочего, определить браузеры, которые следует использовать для запуска тестов в различных средах.
  • приложение / — это фактическая логика приложения. Здесь много чего будет описано ниже.
  • config / — это конфигурация для приложения
    • config / target.js — это список поддерживаемых браузеров. Это используется Бабелем для обеспечения того, чтобы Javascript передавался таким образом, чтобы все они работали.
    • config / environment.js — это основная конфигурация для вашего приложения. Все, что необходимо для приложения, но может варьироваться в зависимости от среды, должно быть здесь.
  • public / — это любые статические ресурсы, которые вы хотите включить в свое приложение. Например, изображения и шрифты.
  • vendor / — это то место, куда переходят любые зависимости внешнего интерфейса, которые не управляются системой сборки
  • тесты / — это то, где все тесты идут
    • tests / unit — это все модульные тесты для приложения
    • тесты / интеграция — это все интеграционные тесты для приложения

Общая структура страницы (включая стороннее содержимое)

Прежде чем мы зайдем слишком далеко, давайте дадим нашей странице некоторую форму структуры. В этом случае мы собираемся добавить в CSS-среду Materialise, чтобы улучшить внешний вид.

Добавление поддержки стороннего контента, подобного этому, может быть выполнено несколькими способами:

  • Прямая ссылка на контент на внешнем сервисе, таком как CDN
  • Использование менеджера пакетов, такого как npm или Bower, чтобы установить его для нас
  • Включая это прямо в нашем приложении.
  • Использование Ember Addon, если оно есть

К сожалению, аддон для Materialize еще не работает с последней версией Ember.js, поэтому вместо этого мы просто будем ссылаться на ресурсы CDN с нашей главной страницы. Чтобы добиться этого, мы собираемся обновить app/index.html , который является структурой главной страницы, в которую отображается наше приложение. Мы просто добавим ссылки CDN для jQuery, Google Icon Font и Materialize.

 <!-- Inside the Head section --> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/css/materialize.min.css"> <!-- Inside the Body section --> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.1/js/materialize.min.js"></script> 

Теперь мы можем обновить главную страницу, чтобы показать наш основной шаблон. Это делается путем редактирования app/templates/application.hbs так:

 <nav> <div class="nav-wrapper"> <a href="#" class="brand-logo"> <i class="material-icons">filter_6</i> Dice Roller </a> <ul id="nav-mobile" class="right hide-on-med-and-down"> </ul> </div> </nav> <div class="container"> {{outlet}} </div> 

Это дает нам Materialize Nav в верхней части экрана с контейнером, содержащим упомянутый ранее тег {{outlet}} .

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

Так что же это за outlet ? Ember работает вне маршрутов, где каждый маршрут считается дочерним по отношению к какому-либо другому маршруту. Самый верхний маршрут обрабатывается Ember автоматически и отображает шаблон app/templates/application.hbs .

outlet тег указывает, где Ember будет отображать следующий маршрут в текущей иерархии — поэтому маршрут первого уровня отображается в этом теге в application.hbs , маршрут второго уровня отображается в этом теге в этом шаблоне первого уровня и так далее.

Создание нового маршрута

В приложении Ember.js каждая страница, которую можно посетить, доступна через маршрут. Существует прямое сопоставление между URL-адресом, который открывает браузер, и маршрутом, который отображает приложение.

Самый простой способ убедиться в этом на примере. Давайте добавим новый маршрут в наше приложение, позволяющее пользователю фактически бросить несколько кубиков. Еще раз, это делается с помощью инструмента ember-cli.

 $ ember generate route roll installing route create app/routes/roll.js create app/templates/roll.hbs updating router add route roll installing route-test create tests/unit/routes/roll-test.js 

Эта команда дала нам следующее:

  • Обработчик маршрута — app/routes/roll.js
  • Шаблон для маршрута — app/templates/roll.hbs
  • Тест для маршрута — tests/unit/routes/roll-test.js
  • Обновлена ​​конфигурация маршрутизатора, чтобы узнать об этом новом маршруте — app/router.js

Давайте посмотрим это в действии. На данный момент у нас будет очень простая страница, позволяющая нам бросать несколько кубиков. Для этого обновите app/templates/roll.hbs следующим образом:

 <div class="row"> <form class="col s12"> <div class="row"> <div class="input-field col s12"> <input placeholder="Name" id="roll_name" type="text" class="validate"> <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value="1"> <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value="6"> <label for="number_of_sides">Number of Sides</label> </div> </div> <div class="row"> <button class="btn waves-effect waves-light" type="submit" name="action"> Roll Dice <i class="material-icons right">send</i> </button> </div> </form> </div> {{outlet}} 

Затем зайдите на http: // localhost: 4200 / roll и посмотрите результат:
Страница "Бросок костей"

Теперь мы должны быть в состоянии добраться сюда. Ember делает это очень просто, используя тег link-to . Это берет (среди прочего) имя маршрута, на который мы отправляем пользователя, и затем создаем разметку, чтобы туда попасть.

В нашем случае мы будем обновлять app/templates/application.hbs чтобы он содержал следующее:

 <ul id="nav-mobile" class="right hide-on-med-and-down"> {{#link-to 'roll' tagName="li"}} <a href="roll">Roll Dice</a> {{/link-to}} </ul> 

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

Ссылка на заголовок

Затем эта новая ссылка приводит пользователя к маршруту «/ roll», который мы только что настроили, именно так, как нужно.

Создание модульных компонентов

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

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

На данный момент мы собираемся создать один компонент, представляющий форму Roll Dice. Как всегда, генерация компонента выполняется с помощью нашего инструмента ember-cli следующим образом:

 $ ember generate component roll-dice installing component create app/components/roll-dice.js create app/templates/components/roll-dice.hbs installing component-test create tests/integration/components/roll-dice-test.js 

Это дало нам:

  • app / components / roll-dice.js — код, который питает компонент
  • app / templates / components / roll-dice.hbs — шаблон, управляющий тем, как он будет выглядеть
  • tests / интеграции / компоненты / roll-dice-test.js — тест для проверки правильности работы компонента

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

Обновите app/templates/components/roll-dice.hbs следующим образом:

 <form class="col s12"> <div class="row"> <div class="input-field col s12"> <input placeholder="Name" id="roll_name" type="text" class="validate"> <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value="1"> <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value="6"> <label for="number_of_sides">Number of Sides</label> </div> </div> <div class="row"> <button class="btn waves-effect waves-light" type="submit" name="action"> Roll Dice <i class="material-icons right">send</i> </button> </div> </form> 

А затем обновите app / templates / roll.hbs следующим образом:

 <div class="row"> {{roll-dice}} </div> {{outlet}} 

Шаблон для нашего компонента — это именно та разметка, которая была у нас ранее в нашем маршруте, и теперь наш маршрут значительно упрощен. Тег roll-dice — это то, что говорит Ember для рендеринга нашего компонента в нужном месте.

Если бы мы запустили это сейчас, мы бы не увидели никакой функциональной разницы, но наш код немного более модульный. Мы собираемся воспользоваться компонентом, чтобы исправить ошибку рендеринга и добавить некоторые функции в нашу систему.

Жизненный цикл компонента

У компонентов Ember есть определенный жизненный цикл, за которым они следуют, с несколькими хуками, которые могут запускаться на разных этапах. Мы собираемся использовать хук didRender который вызывается после рендеринга компонента — либо в первый раз, либо в любой последующий раз — чтобы попросить Materialize обновить метки в текстовых полях.

Это делается путем обновления кода за компонентом, который находится внутри app/components/roll-dice.js , чтобы он выглядел следующим образом:

 /* global Materialize:false */ import Ember from 'ember'; export default Ember.Component.extend({ didRender() { Materialize.updateTextFields(); } }); 

Теперь каждый раз, когда вы посещаете маршрут «/ roll» — будь то с помощью глубокой ссылки на него или с помощью ссылки на заголовок — этот код запускается, и Materialise обновляет метки для правильной передачи.

Привязка данных

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

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

В этом случае у нас есть три поля, поэтому нам нужно добавить следующие три строки в app/components/roll-dice.js , прямо внутри определения компонента:

  rollName: '', numberOfDice: 1, numberOfSides: 6, 

Затем мы обновляем наш шаблон для рендеринга с использованием хелперов вместо прямой рендеринга HTML-разметки. Для этого замените теги <input> следующим образом:

 <div class="row"> <div class="input-field col s12"> <!-- This replaces the <input> tag for "roll_name" --> {{input placeholder="Name" id="roll_name" class="validate" value=(mut rollName)}} <label for="roll_name">Name of Roll</label> </div> </div> <div class="row"> <div class="input-field col s6"> <!-- This replaces the <input> tag for "number_of_dice" --> {{input placeholder="Number of dice" id="number_of_dice" type="number" class="validate" value=(mut numberOfDice)}} <label for="number_of_dice">Number of Dice</label> </div> <div class="input-field col s6"> <!-- This replaces the <input> tag for "number_of_sides" --> {{input placeholder="Number of sides" id="number_of_sides" type="number" class="validate" value=(mut numberOfSides)}} <label for="number_of_sides">Number of Sides</label> </div> </div> 

Обратите внимание, что атрибут value имеет слегка странный синтаксис. Этот синтаксис можно использовать для любого атрибута тега, а не только для value . Это можно использовать тремя способами:

  • В качестве строки в кавычках — значение используется как есть
  • Как строка без кавычек — значение заполняется из этого фрагмента данных компонента, но компонент никогда не обновляется
  • As (mut <name>) — значение заполняется из этого фрагмента данных компонента, а компонент изменяется при изменении значения в браузере.

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

Компонент действия

Следующее, что мы хотим сделать, это взаимодействовать с компонентом. В частности, было бы хорошо справиться, когда нажата наша кнопка «Roll Dice». Ember обрабатывает это с помощью Actions — фрагментов кода в вашем компоненте, которые можно подключить к вашему шаблону. Действия просто определяются как функции в нашем классе компонентов внутри специального поля, называемого actions , которые реализуют нашу желаемую функциональность.

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

Наш блок кода действий внутри app/components/roll-dice.hbs будет выглядеть следующим образом:

  actions: { triggerRoll() { alert(`Rolling ${this.numberOfDice}D${this.numberOfSides} as "${this.rollName}"`); return false; } } 

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

Вы заметите, что мы ссылаемся на наши поля, которые мы ранее определили для доступа к полям ввода. Здесь вообще нет доступа к DOM — это просто взаимодействие с переменными JavaScript.

Теперь нам просто нужно подключить это. В нашем шаблоне нам нужно сообщить тегу формы, что он должен запускать это действие при onsubmit события onsubmit . Это просто добавление одного атрибута к тегу формы с помощью помощника Ember, чтобы подключить его к нашему действию. Это выглядит следующим образом внутри app/templates/components/roll-dice.hbs :

 <form class="col s12" onsubmit={{action 'triggerRoll'}}> 

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

Окно предупреждений

Управление данными между клиентом и сервером

Следующее, что мы хотим сделать, это на самом деле бросить несколько кубиков. Это предполагает некоторую связь с сервером — так как сервер отвечает за бросание костей и запоминание результатов.

Наш желаемый поток здесь:

  • Пользователи определяют кости, которые они хотят бросить
  • Пользователь нажимает кнопку «Roll Dice»
  • Браузер отправляет данные на сервер
  • Сервер бросает кости, запоминает результат и отправляет результаты обратно клиенту
  • Браузер отображает результаты броска костей

Звучит достаточно просто. И, конечно же, с Эмбер это действительно так.

Ember обрабатывает это, используя встроенную концепцию Store, заполненную моделями. Магазин — это единый источник знаний во всем приложении, а каждая Модель — это единая информация в магазине. Все модели знают, как сохранить себя на сервере, а Магазин знает, как создавать и получать доступ к Моделям.

Передача управления от компонентов к маршрутам

Во всем нашем приложении важно сохранять правильность инкапсуляции. Маршруты (и контроллеры, которые мы не рассмотрели) имеют доступ к магазину. Компонентов нет.

Это связано с тем, что маршрут представляет собой конкретную часть функциональности в вашем приложении, тогда как компонент представляет собой небольшую часть пользовательского интерфейса. Чтобы работать с этим, компонент имеет возможность отправлять сигнал вверх по иерархии о том, что произошло какое-то действие — очень похоже, что наши компоненты DOM могут сигнализировать нашему компоненту, что что-то произошло.

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

В логике нашего маршрута — app/routes/roll.js — нам нужно добавить следующий блок, чтобы зарегистрировать действие, которое мы собираемся выполнить.

 actions: { saveRoll: function(rollName, numberOfDice, numberOfSides) { alert(`Rolling ${numberOfDice}D${numberOfSides} as "${rollName}"`); } } 

В логике нашего компонента — app/components/roll-dice.js — нам нужно запустить действие для нашего компонента, когда мы сами сработаем. Это делается с sendAction механизма sendAction внутри нашего существующего обработчика действий.

 triggerRoll() { this.sendAction('roll', this.rollName, this.numberOfDice, this.numberOfSides); return false; } 

И, наконец, нам нужно подключить действие. Это делается в шаблоне для маршрута — app/templates/roll.hbs — путем изменения способа отображения нашего компонента:

 {{roll-dice roll="saveRoll" }} 

Это сообщает компоненту, что список свойств связан с действием saveRoll внутри нашего маршрута. Этот roll имени затем используется внутри нашего компонента, чтобы указать вызывающему игроку, что бросок кубика был сделан. Это имя имеет смысл для нашего компонента — потому что он знает, что запрашивает бросок костей, но ему все равно, как это делает другой код или что он будет делать с информацией.

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

Сохранение в магазине

Прежде чем мы сможем сохранить данные в нашем хранилище, нам нужно определить модель для их представления. Это делается с помощью нашего надежного инструмента ember-cli, чтобы снова создать структуру и затем заполнить ее.

Для создания класса модели мы выполняем:

 $ ember generate model roll installing model create app/models/roll.js installing model-test create tests/unit/models/roll-test.js 

Затем мы рассказываем нашей модели об атрибутах, которые ей необходимо понять. Это делается путем изменения app/models/roll.js чтобы он выглядел следующим образом:

 import DS from 'ember-data'; export default DS.Model.extend({ rollName: DS.attr('string'), numberOfDice: DS.attr('number'), numberOfSides: DS.attr('number'), result: DS.attr('number') }); 

DS.attr определяют новый атрибут указанного типа, называемый Transform in Ember. Здесь используются параметры по умолчанию: «строка», «число», «дата» и «логическое значение», хотя при необходимости вы можете указать свои собственные.

Теперь мы можем использовать это для создания или проката. Это делается путем доступа к магазину из нашего действия, которое мы теперь имеем в app/routes/roll.js :

 saveRoll: function(rollName, numberOfDice, numberOfSides) { let result = 0; for (let i = 0; i < numberOfDice; ++i) { result += 1 + (parseInt(Math.random() * numberOfSides)); } const store = this.get('store'); // This requests that the store give us an instance of our "roll" model with the given data const roll = store.createRecord('roll', { rollName, numberOfDice, numberOfSides, result }); // This tells our model to save itself to our backend roll.save(); } 

Если мы попробуем это, мы увидим, что нажатие кнопки « Roll Dice» вызывает сетевой вызов на наш сервер. Это терпит неудачу, потому что наш сервер еще не ожидает этого, но это прогресс.

Создать ролл

Мы не фокусируемся здесь на бэкэнде, поэтому будем заниматься этим. Если вам нужно разработать приложение Ember без какого-либо бэкэнда, тогда есть варианты, такие как ember-localstorage-adapter, которые будут полностью работать в браузере. В качестве альтернативы вам просто нужно написать соответствующий сервер и убедиться, что сервер и клиент размещены правильно и все будет работать.

Загрузка из магазина

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

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

Поскольку индексный маршрут уже неявно существует, нет необходимости использовать инструмент ember-cli — мы можем напрямую создавать файлы, и он уже подключен.

Наш обработчик маршрута перейдет в app/routes/index.js и будет выглядеть следующим образом:

 import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.get('store').findAll('roll'); } }); 

Здесь наш маршрут имеет прямой доступ к хранилищу и может использовать метод findAll для загрузки каждого оставшегося броска. Затем мы предоставляем их шаблону с использованием метода model .

Наш шаблон перейдет в app/templates/index.hbs следующим образом:

 <table> <thead> <tr> <th>Name</th> <th>Dice Rolled</th> <th>Result</th> </tr> </thead> <tbody> {{#each model as |roll|}} <tr> <td>{{roll.rollName}}</td> <td>{{roll.numberOfDice}}D{{roll.numberOfSides}}</td> <td>{{roll.result}}</td> </tr> {{/each}} </tbody> </table> {{outlet}} 

Это может получить доступ к модели напрямую из маршрута, а затем выполнить итерацию по ней, чтобы получить строки таблицы. Это будет выглядеть следующим образом:

Страница истории

Резюме

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

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

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

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