At the company I work, we've decided to extract most of our Marionette.js code and split them up into several Rails Gems.
At this moment, I've managed to split up Marionette Controllers and Views. Some of the stuff inside Marionette Controllers are necessary for most of our eCommerce apps, so I extracted this JavaScript code into a few Gems. We'll then be able to use the stuff we've already split up into Gems and override / extend Controllers/Views when necessary.
For app-wide actions, we use Backbone.Wreqr. An example of an event handler found in one of our Gems:
Backbone.Wreqr.radio.commands.setHandler('product', 'show', function (slug) {
API.showSingleProduct(slug);
App.navigate("/products/" + slug);
});
However, for some of our apps, we'll need to override this handler to make it work with some custom stuff in our project.
Imagine I want to modify the above handler to something like this:
Backbone.Wreqr.radio.commands.setHandler('product', 'show', function (slug, color) {
API.showSingleProduct(slug, color);
App.navigate("/products/" + slug);
});
When I add such an event handler in our base project, the event will be called twice. I actually only need the last (customized) event handler to be fired.
Is there any way to override existing Backbone.Wreqr events? Or should I remove the previous handlers every time I want to customize the behaviour of a previous handler?
There's no need to remove the previous handler, setting a new one effectively overrides any handler assigned to the same namespace.
Your code should work in terms of registering a single handler, the problem must be somewhere else.
Related
Summary:
This is ultimately a question about Vue.js(although I assume it applies to JavaScript in general).
First a bit of background:
I noticed something in the Angular style guide which serves as a basis for the question about Vue.
Angulars style guide states to prefix custom event handler methods with on. https://angular.io/guide/styleguide#style-05-16
Question:
Back to Vue:
Why would you name an event handler like this:
<CustomComponent #customEvent="onCustomEvent"/>
versus a more descriptive method name like this:
<CustomComponent #customEvent="setUserData" />
You might need to do more than one thing in the handler.
In those cases it becomes hard to name the handler itself, so you just name the handler i.e handleClickEvent.
handleClickEvent: function() {
this.setUserData()
this.logClick()
this.showNotification()
}
I've never used Vue but I've used Polymer which deals with element events as well and this is one of the cases where I forgo naming the handler something specific.
I'm working with a 3rd party product where I am extending the UI with my own custom functionality. Within part of that I need to call an event after the UI has been updated with an AJAX call. Luckily the app fires a call to a Custom Event using the Prototype JS library after the call is complete, like this:
$(document.body).fire("ns:customevent");
If I add my own custom event with the same name then this works as expected
$(document).observe("ns:customevent", function(event) {
//do custom stuff here
});
[I could not get $(document.body).observe() to work here but I don't think that really matters.]
My concern here is that there may be other parts of the app that have registered functions against that event, and I am (blindly) overwriting them with my own, which will lead to issues further down the line.
Does Prototype append custom functions even though they have the same name or does it in fact overwrite them? If it does overwrite them then how can I append my function to anything that is already existing? Is there anyway of viewing what
I would imagine something like this, but I hardly know Protoype and my JS is very rusty these days.
var ExistingCustomEvent = $(document.body).Events["ns:customevent"];
$(document).observe("ns:customevent", function(event) {
ExistingCustomEvent();
//do custom stuff here
});
I can't add my event handler or add in code to call my own function, I want to try avoiding the 3rd party library (if that would even be possible).
Thanks.
As an FYI for anyone else that stumbles upon this question, following the comment from Pointy it turns out that Prototype does append the functions to the custom event.
I verified this by trying the following and both alerts fired.
$(document).observe("ns:customevent", function(event) {
alert("ALERT 1");
});
$(document).observe("ns:customevent", function(event) {
alert("ALERT 2");
});
Great :)
My requirements
Because of the asynchronous architecture of my applications I am looking for an 'event' system which has the following two two properties:
The events should be able to fire multiple times (possible with events, but not with promises)
When I start listening for an event that has already been fired, I want the listener to fire once immediately (as with promises)
The reason for 1. is that there are a lot of events (e.g. the updating of certain data) that I want to be able to fire multiple times. But I would like to combine this with 2. so that if an event has already fired upon adding the listener, this listener gets called immediately. This is because I'm not always sure (and I don't want to be sure) which piece of code gets run first.
My 'solution'
I have thought up the following solution. I'm using this in an AngularJS application therefore the AngularJS context, but the question is applicable for Javascript in general. Note that I simplified the code.
app.controller('AppCtrl', function(CustomEventEmitter){
// Broadcast an event. No listener added so nothing happens
CustomEventEmitter.broadcast('event');
// Add the event listener. Because the event allready fired, the listener gets called immediatly
CustomEventEmitter.on('event', function(){
console.log('Event emitted');
});
// Broadcast an other event
CustomEventEmitter.broadcast('event');
});
app.service('CustomEventEmitter', function(){
var
listeners = {},
memory = [];
this.broadcast = function(name){
// The normal broadcasting of the event to the listener
if(listeners[name]) {
listeners[name].forEach(function(listener){
listener();
});
}
// Push the event into the 'memory'
memory.push(name);
};
this.on = function(name, listener){
// The normal adding of the listener
if(!listeners[name]) {
listeners[name] = [];
}
listeners[name].push(listener);
// If an event is already in memory, call the listener
if(memory.indexOf(name) !== -1) {
listener();
}
};
});
My questions
My questions are these:
What is the 'best practice' solution for my requirements?
What do you think of my 'solution'?
Am I missing something completely obvious?
The reason for the last question is that it seems to me that this is a very common design paradigm but I seem unable to find the best way to solve this in simple and concise way.
Note
I understand this can be solved with the adding of extra code (e.g. before adding the listener, check in an other way if the event you are going to listen for already happened) but this is not what I'm looking for.
A "property" from bacon.js does exactly what you are asking for. This falls under the broader category of functional reactive programming (FRP). The most popular two libraries for this in JavaScript are probably
bacon.js
Reactive Extensions
Both of which provide the specific tool you're asking for, along with a vast array of alternatives.
Good day to all.
I'm writing an application using Marionette.js and recently I started noticing that moving from view to view and starting/stopping different modules memory consumption grows and not getting released. I started wondering whether I unbind my events correctly and whether I bind to them correctly as well.
So, I have the following cases
Modules
My application consists of sub-applications (modules). When I define a module I do some binding to global event aggregator. Something like this:
MyApplication.module(...) {
var api = { ... some functions here ... }
// Binding to events
MyApplication.vent.on('some:event', function() {...});
MyApplication.vent.on('some:other:event', function() {...});
}
I have checked the documentation and understand that "on" is not a very good choice, I should probably use "listenTo":
MyApplication.module(...) {
var api = { ... some functions here ... }
// Binding to events
this.listenTo(MyApplication.vent, 'some:event', function() {...});
this.listenTo(MyApplication.vent, 'some:other:event', function() {...});
}
But, here is the question, when module gets stopped, does it call "stopListening" or some other internal method that unbinds all the events I have bound in it? I checked the source code of the marionette's module and documentation but, if I understood correctly, when stop is called I need to take care of unbinding everything myself. Am I right?
Controllers
Can be initialized and closed. From the documentation I see that:
Each Controller instance has a built in close method that handles unbinding all of the events that are directly attached to the controller instance, as well as those that are bound using the EventBinder from the controller.
Does it mean that if do the following I correctly unbind all of the events I bound in the controller? I guess the answer is yes.
MyApplication.module(...) {
var controller = Marionette.Controller.extend({
...
// This will be unbinded as I understand?
this.listenTo(someObject, 'some:event', _.bind(function() {
// This will also be unbinded
this.listenTo(someOtherObject, 'some:event', function() {
// This won't be, because in this case this is not in a "controller"
// context but in a function's context which wasn't bound to "controler"
// context.
this.listenTo(some3rdObject, 'some:event', function() { ... });
});
}, this));
});
// Create controller when this sub-application gets initialized.
Contents.addInitializer(function () {
MyModule.Controller = new controller();
});
// Destroy controller and unbind all its event handlers.
Contents.addFinalizer(function () {
MyModule.Controller.close();
delete Contents.Controller;
});
}
So, with controllers I don't need to do anything as long as I use "listenTo", correct?
Views
In views, according to documentation, all gets unbinded when the view gets closed. And again, as long as I use
this.listenTo(..., 'some:event', function() {...});
I should be ok, correct?
To summarize... I only need to take care of unbinding in module's stop event, in all other cases it is taken care of by marionette's core as long as I don't use direct "on" and use "this.listenTo" instead.
Thank you all very much in advance for your answers.
Controllers and Views do their cleaning work correctly but Modules doesn't do it.
Here is more detailed info:
Controller
If you close controller it will unbind all events that are bonded using listenTo in context of controller. You can look in in controller source code.
View
According to Backbone.View source code remove method does stopListening. Also Marionette.View's close calls backbone's remove under the hood. Here is source code.
Module
I've checked Marionette.Module source code but there is no stopListening in stop method. So, Marionette.Module#stop does not do unbinding of events and you should do it manually in finalizer or in onStop, onBeforeStop handlers.
UPDATED: After Marionette.js v1.7.0 Marionette.Module calls stopListening on stop to unbind all events.
In jQuery plugins, which way do you think it's best to allow a function to be hooked in your plugin - trough triggers, or options (arguments) passed in the plugin function?
$.trigger('myplugin_completed', someData);
$(document).bind('myplugin_completed', function(event, someData){ ... });
vs
myPluginOptions.onComplete(someData);
$('.stuff').myPlugin({onComplete: function(someData){ ... }});
Solution
I think the best solution - at least in the case you described (maybe not in all the possible cases) - is to combine both in the following way:
$('.stuff').myPlugin(/* some options here */);
$('.stuff').trigger('myplugin.completed', someData);
and this line:
$('.stuff').on('myplugin.completed', function(event, someData){
/* callback code */
});
is responsible for binding the event handler. It can be called also when someone passes onComplete callback within options when initializing your plugin (of course selector should be adjusted to meet the one used by the code initializing your pugin).
Summary
To sum up, you could:
create your own event (completed),
use your plugin's namespace for this event / plugin (in this case myplugin),
use .on() function (available and preferred since jQuery 1.7),
if onComplete option is passed to your plugin, there is no problem in binding it within the code initializing the plugin (so from within the plugin, using the .on() function, binding event handler to your event name within your even namespace).
I will vote for the second option, because in this way one can control the onComplete event, and it is binded only to the element. Binding it to documents it is not good, because one can do $(document).unbind(), that unbind all events.
I think both ways are good and none of them has big advantage over another.
For example, jQuery UI use callback for options and event triggers for actual events such as start/stop dragging.
But, by creating event triggers in code you create maybe more flexible way to add more event handlers, without modyfing existing code. I mean when you got callbacks, it goes this way:
{onComplete: function(someData){ action_1; }}
If you need some extra actions you write them into existion function or put functions inside:
{onComplete: function(){ action_1; action_2 }}
or
{onComplete: function(){ action_1; function_2(); }}
function_2(){ action_2 };
For comparison using events it would be like:
$('selector').on('myplugin_completed.myplugin', function_1 })
Extra actions:
$('selector').on('myplugin_completed.myplugin_extra', function_2 })
If you don't need some actions you can unbind only them.
$('selector').off('myplugin_completed.myplugin_extra');
There are differences between them, but usually it depends on particular situation, which one is better;