How to pass collection inside jeditable function? - backbone.js

I want to edit my collection using jeditable, where modifyCollection is a function associated with the event dblclick. I have the following code:
initialize : function(options) {
view.__super__.initialize.apply(this, arguments);
this.collection = this.options.collection;
this.render();
},
render : function() {
var template = _.template(tpl, {
collectionForTemplate : this.collection ,
});
this.el.html(template);
return this;
},
modifyCollection : function (event){
$('#name').editable(function(value, settings) {
return (value);
}
,
{ onblur: function(value) {
this.modelID=event.target.nameID;
this.collection = this.options.collection;
console.log("This Collection is: " + this.collection); //Shows : undefined
//
this.reset(value);
$(this).html(value);
return (value);
}
});
The idee is to update the model and subsequently, the collection by means of jeditable. The in place editing works fine, but the problem is, I am not able to pass the collection into the function. I want to save all the changes to my collection locally and send them to the server at a later time. What am I doing wrong here?

Moved the comment to a formal answer in case other people find this thread.
The this inside your onblur() function is not pointing to this collection. Try adding var self = this; inside your modifyCollection() function then in your onblur() change this.collection to self.collection like so:
modifyCollection : function (event) {
var self = this; // Added this line
// When working with functions within functions, we need
// to be careful of what this actually points to.
$('#name').editable(function(value, settings) {
return (value);
}, {
onblur: function(value) {
// Since modelID and collection are part of the larger Backbone object,
// we refer to it through the self var we initialized.
self.modelID = event.target.nameID;
self.collection = self.options.collection;
// Self, declared outside of the function refers to the collection
console.log("This Collection is: " + self.collection);
self.reset(value);
// NOTICE: here we use this instead of self...
$(this).html(value); // this correctly refers to the jQuery element $('#name')
return (value);
}
});
});
UPDATE - Foreboding Note on self
#muistooshort makes a good mention that self is actually a property of window so if you don't declare the var self = this; in your code, you'll be referring to a window obj. Can be aggravating if you're not sure why self seems to exist but doesn't seem to work.
Common use of this kind of coding tends to favor using that or _this instead of self. You have been warned. ;-)

Related

Is it okay to call initialize() to initialize a view?

