Google Closure - Html5History fires NAVIGATE event twice - javascript

Why does goog.history.Html5History object fire goog.history.EventType.NAVIGATE event twice each time when fragment is changed? This is the example of code:
var history = goog.history.Html5History.isSupported()
? new goog.history.Html5History()
: new goog.History();
goog.events.listen(history, goog.history.EventType.NAVIGATE, function(e) {
console.log(['navigation', e.target.getToken()]);
});
history.setEnabled(true);
And this is log:
["navigation", "!/properties/new"]
["navigation", "!/properties/new"]
UPD: As I have figured out there are two different values of isNavigation field of e object in callback. First time it takes false value, and second time it takes true value. isNavigation means:
isNavigation True if the event was triggered by a browser action, such
as forward or back, clicking on a link, editing the URL, or calling
window.history.(go|back|forward). False if the token has been changed
by a setToken or replaceToken call.
But how to get only one even fired?

I met the same problem. But in my case both events has isNavigation==true.
init = function() {
var h = goog.history.Html5History.isSupported() ?
new goog.history.Html5History() : new goog.History();
goog.events.listen(h, goog.history.EventType.NAVIGATE, navigationCallback);
h.setEnabled(true);
};
navigationCallback = function(e) {
console.log(e, e.token, e.isNavigation);
};
// And then:
h.setToken("example1");
h.setToken("example2");
// And click "back" button in browser
Output:
goog.history.Event "example1" false
goog.history.Event "example2" false
goog.history.Event "example1" true
goog.history.Event "example1" true

I have the some problem..
Do google fired one time
http://closure-library.googlecode.com/git/closure/goog/demos/history1.html
Perhaps init happens twice?

Related

How to cancel / preventDefault drop event in CKEditor?

