Adding new models to a backbone collection, not replace - javascript

I am trying to add new models to a collection (i'm not saving to the server this time, just doing this in memory). I have the code below:
$(function () {
//Model
var Story = Backbone.Model.extend({
defaults: {
'title': 'This is the title',
'description': 'Description',
'points': 0
},
url: '/'
});
//Collection
var Stories = Backbone.Collection.extend({
model: Story,
url: '/'
});
//View
var BaseView = Backbone.View.extend({
el: $('#container'),
events: {
'click .inner': 'onClickInner'
},
onClickInner: function() {
this.options.x++;
this.story.set({
'title': 'This is my collection test ' + this.options.x,
'description' : 'this is the description'
});
this.stories.add(this.story);
this.render();
},
initialize: function () {
this.stories = new Stories();
this.story = new Story();
},
render: function(){
console.log(this.stories);
}
});
//Initialize App
var app = new BaseView({
'x' : 0
});
});
My question is this, for each time 'onClickInner' runs, I want to add a new model to the collection. However, in my code it replaces the existing model in the collection. How do I add new models and not replace?
Thanks for your time.

It happens because you update current model instead of adding new new one. To fix it you have to just execute add method on your collection. This method adds passed data as a new model to your collection:
this.stories.add({
'title': 'This is my collection test ' + this.options.x,
'description' : 'this is the description'
});

Related

Updating collection and view in Backbonejs

I've created a search bar, but when the data is gathered from the user, it displays the default data over again rather then the users new search criteria.
I'm resetting the collection and giving it a new URL when the user searches, but it doesn't seem to update correctly, and I'm having trouble figuring out where my problem(s) are.
(function(){
'use strict';
var red = red || {};
//model////////////////////////////////////////////////
red.RedditModel = Backbone.Model.extend({
defaults: {
urlTarget: $('#textBox').val(),
urlStart: 'https://www.reddit.com/r/',
urlEnd: '.json'
},
initialize: function() {
this.on('change:urlTarget', function() {
console.log('The Url Target has changed to ' + this.get("urlTarget"));
});
this.on('change:concatURL', function() {
console.log('The model Url has changed to ' + this.get("concatURL"));
});
this.on('change:url', function() {
console.log('The collection url has changed to: ' + this.get('url'));
});
}
});
var redditModel = new red.RedditModel();
var fullURL = new red.RedditModel({
concatURL: redditModel.attributes.urlStart + redditModel.attributes.urlTarget + redditModel.attributes.urlEnd
});
var listElmement,
$list = $('.list');
//collections//////////////////////////////////////////
red.redditCollection = Backbone.Collection.extend({
model: red.RedditModel,
url: fullURL.attributes.concatURL,
parse: function(response) {
var redditData = response.data.children;
return redditData;
}
});
//view////////////////////////////////////
red.RedditView = Backbone.View.extend({
model: fullURL,
collection: redditCollection,
el: '.searchBar',
events: {
'click .searchButton': function(e) {
this.updateModel(e);
this.updateCollection(e);
},
'change #textBox': 'initialize'
},
updateModel: function() {
this.$urlTarget = $('#textBox').val()
this.model.set('urlTarget', this.$urlTarget);
this.model.set('concatURL', redditModel.attributes.urlStart + this.$urlTarget + redditModel.attributes.urlEnd);
},
updateCollection: function() {
this.collection.reset();
this.$urlTarget = $('#textBox').val();
var newUrl = redditModel.attributes.urlStart + this.$urlTarget + redditModel.attributes.urlEnd;
this.collection.add({ urlTarget: this.$urlTarget });
this.collection.add({ url: newUrl });
console.log(newUrl);
},
tagName: 'li',
className: 'listItems',
initialize: function() {
$list.html('');
this.collection.fetch({
success: function(redditData) {
redditData.each(function(redditData) {
redditData = redditData.attributes.data.title
listElmement = $('<li></li>').text(redditData);
$list.append(listElmement);
})
}
});
},
render: function() {
}
});
var redditCollection = new red.redditCollection({
redditModel,
fullURL
});
var myRedditView = new red.RedditView({
model: redditModel,
collection: redditCollection
});
$('.page').html(myRedditView.render());;
})();
Parse within the model, and use it for its intended purpose. No need to store the reddit url and other search related info in a model.
red.RedditModel = Backbone.Model.extend({
parse: function(data) {
return data.data;
},
})
Since you already take care of the reddit url here. Don't be afraid to make yourself some utility functions and getters/setters in your Backbone extended objects (views, model, collection, etc).
red.RedditCollection = Backbone.Collection.extend({
url: function() {
return 'https://www.reddit.com/r/' + this.target + this.extension;
},
initialize: function(models, options) {
this.extension = '.json'; // default extension
},
setExtension: function(ext) {
this.extension = ext;
},
setTarget: function(target) {
this.target = target;
},
parse: function(response) {
return response.data.children;
}
});
Don't be afraid to have a lot of views, Backbone views should be used to wrap small component logic.
So here's the item:
red.RedditItem = Backbone.View.extend({
tagName: 'li',
className: 'listItems',
render: function() {
this.$el.text(this.model.get('title'));
return this;
}
});
Which is used by the list:
red.RedditList = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.listenTo(this.collection, 'sync', this.render);
},
render: function() {
this.$el.empty();
this.collection.each(this.renderItem, this);
return this;
},
renderItem: function(model) {
var view = new red.RedditItem({ model: model });
this.$el.append(view.render().el);
}
});
And the list is just a sub-component (sub-view) of our root view.
red.RedditView = Backbone.View.extend({
el: '.searchBar',
events: {
'click .searchButton': 'onSearchClick',
},
initialize: function() {
// cache the jQuery element for the textbox
this.$target = $('#textBox');
this.collection = new red.RedditCollection();
this.list = new red.RedditList({
collection: this.collection,
// assuming '.list' is within '.searchBar', and it should
el: this.$('.list'),
});
},
render: function() {
this.list.render();
return this;
},
onSearchClick: function(e) {
this.collection.setTarget(this.$target.val());
console.log(this.collection.url());
this.collection.fetch({ reset: true });
},
});
Then, you only need the following to use it:
var myRedditView = new red.RedditView();
myRedditView.render();
Notice the almost non-existent use of the global jQuery selector. If you're using Backbone and everywhere you're using $('#my-element'), you're defeating the purpose of Backbone which is, in part, to apply MVC concepts on top of jQuery.
Some notes on the code posted
Take time to understand what's going on. There are several lines of code in your question that doesn't do anything, or just don't work at all.
Though it's been removed in your answer, the following doesn't make sense because the collection constructor is Backbone.Collection([models], [options]) and what you have here translates to passing an options object (using ES6 shorthand property names { a, b, c}) to the models parameter.
var redditCollection = new red.redditCollection({
redditModel,
fullURL
});
This line does nothing, because .render() doesn't do anything and doesn't return anything.
$('.page').html(myRedditView.render());
Here, you're creating a new element manually using jQuery while you have Backbone which does this for you.
$('<li></li>').text(redditData);
Don't use the attributes directly, always use .get('attributeKey') unless you have a good reason not to.
redditModel.attributes.urlStart
Favor local variables whenever you can. The listElement var here is defined at the "app" level without a need for it.
listElmement = $('<li></li>').text(redditData);
$list.append(listElmement);
A Backbone collection is automatically filled with the new instances of models on success. You do not need to re-parse that in the success callback (in addition to the ambiguity with redditData).
this.collection.fetch({
success: function(redditData) {
redditData.each(function(redditData) {
redditData = redditData.attributes.data.title;
I don't mean to be rude and I took the time to write that long answer to try to help, you, and any future reader that comes by.

Backbone router.navigate() giving Failed to execute 'pushState' on 'History' error

I'm trying to set up links and routing with Backbone (this is my first Backbone app). In particular, I want a link of the form /restaurants/:id to trigger the show route.
This is my code:
var App = {
Models: {},
Views: {},
Collections: {}
};
// RESTAURANT SCHEMA
// name
// type
// rating (average) - virtual attribute
// points
// ratings
App.Models.Restaurant = Backbone.Model.extend({
urlRoot: '/restaurants',
defaults: {
points: 0,
ratings: 0
},
updateRating: function(points) {
this.set({points: points});
this.set({ratings: this.get('ratings') + 1});
this.rating.set({
rating: this.get('points') / this.get('ratings')
});
this.save(); // PUT /restaurants/:id PUT if model exists, POST if not
}
});
App.Collections.Restaurants = new (Backbone.Collection.extend({
model: App.Models.Restaurant,
url: '/restaurants'
}))();
App.Views.Restaurant = Backbone.View.extend({
template: _.template(
'<div class="page-header"><h1><%= name %></h1></div>' +
'<p>Type: <%= type %></p><br />' +
'<label>Enter rating: </label>' +
'<input type="number" class="form-control" min="1" max="5">'
),
events: {
'change input[type=number]': 'updateRating'
},
updateRating: function() {
var points = this.$el.$(); // TODO
this.model.updateRating(points);
},
render: function() {
var attributes = this.model.toJSON();
this.$el.html(this.template(attributes));
}
});
App.Views.Restaurants = Backbone.View.extend({
template: _.template(
'<div class="page-header"><h1>Restaurants</h1></div>' +
'<ul>' +
'<% App.Collections.Restaurants.forEach(function(restaurant){ %>' +
'<li><%= restaurant.get("name") %></li>' + // using cid's like this doesn't seem right. I think I need to get the id after saving to the database, but I haven't done that yet.
'<% }); %>' +
'</ul>'
),
render: function() {
this.$el.html(this.template());
},
events: {
'click a': function(e) {
e.preventDefault();
App.Router.navigate(e.target.pathname, {trigger: true});
}
}
});
App.Router = Backbone.Router.extend({
routes: {
"restaurants": "index",
"restaurants/:id": "show",
"restaurants/new": "new",
"restaurants/:id/edit": "edit"
},
initialize: function() {
console.log('initialize called');
var PicolaBusala = new App.Models.Restaurant({
name: "Picola Busala",
type: "Italian"
});
var Benihanna = new App.Models.Restaurant({
name: "Benihanna",
type: "Asian"
});
var LemonLeaf = new App.Models.Restaurant({
name: "Lemon Leaf",
type: "Thai"
});
var picolaBusala = new App.Views.Restaurant({model: PicolaBusala});
var benihanna = new App.Views.Restaurant({model: Benihanna});
var lemonLeaf = new App.Views.Restaurant({model: LemonLeaf});
App.Collections.Restaurants.add(PicolaBusala);
App.Collections.Restaurants.add(Benihanna);
App.Collections.Restaurants.add(LemonLeaf);
App.Views.restaurantsView = new App.Views.Restaurants({collection: App.Collections.Restaurants});
App.Views.restaurantsView.render();
$("#app").html(App.Views.restaurantsView.el);
},
start: function() {
console.log('start called');
Backbone.history.start({pushState: true});
},
index: function() {
console.log('index called');
App.Collections.Restaurants.fetch();
$("#app").html(App.Views.restaurantsView.el);
},
show: function(id) {
console.log('show called');
console.log('id: ', id);
},
new: function() {
},
edit: function() {
}
});
$(function() {
App.Router = new App.Router(); // because a) initialize() needs to be called once the DOM loads and b) App.Router needs to be instantiated for .navigate()
App.Router.start();
})
The particular error I get when I click the /restaurants/:id link is Uncaught SecurityError: Failed to execute 'pushState' on 'History': A history state object with URL 'file:///Users/adamzerner/code/getable_challenge/restaurants/c3' cannot be created in a document with origin 'null'.
What am I doing wrong?
The likely problem is that you're not running this on a server. You need to set up a local server using something like MAMP or WAMP or Node for example so you'll end up accessing your page through the browser at a location like localhost:8080. This will allow you to load local content like a JSON file.
If this doesn't solve your problem try taking a look at Javascript history.PushState not working?

Unable to get the Backbone View working for a simple example

I am trying to write a simple example using Backbone.js for study. Some how nothing gets printed in the browser. Need a little help here. The code is given below.
Html:
<div id="container">
<ul id="person-list">
</ul>
</div>
Models
var Person = Backbone.Model.extend({
defaults: {
id: 0,
name: ''
}
});
var PersonStore = Backbone.Collection.extend({
model: Person,
url: 'api/person', //currently not using
initialize: function () {
console.log("Store initialize");
}
});
Views
var PersonView = Backbone.View.extend({
tagName: 'li',
initialize: function () {
_.bindAll(this, "render");
},
render: function () {
$(this.el).append(this.model.name) //model.name shows undefined here
return this;
}
});
var PersonListView = Backbone.View.extend({
el: $('#person-list'),
tagName:'ul',
initialize: function () {
_.bindAll(this, "render");
this.render();
},
render: function () {
self = this;
this.collection.each(function (person) { //name property undefined here on person
var personView = new PersonView({ model: person });
$(self.el).append(personView.render().el);
});
}
});
Sample Run
var persons = new PersonStore([
new Person({id:1, name: "Person 1"}),
new Person({ id: 2, name: "Person 2" }),
]);
new PersonListView({ collection: persons });
The above setup prints nothing(blank) on screen. I have struggled now for some time and need a little help here as to why the two Person's name does not get displayed in the browser.
To make your code work you have to replace
this.$el.append(this.model.name)
with
this.$el.append(this.model.get('name'))
Always use method .get() to access model properties.
Also i highly recommend you use templates for rendering views. This approach let you write .render() implementation once and will be no need to change it if you need visual changes, you can make in template

Using views in backbone.js

I'm currently learning backbone.js and have a little problem. I dont' quite get how the view works.
I have created a model, a collection, and another model that again contains the collection:
Sensor = Backbone.Model.extend({
defaults: {
channel: '',
name: '',
temperature: 0,
tempMin: 0,
tempMax: 0
}
});
SensorList = Backbone.Collection.extend({
model: Sensor
});
Now I created a view, so I am able to render the sensor collection with handlebar.js template:
TemperatureView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function(eventName) {
var source = $('#sensor-list-template').html();
var template = Handlebars.compile(source);
var html = template(this.collection.toJSON());
this.$el.html(html);
} 
});
Now I want to load some data and render the information. But I don't know how to get the data into my view...I tried this:
$(document).ready(function() {
var temps = new TemperatureRequest();
temps.fetch({
success: function() {
console.log(temps);
var test = temps.get("sensors");
console.log(test);
var tempView = new TemperatureView({
collection: test
});
}
});
});
The data is fetched correctly. I have a collection of sensors. And now I want to pass them to the view so it is getting rendered....but I don't understand how this is done..pls help!
Since you are passing the collection to the view while creating it, you can access the same using this.collection inside your view anywhere.
var tempView = new TemperatureView({
collection: test
});
More over you have added the render function inside your initialize , it automatically calls the render function.Inside the render it fetches the collection and since your template needs only json object you are converting your collection it to json array objects.Templates takes care of appending the values to html.
If you want to add automatic view render to happen whenever the collection removes a model or adds a model into it you can add a listener and callback function to it
initialize : function(){
console.log("initializing view");
this.collection.on('add', this.render, this);
this.collection.on('reset', this.render, this);
this.render();
}
I just got it. Took me a while and I have definitly some reading to do.
There were several problems. First of all I have to overwrite the parse function, so the collection is stored correctly in my model:
TemperatureRequest = Backbone.Model.extend({
urlRoot: '/temperatures',
defaults: {
timestamp: '',
logfile: '',
sensorList: new SensorList()
},
parse: function(response) {
response.sensorList = new SensorList(response.sensors);
return response;
},
success: function(response) {
console.log('success');
}
});
In my view I know add the listen to events as suggested and also fetch the data within the initialize function to get rid of the success callback:
TemperatureView = Backbone.View.extend({
el: '#temperatures',
initialize: function() {
this.listenTo(this.model, 'reset', this.render);
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'add', this.render);
this.model.fetch();
},
render: function(eventName) {
var list = this.model.get('sensorList');
console.log(list.toJSON());
var source = $('#sensor-list-template').html();
var template = Handlebars.compile(source);
var html = template(list.toJSON());
this.$el.html(html);
this.renderTimestamp();
},
renderTimestamp: function() {
var tsText = $("<p></p>").addClass("text-right");
var timestamp = $("<div></div>").addClass("col-sm-4 col-sm-offset-8").append(tsText);
tsText.text(this.model.get('timestamp'));
$('#timestamp').append(timestamp);
}
});
now I can do this to render the data:
$(document).ready(function() {
var temps = new TemperatureRequest();
var tempsView = new TemperatureView({
model: temps
});
});
Instead of passing the collection to the view I pass the model to it and fetch the data inside of the initialize function.
What I still don't understand is when I have to use "this" and when I have to use _bindAll...

Uncaught TypeError: Object [object Window] has no method 'each' function

Hey guys here is my js file and I am taking error message about each function at line:24 and I do not know why I couldnt find whats wrong. I am just trying to see the list of items on the console.log panel but it does not even give me list on the html page.
(function() {
window.App = {
Models: {},
Collections: {},
Views: {}
};
window.template = function(id){
return _.template( $('#' + id).html() );
};
App.Models.Task = Backbone.Model.extend({});
App.Collections.Task = Backbone.Collection.extend({
model: App.Models.Task
});
App.Views.Tasks = Backbone.View.extend({
tagName: 'ul',
render: function(){
this.collection.each( this.addOne, this);
return this;
},
addOne: function(task){
//creating new child view
var taskView = new App.Views.Task({ model: task });
//append to the root element
this.$el.append(taskView.render().el);
}
});
App.Views.Task = Backbone.View.extend({
tagName: 'li',
template: template('taskTemplate'),
events: {
'click .edit': 'editTask'
},
editTask: function(){
alert('you are editing the tas.');
},
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html(template);
return this;
}
});
var tasksCollection = new App.Views.Task([
{
title: 'Go to the store',
priority: 4
},
{
title: 'Go to the mall',
priority: 3
},
{
title: 'get to work',
priority: 5
}
]);
var tasksView = new App.Views.Tasks({ collection: tasksCollection });
$('.tasks').html(tasksView.render().el);
})();
You're creating a view instance as though it was a class:
App.Views.Tasks = Backbone.View.extend({ /* ... */ });
var tasksCollection = new App.Views.Task([
{
title: 'Go to the store',
priority: 4
},
//...
and then you create another instance of that view and hand it tasksCollection as though it really was a collection:
var tasksView = new App.Views.Tasks({ collection: tasksCollection });
But views and collections are different things and only collection's have an each method (unless you add an each to your view of course).
You want to create tasksCollection as an App.Collections.Task:
var tasksCollection = new App.Collections.Task([
{
title: 'Go to the store',
priority: 4
},
//...
Hi this is happening cus your each method not able to find the collection. As well the singular Task to Tasks
At this line:
Change this
var tasksCollection = new App.Views.Task([
TO, this:
var tasksCollection = new App.Collections.Tasks([

Categories