I'm working on developing a rather lightweight realtime application.
We're using jQuery's event system to talk between modules in our application. So everything that makes changes in the DOM that should affect anything outside the module must do so by events.
These events are caught by a clientside socket.io implementation which asks the nodejs socket.io server to broadcast the events to all other clients.
MY QUESTION:
Is there a way figure out on what selector the event was triggered? So that if I call
$("tag.class").trigger("myevent");
I can somehow do
$(document).on("myevent", function() {
console.log(this.selector)
});
And it will print "tag.class"
This is interesting because it would be interesting to know that a user triggered a certain event on a selection, rather than a certain event on a number of elements which might appear quite random afterwards.
I have read this question but I wish to be able to get the selector on all events fired in the application.
Thank you for your time :)
Taking inspiration from the linked answer, you can override the jQuery trigger function. Something like:
$.fn.origTrigger = $.fn.trigger;
$.fn.trigger = function (fn) {
var selector = this.selector;
this.origTrigger(function (ev) {
fn(ev, selector);
});
};
Your best bet is to send data with the event. It is also more robust, as you have more control. Selectors might not be pretty, but the data are.
$('tag.class').trigger('myevent', ['some', 'param']);
$(document).on('myevent', function(param1, param2) {
console.log(param1); // "some"
console.log(param2); // "param"
});
Of course, if you want to send as many parameters as you want, you can use an "options" object and handle it in such a way:
$('tag.class').trigger('myevent', [{some: 'option', other: 'option'}]);
$(document).on('myevent', function(options) {
var opts = {
default: 'option',
some: 'default',
other: 'default'
};
$.extend(opts, options);
console.log(opts); // {default: "option", some: "option", other: "option"}
});
Related
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:
I'm looking for a simple event aggregator that works with require.js. I have two modules, one containing a view model and another with a "listener" of some sort:
// view model
define(['lib/knockout-2.2.1', 'events/aggregator'], function(ko, events){
var squareViewModel = function(contents) {
this.click = function(){
events.publish('squareClicked', this);
};
};
return squareViewModel;
});
// some listener of some kind
define(['events/aggregator'], function(events){
events.subscribe('squareClicked', function(e){
alert("hurrah for events");
});
});
Is there anything out there that does this? Is this kind of architecture even a good idea? This is my first foray into client-side architecture.
This is similar to what you posted, but I've had good luck with extending Backbone events (you don't actually have to use anything else about Backbone for this to work), something like this:
define(['underscore', 'backbone'], function( _, Backbone ) {
var _events = _.extend({}, Backbone.Events);
return _events;
});
And then all your viewmodels (or any code really) can use it:
define(['knockout', 'myeventbus'], function(ko, eventBus){
return function viewModel() {
eventBus.on('someeventname', function(newValue) {
// do something
});
this.someKOevent = function() {
eventBus.trigger('someotherevent', 'some data');
};
};
});
The original idea came from this article by Derick Bailey. One of my other favorites on client-side events is this one by Jim Cowart
Again, it amounts to nearly the same thing as what you posted. However, the one thing I don't like about the approach of tying it to jQuery document DOM node is that you might have other types of events flying through there as well, bubbled up from child nodes, etc. Whereas by extended backbone events you can have your own dedicated event bus (or even more than one if you e.g. wanted to have separate data events vs. UI events).
Note regarding RequireJS in this example: Backbone and Underscore are not AMD-compatible, so you'll need to load them using a shim config.
I ended up using jQuery to make my own:
define([], function(){
return {
publish: function (type, params){
$(document.body).trigger(type, params);
},
subscribe: function(type, data, callback){
$(document.body).bind(type, data, callback);
},
};
});
It works for what I want it for, but it has not been extensively tested.
As explunit points out in his answer, this will capture any events on document.body. I also discovered a scoping issue when accessing this inside of a passed callback function.
I have a search plugin that is decently complex: it has different versions of UI and functionality as well as a bunch in interdependent domElements. Multiple instances of the plugin will exist on a page at once.
I am using the basic jQuery authoring pattern: http://docs.jquery.com/Plugins/Authoring
In order to save the options, interdependent events and and all sorts of dom lookups across multiple objects, I've come to passing the element in question to every function, and storing state/options/interdependencies in a data attribute which I retrieve each time. It works, and keeps events from colliding, but it seems like a messy way to write code.
What is the best way to store state across multiple instances? Is the way I am doing it a huge overkill and I am missing something? It probably stems from my misunderstanding of creating class like objects in a jQuery plugin pattern.
(function($) {
var _options = {};
var methods = {
init: function(options) {
return this.each(function() {
if (options) {
_options = $.extend($.fn.examplePlugin.defaults, options);
} else {
_options = $.fn.examplePlugin.defaults;
}
$this = $(this);
var data = $this.data('examplePlugin');
if (!data) {
$this.data('examplePlugin', {
target: $this
});
$.each(_options, function(key, value){
$this.data('examplePlugin')[key] = value;
});
data = $this.data('examplePlugin');
}
//Cache dom fragment plugin is in (if passed)
if (data.domContextSelector == null || data.domContextSelector == "") {
data.domContext = $(body);
} else {
data.domContext = $(data.domContextSelector);
}
init($this);
});
}
};
var init = function(element) {
data = getData(element);
//Storing dom elements to avoid lookups
data.relatedElement = $(data.relatedElementSelector, data.domContext);
element.click(function(event){
doSomethingCool($(event.currentTarget));
});
};
var doSomethingCool = function(element) {
data = getData(element);
element.slideUp();
data.relatedElement.slideDown();
};
var adjustHeight = function(element) {
data = getData(element);
element.height(data.relatedElement.height());
};
var getData = function(element) {
return $(element).data('examplePlugin');
};
$.fn.examplePlugin = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
}
else {
$.error('Method ' + method + ' does not exist on jQuery.examplePlugin');
}
return false;
};
$.fn.examplePlugin.defaults = {
defaultA: 'something',
relatedElementSelector: '#related',
domContextSelector: 'header.header'
};})(jQuery);
Yup, if you follow the jQuery guide, you are building it according to how it's supposed to be built and taking advantage of what it was designed to do (especially chaining).
However, I don't necessarily follow that path. There are a lot of ways you can do these plugins, take for example this guy who made a boilerplate for jQuery plugins which are NOT based on jQuery's design but rather in the OOP perspective (which I prefer). I see it as cleaner, but has the sacrifice of not following the usual syntax (the element.myPlugin({options}) and not being able to chain (until you modify a bit)
The same guy has an older post which is a boilerplate for the usual jQuery plugin design.
I've found your tweet when checking how my plugin saves a state, while learning plugin developing along this tutorial:
http://tutsplus.com/lesson/head-first-into-plugin-development/
In this massive lesson, we’ll dive into jQuery plugin development.
Along the way, we’ll review various best practices and techniques for
providing the highest level of flexibility for the users of your
plugins.
Personally, I suggest sticking to what the jQuery team recommends, in terms of plugin design patterns. It helps keeps consistency, and makes your plugin more community friendly.
Having said that...
I've run into the problem of trying to keep the state of multiple elements as well. One solution I've found is to use the jQuery Data API (which looks like this: $( selector ).data( key, value ) ) to keep meta information like an element's state or the application state.
The nice thing about using data() is that it's not updating/acessing the DOM, rather it's using jQuery's internal meta stuff, so it's faster to access than trying to store info hidden input fields, changing class names, or doing other funky tricks that developers have tried to use to store data on the clientside. ( Keep in mind too that you don't need to use the HTML5 doctype to use the data API, but if you do data-*key attributes are extremely helpful! )
It gets tricky when all the elements have their own states but the current element is the one that is controlling the overall plugin state. For this scenario I use the body tag to store data bout the current element, something like this:
$('body').data('myPluginNameSpace.current', selectorRef );
That way, when I need to check the state of my plugin/page/application, or listen for my plugin-specific event that's bubbled up to the document object, I can do a quick lookup for the current/selected element, and apply any UI changes or behaviors to it:
var currentElementRef = $('body').data('myPluginNameSpace.current');
doFunStuff( currElementRef );
There are a number of other ways you can do this too, like creating a custom Event object and attaching custom parameters to it:
var myPluginEvent = jQuery.Event( 'customEvent.myPluginNameSpace', { myProp : myValue });
$( document ).trigger( myPluginEvent );
When your custom Event gets triggered and later handled via a callback function, your custom parameters are attached to the Event Object passed to the handler:
$( document ).on( 'customEvent.myPluginNameSpace', function( e ){
doStuff( e.myProp ); //you can access your custom properties attach to the event
});
You can get to the same destination via many, different roads; that's the beauty and horror of JavaScript.
In your particular case keep in mind that you don't have to have everything running inside return this.each({ }) portion of the methods.init function for your plugin:
For example, unless you are setting specific options for each element, I would take out the part where you're extending the options object for every element!
var methods = {
init: function(options) {
//DO OPTIONS/EVENTLISTENER/etc STUFF OUT HERE
return this.each(function() {
//DONT DO THIS
if (options) {
_options = $.extend($.fn.examplePlugin.defaults, options);
} else {
_options = $.fn.examplePlugin.defaults;
}
Try this instead:
...
var methods = {
init : function( options ){
//do setup type stuff for the entire Plugin out here
var _options = $.MyPlugin.options = $.extend( defaults, options );
//add some listeners to $(document) that will later be handled
//but put them in an external function to keep things organized:
//methods.addListeners()
//this refers to the array of elements returned by $(selector).myPlugin();
//this.each() iterates over, EACH element, and does everything inside (similar to Array.map())
//if the selector has 100 elements youre gonna do whats in here 100 times
return this.each(function(){
//do function calls for individual elements here
});
},
Also, taking advantage of custom events will help you! Add some event listeners to the document object, and let the event handlers figure out which element to interact with using the data API or custom event parameters.
In my JavaScript and Flex applications, users often perform actions that I want other JavaScript code on the page to listen for. For example, if someone adds a friend. I want my JavaScript app to then call something like triggerEvent("addedFriend", name);. Then any other code that was listening for the "addedFriend" event will get called along with the name.
Is there a built-in JavaScript mechanism for handling events? I'm ok with using jQuery for this too and I know jQuery makes extensive use of events. But with jQuery, it seems that its event mechanism is all based around elements. As I understand, you have to tie a custom event to an element. I guess I can do that to a dummy element, but my need has nothing to do with DOM elements on a webpage.
Should I just implement this event mechanism myself?
You have a few options:
jQuery does allow you to do this with objects not associated with the document. An example is provided below.
If you're not already using jQuery on your page, then adding it is probably overkill. There are other libraries designed for this. The pattern you are referring to is called PubSub or Publish/Subscribe.
Implement it yourself, as you've suggested, since this is not difficult if you're looking only for basic functionality.
jQuery example:
var a = {};
jQuery(a).bind("change", function () {
alert("I changed!");
});
jQuery(a).trigger("change");
I would implement such using MVVM pattern with knockjs library.
Just create an element, and use jquery events on it.
It can be just a global variable, doesn't even have to be connected to the DOM.
That way you accomplish your task easily and without any extra libs.
Isn't it possible to bind onchange events in addition to click events? For instance, if addFriend is called and modifies a list on the page, you could bind the change event to then invoke additional functionality.
$('#addFriendButton').click( function() {
// modify the #friendList list
});
$('#friendList').change( function() {
myOtherAction();
});
This is total Host independent, no need for jQuery or dom in this case!
function CustomEvents(){
//object holding eventhandlers
this.handlers_ = {};
}
//check if the event type does not exist, create it.
//then push new callback in array.
CustomEvents.prototype.addEventListner = function (type, callBack){
if (!this.handlers_[type]) this.handlers_[type] = [];
this.handlers_[type].push(callBack);
}
CustomEvents.prototype.triggerEvent = function (type){
//trigger all handlers attached to events
if (!this.handlers_[type]) return;
for (var i=0, handler; handler = this.handlers_[type][i]; i++)
{
//call handler function and supply all the original arguments of this function
//minus the first argument which is the type of the event itself
if (typeof handler === "function") handler.apply(this,arguments.slice(1));
}
}
//delete all handlers to an event
CustomEvents.prototype.purgeEventType = function(type){
return delete this.handlers_[type];
}
test:
var customEvents = new CustomEvents();
customEvents.addEventListner("event A", function(arg){alert('Event A with arguments' + arg);));
customEvents.triggerEvent("event A", "the args");
EDIT added arguments passing
Are there any Event Driven Architecture jQuery plugins?
Step 1: Subscribing
The subscribers subscribe to the event handler in the middle, and pass in a callback method, as well as the name of the event they are listening for...
i.e. The two green subscribers will be listening for p0 events. And the blue subscriber will be listening for p1 events.
Step 2: The p0 event is fired by another component to the Event Handler
A p0 event is fired to the Event Handler
The event handler notifies it's subscribers of the event, calling the callback methods they specified when they subscribed in Step 1: Subscribing.
Note that the blue subscriber is not notified because it was not listening for p0 events.
Step 3: The p1 event is fired a component to the Event Handler
The p1 event is fired by another component
Just as before except that now the blue subscriber receives the event through its callback and the other two green subscribers do not receive the event.
Images by leeand00, on Flickr
I can't seem to find one, but my guess is that they just call it something else in Javascript/jquery
Also is there a name for this pattern? Because it isn't just a basic publisher/subscriber, it has to be called something else I would think.
You probably don't need a plugin to do this. First of all, the DOM itself is entirely event driven. You can use event delegation to listen to all events on the root node (a technique that jQuery live uses). To handle custom events as well that may not be DOM related, you can use a plain old JavaScript object to do the job. I wrote a blog post about creating a central event dispatcher in MooTools with just one line of code.
var EventBus = new Class({Implements: Events});
It's just as easy to do in jQuery too. Use a regular JavaScript object that acts as a central broker for all events. Any client object can publish and subscribe to events on this object. See this related question.
var EventManager = {
subscribe: function(event, fn) {
$(this).bind(event, fn);
},
unsubscribe: function(event, fn) {
$(this).unbind(event, fn);
},
publish: function(event) {
$(this).trigger(event);
}
};
// Your code can publish and subscribe to events as:
EventManager.subscribe("tabClicked", function() {
// do something
});
EventManager.publish("tabClicked");
EventManager.unsubscribe("tabClicked");
Or if you don't care about exposing jQuery, then simply use an empty object and call bind and trigger directly on the jQuery wrapped object.
var EventManager = {};
$(EventManager).bind("tabClicked", function() {
// do something
});
$(EventManager).trigger("tabClicked");
$(EventManager).unbind("tabClicked");
The wrappers are simply there to hide the underlying jQuery library so you can replace the implementation later on, if need be.
This is basically the Publish/Subscribe or the Observer pattern, and some good examples would be Cocoa's NSNotificationCenter class, EventBus pattern popularized by Ray Ryan in the GWT community, and several others.
Though not a jQuery plugin, Twitter released a JavaScript framework called Flight which allows you to create component-based architectures, which communicate via events.
Flight is a lightweight, component-based JavaScript framework from Twitter. Unlike other JavaScript frameworks which are based around the MVC pattern, Flight maps behavior directly to DOM nodes.
Flight is agnostic to how requests are routed or which templating library you decide to use. Flight enforces strict separation of concerns. Components in Flight do not engage each other directly.
They broadcast their actions as events and those components subscribed to those events can take actions based on them. To make use of Flight, you will need the ES5-shim and jQuery along with an AMD loader.
Flight - A Lightweight, Component-Based JavaScript Framework From Twitter
There are actually two of them:
Listen (faster): http://plugins.jquery.com/project/Listen
Intercept (more advanced): http://plugins.jquery.com/project/Intercept
Could this serve as a ligthweight message passing framework?
function MyConstructor() {
this.MessageQueues = {};
this.PostMessage = function (Subject) {
var Queue = this.MessageQueues[Subject];
if (Queue) return function() {
var i = Queue.length - 1;
do Queue[i]();
while (i--);
}
}
this.Listen = function (Subject, Listener) {
var Queue = this.MessageQueues[Subject] || [];
(this.MessageQueues[Subject] = Queue).push(Listener);
}
}
then you could do:
var myInstance = new MyConstructor();
myInstance.Listen("some message", callback());
myInstance.Listen("some other message", anotherCallback());
myInstance.Listen("some message", yesAnotherCallback());
and later:
myInstance.PostMessage("some message");
would dispatch the queues
This can easily be accomplished using a dummy jQuery node as a dispatcher:
var someModule = (function ($) {
var dispatcher = $("<div>");
function init () {
_doSomething();
}
/**
#private
*/
function _doSomething () {
dispatcher.triggerHandler("SOME_CUSTOM_EVENT", [{someEventProperty: 1337}]);
}
return {
dispatcher: dispatcher,
init: init
}
}(jQuery));
var someOtherModule = (function ($) {
function init () {
someModule.dispatcher.bind("SOME_CUSTOM_EVENT", _handleSomeEvent)
}
/**
#private
*/
function _handleSomeEvent (e, extra) {
console.log(extra.someEventProperty) //1337
}
return {
init: init
}
}(jQuery));
$(function () {
someOtherModule.init();
someModule.init();
})
A recent development is msgs.js "Message oriented programming for JavaScript. Inspired by Spring Integration". It also supports communication via WebSockets.
msgs.js applies the vocabulary and patterns defined in the 'Enterprise Integration Patterns' book to JavaScript extending messaging oriented programming into the browser and/or server side JavaScript. Messaging patterns originally developed to integrate loosely coupled disparate systems, apply just as well to loosely coupled modules within a single application process.
[...]
Tested environments:
Node.js (0.6, 0.8)
Chrome (stable)
Firefox (stable, ESR, should work in earlier versions)
IE (6-10)
Safari (5, 6, iOS 4-6, should work in earlier versions)
Opera (11, 12, should work in earlier versions)
I have used the OpenAjax Hub for its publish/subscribe services. It's not a jQuery plugin, but a standalone JavaScript module. You can download and use the reference implementation from SourceForge. I like the hierarchical topic naming and the support for subscribing to multiple topics using wildcard notation.