Issues with event delegation on same container - javascript

I have a function, simplified like this:
var fooFunction = function($container, data) {
$container.data('foobarData', data);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
if (typeof data.validateFunction === 'function' && !data.validateFunction(e)) {
return;
}
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(data.returnText);
});
};
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-companies').length ? true : false;
},
returnText: 'Hello Company!',
});
fooFunction('.same-container', {
validateFunction: function(event) {
return $(e.currentTarget).closest('.type-humans').length ? true : false;
},
returnText: 'Hello Human!',
})
I am using event delegation on the same container (.same-container) with a custom validateFunction() to validate if the code in // Do stuff should run.
For each fooFunction() initiation, I have some different logic that will get called on // Do stuff. The issue is that those two event delegations conflict. It seems that only one of them is called and overwrites the other one.
How can I have multiple event delegations with the option to define via a custom validateFunction which one should be called. I use preventDefault() + stopPropagation() so on click on a <a>, nothing happens as long as validateFunction() returns true.

The problem is that you're overwriting $(e.delegateTarget).data('foobarData') every time.
Instead, you could add the options to an array, which you loop over until a match is found.
var fooFunction = function($container, data) {
var oldData = $container.data('foobarData', data);
if (oldData) { // Already delegated, add the new data
oldData.push(data);
$container.data('foobarData', oldData);
} else { // First time, create initial data and delegate handler
$container.data('foobarData', [data]);
$container.on('click', 'a', function(e) {
var data = $(e.delegateTarget).data('foobarData');
var $target = $(e.currentTarget);
var index = data.find(data => typeof data.validateFunction === 'function' && !data.validateFunction(e));
if (index > -1) {
var foundData = data[index]
e.preventDefault();
e.stopPropagation();
// Do stuff
console.log(foundData.returnText);
}
});
}
}

Related

addEventListener after fetched data (improving, promisifying, using latest techniques)

this is a working code, but I want to simplify - correct, improve the code
window.addEvent = function(elem,type,callback) {
var evt = function(e) {
e = e || window.event;
return callback.call(elem,e);
};
var cb = function(e) { return evt(e); };
elem.addEventListener(type,cb,false);
return elem;
};
window.findParent = function(child,filter,root) {
do {
if( filter(child)) return child;
if( root && child == root) return false;
} while(child = child.parentNode);
return false;
};
function on(type, target, callback) {
window.addEvent(document.body, type, function(e) {
var s = window.findParent(e.srcElement || e.target, function(elm) {
return elm.classList.contains(target);
},this);
if( s && callback ) {
callback(e)
}
});
}
on("click", "page-link", function(e){
console.log("hey its working");
});
In this example, I update the pagination with fetch, and I have to bind addEventlistener to every page link. I am thinking about, If its there a way to resolve this with primise, or another cleaner way, using the latest techniques? (with pure Javascript)

Jquery livequery 'click' doesn't work

