-2

So I'm really new to backbone.js and I'm trying to understand the basic concept when it comes to getting parts(views/models/routes) to interact with other parts.

Here's an example. I have a 'screen' model object being rendered by a 'singleScreen' View to the page. I also have a 'sidebar' model and view being rendered. When i click on a link in the sidebar i want it to render a different screen model object and alter some of the HTML in a separate html div (the heading) according to the 'name' attribute i gave my screen model.

So first question, should all of the code for re-rendering a different view and changing the heading html be done in the routes.js file? It seems like that file would get pretty big pretty fast. If so how do i get a reference to the model object i want to render in the routes.js file so i can access things like myModel.name (which is instantiated in the app.js file)?

Do I handle the browser history and rendering of my view as separate things and add code for 'link is clicked, render this' functionality in my app.js file (the file where I instantiate all my objects)? If that's the case how does my app know what to render if a user tries to go directly to a view by typing in the URL, rather than clicking?

OR, and this is the most likely scenario as far as i can tell,

Do i use the initialize functions of my models/views to trigger/listenTo an event for when a link is clicked(or backbone.history() is changed?) and call render?

I've messed around with all 3 approaches but couldn't understand how to pass references of objects to other parts of the app, without just making those objects global variables (which feels so wrong).

For the last scenario, I messed around with events but everywhere I've read says you have to include a reference to the object that it's listening too, which seems to defeat the whole purpose of setting up an events object and listening for an event rather than just querying the state of a variable of that object...

eg.

this.listenTo(sidebarModel , "change:selected", this.render());

How do i pass a reference to sidebarModel object to the singleScreen view for it to know what it's meant to be listening to.

I'm not really looking for a coded answer, more so just an understanding of best practices and how things are meant to be done.I feel like I'm close but I know i'm missing/not understanding something which is why I'm not able to figure this out myself, so a little enlightening on the whole topic would be greatly appreciated. Thanks.

MattyB
  • 19
  • 1
  • 6

1 Answers1

1

First, you need to understand the role of each Backbone classes. Reading on MVC first might help.

Model

The model isn't necessary when rendering a view. Its role is to handle the data, which can be local, or from an API. All the functions that affect the data should be in the model, and a model shouldn't have anything related to rendering.

There are always exception, where you could use a model to handle data related only to rendering, but you'll know when you find such case.

A simple example is a book:

// The Book model class
var Book = Backbone.Model.extend({ 
    idAttribute: 'code',
    urlRoot: '/api/book'
});

// a Book instance
var solaris = new Book({ code: "1083-lem-solaris" });

Fetching from an API would call:

// GET http://example.com/api/book/1083-lem-solaris
solaris.fetch(); // this is async

When fetching, the API returns the JSON encoded data.

{
    "code": "1083-lem-solaris",
    "title": "Test title",
}

The attributes are merged with the existing attributes, adding the ones that are not there yet, and overwriting the values of the ones already there.

Collection

A collection's role is to manage an array of models, which, again, can be local or fetched from an API. It should contain only functions related to managing the collection.

var Library = Backbone.Collection.extend({
  model: Book,
  url: '/api/book'
});

var myLibrary = new Library();

// just adds an existing book to our array
myLibrary.add(solaris);

You can fetch a collection to get an array of existing books from an API:

myLibrary.fetch();

The API should return:

[
    { "code": "blah-code-123", "title": "Test title" }, 
    { "code": "other-code-321", "title": "Other book title" } 
]

Using the collection to create a new book and sync with the API:

var myNewBook = myLibrary.create({ title: "my new book" });

This will send a POST request with the attributes and the API should return:

{ "code": "new-code-123", "title": "my new book" }, 

View

The view handles its root DOM element. It should handle events from its DOM. It's best used to wrap small component and build bigger components from smaller components.

Put links directly in the templates, in the href of an anchor tag. There's no need to use events for that.

