DOM not showing before onload - javascript

I am having an issue where my DOM is not displaying when my window.onload (or ) fires. In my actual project, the javascript function I call is gathering quite a bit of data, so I am trying to simply display a splash style screen first. Easy...but it is not working as I expect and I feel I am missing something.
I have seen similar questions, but most of them have an 'alert' as part of the testing solution. Anything that breaks the script running allows the DOM to display...so these 'solutions' are not fixing my issue. If I step through the code (IE or Chrome), then all works fine.
Here is a sample that reproduces the issue. At least when I run this in Chrome, nothing displays until the 3rd second...then the alert pops and the Loading div shows. In IE, Loading Div does not show until all 5 seconds pass and function completes.
Thanks in advance for any thoughts!
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#Loading{
position: absolute;
Left: 50px;
Top: 50px;
width: 100%;
height: 100%;
background-color: yellow;
display: block;
}
</style>
<script>
window.onload = Hmm;
function Hmm(){
i = 0;
while (i < 5){
sleep(1000);
i++;
if (i == 3) {
alert("hi");
}
}
}
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
</script>
</head>
<body>
<div id="Loading">Loading...</div>
</body>
</html>

I highly recommend you read Events and Timing In-Depth, now. I'm going to assume your actual project code that's "gathering quite a bit of data" is not written asynchronously, much like your test code here.
Here are a few nuggets of knowledge from the linked article:
Most browsers use [a] single thread for UI and JavaScript, which is
blocked by synchronous calls. So, JavaScript execution blocks the
rendering.
Events are processed asynchronously with the exception of DOM events.
And lastly, the rendering pipeline is not the same across browsers. Your results will vary.

Related

HTML Textarea auto-width with custom font returns wrong scrollWidth initially

EDIT: Please refer to bottom of post with a workaround I found which works in Chrome but not in Safari/Firefox.
I have a textarea which I want the width & height to grow/shrink as user types in it. While most solutions on stack overflow are regarding height auto-grow, I have used the similar technique to get width auto-grow too.
This works well with the default sans-serif font. But if I use a Google font, then the scrollWidth being reported onload is not based on the custom font, it's being reported using the default font and therefore wrong. It seems like the onload method gets called before custom fonts have been applied.
I have implemented a simple test case for this demo. Note that the JSFiddle seems to only show the issue first time sometimes - it seems to cache the font afterwards and no longer shows the issue. But if I use chrome and open the developer tools and disable cache in the Network tab, then it continues to show the issue.
https://jsfiddle.net/fpr0dns6/1/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
#font-face {
font-family: 'Poppins';
src: url(https://fonts.gstatic.com/s/poppins/v15/pxiEyp8kv8JHgFVrJJfecnFHGPc.woff2);
}
textarea {
font-family: "Poppins", sans-serif;
font-size: 25px;
padding: 0;
margin: 0;
background: aquamarine;
border: none;
resize: none;
overflow: hidden;
white-space: nowrap;
}
</style>
</head>
<body>
<script>
onload = function () {
document.body.innerHTML = `<textarea id="mytext" rows="1" cols="1" placeholder="Something Long String" onfocus="auto_grow(this)" onclick="auto_grow(this)" oninput="auto_grow(this)"></textarea>`
auto_grow(document.getElementById('mytext'))
}
function auto_grow(element) {
console.log(getComputedStyle(element).fontFamily)
element.style.width = "auto";
element.style.width = (element.scrollWidth) + "px";
element.style.height = "auto";
console.log(element.scrollHeight + ", " + element.scrollWidth)
element.style.height = (element.scrollHeight) + "px";
}
</script>
</body>
</html>
Here's the result:
For troubleshooting purposes, I have added onfocus and onclick listeners to the textarea. So if the user focuses on the textarea after load, it corrects itself.
I am not sure how to solve this. Is there a way for onload to wait for custom font to apply?
Another thing I noticed in Firefox with an even worse behavior. Even after click/focus, the textarea doesn't show the correct width:
I am aware of the setTimeout way to delay it for 300ms before calling my function but this seems like a hack which will fail sometimes.
EDIT:
I found a workaround using document.fonts.onloadingdone to call auto_grow(document.getElementById('mytext')) which works in Chrome. But it doesn't work on Safari or Firefox. Apparently it's an experimental feature:
https://caniuse.com/?search=onloadingdone
Any better solutions?
EDIT 2: I found this which I am testing now and will update this question with my findings:
https://github.com/typekit/webfontloader
EDIT 3: I just tested with webfontloader library above and it does seem to work well in Safari and Chrome. Firefox for some reason continues to report the wrong scrollWidth but I don't think that's related to this fonts issue.

