Skip to content

Instantly share code, notes, and snippets.

@kkriehl
Last active October 1, 2015 15:10
Show Gist options
  • Save kkriehl/65a5e72c99c253ebab01 to your computer and use it in GitHub Desktop.
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
// 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