<a href="#my-route">link</a>`

Here's how I render (simplified) a list.

// book list item
var BookView = Backbone.View.extend({
    tagName: 'li',
    template: _.template('<a href="#book/<%= code %>"><%= title %></a>'),
    initialize: function() {
        // when the model is removed from the collection, remove the view.
        this.listenTo(this.model, 'remove', this.remove);
    },
    render: function() {
        this.$el.empty().append(this.template(this.model.toJSON()));
        return this;
    }
});

// book list
var LibraryView = Backbone.View.extend({
    template: '<button type="button" class="lib-button">button</button><ul class="list"></ul>',
    events: {
        'click .lib-button': 'onLibButtonClick'
    },

    initialize: function(options) {

        this.listenTo(this.collection, 'add', this.renderBook);
    },

    render: function() {
        // this is a little more optimised than 'html()'
        this.$el.empty().append(this.template);

        // caching the list ul jQuery object
        this.$list = this.$('.list');

        this.collection.each(this.renderBook, this);

        return this; // Chaining view calls, good convention http://backbonejs.org/#View-render
    },

    addItem: function(model) {
        // Passing the model to the Book view
        var view = new BookView({
            model: model
        });
        this.$list.append(view.render().el);
    },

    onLibButtonClick: function() {
        // whatever
    }

});

Router

The router handle routes and should be as simple as possible to avoid it getting too big too fast. It can fetch collections and models, or the view can handle that, it's a matter of pattern at that point.

var LibraryRouter = Backbone.Router.extend({
    routes: {
        '*index': 'index',
        'book/:code': 'bookRoute',
    },

    index: function() {
        var library = new LibraryView({
            el: 'body',
            // hard-coded collection as a simple example
            collection: new Library([
                { "code": "blah-code-123", "title": "Test title" }, 
                { "code": "other-code-321", "title": "Other book title" } 
            ])
        });
        library.render();
    },
    bookRoute: function(code) {
        var model = new Book({ code: code });
        // here, I assume an API is available and I fetch the data
        model.fetch({
            success: function() {
                var view = new BookPageView({
                    el: 'body',
                    model: model
                });
                view.render();
            }
        });
    }
});

Events

Everything in Backbone has the Events mixin, even the global Backbone object. So every class can use listenTo to bind callbacks to events.


It would be very long to go in depth for everything in Backbone, so ask questions and I'll try to extend my answer.

Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • Awesome, thanks for the explanation. Ok i think what I'm getting hung up on is the fact that I'm trying to create one instance of an object and pass it around, whereas what it looks like your meant to create a new instances and pass the values needed as parameters. (and i assume clean up the old instance if you no longer need it.) example where u create `var model = new Book({ code: code });` – MattyB Nov 04 '16 at 00:07
  • To explain why i'm getting hung up on it further, with the app i decided to try and create you could imagine it as knowing that your library will only ever have 4 books, and that you know all the attribute values for them already. So i was trying to create those 4 books in my app.js right at the beginning and then pass references around. But I'm starting to understand the best way to actually handle it is to create a new instance of a book model with the relevant data. Which means that trying to manipulate the DOM in something like routes.js based on data in my book model isn't possible. – MattyB Nov 04 '16 at 00:21
  • Ok so question then, If my view template for BookPageView had a call to get the name of the model `<%= name %>`, in your example, how would view.render(); in your routes have access to that information? Since the only thing you've passed in is the `code` when creating a new instance of book to render. – MattyB Nov 04 '16 at 00:25
  • @MattyB in my example, I assumed a fake API at `url: '/books'` and I use `fetch` to retrieve data that could look like `{ code: "123", title: "My title", etc. }` from the server. – Emile Bergeron Nov 04 '16 at 00:50
  • 1
    Thank you! I've been thinking about this whole thing as a localized application for some reason. But that makes so much sense now. So you create an instance of a model with the same idAttribute as an entry in your database, and call fetch to grab the needed data. Guess i need to get onto the learning how to save data to the database now. Thanks for all your help. – MattyB Nov 04 '16 at 01:13
  • @MattyB Backbone does the syncing really well, that's why I included that in my answer, but you could totally hard-code some data for testing purposes and call it a day. – Emile Bergeron Nov 04 '16 at 01:16
  • *"It would be very long to go in depth for everything in Backbone, so ask questions and I'll try to extend my answer."* - While I appreciate your helping mentality, please do not encourage questions like this, or forum like interaction. This will only encourage flood of poor questions in future. That's against the Stack Overflow format and policy. Instead, ask the OP to split their questions into multiple posts having a solid problem to solve. – T J Nov 07 '16 at 16:03
  • @TJ I did flag the question before answering. But sometimes, from poor questions comes great answers which makes SO a great source of information. – Emile Bergeron Nov 07 '16 at 16:16
  • Sometimes... when there is no well written documentation, books and tutorials elsewhere containing the same +information ;) – T J Nov 07 '16 at 16:20