Fetch and display rest data with Backbone / Marionette - angularjs

I'd need some help on fetching data from my server and displaying it using Marionette.
In Angular I'd do this:
index.html:
<body ng-app="app" ng-controller="AppCtrl as app">
<ul>
<li ng-repeat="person in app.people">
{{person.firstName}} {{person.lastName}}
</li>
</ul>
app.js:
var app;
app = angular.module("app", []);
app.controller("AppCtrl", function($http) {
app = this;
$http.get("http://localhost:3000/people").success(function(data) {
app.people = data;
});
});
and my server on my server (express):
var people = [
{
id: 1,
firstName: 'Bob',
lastName: 'Blob',
phoneNumber: '123'
}, {
id: 2,
firstName: 'Valdemar',
lastName: 'Ugh',
phoneNumber: '456'
}
];
app.get('/people', function(req, res) {
res.send(people);
});
Now, how would I get a similar result using Marionette?
I have a model (using coffeescript here in this example):
class Person extends Backbone.Model
I also have a collection:
class People extends Backbone.Collection
model: Person
url: '/people'
Then I'll do this:
people = new People
people.fetch
success: ->
console.log 'works ok'
return people
error: (data) ->
console.log 'no success'
console.log people
view = new Views model: people
What I get in console.log is:
works ok
People {length: 0, models: Array[0], _byId: Object, constructor: function, model: function…}
_byId: Object
length: 2
models: Array[2]
__proto__: ctor
Now, my question is how do I use this? In order to simply list people do I even need this collection or can I do it only with a model? How would I console.log the first name of all the contact on my list? And why does it show length: 0, models: Array[0] and yet there are 2 models??

The hints I can provide are:
Your JSON has a root. Parse the data the same as what you do in Angular.
class People extends Backbone.Collection
# ...
parse: (data) ->
data.people
When you want to show collection, use CollectionView or CompositeView, not View. And use the collection as option, not model.
class PeopleView extends Marionette.CollectionView
peopleView = new PeopleView
collection: people
There are still plenty of things to know about CollectionView and the collection. You can read the doc for details and practice by yourself. Be patient.

