jQuery ready firing before custom behavior script initializes - javascript

Background
I've inherited an ancient web application that has input controls with custom behaviors defined with an old-fashioned HTC (HTML Component) script, e.g.:
<input name="txtFiscalYearEndDay" type="text" value="30"
maxlength="2" size="5" id="txtFiscalYearEndDay" class="Text1"
style="behavior:url(/path/js/InFocus.htc);" />
Here are the relevant parts of this HTC file to illustrate the issue:
<PUBLIC:COMPONENT tagName="InFocus">
<PUBLIC:METHOD NAME="setValid" />
<PUBLIC:ATTACH EVENT="ondocumentready" HANDLER="initialize" />
<SCRIPT LANGUAGE="javascript">
function initialize() {
// attaches events and adds CSS classes, nothing fancy
}
function setValid(bInternal) {
// checks some flags and changes a label
}
</SCRIPT>
</PUBLIC:COMPONENT>
So, nothing out of the ordinary so far. Additionally, I have some JS that runs on DOM-ready:
$(function() {
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
});
And the validation function:
function txtFiscalYearEndDay_Validate(el) {
...
}
Note: I'm not using $('#txtFiscalYearEndDay') because then I really can't try to call setValid(true); on the element, nor do I want to have to do $('#txtFiscalYearEndDay')[0].setValid(true);.
The problem
At one point in the validation function, I'm attempting to call a method on the element, the one added by the HTC script:
el.setValid(true);
However, the IE debugger gets sad and complains that setValid() is not a function. Inspecting it in the debugger confirms this:
typeof el.setValid // "unknown"
Of course, once the page has completed rendering (or whatever period of time is needed for the document to actually be ready has passed), the validation function works as expected (because I'm calling the same validation function on change and blur events as well). That is, when the function is called outside of jQuery's on-DOM-ready function, it works just fine.
Do any of you have any ideas at to what might be happening here? Is jQuery's "ondomready" being registered before the HTC script's "ondomready"? Can I somehow change that order?
I'm currently seeing this behavior in all versions of IE.
EDIT: WORKAROUND
I discovered a workaround. If you take the function call out of the jQuery ready function and throw it at the end of the page, it works (i.e.:)
...
<script type="text/javascript">
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
</script>
</body>
</html>

I do not know if HTC counts toward page ready but i suspect they do not.
What you might try is check something that only is tru after the HTC hase finished.
You own script should then start something like this:
function MyFunction() {
if(!HTCIsreadyTest()) {
setTimeout(MyFunction, 100);
return;
}
//the rest of your code
}
This basically makes you function check and restart in 100 milliseconds if conditions are not met untill the test succeds.
You could also ad a counter argument increasing it by one for each attempt to have some timeout code trigger if HTC sciprts has not loaded after 2 seconds

The easiest workaround I could find was to move the validation function call out of the jQuery ready() callback and move it to the end of the page:
...
<script type="text/javascript">
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
</script>
</body>
</html>
However, I found a more elegant solution. Because I seemingly need to wait for all page resources to be loaded, I simply needed to move the function call out of the jQuery ready() callback and instead put it in a window load() callback:
$(window).load(function() { // instead of $(function() {
txtFiscalYearEndDay_Validate(document.getElementById('txtFiscalYearEndDay'));
});
I'm using the latter so I can keep all of the JS code together.

Related

Would like help figuring out the best way to watch for changes to the DOM

I'm trying to use Jquery-Mutation Summary https://code.google.com/p/mutation-summary/
"a JavaScript library that makes observing changes to the DOM fast, easy and safe"
It can be found here: https://github.com/joelpurra/jquery-mutation-summary
Here's an example of it at work:
http://joelpurra.github.io/jquery-mutation-summary/example/demo.html
All I want to do is call a function when there's changes to content within an element such as a div with an id "MyDiv"
Here's my code. What am I doing wrong? There is no alert message as my function that's being called in this example is suppose to display once changes are observed.
<script src="http://joelpurra.github.io/jquery-mutation-summary/lib/mutation-summary /src/mutation-summary.js"></script>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://joelpurra.github.io/jquery-mutation-summary/src/jquery.mutation- summary.js"></script>
<script>
function MyFunction(){
alert('changes have been made');
}
// This code won't be executed until jQuery has been loaded.
$(function() {
var $ChangeThere = $("#MyDiv")
// The callback in this case will only print the result
// Connect mutation-summary
$ChangeThere.mutationSummary("connect", callback, [{
all: true
}]);
// Disconnect when done listening
//$ChangeThere.mutationSummary("disconnect");
function callback(summaries) {
MyFunction();
}
});
</script>
In newer versions of Firefox, a mozAfterPaint event exists, but there's obvious compatibility problems with that, so I'd suggest checking for the various things that cause reflows by adding a check into an abstracted version of each cause. A list of causes and some other information can be found in this SO

$(document).ready() fires too early

So, I need to know the width of an element with javascript, the problem I have is that the function fires too early and the width changes when the css is tottally applied. As I understood, the $(document).ready() function was fired when the document is completed, but it doesn't seem to work like that.
Anyways, I'm sure that with the code my problem will be understood (this is a simplified example):
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<link href='http://fonts.googleapis.com/css?family=Parisienne' rel='stylesheet' type='text/css'>
<style type="text/css">
#target {
font-family: 'Parisienne', cursive;
float: left;
}
</style>
</head>
<body>
<div id="target">Element</div>
</body>
</html>
<script type="text/javascript">
$(document).ready(function(){
console.debug($('#target').outerWidth());
alert('hold on');
console.debug($('#target').outerWidth());
});
</script>
I want to know the width of the #target div, the problem is that the code that's executed before the alert gives a different output than the one after, presumably because the font is not fully loaded and it's measuring the div with the default font.
It works as I expect in Google Chrome, but it doesn't on IE and Firefox.
If you rely on external content to be already loaded (e.g. images, fonts), you need to use the window.load event
$(window).on("load", function() {
// code here
});
The behaviour of these events is described in this article:
There is [a] ready-state however known as DOM-ready. This is when the browser has actually constructed the page but still may need to grab a few images or flash files.
Edit: changed syntax to also work with jQuery 3.0, as noted by Alex H
Quote OP:
"As I understood, the $(document).ready() function was fired when the document is completed,"
$(document).ready() fires when the DOM ("document object model") is fully loaded and ready to be manipulated. The DOM is not the same as the "document".
W3C - DOM Frequently Asked Questions
You can try $(window).load() function instead...
$(window).load(function() {
// your code
});
It will wait for all the page's assets (like images and fonts, etc.) to fully load before firing.
The jQuery .ready() function fires as soon as the DOM is complete. That doesn't mean that all assets (like images, CSS etc) have been loaded at that moment and hence the size of elements are subject to change.
Use $(window).load() if you need the size of an element.
The "ready" event fires when the DOM is loaded which means when it is possible to safely work with the markup.
To wait for all assets to be loaded (css, images, external javascript...), you'd rather use the load event.
$(window).load(function() {
...
});
You could use $(window).load(), but that will wait for all resources (eg, images, etc). If you only want to wait for the font to be loaded, you could try something like this:
<script type="text/javascript">
var isFontLoaded = false;
var isDocumentReady = false;
$("link[href*=fonts.googleapis.com]").load(function () {
isFontLoaded = true;
if (isDocumentReady) {
init();
}
});
$(document).ready(function () {
isDocumentReady = true;
if (isFontLoaded) {
init();
}
});
function init () {
// do something with $('#target').outerWidth()
}
</script>
Disclaimer: I'm not totally sure this will work. The <link> onload event may fire as soon as the stylesheet is parsed, but before its external resources have been downloaded. Maybe you could add a hidden <img src="fontFile.eot" /> and put your onload handler on the image instead.
I have absolutely, repeatably seen the same problem in IE9 and IE10. The jquery ready() call fires and one of my <div>'s does not exist. If I detect that and then call again after a brief timeout() it works fine.
My solution, just to be safe, was two-fold:
Append a <script>window.fullyLoaded = true;</script> at the end of the document then check for that variable in the ready() callback, AND
Check if $('#lastElementInTheDocument').length > 0
Yes, I recognize that these are nasty hacks. However, when ready() isn't working as expected some kind of work-around is needed!
As an aside, the "correct" solution probably involves setting $.holdReady in the header, and clearing it at the end of the document. But of course, the really-correct solution is for ready() to work.
The problem $(document).ready() fires too early can happen sometimes because you've declared the jQuery onReady function improperly.
If having problems, make sure your function is declared exactly like so:
$(document).ready(function()
{
// put your code here for what you want to do when the page loads.
});
For example, if you've forgotten the anonymous function part, the code will still run, but it will run "out of order".
console.log('1');
$(document).ready()
{
console.log('3');
}
console.log('2');
this will output
1
3
2

