I am trying to replace some "nested" classes from some HTML with javascript/jquery. I need to get rid of the spans with class="foo" and leave behind only "stuff".
<ul>
<li>
<span class="foo">stuff
<ul>
<li><span class="foo">stuff</span>
</li>
</ul>
</span>
</li>
</ul>
This works in everything but IE8 (which I must unfortunately support):
$(".foo").each(function () {
$(this).replaceWith(this.innerHTML);
});
Before someone points out that there are other similar questions, please note that I've tried several methods outlined in those questions and I think my use case differs because of the "nesting". I've tried the solutions in topics on this site and others. Some of these completely crash IE8, others just don't work.
I am aware of trying to use .empty() before replaceWith, but this doesn't help... my problem isn't performance (yet) it is getting it to even work at all.
I've also tried this:
$(".foo").each(function () {
var HTMLshunt = this.innerHTML;
$(this).after(HTMLshunt).remove();
});
Why the "HTMLshunt" var? I was working on the premise that maybe it wasn't working in IE8 because the "after" wasn't really going "after" .foo, but inside it... because in IE8 something happens with this one: it eliminates every .foo but leaves no contents of foo behind.
This "nesting" isn't helping. I think something else cobbled together along the way would have worked if it weren't for the nesting, but it doesn't matter because there IS this nesting. If anyone can help, please respond.
You could try this, should be IE8 friendly (though I do believe jQuery 1.11.0 should also support IE8). If reflow is any worry then you could cloneNode first at the expense of some memory instead.
HTML
<ul>
<li>
<span class="foo">stuff
<ul>
<li>
<span class="foo">stuff</span>
</li>
</ul>
</span>
</li>
</ul>
Javascript
var fooClasses = document.querySelectorAll('.foo'),
fooIndex,
foo,
fragment;
for (fooIndex = fooClasses.length - 1; fooIndex >= 0; fooIndex -= 1) {
fragment = document.createDocumentFragment();
foo = fooClasses[fooIndex];
while (foo.firstChild) {
fragment.appendChild(foo.firstChild);
}
foo.parentNode.replaceChild(fragment, foo);
}
On jsFiddle
With a cloned node
var fooClasses = document.querySelectorAll('.foo'),
fooIndex,
foo,
fragment,
clone;
for (fooIndex = fooClasses.length - 1; fooIndex >= 0; fooIndex -= 1) {
fragment = document.createDocumentFragment();
foo = fooClasses[fooIndex];
clone = foo.cloneNode(true);
while (clone.firstChild) {
fragment.appendChild(clone.firstChild);
}
foo.parentNode.replaceChild(fragment, foo);
}
On jsFiddle
It seems as though you are using jQuery. In that case, you should just use the unwrap() method:
Description: Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
As an example:
$(".foo").each(function () {
$(this).children().unwrap();
});
jsFiddle demonstration
This fiddle uses jQuery 1.9.1, so it should function in IE8.
EDIT
Okay, so the problem is that jQuery.unwrap doesn't work when a node only contains text content.
In order to work with text content, you'll have to use a slightly different approach:
$(".foo").each(function() {
$(this).replaceWith(this.childNodes);
});
See this fiddle
Full disclosure: I used this answer for the technique.
Related
var topClick = function() {
child = this.parentNode.parentNode.parentNode.parentNode;
child.parentNode.removeChild(child);
var theFirstChild = document.querySelector(".m-ctt .slt");
data[child.getAttribute("data-id")].rank = 5;
insertBlogs();
};
As you see, there is a part of my code like this:
this.parentNode.parentNode.parentNode.parentNode;
Is there another way to optimize the code (without using jQuery)?
You can use a non recursive helper function, for example:
function nthParent(element, n) {
while(n-- && element)
element = element.parentNode;
return element;
}
You can use recursive helper function, for example:
function getNthParent(elem, n) {
return n === 0 ? elem : getNthParent(elem.parentNode, n - 1);
}
var child = getNthParent(someElement, 4);
An alternative approach
Your goal
According to your comments on the original question, your overall goal is this:
There is a list of blogs.Each blog has a button like "edit" and "delete". When I click such buttons I want to find it's blog element.
I believe the best approach to solve the problem you're facing (as opposed to answering the question you asked - sorry!) is to approach the problem in a different manner.
From your comments, you said you have something like this:
<ul id="blog-list">
<li class="blog-item">
<a href="blog1.com">
Boats galore!
</a>
<span class="blog-description">
This is a blog about some of the best boats from Instagram.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
<li class="blog-item">
<a href="blog2.com">
Goats galore!
</a>
<span class="blog-description">
A blog about the everyday adventures of goats in South Africa.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
<li class="blog-item">
<a class="blog-link" href="blog3.com">
Totes galore!
</a>
<span class="blog-description">
A blog about containers and bags, and the owners who love them.
</span>
<span class="blog-button delete-button">
Delete
</span>
<span class="blog-button edit-button">
Edit
</span>
</li>
</ul>
And your goal is to add click event handlers to the button elements for each blog-link item.
So let's just translate that goal from plain English into pseudo-code:
for each `blog-link` `b`
delete_button.onclick = delete_handler(`b`);
edit_button.onclick = edit_handler(`b`);
Example script
Working example at: http://jsfiddle.net/vbt6bjwy/10/
<script>
function deleteClickHandler(event, parent) {
event.stopPropagation();
parent.remove();
}
function editClickHandler(event, parent) {
event.stopPropagation();
var description = parent.getElementsByClassName("blog-description")[0];
description.innerHTML = prompt("Enter a new description:");
}
function addClickHandlers() {
var bloglistElement = document.getElementById("blog-list");
var blogitems = bloglistElement.getElementsByClassName("blog-item");
blogitems.forEach(function(blogitem){
var deleteButtons = blogitem.getElementsByClassName("delete-button");
deleteButtons.forEach(function(deleteButton){
deleteButton.onclick = function(event) {
return deleteClickHandler(event, blogitem);
}
});
var editButtons = blogitem.getElementsByClassName("edit-button");
editButtons.forEach(function(editButton){
editButton.onclick = function(event) {
return editClickHandler(event, blogitem);
}
});
});
}
HTMLCollection.prototype.forEach = Array.prototype.forEach;
addClickHandlers();
</script>
Explanation
The way you've chosen to implement a solution is valid, but I thought I'd give you a different way to look at the same problem.
In my opinion, the inner tags of the blog entity should not have to have knowledge of the structure or properties of the surrounding HTML for your edit and delete buttons to work.
Your original solution has to work backwards from each button up the chain of parents until it finds what it assumes is the correct parent element, based on a brittle method like hard-coding moving up N times in the chain of parent elements. Wouldn't it be nicer if we could use normal JavaScript element selection to be absolutely sure of what we're selecting? That way, no matter how the HTML structure might change, our JavaScript isn't going to break as long as the classes and IDs remain consistent.
This solution iterates over every blog-item in the #blog-list element:
blogitems.forEach(function(blogitem){ ... });
Within the forEach loop, we grab arrays containing .delete-button and .edit-button elements. On each of those elements, we add the appropriate event handler to the onclick property:
deleteButtons.forEach(function(deleteButton){
deleteButton.onclick = function(event) {
return deleteClickHandler(event, blogitem);
}
});
For each deleteButton element in the array, we assign an anonymous function to the event handler onclick. Creating this anonymous function allows us to create a closure.
This means each deleteButton.onclick function will individually "remember" which blogitem it belongs to.
Check out this SO question/answer about closures for more info: How do JavaScript closures work?
And we throw in the HTMLCollection.prototype.forEach... line to provide forEach functionality to all HTMLCollection objects. Functions like getElementsByClassName return an HTMLCollection object. It acts exactly like an array for the most part, but it doesn't let us use forEach by default. A note about compatibility: you can certainly use a standard for loop, since forEach isn't available in all browsers (mostly old IE browsers are to blame). I prefer the forEach idiom.
End result
The end result is a little bit longer code, since the scope of the problem is a little wider than the question you actually asked. The advantage is that this code is much more flexible and resistant to being broken by changes to the HTML structure.
var topClick = function(event){
console.log(event);
child = this.parentNode.parentNode.parentNode.parentNode;
child.parentNode.removeChild(child);
var theFirstChild = document.querySelector(".m-ctt .slt");
data[child.getAttribute("data-id")].rank = 5;
insertBlogs();
};
Surprise! I print the event through console.log.
And find a array like this inside the event:
And I find the element that I want :
function(event){
console.log(event.path[4]);
child = event.path[4];
}
and it work! How magic! Maybe event agent is another way!
Thank you all the same for giving answers!
Next time before asking questions I'll think over first! :D
I have a list of items which diferent event handlers on it.
...
<li>
<div class="item" data-id="1234">
<h3>Item</h3>
<div class="description">...</div>
<ul class="lists">
<li data-list-id="1">Add to list A<li>
<li data-list-id="2">Add to list B<li>
<li data-list-id="3">Add to list C<li>
</ul>
<button class="delete">delete</button>
</div>
</li>
...
Every li under .list has a click event registered which looks like this:
function addToList(event){
var id = event.target.getAttribute('data-id');
var listId = event.target.parentNode.parentNode.getAttribute('data-id');
// XHR stuff
}
There is no problem with this code but the parentNode.parentNode seems really fragile.
For the button would be only one parentNode and for deeper nested elements parentNode^n
I guess this is a common problem and there are more robust solutions?
With jQuery i would use $(target).parentNode('.item')
Whats the best way to do this without jQuery?
As part of my projects, I always write my own toolbox. Sure, I could use jQuery, but I'll stick to my precision toolkit over a sledgehammer, thanks!
With that in mind, have a look at this:
function findParent(source,filter,root) {
root = root || document.documentElement;
while(source != root) {
if( filter(source)) return source;
source = source.parentNode;
}
}
In your case, you can now call:
var listId = findParent(
event.target,
function(e) {return e.attributes && e.attributes['data-id'];}
).getAttribute("data-id");
// note that you should probably break that down, checking if an element
// is found before getting its attribute value... or let the error
// kill your script. Either works.
Now, here it does look a bit more messy than your simple .parentNode.parentNode, but it's much more robust because it doesn't rely on depths being a certain number, which I believe is what you were aiming for.
I have a tweet stream where new tweets are added at the top and the older ones pushed down. You can click on the entire tweet and a panel slides down to reveal, "reply", "retweet", "favorite" etc. The panel is added to each new tweet added in the stream.
The code below works. Shouldn't this be better written so that only one call is being made? Or, as a new tweet is added. would I just have to add to the code with div#tc4, ul#tb4 etc?
$(document).ready(function () {
$("div#tc1").click(function () {
$("ul#tb1").slideToggle("fast");
});
$("div#tc2").click(function () {
$('ul#tb2').slideToggle("fast");
});
$("div#tc3").click(function () {
$('ul#tb3').slideToggle("fast");
});
});
Added Markup:
<div id="tc1" class="tweetcontainer">
<div class="avatarcontainer">
<div class="avatar"></div>
</div>
<div class="content">
<div class="tweetheader">
<div class="name">
<h1>John Drake</h1>
</div>
<div class="tweethandle">
<h2>#Drakejon</h2>
</div>
<div class="tweettime">10m</div>
</div>
<div>
<p>Exceptional Buys Ranger To Give Monitoring Shot In The Arm To Its 'DevOps' Platform http://tcrn.ch/11m3BrO by #sohear </p>
</div>
</div>
</div>
<!-------------Tool Bar -------------------------------->
<ul id="tb1" class="toolbar">
<li><a class="reply" href="#"><span>reply</span></a></li>
<li><a class="retweet" href="#"><span>retweet</span></a></li>
<li><a class="favorite" href="#"><span>favorite</span></a></li>
<li><a class="track" href="#"><span>track</span></a></li>
<li><a class="details" href="#"><span>details</span></a></li>
</ul>
I highly recommend separating your javascript from your detailed page function. The best way to do this is to put the retweeting panel inside the tweet container, then you don't even need to give it an id at all or encode in the javascript information about your html structure and ids. You can then just do:
$('.tweetcontainer').on('click', function(event) {
if ($(event.target).is(':descendantof(.toolbar)')) {
//ignore all clicks within the toolbar itself
return;
}
$(this).find('.toolbar').slideToggle();
});
It's that easy! See it in action in a jsFiddle.
Now you can add as many tweet containers as you want to your page--and your javascript doesn't have to change one bit. Other solutions that require knowledge of specific ids linking to specific ids are suboptimal.
Note the descendantof pseudo-selector is custom (see the fiddle to find out how it works). Also, since you didn't provide any css, I had to choose some--it was quick so don't expect much. (Aww heck I just saw you updated your question to provide a jsFiddle with css giving a far prettier result--but I won't change mine now.) I did have to add a class to the actual tweet itself, but there is probably a better way to style it.
And if you want a click on the displayed toolbar itself (outside of a link) to allow collapsing the toolbar, change the code above to :descendantof(a).
If you don't want to change your page layout, another way to it is to encode the information about the linkage between html parts in the html itself using a data attribute. Change your tweetcontainer div to add a data attribute with a jQuery style selector in it that will properly locate the target:
<div class="tweetcontainer" data-target="#tb1">
You don't really have to remove the id if you use it elsewhere, but I wanted you to see that you don't need it any more. Then on document.ready:
$('.tweetcontainer').click(function () {
$($(this).data('target')).slideToggle('fast');
});
Here is another jsFiddle demonstrating this alternate technique (though it less elegant, in my opinion).
Last, I would like to mention that it seems possible you have a little bit of "div-itis". (We have all been there.) The toolbar anchor elements have unnecessary spans inside of them. The tweet name h1 element is inside a div, but could just be an h1 with class="name" instead.
In general, if there is only a single item inside a div and you can change your stylesheet to eliminate the div, then the div isn't needed. There are an awful lot of nested divs in your html, and I encourage you to remove as many of them as you can. Apply style to the other block elements you use and at least some, if not many, won't be needed.
I'd suggest (though currently untested):
$('div[id^="tc"]').click(function(){
var num = parseInt(this.id.replace(/\D+/g,''),10);
$('#tb' + num).slideToggle("fast");
});
Though given that you don't need the num to be a number (it'd be fine as a string), you could safely omit the parseInt().
Yes, you can write this code much more compactly like this:
$(document).ready(function() {
for (var i = 1; i < 3; i++) {
$("div#tc" + i).click(function() { $("ul#tb" + i).slideToggle("fast"); } );
}
});
I am working on an interface with four main parts:
When a category link is hovered, the projects not in this category are darkened (this seems to be working ok)
When a category link is clicked, the projects not in this category are hidden (seems also to be ok)
The browser window size is detected and a style sheet is chosen to fit. I.e. for older screen or mobile. Go ahead and resize the browser window.
When the browser window is narrow there is an additional script to scroll down to the "main" div.
<div id="container">
<div id="inner-container">
<div id="tag-selector">
<ul>
<li class="all">ALL PROJECTS</li>
<li class="graphic-design">graphic design</li>
<li class="logo-design">logo design</li>
<li class="photography">photography</li>
<li class="web-development">web development</li>
<li class="web-design">web design</li>
</ul>
</div>
<div id="main" role="main">
<p class="items">There are x items in this category</p>
<p class="selected">No category selected</p>
<p class="clicked">No category clicked</p>
<section class="graphic-design">
<p>graphic-design</p>
</section>
<section class="logo-design graphic-design">
<p>logo-design</p><p> graphic-design</p>
</section>
<section class="logo-design graphic-design"><p>etc</p>
</section>
</div>
<footer> </footer>
then here's the javascript. Sorry if it's a bit long. It should be easy enough to read I hope.
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
$(document).ready(function(){
var xwidth =$(window).width();//get width of user window
all_projects_showing_text="All projects showing. There are " + n + " projects, in " + t + " categories.";
adjustStyle(xwidth);
$("p.items").text(all_projects_showing_text + " Width=" + xwidth);
$(".all").addClass("selected");
tag="all"
});
</script>
<script>
var n = $("section").length;//number of section boxes on page
var t = $("#tag-selector li").length;//categories
t--;
$("#tag-selector li").click(function() {//clicking section filter li
$("#tag-selector li").removeClass("selected");//removes all filtered class
$(this).addClass("selected");//then adds it to the chosen link (li), showing it as current
tag=$(this).attr("class");//var tag is the class name of the chosen link, i.e. category
var split = tag.split(' '); // this splits the class string and puts each item in an array
tag = split[0];//this chooses the first item of the array, hence not including the hilite class
var numItems = $('.'+tag).length
var numItems=numItems-1;//correct for real number
if (tag!="all"){//if the all link is not picked
$("section").hide();// hide all the boxes
$("#main ."+tag).fadeIn();//show all the boxes with the tag class
if(tag=="graphic-design"){
tag="Graphic design"
}
else if(tag=="logo-design"){
tag="Logo design"
}
else if(tag=="photography"){
tag="Photography"
}
else if(tag=="web-development"){
tag="Web development"
}
else if(tag=="web-design"){
tag="Web design"
}
$("p.items").text(numItems+" " +tag+ " projects");
$("p.selected").text(tag +" selected.");
$("p.clicked").text(tag +" selected.");
}
else{
$("section").fadeIn();//else show all the boxes
$("p.items").text(all_projects_showing_text);// all_projects_showing_text at onReady
}
});
</script>
<script>
$("#tag-selector li").hover(function () {
hovered_link=$(this).attr("class");//get the class of the category being hovered
var split = hovered_link.split(' '); // this returns an array
hovered_link = split[0];//remove any other classes apart from the first i.e. remove hilite
if (tag=="all"){// if All are shown
if(hovered_link!="all"){
$("section").not("."+hovered_link).addClass("section_darkened");//darken section which does not correspond with hovered category link
$("section").not(".section_darkened").addClass("outerglow");//add glow to not darkened sections
}
}
else{
}
if (tag==hovered_link){// if the projects are already filtered by this category, say so on hover
$("p.selected").text(tag +" already selected.");
}
else{
var numItems = $('.'+hovered_link).length
var numItems=numItems-1;//correct for real number
$("p.selected").text("Click to see only "+hovered_link+ " projects. (" +numItems+ " projects)" );
}
$(this).addClass("hilite");//hilite on hover over link
}, function () {
$(this).removeClass("hilite");
$("p.selected").text("...");
$("section").removeClass("section_darkened");//darken categories not in the selected category
$("section").removeClass("outerglow");//give the selected category items a glow
});
</script>
<script>
$(function() {
$(window).resize(function() {
adjustStyle($(this).width());
});
});
function adjustStyle(width) {
width = parseInt(width);
if (width < 600) {
$("#tag-selector li").click(function() {// SCroll function for handhelds
$('html,body').animate({
scrollTop: $("#main").offset().top},
'slow');
});
$("#size-stylesheet").attr("href", "css/nav-style-narrow.css");//style sheet for handhelds
} else if ((width >= 440)&&(width < 1040)){
$("#size-stylesheet").attr("href", "css/nav-style-med.css");
} else {
$("#size-stylesheet").attr("href", "css/nav-style-wide.css");
}
}
</script>
If you've gotten this far and had a look, thanks!
So my questions are;
Am I missing break; in my loops anywhere? Not too sure how to use break.
When my CSS file is chosen, there is a flash of the first style before it changes. Is there a way to avoid this?
When the browser is at the narrowest style sheet, and I click on my link, I have problems scrolling back up again afterwards. help?! :-)
Any glaring mistakes or omissions that would make this easier?
I start to feel like I have a lot of script on my one page. Maybe I should put it in a separate file. Would that help?
Is it ok to post multiple questions like this or should they be individual?
Thanks in advance for anyone who has a look.
Answer regarding break:
break stops the execution of the current loop or switch. You should use it in loops in order to stop a loop before the end of the current iteration, or on a condition that is not checked in the loop statement itself. You should use it at the end of a case in a switch block in order not to execute subsequent cases.
In your specific code there don't seem to be any loops or switches, so no place for any break anywhere.
OK, I'll bite, though I'm not going to try to offer a comprehensive list:
There are various ways to deal with the CSS flash issue. The easiest is to hide everything until you've loaded the correct stylesheet, then show everything once it's loaded.
Yes, in general it's always a good idea to put Javascript in separate files - it just makes managing your code easier, especially if you want to reuse any of it on multiple pages.
You're missing a lot of var statements, e.g. for all_projects_showing_text. This is syntactically correct, but a Bad Idea in Javascript, because it makes those variables global, attaching them to the window object. If you need a global variable, you should still declare it with var.
I'm not seeing any place where break would be appropriate, or even possible. You generally use break within a for or while loop to stop looping; I'm not seeing any loops like that in your code. JQuery code often uses .each() instead, or just loops implicitly through all the items in the selection; I rarely see break in code using jQuery, though there are of course times when it might be appropriate.
It's often a good idea to either cache or chain jQuery selectors. For example,
$("section").removeClass("section_darkened");
$("section").removeClass("outerglow");
could be
var $section = $section;
$section.removeClass("section_darkened");
$section.removeClass("outerglow");
or
$("section")
.removeClass("section_darkened")
.removeClass("outerglow");
or even (in this case, since .removeClass() can remove several classes at once):
$("section")
.removeClass("section_darkened outerglow");
Your long else if section starting if(tag=="graphic-design"){ could be better structured as a map + lookup:
var tagTitles = {
"graphic-design":"Graphic design",
"logo-design":"Logo design",
// etc
};
tag = tagTitles[tag];
break is not a function. It's a statement, so you don't add parenthesis.
The page loads before the css is choosen. If you want to target different screen sizes, you could take a look at css3 media queries. Adding the styles at the beginning of the page should work without flickering. You could still use js to choose styles as backup method.
I think you are adding a new click handler on every resize event! That's a lot of animations running on a click, try to set the handler only once.
Missing var, as already mentioned by nrabinowitz. Indentation could be better / more consistent.
JS in Separate files is better.
cacheable by clients -> page gets faster after first visit
reusable by different pages
easier to manage (version control)
Single (well researched) questions are generally better.
I want to get an element in the DOM and then lookup what rules in my CSS file(s) are contributing to it's appearance. Similar to what firebug or webkits inspector does. Is there a way to do this in JavaScript?
Update:
I should provide a constraint and a specific example - I am only interested in achieving this in webkit based browsers so cross-browser difficulties are not so much an issue. What I am specifically trying to achieve is this. Let's say I have a stylesheet as follows:
div {
background: #f0f0f0;
}
.example {
padding: 10px;
}
And then let's say some html code like this:
<div id="test" class="example">
<hgroup>
<h1>Test</h1>
<h2>A sample file to play with.</h2>
</hgroup>
<ul class="sample">
<li id="item_1">Item 1</li>
<li id="item_2">Item 2</li>
</ul>
</div>
So then in javascript I want to be able to have a function that can find the selectors from the CSS that are styling the object:
get_selectors_for(document.getElementById('test'))
// should return:
// ['div','.example']
How complicated is it to reverse query selectors knowing we only need to worry about webkit as opposed to all browsers?
This is what you want. WebKit only. I found out about getMatchedCSSRules by looking at the chromium web inspector source.
function getAppliedSelectors(node) {
var selectors = [];
var rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '');
var i = rules.length;
while (i--) {
selectors.push(rules[i].selectorText);
}
return selectors;
}
A cross-browser solution I've had good success with is http://www.brothercake.com/site/resources/scripts/cssutilities/
It is very powerful and accurate, derives a lot more information than the webkit-only function mentioned above, and it works on all styles (including those that are cross-site and that aren't active or have been overridden).
Is it possible? Absolutely...is it simple (especially cross-browser with IE in the mix), not so much. If you're really interested in doing this, check out the Firebug Lite CSS source here. At least the methods are decently commented showing what information each is fetching.
....or if you're wanting simply to inspect in a browser that doesn't have an inspector, just use Firebug Lite.
There is a reliable way of getting it, mentioned in this blog post:
function getStyle(oElm, strCssRule){
var strValue = "";
if(document.defaultView && document.defaultView.getComputedStyle){
strValue = document.defaultView
.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
}
else if(oElm.currentStyle){
strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
strValue = oElm.currentStyle[strCssRule];
}
return strValue;
}
If you are using Firefox and Firebug, you can try running this code in StackOverflow, to see what you get:
document.defaultView.getComputedStyle(document.getElementById("custom-header"),"")
And with IE and Firebug Lite, you could do:
document.getElementById("custom-header").currentStyle
Well, it's an old subject.
Good for Webkit for offering a solution.
As suggested, I've looked into firebug-lite and... surprise!!
For every node it loops over every rule in every stylesheet checking if the selector matches our nodes or not.