Tuesday, October 9, 2007

Setting html element style thru Javascript

I guess that setting some style to an html element thru CSS has become something very usual : writing an xhtml compliant document and then decorate it with css thru classes, id and all the tools we can use.
In this entry, we will see a simple way to set some style thru javascript allowing dynamic styling and why not,creating some cool transitional effects from one style to an other !
For now, let's see the dynamic setting in action:

Background-color:#00FFFF
opacity:.5
font-size:15px
width:100px

cool text

The CSS way


I am not going to explain how CSS works as this will be too long but we will just see a simple class defined in an coolStyle.css file:
div#mySuperId{
    width:200px;
    height:200px;
    color:#FFFFFF;
    background-color:#000000;  
    z-index:100;
}
Alright, we are just styling a div set with an id called mySuperId.
We set different properties like the width, height, color and background-color...
Something you have certainly done hundreds of time!
As you can see properties require to specify an unit, a color, an integer or a string.
Composite words are separated by an hyphen, therefore making it easier to read.
Do you know the other way we could do this in general ?
Well, we could use the camelcase style!
This will transform our hyphened properties to this:

backgroundColor:#000000;
zIndex:100;
Basically, we write the first word in lower-case and separate each new words by putting the first letter in upper-case, which is not without reminding the humps of the camel, thus the name!
You know what?
This is with this convention that javascript can access the style properties of an html element!

The Javascript Way


So first we need to get the element we will be working on, here an html div element:
var elm=document.getElementById('mySuperId');
//now we can work on the style properties of this element:
elm.style.width='400px';
elm.style.height='100px';
elm.style.background-color='#FF00FF'; //Won't work!
elm.style.backgroundColor='#FF00FF'; // Now, it will
From there, we can decide between 2 options : people write the css properties in javascript with the camelcase style or we find a way to let them write the css way.
Well, we need to change a word that is hyphenized by doing so :
If the string has an hyphen followed by a letter, erase the hyphen and then change the following first letter into an upper-case...
This may seem complicated, but this will be almost a one-liner!
Thanks to regular expressions, we can do this:
function camelize(val) {
   return val.replace(/-(.)/g, function(m, l){return l.toUpperCase()});
}
//example
camelize('background-color'); return backgroundColor
Thru the replace function we look for an hyphen followed by any letter ('.').
We capture the letter found thanks to the parenthizes then we apply a an anonymous function to the result.
The first parameter is the entire match which, in that case will be -c.
The following parameters represent each capturing parentheses.
We just change the captured letter to upper-case and send it back!
Was easy right ?

A first draft


As I think that being inventive is one of my best attribute in this world, I will call this function... setStyle...
This is how we are going to call the function:
setStyle(elm,prop,val);
//example
setStyle(document.getElementById('mySuperClass'),'background-color','#FF00FF');
And as you can see we will have to camelize the property, so let's do it:
function setStyle(elm,prop,val) {
           prop=camelize(prop);
           elm.style[prop]=val;
           return elm;
}
This is a blueprint because unfortunately, if we have seen the css way, the javascript way, now we have to see the browser way !

The browser way


This should be the browsers way in fact!
If you have read the entries about javascript animation, we've been already setting style to the properties and we have done some cross-browser correction.
Let's see the properties that could be troublesome.

Opacity


Well, this is easy: this property just do not exist in IE!
IE has it's own property that is filter:[typeOfFilter:value];
Where others accept a value between 0 and 1, IE wants a value between 0 and 100!
So to set the opacity of an element in cross-browser way, we will create one function :
function setOpacity(elm,val) {
          if(window.attachEvent) {
               elm.style.zoom = 1;
               elm.style.filter = "alpha(opacity=" + parseFloat(val*100) + ")";
                return elm;
          }
          elm.style.opacity  = parseFloat(val); 
   return elm;
}
First we check for an IE function that is not implemented in other browsers.
If the function exist we set an other IE property, zoom. (you can check what it is here:hasLayout
Zoom is an IE style property that gives the element the layout properties, adding some new features to the element)
We set the property the IE way and then we return.
If it's not IE, we do it the standard way.

I even think that we can simplify even more by just not checking the browser:
function setOpacity(elm,val) {
          elm.style.zoom = 1;
          elm.style.filter = "alpha(opacity=" + parseFloat(val*100) + ")";
          elm.style.opacity  = parseFloat(val); 
   return elm;
}
If we set a property that is not understood, the browser will just set it and ignore it but apply the property it can handle.

