It might be an old article (2008), but its still actual. Matt Gemmel explains how developers should approach problem-solving and, more important, the process of learning new skills.
Tips & Fixes
We try to understand the world of the web. Sometimes it needs some clarification, sometimes it needs a little help. Here you can find posts about tips, tricks and web-issues.
Off-DOM element dimension
It can be really frustrating; you want to know the height of an element, but it is not in the DOM yet. Since the element’s width and height depends on the styling, the measures can only be calculated on screen, in the DOM.
Size & Styling
Your best shot is adding the element to the body in a hidden state (visibility: hidden
) or off-screen (position: absolute; left: -9999px;
), get the width and height and remove it from the DOM. But sometimes the size of the element depends on the css of an ancestor element, like width: 100%;
, which is not present in the body. Or better, you can also append the element to the eventual element, but only if that one exists and sometimes it doesn’t.
Family-tree-substitute
If it doesn’t, we could replicate it. We can make a structure that will ‘mimic’ the eventual structure that the elements dimension depends on. An example:
1 2 3 |
section.column p { font-size: 20px; } |
1 2 3 |
var $p = $('').text('Lorem ipsum dolor sit amet, consectetuer adipiscing elit...'); |
The size of the paragraph depends on the ancestor elements; when it is a child of a section
with the class .column
it will have a maximum width of 300px
and, since it has a larger font-size than the default 12px
, will be higher than outside of the column.
Now, lets replicate the ancestors. I use the jQuery css-parser I wrote to create the elements.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function getDimension($el, tree) { var ancestors = tree.split(' '); // As 'base' I use a class for hiding the tree // by using visibility: hidden; and a position somewhere off screen var $chain = $().parse('div.secret'); // Create an element for each ancestor // and append them to each other. for (i in ancestors) { var $ancestor = $().parse(ancestors[i]); $chain = $ancestor.appendTo($chain); } // Append the given element to the chain // and add the complete tree to the body... $chain.append($el) .closest('.secret') .appendTo('body'); // ...so we can get the dimensions of the element var dimension = { width: $el.outerWidth(), height: $el.outerHeight() } // Now we can remove the element $('.secret').remove(); // And were done! return dimension; } |
This function will replicate the family tree needed to get the proper dimensions and can be used as simple as this:
1 2 3 4 5 |
var size = getDimensions($p, 'section.column'); console.log(size.width, size.height); |
Ok, this only works if you now the chain of ancestors, but hey, even the web can’t be perfect…
Out of bounds with box-sizing
Using box-sizing makes life easier, but it does not always work the way you think.
1 2 3 4 5 6 7 |
div { box-sizing: padding-box; max-height: 100px; padding-bottom: 150px; } |
In this case you would expect the div to have a height of 100px, because of the max-height
, but no, it will have a height of 150px. This is because of the way box-sizing works. About box-sizing on dev.w3.org:
…any padding specified on the element is laid out and drawn inside this specified width and height. The content width and height are calculated by subtracting the padding widths of the respective sides from the specified ‘width’ and ‘height’ properties. As the content width and height cannot be negative, this computation is floored at 0.
So, when the padding exceeds the height (or width) of the element, the content is floored to 0. This means there is a problem, because the padding does not fit in the element. In that case the box-sizing property is ignored and the element will overflow the max-height.
A fiddle.
innerHTML in IE
I stumbled upon an interesting issue today. Internet Explorer seems to store its references to DOM elements in another way than Chrome or Firefox. A (vanilla) javascript-object of an element is linked to the element in the DOM. In Chrome and Firefox, when that element is removed from the DOM, the HTML for it still exists in the innerHTML attribute in the js-object, but in IE the innerHTML is cleared.
The Problem
We have the next example (jsfiddle):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Create an element var el = document.createElement('p'); // Add some text var txt = document.createTextNode('some text'); el.appendChild(txt); // Append the element to the body document.body.appendChild(el); // Log the element's content console.log(el.innerHTML); // IE: 'some text' // Chrome: 'some text' // Clear the body document.body.innerHTML = ''; // Log the element's content console.log(el.innerHTML); // IE: '' // Chrome: 'some text' |
An elment is created, text is added and it is appended to the body. When we clear the body, by replacing its HTML with nothing, in IE, the html of the element is also emptied.
The Cause
Javascript in IE seems to hold reference to the ‘real’ HTML in the DOM and its values depend on it. So, when the html is not available in the DOM, its not in the object. This is more logical than you might think. The innerHTML attribute in the object is just holding a string of HTML-code, not any objects of the inner elements. When the content is cleared, so is the innerHTML, to keep the attribute’s value up to date.
Chrome and Firefox, however, keep their reference to the content. Which will leave the element intact when you remove it from the DOM, so it can be added later in the process.
The Fix
To fix this issue, the innerHTML shouldn’t be cleared. An alternative is to remove the elements from, in this example, the body by using removeChild.
16 17 18 19 20 21 22 23 24 25 26 27 |
// Clear the body while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } // Log the element's content console.log(el.innerHTML); // IE: 'some text' // Chrome: 'some text' |
This loop will remove all elements in the body, which has the same effect as clearing the innerHTML. The difference is, that the HTML is not really removed. It is still accessible in the javascript object.
jQuery: html vs empty
Maybe this issue doesn’t seem that common, but using jQuery you might run into it. jQuery uses both ways to clear the content of an element. The first is used in the html
method and the second in the empty
method. When using front-end mvc’s, like Backbone, there are situations where you want to re-render a view without re-rendering it’s sub-view. By first emptying the view’s html, the element will be left intact.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
App.View = Backbone.View.extend({ initialize: function() { // Create a sub-view // and render it this.sub_view = new App.SubView(); this.sub_view.render(); // Render the view, every time the model changes this.listenTo(this.model, 'change', this.render); }, render: function() { // Empty the html // Whithout this line the HTML of this.sub_view will also be cleared this.$el.empty(); // Parse the template with the model-data and add it to the view var template = _.template(this.template.html(), {data: this.model}); this.$el.html(template); // Add the sub-view to the view this.$el.find('.sub-view').append(this.sub_view.el); } }); |