jquery $(this) not working with coffeescript and backbone - backbone.js

$(this) is not working with cofeescript and backbone.Its generating valid code but I don't understand why it was not working. For work around I used $(event.target).
Jobmatch.Views.Jobs ||= {}
class Jobmatch.Views.Jobs.JobView extends Backbone.View
template: JST["recommendation/templates/jobs/job"]
initialize: () ->
#ajs = new Jobmatch.Collections.ApplicantJobsCollection()
#ajs.reset(#options.applicant_jobs || [])
#aj = new #ajs.model()
#index = #options.index || 0
events:
"click .job_apply" : "apply"
tagName: "tr"
apply: (event)->
target = $(this) // As this is not working as I expected,So I used below line.
target = $(event.target)
if #options.user_jobmatch_valid
#ajs.create({job_id: #model.get('id') })
target.parents("a.job_apply").prev().click();
else
target.parents("a.job_apply").next().click();
false
render: ->
$(this.el).html(#template(#model.toJSON()))
#
And this cofeescript has generated following code:
JobView.prototype.apply = function(event) {
var target; target = $(this); // not working it is not elementt object
target = $(event.target);// this is element object ,working fine
target.parents("a.job_apply").prev().click();
};

In the backbone documentation, http://backbonejs.org/#View-delegateEvents
It states clearly:
All attached callbacks are bound to the view before being handed off
to jQuery, so when the callbacks are invoked, this continues to refer
to the view object.
This resolves one of the issues that many object-oriented programmers have with jQuery, that it overrides the context in all its event handlers. As stated earlier you can often use the event, which is passed as the handler's argument. However, event.target can often refer to child elements as opposed to this, which always refers to the element selected with the query. For instance:
<b>my link</b>
$('a').on('click', myfunction); function myfunction(event) {
this // refers to the "a" element
event.target // refers to the "b" element
}
In these cases, when attaching mouse events with Backbone to elements with children, use event.currentTarget to get a reference to the element that the event handler was attached to.
events: {
"click a" : "myFunction" },
...
function myFunction(event) {
var $this = $(event.currentTarget);
// Now, $this is the same as jQuery's $(this)
}

it all depends on the context,
this is in certain cases bound to your view itself,
take this example:
var myView = Backbone.View.extend({
events: { "click a" : "myfunction" },
initialize: function(){
_.bindAll(this, 'render', 'myfunction');
},
render: function(){
// rendering data here...
},
myfunction: function(e){
console.log(e.target); // will log the clicked DOM element => <a>
console.log(this); // will log the view => myView
}
});
window.v = new myView({ el : $('#mydiv') });
window.v.render();
as you can see, if you should run this example, you can see the differences between this and e.target. however, this is all due to the keyline in the initialize method.
_.bindAll(this, 'methodname', [ methodnames ] );
this binds the view to this when in 1 of the given methods.
if you would remove myfunction from that list, console.log(this); would log the element itself.
but, you would not be able to reach data or functions from your view....
you are free to chose if you bind the view in to this.

With events bound through a Backbone view, this will be set to the view itself. This is different from normal jQuery where this refers to the DOM element
So in a Backbone view:
events: { "click a" : "myfunction" },
...
function myfunction(e) {
this // refers to the view
e.target // refers to the element
}
Whereas in normal jQuery:
$('a').on('click', myfunction);
...
function myfunction() {
this // refers to the element
e.target // also refers to the element
}
In both cases, you can use e.target to refer to the element

Related

How to use events on backboneJS views

So I'm trying to learn how to use the backbone events using the documentation
but I'm stuck at the events part, when I click on the page class content it should alert "page tag has been clicked" but it throws an error on the commented line.
<body>
<div class="page"></div>
</body>
<script type="text/javascript">
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: function()
{
//Uncaught SyntaxError: Unexpected token :
"click .page" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
</script>
Your events property must be either a hash
events: {
"click .page" : "callme"
}
or a function that returns a hash
events: function() {
return {
"click .page" : "callme"
};
}
You indicate a .page selector but that's your view element. Either use a global selector
events: {
"click " : "callme"
}
or set your el to an ancestor node, for example
var view = new View({
el: 'body'
});
The events is actually just an object it isn't a function. It'll work with the code below. Hope that helps.
events: {
"click .page" : "callme"
}
The other error you have is that you're not actually using the Backbone view.
You create the View correctly but you're not appending it to the view as far as I can tell.
You'd need to do something like.
$('body').html(view.render().el);
That will append your view to the DOM and add all of the event listeners.
Also instead of passing in the el to the BackboneView you could just add the class of page onto the View. Example below.
className: page,
You actually have two problems with your code, the first is that you are using a function for our events hash without returning an object (using a function is fine, but you need to return an object, or bind directly to the events hash). The second is that your are listing for an event for a child element with the page class, which doesn't exist. You want to either remove the class from where you are binding to the event and just listen to a click anywhere in your view, or listen to an existing element.
For example
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: {
"click" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
http://jsbin.com/temicema/1/
That should be enough to get your code working, however it is probably worth understanding how backbone events work. When you specify an event in backbone that event is bound to the root el and and then listens to the events you specify matching the selector you specified. Under the hood backbone is basically using jQuery's .on to delegate the events, so in your case backbone is basically doing this.$el.on('click, '.page', this.callme).

Creating backbone views with models from other views

Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.

How can I bind the model to the view?

When the view is initialized, how can I bind the model to the specific View that is created? The view is current initialized at the start of the application. Also, how can I bind the model to the collection?
(function ($) { //loads at the dom everything
//Creation, Edit, Deletion, Date
var Note = Backbone.Model.extend({
defaults: {
text: "write here...",
done: false
},
initialize: function (){
if(!this.get("text")){
this.set({"text": this.default.text});
}
},
edit: function (){
this.save({done: !this.get("done")});
},
clear: function (){
this.destroy();
}
});
var NoteList = Backbone.Collection.extend({
model:Note
});
var NoteView = Backbone.View.extend ({
el: "body",
initialize: function(){
alert("initialized");
var list = new NoteList;
return list;
},
events: {
"click #lol" : "createNote"
},
createNote : function(){
var note = new Note;
this.push(note);
alert("noted");
}
});
var ninja = new NoteView;
})(jQuery);
Update
I just took a look at #James Woodruff's answer, and that prompted me to take another look at your code. I didn't look closely enough the first time, but I'm still not sure what you're asking. If you're asking how to have a model or view listen for and handle events triggered on the other, then check out James's example of calling bind() to have the view listen for change (or change:attr) events on the model (although I'd recommend using on() instead of bind(), depending what version of Backbone you're using).
But based on looking at your code again, I've revised my answer, because I see some things you're trying to do in ways that don't make sense, so maybe that's what you're asking about.
New Answer
Here's the code from your question, with comments added by me:
var NoteView = Backbone.View.extend ({
// JMM: This doesn't make sense. You wouldn't normally pass `el`
// to extend(). I think what you really mean here is
// passing el : $( "body" )[0] to your constructor when you
// instantiate the view, as there can only be one BODY element.
el: "body",
initialize: function(){
alert("initialized");
// JMM: the next 2 lines of code won't accomplish anything.
// Your NoteList object will just disappear into thin air.
// Probably what you want is one of the following:
// this.collection = new NoteList;
// this.list = new NoteList;
// this.options.list = new NoteList;
var list = new NoteList;
// Returning something from initialize() won't normally
// have any effect.
return list;
},
events: {
"click #lol" : "createNote"
},
createNote : function(){
var note = new Note;
// JMM: the way you have your code setup, `this` will be
// your view object when createNote() is called. Depending
// what variable you store the NoteList object in (see above),
// you want something here like:
// this.collection.push( note ).
this.push(note);
alert("noted");
}
});
Here is a revised version of your code incorporating changes to the things I commented on:
var NoteView = Backbone.View.extend( {
initialize : function () {
this.collection = new NoteList;
},
// initialize
events : {
"click #lol" : "createNote"
},
// events
createNote : function () {
this.collection.push( new Note );
// Or, because you've set the `model` property of your
// collection class, you can just pass in attrs.
this.collection.push( {} );
}
// createNote
} );
var note = new NoteView( { el : $( "body" )[0] } );
You have to bind views to models so when a model updates [triggers an event], all of the corresponding views that are bound to the model update as well. A collection is a container for like models... for example: Comments Collection holds models of type Comment.
In order to bind a view to a model they both have to be instantiated. Example:
var Note = Backbone.Model.extend({
defaults: {
text: "write here..."
},
initialize: function(){
},
// More code here...
});
var NoteView = Backbone.View.extend({
initialize: function(){
// Listen for a change in the model's text attribute
// and render the change in the DOM.
this.model.bind("change:text", this.render, this);
},
render: function(){
// Render the note in the DOM
// This is called anytime a 'Change' event
// from the model is fired.
return this;
},
// More code here...
});
Now comes the Collection.
var NoteList = Backbone.Collection.extend({
model: Note,
// More code here...
});
Now it is time to instantiate everything.
var Collection_NoteList = new NoteList();
var Model_Note = new Note();
var View_Note = new NoteView({el: $("Some Element"), model: Model_Note});
// Now add the model to the collection
Collection_NoteList.add(Model_Note);
I hope this answers your question(s) and or leads you in the right direction.

In a Backbone View's delegated event, how to access the source element of the event?

I am in the process of rewriting jQuery code to Backbone, but am stuck on a seemingly trivial issue:
Inside an event callback on an element that's a child of the main View element, how do I access the specific child element that was clicked (for example)?
var Composer = Backbone.View.extend({
events: {
"click #postnow": "postnow"
},
postnow: function(){
// #fixme:
var btn = $("#postnow");
// Do something
}
});
new Composer({el: $('.composer')});
In jQuery, I would use $(this), but in Backbone it refers to the View element, not the clicked child.
Is there any way to do that without explicitly specifying the selector again?
In view callbacks, you have access to the jQuery events and their properties, more specifically to event.currentTarget
Try
var Composer = Backbone.View.extend({
events: {
"click #postnow": "postnow"
},
postnow: function(e){
var btn = $(e.currentTarget);
}
});
A implicit parameter is sent to the function callback this parameter is a jQuery Event Object and it contains a lot of information including the DOM Element where the event was targeted:
var Composer = Backbone.View.extend({
events: {
"click #postnow": "postnow"
},
postnow: function( event ){
console.log( "postnow()" );
console.log( "this", this );
console.log( "event", event );
console.log( "target", event.target );
console.log( "data-value", $(event.target).attr( "data-value" ) );
}
});
Check the working jsFiddle

marionette simple event delegation

I am trying to add a simple event to the children under my compositeview but it is not triggering at all..and frankly I am not sure why, it seems so simple, I could do this just fine with normal backbone.view.
In the example below, the alert is not triggered at all, however when I purposefully change the function name the event binds to , to something else that doesnt exist, it complaints that the function doesnt exist, so I think it's something else...help?
App.View.ContentContainer = Backbone.Marionette.CollectionView.extend({
className:'content_container',
itemView:App.View.ContentBrowseItem,
events:{
'click .browse_item':'focus_content'
},
initialize:function () {
//this.views = {} //indexed by id
//this.create_modal_container()
var coll = this.collection
coll.calculate_size()
coll.sort_by('title', -1)
},
focus_content:function (e) {
alert('here???')
var $modal_container = this.$modal_container
var content_id = $(e.currentTarget).data('content-id')
var $selected_view = this.views[content_id]
var $focused_content = new App.View.FocusedItem({model:$selected_view.model})
$modal_container.empty().show().append($focused_content.el).reveal().bind('reveal:close', function () {
$focused_content.close()
})
return false
},
onShow:function(){
this.$el.addClass('content_container').isotope({
selector:'.content_item',
resizable:true,
layoutMode:'masonry',
masonry:{ columnWidth:64 }
})
}
EDIT: this is the resulting HTML: http://pastebin.com/uW2X8iPp the div.content_container is the resulting el of App.View.ContentContainer
Is .browse_item a selector for the App.View.ContentBrowseItem itemView element? In that case, you need to bind the event in the ItemView definition, not in the CollectionView definition. The reason is that events are bound when a view is rendered. The CollectionView itself is rendered before any of its child itemViews.
Also, if you are opening up another modal view on this click event, I would let the app handle that, rather than your CollectionView
Try something like this:
App.View.ContentBrowseItem = Backbone.Marionette.ItemView.extend({
...
initialize: function() {
// Maintain context on event handlers
_.bindAll(this, "selectItem")
},
events: {
"click" : "selectItem"
}
selectItem: function() {
App.vent.trigger("item:select", this.model);
}
...
});
And to actually show the modal detail view:
App.vent.on("item:select", function(itemModel) {
var detailView = new App.View.FocusedItem({ model: itemModel });
// You may also want to create a region for your modal container.
// It might simplify some of your `$modal_container.empty().show().append(..).etc().etc()
App.modalRegion.show(detailView);
});
Allowing each of your views to handle their own events is part of what makes Backbone and Marionette so beautiful. You'll just want to avoid one view getting all up in another view's business (eg. a CollectionView trying to handle its ItemView's events, an ItemView creating event bindings to show and close a separate modal view, etc.)
Hope this helps!

Resources