In my Backbone app, I have the following
playlistView = new PlaylistView({ model: Playlist });
Playlist.getNewSongs(function() {
playlistView.initialize();
}, genre, numSongs);
Playlist.getNewSongs() is called back when some ajax request is finished. I want to re-initialize the view then. However, I believe the way I'm doing it leads to this problem of a view listening to a same event twice. Is calling initialize() like this acceptable? If not, what should I do instead?
Update:
I wrote this chrome extension in Backbone to learn Backbone, and it's in a design hell at the moment. I am in the middle of refactoring the entire codebase. The snippet below is my PlaylistView initialize() code block.
var PlaylistView = Backbone.View.extend({
el: '#expanded-container',
initialize: function() {
var playlistModel = this.model;
var bg = chrome.extension.getBackgroundPage();
if (!bg.player) {
console.log("aborting playlistView initialize because player isn't ready");
return;
}
this.listenTo(playlistModel.get('songs'), 'add', function (song) {
var songView = new SongView({ model: song });
this.$('.playlist-songs').prepend(songView.render().el);
});
this.$('#song-search-form-group').empty();
// Empty the current playlist and populate with newly loaded songs
this.$('.playlist-songs').empty();
var songs = playlistModel.get('songs').models;
// Add a search form
var userLocale = chrome.i18n.getMessage("##ui_locale");
var inputEl = '<input class="form-control flat" id="song-search-form" type="search" placeholder="John Lennon Imagine">' +
'<span class="search-heart-icon fa fa-heart"></span>'+
'<span class="search-input-icon fui-search"></span>';
}
this.$('#song-search-form-group').append(inputEl);
var form = this.$('input');
$(form).keypress(function (e) {
if (e.charCode == 13) {
var query = form.val();
playlistModel.lookUpAndAddSingleSong(query);
}
});
// Fetch song models from bg.Songs's localStorage
// Pass in reset option to prevent fetch() from calling "add" event
// for every Song stored in localStorage
if (playlistModel.get('musicChart').source == "myself") {
playlistModel.get('songs').fetch({ reset: true });
songs = playlistModel.get('songs').models;
}
// Create and render a song view for each song model in the collection
_.each(songs, function (song) {
var songView = new SongView({ model: song });
this.$('.playlist-songs').append(songView.render().el);
}, this);
// Highlight the currently played song
var currentSong = playlistModel.get('currentSong');
if (currentSong)
var currentVideoId = currentSong.get('videoId');
else {
var firstSong = playlistModel.get('songs').at(0);
if (!firstSong) {
// FIXME: this should be done via triggering event and by Popup model
$('.music-info').text(chrome.i18n.getMessage("try_different_chart"));
$('.music-info').fadeOut(2000);
//console.log("something wrong with the chart");
return;
}
var currentVideoId = firstSong.get('videoId');
}
_.find($('.list-group-item'), function (item) {
if (item.id == currentVideoId)
return $(item).addClass('active');
});
},
It is not wrong but probably not a good practice. You did not post the code in your initialize but maybe you have too much logic here.
If you are simply initializing the view again so that the new data is rendered, you should use event listener as such:
myView = Backbone. View.extend ({
initialize : function() {
// We bind the render method to the change event of the model.
//When the data of the model of the view changes, the method will be called.
this.model.bind( "change" , this.render, this);
// Other init code that you only need once goes here ...
this.template = _.template (templateLoader. get( 'config'));
},
// In the render method we update the view to represent the current model
render : function(eventName) {
$ (this.el ).html(this .template ((this.model .toJSON())));
return this;
}
});
If the logic in your initiialize is something totally else, please include it. Maybe there is a beter place for it.

why behaviors are not allowed to pass dynamically?

I am working on Marionette.behavior.I was trying to pass the behaviors hash dynamically at the time of view initialization but it is not getting assigned to the behaviors object of view.because behaviors are getting initialized at the time of view construction.
so we achieved the solution in the following way but is it the right way to achieve it?
is there any other way to achieve? and
why behaviors are not allowed to pass dynamically?
Here's the code:
var Behaviour = new Marionette.Application();
Behaviour.addRegions({
mainRegion:"#main-region"
});
var Person = Backbone.Model.extend({
defaults:{
firstName:"NA",
lastName:"NA",
phoneNumber:"NA",
presentAddr:"NA",
permanantAddr:"NA"
}
});
var buttonView=Marionette.ItemView.extend({
template:"#buttontemplate",
constructor:function(options){
this.behaviors = options.behaviors;
Marionette.ItemView.apply(this, arguments);
},
events:{
"click .display":"displayDetail"
},
displayDetail:function(){
this.triggerMethod("DisplayPersonDetails");
},
//behaviors:{Behavior1:{ },Behavior2:{ }}
})
var PersonDetailsView = Marionette.ItemView.extend({
template:"#static-template",
ui: {
"Change": ".change"
},
events:{
"click #ui.Change":"changeBehavior"
},
changeBehavior:function(){
},
});
var Behavior1 = Marionette.Behavior.extend({
onDisplayPersonDetails:function(){
var person=new Person({firstName:"abhijeet",lastName:"avhad",phoneNumber:"9604074690",permanantAddr:"sangamner",presentAddr:""})
var myView = new PersonDetailsView({model:person});
Behaviour.mainRegion.show(myView);
}
});
var Behavior2 = Marionette.Behavior.extend({
onDisplayPersonDetails:function(){
var person =new Person({firstName:"abhijeet",lastName:"avhad",phoneNumber:"9604074690",permanantAddr:"",presentAddr:"shivajinagar"})
var myView =new PersonDetailsView({model:person});
Behaviour.mainRegion.show(myView);
}
});
Behaviour.on("initialize:after", function(){
console.log(" started!");
Marionette.Behaviors.behaviorsLookup = function() {
return window.Behaviors;
};
window.Behaviors = {};
window.Behaviors.Behavior1 = Behavior1;
window.Behaviors.Behavior2 = Behavior2;
var buttonview=new buttonView({behaviors:{Behavior1:{ },Behavior2:{}}});
Behaviour.mainRegion.show(buttonview);
});
Behaviour.start();
The other way of achieving that is in your definition declare a function that returns the behaviors supplied at initialization, like this:
var buttonView=Marionette.ItemView.extend({
...
behaviors: function () {
return this.options.behaviors;
},
...
This is because the Marionette applies the behaviors in the constructor:
if (_.isObject(this.behaviors)) {
new Marionette.Behaviors(this);
}
You may try to do the same in your initialize method, but I'm not sure if it will work correctly if you already had some behaviors assigned beforehand.
After hacking through the source, I've come up with the following. It breaks encapsulation, which leads me to believe that there is probably a better way. Nonetheless, until I find it, this is going straight into production.
// Define Behavior.
var Behavior1 = { /* Behavior definition */ }
// Create View like normal.
var view = new ItemView({
behaviors: {
behavior1: { behaviorClass: Behavior1 }
}
});
// Here's the ugly part.
view.undelegateEvents();
view._behaviors = Marionette.Behaviors(subview);
view.delegateEvents();
After you do that, your Behaviors should all work.
Behavior can be passed directly with behaviorClass property within declaration of behaviors:
As seen in the marionette.behaviors docs, for example we have Tooltip behavior, which we want to pass directly and not from global list.
define(['marionette', 'lib/tooltip'], function(Marionette, Tooltip) {
var View = Marionette.ItemView.extend({
behaviors: {
Tooltip: {
behaviorClass: Tooltip, // <-- passing the behavior directly here
message: "hello world"
}
}
});
});

Nested backbone model results in infinite recursion when saving

This problem just seemed to appear while I updated to Backbone 1.1. I have a nested Backbone model:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""},
parse: function (response) {
response.name = response.set_id;
response.problems = new ProblemList(response.problems);
return response;
}
});
var ProblemList = Backbone.Collection.extend({
model: Problem
});
I initially load in a ProblemSetList, which is a collection of ProblemSet models in my page. Any changes to the open_date or due_date fields of any ProblemSet, first go to the server and update that property, then returns. This fires another change event on the ProblemSet.
It appears that all subsequent returns from the server fires another change event and the changed attribute is the "problems" attribute. This results in infinite recursive calls.
The problem appears to come from the part of set method of Backbone.Model (code listed here from line 339)
// For each `set` attribute, update or delete the current value.
for (attr in attrs) {
val = attrs[attr];
if (!_.isEqual(current[attr], val)) changes.push(attr);
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
}
// Trigger all relevant attribute changes.
if (!silent) {
if (changes.length) this._pending = true;
for (var i = 0, l = changes.length; i < l; i++) {
this.trigger('change:' + changes[i], this, current[changes[i]], options);
}
}
The comparison on the problems attribute returns false from _.isEqual() and therefore fires a change event.
My question is: is this the right way to do a nested Backbone model? I had something similar working in Backbone 1.1. Other thoughts about how to proceed to avoid this issue?
You reinstantiate your problems attribute each time your model.fetch completes, the objects are different and thus trigger a new cycle.
What I usually do to handle nested models:
use a model property outside of the attributes handled by Backbone,
instantiate it in the initialize function,
set or reset this object in the parent parse function and return a response omitting the set data
Something like this:
var ProblemSet = Backbone.Model.extend({
defaults: {
name: "",
open_date: "",
due_date: ""
},
initialize: function (opts) {
var pbs = (opts && opts.problems) ? opts.problems : [];
this.problems = new ProblemList(pbs);
},
parse: function (response) {
response.name = response.set_id;
if (response.problems)
this.problems.set(response.problems);
return _.omit(response, 'problems');
}
});
parse gets called on fetch and save (according to backbone documentation), this might cause your infinite loop. I don't think that the parse function is the right place to create the new ProblemsList sub-collection, do it in the initialize function of your model instead.

