问题
I'm trying to build an order system for a friend using django/tastypie on the server side and backbone/marionette on the client side. The server side poses no bigger problem but since I'm an inexperienced frontend developer I'm kinda stuck;
The simpler case went just fine, e.g. to list, add, edit and remove an Article (just a table in my database with sku, description and so on) using Composite- and ItemViews.The problem is when I'm trying to construct the views for an Order since it consists of several tables with relations on the server side.
Order
LineItem
Article
StoreQuantity
Store
StoreQuantity
Store
LineItem
Article
StoreQuantity
Store
StoreQuantity
Store
...
So an Order consists of several LineItems. A LineItem consists of an Article and several StoreQuantity:s making it possible to model something like "Order Article A; 10 copies to Store X and 4 copies to Store Y, Article B; 4 copies to Store X and 1 copy to Store Y".
I guess my question is; how would I go about to construct my views for something like above?
Would something like below be the wrong way?
Create an OrderCompositeView and pass it the OrderModel from my controller.
When OrderModel is fetched from the server, let OrderCompositeView create a LineItemCompositeView.
When LineItemCompositeView has fetched its' LineItemCollection from the server.. and so on recursively
Should I create a REST-url that returns the entire JSON for an Order and its relations instead of several smaller recursive calls, and then try to parse the JSON client side?
I've found several good resources on how to get going with Marionette but none on how to handle data nested several layers deep.
Thanks /Magnus
Edit:
Showing some code illustrating what I've been testing
(Views)
var LineItemDetailView = Backbone.Marionette.ItemView.extend({
template: "#lineitem-layout-template",
tagName: "div",
initialize: function() {
}
});
var LineItemView = Backbone.Marionette.CompositeView.extend({
template: "#lineitem-wrapper-template",
childView: LineItemDetailView,
childViewContainer: "div",
initialize: function(coll, obj) {
this.collection = new LineItemCollection({url: "api/v1/lineitem/?order__id=" + obj["order_id"]});
this.collection.fetch({
success: function() {
console.log("Successfully fetched lineitems");
}
});
}
});
var OrderDetailView = Backbone.Marionette.CompositeView.extend({
template: "#order-detail-template",
childView: LineItemView,
childViewContainer: "#lineitems",
initialize: function() {
this.model.on("sync", function(mod) {
lineitemView = new LineItemView([],{order_id: mod.get("id")});
});
}
});
Something along those lines. OrderDetailView is created from my controller and passed the OrderModel. I from this I get OrderDetailView:s template to render and the LineItemCollection is fetched from server but nothing more happens.
回答1:
So I ran into this when creating a survey portion of an app the other day. It had a structure like this: Survey: Question: Answer Answer Question: Answer Answer
So pretty similar to what you're doing. I used the backbone-relational gem - http://backbonerelational.org/ to relate the models together and it worked great. My API sends back all of the JSON in a single call. So surveys/1.json brings back all of the above pieces/their data. Then I parse/break them up with Backbone relational. Here's what they look like:
Survey:
class Entities.Survey extends App.Entities.Model
urlRoot: "surveys"
defaults:
status: "Draft"
number_taken: 0
survey_limit: 500
relations: [
type: Backbone.HasMany
key: "questions"
relatedModel: Entities.Question
reverseRelation:
key: 'survey'
includeInJSON: 'id'
]
Question:
class Entities.Question extends App.Entities.Model
urlRoot: "questions"
defaults:
single_response: true
terminate: false
free_text: false
relations: [
type: Backbone.HasMany
key: "answers"
relatedModel: Entities.Answer
reverseRelation:
key: 'question'
includeInJSON: 'id'
]
Answer:
class Entities.Answer extends App.Entities.Model
urlRoot: "answers"
defaults:
branching: false
next_question_id: null
Then when you go to display them, in my survey display view I have a layout view that has a question region which uses a composite view of the survey questions like this:
class Show.Controller extends App.Controllers.Application
initialize: (options) ->
{ survey, id } = options
survey or= App.request "survey:entity", id
App.execute "when:fetched", survey, =>
@layout = @getLayoutView()
@listenTo @layout, "show", =>
@panelRegion survey
@questionRegion survey
@bannerRegion survey
@show @layout
questionRegion: (survey) ->
App.request "show:survey:questions", survey, @layout.questionRegion
Then I come in and get the questions:
questionRegion: (survey) ->
questions = survey.get('questions')
questionView = @getQuestionView questions, survey
The childview of the Questions CompositeView is itself a CompositeView with a childview of answers.
So Survey has a Questions CompositeView of Questions, each of which is a CompositeView of Answers.
You should be able to follow a similar structure with your app. Let me know if you get stuck anywhere!
Edit: Adding View/Controllers.
So here's what I do, when the user navigates to a certain route - say localhost:3000/#surveys/1/edit it hits my surveysrouter (note some code like the list piece I stripped out):
@TheoremReach.module "SurveysApp", (SurveysApp, App, Backbone, Marionette, $, _) ->
class SurveysApp.Router extends Marionette.AppRouter
appRoutes:
"surveys" : "list"
"surveys/:id" : "show"
"surveys/:id/take": "take"
API =
show: (id, survey) ->
new SurveysApp.Show.Controller
id: id
survey: survey
take: (id, survey) ->
new SurveysApp.Take.Controller
id: id
survey: survey
App.vent.on "survey:clicked", (survey) ->
App.navigate "surveys/" + survey.id
API.show survey.id, survey
App.vent.on "take:survey:button:clicked", (survey) ->
App.navigate "surveys/" + survey.id + "/take"
API.take survey.id, survey
App.addInitializer ->
new SurveysApp.Router
controller: API
So I can get here when navigating or by triggering the "survey:clicked" event. This then creates my show controller:
@TheoremReach.module "SurveysApp.Show", (Show, App, Backbone, Marionette, $, _) ->
class Show.Controller extends App.Controllers.Application
initialize: (options) ->
{ survey, id } = options
survey or= App.request "survey:entity", id
App.execute "when:fetched", survey, =>
@layout = @getLayoutView()
@listenTo @layout, "show", =>
@panelRegion survey
@questionRegion survey
@bannerRegion survey
@show @layout
questionRegion: (survey) ->
App.request "show:survey:questions", survey, @layout.questionRegion
panelRegion: (survey) ->
panelView = @getPanelView survey
@listenTo panelView, "new:question:clicked", (args) ->
question = App.request "new:question:entity"
model = args.model
model.get('questions').add(question)
question.set(survey_id: model.get('id'))
App.request "new:question:added"
@show panelView, region: @layout.panelRegion
bannerRegion: (survey) ->
bannerView = @getBannerView survey
@listenTo bannerView, "take:survey:button:clicked", (args) ->
App.vent.trigger "take:survey:button:clicked", args.model
@show bannerView, region: @layout.bannerRegion
getLayoutView: ->
new Show.Layout
getBannerView: (survey) ->
new Show.Banner
model: survey
getPanelView: (survey) ->
new Show.Panel
model: survey
This makes a new Questions Show Controller (same router case as above that handles "show:survey:questions"
request and instigates a new controller so I'll skip that code).
@TheoremReach.module "QuestionsApp.Show", (Show, App, Backbone, Marionette, $, _) ->
class Show.Controller extends App.Controllers.Application
initialize: (options) ->
{ survey } = options
@layout = @getLayoutView()
@listenTo @layout, "show", =>
@questionRegion survey
@show @layout
questionRegion: (survey) ->
questions = survey.get('questions')
questionView = @getQuestionView questions, survey
App.reqres.setHandler "new:question:added", ->
questionView.render()
@show questionView, region: @layout.questionRegion
getLayoutView: ->
new Show.Layout
getQuestionView: (questions, survey) ->
new Show.Questions
collection: questions
model: survey
Standard composite view for the questions:
class Show.Questions extends App.Views.CompositeView
template: "questions/show/_questions"
className: "questions"
itemViewContainer: ".editor"
itemView: Show.Question
Then each question is a composite view:
class Show.Question extends App.Views.CompositeView
template: "questions/show/_question"
id: "1000"
className: "step"
initialize: ->
@collection = @model.get("answers")
@model.set(question_number: @model.collection.indexOf(@model) + 1)
if @model.get('free_text') and @model.get('answers').length < 1
answer = App.request "new:answer:entity"
answer.set(free_text: true, question: @model, title: @model.get('title'))
@collection.reset(answer, {silent: true})
@on "childview:answer:delete:clicked", (child, args) =>
args.collection = @model.get('answers')
@trigger "answer:delete:clicked", args
itemView: Show.Answer
itemViewContainer: ".answer-container"
It gets its collection from the answers group from backbone relational. I would note though that this probably should just be a layout and in the initialize function I should send a request to the answers app to get a list of answers and add those to the answer region. I just haven't gotten around to that yet :).
来源:https://stackoverflow.com/questions/25602499/construct-views-for-data-nested-several-levels-deep