Construct views for data nested several levels deep

╄→尐↘猪︶ㄣ 提交于 2020-01-06 20:36:40

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!