I am building the diagram component in JavaScript. It has two layers rendered separately: foreground and background.
To determine the required size of the background:
render the foreground
measure the height of the result
render the foreground and the
background together
In code it looks like this:
var foreground = renderForegroundIntoString();
parentDiv.innerHTML = foreground;
var height = parentDiv.children[0].clientHeight;
var background = renderBackgroundIntoString(height);
parentDiv.innerHTML = foreground + background;
Using IE7, this is a piece of cake. However, Firefox2 is not really willing to render the parentDiv.innerHTML right away, therefore I cannot read out the foreground height.
When does Firefox execute the rendering and how can I delay my background generation till foreground rendering is completed, or is there any alternative way to determine the height of my foreground elements?
[Appended after testing Dan's answer (thanx Dan)]
Within the body of the callback method (called back by setTimeout(...)) I can see, the rendering of the innerHTML is still not complete.
You should never, ever rely on something you just inserted into the DOM being rendered by the next line of code. All browsers will group these changes together to some degree, and it can be tricky to work out when and why.
The best way to deal with it is to execute the second part in response to some kind of event. Though it doesn't look like there's a good one you can use in that situation, so failing that, you can trigger the second part with:
setTimeout(renderBackground, 0)
That will ensure the current thread is completed before the second part of the code is executed.
I don't think you want parentDiv.children[0] (children is not a valid property in FF3 anyway), instead you want parentDiv.childNodes[0], but note that this includes text nodes that may have no height. You could try looping waiting for parentDiv's descendants to be rendered like so:
function getRenderedHeight(parentDiv) {
if (parentDiv.childNodes) {
var i = 0;
while (parentDiv.childNodes[i].nodeType == 3) { i++; }
//Now parentDiv.childNodes[i] is your first non-text child
return parentDiv.childNodes[i].clientHeight;
//your other code here ...
} else {
setTimeout("isRendered("+parentDiv+")",200);
}
}
and then invoke by: getRenderedHeight(parentDiv) after setting the innerHTML.
Hope that gives some ideas, anyway.
Related
Last days I've faced a situation that almost got me insane. May be an stupidness of mine, but I really think this question can't be explored without thinking at OS's process escalonation and stuff.
The question is the following: Imagine I have a little red square with a click event:
document.querySelector("#button").onclick = setOpacity1;
The function setOpacity1 is responsible for making another square - this one black and bigger - appear. First, it sets the display of this square to block
(it was none before); Secondly, it sets it's opacity to 1 (it was 0 before):
function setOpacity(){
document.querySelector("#square").style.display = "block";
document.querySelector("#square").style.opacity = 1;
}
But here we have a particularity: the change of opacity of the black square has a transition associated with it. So, the expected behaviour is that the square appears slowly, passing of "100% white" to "100% black".
But it does not occurs. However, this behaviour is achieved with this code:
document.querySelector("#button").onclick = setOpacity1;
function setOpacity2(){
document.querySelector("#square").style.opacity = 1;
}
function setOpacity1(){
document.querySelector("#square").style.display = "block";
setTimeout(setOpacity2,20);
}
So, it seems to me that, in the first case, the change of opacity starts before the change of display...then, when the display finally gets set, the opacity's transition gets "broked". In the second case, I order the browser to wait a bit before it sets the opacity, what gives him time to set the display before.
Does this argument makes any sense? If not, what is happening here?
Fiddle of the code not working: https://jsfiddle.net/zjoeyhdz/
Fiddle of the code working: https://jsfiddle.net/sj8vzuhx/
I guess it works as follows.
When the statement document.querySelector("#square").style.... = .... is executed, the browser does not immediately change the view, it just remembers the new style. When JavaScript execution has finished, it applies all style changes, but not necessarily in the order of the execution of the statements. In some browsers, it will do the display first and then the opacity (like in IE11, which works fine with both your fiddles) and in other browsers, it will happen the other way around (Chrome, Firefox).
Yes, your workaround will solve the problem for all browsers.
I have to use a website (ticket system) with a select element, that contains currently 9994 options. This is not changeable and has to be accepted as is.
When I work on a ticket on that website, I have to select a specific entry from that select. I have a total set of around 30 entries I have to choose from. I don't care for the other entries.
The required 30 entries can be seperated into 3 patterns for a RegEx filter.
So I decided to use Greasemonkey+JQuery to clean that select element up, so I can easily and quickly find the entries I am looking for.
The filtering is working fine, but it takes time (of course it does...), so I want to show a little "please wait" div as overlay, while the filter is running to give some kind of user feedback.
On page load I create the overlay:
$("body").append('<div id="pleaseWaitOverlay" style="position: fixed; top: 0; left: 0; bottom: 0; right: 0; background-color: rgb(255,255,255);">HALLO WELT</div>');
$("#pleaseWaitOverlay").hide();
//This is the select element with the "few" entries
fixedInReleaseElement = $('select[name=resolvedReleasePath]');
//Adding buttons to filter for one of the patterns are also added on page load
If I press on one of the filter buttons, the following function will be called:
function filterFixedInReleaseList(filterFor) {
$("#pleaseWaitOverlay").show();
//$("#pleaseWaitOverlay").show().delay(500).show(); - or as hack without success...
var pattern;
//Based on "filterFor" parameter, the required pattern will be used.
// [MORE CODE]
fixedInReleaseElement.find("option").each(function() {
var currentOption = $(this);
if (pattern === "") {
currentOption.show();
}
else {
if (pattern.test(currentOption.text())) {
currentOption.show();
}
else {
currentOption.hide();
}
}
});
//$("#pleaseWaitOverlay").hide();
}
But somehow, the filter will take place and THEN the overlay will be shown.
Please note:
Currently, the .hide() lines are commented out, as the popup would not be shown (or rather seen) at all with those lines executed.
The .show().delay(500).show() was a try to kind of hack it, but it changed absolutly nothing.
I also tried fixedInReleaseElement.find("option").delay(1000).each() without success. I appears that delay does not work at all?
So, what is the problem here? Why is the overlay shown after the filter has been executed?
The complete Greasemonkey script can be found here:
http://pastebin.com/auafMSR1
A browser tab only has one thread, that is shared between JavaScript and UI updates. Thus, if your JavaScript is running, the UI is not getting updated.
So, this:
function doSomethingLongWithOverlayWrongly() {
$x.show();
doSomethingLong();
$x.hide();
}
will set appropriate attributes of $x to be hidden, then do something long, then set the attributes back; and when doSomethingLongWithOverlayWrongly (and all the computation that is in its future) finally exits and relinquishes control of the executing thread, the browser will take note that some attributes were changed, and repaint if necessary (but it's not, since the element was set to invisible, and is now still set to invisible).
Do this instead:
function doSomethingLongWithOverlayCorrectly() {
$x.show();
setTimeout(function() {
doSomethingLong();
$x.hide();
}, 0);
}
This will set $x to be hidden, then schedule a timeout, then exit. The browser takes a look, sees a repaint is in order, and shows your overlay. Then the timeouted function gets run, does something long, and sets $x to be hidden again. When it exits, the browser takes another look, sees that a repaint is required, and hides your overlay.
Warning: not duplicate with existing questions, read through
I know I can have an event listen on changes on an contenteditable element.
What I would like is to be able to know what the changes are.
For example:
inserted "This is a sentence." at position X.
deleted from position X to Y.
formatted from X to Y with <strong>
Is that possible? (other than by doing a diff I mean)
The reason for this is to make a WYSIWYG editor of other languages than HTML, for example Markdown.
So I'd like to apply the changes to the Markdown source (instead of having to go from HTML to Markdown).
You may be able to do something with MutationObservers (falling back to DOM Mutation events in older browsers, although IE <= 8 supports neither) but I suspect it will still be hard work to achieve what you want.
Here's a simple example using MutationObservers:
http://jsfiddle.net/timdown/4n2Gz/
Sorry, but there is no way to find out what the changes are without doing a diff between the original content and the modified one when changes occur.
Are you looking for this
var strong=document.createElement("strong");
var range=window.getSelection().toString().getRangeAt(0);
range.surroundContents(strong);
this was for third part
You just need to select what you want to surround using real User interaction.
If you wanna do it dynamically
var range=document.createRange();
range.setStart(parentNode[textNode],index to start[X])
range.setEnd(parentNode[textNode],index to end[Y])
range.surroundContents(strong);
For 2nd Part
range.deleteContents()
1st part can be done by using simple iteration
var textnode=// node of the Element you are working with
textnode.splitText(offset)
offset- position about which text node splitting takes place[here==X]
Two child Nodes have been created of the parent editable Element
Now use simple insertBefore() on parent editable Element Node.
hope you will find it useful
The API you're looking for does not exist, as DOM nodes do not store their previous states.
The data / events you're wishing to get back are not native implementations in any browser Ive come across, and I struggle to think of a datatype that would be able to generically handle all those cases. perhaps something like this:
function getChanges() {
/* do stuff here to analyse changes */
var change = {
changeType : 'contentAdded',
changeStart : 50, /* beginning character */
changeContent : 'This is a sentence'
}
return change;
}
Since you're trying to get custom events / data, you're probably going to need a custom module or micro-library. Either way, to look at the changes of something, you need somehow be aware of what has changed, which can only be done by comparing what it was to what it is now.
I'm trying to precisely fit a string into a certain width. This means the font-size changes depending on the string. I now use the following function, using jQuery:
function fontResize ( )
{
for (var i = $("#date").css("font-size").slice(0, -2); $("#date").width() < $("#clock").width(); i++)
$("#date").css("font-size", i.toString() + "px");
}
The idea is that I set the font-size in the CSS to the lowest possible value. I initialize the loop with this value and increment the font-size with 1 until the string is wider than the width of the containing element. The string in this case is "date" and the containing element is "clock".
Although this works, I think the main disadvantage is that the string has to be first drawn before the width can be determined. This means I cannot call this function before loading the body.
If anyone knows a better way to do this please let me know! Thanks.
To make sure you're getting all the styles and such applied to it that will be applied when the page is fully rendered, yes, you do want to put the element in the DOM (and in the right place in the DOM) before you do your measurement stuff. But you don't have to wait until everything else is there (unless you think it will affect the styling of the string you're measuring). You can put your code in a script block immediately after the element in question — no waiting for ready. The date element will be there and accessible, according to Google's Closure library engineers. E.g., if date is a span:
<body>
...
<span id="date">December 13th</span>
<script>fontResize();</script>
...
...
</body>
It's unfortunate to intermix code and markup like that (particularly if you have separate teams doing the markup and the code), but if your requirement is to size the text absolutely as soon as possible, that's how.
The above also assumes your fontResize function is already loaded (e.g., in a script block higher on the page). This is another reason it's unfortunate to mix markup and code like this, because normally of course you want to put your scripts at the bottom, just before closing the body tag.
However: It may be worth experimenting to see if you can do your resizing in the script you put just before the closing body tag instead. There'd be a small window of opportunity for the page not to look right, but quite small and of course pages tend to look a little funny as they load anyway. Then you wouldn't have the intermixing problem and wouldn't have to load your scripts early. You may find that the just-before-the-closing-body-tag is soon enough.
How about using the canvas, and the measureText method?
$(function () {
var canvas = $("canvas")[0];
var context = canvas.getContext("2d");
var text = "hello world";
context.font = "40pt Calibri";
var metrics = context.measureText(text);
alert(metrics.width);
});
I have a (legacy) JS function, that shows or hides child nodes of argument element. It is used in mouseover and mouseout event handlers to show-hide img tags.
The function looks like this:
function displayElem(elem, value, handlerRoot){
try{
var display = 'inline';
if(!value)
display = 'none';
if(handlerRoot)
elem.style.display = display;
var childs = elem.childNodes;
for (i = 0; i < childs.length; i++){
if(childs[i].nodeType == Node.ELEMENT_NODE){
childs[i].style.display = display;
alert("Node "+childs[i].tagName+" style set to " +childs[i].style.display);
}
}
}catch(e){
alert('displayElem: ' + e);
}
}
Here, value and handlerRoot are boolean flags.
This function works perfectly, if target html page has no doctype. Adding any doctype (strict or transitional) breaks this. Alert shows style has been set to the right value, but child elements are not displayed.
Would be good, if this function could work with any DOCTYPE.
Image (a child node of elem) is initialized like this (perhaps something is wrong here?):
var img = new Image();
img.style.cssText =
'background: transparent url("chrome://{appname}/content/dbutton.png") right top no-repeat;' +
'position: relative;' +
'height:18px;'+
'width:18px;'+
'display:none;';
JavaScript doesn't really work over plain HTML but on the DOM tree generated by the browser. Thus the DOCTYPE does not have a direct influence on JavaScript but on the way the browser handles invalid HTML and CSS.
I think the first step is to clean-up the HTML and make sure it's valid, esp. that tags are used in allowed places and properly nested. That will guarantee that the generated node tree is the same no matter the rendering mode.
You can also use your favourite browser tool (such as Firebug) the inspect the real tree and make sure nodes are placed where you think they are.
Update:
I wonder if when dealing with a document in standards mode (the document has a DOCTYPE), Firefox is inserting an implied element that it doesn't insert in backward-compat mode (no DOCTYPE), and so the image isn't an immediate child of elem but instead a child of this implied element that's then a child of elem; so you won't see the image in elem.childNodes. Walking through the code in a debugger is the best way to tell, but failing that, alert the tagName of each of the child nodes you're iterating through in the loop.
For example, with this markup:
<table id='theTable'>
<tr><td>Hi there</td></tr>
</table>
...Firefox will insert a tbody element, so the DOM looks like this:
<table id='theTable'>
<tbody>
<tr><td>Hi there</td></tr>
</tbody>
</table>
...but it won't be that specific example unless the DOCTYPE is a red herring, because I just tested and Firefox does that even in backward-compat mode. But perhaps you were testing two slightly different documents? Or perhaps it does it with some elements only in standards mode.
Original:
Not immediately seeing the problem, but I do see two issues:
i isn't declared in the function, and so you're falling prey to the Horror of Implicit Globals. Since your alert is showing the correct value, I can't see why that would be the problem.
url(..) in CSS doesn't use quotes. Yes they can, optionally.
Thanks to Álvaro G. Vicario. Though he didn't gave an exact answer, the direction was right.
I've checked the page with w3c validator, and found that my Image objects were missing src attribute. Thus, adding img.src = "chrome://{appname}/content/dbutton.png"; helped.
Still, I'm not sure, why the original code author used background style instead of src... Perhaps, that would remain a mystery. :)