This wasn't created specifically as an answer for you, but check out this jsFiddle which is a barebones test of getting data from a REST api and displaying it in Backbone/Marionette - http://jsfiddle.net/tonicboy/5dMjD/.
The key concept you're missing is model events. In your view, you should bind a handler to the "reset" event of your collection. When the collection is fetched, this callback will be used to then render the data to your template. The collection is available from your view as this.collection.toJSON().
Here is the equivalent code from my Fiddle (although it's old code I was playing with while learning Backbone and not how I would do it today). I'll try to update my Fiddle to have more 'best practices' code.
myBook.bind('change', function (model, response) {
var view = new MainView({
el: $("#main"),
model: model
});
this.region.attachView(view);
this.region.show(view);
}, this);

Related

Backbone Marionette : Add a model (and render its view) to a nested Composite View

If you don't want to see the complete code, here is what I am trying to do.
I have multiple pages and each page has multiple tags. There is a composite View called PageManyView for rendering pages which called its childView PageView. Page View is a nested composite view which renders tags, passing this.model.get('tags') as collection.
Now I can easily add a new page by using pages.add(newPage). Here pages is the collection. I am facing problem in adding a new Tag. How can I do that. Please help.
CODE
var PageModel = Backbone.Model.extend({});
var PageCollection = Backbone.Collection.extend({
model: PageModel
});
My JSON at /data endpoint is coming like this
[
{
_id: '1', 'name': '1', info: 'Page 1',
tags: [{name:'main', color:'red'}, {name:'page', color:'blue'}]
},
{
_id: '1', 'name': '2', info: 'Page 2',
tags: [{name:'section', color:'blue'} {name:'about', color:'yellow'}]
}
]
I have created Nested Views in Marionette like this:
TagView = Marionette.ItemView.extend({
template: '#tagOneTemplate'
});
PageView = Marionette.CompositeView.extend({
template: '#pagesTemplate',
childViewContainer: 'div.tags',
childView: EntityViews.TagView,
initialize: function(){
var tags = this.model.get('tags');
this.collection = new Backbone.Collection(tags);
}
});
PageManyView = Marionette.CompositeView.extend({
template: '#pageManyTemplate',
childView: EntityViews.PageView,
childViewContainer: 'div#all-pages'
});
Now here is where i am facing problem. Inside Controller of my application, lets say if I have to add a new page
showPages: function(){
//Getting pages by using jQuery deferred
var view = PageMainView({collection:pages});
view.on("add:page", function(){
var newPage = Page({_id: 3});
pages.add(newPage);
});
}
Now this add function renders the new page automatically.
BUT I AM FACING PROBLEM IN ADDING a NEW TAG. HOW CAN I ADD A NEW TAG?
Finally it worked. Here is what I have done.
Step 1: Get Current model (page) from pages collection.
var currentpage = pages.get(pageid);
Step 2: Use Marionette BabySitter to get the view of the page where I want to insert a new tag.
var v = view.children.findByModel(currentpage);
Step 3: Add tag to v.collection. Since v is the View of the page where I want to insert new tag, v.collection returns the initialised tags collection
v.collection.add(tag);
This works for me. Let me know if I am wrong somewhere or a better way exists. Hope it helps.
this can be done quite easily by shifting around how your collection is being passed in. Instead of setting the collection on initialize in your compositeView, you should pass it in directly during instantiation. This way when you make a change to the collection from within your model, the compositeView will hear the "add" event on collection and add node automagically for you
For example it might look something like this.
PageView = Marionette.CompositeView.extend({
template: '#pagesTemplate',
childViewContainer: 'div.tags',
childView: EntityViews.TagView,
});
new PageView({
model: myModel,
collection: myModel.get("tags")
});
myModel.get("tags").add([{new: "object"}])

How does Chaplin.js handle passing a collection to a view?

I can create a simple model like so:
define(["models/base/model"], function(Model) {
"use strict";
var IssueModel = Model.extend({
defaults:{
lastName: "Bob",
firstName: "Doe"
}
});
return IssueModel;
});
And then from my controller I can do this:
this.model = new IssueModel();
And then when I create my view I can pass it my model like so:
this.view = new IssueView({model: this.model});
Finally, in my template I can successfully get properties on the model by doing this:
Hi {{firstName}} {{lastName}}
But when I define a collection using IssueModel and I try to pass the collection to my view (and not the model like I showed previously) I can't figure out how to reference the models in my Handlebars template:
var self = this;
this.collection = new IssueCollection();
this.collection.fetch({
success: function(collection) {
self.view = new IssueView({collection: collection});
console.log(self.collection);
},
error: function(collection, error) {
// The collection could not be retrieved.
}
});
I know fetch properly retrieves 5 models from my Parse.com backend because this is what I get on the console:
My question is this. I know Chaplin.js uses getTemplateData, but when I pass a model I don't have to do anything special in order to reference the properties in my view. How would I reference, specifically iterate, over the collection I passed to my view in my Handlebars template?
{{#each [Model in the collection I passed to the view]}}
{{title}}
{{/each}}
Chaplin will render a collection using a CollectionView, it's basicly an extention of a normal view that listens for changes in your collection and adds/removes subviews accordingly.
this.view = new IssueCollectionView({collection: this.collection});
Also there is no need to wait for success call when using a collection view since it will automaticly render every child item when data is added.

backbone-relational: usage with standalone model without relations

I am using backbone-relational. The usage for a onetomany model works fine.
But I am having troubles using backbone-relational for a single/standalone model:
window.BeerOnlineShopPosition = Backbone.RelationalModel.extend({
urlRoot:"../api/beeronlineshoppositions",
idAttribute: 'id',
relations: [
],
defaults:{
"id":null,
"position_amount":""
}
});
window.BeerOnlineShopPositionCollection = Backbone.Collection.extend({
model:BeerOnlineShopPosition,
url:"../api/beeronlineshoppositions"
});
in main i have:
beeronlineshopproductDetails:function (id) {
var beeronlineshopproduct = BeerOnlineShopProduct.findOrCreate(id);
beeronlineshopproduct.fetch();
var beeronlineshopproduct_view = new BeerOnlineShopProductView({el: $('#content'), model: beeronlineshopproduct});
},
So, when jump to an existing records (...beeronlineshop/beeronlineshopproducts/#4), nothing happens. But debugging shows, that the fetch is executed and the view gets loaded. but the view is not rendered somehow.
When I refresh (f5), the view gets rendered correctly.
As mentioned the whole thing works for one-to-many model, so my question is:
did i make some trivial syntax-error on the model part?... or is there any other obvious reason for the troubles i have?
It may be because of the asynchronous request created by findOrCreate. beeronlineshopproduct.fetch() is making a request to the server but the view is being rendered before the server returns with a response.
You have two options. You can pass in the rendering of the view as a callback upon fetch's success like so:
beeronlineshop.fetch({
success: function() {
var beeronlineshopproduct_view = new BeerOnlineShopProductView({el: $('#content'), model: beeronlineshopproduct})
});
Or you can pass an initializer in your BeerOnlineShopProductView that listens to the its model syncing with the server, and calls for the view to re-render itself, like so:
window.BeerOnlineShopProductView = Backbone.View.extend({
initialize: function() {
this.listenTo (this.model, "sync", this.render)
},
})

Cannot access all backbone model attributes from template

The problem I am trying to debug is that, in my backbone view, all the model attributes (name, id, email etc) are available, (I have console.log() the model attributes to verify this), however, when I render the view passing the model, only the name attribute is available in the template, all other attributes are undefined,please is there any thing I am missing, as this is my first backbone application and I have spent hours trying to debug this and have gone through many tutors online and my code seems correct, thanks
//BACKbone view method(I have verified and all the model values are available here)
var profileView=Backbone.View.extend({
el:$('#content'),
initialize:function(){
this.model.bind('change',this.render,this);
},
render:function(){
var self=this;
this.$el.html(_.template(profileTemplate,this.model.toJSON()));
}
});
//HTML TEMPLATE (profileTemplate), only the name attribute is available in the template, //browser gives an error of 'undefined' for all other attributes except the name attribute
<h1><%=name.first%> <%=name.last%> </h1>//displays correctly
<%=email%> //undefined
//SCHEMA
var AccountSchema=new mongoose.Schema({
email:{type:String,unique:true},
password:{type:String},
name:{
first:{type:String},
last:{type:String},
full:{type:String}
},
});
//IT IS SOLVED NOW, IT happened because I executed the fetch command on the model after calling the render method instead of before calling the render method
This is my router, I call the model.fetch() from the router after the view is created. The problem stopped when I called model.fetch() before the rendering of the before
define([''views/profile''],function(,ProfileView){
var router = Backbone.Router.extend({
currentView: null,
socketEvents: _.extend({}, Backbone.Events),
routes: {
'addcontact': 'addcontact',
'index': 'index',
'login': 'login',
'register': 'register',
'forgotpassword': 'forgotpassword',
'profile/:id': 'profile',
'contacts/:id': 'contacts',
"upload/:id":"upload"
},
profile: function(id) {
var model = new Account({id:id});
console.log('profile user:'+id);
this.changeView(new ProfileView({model:model})); //View is created here
model.fetch(); // model is fetched after view is rendered, cause for the problem in my case
}
});
return new router();
});

Representing existing HTML as Backbone.js data structures in CoffeeScript

I'm having a rough time wrapping my head around this.
I have an HTML list, and I want to use Backbone.js to handle events on those list items. Here's what I've got so far. This is a simplified scenario to help me better understand how to structure a larger application. For my example, I simply want to ingest an existing HTML list into the Backbone structure, and handle click events through the Backbone view.
I'm getting an error related to using #model in the view, but I'm fairly certain I'm misunderstanding things conceptually here.
CoffeeScript:
$ ->
class Item extends Backbone.Model
name: null
class ItemList extends Backbone.Collection
model: Item
class ItemView extends Backbone.View
tagName: 'li'
initialize: =>
#model.bind('change', this.render)
#model.view = this
events:
'click' : 'clicked'
clicked: ->
console.log 'clicked'
render: =>
this
class ItemListView extends Backbone.View
el: $('ul#test')
initialize: =>
$('li', #el).each(#addItem)
addItem: (item) ->
item = new ItemView({ el: item })
render: =>
this
Items = new ItemListView
HTML:
<ul id="test">
<li>Hi thar</li>
<li>Yeah</li>
<li>OK</li>
</ul>
Here's a jsfiddle I started earlier: http://jsfiddle.net/Saxx4/
I never really like CoffeeScript (Javascript is so nice, why replace it?), but it looks like there are a few issues here:
You're getting an error on #model because you never set it on the ItemView. This doesn't happen automatically - you have to either instantiate the view's model in initialize() or pass it into the constructor, e.g.:
addItem: (item) ->
model = new ItemView({
el: item,
model: new Item({
// assuming you might want the list item text
// in the model data
text: $(item).text()
})
})
You usually just want to specify a selector in el, not a jQuery object - otherwise the DOM might not be ready when you load your Backbone code: el: '#test'
You need to pass an options object to the ItemListView constructor, not just a single argument, no matter what you do in initialize():
class ItemListView extends Backbone.View
initialize: (opts) =>
opts.items.each(#addItem)
// ...
Items = new ItemListView({ items: $('ul#test li') })

Resources