简体   繁体   中英

Backbone: Use Model's Data and Functions in View

I'm fairly new to Backbone and was wondering how to access a model's data and functions from a view that injects the model as a dependency.

My model looks like this:

countries.coffee

define [
  'underscore'
  'backbone'
  'parse'
], (_, Backbone, Parse) ->
  'use strict';

  class CountriesModel extends Parse.Object

    countries: ['GB','US','FR','JP','WL','ZM','NG']

    returnCode = (code) ->
      return code

And my view looks like this:

country.coffee

define [
  'jquery'
  'underscore'
  'backbone'
  'templates'
  'models/projects'
  'models/countries'
], ($, _, Backbone, JST, CountriesModel, ProjectModel) ->
  class CountryView extends Backbone.View

    ...

    console.log countries

    returnCode(4)

I'm injecting the CountriesModel as a dependency, but when I call the function or log the countries I get the following error:

Uncaught ReferenceError: returnCode is not defined

I can't work out what I'm doing wrong. Any help is appreciated. Thanks in advance!

UPDATE

I've updated the code above to provide a bit more context.

I'm trying to create a re-usable model ( CountriesModel ), so I can access that countries array and the returnCode function on different views across my app. But I can't work out how to access them on my CountryView .

My CountryView is already requiring a model ProjectModel , and I'm able to call functions and arrays from ProjectModel like this:

this.model.exampleArray
this.model.exampleFunction()

I can't work out how I call functions or arrays from my CountriesModel .

Anyone know what I'm doing wrong?

I think that in this particular case, it would be useful for you to create a model "countryModel" and a backbone collection "countriesCollection". But that might not be the nature of your question (your update indicates that you struggle with reusability of the model) so I won't take that into account in my answer.
I don't know coffeescript, but I'll elaborate using Javascript.
The answer is indeed technically to pass models via the options parameter to the view during instantiation.

I think that it is overall a good idea to use presenter objects to manage specific views groups. This object would instantiate views that are related, and as you mentioned it, allow injecting an instance of the countriesModel into this presenter.
Imagine hypothetically that you have a webapp that renders a map and list with places that require the model you describe for some reason. You could have code that looks like this:

var countriesModel = new CountriesModel(); 

var headerPresenter = new HeaderPresenter(); 
var mapPresenter = new MapPresenter(countriesModel); 
var listPresenter = new ListPresenter(countriesModel); 

What happens is that you instantiate the model only once, and inject the instance into the presenters that require it.
In the presenter object you can immediately access the properties / methods set on the passed model.

This approach allows you to quickly identify which presenters require the re-usable model.
Should you require the component in new presenters as well, it is easy to pass it in.
Then, within the presenter you can still choose to which views specifically you want to send the model.

Eg. list presenter:

function listPresenter(countriesModel){ 
    this.listView = new ListView({ "model": countriesModel); 
    //More views can be added with the same model instance 
}; 

Either from within the views or the presenters, you are able to listen to events on the model, execute its methods and re-render the views.
Personally I manage this logic from the presenter, because this is the place where I use other injected services and components to perform eg. server calls or specific calculations that might be common for different views. Handling this common logic for different views is easily done by passing an event aggregator to each of the view instances. The views trigger custom events to do what is required and the presenter listens to custom events, executes the required logic and (re-)renders views.
I prefer to keep the view clean and focused on DOM interaction / DOM event binding.

Sidenote : Backbone Marionette offers an app level event aggregator, which saves you the pain of passing in event aggregators to every view individually.
It is also a very convenient lib to render views from the presenter by using syntax like:

var someView = new SomeView(); 
var region = new Marionette.Region({ el: "#some-region" }); 
region.show(someView); 

Re-showing views by using Marionette regions is memory-safe.

Hopefully this is of any help.

TBH I don't really get why you are using your model as a deps. A good view should abstract from the model and the model should be attached to the view from the route.

// this goes in your view file
var CountryView = Backbone.view.extend({
    // here goes all the view logic to display the model data
    // you can refer to your model using this.model
})

...
// in the router file
var myModel = new CountriesModel();

var router = Backbone.router.extend({
    routes: {
        "": home,
        "home": home
    },
    home: function (){
       var view = new CountryView({ model: myModel});
       view.render(); //if you didn't rendered the view in the initialize method
    }
})

You can access another model in the view attaching it at runtime in the router

...
// in the router file
var myModel = new CountriesModel();
var anotherModel = new ProjectModel();


var router = Backbone.router.extend({
    routes: {
        "": home,
        "home": home
    },
    home: function (){
       var view = new CountryView({
                               model: myModel
                               anotherModel: anotherModel
                               });
       view.render(); //if you didn't rendered the view in the initialize method
    }
})

and in CountryView attach it in the initialize function

initialize: function (options){
    this = _.extend(options, this)
    // or
    // this.anotherModel = options.anotherModel
    // this.model = options.model
}

But you shoul not mix models in a view, just have another view for the projectModel and use them both when needed

// something like
var countryView = new CountryView({model: myModel});
var projectView = new ProjectView({model: anotherModel});

and render them together from the router

In your view you can instantiate models other than the one which is referenced at this.model . Eg:

var SomeView = Backbone.View.extend({

    initialize: function() {
        this.countriesModel = new CountriesModel();

        // Anywhere in your view do stuff with `this.countriesModel`. E.g:
        // this.listenTo(this.countriesModel, ...)
        // this.countriesModel.fetch()
        // etc
    }

});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM