Monday, September 14, 2009

Modifying CSS in Javascript

Modify CSS class/selector value of property/key by using Javascript without changing for each element style.

In some cases, we need to change a CSS property for a CSS class to make all elements uses the class will take into effect for the property changed. If you are able to author and modify the CSS and the web page. You can use two or more classes that refer to the same styling but little different change to its property. So whenever you want to change the styling, just swap class name for any element you want.

Let say you have a table with all rows have class named .RowStyle. So the class may look like this:

.RowStyle
{
  color: #000000;
  ...
  text-align: center;
}

Now you just want to change only a color property for all rows. So you need to define new class that have nearly same properties like this:

.RowStyleRed
{
  color: #FF0000;
  ...
  text-align: center;
}

Then you need to find all rows and replace all class RowStyle with RowStyleRed because the reason you don't need to change back to the old class and still assume both class refer to the same styling but just changing a little bit.

In this case, it is good if there is an simple way to change a property is specific class so we don't need to find back all element that use the class. That the thing I want to discover.

My Situation:

In my situation, I have no power to change it. For example, like this blog, I want to change the height of Reaction features (I rename it as Feedback as shown bellow). It was defined in the ".reactions-iframe". The CSS classes is auto-generated just like the HTML and JavaScript.

This is the original properties of the class:
.reactions-iframe
{
  -moz-background-clip:border;
  -moz-background-inline-policy:continuous;
  -moz-background-origin:padding;
  background:transparent none repeat scroll 0 0;
  border:0 none;
  height:2.3em;
  width:100%;
}
I try to add the class again at the bottom of the HTML to overwrite the height like this:
.reactions-iframe
{
  height:10em;
}

However it is not overwrite the height only but the entire class. Thus, all other properties are removed. So I try to find the solution by modifying CSS class properties in JavaScript. Note that changing the value in CSS class property is not same like changing style for a DOM element which is just for that element only. Change in CSS class will take effect to all elements that using the class.

The Solution:

I found that there is no easy way to do. I don't want to use jQuery in my case since I just want to solve this CSS problem only. This post, Altering CSS Class Attributes with JavaScript give me an idea on how
possible and cross-browser compatible to modify CSS property by using JavaScript. No simple command like changing style of an element like document.getElementById('mydiv').style.height = '5.5em'. Everything is under document.styleSheets and there is a different version depend on browser. document.styleSheets[i].cssRules is used by FireFox while document.styleSheets[i].rules is used by IE where i is the style sheet document index number need to enumerate.

To make it easier we can specify document.styleSheets[i][cssRules] where cssRules is a string variable that can be "rules" or "cssRules" depend on browser. It will return an array of selector or class defined in the style sheet document. So document.styleSheets[i][cssRules][i] will return an object for single selector or class. Not so fast, a lot things to know. document.styleSheets[i][cssRules][i].selectorText is the selector or class name and document.styleSheets[i][cssRules][i].style['height'] will give you access to the height property just for example.

Here is the function:
function SetCSS(theClass, property, value) {
            var cssRules;
            var added = false;

            // Find the compatibility only one time.
            if (document.styleSheets.length > 0) {
                if (document.styleSheets[0]['rules']) {
                    cssRules = 'rules'; // IE compatible
                } else if (document.styleSheets[0]['cssRules']) {
                    cssRules = 'cssRules'; // FireFox compatible
                } else return; // unknown compatibility
            } else return; // no style sheet found

            for (var i = 0; i < document.styleSheets.length; i++) {
                for (var j = 0; j < document.styleSheets[i][cssRules].length; j++) {
                    if (document.styleSheets[i][cssRules][j].selectorText == theClass) {
                        if (document.styleSheets[i][cssRules][j].style[property]) {
                            document.styleSheets[i][cssRules][j].style[property] = value;
                            added = true;
                            break;
                        }

                    }
                }
                if (added) {
                    break; // break the outer for loop
                }
                else {
                    if (document.styleSheets[i].insertRule) {
                        document.styleSheets[i].insertRule(theClass + ' { ' + property + ': ' + value + '; }', document.styleSheets[i][cssRules].length);
                    } else if (document.styleSheets[i].addRule) {
                        document.styleSheets[i].addRule(theClass, property + ': ' + value + ';');
                    }
                }
            }
        }
This function need an enhancement since setting CSS each time will enumerate in the style sheet documents. Using like a hash table for lookup will make the enumeration faster since class name or selector is unique for a page and we can map the name to the style sheet document index. I use this function successfully in my local. However, using this function in other than localhost like in the blogger will throw an error Security error code 1000 NS_ERROR_DOM_SECURITY_ERR in FireFox. IE7 has no problem. I still inspecting why this happened. FireFox dont allow this way. I fustraited, the hole day I wasting for this endless solution. Quickly I change to the other idea.

The Real Solution:

We can't modify the defined class instead of we need to change for each object. We can reduce performance by modifying the class since it will update for all object that use the class but the allowable ways is just change the style the objects. Since I lost my day now I need the fast solution by just change it for each object.

Here is the code to change height of 'iframe' that use class 'reactions-iframe':
function SetReactionsHeight(Height) {
  var iframes = document.getElementsByTagName('iframe');
  for (var i in iframes) {
    if (iframes[i].className == 'reactions-iframe') {
      iframes[i].style.height = Height;
    }
  }
}
SetReactionsHeight('5.5em');
I dont like this solution since it will enumerate all element (if we dont know the tag name) and will cause performance hit. To enumerate for each element available in the html, use document.getElementsByTagName('*') like in this post  document.getAllElements() - or equivalent.
However it is an easy solution for simple page. If you plan using JQuery $('.reactions-iframe').css('height','5.5em') is the answer as stated How can I change the css class rules using jQuery. It just use 45 letters in single line of code to do the same like the real solution.

Friday, September 11, 2009

Javascript minify consern

Minify Javascript/CSS/HTML/XML file to make it smaller in size and fast download.

In the most recent days, I was dealing with big JavaScript code and using DotNetNuke that also use quite large JS file. I realize, it will eat my bandwidth but I just let it happened until I use jQuery. Downloading and using jquery.min.js realizing to make all my js file minified.

Before this I found JS file that minified but that time I am not realizing it will save file size about half of it. The term minify, minifying or minified is for JS with removed whitespace and comment.

I am interested with this kind of ways and here is my reading so far:

Combine, minify and compress JavaScript files to load ASP.NET pages faster
JSMin
Automatically minify and combine JavaScript in Visual Studio
Optimize WebResource.axd and ScriptResource.axd

There is so many tools out there but I want to implement it either pre-minified or automatically at runtime and combined with exception and disable when debugging in my ASP.NET application based on my options. Based on my readings, I can't find the best tool or code that implement both in one solution to make easier for developer like me.

I just have an idea to create new SourceForge project that will grab all the idea and put it into one solution.

Moreover I wanted to have this features also:
- There is a standalone GUI/command line tools to minify and/or compress included.
- The tools can be integrated to popular SDK like VisualStudio to minify and/or compress during compilation.
- Option to combine all JS files into one or not.
- Option to include or exclude one or a few JS files in the combination (Sometimes we need to seperate the JS files rather than combine it all and put in the header and make page loading later even JS script downloading become faster).
- Option to include or exclude one or a few JS files to be minify and/or compress (because maybe we want to debug one of the JS file).
- Option to disable when debugging.
- Put the minifier and compressor engine in seperate library for easier upgrade either this tool or the engine.
- * the best thing * One simple step just to add http handler or module to minify and/or compress on the fly each JS files and no need to modify anything.
- Support a few more MIME type like JS, CSS, html and xml.