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.
Related
I'm building a file uploader plugin for studying purposes, and I'm struggling trying to get my callback working the way I want to. Briefly, this widget opperates on an input field with the type file. A little of code to explain better:
$.widget('ultimatum.uploadify', {
create: function() {
// Irrelevant code here
},
_onChange: function(event) {
// Private function that is fired when an "change" event
// is triggered by the input.
var files = event.target.files;
var fileInfo = {};
// When the file processing finish, I want to trigger this custom event:
this._trigger("fileready", null, fileInfo);
}
});
Ok, doing this way, I can handle the callback like so:
$('#upload-files').uploadify({
fileready: function(event, file) {
// My code here
}
});
The problem is that I want to handle this event like so:
$('#upload-files').uploadify();
$('.upload-files').on('fileready', function(event, file) {
// My code here.
});
Although the former way works perfectly well, the latter doesn't. Is it possible to handle custom jQuery events this way, using "on"?
From http://api.jqueryui.com/jQuery.widget/
Events
All widgets have events associated with their various behaviors to notify you when the state is changing. For most widgets, when the events are triggered, the names are prefixed with the widget name and lowercased. For example, we can bind to progressbar's change event which is triggered whenever the value changes.
$( "#elem" ).bind( "progressbarchange", function() {`
alert( "The value has changed!" );
});
Each event has a corresponding callback, which is exposed as an option. We can hook into progressbar's change callback instead of binding to the progressbarchange event, if we want to.
$( "#elem" ).progressbar({
change: function() {
alert( "The value has changed!" );
}
});
All widgets have a create event which is triggered upon instantiation.
So for your widget, this would look like:
$('#upload-files').uploadify();
$('#upload-files').on('uploadifyfileready', function(event, file) {
// My code here.
});
As I mentioned in the comment, I think that $('.upload-files') may be a typo, and that the proper selector is $('#upload-files').
I'm attempting to write some Javascript objects to manage dynamic forms on a page.
The forms object stores an array for forms and renders them into a container.
I'd like to have click events for certain fields on each form so decided to make a seperate object and tried to bind an event inside the objects init method.
The init method is clearly fired for every new form that I add. However on change event only ever fires for the last form object in my array.
JS Fiddle Demonstrating Issue
can be found: here
function Form(node) {
this.node = node;
this.init = function() {
$(this.node).find("input:checkbox").change(event => {
console.log('Event fired');
});
};
this.init();
}
// Object to manage addition / removal
var forms = {
init: function() {
this.formsArray = [];
this.cacheDom();
this.bindEvents();
this.render();
}
// Only selector elems from the DOM once on init
cacheDom: function() { ... },
// Set up add / remove buttons to fire events
bindEvents: function() { ... },
render: function() {
for (let form of forms)
this.$formSetContainer.append(form.node)
}
addForm: function() {
// Logic to create newRow var
this.formsArray.push(new Form(newRow));
},
removeForm: function() {
// Logic to check if a form can be removed
this.formsArray.pop();
}
},
What I've Tried Already
I'm actually able to bind events inside render by removing this.init() inside the Form constructor and altering render like so:
for (let form of this.formsArray) {
this.$formSetContainer.append(form.node)
form.init();
}
Then the events will successfully fire for every form
But I'd rather not have this code run every time I call render() which is called every time I add / remove forms.
I have a feeling that this is a scoping issue or that the event is somehow being clobbered. Either that or I'm misunderstanding how events are bound. Any pointers would be appreciated
Looking at the code in the JSFiddle, the problem comes from using this.$formSetContainer.empty() in the render function. .empty() removes all the event handlers from your DOM nodes.
To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.
If you want to remove elements without destroying their data or event handlers (so they can be re-added later), use .detach() instead.
https://api.jquery.com/empty/
You can replace this with this.$formsetContainer.children().detach() and it will do what you want.
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;
},
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.
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