Add custom arguments for jQuery special event - javascript

I am writing a jQuery event plugin and I need to pass some data to a argument. So if I use it like this:
$(element).on('myevent', function(event, myargument) { console.log(myargument); });
I wan't to get the myargument object and it's properties (e.g. name), which is set in the handler.
So how would this work with the code below?
SmartScroll.myevent = {
setup: function() {
var myargumentData,
handler = function(evt) {
var _self = this,
_args = arguments;
// The data I wan't to get
myargumentData = {
name: "Hello"
};
evt.type = 'myevent';
jQuery.event.handle.apply(_self, _args);
};
jQuery(this).bind('scroll', handler).data(myargumentData, handler);
},
teardown: function() {
jQuery(this).unbind('scroll', jQuery(this).data(myargumentData));
}
};

You can modify an object passed to add.
$.event.special.foo = { add: function(h) {
var hh = h.handler;
h.handler = function(e, a) {
hh.call(this, e, a * 2);
};
} };
Now, let's bind the event:
$("body").on("foo", function(e, a) {
console.log(a);
});
Fire the event and see what happens:
$("body").trigger("foo", [ 10 ]); // 20

on: function( types, selector, data, fn, /*INTERNAL*/ one )
According to the source.
so if i understand your problem , just do :
$(element).on('myevent',{my_data:"foo"},function(event) {
console.log(event.data.my_data);
});
prints "foo"

Related

mootools | Open/close popup menu and outer click event

