В прошлый раз мы завершили элементы на стороне клиента, необходимые для отображения событий на странице пользовательских событий. Теперь мы сосредоточены на добавлении и удалении событий асинхронно с использованием Backbone.
На скриншоте страницы «События» единственная часть представления, которую мы не реализовали, — это EventFormView. Очевидно, что это представление будет отвечать за отображение формы для пользователя, которая позволяет создавать новое событие. Форма проста:
Первое, что я хочу изменить, это имя. Помещение «формы» в имя представления кажется мне хрупким, поэтому давайте изменим его на то, что мы делаем, то есть создаем событие: CreateEventView.
CreateEventView должен:
- Содержать форму.
- Создать событие.
Я думаю, что стоит отметить, что мы не тестируем связывание событий (имеется в виду, как обрабатывается событие отправки формы), потому что это будет тестирование инфраструктуры Backbone.
Наши тесты выглядят так:
describe("CreateEventView", function() { beforeEach(function() { loadFixtures("eventForm.html"); this.view = new App.CreateEventView(); this.form = $(this.view.el).find("form")[0]; }); it("should provide a form", function() { expect($(this.view.el).find("form").length).toEqual(1); }); describe("creating an event", function() { beforeEach(function() { window.eventCollection = new Backbone.Collection(); this.createStub = sinon.stub(window.eventCollection, "create"); $(this.form).find("#event_name").val("Test Event"); $(this.form).find("#event_description").val("Test Event Description"); this.view.createEvent(); }); it("should call create on the EventCollection", function() { expect(this.createStub).toHaveBeenCalled(); }); }); });
При выполнении этих тестов характеристики жасмина становятся красными, как и должно быть. Рассматривая спецификацию «создание события», мы window.eventCollection.create()
некоторые данные в форму, а затем window.eventCollection.create()
ли window.eventCollection.create()
. Backbone предлагает метод create
для коллекций для удобства как создания объекта, так и добавления его в коллекцию. Ницца.
Эти тесты устранили проблему, связанную с формой. Чтобы создать App.TestEvent
, нам нужно проанализировать значения атрибутов из формы. Есть много утилит, которые будут сериализовать форму в json, но я думаю, что мы сами пока с этим справимся.
К сожалению, мы не можем просто использовать что-то столь же простое, как метод jQuery form.serializeArray()
, потому что в форме есть значения, которые не являются атрибутами в событии. Примером является «authenticity_token», используемый Rails для помощи в обнаружении CSRF-атак. Мы не хотим этого на нашем мероприятии. Я собираюсь написать служебный метод, чтобы делать то, что я думаю, нам нужно. Тест на авось:
describe("parsing form attributes", function() { it("should have the correct attribute values", function() { $(this.form).find("#event_name").val("Test Event"); $(this.form).find("#event_description").val("Test Event Description"); var attributes = this.view.parseFormAttributes().event; expect(attributes.name).toEqual("Test Event"); expect(attributes.description).toEqual("Test Event Description"); }); });
Реализация:
parseFormAttributes: -> _.inject( @form.serializeArray(), (memo, pair) -> key = pair.name return memo unless /^event/.test(key) val = pair.value if key.indexOf('[') > 0 parentKey = key.substr(0, key.indexOf('[')) childKey = key.split('[')[1].split(']')[0] if typeof memo[parentKey] == "undefined" memo[parentKey] = {} memo[parentKey][childKey] = val else memo[key] = val return memo ,{})
С parseFormAttributes()
на месте, мы можем завершить createEvent()
. Вот весь CreateEventView:
App or= {} App.CreateEventView = Backbone.View.extend( el: "#edit_event" initialize: -> @form = $(this.el).find("form") events: "submit form" : "handleFormSubmission" handleFormSubmission: (e) -> e.stopPropagation() @createEvent() false createEvent: ()-> evento = new App.Event(@parseFormAttributes().event) has_id = @form.attr("action").match(//events/(w*)/) if has_id evento.id = has_id[1] evento.save() else eventCollection.create(evento) parseFormAttributes: -> _.inject( @form.serializeArray(), (memo, pair) -> key = pair.name return memo unless /^event/.test(key) val = pair.value if key.indexOf('[') > 0 parentKey = key.substr(0, key.indexOf('[')) childKey = key.split('[')[1].split(']')[0] if typeof memo[parentKey] == "undefined" memo[parentKey] = {} memo[parentKey][childKey] = val else memo[key] = val return memo ,{}) )
Наконец, мы должны сказать нашему маршрутизатору создать это представление вместе с другими представлениями.
// spec/javscripts/router_spec.js describe("index", function() { beforeEach(function() { ... this.createViewSpy = sinon.stub(App, "CreateEventView").returns(this.mockView); this.router.index(); }); afterEach(function() { ... App.CreateEventView.restore(); }); ... it("should create the CreateEventView", function() { expect(this.createViewSpy).toHaveBeenCalled(); }); }); });
(Если не понятно, ...
выше означает, что я удалил существующий код из последнего поста … просто добавьте строки здесь)
Вспоминая предыдущий пост, нам нужно добавить метод в метод index
маршрутизатора, например:
# app/assets/javascripts/router.js.coffee index: -> @eventListView = new App.EventListView({collection: window.eventCollection or= new App.EventsCollection()}) @eventListView.render() @createEventView = new App.CreateEventView() if $('#map').length > 0 @mapView = new App.MapView(App.MapProviders.Leaflet) @mapView.render()
ОБНОВЛЕНИЕ : читатель с предупреждением (см. Комментарии) обнаружил в статье некоторые упущения, которые усложняли его выполнение … извините! Я действительно ценю людей, которые находят подобные вещи.
Вы должны убедиться, что ваш метод EventsController # create выглядит так:
def create event = current_user.events.build(params[:event]) event.save! respond_with(event) do |format| format.html { redirect_to events_path } end end
Также убедитесь, что это в верхней части определения класса EventsController:
respond_to :html, :json
Если вы этого не сделаете, возвращается неправильный статус HTTP, и Backbone не будет обновлять представление. (Спасибо, Николай!)
Если вы перейдете по адресу http: // localhost: 3000 и нажмете на страницу «Мои события», вы сможете добавить события и посмотреть, как они отображаются в нашем списке.
Удаление событий
Инь к нашему добавлению Events Yang (звучит немного грязно …) удаляет события. Меня разрывает вопрос о том, стоит ли включать функцию удаления на индексную страницу events # как часть списка. Несмотря на то, что я могу видеть случаи использования желания удалить, я также могу видеть, как они кликают по странице события, чтобы удалить событие как более явный пользовательский интерфейс, который вы лучше знаете, что, черт возьми, делаете. течь. Давайте предположим, что наши пользователи не слишком довольны кликами и достаточно взрослые, чтобы справиться с удалением событий из списка.
Одно событие за раз
Некоторое время назад я решил, что страница events # show должна была быть отдельной страницей от страницы index # events, а не пытаться использовать одностраничный подход. Это решение привело к вопросу о том, как выполнить правильный javascript на каждой странице. В случае с индексом event # мы имеем коллекцию EventsCollection и просматриваем список и создаем события. На странице событий # show мы сфокусируемся на списке событий и взглядах на манипуляции с событиями для текущего события.
Немного поиска привело меня к этому посту Джейсона Гарбера, который расширяет подход (несравненный Пол Айриш) к этой проблеме. Вы должны прочитать этот пост для получения полной информации, но суть подхода заключается в создании вспомогательного класса, который вызывает метод загрузки на основе некоторых атрибутов data- *, записанных в элементе body. Следуя этому посту, мы изменим наш элемент body в app / views / layout / application.haml.html следующим образом:
%body{:"data-controller" => controller_name, :"data-action" => action_name }
После этого я изменил app / assets / javascripts / app.js.coffee, добавив наш новый класс утилит :
window.App = common: init: -> events: init: -> index: -> window.eventCollection = new App.EventsCollection(bootstrapEvents) new App.EventsShowRouter() Backbone.history.start root: "/events" show: new App.EventRouter() ev_id = location.href.match(//events/(.*)/)[1] Backbone.history.start root: "/events/"+ev_id UTIL = exec: ( controller, action )-> ns = App action or= "init" if ( controller != "" && ns[controller] && typeof ns[controller][action] == "function" ) ns[controller][action]() init: -> body = document.body controller = body.getAttribute( "data-controller" ) action = body.getAttribute( "data-action" ) UTIL.exec( "common" ) UTIL.exec( controller ) UTIL.exec( controller, action ) $(UTIL.init)
В рамках этого изменения я переименовал App.Router
в App.EventsRouter
, создал папку app / assets / javascripts / routers и скопировал недавно переименованный eventsRouter.js.coffee в этот каталог. Мне также пришлось переименовать спецификацию в eventsRouter_spec.js и изменить оба файла, изменив App.Router
на App.EventsRouter
. После каждого изменения я переустанавливал свой набор Жасмин и исправлял вещи, пока набор не прошел. Я люблю проходить тесты!
Последнее приспособление для этого изменения было изменить app / assets / application / js , удалив
//= require router
//= require_tree ./routers
и удаление вызова $(App.start)
из нижней части этого файла.
Все спецификации должны пройти, и существующая функциональность должна снова заработать. Теперь мы можем сосредоточиться на одном событии и его случаях.
Наконец, повод для случаев
Мы можем официально назвать это «склоном вниз». Как только мы сможем добавить случаи и увидеть их на карте, мы очень близки к завершению.
Подход к этой странице будет очень похож на страницу событий. Поэтому я думаю, что у вас, читателя Loccasions, есть прекрасная возможность попытаться создать страницу события # show самостоятельно. Как минимум, страница должна быть в состоянии:
- Добавить случаи
- Удалить случаи
- Перечислите все Случаи для События
В качестве отправной точки, вот как выглядит моя: