Lidando com visualizações de backbone com injeção de dependência

** Meus exemplos estão em Coffeescript. Desculpe, nerds hardcore js: / **

Como muitos sabem, trabalhar com a espinha dorsal pode começar de uma forma excitante e rapidamente levar à frustração. Um desses pontos problemáticos são as visualizações. Vistas especificamente aninhadas. Normalmente, você terá uma visualização principal que contém muitas outras visualizações. Se você está testando suas visualizações (você está testando suas visualizações, certo?) Torna-se ainda mais chato lidar com visualizações porque, para testar uma visualização, você geralmente tem que configurar um monte de visualizações com modelos e coleções e mais e mais louco blá lbah! waka maldito inferno! Ok, então está uma bagunça. Mas há uma maneira melhor. Sim, preparado para ser iluminado. Bem-vindo ao Module Views (eu meio que inventei isso, mas acho que faz sentido).

É assim que funciona. Digamos que você tenha um pequeno aplicativo de lista de tarefas (todo mundo tem) e na página principal você tenha uma visão do que está acontecendo. Você tem:

  • Lista de todos
  • Cabeçalho com informações do usuário

Sua impressão inicial pode ser a de criar algo que se pareça um pouco com isso.

class IndexView extends Backbone.View
initialize
: ->
@todos = new ToDoCollection
@todos.fetch()
@todos = new ToDoView @todos

@user = new User ENV.CURRENT_USER
@headerView = new HeaderView @user

render
: ->
@$el.append @todos.render().el
@$el.find('.header').html @headerView.render().el

# SOME ROUTER/BUNDLE/OTHER JS FIlE

@indexView = new IndexView()
$
('body').html @indexView.render().el

Existem alguns problemas com isso. Primeiro, se você estiver inicializando dados na página como estou na linha 7 com ENV.CURRENT USER, você acabou de criar uma dependência em sua classe de backbone com a fonte de seus dados. Isso é ruim. Além disso, agora esta é uma classe muito difícil de testar. Você precisa simular todas as solicitações fetch (), criar dados de USUÁRIO ENV.CURRENT falsos e ENTÃO você pode finalmente iniciar seu teste. Parece uma grande bagunça. Uma pequena injeção de dependência pode nos tirar dessa confusão.

Apresentando Visualizações de Módulo aka (Visualizações de crianças / pais)

class IndexView extends Backbone.View
# No need to call render or initialize

# SOME ROUTER/BUNDLE/OTHER JS FIlE

current_user
= new User ENV.CURRENT_USER
todos
= new ToDoCollection

todos
.fetch()

indexView
= new IndexView
el
: 'body'
views
:
"#header" : new HeaderView
model
: current_user
"#todos" : new ToDoView
collection
: todos

indexView
.render()
# note you could leave out the el: 'body' and just do
# something like $('body').html indexView.render().el

Isso tudo é muito legal, mas deixei de fora uma parte. O motor real por trás disso torna tudo isso possível. Em primeiro lugar, obrigado a Ryan Florence por ter apresentado esta ideia e por escrever a maior parte deste código. Ele é um bom programador e é um prazer trabalhar com ele na Instructure.

# Extends Backbone.View on top of itself with some added features
# we use regularly
class Backbone.View extends Backbone.View

##
# Manages child views and renders them whenever the parent view is rendered.
# Specify views as key:value pairs of `className: view` where `className` is
# a CSS className to find the element in which to to append a rendered
# `view.el`
#
# Be sure to call `super` in the parent view's `render` method _after_ the
# html has been set.
views
: false
# example: new ExampleView

##
# Define default options, options passed in to the view will overwrite these
defaults
:

# can hand a view a template option to avoid subclasses that only add a
# different template
template: null

initialize
: (options) ->
@options = _.extend {}, @defaults, @options, options
@setTemplate()
@$el.data 'view', this
this

setTemplate
: ->
@template = @options.template if @options.template

##
# Extends render to add support for chid views and element filtering
render
: (opts = {}) =>
@renderEl()
@_afterRender()
this

renderEl
: ->
@$el.html @template(@toJSON()) if @template

##
# Internal afterRender
# @api private
_afterRender
: ->
@cacheEls() if @els
@$('[data-bind]').each @createBinding
@afterRender()
# its important for renderViews to come last so we don't filter
# and cache all the child views elements
@renderViews() if @options.views

##
# Add behavior and bindings to elements.
afterRender
: ->

##
# in charge of getting variables ready to pass to handlebars during render
# override with your own logic to do something fancy.
toJSON
: ->
json
= ((@model ? @collection)?.toJSON arguments...) || {}
json
.cid = @cid
json


##
# Renders all child views
#
# @api private
renderViews
: ->
_
.each @options.views, @renderView

##
# Renders a single child view and appends its designated element
# Use ids in your view, not classes. This
#
# @api private
renderView
: (view, selector) =>
target
= @$("##{selector}")
target
= @$(".#{selector}") unless target.length
view
.setElement target
view
.render()
@[selector] ?= view


Backbone.View

Ok, então há um monte de coisas aqui, mas como seguir isso é apenas para começar com a função de renderização. Como você pode ver, ele chamará _afterRender () que então chegará a renderViews. Assumindo que você criou cada uma de suas visualizações com um modelo, ele automaticamente chamará toJSON e despejará isso nesses modelos, em seguida, obterá a exibição renderizada e despejará em um ID / classe / elemento de sua escolha.

** Conclusão **

Ao estender a espinha dorsal de maneiras adequadas, você pode tornar sua vida cotidiana muito mais fácil. A injeção de dependência pode tornar mais fácil escrever, entender e testar suas classes e garantir que o acoplamento não exista ajuda a reduzir códigos malucos voando para todos os lados.

Estou aberto a ideias e posso postar isso em um repositório, se desejar.

  • Sterling

ATUALIZAÇÃO 29 de janeiro de 2013 – Aqui está um GIST do código, uma vez que o coderwall suga um código de formatação. Irônico que todo um serviço em torno do compartilhamento de código não formate o código corretamente. haha

https://gist.github.com/4666340