I had a trouble with script element async loading guarantees.
When 'load' callback was called on a page with a lot of other scripts, the global variable, which should be defined in external script, was not yet available. On the other hand, after some timeout it became visible. You can see example of how I am doing it:
function async(u, c) {
var d = document, t = 'script',
o = d.createElement(t),
s = d.getElementsByTagName(t)[0];
o.async = true;
o.src = '//' + u;
if (c) { var once = false;
o.addEventListener('load', function (e) { if (!once) { once = true; c(null, e); }}, false);
o.onreadystatechange = function () {
//for IE <= 8
if (this.readyState == "complete" || this.readyState == "loaded") {
if (!once) { once = true; c(null, e); }
}
}; }
s.parentNode.insertBefore(o, s);
};
async("cdnjs.cloudflare.com/ajax/libs/ifvisible/1.0.6/ifvisible.min.js", function() {
var f = function() {
var activeSent = false;
// We detected that sometimes ifvisible is actually undefined !!!
if (typeof ifvisible != 'undefined') {
ifvisible.on('wakeup', function () {
if (!activeSent) {
activeSent = true;
console.log("I detected this move.")
}
});
ifvisible.idle();
} else {
console.log("not init.")
}
};
f();
});
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<p id="me">
Just move on ME with your mouse
</p>
My question - is there a strong or relaxed guarantee that after 'load' callback is invoked, the script was actually fully loaded on the page? Thank you.
Related
Following the instruction of the this documentation, I end up with the following code:
window.zEmbed || (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");
$.onload = (event) => {
console.log("chat script has loaded");
};
$.src = "//v2.zopim.com/?#zopimClientID"; z.t = +new Date; $.
type = "text/javascript"; e.parentNode.insertBefore($, e);
})(document, "script");
var onChatStart = function () {
$zopim.livechat.window.show();
window['onChatStart'] && window['onChatStart']();
};
$zopim(function () {
$zopim.livechat.clearAll();
(......)
});
The problem is that the $zopim(funtion() {..}) callback function is never called, but the script is successfully loaded. I know based on the console.log made by:
$.onload = (event) => {
console.log("chat script has loaded");
};
Any one knows why this is happening?
Thank you
The problem is you're running the $zopim script before it's fully loaded. Once all the parts of the $zopim object have loaded it will stop looping and run your script.
var zopimConfig = function() {
$zopim(function() {
$zopim.livechat.clearAll();
(......)
});
};
var waitForZopim = setInterval(function () {
if (
window.$zopim === undefined ||
window.$zopim.livechat === undefined ||
window.$zopim.livechat.departments === undefined
) {
return;
}
zopimConfig();
clearInterval(waitForZopim);
}, 100);
Waiting for window.$zopim.livechat.departments is optional, however if you're doing anything around departments I would recommend keeping this
Given the following snippet of code
var empowerInstance = null;
function onClick_btnSendMessage() {
var childIFrame = window.document.getElementById("editorFrame");
if (!empowerInstance) {
empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
}
empowerInstance.document.hasChanged(hasChangedCallback);
}
function hasChangedCallback(returnValue) {
console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
if (returnValue.success === true && returnValue.isDirty === true) {
empowerInstance.document.save(saveCallback);
}
}
function saveCallback(returnValue) {
console.log("empowerInstance.document.save = " + returnValue.success);
if (returnValue.success === false) {
console.log(returnValue.message);
}
}
window.addEventListener("DOMContentLoaded", function (event) {
console.log("DOM fully loaded and parsed");
if (typeof location.origin === "undefined")
window.location.origin = window.location.protocol + "//" + window.location.host;
document.getElementById("btnSendMessage").addEventListener("click", onClick_btnSendMessage);
});
Instead of wiring the button up , I'd like to fire the code from the activation of a Bootstrap tab event.
$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {
onClick_btnSendMessage(); // Naive way, as this does not wait
var target = $(e.target).attr("data-EditorUrl"); // activated tab
var childIFrame = $("#editorFrame");
childIFrame.attr("src", target);
});
So my question is "How do I wait on this function to complete before changing the source of childIFrame?".
empowerInstance.document.hasChanged(hasChangedCallback);
I conceptually understand the use of Promises and Callbacks, but writing one that functions correctly is a different story.
UPDATED
This version is refactored to eliminate the button handler, thus improving readability.
The usage is also important. When the page loads for the first time it is positioned on a tab. This tab is associated to a document that is hosted in an iFrame. If the user edits this document then tries to change tabs, I'd like to invoke the check for being dirty/save, then once saved, move to the next tab/document. There is also the case that switching between tabs/documents won't cause a save because the document is not dirty.
var empowerInstance = null;
function hasChangedCallback(returnValue) {
console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
if (returnValue.success === true && returnValue.isDirty === true) {
empowerInstance.document.save(saveCallback);
}
}
function saveCallback(returnValue) {
console.log("empowerInstance.document.save = " + returnValue.success);
if (returnValue.success === false) {
console.log(returnValue.message);
}
}
$(function () {
if (typeof location.origin === "undefined") {
window.location.origin = window.location.protocol + "//" + window.location.host;
}
$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {
var childIFrame = $("#editorFrame");
if (!empowerInstance) {
empowerInstance = EditorAPI.getInstance(childIFrame[0].contentWindow, window.location.origin);
}
empowerInstance.document.hasChanged(hasChangedCallback);// Need to wait for completion
var target = $(e.target).attr("data-EditorUrl"); // activated tab
childIFrame.attr("src", target);
});
});
Thank you,
Stephen
I've refactored your code to show how this can be done using promises.
function onClick_btnSendMessage() {
var childIFrame = window.document.getElementById("editorFrame");
if (!empowerInstance) {
empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
}
var doc = empowerInstance.document;
return hasChanged(doc).then(function() { return save(doc) })
}
function hasChanged(doc) {
return new Promise(function(resolve, reject) {
doc.hasChanged(function(returnValue) {
if (returnValue.success === true && returnValue.isDirty === true) {
resolve(returnValue)
} else {
reject(returnValue)
}
})
})
}
function save(doc) {
return new Promise(function(resolve, reject) {
doc.save(function(returnValue) {
if (returnValue.success === false) {
console.log(returnValue.message);
reject(returnValue)
} else {
resolve(returnValue)
}
})
})
}
// ------
$('a[data-toggle="tab"]').on("shown.bs.tab", function(e) {
onClick_btnSendMessage().then(function() {
var target = $(e.target).attr("data-EditorUrl"); // activated tab
var childIFrame = $("#editorFrame");
childIFrame.attr("src", target);
}).catch(function(error) {
// handle the error
console.error('Error!', error)
})
});
You can use some higher order functions to do what you want. Instead of passing the hasChangedCallback and saveCallback directly to the empowerInstance.document methods, you'll instead invoke a function that returns those callbacks, but also passes along your own callback that you'll call once all the async operations have finally completed. Here's what it'll look like:
$('a[data-toggle="tab"]').on("shown.bs.tab", function (e) {
var target = $(e.target).attr("data-EditorUrl"); // activated tab
onClick_btnSendMessage(function () {
var childIFrame = $("#editorFrame");
childIFrame.attr("src", target);
});
});
function onClick_btnSendMessage(myCallback) {
var childIFrame = window.document.getElementById("editorFrame");
if (!empowerInstance) {
empowerInstance = EditorAPI.getInstance(childIFrame.contentWindow, window.location.origin);
}
empowerInstance.document.hasChanged(getHasChangedCallback(myCallback));
}
function getHasChangedCallback(myCallback) {
return function hasChangedCallback(returnValue, myCallback) {
console.log("empowerInstance.document.hasChanged = " + returnValue.isDirty);
if (returnValue.success === true && returnValue.isDirty === true) {
empowerInstance.document.save(getSaveCallback(myCallback));
}
}
}
function getSaveCallback(myCallback) {
return function saveCallback(returnValue) {
console.log("empowerInstance.document.save = " + returnValue.success);
if (returnValue.success === false) {
console.log(returnValue.message);
}
myCallback && myCallback(); // make sure myCallback isn't null before invoking
}
}
It's not exactly attractive, but it should get you what you want.
i have call the below function in my application
function workerCall() {
debugger;
if (typeof (Worker) !== "undefined") {
var worker = new Worker("Scripts/worker.js");
worker.onmessage = workerResultReceiver;
worker.onerror = workerErrorReceiver;
worker.postMessage({ 'username': Username });
function workerResultReceiver(e) {
$('.NotificationCount').html(e.data);
if (parseInt(e.data) != 0 && currentPage == "Alert") {
StateFlag = false;
$('.Notification').show();
$('.Drildown').each(function () {
var temp = this.id;
if ($('#' + temp).attr('expand') == "true") {
currentTab = temp;
StateFlag = true;
}
});
currentScrollPosition = $('body').scrollTop();
GetAlerts();
} else {
$('.Notification').hide();
}
}
function workerErrorReceiver(e) {
console.log("there was a problem with the WebWorker within " + e);
}
}
else {
}
}
the method will execute in IE,Chrome but when comes to Mozilla i got an error ReferenceError: workerResultReceiver is not defined.How can i resolve this error?
This happens because you are making reference to function that is not created yet. You need to put this:
worker.onmessage = workerResultReceiver;
worker.onerror = workerErrorReceiver;
Above
function workerErrorReceiver
line or at the end of the scope.
I have the following code snippet that controls an embedded youtube player. It works great on Chrome and Safari but not on Firefox.
jsfiddle : http://jsfiddle.net/fuSSn/4/
Code from my app:
the iframe:
<div class="tubeframe" id="video-frame-155" style="">
<iframe title="YouTube video player" width="350" height="262" src="http://www.youtube.com/embed/hc5xkf9JqoE?HD=1;rel=0;showinfo=0;autohide=1" frameborder="0" allowfullscreen="" id="video-frame-155-frame"></iframe>
</div>
my javascript:
var source_tag = document.createElement("script");
var first_source_tag = document.getElementsByTagName("script")[0];
first_source_tag.parentNode.insertBefore(source_tag, first_source_tag);
// This function will be called when the API is fully loaded
function onYouTubeIframeAPIReady() {
YT_ready(true)
console.log("api loaded! yikes")
}
function getFrameID(id){
var elem = document.getElementById(id);
if (elem) {
if(/^iframe$/i.test(elem.tagName)) return id; //Frame, OK
// else: Look for frame
var elems = elem.getElementsByTagName("iframe");
if (!elems.length) return null; //No iframe found, FAILURE
for (var i=0; i<elems.length; i++) {
if (/^https?:\/\/(?:www\.)?youtube(?:-nocookie)?\.com(\/|$)/i.test(elems[i].src)) break;
}
elem = elems[i]; //The only, or the best iFrame
if (elem.id) return elem.id; //Existing ID, return it
// else: Create a new ID
do { //Keep postfixing `-frame` until the ID is unique
id += "-frame";
} while (document.getElementById(id));
elem.id = id;
return id;
}
// If no element, return null.
return null;
}
// Define YT_ready function.
var YT_ready = (function(){
var onReady_funcs = [], api_isReady = false;
return function(func, b_before){
if (func === true) {
api_isReady = true;
while(onReady_funcs.length > 0){
// Removes the first func from the array, and execute func
onReady_funcs.shift()();
}
}
else if(typeof func == "function") {
if (api_isReady) func();
else onReady_funcs[b_before?"unshift":"push"](func);
}
}
})();
var video = function ( videoid, frameid) {
var player;
var that;
var seconds;
var duration;
var stateChangeCallback;
var update_play = 0;
return {
setOnStateChangeCallback: function(callback) {
stateChangeCallback = callback;
},
getCurrentTime: function() {
return player.getCurrentTime();
},
getPlayer: function () {
return player;
},
getVideoFrameId: function () {
return "video-frame-" + videoid;
},
initVideo: function (second) {
console.log("initing")
that = this;
YT_ready(function(){
var frameID = getFrameID("video-frame-" + videoid);
console.log("creating player")
console.log(frameID)
if (frameID) { //If the frame exists
console.log("frame exists")
player = new YT.Player(frameID, {
events: {
"onStateChange": that.stateChange
}
});
console.log("Player Created!");
if (second) {
console.log(second)
setTimeout(function() { console.log("seek to"); player.seekTo(second, false); player.stopVideo()}, 1000);
}
}
});
},
stateChange: function (event) {
console.log("event.data = ", event.data);
switch(event.data) {
case YT.PlayerState.PLAYING:
{
if (stateChangeCallback)
stateChangeCallback("play", player.getCurrentTime(), player.getDuration());
onsole.log("play");
}
break;
case YT.PlayerState.PAUSED:
case YT.PlayerState.CUED:
case YT.PlayerState.ENDED:
{
if (stateChangeCallback)
stateChangeCallback("pause", player.getCurrentTime(), player.getDuration());
console.log("pause");
}
break;
}
},
pauseVideo: function () {
player.stopVideo();
console.log('player.stopVid()');
},
seekTo: function(second) {
player.seekTo(second, false);
}
};
};
function onStateChange(vid, action, second, total) {
if (Videos[vid]) {
console.log( (second / total) * 100);
}
};
$(document).ready(function () {
var Videos = {};
logger.info("heyyy")
var videoId=155;
//if (videoId) {
Videos[videoId] = video(videoId, 155);
console.log(Videos[155])
Videos[155].initVideo();
Videos[155].setOnStateChangeCallback(function(action, second, total) {
onStateChange(155, action, second, total);
});
//}
Videos[155].seekTo(1000, false);
onStateChange(155, "start", 0, 0);
});
I know that the required script tags are being added, I can test that from console. I also know that onYouTubePlayerAPIReady() is actually called. But I still receive errors like
TypeError: player.stopVideo is not a function
When I run the three lines that adds the source tag again from the web console on firefox, the api seems to load and everything starts working again.
I have been struggling with this for days and I really need help figuring out what might be wrong. If it helps my application is developed in ruby on rails but I don't think this is relevant information.
Thanks
There is no problem with the above code. My video was loaded in a bootstrap modal. Modal's hide property would make it invisible to firefox and firefox would not load the api at all. So I removed the modal hide class and instead of display:none I used item.css("visibility", "visible"); and item.css("visibility", "hidden"); which made firefox load the api.
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
What is the difference of using addEventListener?
I noticed that events can be assigned directly on objects, without having to use addEventListener:
document.onload = function(e){
// do stuff..
};
instead of:
document.addEventListener('load', function(e){
// do stuff..
});
So is there any reason I shouldn't use the first method? Why don't other people use it?
Also this appear to work in old IE too (in which you needed attachEvent).
Consider what happens if you try the following (I'm attaching the events to window because that is where you should listen for this event)
window.onload = function (e) {console.log('A');};
window.onload = function (e) {console.log('B');};
vs
window.addEventListener('load', function (e) {console.log('C');}, false);
window.addEventListener('load', function (e) {console.log('D');}, false);
From the first code block you will only see "B", but from the second you'll see both "C" and "D". Fiddle (please open console to see).
Apart from the fact that, as the accepted answer shows, directly binding handlers to the DOM limits the amout of handlers, addEventListener has a lot more to offer:
The event listener needn't be bound to the element directly (it doesn't have to exist in the DOM). This is useful when using ajax (think of it as jQuery's .on method).
A single listener can deal with all events of a particular type, so using event listeners requires less resources (which can speed up the overall performance)
For X-browser compatibility (like IE8), it's a lot easier to avoid memory-leaks:
window.onload = function(e)
{
alert('In IE8, this causes mem-leaks!');
};
var load = function(e)
{//cf #PaulS.'s comment & link on IE8 and symbol bleeding
e = e || window.event;//X-browser stuff
var target = e.target || e.srcElement;//so you can use this callback for all browsers
if (window.removeEventListener)
{//more X-browser stuff
return window.removeEventListener('load',load,false);
}
window.detachEvent('onload',load);//reference callback by variable name
};
if (window.addEventListener)
{
window.addEventListener('load',load,false);
}
else
{//in IE8, addEventListener doesn't exist, but it has a jScript counterpart:
//no mem-leaks in IE AFAIK
window.attachEvent('onload', load);
}
Here's a couple of links which might interest you (Yes, I know, shameless self-promotion - sorry):
Why do we need event listeners?
mem-leaks & event delegation & closures in IE8
And just for fun: a script I wrote a while back that uses event delegation and works on IE, FF, chrome, ... and touch devices. Which was a bit more tricky than I expected.
/**
* Copyright 2012, Elias Van Ootegem
* Date: Tue Jul 03 2012 +0100
*/
(function(G,undef)
{
'use strict';
var load,clickHandler,touchHandler,hide,reveal;
hide = function(elem)
{
elem.setAttribute('style','display:none;');
};
reveal = function(show,nextTo)
{
var str = 'display: block; position:relative; left:220px; top: ' + (nextTo.offsetTop - show.parentNode.offsetTop) + 'px;';
show.setAttribute('style',str);
}
load = function()
{
var doc = G.document;
if (G.removeEventListener)
{
G.removeEventListener('load',load,false);
}
else
{
G.detachEvent('onload',load);
}
if (doc.hasOwnProperty('ontouchstart'))
{//We have a touch device
touchHandler = (function(subNavs)
{
var current,divs = (function()
{
var i,r = {};
for (i=0;i<subNavs.length;i++)
{
r[subNavs[i].id] = doc.getElementById(subNavs[i].id + 'd');
hide(r[subNavs[i].id]);
}
return r;
}());
return function(e)
{
e = e || G.event;
if (e.changedTouches.length !== 1)
{//multi-touch
return e;
}
var timer,endListener,coords,target = e.target || e.srcElement;
if (target.tagName.toLowerCase() === 'img' && target.id.match(/^close[0-9]+$/))
{
hide(current);
current = undef;
return e;
}
if (target.tagName.toLowerCase() === 'a')
{
target = target.parentNode;
}
if (target.tagName.toLowerCase() !== 'p' || !target.id || !divs[target.id])
{
if (current === undef)
{
return e;
}
while(target !== doc.body)
{
target = target.parentNode;
if (target === current)
{
return e;
}
}
timer = setTimeout(function()
{
doc.body.removeEventListener('touchend',endListener,false);
clearTimeout(timer);
timer = undef;
},300);
endListener = function(e)
{
doc.body.removeEventListener('touchend',endListener,false);
clearTimeout(timer);
timer = undef;
hide(current);
current = undef;
return e;
};
return doc.body.addEventListener('touchend',endListener,false);
}
coords = {x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY};
timer = setTimeout(function()
{
doc.body.removeEventListener('touchend',endListener,false);
clearTimeout(timer);
timer = undef;
},300);
endListener = function(e)
{
e = e || G.event;
clearTimeout(timer);
timer = undef;
doc.body.removeEventListener('touchend',endListener,false);
var endCoords,endTarget = e.target || e.srcElement;
if (endTarget !== target)
{
endCoords = {x:e.changedTouches[0].clientX,y:e.changedTouches[0].clientY};
if (Math.abs(coords.x - endCoords.x) < 26 && Math.abs(coords.y - endCoords.y) < 26)
{
endTarget = target;
}
}
if (endTarget !== target)
{
return e;
}
if (current !== undef)
{
hide(current);
current = undef;
}
current = divs[target.id];
reveal(current,target);
};
doc.body.addEventListener('touchend',endListener,false);
};
}(doc.getElementsByClassName('subnavbar')));
return doc.body.addEventListener('touchstart',touchHandler,false);
}
clickHandler = (function(subNavs)
{
var current,divs = (function()
{
var i,r = {};
for (i=0;i<subNavs.length;i++)
{
r[subNavs[i].id] = doc.getElementById(subNavs[i].id + 'd');
hide(r[subNavs[i].id]);
}
return r;
}());
return function(e)
{
e = e || G.event;
var target = e.target || e.srcElement;
if (target.tagName.toLowerCase() === 'img' && target.id.match(/^close[0-9]+$/))
{
hide(current);
current = undef;
return e;
}
if (target.tagName.toLowerCase() === 'a')
{
target = target.parentNode;
}
if (target.tagName.toLowerCase() !== 'p' || !target.className.match(/\bsubnavbar\b/))
{
if (current !== undef)
{
target = (function()
{
while (target !== doc.body)
{
target = target.parentNode;
if (target === current)
{
return current;
}
}
}());
if (target !== current)
{
hide(current);
current = undef;
}
}
return e;
}
if (e.preventDefault)
{
e.preventDefault();
e.stopPropagation();
}
else
{
e.returnValue = false;
e.cancelBubble = true;
}
if (current !== undef)
{
hide(current);
}
current = divs[target.id];
reveal(current,target);
};
}(doc.getElementsByClassName('subnavbar')));
if (doc.body.addEventListener)
{
return doc.body.addEventListener('click',clickHandler,false);
}
return doc.body.attachEvent('onclick',clickHandler);
};
if (G.addEventListener)
{
return G.addEventListener('load',load,false);
}
return G.attachEvent('onload',load);
}(this));