klaro-js
klaro-js copied to clipboard
Implements some basic events for loaded, initialized, beforeShown to (for example) allow add watches reliably
The problem
If you want to add a watch you have to wait until the window.onload event to make a klaro.getManager().watch() call.(because there is no way to know when the klaro is loaded, or initialized). The problem is that if user click on the "Accept all cookies" before the watch is added (in window onload event), your watch update function will never be executed.
Proposed solution
The proposed solution is to add some basic events triggering like **klaro.events.onLoad(), klaro.events.beforeShown(), klaro.events.beforeRender(), etc.
That way we will be able to add watches before the popup is shown (or even when the klaro library is loaded).
Alternative solutions
An alternative solution would be to allow add watches in the config file, so you don't have to worry about when the klaro library is loaded.
I agree 100%. I am currently using a simple MutationObserver to track the consent modal status.
var cm_event = document.createEvent('Event');
cm_event.initEvent('consentModalOpen', true, true);
document.addEventListener('consentModalOpen', function (e) { console.log("consentModalOpen") }, false);
document.addEventListener("DOMContentLoaded", function(e) {
var cm_target = document.getElementById( "klaro" ),
cm_visible = false;
var observer = new MutationObserver(function(mutationRecords) {
if(document.querySelectorAll('#klaro .cookie-modal').length > 0 && cm_visible == false){
cm_visible = true;
document.dispatchEvent(cm_event);
}else{
cm_visible = false;
}
});
observer.observe(cm_target, {
childList: true,
subtree: true,
characterDataOldValue: true
});
});
That sounds reasonable. We already have a addEventListener function in Klaro (https://github.com/kiprotect/klaro/blob/master/src/lib.js#L40) where you can register an event listener that will e.g. fire when the modal or notice is rendered, we can add more event types to that. We might also unify that with the consent managers' .watch() function to get a more consistent way of observing changes in Klaros state.
Just to understand your use case a bit better: What do you currently use the watch() function of the consent manager for?
Hi @adewes and thanks for your response, let me introduce what I was achieving. But firstly let me tell you that I'm not used to react programming (just php + javascript) so I didn't even tried to setup the development environment to help you with the tool (probably if you tell me the steps to contribute with the lib I could do it).
First of all, regarding the watches, I needed a way to be able to "catch" not only the external and inline scripts but the embedded scripts (like the ones we use to use inside some window.onLoad or domReady events). So I ended wrapping those "bits of scripts" with a function named with the service name (the data-name), so when a klaro "saveConsents" event occur I just iterate over the services and call the "wrapper" functions with such a name in case the service is true consented. I also have to do it properly for the nexts requests (where the consents are already stored) so I call the same functions if the "getManager().confirmed" property is also true.
Here the code for an example of wrapper function (ie. data-name=ZopimService):
window['ZopimService'] = function(){
window.$zopim||(function(d,s){var z=$zopim=function(c){z._.push(c)},$=z.s=d.createElement(s),e=d.getElementsByTagName(s)[0];z.set=function(o){z.set._.push(o)};z._=[];z.set._=[];$.async=!0;$.setAttribute("charset","utf-8");$.src="https://v2.zopim.com/?61xcryeGaGa0DRQafAJFcEIYOPMaDL1t";z.t=+new Date();$.type="text/javascript";e.parentNode.insertBefore($,e)})(document,"script")
};
And here the code for the proper execution of embedded scripts:
var klaroExecuteEmbeddedScripts = function(consents) {
for (var consent in consents) {
if (consents[consent]) {
if (typeof window[consent] === "function") {
window[consent]();
delete window[consent]; //to avoid multiple calls due to bad multiple events thrown by manager with some events
}
}
}
};
//due to lack of proper ready, onload, beforeShown, etc. events we use a interval
var klaroInterval = setInterval(function() {
if (typeof klaro != "object")
return;
if (klaro.getManager().confirmed) {
klaroExecuteEmbeddedScripts(klaro.getManager().consents);
}
klaro.getManager().watch({
update: function(obj, name, data) {
if (name === 'saveConsents')
klaroExecuteEmbeddedScripts(data.consents)
}
});
clearInterval(klaroInterval);
}, 100); // check every 100ms
I'm pretty sure that this could be done in the source code pretty more easily.
BTW, the consents events are pretty annoying and for its not well implemented, because if you for example consent the cookies you are receiving tons of "consents" events (one per service) but moreover the value of the consent you are tracking (the last one for example) is not real updated until the last consent event, so you are not even able to know what is the "good event" to catch...
More over, to not facilitate the opt-out to users too much (as many libraries also does) we had to change some behaviour for the pop-up and the buttons (even it's not working in all scenarios due to the absence of proper events for beforeShownModal, afterCloseModal, beforeShownNotice, afterCloseNotice)
We do this for the css:
.cm-btn-preferences {padding:0.4em} .cm-btn-accept {visibility:hidden} .cm-body {display:none} .cm-footer-buttons {align-items: center;} .cm-btn.cm-btn-accept-all { padding: 0.8em 1.5em !important; } .cm-powered-by {display:none !important} .cm-toggle-all{display:none !important}
And do this as soon as the klaro object is found in the interval checking:
$('.cm-footer-buttons').prepend('<a class="cm-btn-preferences">Preferencias</a>');
$('.cm-btn-preferences').click(function() {
$('.cm-body').show();
$('.cm-btn-accept').css('visibility', 'visible');
$('.cm-btn-preferences').hide();
});
Such a way, we end with this "two" steps:
Step1:

Step2 (after press preferences button):

Note: The problem is that as we hide the "Accept button" from css, it's also hide when you use "mustConsent=false" and it's never shown by the clicking of our fake "preferences" button because the button is not inserted in the modal because it doesn't exists till you click in the Notice.
I'm pretty sure that this could be also done pretty easily in the source code as a boolean property or maybe by default (because this is the way that pretty much all the consent libraries does).
Btw: excuses for my english Regards
Would be great to have these events! 👍