I've got a problem with my Jquery, the alert doesn't pop.
Here is the code :
HTML (I've this div 4 times, which is a button) :
<div class="grid4 text_center" style = "margin: 20px 20px 0px 0px;">
<input value="Calculer" class="bouton btvalid btlong250 validation" type="submit">
</div>
JS :
$(document).ready(function(){
$('.grid4 text_center .bouton btvalid btlong250 validation').livequery('click', function () {
var sTest = 'Click';
alert('' + sTest);
});
});
Change your selector to this:
$('.grid4.text_center .bouton.btvalid.btlong250.validation')
space separated css class names should be concat with a ., so for div that you can do .grid4.text_center then a space now same with the input's css classes .bouton.btvalid.btlong250.validation.
May be this way you should do it:
$(document).ready(function() {
$('.grid4.text_center .bouton.btvalid.btlong250.validation').livequery(function() {
$(this).click(function() {
var sTest = 'Click';
alert('' + sTest);
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="grid4 text_center" style="margin: 20px 20px 0px 0px;">
<input value="Calculer" class="bouton btvalid btlong250 validation" type="submit">
</div>
<script>
(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($, undefined) {
function _match(me, query, fn, fn2) {
return me.selector == query.selector &&
me.context == query.context &&
(!fn || fn.$lqguid == query.fn.$lqguid) &&
(!fn2 || fn2.$lqguid == query.fn2.$lqguid);
}
$.extend($.fn, {
livequery: function(fn, fn2) {
var me = this,
q;
// See if Live Query already exists
$.each($jQlq.queries, function(i, query) {
if (_match(me, query, fn, fn2))
// Found the query, exit the each loop
return (q = query) && false;
});
// Create new Live Query if it wasn't found
q = q || new $jQlq(me.selector, me.context, fn, fn2);
// Make sure it is running
q.stopped = false;
// Run it immediately for the first time
q.run();
// Contnue the chain
return me;
},
expire: function(fn, fn2) {
var me = this;
// Find the Live Query based on arguments and stop it
$.each($jQlq.queries, function(i, query) {
if (_match(me, query, fn, fn2) && !me.stopped)
$jQlq.stop(query.id);
});
// Continue the chain
return me;
}
});
var $jQlq = $.livequery = function(selector, context, fn, fn2) {
var me = this;
me.selector = selector;
me.context = context;
me.fn = fn;
me.fn2 = fn2;
me.elements = $([]);
me.stopped = false;
// The id is the index of the Live Query in $.livequery.queries
me.id = $jQlq.queries.push(me) - 1;
// Mark the functions for matching later on
fn.$lqguid = fn.$lqguid || $jQlq.guid++;
if (fn2) fn2.$lqguid = fn2.$lqguid || $jQlq.guid++;
// Return the Live Query
return me;
};
$jQlq.prototype = {
stop: function() {
var me = this;
// Short-circuit if stopped
if (me.stopped) return;
if (me.fn2)
// Call the second function for all matched elements
me.elements.each(me.fn2);
// Clear out matched elements
me.elements = $([]);
// Stop the Live Query from running until restarted
me.stopped = true;
},
run: function() {
var me = this;
// Short-circuit if stopped
if (me.stopped) return;
var oEls = me.elements,
els = $(me.selector, me.context),
newEls = els.not(oEls),
delEls = oEls.not(els);
// Set elements to the latest set of matched elements
me.elements = els;
// Call the first function for newly matched elements
newEls.each(me.fn);
// Call the second function for elements no longer matched
if (me.fn2)
delEls.each(me.fn2);
}
};
$.extend($jQlq, {
guid: 0,
queries: [],
queue: [],
running: false,
timeout: null,
registered: [],
checkQueue: function() {
if ($jQlq.running && $jQlq.queue.length) {
var length = $jQlq.queue.length;
// Run each Live Query currently in the queue
while (length--)
$jQlq.queries[$jQlq.queue.shift()].run();
}
},
pause: function() {
// Don't run anymore Live Queries until restarted
$jQlq.running = false;
},
play: function() {
// Restart Live Queries
$jQlq.running = true;
// Request a run of the Live Queries
$jQlq.run();
},
registerPlugin: function() {
$.each(arguments, function(i, n) {
// Short-circuit if the method doesn't exist
if (!$.fn[n] || $.inArray(n, $jQlq.registered) > 0) return;
// Save a reference to the original method
var old = $.fn[n];
// Create a new method
$.fn[n] = function() {
// Call the original method
var r = old.apply(this, arguments);
// Request a run of the Live Queries
$jQlq.run();
// Return the original methods result
return r;
};
$jQlq.registered.push(n);
});
},
run: function(id) {
if (id !== undefined) {
// Put the particular Live Query in the queue if it doesn't already exist
if ($.inArray(id, $jQlq.queue) < 0)
$jQlq.queue.push(id);
} else
// Put each Live Query in the queue if it doesn't already exist
$.each($jQlq.queries, function(id) {
if ($.inArray(id, $jQlq.queue) < 0)
$jQlq.queue.push(id);
});
// Clear timeout if it already exists
if ($jQlq.timeout) clearTimeout($jQlq.timeout);
// Create a timeout to check the queue and actually run the Live Queries
$jQlq.timeout = setTimeout($jQlq.checkQueue, 20);
},
stop: function(id) {
if (id !== undefined)
// Stop are particular Live Query
$jQlq.queries[id].stop();
else
// Stop all Live Queries
$.each($jQlq.queries, $jQlq.prototype.stop);
}
});
// Register core DOM manipulation methods
$jQlq.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');
// Run Live Queries when the Document is ready
$(function() {
$jQlq.play();
});
}));
</script>
Change your selector to this.
$('.grid4 .validation');

Callbacks with Require JS

So i'm trying to make two functions that allow a user to move an item from their cart to the "saved cart" and vise versa. These functions depend on the "cart group item" module which also contains the events for the button clicks. My question is, i'm unsure how to correctly call these functions to allow the click event to take place in my current js file. Hopefully someone can help!
Event's in module:
cartGroupItem.prototype.createMoveEvent = function (elem) {
if (undefined !== elem && null !== elem) {
var button = elem.querySelector('.cartGroupItem__move');
if (button !== null) {
button.addEventListener('click', function (e) {
ajax('GET',
'/resources/ajax/cart.aspx?sub=3&Saved=0&Cart_Group_ID='+this.cartGroupId,
true, {}, function () {
this.moveToCartCallback();
}.bind(this), function () {
this.showErrorDiv();
}.bind(this));
}.bind(this));
}
}
};
cartGroupItem.prototype.createSaveEvent = function (elem) {
if (undefined !== elem && null !== elem) {
var button = elem.querySelector('.cartGroupItem__save');
if (button !== null) {
button.addEventListener('click', function (e) {
ajax('GET',
'/resources/ajax/cart.aspx?sub=3&Saved=1&Cart_Group_ID='+this.cartGroupId,
true, {}, this.saveForLaterCallback.bind(this), this.showErrorDiv.bind(this));
}.bind(this));
}
}
};
Move functions:
function moveToSaved(cartGroupId) {
for (var i = 0, l = activeCartList.length; i < l; i++) {
if (activeCartList[i].cartGroupId === cartGroupId) {
activeCartList.remove();
savedCartList.push(activeCartList[i]);
}
}
}
function moveToActive(cartGroupId) {
for (var i = 0, l = savedCartList.length; i < l; i++) {
if (savedCartList[i].cartGroupId === cartGroupId) {
savedCartList.remove();
activeCartList.push(savedCartList[i]);
}
}
}
Your Event module probably define the function cartGroupItem right?
What you need to do is pass this function from its file to your current file, and then "instanciate" a carteGroupItem:
// In your current JS file
var myCartGroupItem = new cartGroupItem();
myCartGroupItem.createMoveEvent();
myCartGroupItem.createSaveEvent();
We also would need to see this function "constructor" (where it is defined) as it probably takes a few callbacks as parameters. Otherwise you can add them manually:
myCartGroupItem.moveToCartCallback = function() {
// do what you want when the item is moved to cart
};
// Same for the other callbacks
myCartGroupItem.saveForLaterCallback = function() { ... };
myCartGroupItem.showErrorDiv = function() { ... };
Finally, the way to pass things with RequireJS is that for instance, your Event module returns cartGroupItem so in your file module:
define(['Event'], function(cartGroupItem) {
// Your code where you can use cartGroupItem
});

An approach for tracking and using sequential clicks

I'd like to define a relationship between two dom elements (in my particular case, draw lines between to nodes) by clicking on the first followed by a click on the second. The following is my approach. I don't think it's particularly elegant or clean. Is there a better way to do this?
$(function() {
var State = {
initialClick: false, // tracks clicks status for drawing lines
initialID: undefined // remember data gotten from the first click
};
...
$map.on('click', '.node', function() {
var $this = $(this),
currentID = $this.attr('data-id');
if (State.initialClick) {
if (State.initialID === currentID) {
// process first click
} else {
// process second click
}
resetClickTracking();
} else {
setState(currentID);
}
});
...
function setState(id) {
State.initialClick = true;
State.initialID = id;
}
function resetState() {
State.initialClick = false;
State.initialID = undefined;
}
});
I would probably go for a delegate pattern:
var lastNodeClicked = null;
$map.on('click', '.node', function() {
if (lastNodeClicked && lastNodeClicked !== this) {
// draw line
lastNodeClicked = null; // reset
} else {
lastNodeClicked = this;
}
});
Instead of resetting the last clicked node, you can also unconditionally update it with the clicked node; that way you can draw lines with less clicks ;-)

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)
},

Categories