Со всем вниманием, которое уделяют клиентские фреймворки MVC, я решил, что пришло время по-настоящему взглянуть на одну из них и решить для себя, стоит ли эта реклама.
Я уверен, что многие из вас играли с этими структурами, наблюдая, как другие делают то же самое . Эти короткие заметки мало что говорят о том, что значит создавать что-то полезное. В этой части я буду исследовать, что значит создавать что-то действительно ценное.
Выбор правильной платформы MVC может показаться сложным. Есть только Backbone.js, Angular.js, Ember.js. По моим оценкам, прагматичный разработчик Rails решил, что Ember.js — самая дружественная среда Rails. Он хорошо интегрируется с Rails и хорошо подходит для переключения с бэкенда на внешний интерфейс.
Чтобы построить что-то интересное, а также не изобретать велосипед, мы будем строить поверх приложения TodoMVC . Это то же самое приложение, которое используется в качестве примера в официальном руководстве по ember . Наша версия будет сфокусирована на том, как создать и расширить ее следующими способами:
- перейти к проекту Rails
- использовать Rails в качестве внутреннего хранилища
- добавить аутентификацию
- добавить личные списки для аутентифицированных пользователей
- добавить защищенный обмен списками
Есть много оснований, так что это займет пару постов. Сегодня мы расскажем о том, как перевести приложение на запуск внутри проекта Rails и использовать Rails для внутреннего хранения.
TodoMVC в Эмбер
Приложение TodoMVC используется в качестве универсального примера для сравнения оболочек javascript. Он обладает достаточной функциональностью для демонстрации фреймворка и в то же время мгновенно знаком с любым потенциальным разработчиком. Давайте кратко рассмотрим особенности.
Приложение отображает список элементов Todo с текстовым полем вверху. Вы можете добавлять новые элементы в список, используя текстовое поле. Отдельные элементы также можно редактировать, дважды щелкнув по ним, и удалить с помощью значка удаления, который отображается при наведении курсора. Все задачи могут быть помечены как выполненные с помощью флажка nex для ввода.
Ниже списка есть счетчик незавершенных предметов и фильтр для отображения всех / активных / выполненных задач. Наконец, вы можете удалить все завершенные элементы из списка, используя кнопку «Очистить завершено» внизу.
Эта статья не собирается вдаваться в детали, так как для этого есть отличная статья в официальном руководстве по ember . Здесь основное внимание уделяется высокоуровневому обзору того, как части сочетаются друг с другом, и проясняется, что происходит, когда мы переносим пример для размещения внутри проекта Rails.
Базовый шаблон — это место, где можно начать знакомство с приложением Ember. В этом шаблоне все собрано вместе: вы получаете обзор (из тегов script
Следующая выдержка из приложения TodoMVC:
<!doctype html>
<html lang="en" data-framework="emberjs">
<head>
<meta charset="utf-8">
<title>ember.js • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
</head>
<body>
<script type="text/x-handlebars" data-template-name="todos">
<!--handlebars template content omitted-->
</script>
<!--library files-->
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/handlebars/handlebars.js"></script>
<script src="bower_components/ember/ember.js"></script>
<script src="bower_components/ember-data/ember-data.js"></script>
<script src="bower_components/ember-localstorage-adapter/localstorage_adapter.js"></script>
<!--application files-->
<script src="js/app.js"></script>
<script src="js/router.js"></script>
<script src="js/models/todo.js"></script>
<script src="js/controllers/todos_controller.js"></script>
<script src="js/controllers/todo_controller.js"></script>
<script src="js/views/edit_todo_view.js"></script>
<script src="js/views/todos_view.js"></script>
<script src="js/helpers/pluralize.js"></script>
</body>
</html>
По большей части это выглядит как стандартный документ HTML5 с большим количеством javascript. Единственная нестандартная часть — это шаблон x-handlebars . Код здесь опущен, но обсуждается в официальном руководстве ember . Наличие такого кода внутри HTML хорошо для небольших приложений, но мы извлечем его как часть перехода на Rails.
Импорт JavaScript состоит из двух частей: первая часть — это импорт файлов библиотеки, необходимых для запуска приложения Ember, а другая — это само приложение Ember. Оба из них обсуждаются более подробно в руководстве, поэтому обратитесь к нему для получения дополнительной информации.
Настройка Rails
Rails имеет хорошую поддержку для размещения приложений Ember. Все, что вам нужно сделать, это включить гем ember-rails в ваш Gemfile и сгенерировать установочные файлы.
gem 'ember-rails'
gem 'ember-data-source', '>= 1.0.0.beta7'
rails g ember:bootstrap
Генератор создает структуру папок ember в app / assets / javascripts . Текущая версия не идеальна, и для завершения настройки требуются небольшие изменения.
Сначала удалите исходный файл app / assets / javascripts / application.js . Затем добавьте следующие две строки в самый верх app / assets / javascripts / application.js.coffee, чтобы загрузить jQuery перед загрузкой Ember.
#= require jquery
#= require jquery_ujs
Чтобы открыть корневую страницу, добавьте следующее в config / rout.rb
Rails.application.routes.draw do
root to: 'application#index'
end
Также добавьте пустой app / views / application / index.html.erb . Это хорошая отправная точка, использующая стандартный ApplicationController
index
Запустите приложение Rails ( rails s
http: // localhost: 3000, чтобы убедиться, что все подключено.
Перемещение TodoMVC в Rails
Пришло время скопировать приложение TodoMVC в наше приложение Rails. Полученный код находится на github , если вы хотите перейти к концу.
Начните с копирования шаблона руля, который обсуждался ранее, в app / views / application / index.html.haml . Отредактируйте файл app / views / layouts / application.html.erb , удалив ссылки на turbolinks
javascript_include_tag
yield
body
Для дополнительного кредита мы можем удалить турболинки из Gemfile, потому что мы не будем их использовать.
Завершите миграцию, скопировав следующие файлы и преобразовав их в CoffeeScript.
js / rout.js => приложение / assets / javascripts / rout.js.coffee
TadaEmber.Router.map ->
@resource 'todos', path: '/', ->
@route 'active'
@route 'completed'
TadaEmber.TodosRoute = Ember.Route.extend
model: -> @store.find('todo')
TadaEmber.TodosIndexRoute = Ember.Route.extend
setupController: -> @controllerFor('todos').set('filteredTodos', this.modelFor('todos'))
TadaEmber.TodosActiveRoute = Ember.Route.extend
setupController: ->
todos = @store.filter 'todo', (todo) ->
!todo.get('isCompleted')
@controllerFor('todos').set('filteredTodos', todos)
TadaEmber.TodosCompletedRoute = Ember.Route.extend
setupController: ->
todos = @store.filter 'todo', (todo) ->
todo.get('isCompleted')
@controllerFor('todos').set('filteredTodos', todos)
js / models / todo.js => приложение / assets / javascripts / models / todo.js
TadaEmber.Todo = DS.Model.extend
title: DS.attr('string')
isCompleted: DS.attr('boolean')
js / controllers / todos controller.js => приложение / assets / javascripts / controllers / todos controller.js.cofee
TadaEmber.TodosController = Ember.ArrayController.extend
actions:
createTodo: ->
title = @get('newTitle').trim()
return if !title
todo = @store.createRecord 'todo',
title: title
isCompleted: false
todo.save()
@set('newTitle', '')
clearCompleted: ->
completed = @get('completed')
completed.invoke('deleteRecord')
completed.invoke('save')
remaining: Ember.computed.filterBy('content', 'isCompleted', false)
completed: Ember.computed.filterBy('content', 'isCompleted', true)
allAreDone: ((key, value) ->
if value != undefined
@setEach('isCompleted', value)
return value;
else
length = @get('length')
completedLength = @get('completed.length')
return length > 0 && length == completedLength
).property('length', 'completed.length')
js / controllers / todo controller.js => приложение / assets / javascripts / controllers / todo controller.js.coffee
TadaEmber.TodoController = Ember.ObjectController.extend
isEditing: false
bufferedTitle: Ember.computed.oneWay('title')
actions:
editTodo: -> @set('isEditing', true)
doneEditing: ->
bufferedTitle = @get('bufferedTitle').trim()
if Ember.isEmpty(bufferedTitle)
Ember.run.debounce(@, 'removeTodo', 0)
else
todo = @get('model')
todo.set('title', bufferedTitle)
todo.save()
@set('bufferedTitle', bufferedTitle)
@set('isEditing', false)
cancelEditing: ->
@set('bufferedTitle', @get('title'))
@set('isEditing', false)
removeTodo: -> @removeTodo()
removeTodo: ->
todo = @get('model')
todo.deleteRecord()
todo.save()
saveWhenCompleted: (->
@get('model').save()
).observes('isCompleted')
js / views / edit todo view.js => приложение / assets / javascripts / views / edit todo view.js.coffee
TadaEmber.EditTodoView = Ember.TextField.extend
focusOnInsert: (->
@.$().val(@.$().val())
@.$().focus
).on('disInsertElement')
Ember.Handlebars.helper('edit-todo', TadaEmber.EditTodoView)
js / views / todos view.js => приложение / assets / javascripts / views / todos view.js.coffee
TadaEmber.TodosView = Ember.View.extend
focusInput: (-> @.$('#new-todo').focus() ).on('disInsertElement')
js / helpers / множественное число.js => приложение / активы / javascripts / помощники / множественное число.js
Ember.Handlebars.helper 'pluralize', (singular, count) ->
inflector = Ember.Inflector.inflector;
count == 1 ? singular : inflector.pluralize(singular)
приложение / активы / JavaScripts / store.js.coffee
TadaEmber.Store = DS.Store.extend()
# Override the default adapter with the `DS.ActiveModelAdapter` which
# is built to work nicely with the ActiveModel::Serializers gem.
#adapter: '_ams'
TadaEmber.ApplicationAdapter = DS.LSAdapter.extend
namespace: 'tada-emberjs'
Почти сделано. Скопируйте компоненты bower / ember-localstorage-adapter / localstorage adapter.js в app / assets / javascript / localstorage adapter.js и добавьте следующую строку в начало файла app / assets / javascript / tadaember.js.coffee
#= require ./localstorage_adapter
Завершите преобразование, скопировав содержимое тега script
app / views / application / index.html.erb в app / javascripts / templates / todos.hbs . Наконец, копирование CSS и изображений из исходного кода в наш каталог ресурсов добавит некоторые стили.
Добавление Rails в бэкэнд
Список хранит свои данные внутри localstorage
Открытие приложения в другом браузере приведет к сбросу приложения в чистое состояние без каких-либо задач. Мы исправим это, используя приложение Rails в качестве поставщика хранилища.
Сначала создайте модель и перенесите
rails g model Todo title is_completed:boolean
rake db:migrate
Добавьте контроллер, который будет действовать как API для приложения Ember. Не забудьте добавить ресурсный вызов в роутер.
приложение / контроллеры / todos_controller.rb
class TodosController < ApplicationController
respond_to :json
def index
respond_with Todo.all
end
def show
respond_with Todo.find(params[:id])
end
def create
respond_with Todo.create(todo_params)
end
def update
respond_with Todo.update(params[:id], todo_params)
end
def destroy
respond_with Todo.destroy(params[:id])
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def todo_params
params.require(:todo).permit(:title, :is_completed)
end
end
конфиг / routes.rb
Rails.application.routes.draw do
resources :todos
root to: 'application#index'
end
Наконец, добавьте сериализатор для Rails, чтобы правильно сериализовать модель. Ember ожидает идентификатор строки для каждой модели. ActiveModelAdapter
is_completed
isCompleted
Приложение / сериализаторов / todo_serializer.rb
class TodoSerializer < ActiveModel::Serializer
# fix for ember-data deserializer not being able to handle non-string ids
def id
object.id.to_s
end
attributes :id, :title, :is_completed
end
Чтобы снять учебные колеса и использовать новый бэкэнд Rails, обновите хранилище Ember, чтобы использовать хранилище activemodel, предоставляемое самоцветом ember-rails. (см. это для деталей.)
TadaEmber.Store = DS.Store.extend
# Override the default adapter with the `DS.ActiveModelAdapter` which
# is built to work nicely with the ActiveModel::Serializers gem.
adapter: '-active-model'
Конечный продукт доступен на GitHub
Вывод
Мы успешно перенесли приложение TodoMVC из автономного приложения в приложение Rails. Мы также отошли от локального хранилища и храним данные в Rails. В следующих статьях мы рассмотрим добавление аутентификации и возможность делиться списком через URL.