JavaScript onload function call suddenly produces "Object expected"

The following onload function call was working:
<script type="text/javascript">
function frameloaded() {
if (parent.leftframe) {
parent.leftframe.reportRightFrameReloaded();
}
}
</script>
</head>
<body onload="frameloaded();">
.... etc.
until I added an external javascript reference
<script type="text/javascript" src="sorttable.js"></script<
immediately before it. Then suddenly it started giving me "Object expected" in IE (I have IE8) and simply stopped working in Firefox (3.6.3). I figured there was a duplicate function name in the included file, so I gave it a random name and it still failed. I tried using
onload="this.frameloaded();"
and
onload="document.frameloaded();"
with no luck.
I tried moving my function above the included statement, but just got an empty frame.
Any ideas?
Thanks!
#Hamish was right. The problem was in sorttable.js. It uses window.onload, which conflicted with my BODY onload. In sorttable.js, there were several window.onload statements within some elaborate logic, so I couldn't just use the recommended solution in such cases, which would be to trigger all the required onload functions in the BODY onload event.
Instead, my solution, which I am not entirely comfortable with, is to put the contents of my frameloaded() method at the bottom of the BODY, but not inside a function. This way it executes as late as possible during the load process. This works (i.e. it runs after the tables that have to be loaded first have been loaded) in IE and Safari, but I am having trouble with the other browsers.