JavaScript: setAttribute works but .style doesn't

NOTE: I have no problem using setAttribute in my project. This is merely a question as to why something is not working.
I have made a basic .js, .html and .css file. Here is the code for each:
//Load Document
window.onload = () => {
//get Body Height and Width
let body = document.body;
let html = document.documentElement;
let bH = Math.max(body.offsetHeight, body.scrollHeight, body.clientHeight, html.offsetHeight, html.scrollHeight, html.clientHeight);
let bW = Math.max(body.offsetWidth, body.scrollWidth, body.clientWidth, html.offsetWidth, html.scrollWidth, html.clientWidth);
console.log(`Body Height: ${bH}px`);
//get document elements
const menu = document.getElementById("getMenu");
const menuMarginTB = (bH - menu.offsetHeight) / 2;
//menu.setAttribute("style", "margin:"+menuMarginTB+"px auto;");
menu.style.margin = `${menuMarginTB}px auto;`;
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
height: 100%;
}
.container {
position: absolute;
top: 0;
left: 0;
background-color: black;
height: 100%;
width: 100%;
}
.menu {
height: 400px;
width: 600px;
border: 1px solid white;
}
<!DOCTYPE html>
<html>
<head>
<title>Random Website</title>
<link href="./main.css" type="text/css" rel="stylesheet" />
<script defer src="./main.js" type="text/javascript"></script>
</head>
<body>
<div id="getContainer" class="container">
<div id="getMenu" class="menu">
</div>
</div>
</body>
</html>
I have commented out setAttribute because it works, but when I try to use .style.margin it doesn't work. There are no errors that pop up in Console (I'm using Google Chrome). I don't know why it's not working. Here is what I tried:
I tried using document.onload and document.addEventListener("DOMContentLoaded", ...); but these failed. I am currently using window.onload, and it doesn't work either.
I tried removing defer from the script tag; however, defer is only suppose to run this script after every HTML element was parsed (to my knowledge), I don't know how removing or keeping it in will have an affect on .style.margin, if any.
I tried changing the id name from "menu" to "getMenu". I assumed that having the same name for both id and class was not only bad practice, but affecting the way .style.margin worked. However, it didn't really seem to help when I chaned it.
I tried concatenating the string instead of interpolating it. I assumed that maybe template interpolation didn't work in the was I was using it, but string concatenation didn't seem to help.
[NOTE: I may have tried more things, but I can't remember.]
I think this problem extends to all .style properties because upon looking at the Dev Tool for Google Chrome, all of the properties are blank, even though they are assigned in the .css file.
Proof 1
I already assigned height to 400px but here it shows up as "". The same thing happens after I run the last line of the .js file is run using a breakpoint. When I hover over menu.style.margin, it, too, results in "". Proof 2
The only thing I can think of is that maybe something didn't load in causing this to happen, but even it it didn't load in, I don't know how to fix it. Any help on the matter is much appreciate.
This is actually a very simple error :)
element.style = value only works if value is a valid value for this css property. In the code you posted it is not: you may not include the semicolon in the string.

window.alert() is not available in packaged apps

I am making a Chrome application. I keep getting the error in console:
window.alert() is not available in packaged apps. extensions::platformApp:17
Line 17 of that file is just a line of code to log the error to the console. Is there a way to alert the user of an event in a chrome app (like this image?)Is it because I have adblock installed in Firefox (I don't use Chrome)? I thought alert() was pretty basic. Thanks for any help.
The alert function has been disabled in Chrome packaged apps, because it halts the execution of Javascript and thus provides a poor user experience. For development purposes you should use console.log and for user facing interactions you should use a HTML based dialog.
You can make your own synchronous alert/prompt with a function that waits for a jQuery promise before completion and then when the user clicks the "OK" button resolves the promise. Then when ever you replace your "alert" with "myCustomAlert" you have to declare the function it's called in as an "async function" and then "await" before the call.
This may sound complicated but if you play with it in JSFiddle it's quite simple.
I found this useful if you are porting over an app where you can't break the function up into different sections very easily. This does requires the jQuery library.
Here is my example https://jsfiddle.net/littlej247/g4k2h56c/5/
//*****HTML*********
<button type="button" id="alertMe">Alert ME!</button>
<div id="backgroudDiv"> <!-- This is optional but I like to grey out the background -->
<div id="alertDiv">
<b><span id="alertTitle"></span></b>
<br />
<span id="alertText"></span>
<hr>
<button type="button" id="alertDone">OK</button>
</div>
</div>
//Remember JS can't run in HTML files on chrome apps so functions are called by DOM
document.getElementById("alertMe").onclick = async function () {
console.log("Starting function \n Processing a bunch of stuff, calculating variable(s)....");
await myCustomAlert("Alert Title Here","Message");
console.log("Continue processing stuff with the variable(s) after the alert is clicked.... \n function finished");
};
//*****JavaScript*********
//Alerts can not be used in chrome apps, myCustomAlert function is a replacement.
var alertPromise
function myCustomAlert(messageTitle,messageBody){
console.log("myCustomAlert has been called");
alertPromise = $.Deferred();
document.getElementById("alertTitle").textContent=messageTitle;
document.getElementById("alertText").textContent=messageBody;
document.getElementById('backgroudDiv').style.display='block';
return $.when(alertPromise).done();
}
document.getElementById("alertDone").onclick = function () {
console.log("Finally, User clicked Done, \n now we can get back to work..");
document.getElementById('backgroudDiv').style.display='none';
alertPromise.resolve();
return(false);
};
//*****Styles*********
#alertDiv {
width: 400px;
padding: 15px;
margin: 100px auto auto auto;
z-index: 10000;
background-color: lightblue;
border-style: solid;
border-color: black;
}
#backgroudDiv {
display: none;
position: fixed;
z-index: 9000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.4);
}

javascript not working locally but works in jsfiddle [duplicate]

This question already has answers here:
Why does jQuery or a DOM method such as getElementById not find the element?
(6 answers)
Closed 9 years ago.
I have very simple code
html
<div id="ball" class="ball"></div>
css
#ball{
width: 20px;
height: 20px;
border-radius: 100%;
background: #0f0;
position: absolute;
bottom: 150px;
left: 350px;
}
javascript
<script type="text/javascript">
document.getElementById('ball').style.backgroundColor="red";
</script>
I tried this code in jsfiddle and it works but why this is not working locally? I tried changing to , but I've no idea why this is not working. It's showing the error:
TypeError: document.getElementById(...) is null
As mentioned in the comment, your initialization order is messed up. You can use window.onload to fix it:
<script type="text/javascript">
window.onload = function() {
document.getElementById('ball').style.backgroundColor="red";
};
</script>
Note also that you can only have one onload trigger function. Here are some ideas on how to support multiple trigger functions. The easiest, is of course, to just use JQuery's ready function.
Another way is to put the <script> tag into the body after the DOM element(s) that it depends on. This is often encouraged for performance reasons, but it tends to make things less readable.
The reason as to why <script> tags in the head are executed in sequence (instead of delaying execution until after the DOM has loaded) is: Performance. For example, you sometimes might want to start asynchronous requests before the document has finished loading. If you are not concerned about performance, it's safer to execute them "onload".
Ultimately, there are a lot of considerations to be made when placing your <script> tag. If you want to learn more, this might be a good starting point.
About the second part of your question: The reason as to why it works in JSFiddle is that, by default, it executes scripts onload. You can reproduce the bug on JSfiddle by choosing "no wrap - in head": http://jsfiddle.net/aDuwg/.

Get AngularJS to play nice with other javascript

I have started using AngularJS with some code that was already written and need to get it to play nice together.
So I have
<html ng-app="MainPage">
<head>
...Some JS includes
</head>
<body>
<div id="divDropDownMenu" class="DropDownMenu">
....AngularJS stuff in here
</div>
</body>
I'm using this code to append a button at the end of "divDropDownMenu" and when the button is clicked slide the up and down to reveal the menu items.
var divPanel = $("<div class='slide-panel'>");
var divContent = $("<div class='content'>");
(function ($, $scope) {
$.fn.slideBox = function(params){
var content = $(this).html();
var defaults = {
width: "100%",
height: "500px",
position: "top" // Possible values : "top", "bottom"
}
// extending the function
if(params) $.extend(defaults, params);
$(divContent).html(content);
$(divPanel).addClass(defaults.position);
$(divPanel).css("width", defaults.width);
// centering the slide panel
$(divPanel).css("left", (100 - parseInt(defaults.width))/2 + "%");
// if position is top we're adding
if(defaults.position == "top")
$(divPanel).append($(divContent));
// adding buttons
$(divPanel).append("<div class='slide-button'>Open Menu</div>");
$(divPanel).append("<div style='display: none' id='close-button' class='slide-button'>Close Menu</div>");
if(defaults.position == "bottom")
$(divPanel).append($(divContent));
//$(this).replaceWith($(divPanel));
// Buttons action
$(".slide-button").click(function(){
if($(this).attr("id") == "close-button")
$(divContent).animate({height: "0px"}, 1000);
else
$(divContent).animate({height: defaults.height}, 1000);
$(".slide-button").toggle();
});
};
})(jQuery);
function SlidePanelExpandCollapse(ExpandCollapse)
{
if (ExpandCollapse == "Expand") {
$(divContent).animate({ height: defaults.height }, 1000);
}
else {
$(divContent).animate({ height: "0px" }, 1000);
}
$(".slide-button").toggle();
}
The problem is that any angular inside the targeted div does not fire when using the above code.
this is the CSS that goes with the above JS menu slider
/* #override
http://samuelgarneau.com/slidebox.css
http://samuelgarneau.com/lab/validator/slidebox.css
http://samuelgarneau.com/lab/slidebox.css
http://samuelgarneau.com/lab/slidebox/style/slidebox.css
*/
body {
margin: 0;
padding: 75px 0 0;
}
.slide-panel {
z-index: 9999;
width: 5px;
position:absolute;
}
.bottom {
bottom: 0;
}
.right {
}
.left {
position: absolute;
left: 0;
}
.top {
top: 0;
}
.content {
margin-left: auto;
margin-right: auto;
z-index: 10;
overflow: hidden;
text-align: left;
background-color: #343434;
height: 0;
width: 100%;
color: #fff;
}
.slide-button {
background: none repeat scroll 0 0 gray;
margin-left: auto;
margin-right: auto;
position:relative;
width: 150px;
z-index: 20;
cursor: pointer;
height: 30px;
padding-top: 10px;
text-align: center;
}
.slide-button:hover {
color: #ffffff;
}
I don't see any actual use of AngularJS in your example at all. The only thing which reminded me of AngularJS was the mention of $scope in your function definition. Now while AngularJS appears to work magically in some areas this still won't do anything.
Also it might appear tempting to keep existing code and just add some AngularJS parts. However seeing your example I would recommend that you rewrite this functionality using the means of AngularJS. Showing and Hiding stuff can be easily done with ng:hide or ng:show. The animation stuff you do can be done with ng:animate. And if you need to dynamically show data, put it into a model (in your scope) and use that with functions like ng:repeat.
It appears that what you are doing in your example would boil down to only a couple of lines using the AngularJS functionality, so the result would be better and easier to read and maintain. Do yourself a favor and get familiar with what AngularJS can provide you with and stop bothering with direct DOM manipulation. It is a bad pattern. DOM manipulation is pesky, complicated and prone to break. It is also usually an unmaintable mess. And the best: If you are using AngularJS anyway, it is complete superflous because everything can be achieved much easier by having a model triggering conditional logic in your HTML.
Some impressive fact from the talk of the AngularJS creator at Google I/O 2013: He used AngularJS in its beginning to rewrite a internal project of Google. The result was that 14000 lines of code were reduced to 1500.
I recognize that this does not really count as an answer to your problem but I really believe that you would be better off stopping this approach right here and now and instead of wasting time to get this working rewrite it in AngularJS. This seriously should not take longer than half an hour even if you have to read up all the details still.
Edit/Addendum: The talk I mentioned is "Google I/O 2013 - Design Decisions in AngularJS" at http://www.youtube.com/watch?v=HCR7i5F5L8c I highly recommend watching this video even to people who have already experience with AngularJS. It gives good insight to the AngularJS way and its points come handy when trying to convince someone else in trying AngularJS :)
Edit/Addendum2: As Sprottenwels helpfully mentioned above, there is another question here on stackoverflow which gives a much more thorough explanation of what I boiled down above, so please give it a read: "Thinking in AngularJS" if I have a jQuery background?
Edit/Addendum3: As again Sprottenwels helpfully mentioned: The videos at http://www.egghead.io/lessons are a great resource. I personally found them sometime a little hard to understand (you might need to stop the video and read up in the documentation of AngularJS which thankfully is nowadays much better than it used to be).
Oh and on a personal note: While I did web development since about 1997, I was never a friend of doing application like stuff in JavaScript because all those frameworks are so complicated with lots of boilerplate and you are doing stuff which feels like a waste of time. AngularJS really did wonders to my motivation doing such stuff because it finally is totally logical and its magically like inner workings totally freed me from doing stuff I hated like synchronisation of data and view.
I am totally thankful to the AngularJS people and I absolutely believe that this is the only real future of doing web application programming. Right now AngularJS can be a problem performance-wise because of doing "bad" stuff like dirty-checking but this is going to change with new browser features like Object.observe. So I really think that choosing to use AngularJS to its full extent is a wise and future-safe move.

Categories