In CKEditor 4 application (working jsFiddle ) I do not want the default drop functionality to be executed in case "special" element is dropped. So I created a listener on "drop" event where I call a function editor.execCommand("nonEditableBlock" that I created. This function inserts a block. It also inserts text. I do not want the text to be inserted. I tried to insert "" or null but it causing other issues. So I thought I would quit the drop event. I tried
evt.stop(); // works only 1st time of dropping
evt.data.preventDefault() // not a function
evt.data.preventDefault(true) // not a function
evt.preventDefault() // not a function
evt.cancel() // works only 1st time
return false // works only 1st time
evt.stopPropagation() // not a function
evt.data.stopPropagation() // not a function
Would you know how to prevent the default or how to not to paste the dragged text?
CKEDITOR.on('instanceReady', function(ev) {
ev.editor.on('drop', function (evt) {
console.log('Drop started')
var timestamp = " [["+new Date().toLocaleString().replace(',','')+"]]"
var data = evt.data.dataTransfer.getData('block')
if (data){
var dataTransfer = evt.data.dataTransfer;
// dataTransfer.setData( 'text/html', null ); //set data
dataTransfer.setData( 'text/html', "xxx "+timestamp ); //set data
console.log("nulling data",evt)
console.log("nulling data",evt.data)
}
console.log("testing drop 1")
editor.execCommand("nonEditableBlock", {startupData: {
id: data.data("id"),
content: data.data("data"),
timestamp:timestamp,
}});
console.log("testing drop 2")
// Stop execute next callbacks.
//evt.stop(); // works only 1st time
// Stop the default event action.
// evt.data.preventDefault() // not a function
// evt.data.preventDefault(true) // not a function
// evt.preventDefault() // not a function
// evt.cancel() // works only 1st time
//evt.stopPropagation() // not a function
//evt.data.stopPropagation() // not a function
console.log("testing drop 3")
//return false // works only 1st time
})
What I find confusing is that
if evt.cancel() is used then drop event is fired but editor.execCommand(" command is NOT executed. Although it is inside the event. See the console for debugging. On the first run the command is called so you can see nonEditableBlock widget called in the console.
if you create empty line by pressing enter then it works. In fact in the editor will be only blocks. Nothing except block will be allowed. No text, no empty lines.

Setter not called in one place, working fine elsewhere

I created a js class:
class Drawer {
}
Then added a constructor with a single argument:
constructor (elemId) {
// get element by passed id
this.elem = document.getElementById(elemId);
// make sure the element was found
console.log(elem);
// listen for clicks on the found element
this.elem.addEventListener('click', this.handleDimClick);
// listen for touch events (start, move, cancel, end)
document.body.addEventListener('touchstart', this.handleTouchStart);
...
}
This logs the correct element to console.
The handleDimClick function sets a property to false.
Here's the relevant property:
get open() {
return this.isOpen;
}
set open(value) {
console.log('setting open to ' + value);
this.isOpen = value;
// code omitted
}
Here is the handleDimClick method:
handleDimClick() {
console.log('handleDimClick setting open to false');
this.open = false;
console.log('handleDimClick open should be false');
}
This, for some reason, only logs 2 lines to the console:
handleDimClick setting open to false
handleDimClick open should be false
One of the touch handling functions also sets the open property:
handleTouchEnd(e) {
console.log('handleTouchEnd setting open to ' + (irrelevant));
this.open = (irrelevant);
console.log('handleTouchEnd open should be ' + (irrelevant));
}
And as expected logs to the console:
handleTouchEnd setting open to false
setting open to false
handleTouchEnd open should be false
I'm absolutely out of ideas on why this happens. I tried Ctrl+Shift+R to refresh without cache, but it didn't help. I can see the file is ok in the developer console. The behavior is consistent.

Resetting a setTimeout using addEventListener Javascript

i am trying to make a function that when clicking your mouse, can see that you are active or AFK. When the time runs out, this person must be offline. If the person clicks again after the time runs out, the person must come back online. Furthermore, the time must always be reset if he clicks again within the time so you can see that he is active on the page. Does anyone know what I need to adjust here to make it work?
var time;
window.addEventListener('click', function() {
time = setTimeout(function() {
SaveUrl('Save/SetIsOnlineUser', { isOnline: false });
}, 20000);
})
window.addEventListener("click", myFunction);
function myFunction() {
clearTimeout(time)
SaveUrl('Save/SetIsOnlineUser', { isOnline: true });
}
Your logic is flawed as you run opposing logic under two handlers which run under the same event.
To fix this you instead need to use a single event handler. You can set the user online immediately within the click handler, then set the timeout for N seconds later to mark them as offline, something like this:
var time;
window.addEventListener('click', function() {
clearTimeout(time)
SaveUrl('Save/SetIsOnlineUser', {
isOnline: true
});
time = setTimeout(function() {
SaveUrl('Save/SetIsOnlineUser', {
isOnline: false
});
}, 5000);
})
function SaveUrl(u, o) {
console.log('Online:', o.isOnline);
}
Note that I made the timer only 5 seconds to make the effect more obvious.

Polymer - JavaScript - Data Binding: Real-time update?

I'm having two pages: my-view1 and my-view2.
On my-view1 I have two buttons that add and remove data from LocalStorage.
On my-view2 I have two simple div's that READ (display) total value, and total in last 6 months value.
However, on the second page, the value will not update if the page is not refreshed. I want the values from my-view2 to be automatically updated everytime the page is loaded (viewed, even if cached).
Here is a plnkr with my-view2 so you can undestrand what I'm trying to do.
https://plnkr.co/edit/ul4T2mduxjElCN4rUUKd?p=info
How can I do this?
You can listen to the storage event to trigger and update some prop in my-view2 when you update localStorage:
<my-view-2 id="myView2"></my-view-2>
<script>
window.onstorage = function(e) {
if (e.key !== 'someKeyYouWant') return;
document.getElementById('myView2').set('someProp', {
oldValue: e.oldValue,
newValue: e.newValue
});
};
</script>
EDIT (working plunk): Because of a behavior described here the storage event will not be triggered on the window originating the change, so you have to trigger your own storage event, consider the method below:
saveToLs(e) {
e.preventDefault();
const newName = this.get('dogName');
const ls = window.localStorage;
const synthEvent = new StorageEvent('storage');
const eventConfig = [
'storage',
true,
true,
'myDog',
ls.getItem('myDog'),
newName
];
synthEvent.initStorageEvent(...eventConfig);
setTimeout((() => { // ensure async queue
ls.setItem('myDog', newName);
this.dispatchEvent(synthEvent);
}).bind(this), 0);
}
...and on the listening side:
handleStorageUpdate(e) {
if (e.key !== 'myDog' || e.newValue === this.get('dogName')) return;
this.set('dogName', e.newValue);
}
Please note the if conditional handling potential duplicate updates with the same value.
Here is a working plunk for you to play with

detect when challenge window is closed for Google recaptcha

I am using Google invisible recaptcha. Is there a way to detect when the challenge window is closed? By challenge window I mean window where you have to pick some images for verification.
I currently put a spinner on the button that rendered the recaptcha challenge, once the button is clicked. There is no way for the user to be prompted with another challenge window.
I am calling render function programmatically:
grecaptcha.render(htmlElement, { callback: this.verified, expiredCallback: this.resetRecaptcha, sitekey: this.siteKey, theme: "light", size: "invisible" });
I have 2 callback functions wired up the verified and the resetRecaptcha functions which look like:
function resetRecaptcha() {
grecaptcha.reset();
}
function verified(recaptchaResponse)
{
/*
which calls the server to validate
*/
}
I would have expected that grecaptcha.render has another callback that is called when the challenge screen is closed without the user verifying himself by selecting the images.
As you mention, the API doesn't support this feature.
However, you can add this feature yourself. You may use the following code with caution, Google might change its reCaptcha and by doing so break this custom code. The solution relies on two characteristics of reCaptcha, so if the code doesn't work, look there first:
the window iframe src: contains "google.com/recaptcha/api2/bframe"
the CSS opacity property: changed to 0 when the window is closed
// to begin: we listen to the click on our submit button
// where the invisible reCaptcha has been attachtted to
// when clicked the first time, we setup the close listener
recaptchaButton.addEventListener('click', function(){
if(!window.recaptchaCloseListener) initListener()
})
function initListener() {
// set a global to tell that we are listening
window.recaptchaCloseListener = true
// find the open reCaptcha window
HTMLCollection.prototype.find = Array.prototype.find
var recaptchaWindow = document
.getElementsByTagName('iframe')
.find(x=>x.src.includes('google.com/recaptcha/api2/bframe'))
.parentNode.parentNode
// and now we are listening on CSS changes on it
// when the opacity has been changed to 0 we know that
// the window has been closed
new MutationObserver(x => recaptchaWindow.style.opacity == 0 && onClose())
.observe(recaptchaWindow, { attributes: true, attributeFilter: ['style'] })
}
// now do something with this information
function onClose() {
console.log('recaptcha window has been closed')
}
As I mentioned in the comments of the answer submitted by #arcs, it is a good solution which works but it also fires onClose() when the user successfully completes the challenge. My solution is to change the onClose() function like so:
// now do something with this information
function onClose() {
if(!grecaptcha.getResponse()) {
console.log('recaptcha window has been closed')
}
}
This way, it only executes the desired code if the challenge has been closed and it has not been completed by the user, thus the response cannot be returned with grecaptcha.getResponse()
The drawback of detecting when iframe was hidden is that it fires not only when user closes captcha by clicking in background, but also when he submits the answer.
What I needed is detect only the first situation (cancel captcha).
I created a dom observer to detect when captcha is attached to DOM, then I disconnect it (because it is no longer needed) and add click handler to its background element.
Keep in mind that this solution is sensitive to any changes in DOM structure, so if google decides to change it for whatever reason, it may break.
Also remember to cleanup the observers/listeners, in my case (react) I do it in cleanup function of useEffect.
const captchaBackgroundClickHandler = () => {
...do whatever you need on captcha cancel
};
const domObserver = new MutationObserver(() => {
const iframe = document.querySelector("iframe[src^=\"https://www.google.com/recaptcha\"][src*=\"bframe\"]");
if (iframe) {
domObserver.disconnect();
captchaBackground = iframe.parentNode?.parentNode?.firstChild;
captchaBackground?.addEventListener("click", captchaBackgroundClickHandler);
}
});
domObserver.observe(document.documentElement || document.body, { childList: true, subtree: true });
To work in IE this solution needs polyfills for .include() and Array.from(), found below:
Array.from on the Internet Explorer
ie does not support 'includes' method
And the updated code:
function initListener() {
// set a global to tell that we are listening
window.recaptchaCloseListener = true
// find the open reCaptcha window
var frames = Array.from(document.getElementsByTagName('iframe'));
var recaptchaWindow;
frames.forEach(function(x){
if (x.src.includes('google.com/recaptcha/api2/bframe') ){
recaptchaWindow = x.parentNode.parentNode;
};
});
// and now we are listening on CSS changes on it
// when the opacity has been changed to 0 we know that
// the window has been closed
new MutationObserver(function(){
recaptchaWindow.style.opacity == 0 && onClose();
})
.observe(recaptchaWindow, { attributes: true, attributeFilter: ['style'] })
}
My solution:
let removeRecaptchaOverlayEventListener = null
const reassignGRecatchaExecute = () => {
if (!window.grecaptcha || !window.grecaptcha.execute) {
return
}
/* save original grecaptcha.execute */
const originalExecute = window.grecaptcha.execute
window.grecaptcha.execute = (...params) => {
try {
/* find challenge iframe */
const recaptchaIframe = [...document.body.getElementsByTagName('iframe')].find(el => el.src.match('https://www.google.com/recaptcha/api2/bframe'))
const recaptchaOverlay = recaptchaIframe.parentElement.parentElement.firstElementChild
/* detect when the recaptcha challenge window is closed and reset captcha */
!removeRecaptchaOverlayEventListener && recaptchaOverlay.addEventListener('click', window.grecaptcha.reset)
/* save remove event listener for click event */
removeRecaptchaOverlayEventListener = () => recaptchaOverlay.removeEventListener('click', window.grecaptcha.reset)
} catch (error) {
console.error(error)
} finally {
originalExecute(...params)
}
}
}
Call this function after you run window.grecaptcha.render() and before window.grecaptcha.execute()
And don't forget to remove event listener: removeRecaptchaOverlayEventListener()
For everybody that didn't quite get how it all works, here is another example with explanations that you might find useful:
So we have 2 challenges here.
1) Detect when the challenge is shown and get the overlay div of the challenge
function detectWhenReCaptchaChallengeIsShown() {
return new Promise(function(resolve) {
const targetElement = document.body;
const observerConfig = {
childList: true,
attributes: false,
attributeOldValue: false,
characterData: false,
characterDataOldValue: false,
subtree: false
};
function DOMChangeCallbackFunction(mutationRecords) {
mutationRecords.forEach((mutationRecord) => {
if (mutationRecord.addedNodes.length) {
var reCaptchaParentContainer = mutationRecord.addedNodes[0];
var reCaptchaIframe = reCaptchaParentContainer.querySelectorAll('iframe[title*="recaptcha"]');
if (reCaptchaIframe.length) {
var reCaptchaChallengeOverlayDiv = reCaptchaParentContainer.firstChild;
if (reCaptchaChallengeOverlayDiv.length) {
reCaptchaObserver.disconnect();
resolve(reCaptchaChallengeOverlayDiv);
}
}
}
});
}
const reCaptchaObserver = new MutationObserver(DOMChangeCallbackFunction);
reCaptchaObserver.observe(targetElement, observerConfig);
});
}
First, we created a target element that we would observe for Google iframe appearance. We targeted document.body as an iframe will be appended to it:
const targetElement = document.body;
Then we created a config object for MutationObserver. Here we might specify what exactly we track in DOM changes. Please note that all values are 'false' by default so we could only leave 'childList' - which means that we would observe only the child node changes for the target element - document.body in our case:
const observerConfig = {
childList: true,
attributes: false,
attributeOldValue: false,
characterData: false,
characterDataOldValue: false,
subtree: false
};
Then we created a function that would be invoked when an observer detects a specific type of DOM change that we specified in config object. The first argument represents an array of Mutation Observer objects. We grabbed the overlay div and returned in with Promise.
function DOMChangeCallbackFunction(mutationRecords) {
mutationRecords.forEach((mutationRecord) => {
if (mutationRecord.addedNodes.length) { //check only when notes were added to DOM
var reCaptchaParentContainer = mutationRecord.addedNodes[0];
var reCaptchaIframe = reCaptchaParentContainer.querySelectorAll('iframe[title*="recaptcha"]');
if (reCaptchaIframe.length) { // Google reCaptcha iframe was loaded
var reCaptchaChallengeOverlayDiv = reCaptchaParentContainer.firstChild;
if (reCaptchaChallengeOverlayDiv.length) {
reCaptchaObserver.disconnect(); // We don't want to observe more DOM changes for better performance
resolve(reCaptchaChallengeOverlayDiv); // Returning the overlay div to detect close events
}
}
}
});
}
Lastly we instantiated an observer itself and started observing DOM changes:
const reCaptchaObserver = new MutationObserver(DOMChangeCallbackFunction);
reCaptchaObserver.observe(targetElement, observerConfig);
2) Second challenge is the main question of that post - how do we detect that the challenge is closed? Well, we need help of MutationObserver again.
detectReCaptchaChallengeAppearance().then(function (reCaptchaChallengeOverlayDiv) {
var reCaptchaChallengeClosureObserver = new MutationObserver(function () {
if ((reCaptchaChallengeOverlayDiv.style.visibility === 'hidden') && !grecaptcha.getResponse()) {
// TADA!! Do something here as the challenge was either closed by hitting outside of an overlay div OR by pressing ESC key
reCaptchaChallengeClosureObserver.disconnect();
}
});
reCaptchaChallengeClosureObserver.observe(reCaptchaChallengeOverlayDiv, {
attributes: true,
attributeFilter: ['style']
});
});
So what we did is we get the Google reCaptcha challenge overlay div with the Promise we created in Step1 and then we subscribed for "style" changes on overlay div. This is because when the challenge is closed - Google fade it out.
It's important to note that the visibility will be also hidden when a person solves the captcha successfully. That is why we added !grecaptcha.getResponse() check. It will return nothing unless the challenge is resolved.
This is pretty much it - I hope that helps :)

Categories