Last active
October 1, 2015 15:10
-
-
Save kkriehl/65a5e72c99c253ebab01 to your computer and use it in GitHub Desktop.
A solution for registering the click/touchend outside an element without stopping event propagation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// usage: data-offclick='json-config' | |
// config: { | |
// "target": "selector", // -> optional child selector for action/trigger target | |
// "action": "action", // -> optional action to execute - for now only "uncheck" a checkbox | |
// "trigger": "event" // -> optional event to trigger (not if action specified) - default is "offclick" | |
// } | |
;(function($, window, document, undefined) { | |
'use strict'; | |
var listener = []; | |
/** | |
* Register a callback for an element to trigger if a click outside the | |
* element is detected | |
* @param e | |
* @param cb | |
*/ | |
function registerListener(e, cb) { | |
// avoid multiple registrations | |
if(!('__offclick_callback' in e)) { | |
// add a click/touchend handler to "update" the event with a ignore | |
// list containing the registered element if said element receives | |
// a click event | |
$(e).on('click touchend', function(evt, data) { | |
if(data === undefined) { | |
data = { __offclick_ignore: [] }; | |
} else if(!("__offclick_ignore" in data)) { | |
data = $.extend( | |
typeof data === 'object' && data !== null ? data : {}, | |
{ __offclick_ignore: [] } | |
); | |
} | |
// the newly triggered event will trigger this handler again | |
// we're making sure to not create a loop if this element is | |
// already contained within the ignore list | |
if($.inArray(this, data.__offclick_ignore) === -1) { | |
evt.stopPropagation(); | |
data.__offclick_ignore.push(this); | |
$(this).trigger(evt.type, data); | |
} | |
}); | |
// add the callback the the dom element | |
e.__offclick_callback = cb; | |
listener.push(e); | |
// warn if there are many listeners on the page that get iterated | |
// and executed on every. single. click/touchend. | |
if(listener.length > 10 && 'console' in window) { | |
console.warn('Watching more than 10 elements for offclick!'); | |
} | |
} | |
} | |
// listen for incoming click/touchend events on the body element to iterate | |
// over all registered offlick listeners and execute their callbacks, except | |
// if the listener has been added to an ignore list | |
$('body').on('click touchend', function(evt, data) { | |
if(data && '__offclick_ignore' in data && data.__offclick_ignore.length > 0) { | |
for (var i = 0; i < listener.length; i++) { | |
if ($.inArray(listener[i], data.__offclick_ignore) === -1) { | |
listener[i].__offclick_callback(); | |
} | |
} | |
} else { | |
for (var i = 0; i < listener.length; i++) { | |
listener[i].__offclick_callback(); | |
} | |
} | |
}); | |
// register each element that has a data-offlick attribute with it's specific | |
// config to create default handlers automatically | |
$('[data-offclick]').each(function() { | |
var $e = $(this), | |
config = $e.data('offclick'), | |
$target = $e, | |
handler; | |
if('target' in config) { | |
$target = $target.find(config.target); | |
} | |
// default handler creation | |
if('action' in config) { | |
if(config.action === 'uncheck') { | |
handler = function() { | |
if($target.prop('checked')) { | |
$target.prop('checked', false); | |
} | |
}; | |
} | |
} else { | |
handler = function() { | |
$target.trigger('trigger' in config ? config.trigger : 'offclick'); | |
}; | |
} | |
registerListener(this, handler); | |
}); | |
})(jQuery, window, document); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment