Updating collection and view in Backbonejs - javascript

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.

Related

Backbone: Move a model to another Collection

This is my first time using backbone, so I'm pretty confused about everything. I'm trying to make a todo list. Once I click "finished" on the todo, I want it to append to the "Completed" list.
I've been following this tutorial, and I tried to replicate the code(I tried to create a new completedTodo view and stuff like that), and I tried to do when clicking "finished" it would delete the $el, and I would add to the completedTodos. I think the problem here is even if it's added, it's not doing anything.
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
I put in a debugger there to see if it actually added to the collection, and when i did completedTodos.toJSON();, it does give me back the new thing I just added.
However, it does not append to my collection list.
Here is my whole entire script file, in case I named anything wrong.
var Todo = Backbone.Model.extend({
defaults: {
task: '',
priority: ''
}
});
var CompletedTodo = Backbone.Model.extend({
defaults: {
completedTask: '',
completedPriority: ''
}
});
var Todos = Backbone.Collection.extend({});
var todos = new Todos();
var CompletedTodos = Backbone.Collection.extend({});
var completedTodos = new CompletedTodos();
//Backbone view for one todo
var TodoView = Backbone.View.extend({
model: new Todo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.todos-list-template').html());
},
events: {
'click .finished-todo': 'done',
'click .delete-todo' : 'delete'
},
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
delete: function() {
this.model.destroy();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//Backbone view for all todos
var TodosView = Backbone.View.extend({
model: todos,
el: $('.todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
this.model.on('remove', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(todo) {
self.$el.append((new TodoView({model: todo})).render().$el);
});
return this;
}
});
//View for one Completed Todo
var CompletedTodoView = Backbone.View.extend({
model: new CompletedTodo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.completed-todos-template').html());
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//View for all Completed Todos
var CompletedTodosView = Backbone.View.extend({
model: completedTodos,
el: $('.completed-todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(completedTodo) {
self.$el.append((new CompletedTodoView({model: completedTodo})).render().$el);
});
return this;
}
});
var todosView = new TodosView();
$(document).ready(function() {
$('.add-todo').on('click', function() {
var todo = new Todo({
task: $('.task-input').val(),
priority: $('.priority-input').val()
});
$('.task-input').val('');
$('.priority-input').val('');
todos.add(todo);
});
});
After this, I also have to figure out how to use Parse to make it persist to the database. I figured I'd get everything working in backbone first, and then try to do put in the database. I'm also suppose to use node/express, so would that help? I'm pretty much a Ruby on Rails kind of person, so I don't really know any of these javascript framework type of stuff.
Thanks for your help!
Alright,
It was just because I didn't initialize the view.
var completedTodosView = new CompletedTodosView();
This fixed it.

Create new Backbone Views in click event of Backbone View

in the click event of the PodcastView I would like to create multiple new PodcastItemView objects. The jquery $.get works flawlessly, btw.
If I do console.debug(this.pinfo) in the start-function, I receive the JSON array of my podcast items (title, desc, url,...), so this is not the problem. Also it iterates x times through this array, so this works, too.
PodcastView = Backbone.View.extend({
tagName: "li",
itemTpl: _.template($("#podcast-item").html()),
events: {
"click .p-click" : "start"
},
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function() {
this.$el.html(this.itemTpl(this.model.toJSON()));
return this;
},
start: function() {
$.get(restUri + "podcast/" + this.model.get("title") + "/items", _.bind(function(data) {
this.pinfo = data;
_.each(this.pinfo, function(o) {
var v = new PodcastItemView({model: o});
$("#podcast-items").append(v.render().el);
}, this);
}));
}
});
What does not work, however, is the the creation of new PodcastItemView and to append them to #podcast-items. I get the following error:
TypeError: obj[implementation] is not a function (Backbone.js:225)
This is my PodcastItemView.
PodcastItemView = Backbone.View.extend({
tagName: "div",
itemTpl: _.template($("#podcast-item-list").html()),
initialize: function() {
this.listenTo(this.model, "change", this.render);
},
render: function() {
this.$el.html(this.itemTpl(this.model.toJSON()));
return this;
}
});
I am thankful for every tip or response.
Rewrite the start function to:
start: function() {
$.get(restUri + "podcast/" + this.model.get("title") + "/items", _.bind(function(data) {
this.pinfo = data;
_.each(this.pinfo, function(o) {
var model = new Backbone.Model(o)
var v = new PodcastItemView({model: model});
$("#podcast-items").append(v.render().el);
}, this);
}));
As mentioned in comments your code fails because you are trying to bind native object to the view instead of Backbone.Model.
Hope this helps.

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 ReferenceError: text is not defined

http://jsfiddle.net/3pSg7/
I wonder if someone can help to find what's wrong in this case.
I get "Uncaught ReferenceError: text is not defined" in line 6.
Using template and local .txt files for testing until APIs are available.
Backbone.js model script:
var Letter = Backbone.Model.extend( {
urlRoot: 'data/json/news',
initialize: function() {
},
defaults: {
_type: "",
text: "",
is_read: 0
}
});
var News = Backbone.Collection.extend({
model: Letter,
url: 'data/json/list_news.txt',
initialize: function() {
},
fetchMyNews: function() {
this.fetch({async:false});
}
});
var news = new News();
View script:
var NewsView = Backbone.View.extend({
initialize: function() {
this.isShown = false;
this.render();
this.listenTo(news, "all", this.doListen);
},
doListen: function(eventName){
if(eventName == "change"){
this.render();
}
},
isShown: false,
events: {
},
render: function() {
this.$el.attr("z-index", "1000");
news.fetchMyNews();
var sHtml = JST["news/row"](news.attributes);
$("#news_tbody").html(sHtml);
}
});
a few things in your code.
you are defining a global variable 'news' for your collection. that's not recommend, you can just pass a new collection to your view when you instantiate it :
var NewsView = new NewsView({
collection: new News()
});
and change all your 'news' reference in the view to 'this.collection'
and, I usually don't like async ajax calls. try to change them to callbacks, or just listen to events in your view. oh, and also, try not to fetch data in your render(). your function should only do what they are named for. :)
so in your view:
initialize: function() {
this.isShown = false;
this.listenTo(this.collection, "all", this.doListen);
this.collection.fetch();
},
doListen: function(eventName){
if(eventName == "change" || eventName == 'reset'){
this.render();
}
}
and in your render:
var sHtml = JST["news/row"](new.attributes);
$("#news_tbody").html(sHtml);
you are calling news.attributes, news is a collection here..."attributes" doesn't give you anything. I'm not sure what your template looks like, but you may be calling '.text' in your template, which is giving your this error here since news.attributes is undefined.

How to filter Backbone.js Collection and Rerender App View?

Is is a total Backbone.js noob question. I am working off of the ToDo Backbone.js example trying to build out a fairly simple single app interface. While the todo project is more about user input, this app is more about filtering the data based on the user options (click events).
I am completely new to Backbone.js and Mongoose and have been unable to find a good example of what I am trying to do. I have been able to get my api to pull the data from the MongoDB collection and drop it into the Backbone.js collection which renders it in the app. What I cannot for the life of me figure out how to do is filter that data and re-render the app view. I am trying to filter by the "type" field in the document.
Here is my script:
(I am totally aware of some major refactoring needed, I am just rapid prototyping a concept.)
$(function() {
window.Job = Backbone.Model.extend({
idAttribute: "_id",
defaults: function() {
return {
attachments: false
}
}
});
window.JobsList = Backbone.Collection.extend({
model: Job,
url: '/api/jobs',
leads: function() {
return this.filter(function(job){ return job.get('type') == "Lead"; });
}
});
window.Jobs = new JobsList;
window.JobView = Backbone.View.extend({
tagName: "div",
className: "item",
template: _.template($('#item-template').html()),
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.remove, this);
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setText();
return this;
},
setText: function() {
var month=new Array();
month[0]="Jan";
month[1]="Feb";
month[2]="Mar";
month[3]="Apr";
month[4]="May";
month[5]="Jun";
month[6]="Jul";
month[7]="Aug";
month[8]="Sep";
month[9]="Oct";
month[10]="Nov";
month[11]="Dec";
var title = this.model.get('title');
var description = this.model.get('description');
var datemonth = this.model.get('datem');
var dateday = this.model.get('dated');
var jobtype = this.model.get('type');
var jobstatus = this.model.get('status');
var amount = this.model.get('amount');
var paymentstatus = this.model.get('paymentstatus')
var type = this.$('.status .jobtype');
var status = this.$('.status .jobstatus');
this.$('.title a').text(title);
this.$('.description').text(description);
this.$('.date .month').text(month[datemonth]);
this.$('.date .day').text(dateday);
type.text(jobtype);
status.text(jobstatus);
if(amount > 0)
this.$('.paymentamount').text(amount)
if(paymentstatus)
this.$('.paymentstatus').text(paymentstatus)
if(jobstatus === 'New') {
status.addClass('new');
} else if (jobstatus === 'Past Due') {
status.addClass('pastdue')
};
if(jobtype === 'Lead') {
type.addClass('lead');
} else if (jobtype === '') {
type.addClass('');
};
},
remove: function() {
$(this.el).remove();
},
clear: function() {
this.model.destroy();
}
});
window.AppView = Backbone.View.extend({
el: $("#main"),
events: {
"click #leads .highlight" : "filterLeads"
},
initialize: function() {
Jobs.bind('add', this.addOne, this);
Jobs.bind('reset', this.addAll, this);
Jobs.bind('all', this.render, this);
Jobs.fetch();
},
addOne: function(job) {
var view = new JobView({model: job});
this.$("#activitystream").append(view.render().el);
},
addAll: function() {
Jobs.each(this.addOne);
},
filterLeads: function() {
// left here, this event fires but i need to figure out how to filter the activity list.
}
});
window.App = new AppView;
});
Have you tried resetting the collection with the result of the "leads" filter?
Something like
window.AppView = Backbone.View.extend({
el: $("#main"),
events: {
"click #leads .highlight" : "filterLeads"
},
initialize: function() {
Jobs.bind('add', this.addOne, this);
Jobs.bind('reset', this.render, this); //render on reset
Jobs.fetch(); //this triggers reset
},
addOne: function(job) {
var view = new JobView({model: job});
this.$("#activitystream").append(view.render().el);
},
//add a render function
render: function() {
this.$("#activitystream").empty(); //empty out anything thats in there
Jobs.each(this.addOne);
},
filterLeads: function() {
Jobs.reset(Jobs.leads()); //reset Jobs with only the leads
}
});
Also your AppView has no 'render' method, yet you reference it here:
Jobs.bind('all', this.render, this);

Categories