Is Backbone's view 'events' property only for DOM events? - javascript

I've got this piece code in a view:
//dom events -----
,events:{
'click #language-switcher input[type="radio"]': function(e){
this.current_language = $(e.target).val();
}
,'click .create-gcontainer-button': function(){
this.collection.add(new Group());
}
}
,set_events:function(){
//model events -----
this.listenTo(this.collection,'add',function(group){
var group = new GroupView({ model: group });
this.group_views[group.cid] = group;
this.groups_container.append(group.el);
EventTools.trigger("group_view:create",{ lang:this.current_language });
});
this.listenTo(this.collection,'destroy',function(model){
console.log('removing model:', model);
});
//emitter events ---
EventTools.on('group_view:clear',this.refresh_groups, this);
}//set_events
Note: set_events gets called on initialization.
Well, I don't like defining events in 2 different places, but since the docs say that events defined from the 'events' prop are bound to the element (or children of it if a selector is passed), I guess I cannot use it for other types of events. Am I correct?
I also tried to define 'events' from inside my set_events function, but for some reason that leads to a memory leak or something similar (browser gets stuck).
So another more general question could be: on a Backbone view, is there a way to define all the events in one single place?

Within a View, there are two types of events you can listen for: DOM events and events triggered using the Event API. It is important to understand the differences in how views bind to these events and the context in which their callbacks are invoked.
OM events can be bound to using the View’s events property or using jQuery.on(). Within callbacks bound using the events property, this refers to the View object; whereas any callbacks bound directly using jQuery will have this set to the handling DOM element by jQuery. All DOM event callbacks are passed an event object by jQuery. See delegateEvents() in the Backbone documentation for additional details.
Event API events are bound as described in this section. If the event is bound using on() on the observed object, a context parameter can be passed as the third argument. If the event is bound using listenTo() then within the callback this refers to the listener. The arguments passed to Event API callbacks depends on the type of event. See the Catalog of Events in the Backbone documentation for details.
Yes you can define all the events in the view initialize method, see the below example
// Add your javascript here
var View = Backbone.View.extend({
el: '#todo',
// bind to DOM event using events property
initialize: function() {
// bind to DOM event using jQuery
this.events = {
'click [type="checkbox"]': 'clicked'
};
this.$el.click(this.jqueryClicked);
// bind to API event
this.on('apiEvent', this.callback);
},
// 'this' is view
clicked: function(event) {
console.log("events handler for " + this.el.outerHTML);
this.trigger('apiEvent', event.type);
},
// 'this' is handling DOM element
jqueryClicked: function(event) {
console.log("jQuery handler for " + this.outerHTML);
},
callback: function(eventType) {
console.log("event type was " + eventType);
}
});
you can see the demo here
for your code
set_events:function(){
//dom events -----
this.events={
'click #language-switcher input[type="radio"]': function(e){
this.current_language = $(e.target).val();
}
,'click .create-gcontainer-button': function(){
this.collection.add(new Group());
}
};
//model events -----
this.listenTo(this.collection,'add',function(group){
var group = new GroupView({ model: group });
this.group_views[group.cid] = group;
this.groups_container.append(group.el);
EventTools.trigger("group_view:create",{ lang:this.current_language });
});
this.listenTo(this.collection,'destroy',function(model){
console.log('removing model:', model);
});
//emitter events ---
EventTools.on('group_view:clear',this.refresh_groups, this);
}//set_events

As I commented above, for some strange reason, placing events inside initialize() or set_events() fails silently. I found out that doing one of the 2, backbone doesn't find the events. This backbone's view's function says undefined to console:
delegateEvents: function(events) {
events || (events = _.result(this, 'events'));
console.log(events); //outputs undefined
if (!events) return this;
this.undelegateEvents();
for (var key in events) {
var method = events[key];
if (!_.isFunction(method)) method = this[method];
if (!method) continue;
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
}
return this;
},
Though, if I place events like a regular view property, just as my code on the main question, it will correctly log the events.

Related

How to simulate a dataTransfer object? [duplicate]

I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:

Difference between events and delegateEvents?