jQuery not getting called in all browsers

Disclaimer: I am new to jQuery.
I am trying to implement a fadeOut effect in jQuery for a div block, and then fadeIn effect on two other div blocks.
However, these effects are only working in the Chrome browser (i.e. they won't work in Safari, FireFox, Opera) which is rather perplexing to me. I have tried clearing my cache in case it was storing an old file, but none of that seemed to do anything.
Basic idea (stored in mainsite.js file):
$("#videoThumbnail_XYZ").click(function () {
$("#thumbnailDescription_XYZ").fadeOut(300);
$("#videoPlayer_XYZ").delay(300).fadeIn(100);
$("#videoHiddenOptions_XYZ").delay(300).fadeIn(100);
});
So when a div tag with the id of videoThumbnail_XYZ is clicked, it starts the fadeOut and fadeIn calls on the other div tags.
I am loading my javascript files into the page in this order (so jQuery is loaded first):
<script src="http://code.jquery.com/jquery-1.4.4.js"></script>
<script async="" type="text/javascript" src="javascripts/mainsite.js"></script>
Any guidance you could give is greatly appreciated!
Make sure the DOM is fully loaded before your code runs.
A common way of doing this when using jQuery is to wrap your code like this.
$(function() {
$("#videoThumbnail_XYZ").click(function () {
$("#thumbnailDescription_XYZ").fadeOut(300);
$("#videoPlayer_XYZ").delay(300).fadeIn(100);
$("#videoHiddenOptions_XYZ").delay(300).fadeIn(100);
});
});
This is a shortcut for wrapping your code in a .ready() handler, which ensure that the DOM is loaded before your code runs.
If you don't use some means of ensuring that the DOM is loaded, then the #videoThumbnail_XYZ element may not exist when you try to select it.
Another approach would be to place your javascript code after your content, but inside the closing </body> tag.
<!DOCTYPE html>
<html>
<head><title>your title</title></head>
<body>
<!-- your other content -->
<script src="http://code.jquery.com/jquery-1.4.4.js"></script>
<script async="" type="text/javascript" src="javascripts/mainsite.js"></script>
</body>
</html>
If mainsite.js is being included before your div is rendered, that might be throwing the browsers for a loop. Try wrapping this around your click handler setup:
$(document).ready(function(){
// your function here
});
That'll make sure that isn't run before the DOM is ready.
Also, you might consider putting the fadeIn calls in the callback function of your fadeOut, so if you decide to change the duration later on, you only have to change it in one place.
The way that'd look is like this:
$("#thumbnailDescription_XYZ").fadeOut(300,function(){
$("#videoPlayer_XYZ").fadeIn(100);
$("#videoHiddenOptions_XYZ").fadeIn(100);
});
I see you have a delay set to the same duration your fadeOut is, I would recommend instead of delaying which in essence your waiting for the animation to complete that instead you use the callback function.
$("#videoThumbnail_XYZ").click(function () {
$("#thumbnailDescription_XYZ").fadeOut(300, function() {
$("#videoPlayer_XYZ").fadeIn(100);
$("#videoHiddenOptions_XYZ").fadeIn(100);
});
});
While JavaScript provides the load event for executing code when a page is rendered, this event does not get triggered until all assets such as images have been completely received. In most cases, the script can be run as soon as the DOM hierarchy has been fully constructed. The handler passed to .ready() is guaranteed to be executed after the DOM is ready, so this is usually the best place to attach all other event handlers and run other jQuery code.
$(document).ready(function(){
$("#videoThumbnail_XYZ").click(function () {
$("#thumbnailDescription_XYZ").fadeOut(300);
$("#videoPlayer_XYZ").delay(300).fadeIn(100);
$("#videoHiddenOptions_XYZ").delay(300).fadeIn(100);
});
});
All three of the following syntaxes are equivalent:
* $(document).ready(handler)
* $().ready(handler) (this is not recommended)
* $(handler)

Event handling issues

I have a couple of, what may end up being for this forum, overly-novice questions regarding unobtrusive event handling.
As I understand it, a properly set-up document would look something like this:
<html>
<head>
<title>Title</title>
<script src="jsfile.js" type="text/javascript></script>
</head>
<body>
//Body content, like some form elements in my case
</body>
</html>
Jsfile.js would look something like this:
function a() {
//code;
}
function b()...
window.addEventListener('load', a, false);
document.getElementById("id").addEventListener('click', b, false);
document.myForm.typeSel.addEventListener('change', c, false);
//or to use better browser-compatible code...
function addEvent(obj,evt,fn) {
if (obj.addEventListener)
obj.addEventListener(evt,fn,false);
else if (obj.attachEvent)
obj.attachEvent('on'+evt,fn);
}
addEvent(window, 'load', a);
addEvent(document.getElementById('id'), 'click', b);
addEvent(document.myForm.typeSel, 'change', c);
As I understand it, while in the head the browser will load this JavaScript code, adding each of those event handlers to their respective elements. HOWEVER... While the window handler is added properly, none of the others are. But if placed within a function, the (for instance) getElementById method of accessing an element works just fine, and the event handler is added. So I could conceivably make a loadEvents() function which is called via window onload, which contains all of the addEvent() functions for the other document elements for which I need event handlers. But as I understand the whole thing, I shouldn't have to do this.
In addition, if I were to stick the addEvent code within the body along with the element it addresses, such as:
<input type="checkbox" id="test" />
<script type="text/javascript>
document.getElementById("test").onclick = func;
</script>
...then it works fine. But of course it also violates the whole reason for removing inline event handlers!
So question being: In order to use *element*.addEventListener('click',func,false), addEvent(*element*,'click',func), or even *element*.onclick = func - how can I successfully reference an element at the end of a script file in the head, without having to stick it in another function? Why does getElementById and other such methods not work outside of a function in the head?
Or, is there some flaw in my underlying understanding?
Putting <script> in the <head> used to be the wisdom. But nowadays, with heavy ajax pages, <script> is more and more often but in the body, as far down below as possible. The idea is that the loading and parsing of the <script> keeps the rest of the page from loading, so the user will be looking at a blank page. By making sure the body is loaded as fast as possible, you give the user something to look at. See YAHOO best practices for a great explanation on that issue: http://developer.yahoo.com/performance/rules.html
Now, regardless of that issue, the code as you set it up now, can't work - at least, not when the elements you attempt to attach the handlers to aren't created yet. For example, in this line:
document.getElementById("id").addEventListener('click', b, false);
you will get a runtime error if the element with id="id" is inside the body. Now, if you put the <script> in the body, way below, after the content (including the lement with id="id", it will just work, since the script is executed after the html code for those elements is parsed and added to the DOM.
If you do want to have the script in the head, then you can do so, but you'll need to synchronize the adding of the event handlers with the rendering of the page content. You could do this by adding them all inside the document or window load handler. So, if you'd write:
//cross browser add event handler
function addEventHandler(obj,evt,fn) {
if (obj.addEventListener) {
obj.addEventListener(evt,fn,false);
} else if (obj.attachEvent) {
obj.attachEvent('on'+evt,fn);
}
}
addEventHandler(document, 'load', function(){
//set up all handlers after loading the document
addEventHandler(document.getElementById('id'), 'click', b);
addEventHandler(document.myForm.typeSel, 'change', c);
});
it does work.
The reason why window.addEventListener works while document.getEle...().addEventListener does not is simple: window object exists when you're executing that code while element with id="abc" is still not loaded.
When your browser downloads page's sources the source code is parsed and executed as soon as possible. So if you place script in head element - on the very beginning of the source - it's executed before some <div id="abc">...</div> is even downloaded.
So I think now you know why
<div id="test">Blah</div>
<script type="text/javascript">document.getElementById("test").style.color = "red";</script>
works, while this:
<script type="text/javascript">document.getElementById("test").style.color = "red";</script>
<div id="test">Blah</div>
doesn't.
You can handle that problem in many ways. The most popular are:
putting scripts at the end of document (right before </body>)
using events to delay execution of scripts
The first way should be clear right now, but personally I prefer last one (even if it's a little bit worse).
So how to deal with events? When browser finally download and parse whole source the DOMContentLoaded event is executed. This event means that the source is ready, and you can manipulate DOM using JavaScript.
window.addEventListener("DOMContentLoaded", function() {
//here you can safely use document.getElementById("...") etc.
}, false);
Unfortunately not every browser support DOMContentLoaded event, but as always... Google is the anwser. But it's not the end of bad news. As you noticed addEventListener isn't well supported by IE. Well... this browser really makes life difficult and you'll have to hack one more thing... Yes... once again - Google. But it's IE so it's not all. Normal browsers (like Opera or Firefox) supports W3C Event Model while IE supports its own - so once again - Google for cross-browser solution.
addEventListener might seems now the worst way to attach events but in fact it's the best one. It let you easly add or remove many listeners for single event on single element.
PS. I noticed that you consider of using Load event to execute your scripts. Don't do that. Load event is execute too late. You have to wait till every image or file is loaded. You should use DOMContentLoaded event. ;)
EDIT:
I've forgotten... dealing with cross-browser event model is much easier when you're using some framework like very popular jQuery. But it's good to know how the browsers work.
are you familiar with jQuery?
its a javascript library featuring some really awesome tools.
for instance if you want to have some js action done just after your page if fully loaded and all DOM elements are created (to avoid those annoying exceptions) you can simply use the ready() method.
also i see you want to attach click \ change events jQuery takes care of this too :) and you don't have to worry about all those cross-browser issues.
take a look at jQuery selectors to make your life easier when attempting to fetch an element.
well thats it, just give it a shot, its has a very intuitive API and a good documentation.

Categories

Resources