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...
Related
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.
I've been working through Code School's Anatomy of Backbone.js course, but am confused when trying to save model changes back to the server. Perhaps you can help.
This is what I understand needs to happen:
Populate collection from a JSON data source using fetch();
Append the collection to the DOM
Edit a model (uncheck checkbox, which sets 'favourite' to false)
Save the model.
My assumption is that if I were to unselect a record as a 'favourite' then hit refresh, the change would be persistant and also evident in the JSON file. However, this isn't the case and the original collection is loaded and JSON is unchanged.
I think my confusion is in using the fetch method and declaring the URL within the model and collection.
How can I get this model change to be persistant?
Model:
var Contact = Backbone.Model.extend({
url: '/contacts',
defaults:{
favourite: false
},
toggleFavourite: function(){
if(this.get('favourite') === false)
{
this.set({ 'favourite': true });
} else {
this.set({ 'favourite': false })
}
this.save();
}
});
Collection
var Contacts = Backbone.Collection.extend({
model: Contact,
url: '/contacts'
});
Views
var ContactView = Backbone.View.extend({
className: 'record',
template: _.template('<span><%= name %></span>' +
'<span class="phone-number"><%= phone %></span>' +
'<input type="checkbox" <% if(favourite === true) print("checked") %>/>'),
events: {
'change input': 'toggleFavourite',
'click .phone-number': 'dial'
},
initialize: function(){
this.model.on('change', this.render, this);
},
toggleFavourite: function(e){
this.model.toggleFavourite();
},
dial: function(e){
alert('Dialing now...');
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var ContactsView = Backbone.View.extend({
initialize: function(){
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
},
addOne: function(contact){
var contactView = new ContactView({ model: contact });
this.$el.append(contactView.render().el);
},
addAll: function(){
this.collection.forEach(this.addOne, this);
},
render: function(){
this.addAll();
}
});
App.js
var contacts = new Contacts(); //creates list
contactsView = new ContactsView({ collection: contacts}); //creates list view
contacts.fetch({url: 'contacts/data.json'}); //populates list
$('#mainPanel').append(contactsView.el); //appends list to DOM
Backbone works on client, and can't change file on server itself.
You need to store dynamic data somewhere on server (maybe mongodb if you use json it will be easier).
contacts/data.json named static file. because it is not changing while you did't owerwrite it on the server.
I've looked everywhere for an answer but wasn't satisfied with what I've found.
The issue is, I'm doing a tutorial from Addy Osmani to make a 'Todo' app in Backbone, but when I look at the console, I get an error saying that this.model is undefined.
I even tried this SO answer Backbone model error displayed in console, but I still get the same error. Please tell me what is wrong.
By the way, what are this.model or this.collection? I've got an idea that they refer to Backbone.Model and Backbone.Collection but how do they work? I'm asking this because in another tutorial this.collection and this.model.models were also undefined, when I've clearly defined the Model and Collection.
Many Thanks
JS:
//Model
var Todo = Backbone.Model.extend({
defaults: {
title: 'Enter title here',
completed: true
},
validate: function(attrs) {
if (attrs.title === undefined) {
return 'Remember to enter a title';
}
},
initialize: function() {
console.log('This model has been initialized');
this.on('change:title', function() {
console.log('-Title values for this model have changed');
});
this.on('invalid', function(model, error) {
console.log(error);
});
}
});
//View
var TodoView = Backbone.View.extend({
el: '#todo',
tagName: 'li',
template: _.template($('#todoTemplate').html()),
events: {
'dbclick label': 'edit',
'click .edit': 'updateOnEnter',
'blur .edit': 'close'
},
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.input = this.$('.edit');
console.log(this.model.toJSON());
return this;
},
edit: function() {
//do something...
},
close: function() {
//do something...
},
updateOnEnter: function() {
//do something...
}
});
var todoview = new TodoView();
console.log(todoview.el);
//Collection
var TodoList = Backbone.Collection.extend({
model: Todo
});
You need to instantiate a Model or Collection and pass it to your View. Otherwise, when the render method is called on your TodoView, this.model will be null.
For example, try rearranging the last few lines of your code like this:
//Collection
var TodoList = Backbone.Collection.extend({
model: Todo
});
var todos = new TodoList();
var todoview = new TodoView({model: todos});
From that point onward, you can modify todos (which is a Collection) and your view can listen to todos' events and re-render accordingly.
The answer in the other question is the answer to your question: you're not passing the model to the view when you instantiate the view.
var model = new Todo();
var todoview = new TodoView({model: model});
When you pass an object to a view's constructor, it looks for certain keys and attaches them directly to the view.
You can see which by looking at Backbone's source and searching for viewOptions.
That's how you get the this.model and this.collection automatically attached to the view's this.
You didn't say, but I assume the error you are getting is occurring in the render() method.
Your problem is that you define a new type of model (var Todo = Backbone.Model.extend({...) however you never instantiate it, nor do you pass the model to the todoview constructor.
So at the very least you need to do:
var todomodel = new Todo();
var todoview = new TodoView({
model: todomodel
});
I have a web application using BackboneJS. In this application, I have a LayoutView.js file in which there is a Backbone View (called LayoutView). LayoutView has other functions (methods) that call other views. I am fetching some data in the initialize function of LayoutView, and I need to get this same data (model) in another view and work (update/delete) on it. Below is how I am passing data from LayoutView to myView:
var LayoutView = Backbone.View.extend({
el: $("#mi-body"),
initialize: function () {
var that = this;
this.ConfigData = new Configurations(); //Configurations is a collection
this.ConfigData.fetch({
success: function () {
alert("success");
},
error: function () {
alert("error");
}
});
this.render();
Session.on('change:auth', function (session) {
var self = that;
that.render();
});
},
render: function () {
// other code
},
events: {
'click #logout': 'logout',
'click #divheadernav .nav li a': 'highlightSelected'
},
myView: function () {
if (Session.get('auth')) {
this.$el.find('#mi-content').html('');
this.options.navigate('Myview');
return new MyLayout(this.ConfigData);
}
}
});
Still, I do not know how to "get"/access this data as my current data/model/collection (I am not sure which term is correct) in myView and work on it using Backbone's "model.save(), model.destroy()" methods. Also, whenever an edit/delete happens, the data of ConfigData should be modified and the update should reflect in the html displayed to the user.
Below is the code from MyView:
var MyView = Backbone.View.extend({
tagName: 'div',
id: "divConfigurationLayout",
initialize: function (attrs) {
this.render();
},
render: function () {
var that = this;
},
events: {
"click #Update": "update",
"click #delete": "delete"
},
update: function(){
//code for updating the data like model.save...
},
delete: function(){
//code for deleting the data like model.destroy...
}
});
Now the data I passed is in attrs in the initialize function. How to get this done..?
The syntax for instantiating a Backbone view is new View(options) where options is an Object with key-value pairs.
To pass a collection to your view, you'd instantiate it like so:
new MyLayout({
collection : this.configData
});
Within your view, this.collection would refer to your configData collection.
This is a first attempt to making a backbone.js application.
I have a contact which is my model and a list/collection of contacts.
The initial rendering and fetching of the list of contacts works fine.
Now I'm trying to remove a contact from the collection after a click event.
It seems like I'm doing something wrong in the delete function of the ContactsView.
When I console.log contacts at the end of that method, the this.contacts collection is not changed.
Any help is appreciated!
var Contact = Backbone.Model.extend({
});
var Contacts = Backbone.Collection.extend({
model: Contact,
url: '/backbone/crm/contact'
});
var ContactsView = Backbone.View.extend({
initialize: function() {
this.contacts = new Contacts();
this.contacts.bind("reset", this.render, this);
this.contacts.bind("change", this.render, this);
this.contacts.bind("remove", this.render, this);
this.contacts.fetch();
},
events: {
"click .delete": "delete"
},
render: function() {
$("#contacts tbody").replaceWith(
$("#contacts_tmpl").render({ 'contacts': this.contacts.toJSON() }));
},
delete: function(e) {
var id = $(e.currentTarget).parents("tr").attr("id");
var model = this.contacts.get(id);
this.contacts.remove(model);
}
});
var contactsView = new ContactsView({ el: $("#contacts")});
The model is effectively removed from the collection. But now I need to remove it server side to.
I've implemented this by doing:
var Contacts = Backbone.Collection.extend({
model: Contact,
url: '/backbone/crm/contact',
initialize: function() {
this.bind("remove", this.delete, this);
},
delete: function(model) {
model.destroy();
}
This works and sends the correct delete request, but I find this kind of awkward, to call a destroy method from the delete method.