I'm reading the docs for Views and I'm confused about what the difference is between events and delegateEvents. They both seem to be called the events method on the View object.
The confusing part to me is what the key should be inside the events hash.
From the docs:
delegateEvents([events]) Uses jQuery's on function to
provide declarative callbacks for DOM events within a view. If an
events hash is not passed directly, uses this.events as the source.
Events are written in the format {"event selector": "callback"}
But events from the standard events are written differently:
var InputView = Backbone.View.extend({
tagName: 'input',
events: {
"keydown" : "keyAction",
},
So how are events supposed to be written? Can you combine the two syntaxes?
delegateEvents is the function on the view which is called to apply the events from the events view property.
Omitting the selector causes the event to be bound to the view's root element (this.el). By default, delegateEvents is called within the View's constructor for
you, so if you have a simple events hash, all of your DOM events will always already be connected, and you will never have to call this function yourself.
This is happening inside the setElement function (line 1273):
setElement: function(element) {
this.undelegateEvents();
this._setElement(element);
this.delegateEvents();
return this;
},
So the syntax is the same and both syntax works.
var InputView = Backbone.View.extend({
events: {
// keydown event from ".sub-element", which must be a child of the view's root
"keydown .sub-element" : "keyAction",
"keydown" : "keyAction", // keydown event from the root element
},
});
Inside the delegateEvents function, the key (e.g. "keydown .sub-element") is split using a regex (line 1311).
var match = key.match(delegateEventSplitter);
this.delegate(match[1], match[2], _.bind(method, this));
The regex to split the event from the selector (line 1227):
// Cached regex to split keys for `delegate`.
var delegateEventSplitter = /^(\S+)\s*(.*)$/;
And the delegate function (line 1317):
// Add a single event listener to the view's element (or a child element
// using `selector`). This only works for delegate-able events: not `focus`,
// `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
delegate: function(eventName, selector, listener) {
this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
return this;
},

Event delegation for objects; How to listen for events?

How can I listen for custom events on a javascript object? I'm trying to have a method of an app level object fire once an event is triggered, but unlike DOM elements, I cant register the listener for the object.
How can I structure my application to listen for triggered events, and then perfrom an action?
js:
$(document).ready(function(){
var app = {};
app.doStuff = function(){ alert("Yo!");};
$(app).on("myCustomEvent", function(){
alert("doing stuff");
app.doStuff();
});
$("body").on('click', "#trigger", function(){
alert("triggered");
$(this).trigger("myCustomEvent");
});
});
reporduced in a jsbin:
http://jsbin.com/zopubuso/1/edit
The reason behind this:
I'm creating a backbone application, in which I have a global event vent inside my app object. I'm trying to communiate between views by triggering events:
ie.
app.on("myEvent", MyView.model.doStuff);
app.trigger("myEvent");
But I'm unable to get the events to fire.
try:
trigger with jquery:
$(app).trigger("myCustomEvent");
http://jsfiddle.net/9w8A5/
trigger as backbone event:
first You will need to extend the object with Backbone.Events:
var app = {};
_.extend(app, Backbone.Events); // underscore.js '_.extend', We can also use jquery '$.extend()'
... on("myCustomEvent")...
app.trigger("myCustomEvent");
demo: http://jsfiddle.net/A5484/1/
Update from agconti.
Here's a simple way to accomplish attaching events in backbone:
var app = _.extend({}, Backbone.Events);
var myView = Backbone.View.extend({
initialize: function(){
... other configuration stuff ...
app.on( "myCustomEvent", function(){
this.doStuff();
}, myView);
},
doStuff: function(){ alert("Yo!"); }
});
app.trigger('myCustomEvent');
* its much more advantageous to use backbone's event object then attaching a listener with app.on(); like i was trying to do.

How to trigger Backbone event with "qualifier"

I want to trigger a render event when my views are being rendered.
function Renderer() {
_.extend(this, Backbone.Events);
};
Renderer.prototype.render = function(view, model) {
this.trigger('render:before');
// Do some checks to see how
// we should render the view
// and then call render
this.trigger('render:after');
};
var renderer = new Renderer();
Now I can register for events on the Renderer, but I must use the full name. I.e. this works:
renderer.on('render:before', function() { console.log("before rendering"); });
renderer.on('render:after', function() { console.log("after rendering"); });
renderer.on('all', function() { console.log("All events from renderer"); });
But this does not:
renderer.on('render', function() { console.log("Any rendering events"); });
I expected the last one to be equivalent to registering on all events for the renderer.
Is there a way to make listening to render equivalent to listening for both render:before and render:after?
Namespacing event names by using the colon is just a convention:
If you have a large number of different events on a page, the
convention is to use colons to namespace them: "poll:start", or
"change:selection".
The source code of Events.trigger shows that the event handler to be called is searched for by the full name of the event, independently of whether it contains a colon or not:
var events = this._events[name];
...
if (events) triggerEvents(events, args);
You can:
define and trigger an 'all' event,
trigger multiple event handlers by calling trigger with a space-delimited list of event names, or
modify the source code of Events.trigger in backbone.js to add this feature.

Pass return values as arguments between Backbone View methods

I am trying to make a little application with a view that handles mousedown and mouseup jQuery events.
I have a getCoords method that returns the coordinates of a given mouse event
I call that method from determineEvent, and store the coordinates of the mousedown in mouseDownCoords. It also returns the mouseDownCoords variable. The purpose of this method is to distinguish between a click and a drag event, but I ommited the code for brevity)
The method doStuff is triggered on mouseup and stores the coordinates of the mouseup in mouseUpCoords. The problem is that I don't know how to access the mouseDownCoords variable from within this method.
I though of triggering the event manually, through code, but that doesn't make much sense (triggerring a mouseup event from a mousedown eventhandler...)
Also calling the determineEvent method from doStuff, doesn't make sense because I'm passing a different event to it (mouseup)
Here's my view code
var ScreenView = Backbone.View.extend({
tagName: 'div',
className:"screen",
events: {
"mousedown": "determineScreenEvent",
"mouseup": "doStuff"
},
getCoords: function(e) {
var screenOffset = this.$el.offset();
var coords = [e.pageX - screenOffset.left, e.pageY - screenOffset.top];
return coords;
},
determineEvent: function(e) {
mouseDownCoords = this.getCoords(e);
return mouseDownCoords;
//code for distinguishing between click/drag
},
doStuff: function(e, mouseDownCoords) {
mouseUpCoords = this.getCoords(e);
//do stuff with mouseUpCoords and mouseDownCoords
}
});
Any pointers would be helpful :)
Store it in the view if you want a quick solution:
determineEvent: function(e) {
this.mouseDownCoords = this.getCoords(e);
return this.mouseDownCoords;
//code for distinguishing between click/drag
},
doStuff: function(e, mouseDownCoords) {
var mouseUpCoords = this.getCoords(e);
//use this.mouseDownCoords
//do stuff with mouseUpCoords and mouseDownCoords
}
W.r.t Backbone you should have a Model attached to view that will contain the necessary data which is required to render the view.
I recommend that you should add a model to view and set these mouseDownCoords and mouseUpCoords as part of that model that will be accessible within the view at any given time.
If your don't want to strictly follow Backbone way then simply store the coordinates as varibales in view but this is not recommended

Categories