I'm using the get backbone method on a collection but in the same file (router),in a function works while in other function doesn't work.Below the function where doesn't works
var Models = {};
var AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"user/:id":"userDetails",
"settings":"settings",//mettere id dell utente loggato
"friends":"friends",
"mailbox":"mailbox",
"landscape":"landscape",
"gestione_richieste_amic":"gestione_richieste_amic"
},
friends: function(){
console.log("friend_router");
var self=this;
Models.utenti = new Usercollection();
Models.utenti.fetch({
success: function(object) {
console.log(object);
var view=new FriendsView({model:object});
self.changePage(view);
},
error: function(amici, error) {
}
});
console.log(Models.utenti);
var cur_user=Parse.User.current().id;
console.log(Models.utenti.get(cur_user));<--undefined, don't works here
console.log(cur_user);
} ,
The reason for this the Asynchronous nature of Ajax (fetch method).
The line where you log to the console will be executed before the collection is fetched. So you see an error.
1st Option - resolving the error is moving the log to inside of the success handler
friends: function () {
console.log("friend_router");
var self = this,
Models.utenti = new Usercollection();
Models.utenti.fetch({
success: function (object) {
console.log(object);
var view = new FriendsView({
model: object
});
self.changePage(view);
console.log(Models.utenti);
var cur_user = Parse.User.current().id;
console.log(Models.utenti.get(cur_user));
console.log(cur_user);
},
error: function (amici, error) {
}
});
},
2nd Option - you might take is to bind a sync event on the collection..
initialize: function () {
this.Models.utenti = new Usercollection();
this.listenTo(this.Models.utenti, 'sync', this.logCollection);
_.bindAll(this, 'logCollection');
},
logCollection: function () {
console.log(this.Models.utenti);
var cur_user = Parse.User.current().id;
console.log(this.Models.utenti.get(cur_user));
console.log(cur_user);
},
friends: function () {
console.log("friend_router");
var self = this;
this.Models.utenti.fetch({
success: function (object) {
console.log(object);
var view = new FriendsView({
model: object
});
self.changePage(view);
},
error: function (amici, error) {
}
});
},
Related
I put the fetch url with deferred method and I expect it will only invoke the remote ajax request one time.
However, it calls three times when I load the page.
How could I fix it? Thanks
js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
var comments = new Comments();
...
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
compelte js scripts
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
initialize: function() {
this.fetch({
success: this.fetchSuccess,
error: this.fetchError
});
this.deferred = new $.Deferred();
},
deferred: Function.constructor.prototype,
fetchSuccess: function(collection, response) {
collection.deferred.resolve();
},
fetchError: function(collection, response) {
throw new Error("Products fetch did get collection from API");
},
wellFormedComments: function () {
var MESSAGE_LIMIT_LENGTH = 80
var models = comments.select(function (model) {
var msg = model.get("message")
if (msg!=null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH/2));
} else{
};
return true
}
else{
return false
};
});
return new Comments(models);
},
emptyComments: function () {
var models = comments.select(function (model) {
var msg = model.get("message")
return false===_(msg).notBlank();
});
return new Comments(models);
}
});
var comments = new Comments();
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.deferred.done(function() {
commentView.render();
emptyCommentView.render();
});
The problem is that your comments collection triggers fetch when initialized. It's methods wellFormedComments and emptyComments creates new comments collections so they triggers fetch as well.
You can fix this by manually triggering fetch when required, something like:
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
} else {};
return true
} else {
return false
};
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
render: function() {
var notNullComments = comments.wellFormedComments();
if (notNullComments.length > 0) {
$("#dadasay_comments_plugin").show();
}
var html = commentsTmpl(notNullComments.toJSON());
$(this.el).append(html);
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
render: function() {
var source = $('#empty_comments_list_tmpl').html();
var emptyComments = comments.emptyComments();
var html = emptyCommentsTmpl(emptyComments.toJSON());
$(this.el).html(html);
},
});
var comments = new Comments();
var commentView = new CommentView({
collection: comments
});
var emptyCommentView = new EmptyCommentView({
collection: comments
});
comments.fetch({ // <--------- Do this manually once
success: function() {
commentView.render();
emptyCommentView.render();
},
error: function() {}
});
I think you can better structure your code as shown below, hope the comments explain the changes
var Comments = Backbone.Collection.extend({
model: Comment,
url: fetch_comments_url,
wellFormedComments: function() {
var MESSAGE_LIMIT_LENGTH = 80
var models = this.select(function(model) {
var msg = model.get("message")
if (msg != null) {
msg = msg.replace(/^\s+|\s+$/g, '')
if (msg.length >= MESSAGE_LIMIT_LENGTH) {
model.set("preview_message", msg.substr(0, MESSAGE_LIMIT_LENGTH / 2));
}
return true
}
return false
});
return new Comments(models);
},
emptyComments: function() {
var models = this.select(function(model) {
var msg = model.get("message")
return false === _(msg).notBlank();
});
return new Comments(models);
}
});
var CommentView = Backbone.View.extend({
el: $("#comments_section"),
template: commentsTmpl, // template reference, better create it here
initialize: function() {
this.render(); // self rendering
},
render: function() {
if (this.collection.length) { // use this.collection to refer to view's collection rather than external variables
$("#dadasay_comments_plugin").show(); //This shouldn't be a global selection
}
var html = this.template(this.collection.toJSON());
this.$el.append(html);
//---^ use cached jQuery object rather than creating new one
},
});
var EmptyCommentView = Backbone.View.extend({
el: $("#empty_comments_list"),
template: emptyCommentsTmpl,
initialize: function() {
this.render();
},
render: function() {
var source = $('#empty_comments_list_tmpl').html(); // unused?
var html = this.template(this.collection.toJSON());
this.$el.html(html);
},
});
var comments = new Comments();
comments.fetch({ // <--------- Do this manually once
success: function(collection, response) {
//----------------^ comments collection, all comments
var commentView = new CommentView({
collection: collection.wellFormedComments() // pass the resuting collection
});
var emptyCommentView = new EmptyCommentView({
collection: collection.emptyComments() // pass the resuting collection
});
},
error: function() {}
});
I'm making a simple list of people with option when clicking on person's name the Router will take a name as a parameter 'student/:name' and find a right person's object in a collection. I instantiate collection in a GroupView class by fetching it from the server. And that's where the Error appears: to get the access to collection (so I can find right object) in my viewStudent() method in Router class, I'm making one more instance of GroupView(), and console shows an error and that's right, 'cause there're no objects in collection.
I cannot wrap my head around this, why in GroupView() I receive data from the server and my collection just works fine, but second time I instantiate GroupView() in a Router - there's no collection? Maybe there's any other way I can get access to the collection in my Router? Any help would be greatly appreciated.
var StudentModel = Backbone.Model.extend({
defaults: {
name: 'Volodya',
lastName: 'Peterson',
age: 22,
gender: 'male'
}
});
var StudentsCollection = Backbone.Collection.extend({
model: StudentModel,
url: '/students.json'
});
var StudentView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#studentTpl').html()),
events: {
'click': function () {
eventAggregator.trigger('student:selected', this.model);
}
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var GroupView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new StudentsCollection();
this.collection.on('update', this.render, this);
this.collection.fetch();
},
render: function () {
var self = this;
this.collection.each(function (student) {
var studentView = new StudentView({
model: student
});
self.$el.append(studentView.render().el);
});
$('body').html(this.$el);
}
});
var RouterView = Backbone.View.extend({
tagName: 'ul',
render: function () {
var self = this;
_.each(this.model.toJSON(), function (value) {
self.$el.append('<li>' + value + '</li>');
});
return this;
}
});
var GroupController = function () {
this.start = function () {
var groupView = new GroupView();
};
};
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'student/:name': 'viewStudent'
},
index: function () {
groupController.start();
},
viewStudent: function (name) {
var groupView = new GroupView();
var selectedStudent = groupView.collection.find(function (student) {
return student.get('name') === name;
});
$('body').append((new RouterView({ model : selectedStudent})).render().el);
}
});
var eventAggregator= _.extend({}, Backbone.Events),
groupController;
$(function () {
var router = new Router();
groupController = new GroupController();
Backbone.history.start();
eventAggregator.on('student:selected', function (student) {
var urlpath= 'student/'+ student.get('name');
router.navigate(urlpath, {trigger: true});
});
});
I've created 2 separate views, 1 to render the template and the other one is where I bind the events, then I tried merging them into one in which case it causes an Uncaught TypeError: Object [object Object] has no method 'template'. It renders the template and the events are working as well, but I get the error.
edit.js, this is the combined view, which I think it has something to do with their el where the error is coming from
window.EditView = Backbone.View.extend ({
events: {
"click #btn-save" : "submit"
},
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
submit: function () {
console.log('editing');
$.ajax({ ... });
return false;
}
});
var editView = new EditView();
signin.js, this is the view that I can't merge because of the el being used by the ajax call and in SigninView's $(this.el) which causes the rendering of the templates faulty
window.toSigninView = Backbone.View.extend ({
el: '#signin-container',
events: {
"click #btn-signin" : "submit"
},
initialize: function() {
console.log('Signin View');
},
submit: function() {
$.ajax({ ... });
return false;
}
});
var toSignin = new toSigninView();
window.SigninView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
and I use utils.js to call my templates
window.utils = {
loadTpl: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('templates/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
In my Router.js, this is how I call the rendering of templates
editProfile: function() {
if (!this.editView) {
this.editView = new EditView();
}
$('#global-container').html(this.editView.el);
},
utils.loadTpl (['SigninView', 'EditView'],
function() {
appRouter = new AppRouter();
Backbone.history.start();
});
I think that I figured out your problem.
First merge your views and delete the line var toSignin = new toSigninView();
Second modify your utils.js code like this :
window[view].prototype.template = _.template(data);
new window[view]();
I have an object
var actions = {
'photos': function()
{
var self = this; // self = actions
$.get('./data.php?get=photos', function(data)
{
self.result = data;
});
},
'videos': function()
{
var self = this;
$.get('./data.php?get=videos', function(data)
{
self.result = data;
});
}
};
Each function creates one more item in actions called result
Then, instead of switch I use this (works good):
if (actions[action])
{
actions[action](); // call a function
console.log(actions);
console.log(actions.result);
}
action is a variable with value photos or videos.
console.log(actions) gives this:
Object
message: function ()
messages: function ()
profile: function ()
profile-edit: function ()
result: "<div>...</div>"
__proto__: Object
So I think there is resultitem in actions with the value "<div>...</div>".
But, console.log(actions.result) returns undefined.
Why?
I know all this code may be rewrited, but I would like to understand the reason of undefined.
Because we are dealing with asynchronous requests, we use "callbacks".
A callback is called when an asynchronous request is ready. Your request will get a response, and you send that response with the callback. The callback handles the response.
var actions = {
'photos': function(callback)
{
$.get('./data.php?get=photos', callback);
},
'videos': function(callback)
{
$.get('./data.php?get=videos', callback);
}
};
var action = 'photos';
actions[action](function(data) {
console.log(data);
});
Since you ensist on keeping the values, I would use this structure:
var actions = {
'photos': function()
{
$.get('./data.php?get=photos', function() {
this.__callback('photos', data);
});
},
'videos': function()
{
$.get('./data.php?get=videos', function() {
this.__callback('videos', data);
});
},
'__callback': function(action, data) {
this.results[action].push(data);
},
'results': {
'photos': [],
'videos': []
}
};
var action = 'photos';
actions[action]();
// use a timeout because we are dealing with async requests
setTimeout(function() {
console.log(actions.results); // shows all results
console.log(actions.results.photos); // shows all photos results
console.log(actions.results.videos); // shows all videos results
}, 3000);
gaaah what a horrible piece of code...
How can I set an observable property without any subscriptions firing for it?
I have a scenario were the page loads, an ajax call is made to get some data, the data is looped over and the currently selected item is then set to an observable. I want to be able to set this observable without any subscriptions for it firing because the first time this observable is set is considered its initial sate and the subscriptions should not execute on initial state.
function PlanViewModel() {
var self = this;
self.plans = ko.observableArray();
self.selectedItem = ko.observable();
self.getAllPlans = function () {
$.ajax({
url: "/Backoffice/Home/GetAllPlans",
type: "POST",
data: {},
context: this,
success: function (result) {
var planList = this.plans;
// clear the plan list
planList.removeAll();
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
self.selectedItem(planDetail); // how do I set this without the subscriptions firing?
}
planList.push(planDetail);
});
},
error: function (result) {
alert("An error occured getting plans.");
}
});
}
self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
} .bind(self));
}
You could restructure your code like this:
function PlanViewModel() {
var self = this;
self.plans = ko.observableArray();
self.getAllPlans = function () {
$.ajax({
// …
success: function (result) {
// …
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
self.selectedItem = ko.observable(planDetail);
}
planList.push(planDetail);
});
if (self.selectedItem === undefined) {
self.selectedItem = ko.observable();
}
self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
}.bind(self));
},
// …
});
}
}
That is, only start Knockout after your desired initial state is achieved.
Thanks, I went down that route and its working with some modifications. The selectedItem observable must be defined on the model from the get go because its used in bindings all over the place but I did move the subscription portion like you've suggested and that's working out great.
function PlanViewModel() {
var self = this;
var selectedItemSubscription = null;
self.plans = ko.observableArray();
self.selectedItem = ko.observable();
self.getAllPlans = function () {
$.ajax({
url: "/Backoffice/Home/GetAllPlans",
type: "POST",
data: {},
context: this,
success: function (result) {
var planList = this.plans;
// clear the plan list
planList.removeAll();
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
if (selectedItemSubscription != null)
selectedItemSubscription.dispose();
self.selectedItem(planDetail);
}
planList.push(planDetail);
});
selectedItemSubscription = self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
}.bind(self));
},
error: function (result) {
alert("An error occured getting plans.");
}
});
}
}