Passing JSON object as parameter from View to Controller function?

Basically I've a panel called DummyPanel, Now on dummypanel initialize event I've called a controller function like as follows:
var me = component;
var fieldCollection =
{
"Order" : 'ordNumber',
"Ref": 'refNumber'
};
me.fireEvent('myControllerFunction','Param1', fieldCollection, 'Param3');
Now I want to get fieldCollection JSON object value within function myControllerFunction, to get value from fieldCollection I'm using following code:
myControllerFunction(param1, collection, param3)
{
Ext.Msg.alert(collection.Order);
}
But it does not return anything. So please let me know how to resolve this problem!!
Any comment will appreciated!!
I'm not quite sure what it means "But it does not return anything", but I'll try.
So, your "DummyPanel" view have a alias or itemId property. In yor controller (in init() function), you need "keep track" of your view. For example:
In your view:
me.fireEvent('myEventName','Param1', fieldCollection, 'Param3');
In your controller:
init:function(){
var me = this;
this.control({
'panel[itemId=your-view-itemId]': { // call your function after event
myEventName: me.myControllerFunction
}
});
...
},
...
myControllerFunction: function(...) {
...
}
Should it not be
Ext.Msg.alert(collection["Order"])?
Or if you want to keep Ext.Msg.alert the way it is fieldCollection should be defined this way
var fieldCollection =
{
Order : 'ordNumber',
Ref : 'refNumber'
};