Float


The float is a keyword in Javascript and cannot be used, so javascript needs to refer to an other naming convention to refer to the style float property.

Ie refers to it as styleFloat
Others refer to it as cssFloat

let's update the function:
function setStyle(elm,prop,val) {
         if(prop=='opacity') return setOpacity(elm,parseFloat(val));
  if(prop=='float')   prop = (window.attachEvent) ? 'styleFloat' : 'cssFloat';
  prop = camelize(prop);
  elm.style[prop] = val;
         return elm;
}
function setOpacity(elm,val) {
          elm.style.zoom = 1;
          elm.style.filter = "alpha(opacity=" + parseFloat(val*100) + ")";
          elm.style.opacity  = parseFloat(val); 
   return elm;
}
we first check the 2 above properties: if it is opacity, we just return the setOpacity function, if it is the float property, we set the right naming according to the browser.
We could by the way do some lazy initialization here regarding the float property:
var styleFloat=(window.attachEvent) ? 'styleFloat' : 'cssFloat';
function setStyle(elm,prop,val) {
         if(prop=='opacity') return setOpacity(elm,parseFloat(val));
  if(prop=='float')   prop = styleFloat;
  prop = camelize(prop);
  elm.style[prop] = val;
         return elm;
}
The styleFloat variable will be set just once so we won't be checking this everytime the setStyle function is called.
This is not going to speed up your app that much but it's a way to do it.

Other properties

Well, all properties accept a unit like pixel, em, pt and whatsoever but the followings:
z-index,zoom and all the properties related to color!
So we are just going to check for the property and add the value with the proper unit:
function setStyle(elm,prop,val) {
        if(prop=='opacity') return setOpacity(elm,parseFloat(val));
  if(prop=='float')   prop = (window.attachEvent) ? 'styleFloat' : 'cssFloat';
  prop = camelize(prop);
         unit=(prop=='zIndex'||prop=='zoom'||/olor/.test(prop)) ? '':'px';
  elm.style[prop] = (typeof val=='string') ? val : val+unit;
         return elm;
}
If this is a property that doesn't require a unit, we set the unit to empty else we apply a default unit that is px,for pixel.
Then we check if the value is a string ('#FFFFFF','400px'), if it is we set it as it is, if it's a number, we set the unit to 'px'.

Here is how you can call the function:
var elm=document.getElementById('mySuperId');
setStyle(elm,'background-color','#00FFFF');
setStyle(elm,'opacity',.5);
setStyle(elm,'font-size','15px');
setStyle(elm,'width',200);


Conclusion


Well, this function allows us now to set the style of an html element in a cross-browser manner but we didn't check for complex properties like margin:0px 10px 2px 3px; or border:1px solid red and this will not work for now ! you will have to write each of them: margin-top, margin-left...
Also we can only set one property at a time, which is not a very fast way of doing it but let's see this in a next entry!

2 comments:

Unknown said...

Is there any reason you're rewriting a lot of the same functions that exist in pretty much every mainstream library?

shiriru said...

[!-- This is an edited version with fewer english errors... --!]
i have read this kind of question in many tutorials...
Is there any reason you're rewriting a question that has been asked in all the mainstream tutorials?

So let's go for an anwser :

TIMTOWTDI.
(Perl get out of my body!)

And to develop a little bit more, I would like people (and my self first)to understand what's behind the magic of most of these frameworks.
I use them a lot (Jquery, Mootools, Scriptaculous) and looking at the inner functionment is a good way to learn javascript and all the possible implementations.
(none of the frameworks above implement this function the same way)
Not everyone wants to look thru 2000 lines of code or can understand how it works.
I hope these entries will help people to understand how things work and then, for those who want to use these frameworks, allow them to do so with more confidence.(I am the first one who needs to understand what's going on,anyway).

In the javascript animation series, I talk about these frameworks because they offer ways to implement new transitions, easing effect but if you don't understand how it works, you will just have to use the framework, blindly.
I hope these entries will allow people to go beyond that and become from simple users to why not, contributors to the open source community.
Obviously, I don't claim the code being listed here as being the better or perfect, it's a starter.

Thank you for commenting !