I'm trying to learn backbone and integrate it into this app i'm building. The goal is to query an api that returns some JSON,
I then want to render it in a table. Right now, I'm getting the JSON back, but not sure how to get the collection to talk with the view.
I got most of this from a tutorial that may be old at this point. So any suggestions or tutorials on how to move forward would be great.
The error i'm getting is
Uncaught TypeError: this.jds_collection.refresh is not a function
Heres the code
$(document).ready(function(){
var Jd = Backbone.Model.extend({
});
JdsCollection = Backbone.Collection.extend({
// url: 'http://localhost:3000/api/blogs'
model: Jd,
initialize: function(){
console.log("Initialize JdsCollection collection");
this.jdtable = new JdTable;
this.bind("refresh", this.jdtable.renderTable);
}
});
JdTable = Backbone.View.extend({
el: $("#mainTable"),
initialize: function(){
console.log("Initialize JdTable view");
// console.log($("#mainTable2"));
this.$el.html("This is the inital content");
},
renderTable: function(collection){
console.log(collection);
}
});
AppView = Backbone.View.extend({
el: $("#appview"),
initialize: function(){
console.log("Initialize AppView view");
this.jds_collection = new JdsCollection;
// this.loadResults({});
},
events: {
"keyup #jd-search-input": "loadResults"
},
loadResults: function(event){
query = $(event.currentTarget).val();
$.when(this.ajaxGetJds( query ) )
.then($.proxy(function( response ){
entries = response.Output.Results;
console.log(this.jds_collection);
this.jds_collection.refresh(entries);
}, this ) );
},
ajaxGetJds: function( query ){
return $.ajax(jds_api + "?searchText=" + query, {dataType: "jsonp", cache: true, data:{outputType: "JSONP"}});
}
});
console.log($("#jd-search-input"));
var appview = new AppView;
});
Related
Sorry for my bad English. Tell me why the following happens:
I have some backbone collection:
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
success: this.fetchSuccess(this),
error: this.fetchError
});
},
fetchSuccess: function( collect_model ) {
new BackgroundsView ({ collection : collect_model });
},
fetchError: function() {
throw new Error("Error fetching backgrounds");
}
});
And some view:
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.render();
},
render: function() {
console.log(this.collection);
this.collection.each( function (background) {
console.log(background);
//var backgroundView = new BackgroundView ({ model: background });
//this.$el.append(backgroundView.render().el);
});
}
});
now i creating collection
var backgrounds = new Models.Backgrounds();
but when I must render this view, in the process of sorting the collection its length is 0, but should be two. This log I see at console. How is this possible? What am I doing wrong??
You are creating the view before the collection fetch is successfull. Your code should be:
initialize: function() {
this.fetch({
success: this.fetchSuccess,
//------------------------^ do not invoke manually
error: this.fetchError
});
},
fetchSuccess: function(collection, response) {
new BackgroundsView ({ collection : collection});
},
You should let backbone call fetchSuccess when the fetch succeeds. Right now you're invoking the funcion immediately and passing the return value undefined as success callback.
This looks like a wrong pattern. Your data models shouldn't be aware of/controlling the presentation logic.
You have a view floating around without any reference to it. You should be creating a view instance with reference(for example from a router, or whatever is kick starting your application) and passing the collection to it. Then fetch the collection from it's initialize method and render after the fetch succeeds. Collection can be referenced via this.collection inside view.
Alternatively you can fetch the collection from router itself and then create view instance. Either way collection/model shouldn't be controlling views.
If the code is structured in the following way, the problem is solved. It was necessary to add a parameter reset to fetch.
var Background = window.Models.Background = Backbone.Model.extend({});
var Backgrounds = window.Models.Backgrounds = Backbone.Collection.extend({
model: window.Models.Background,
url: '/backgrounds/',
initialize: function() {
this.fetch({
reset : true,
});
}
});
var BackgroundsView = window.Views.BackgroundsView = Backbone.View.extend({
tagName: 'div',
className: 'hor_slider',
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each( function (background) {
var backgroundView = new BackgroundView ({ model: background });
this.$el.append(backgroundView.render().el);
}, this);
$('#view_list').empty();
$('#view_list').append(this.$el);
return this;
}
});
I am fairly new to Backbone and am trying to get my head around routers and calling a collection from a database.
I have the following
Collection:
var Scorecards = Backbone.Collection.extend({
model:Scorecard,
url:"http://localhost:3002/api/scorecards",
initialize:function(){
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
},
fetchSuccess: function (collection, response) {
console.log("results");
if(collection.length>0) {
var view = new ScorecardsView({el:'#scorecards-container', model:scorecards});
view.render();
}
else{
var view = new NoScorecardsView({el:'#scorecards-container'});
view.render();
}
},
fetchError: function(collection, response) {
throw new Error("Failed to get scorecards");
}
});
Router:
var ScorecardRouter = Backbone.Router.extend ({
routes: {
'' : 'home',
'create': 'createScorecard',
'edit': 'editScorecard'
},
home: function () {
console.log("Home view");
var view = new ScorecardsView({el:'#scorecards-container', model:scorecards});
view.render();
},
createScorecard: function () {
console.log('Create view');
var view = new CreateScorecardView({el:'#scorecards-container'});
view.render();
}
});
Scorecards view:
var ScorecardsView = Backbone.View.extend({
initialize: function(){
this.model.on('destroy', this.render, this);
},
render: function() {
console.log("Scorecard render");
var self = this;
this.$el.html(ScorecardContTemp);
this.model.each(function(scorecard){
var scorecardView = new ScorecardView({model:scorecard});
self.$('.scorecards-items tbody').append(scorecardView.render().$el);
});
},
events: {
"click #scorecard-create-btn" : "createScorecardView",
},
createScorecardView: function(e) {
e.preventDefault();
scorecardRouter.navigate('create', {trigger: true});
}
});
and I start things off with this
var scorecards = new Scorecards;
var scorecardRouter = new ScorecardRouter();
Backbone.history.start();
My problem is, when I first hit the home route, I'm getting the view render function running twice. Because firstly the fetch is calling it and also the route is calling it.
I need to remove the call from either the fetch success or the route, but when I do I get no results on initial load and I have to navigate to a different route and back to.
How are you supposed to achieve this? So I can fetch the results once and then display them via the route the fetch is successful but also show them in the route when a user navigates to it.
I hope that makes sense?
Any help would be great.
First of all, your data shouldn't know how it is rendered, so new View() anywhere within a Model or a Collection is a sure sign of a problem. Your views should watch their data and update themselves.
Your other possible source of confusion is passing {trigger: true} to your router navigate method. What kind of trouble that brings is elaborately explained in this classic Backbone article: Don’t Execute A Backbone.js Route Handler From Your Code.
For now, you definitely should remove the view rendering from the collection. Instead, your view should be aware of the collection and update itself when the data changes.
Here's an example of how I would setup my view to watch the collection:
/** Scorecard model */
var Scorecard = Backbone.Model.extend({
defaults: {
name: '',
email: ''
}
});
/** Scorecard View (I know it totally doesn't look like a scorecard, just an example view) */
var ScorecardView = Backbone.View.extend({
template: _.template('<%=name%>, <em><%=email%></em>'),
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
});
/** Collection */
var Scorecards = Backbone.Collection.extend({
model: Scorecard,
/** using fake api for the sake of this example to work */
url: "http://jsonplaceholder.typicode.com/users",
initialize:function(){
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
},
fetchSuccess: function (collection, response) {
console.log("results:", collection);
},
fetchError: function(collection, response) {
throw new Error("Failed to get scorecards");
}
});
/** Scorecards view: */
var ScorecardsView = Backbone.View.extend({
initialize: function(){
this.collection.on('destroy', this.render, this);
/** render one added item whenever it comes to collection */
this.collection.on('add', this.addOne, this);
},
render: function() {
console.log("Scorecard render");
/** clean the items container,
which will be useful when items get destroyed
and we'll want to re-render whole collection */
this.$el.find('.scorecards-items').empty();
/** in case collection already has data, let's render it */
this.collection.each(this.addOne, this);
},
addOne: function(scorecard){
var scorecardView = new ScorecardView({ model: scorecard });
this.$('.scorecards-items').append(scorecardView.render().$el);
}
});
/** Router: */
var ScorecardRouter = Backbone.Router.extend ({
routes: {
'' : 'home'
},
home: function () {
console.log("Home view");
var view = new ScorecardsView({
el:'#scorecards-container',
collection: scorecards
});
view.render();
}
});
/** starting things off */
var scorecards = new Scorecards();
var scorecardRouter = new ScorecardRouter();
Backbone.history.start();
<script src='http://code.jquery.com/jquery.js'></script>
<script src='http://underscorejs.org/underscore.js'></script>
<script src='http://backbonejs.org/backbone.js'></script>
<div id="scorecards-container">
<div class="scorecards-items"></div>
</div>
I'm using backbone and I'm very new at it, I have a list of products sizes and a list of quantities / prices. When someone selects a different product size, I use backbone to do an ajax call to the server to get me an updated price list.
I'm struggling to get the save function to work so that I can return the updated collection. I will have to pass back a couple params, but for the time being, I'm just trying to get it to save to the backend. I've read save can be used to automatically setup the ajax request.
I'd also only like this to load the template when the li element is clicked, not on page load.
My code
var models = {};
models.PriceModel = Backbone.Model.extend({
})
models.PriceList = Backbone.Collection.extend({
initialize: function(options) {
this.productId = options.productId;
},
model: models.PriceModel,
url: function() {
return '../product/pricing/' + this.productId + '.json'
}
});
View
var PriceView = Backbone.View.extend({
el: '#product-module',
template: Handlebars.compile($("#priceTemplate").html()),
events: {
"click #product-dimensions li": "dimensionClicked",
},
initialize: function(){
this.listenTo(this.collection, 'add', this.render);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.$el.find('#product-quantities').html( this.template(this.collection.toJSON()));
},
dimensionClicked: function(event, callback){
this.collection.save({},{
success: function(model, data){
console.log('success')
this.collection.fetch();
},
error: function(model, response) {
console.log('error! ' + response);
}
});
},
});
Page
<script>
var prices = new models.PriceList({productId:${productInstance.id}});
var priceView = new PriceView({collection: prices});
<%-- prices.fetch({reset: true});--%>
</script>
The error I'm getting.
TypeError: this.collection.save is not a function
this.collection.save({},{
How do I pass back a couple of params and then refresh the template?
The solution was to use
this.collection.fetch({data: {customParm : searchData}, reset: true});
I can't seem to get JSON that is loading into my FriendsCollection to render into FriendListView. I can see that it is loading through the network panel, and I can log the data to the console, but for some reason the fetch command isn't passing the data to the view to be rendered.
I'm using Backbone 1.0.
The code i'm using is available on jsbin here: http://jsbin.com/OHePaki/1/edit?html,js,output
// MODELS
var ArtifactModel = Backbone.Model.extend({
initialize: function() {
this.on('reset', function(){ artifactView.render() })
},
defaults: {
"text": "Unknown Text",
"timestamp": "Unknown timestamp"
}
});
var artifactModel = new ArtifactModel();
// COLLECTIONS
var ArtifactCollection = Backbone.Collection.extend({
model: ArtifactModel,
url: '/getDigest.json',
// url: 'http://we365.local/Artifact/GetShareableArtifact?token=b88d2640826bb8593f6edb308ce604f28225f240&artifact_id=2&social_site=tw&log_inside=&go',
parse: function(data) {
console.log('running parse');
return _.map(data.response.content, _.identity);
},
initialize: function(){
this.on('reset', function(){ artifactListView.render(); }),
console.log('running init function for ArtifactCollection');
this.fetch();
//this.reset(artifactjson, { parse: true });
console.log(this.toJSON());
}
});
var artifactCollection = new ArtifactCollection();
// VIEWS
var ArtifactView = Backbone.View.extend({
tagName: 'li',
className: 'single-model',
render: function(){
var template = Handlebars.compile($('#stream_getDigest').html());
this.$el.html(template(this.model.toJSON()));
return this;
}
});
var ArtifactListView = Backbone.View.extend({
initalize: function(){
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(artifactModel){
var artifactView = new ArtifactView({model: artifactModel});
this.$el.append(artifactView.render().el);
}
});
// rendering
var artifactView = new ArtifactView({model: artifactModel});
var artifactListView = new ArtifactListView({collection: artifactCollection});
artifactView.render();
artifactListView.render();
$('#list').html(artifactListView.$el.html());
By default jQuery ajax call is asynchronous, the code will keep running without waiting for the .fetch() to be finished. In your code the view is rendered before the collection is ready so the data for the view is empty.
You can pass jQuery ajax option to fetch function so you can do the following (http://backbonejs.org/#Collection-fetch):
...
initialize: function(){
this.on('reset', function(){ artifactListView.render(); }),
console.log('running init function for ArtifactCollection');
this.fetch({async:false});
console.log(this.toJSON()); //This will log the loaded collection
}
...
Or you can change fetching strategy to take the advantage of asynchronous load:
this.fetch().done(function(){
//Things to do after collection is loaded
});
//this's not good to use in init function
You need to set handlers on the models. Something like this:
friendModel.on('change', function() { friendView.render(); });
friendCollection.on('change', function() { friendListView.render(); });
Or better yet, put these lines in the constructors for friendModel and friendCollection (see http://backbonejs.org/#View-constructor ).
I am creating an app that will list the days of an event as buttons, then let you add dates and click each date to get a new "daily calendar".
This is my first real world app using backbone and underscore, so I keep running into road blocks. I would really appreciate anyone helping me out.
I am now at the point where my collection is full of dates, and I can add to those dates. Now what I am trying to figure out it routing the links to switch out the calendar, depending on the selected date.
Heres what I have relating to this part of the app so far:
Collections
var Days = Backbone.Collection.extend({
url: daysURL
});
var Calendar = Backbone.Collection.extend({
url: URL
});
Models
var Header = Backbone.Model.extend();
var header = new Header();
var ConferenceDay = Backbone.Model.extend();
var conferenceDay = new ConferenceDay();
View
var HeaderView = Backbone.View.extend({
el: $(".conf_days"),
template: _.template($('#days').html()),
events: {
'click a.day-link': 'changeDay',
'click #add_day' : 'addDay',
'click #previous_day' : 'prevDay',
'click #next_day' : 'nextDay',
'click #delete_day' : 'deleteDay'
},
initialize: function(){
_.bindAll(this, "render");
this.collection = new Days();
this.collection.fetch();
this.collection.bind("reset", this.render, this);
},
render: function(){
var JSONdata = this.collection.toJSON();
this.$el.html(this.template({days: JSONdata}));
console.log(JSON.stringify(JSONdata))
return this;
},
changeDay: function(e){
AppRouter.history.navigate($(this).attr('href'));
return false;
},
addDay: function() {
newDate = Date.parse($('.day-link:first-child').text()).add(1).day();
var newDay = new ConferenceDay();
newDay.set({date_formatted: newDate});
this.collection.add(newDay)
newDay.save({
success: function(){
alert('yes')
},
error: function(){
alert('no')
}
});
},
deleteDay: function(event){
var id = $('.day-link:last-child').data("id");
$('.day-link:last-child').remove();
},
prevDay: function() {
},
nextDay: function() {
},
loadTimes: function(){
var html = time.get('times');
$('.time_td').append(html);
}
});
var headerView = new HeaderView({ model: header });
ConferenceView = Backbone.View.extend({
el: $(".calendar"),
template: _.template($('#calendar').html()),
events: {
},
initialize: function(){
//this.listTracks();
this.collection = new Calendar();
this.collection.fetch();
this.collection.bind("reset", this.render, this);
},
render: function(){
var JSONdata = this.collection.toJSON();
this.$el.html(this.template({days: JSONdata}));
},
listTracks: function() {
}
});
var conferenceView = new ConferenceView({model:conferenceDay});
My current routing
var AppRouter = Backbone.Router.extend({
routes: {
'' : 'index',
'day/:id' : 'changeDay'
},
initialize: function() {
},
index: function() {
},
changeDay: function(id){
alert("changed");
this.calender.changeDay(id);
this.dayView = new ConferenceView({model:conferenceDay});
$('#calender').html(this.dayView.render().el).text('test');
},
});
var app = {
init: function() {
var routes = new AppRouter();
Backbone.history.start({pushState: true});
}
}
app.init();
Ideally, I would like the user to click the day-link button and have the url update via push state to the day/:id and then the #calender template would update with the correct model info received from the day update.
There's a lot of code in your post, so I'm not 100% sure the below will cover everything you need to do, but it's a start
This event handler might be causing some problems:
changeDay: function(e){
AppRouter.history.navigate($(this).attr('href'));
return false;
}
On a detail level, couple of things are off here:
You don't need to reference history. I'm not sure that the router even has such property. You should call AppRouter.navigate instead.
If you want the router to trigger your changeDay route method, you need to pass an option trigger:true, like so:
AppRouter.navigate($(this).attr('href'), {trigger:true}).
However, the actual solution is still simpler than that. You can remove the HeaderView.changeDay event handler, and the click a.day-link event binding from the events hash entirely. Backbone Router will detect the changed URL, and call the router method which matches the new URL automatically.