Привязка модели базовой модели к марионетке ItemView - блокировка .fetch()?
Это вопрос из двух частей. 1) Есть ли лучший способ визуализации модели для представления асинхронно? В настоящее время я делаю запрос ajax с использованием метода fetch
в модели (хотя я вызываю его явно при инициализации), а затем создаю шаблонное представление с использованием события приложения vent
, которое публикуется внутри модели после вызова метода parse
. Прохладный, но вонючий? 2) Может ли использоваться метод блокировки fetch
, и возможно ли это?
Приложение отображает это на странице:
layout
navbar
index
Затем он извлекает модель и делает это:
layout
navbar
thing
1
something
somethingelse
Но если я не использую триггер vent
, он (предположительно) отображает:
layout
navbar
thing
1
null
null
Шаблоны html:
<!-- Region: NavBar -->
<script type="text/template" id="template-navbar">
<div id="navbar">
navbar
</div>
</script>
<!-- View: IndexView -->
<script type="text/template" id="template-index">
<div id="index">
index
</div>
</script>
<!-- View: ThingView -->
<script type="text/template" id="template-thing">
<div id="thing">
thing<br/>
<%= id %><br/>
<%= valOne %><br/>
<%= valTwo %><br/>
</div>
</script>
<!-- Region -->
<div id="default-region">
<!-- Layout -->
<script type="text/template" id="template-default">
layout
<div id="region-navbar">
</div>
<div id="region-content">
</div>
</script>
</div>
app.js
window.App = { }
# Region
class RegionContainer extends Backbone.Marionette.Region
el: '#default-region'
# Called on the region when the view has been rendered
onShow: (view) ->
console.log 'onShow RegionContainer'
App.RegionContainer = RegionContainer
# Layout
class DefaultLayout extends Backbone.Marionette.Layout
template: '#template-default'
regions:
navbarRegion: '#region-navbar'
contentRegion: '#region-content'
onShow: (view) ->
console.log 'onShow DefaultLayout'
App.DefaultLayout = DefaultLayout
# NavBar (View)
class NavBar extends Backbone.Marionette.ItemView
template: '#template-navbar'
initialize: () ->
console.log 'init App.NavBar'
App.NavBar = NavBar
# Index View
class IndexView extends Backbone.Marionette.ItemView
template: '#template-index'
initialize: () ->
console.log 'init App.IndexView'
App.IndexView = IndexView
# Thing View
class ThingView extends Backbone.Marionette.ItemView
template: '#template-thing'
model: null
initialize: () ->
console.log 'init App.ThingView'
events:
'click .test_button button': 'doSomething'
doSomething: () ->
console.log 'ItemView event -> doSomething()'
App.ThingView = ThingView
# Thing Model
class Thing extends Backbone.Model
defaults:
id: null
valOne: null
valTwo: null
url: () ->
'/thing/' + @attributes.id
initialize: (item) ->
console.log 'init App.Thing'
@fetch()
parse: (resp, xhr) ->
console.log 'parse response: ' + JSON.stringify resp
# resp: {"id":"1","valOne":"something","valTwo":"somethingelse"}
@attributes.id = resp.id
@attributes.valOne = resp.valOne
@attributes.valTwo = resp.valTwo
console.log 'Thing: ' + JSON.stringify @
@
App.MyApp.vent.trigger 'thingisdone'
App.Thing = Thing
# App
$ ->
# Create application, allow for global access
MyApp = new Backbone.Marionette.Application()
App.MyApp = MyApp
# RegionContainer
regionContainer = new App.RegionContainer
# DefaultLayout
defaultLayout = new App.DefaultLayout
regionContainer.show defaultLayout
# Views
navBarView = new App.NavBar
indexView = new App.IndexView
# Show defaults
defaultLayout.navbarRegion.show navBarView
defaultLayout.contentRegion.show indexView
# Allow for global access
App.defaultRegion = regionContainer
App.defaultLayout = defaultLayout
# Set default data for MyQpp (can't be empty?)
data =
that: 'this'
# On application init...
App.MyApp.addInitializer (data) ->
console.log 'init App.MyApp'
# Test
App.modelViewTrigger = ->
console.log 'trigger ajax request via model, render view'
App.MyApp.vent.trigger 'show:thing'
App.timeoutInit = ->
console.log 'init timeout'
setTimeout 'App.modelViewTrigger()', 2000
App.timeoutInit()
# Event pub/sub handling
App.MyApp.vent.on 'show:thing', ->
console.log 'received message -> show:thing'
thing = new App.Thing(id: '1')
App.thingView = new App.ThingView(model: thing)
# I should be able to do this, but it renders null
# App.defaultLayout.contentRegion.show App.thingView
# Testing to see if I could pub from inside model..yes!
App.MyApp.vent.on 'thingisdone', ->
console.log 'received message -> thingisdone'
App.defaultLayout.contentRegion.show App.thingView
MyApp.start data
Ответы
Ответ 1
С очень простой точки зрения, отбросив конкретный пример, который вы предоставили, я хотел бы подойти к проблеме и решению.
Общая задача/решение
Вот общая версия проблемы:
- Вам нужно получить модель по ее идентификатору.
- Вам нужно просмотреть рендер после того, как модель была извлечена.
Это довольно просто. Прикрепите модель к представлению перед извлечением данных, а затем используйте событие "sync" модели для визуализации представления:
MyView = Backbone.View.extend({
initialize: function(){
this.model.on("sync", this.render, this);
},
render: function(){ ... }
});
myModel = new MyModel({id: someId});
new MyView({
model: myModel
});
myModel.fetch();
Примечания:
Я настраиваю модель с ее идентификатором и представлением с моделью перед вызовом fetch
на модели. Это необходимо для предотвращения состояния гонки между загрузкой данных и визуализации представления.
Я указал здесь общий материал Backbone. Marionette, как правило, работает одинаково, но делает рендеринг для вас.
Ваши конкретные потребности
Блокировка выборки
Плохая идея, все вокруг. Не пытайтесь.
Блокирующая выборка сделает ваше приложение полностью безответственным, пока данные не вернутся с сервера. Это проявится как приложение, которое работает плохо и зависает в любое время, когда пользователь пытается что-либо сделать.
Ключом к тому, чтобы не делать этого, является использование событий и обеспечение того, чтобы ваши события были настроены до того, как вы действительно сделаете асинхронный вызов, как показано в моем общем примере.
И не вызывайте выборку из инициализатора модели. Это требует неприятностей, так как вы не сможете настроить какие-либо представления или события до того, как произойдет выбор. Я уверен, что это решит большинство проблем, которые вы испытываете при асинхронном вызове.
События между представлением и моделью
Во-первых, я бы не использовал MyApp.vent
для связи между моделью и экземпляром представления. В представлении уже есть ссылка на модель, поэтому они должны общаться напрямую друг с другом.
Другими словами, модель должна непосредственно инициировать событие, и представление должно прослушивать событие на модели. Это работает так же, как мой простой пример, но вы можете заставить свою модель запускать любое событие, которое вы хотите в любое время.
Я также был бы уверен в использовании функции bindTo
в представлениях Marionette, чтобы помочь в очистке событий при закрытии представления.
MyView = Backbone.Marionette.ItemView.extend({
initialize: function(){
this.bindTo(this.model, "do:something", this.render, this);
}
});
MyModel = Backbone.Model.extend({
doSomething: function(){
this.trigger('do:something');
}
});
myModel = new MyModel();
new MyView({
model: myModel
});
myModel.doSomething();
Другие элементы
Есть некоторые другие элементы, которые, как я думаю, вызывают некоторые проблемы или приводят к нечетным ситуациям, которые могут вызвать проблемы.
Например, у вас слишком много событий в событии DOMReady: $ ->
Это не значит, что у вас слишком много кода, выполняемого из этого события, но в этом событии слишком много кода. Вам не нужно делать ничего более:
$ ->
App.MyApp.start(data)
Не указывайте свой объект Marionette.Application в этом обратном вызове. Это необходимо определить самостоятельно, чтобы вы могли настроить свои инициализаторы вне обратного вызова DOMReady, а затем запускать их с помощью вызова app.start()
.
Взгляните на пример приложения BBCloneMail для примера при рендеринге макета, а затем заполните его регионы после загрузки данных и внешних шаблонов:
источник: https://github.com/derickbailey/bbclonemail
live app: http://bbclonemail.heroku.com/
Я не думаю, что я прямо отвечаю на ваши вопросы так, как вам может захотеть, но идеи, которые я представляю, должны привести вас к тому, что вам нужно. Надеюсь, это поможет хотя бы.:)
Ответ 2
См. новое предложение Derick для решения этой общей проблемы: https://github.com/marionettejs/backbone.marionette/blob/master/upgradeGuide.md#marionetteasync-is-no-longer-supported
Короче говоря, переместите асинхронный код в сторону от ваших представлений, а это означает, что вам необходимо предоставить им модели, данные которых уже были извлечены. Из примера в руководстве по обновлению Marionette:
Marionette.Controller.extend({
showById: function(id){
var model = new MyModel({
id: id
});
var promise = model.fetch();
$.when(promise).then(_.bind(this.showIt, this));
},
showIt: function(model){
var view = new MyView({
model: model
});
MyApp.myRegion.show(view);
}
});