Backbone.js Collection model value not used

Backbone is not using the model specified for the collection. I must be missing something.
App.Models.Folder = Backbone.Model.extend({
initialize: function() {
_.extend(this, Backbone.Events);
this.url = "/folders";
this.items = new App.Collections.FolderItems();
this.items.url = '/folders/' + this.id + '/items';
},
get_item: function(id) {
return this.items.get(id);
}
});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
App.Models.FolderItem = Backbone.Model.extend({
initialize: function() {
console.log("FOLDER ITEM INIT");
}
});
var folder = new App.Models.Folder({id:id})
folder.fetch();
// later on change event in a view
folder.items.fetch();
The folder is loaded, the items are then loaded, but they are not FolderItem objects and FOLDER ITEM INIT is never called. They are basic Model objects.
What did I miss? Should I do this differently?
EDIT:
Not sure why this works vs the documentation, but the following works. Backbone 5.3
App.Collections.FolderItems = Backbone.Collection.extend({
model: function(attributes) {
return new App.Models.FolderItem(attributes);
}
});
the problem is order of declaration for your model vs collection. basically, you need to define the model first.
App.Models.FolderItem = Backbone.Model.extend({...});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
the reason is that backbone objects are defined with object literal syntax, which means they are evaluated immediately upon definition.
this code is functionality the same, but illustrates the object literal nature:
var folderItemDef = { ... };
var folderItemsDef = {
model: App.Models.FolderItem
}
App.Models.FolderItem = Backbone.Model.extend(folderItemDef);
App.Collections.FolderItems = Backbone.Collection.extend(folderItemsDef);
you can see in this example that folderItemDef and folderItems Def are both object literals, which have their key: value pairs evaluated immediately upon definition of the literal.
in your original code, you defined the collection first. this means App.Models.FolderItem is undefined when the collection is defined. so you are essentially doing this:
App.Collection.extend({
model: undefined
});
By moving the model definition above the collection definition, though, the collection will be able to find the model and it will be associated correctly.
FWIW: the reason your function version of setting the collection's model works, is that the function is not evaluated until the app is executed and a model is loaded into the collection. at that point, the javascript interpreter has already found the model's definition and it loads it correctly.

Resources