I'm using mootools and working on popup menu:
document.getElement('.cart a').toggle(
function() {
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
},
function() {
this.getParent('div').addClass('open');
this.getNext('.cart_contents').show();
})
);
The toggle function implementation:
Element.implement({
toggle: function(fn1,fn2){
this.store('toggled',false);
return this.addEvent('click',function(event){
event.stop();
if(this.retrieve('toggled')){
fn1.call(this);
}else{
fn2.call(this);
}
this.store('toggled',!(this.retrieve('toggled')));
});
}
});
The outer click function:
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
this.getDocument().addEvent('click', fn);
},
onRemove : function(fn){
this.getDocument().removeEvent('click', fn);
}
};
I would like to add outerclick event to close my popup menu:
document.getElement('.cart a').toggle(
function() {
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
},
function() {
this.getParent('div').addClass('open');
this.getNext('.cart_contents').show();
}).addEvent('outerClick',function() {
// Error: Wrong code below
// Uncaught TypeError: Object #<HTMLDocument> has no method 'getParent'
this.getParent('div').removeClass('open');
this.getNext('.cart_contents').hide();
});
Error: Uncaught TypeError: Object # has no method 'getParent'
Thanks.
This is a problem (or a deficiency) to do with the outerClick implementation by Darren. It's not a bug - it's built to work as fast as possible. you just need to understand what it does when it binds the actual event to document.
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
// the event actually gets added to document!
// hence scope in fn will be document as delegator.
this.getDocument().addEvent('click', fn);
},
onRemove : function(fn){
this.getDocument().removeEvent('click', fn);
}
};
So the functions will run with context this === document.
One way to fix it is to bind the callback specifically to the element. Problem is, removing it won't work as .bind will return a unique new function that won't match the same function again.
(function(){
var Element = this.Element,
Elements = this.Elements;
[Element, Elements].invoke('implement', {
toggle: function(){
var args = Array.prototype.slice.call(arguments),
count = args.length-1,
// start at 0
index = 0;
return this.addEvent('click', function(){
var fn = args[index];
typeof fn === 'function' && fn.apply(this, arguments);
// loop args.
index = count > index ? index+1 : 0;
});
}
});
Element.Events.outerClick = {
base : 'click',
condition : function(event){
event.stopPropagation();
return false;
},
onAdd : function(fn){
this.getDocument().addEvent('click', fn.bind(this));
},
onRemove : function(fn){
// WARNING: fn.bind(this) !== fn.bind(this) so the following
// will not work. you need to keep track of bound fns or
// do it upstream before you add the event.
this.getDocument().removeEvent('click', fn.bind(this));
}
};
}());
document.id('myp').toggle(
function(e){
console.log(e); // event.
this.set('html', 'new text');
},
function(){
console.log(this); // element
this.set('html', 'old text');
},
function(){
this.set("html", "function 3!");
}
).addEvent('outerClick', function(e){
console.log(this, e);
});
http://jsfiddle.net/dimitar/UZRx5/ - this will work for now - depends if you have a destructor that removes it.
Another approach is when you add the event to do so:
var el = document.getElement('.cart a');
el.addEvent('outerClick', function(){ el.getParent(); // etc });
// or bind it
el.addEvent('outerClick', function(){ this.getParent(); }.bind(el));
it can then be removes if you save a ref
var el = document.getElement('.cart a'),
bound = function(){
this.getParent('div');
}.bind(el);
el.addEvent('outerClick', bound);
// later
el.removeEvent('outerClick', bound);
that's about it. an alternative outerClick is here: https://github.com/yearofmoo/MooTools-Event.outerClick/blob/master/Source/Event.outerClick.js - not tried it but looks like it tries to do the right thing by changing scope to element and keeping a reference of the memoized functions - though multiple events will likely cause an issue, needs event ids to identify the precise function to remove. Also, it looks quite heavy - considering the event is on document, you want to NOT do too much logic on each click that bubbles to that.

Rivets.js event handlers with custom arguments

I've just started with Rivets.js, which looks promising as simple data-binding framework.
I've arrived at the point that I don't know how to pass "custom arguments" to the rv-on-click binder, so I tried to take the idea from this: https://github.com/mikeric/rivets/pull/34
My code:
rivets.binders["on-click-args"] = {
bind: function(el) {
model = this.model;
keypath = this.keypath;
if(model && keypath)
{
var args = keypath.split(' ');
var modelFunction = args.shift();
args.splice(0, 0, model);
var fn = model[modelFunction];
if(typeof(fn) == "function")
{
this.callback = function(e) {
//copy by value
var params = args.slice();
params.splice(0, 0, e);
fn.apply(model, params);
}
$(el).on('click', this.callback);
}
}
},
unbind: function(el) {
$(el).off('click', this.callback);
},
routine: function(el, value) {
}
}
This code is working, my question is: is this the correct way?
If you want to pass a custom argument to the event handler then this code might be simpler:
rivets.configure({
// extracted from: https://github.com/mikeric/rivets/issues/258#issuecomment-52489864
// This configuration allows for on- handlers to receive arguments
// so that you can onclick="steps.go" data-on-click="share"
handler: function (target, event, binding) {
var eventType = binding.args[0];
var arg = target.getAttribute('data-on-' + eventType);
if (arg) {
this.call(binding.model, arg);
} else {
// that's rivets' default behavior afaik
this.call(binding.model, event, binding);
}
}
});
With this configuration enabled, the first and only argument sent to the rv-on-click handler is the value specified by data-on-click.
<a rv-on-click="steps.go" data-on-click="homePage">home</a>
This is not my code (I found it here), but it does work with Rivets 0.8.1.
There is also a way to pass the current context to the event handler. Basically, when an event fires, the first argument passed to the handler is the event itself (click, etc), and the second argument is the model context.
So lets say that you are dealing with a model object that is the product of a rv-each loop...
<div rv-each-group="menu.groups">
<input rv-input="group.name"><button rv-on-click="vm.addItem">Add item</button>
___ more code here ___
</div>
Then you can access the current "group" object in the event handler like this:
var viewModel = {
addItem: function(ev, view) {
var group = view.group;
}
};
More details on this technique can he found here https://github.com/mikeric/rivets/pull/162
I hope this helps.
There is another answer here:
https://github.com/mikeric/rivets/issues/682
by Namek:
You could define args formatter:
rivets.formatters.args = function(fn) {
let args = Array.prototype.slice.call(arguments, 1)
return () => fn.apply(null, args)
}
and then:
rv-on-click="on_click | args 1"
Please note that args is not a special id, you can define anything instead of args.
To pass multiple arguments use space: "on_click | args 1 2 3 'str'"

bind callback parameter

I have this code to set "State Machine" in view of a javascript application:
var Events = {
bind: function(){
if ( !this.o ) this.o = $({});
this.o.bind(arguments[0], arguments[1])
},
trigger: function(){
if ( !this.o ) this.o = $({});
this.o.trigger(arguments[0], arguments[1])
}
};
var StateMachine = function(){};
StateMachine.fn = StateMachine.prototype;
$.extend(StateMachine.fn, Events);
StateMachine.fn.add = function(controller){
this.bind("change", function(e, current){
console.log(current);
if (controller == current)
controller.activate();
else
controller.deactivate();
});
controller.active = $.proxy(function(){
this.trigger("change", controller);
}, this);
};
var con1 = {
activate: function(){
console.log("controller 1 activated");
},
deactivate: function(){
console.log("controller 1 deactivated");
}
};
var sm = new StateMachine;
sm.add(con1);
con1.active();
What I don't understand at this point is where the current parameter in bind function comes from (That is: this.bind("change", function(e, current){...}). I try to log it on firebug console panel and it seems to be the controller parameter in StateMachine.fn.add function. Could you tell me where this parameter comes from?
Thank you.
As far as I understand, you specified the second argument to be passed to you event callback here:
this.trigger("change", controller);
jQuery's trigger method will call all binded functions, passing the Event object as the first argument (always), and then, after it, all the arguments you passed to .trigger() method after the name of event.

custom jQuery plugin: events not behaving as expected

I'm writing a lightweight jQuery plugin to detect dirty forms but having some trouble with events. As you can see in the following code, the plugin attaches an event listener to 'beforeunload' that tests if a form is dirty and generates a popup is that is the case.
There is also another event listener attached to that form's "submit" that should in theory remove the 'beforeunload' listener for that specific form (i.e. the current form I am submitting should not be tested for dirt, but other forms on the page should be).
I've inserted a bunch of console.log statements to try and debug it but no luck. Thoughts?
// Checks if any forms are dirty if leaving page or submitting another forms
// Usage:
// $(document).ready(function(){
// $("form.dirty").dirtyforms({
// excluded: $('#name, #number'),
// message: "please don't leave dirty forms around"
// });
// });
(function($) {
////// private variables //////
var instances = [];
////// general private functions //////
function _includes(obj, arr) {
return (arr._indexOf(obj) != -1);
}
function _indexOf(obj) {
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (obj, fromIndex) {
if (fromIndex == null) {
fromIndex = 0;
} else if (fromIndex < 0) {
fromIndex = Math.max(0, this.length + fromIndex);
}
for (var i = fromIndex, j = this.length; i < j; i++) {
if (this[i] === obj)
return i;
}
return -1;
};
}
}
////// the meat of the matter //////
// DirtyForm initialization
var DirtyForm = function(form, options) {
// unique name for testing purposes
this.name = "instance_" + instances.length
this.form = form;
this.settings = $.extend({
'excluded' : [],
'message' : 'You will lose all unsaved changes.'
}, options);
// remember intial state of form
this.memorize_current();
// activate dirty tracking, but disable it if this form is submitted
this.enable();
$(this.form).on('submit', $.proxy(this.disable, this));
// remember all trackable forms
instances.push(this);
}
// DirtyForm methods
DirtyForm.prototype = {
memorize_current: function() {
this.originalForm = this.serializeForm();
},
isDirty: function() {
var currentForm = this.serializeForm();
console.log("isDirty called...")
return (currentForm != this.originalForm);
},
enable: function() {
$(window).on('beforeunload', $.proxy(this.beforeUnloadListener, this));
console.log("enable called on " + this.name)
},
disable: function(e) {
$(window).off('beforeunload', $.proxy(this.beforeUnloadListener, this));
console.log("disable called on " + this.name)
},
disableAll: function() {
$.each(instances, function(index, instance) {
$.proxy(instance.disable, instance)
});
},
beforeUnloadListener: function(e) {
console.log("beforeUnloadListener called on " + this.name)
console.log("... and it is " + this.isDirty())
if (this.isDirty()) {
e.returnValue = this.settings.message;
return this.settings.message;
}
},
setExcludedFields: function(excluded) {
this.settings.excluded = excluded;
this.memorize_current();
this.enable();
},
serializeForm: function() {
var blacklist = this.settings.excludes
var filtered = [];
var form_elements = $(this.form).children();
// if element is not in the excluded list
// then let's add it to the list of filtered form elements
if(blacklist) {
$.each(form_elements, function(index, element) {
if(!_includes(element, blacklist)) {
filtered.push(element);
}
});
return $(filtered).serialize();
} else {
return $(this.form).serialize();
}
}
};
////// the jquery plugin part //////
$.fn.dirtyForms = function(options) {
return this.each(function() {
new DirtyForm(this, options);
});
};
})(jQuery);
[EDIT]
I ended up fixing this by using jQuery's .on() new namespace feature to identify the handler. The problem was that I was passing new anonymous functions as the handler argument to .off(). Thanks #FelixKling for your solution!
this.id = instances.length
[...]
enable: function () {
$(window).on('beforeunload.' + this.id, $.proxy(this.beforeUnloadListener, this));
},
disable: function () {
$(window).off('beforeunload.' + this.id);
},
Whenever you are calling $.proxy() it returns a new function. Thus,
$(window).off('beforeunload', $.proxy(this.beforeUnloadListener, this));
won't have any effect, since you are trying to unbind a function which was not bound.
You have to store a reference to the function created with $.proxy, so that you can unbind it later:
enable: function() {
this.beforeUnloadListener = $.proxy(DirtyForm.prototype.beforeUnloadListener, this);
$(window).on('beforeunload', this.beforeUnloadListener);
console.log("enable called on " + this.name)
},
disable: function(e) {
$(window).off('beforeunload', this.beforeUnloadListener);
console.log("disable called on " + this.name)
},

Passing parameters in addEventListener

I am working on a Firefox extension, and I am trying to pass a parameter to addEventListener. I am "listening" for changes in the page title, my code looks something like this:
function Test()
{
this.checkTitle = function( event )
{
var func = function() { this.onTitleChange ( event ); };
var target = content.document.getElementsByTagName('TITLE')[0];
target.addEventListener('DOMSubtreeModified', func, false);
}
this.onTitleChange = function( e )
{
// do stuff with e
alert('test');
}
this.handleEvent = function (event)
{
switch (event.type)
{
case "DOMContentLoaded":
{
this.checkTitle( event );
}
}
}
window.addEventListener ("DOMContentLoaded", this, false);
}
I never get the 'test' alert, if I use func = function() { alert(event); }; it does show an alert with '[object Event]'. Also tried without using this. on func but still wont work.
How can I make this work to be able to access checkTitle parameter "event" into onTitleChange?
When the browser calls the event handler, this will refer to the element, not your instance.
You need to save a copy of the desired this in a separate variable:
var self = this;
var func = function(e) { self.onTitleChange (e); };
var target = content.document.getElementsByTagName('TITLE')[0];
target.addEventListener('DOMSubtreeModified', func, false);

Categories