This user is yet to take control of their account and provide a biography. If you are the author of this article, please contact us via support AT actionscript DOT org. Flash MX 2004 introduced a very useful new class: Stylesheet. With this class we can use CSS stylesheets to format text in our Flash movies. This is a massive step forward for Flash's typographic abilities. However, you don't need Flash MX 2004 and Actionscript 2.0 to use CSS. With a little bit of actionscript 1.0, you can use CSS in Flash MX too. In this article I want to discuss how to write a Stylesheet class in Actionscript 1.0. With this class, you can use CSS stylesheets in Flash MX. This technique can also be extended so you can use CSS stylesheets to style not just text but also lines, fills, colors, or anything else you might want to style. We'll consider how to style some of these other entities at the end of the article. With respect to styling non-textual Flash entities with stylesheets, the techniques covered here are just as useful in Actionscript 2.0 for extending CSS capabilities beyond mere TextFields as they are in Actionscript 1.0.
Writing a CSS stylesheet is actually very simple. Flash provides us with a TextFormat object with which we can format text. The properties supported by the TextFormat object more or less correspond to many basic CSS properties. Applying CSS styles to a Flash TextField then entails simply that we map CSS properties to their corresponding TextFormat properties and then apply those TextFormat properties to a TextField.
To accomplish this, we will write a class which performs a number of tasks: (1) load a CSS stylesheet; (2) parse that stylesheet, convert the styles into objects, and assign the CSS properties and their values to those objects; (3) make those properties available for use by Flash objects such as TextFields. Throughout we will stick to Actionscript 1.0 code, but it is a trivial matter to accomplish the same thing with Actionscript 2.0.
To begin, let's compose a very basic CSS stylesheet from which we can work. Open a blank document in your favorite text editor (or code editor) and save it as 'myStyles.css'. Enter the following code:
[as]style1 {
font-family: Arial;
font-size: 16;
}
style2 {
font-family: Georgia;
font-size: 10;
}
[/as]
We'll use this simple stylesheet as our example stylesheet. Here we only have two styles, each with two properties: font-family and font-size. Later on we'll add a few more styles and properties as needed.
Now we can turn to the class. Before we start writing the class itself, we need to include a few custom String methods which strip a string of any extra spaces (whitespace) before and after the string. So to begin, open up a new blank document in your favorite text editor (or code editor such as Dreamweaver), save it as 'Stylesheet.as', and copy the following code:
[as]// ------------------------------------------------------------- //
// Some String Methods for stripping white space //
// ------------------------------------------------------------- //
String.prototype.trimL = function () {
for (var i = 0; i < this.length; i++) {
if (this.charCodeAt (i) > 32) {
return this.substr (i, this.length);
} // end if (this.charCodeAt (i) > 32)
} // end looping through characters
return this;
} // end String.trimL ()
String.prototype.trimR = function () {
for (var i = this.length; i > 0; i--) {
if (this.charCodeAt (i) > 32) {
return this.substring (0, i + 1);
} // end if (this.charCodeAt (i) > 32)
} // end looping through characters backwards
return this;
} // end String.trimR ()
String.prototype.trimWhite = function() {
this = this.trimL ();
return this.trimR ();
} // end String.trimWhiteThoth ()
// End String Methods //
// ------------------------------------------------------------- //
[/as]
You don't need to worry too much about how these methods work. Basically, they remove all characters on the left and right side of a string which have an ASCII code less than 32, since all ASCII codes less than 32 are white spaces. This is very useful because if you want to strip the extra spaces before and after " test ", you can just invoke the String.trimWhite () method, and it will return "test", stripped of those two extra spaces on either side. We'll use these methods later on in our class, so for now you needn't worry about them, just be sure they're pasted into the top of your document.
Now we are ready to start writing our class. The first step is to set up the basic class. For obvious reasons, let's call our class Stylesheet . Here we'll make it a global class, just to keep things simpler, but in practice one should always use namespaces to avoid confusing two classes by the same name. In any case, the first step is to write the constructor. The contructor will take one argument: the name of the stylesheet to load. We'll call this parameter stylesheet . Give yourself a few lines of whitespace after the above String methods and add the following constructor code:
[as]_global.Stylesheet = function (stylesheet) {
} // end constructor
[/as]
Next, we need to create a method which will get the class ready to go by performing any activities which must happen right away (such as setting variables, setting some default values, and so forth). This process of getting the class ready to go is called 'initialization', and by convention we name the method which initializes a class init () :
[as]_global.Stylesheet = function (stylesheet) {
} // end constructor
Stylesheet.prototype.init = function (stylesheet) {
// initialize the class here
} // end init () method
[/as]
Of course, when the constructor is invoked, we want it to invoke the init () method:
[as]_global.Stylesheet = function (stylesheet) {
this.init (); // invoke the init () method
} // end constructor
Stylesheet.prototype.init = function (stylesheet) {
// initialize the class here
} // end init () method
[/as]
For reasons we won't discuss here, it is more advantageous to invoke the init () method with Function.apply () rather than as we have done it here. Using Function.apply () is convenient because it passes the constructor's parameters on to the init () method. If you aren't familiar with Function.apply () , don't worry too much about it, because invoking the init () method with Function.apply () inside an Actionscript 1.0 constructor looks exactly the same every time:
[as]this.init.apply (this, arguments);
[/as]
Thus, to invoke the init () method with Function.apply () , we simply need to paste that line into our constructor function:
[as]_global.Stylesheet = function (stylesheet) {
this.init.apply (this, arguments);
} // end constructor
Stylesheet.prototype.init = function (stylesheet) {
// initialize the class here
} // end init () method
[/as]
Now our constructor is set up: it invokes an init () method (with the alternate Function.apply () syntax), and the argument passed to the constructor is passed onto the init () method. Now we can write any code for initializing the class inside the init () method. We'll discuss initializing the class in a moment. First we need to establish some inheritance.
Since we need to load a stylesheet and parse it, and since a stylesheet is nothing but a text file with some CSS code in it, we can make our Stylesheet class inherit from the LoadVars class. This way our class can inherit LoadVars methods for loading and parsing a text file. To make our Stylesheet class a subclass of LoadVars, set the Stylesheet.prototype.__proto__ property to LoadVars.prototype (you may also use Stylesheet.prototype = new LoadVars () ; I do it this way so as not to invoke the superclass's constructor):
[as]_global.Stylesheet = function (stylesheet) {
this.init.apply (this, arguments);
} // end constructor
Stylesheet.prototype.__proto__ = LoadVars.prototype;
Stylesheet.prototype.init = function (stylesheet) {
// initialize the class here
} // end init () method
[/as]
Now our class inherits from the LoadVars object. All the LoadVars methods are now available to our class. Three very useful LoadVars methods are load () , onData () , and onLoad () , all of which we will discuss as we proceed.
Now that we've set up the class constructor, inheritance, and set the constructor to invoke the init () method we are ready to initialize the class. Thus, we now turn to the init () method.
I mentioned earlier that our class will load a stylesheet, parse it, and convert each style in that stylesheet into an object. We will thus need a place to store those style objects. For this we will create an object called stylesDictionary . All of our styles will be stored as objects inside the stylesDictionary . When we need the styles later, we can 'look them up' from the stylesDictionary object. The first initialization step then is to create the stylesDictionary object as a variable of our class:
[as]Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
} // end init () method
[/as]
Now any method in our class has access to this.stylesDictionary .
The next step is to load the stylesheet. Presumably, the user has passed the name of a stylesheet as a parameter to the constructor. For example, the user might construct the class with 'myStyles.css' as a parameter:
[as]var myStylesheet = new Stylesheet ("myStyles.css");
[/as]
Our constructor receives this value as the stylesheet variable, and it passes that variable onto the init () method. So within our init () method, we can use the stylesheet variable to access the name of the stylesheet passed to the constructor by the user.
Since we might use this variable later on in the class, it's a good idea to store it as a class variable. Thus, the next step in our init () method is to store the value of stylesheet as this.stylesheet so that other methods can access the value of this.stylesheet later on:
[as]Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// save 'stylesheet' as 'this.stylesheet'
this.stylesheet = stylesheet;
} // end init () method
[/as]
At this point the class is initialized as much as we need for now, so we are ready to load the stylesheet. Since our Stylesheet class inherits from the LoadVars object, we don't need to define a load () method. The LoadVars load () method is already available. This is convenient because we don't need to write the load () method ourself. It is already done for us. Thus, we simply need to call this.load (this.stylesheet) in our init () method, and our class will load the specified stylesheet:
[as]Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// save 'stylesheet' as 'this.stylesheet'
this.stylesheet = stylesheet;
// load the stylesheet
this.load (this.stylesheet);
} // end init () method
[/as]
Before moving on, we should do one more thing to our init () method. As it stands, the user has to provide the name of a stylesheet to the constructor when they instantiate the class. There may be situations when this is not very desirable. Thus, we should make it optional. To do that, we simply need to check whether stylesheet is defined. If it is defined, then we can store stylesheet and this.stylesheet and load the stylesheet. If it is not defined, we should make our class do nothing. If the user does not provide the name of a stylesheet to the constructor, then they will have to invoke the load () method themselves. We can modify our init () method to look like this:
[as]Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// if 'stylesheet' has been defined,
// then save it as 'this.stylesheet'
// and then load it. If it hasn't been
// defined, do nothing and the user can
// invoke the load (stylesheet) method themselves.
if (stylesheet != undefined) {
this.stylesheet = stylesheet;
this.load (stylesheet);
} // end if (stylesheet != undefined)
} // end init () method
[/as]
This way the user doesn't have to provide to the constructor the name of a stylesheet. If they do, the class will automatically load the stylesheet, but if they do not, they are free to invoke the Stylesheet.load (stylesheet) method themselves when they see fit (there is good reason to let the user invoke or even bypass the load () method altogether; the user might even build styles purely as actionscript objects and never load a stylesheet at all, but more on that in section 1.4).
Thus far, our Stylesheet class looks like this:
[as]_global.Stylesheet = function (stylesheet) {
this.init.apply (this, arguments);
} // end constructor
Stylesheet.prototype.__proto__ = LoadVars.prototype;
Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// if 'stylesheet' has been defined,
// then save it as 'this.stylesheet'
// and then load it. If it hasn't been
// defined, do nothing and the user can
// invoke the load (stylesheet) method themselves.
if (stylesheet != undefined) {
this.stylesheet = stylesheet;
this.load (stylesheet);
} // end if (stylesheet != undefined)
} // end init () method
[/as]
Before moving on to parsing the stylesheet, we need to pause a moment and figure out exactly how we will store the styles and their properties in this.stylesDictionary . This this.stylesDictionary variable serves as a dictionary because we can look up our styles and their properties since they'll all be stored in this object. Additionally, we need to write some methods which let us get and set styles in the dictionary. Obviously we need to do this before we parse the stylesheet, because when we parse the stylesheet we'll need to store in the dictionary the styles we find in the stylesheet.
Earlier I mentioned that we want to convert each style into an object and the store the style's properties and values inside that object. For example, our current example stylesheet (myStyles.css) has two styles: style1 and style2 . We want to convert each of these into an object. From style1 , then, we will create one object by the same name, and from style2 we will create another object by the same name. Each of these objects will be subsequently stored in the dictionary. So first we have the dictionary: this.stylesDictionary . Inside that, we will then have two objects: style1 ( this.stylesDictionary.style1 ) and style2 ( this.stylesDictionary.style2 ).
At this point we can write a getter and setter method for these style objects. First we'll write the setter method. This method will create an object of a given name inside this.stylesDictionary . We'll call this method setStyle (style) , where style is the name of the style object:
[as]Stylesheet.prototype.setStyle = function (style) {
this.stylesDictionary[style] = new Object ();
} // end setStyle () method
[/as]
Conversely, we need a method with which to get a style object with a given name. We'll call this method getStyle (style) , where style is the name of the style object to be retreived:
[as]Stylesheet.prototype.getStyle = function (style) {
return this.stylesDictionary[style];
} // end getStyle () method
[/as]
With these two methods we can enter or retreive a style object from the styles dictionary. The next stage is the properties and values of each style. Each style has a list of properties and values, and we want to store these properties and values in each style object. This is simple enough: we can make each CSS property a property of the style object and assign the CSS value to that object. For example, in style1 , the first property is font-family , which has a value of Arial . By assigning this property and its value to the style object we will have this:
[as]this.stylesDictionary.style1["font-family"] = "Arial";
[/as]
We can write a getter and setter method for these properties. First we'll write the setter method. This method will assign a value to a property of a specified style object. We'll call this method setProperty (style, property, value) , where style is the name of the relevant style object, property is the name of the property we wish to set, and value is the value we wish to assign to the property:
[as]Stylesheet.prototype.setProperty = function (style, property, value) {
this.stylesDictionary[style][property] = value;
} // end setProperty () method
[/as]
Our getter method will retrieve the value of a specified property. We'll call this method getProperty (style, property) , where style is the name of the style object whose property we want to retreive, and property is the name of the property whose value we need:
[as]Stylesheet.prototype.getProperty = function (style, property) {
return this.stylesDictionary[style][property];
} // end getProperty () method
[/as]
With the setProperty () and getProperty () methods, we can set and retreive properties belonging to specific style objects. Along with the setStyle () and getStyle () method, we can now easily store and retreive styles and their properties in the styles dictionary.
Now we are ready to parse the stylesheet and place the styles and their properties in the styles dictionary. The stylesheet gets loaded with the load () method inherited from the LoadVars object, whether the init () method invokes it or whether the user invokes it themselves, so the load () method handles all of the loading process for us. The next step then is to parse the stylesheet once it is fully loaded.
Here we can again draw on the LoadVars object. The LoadVars object has a method named onData () . This method is automatically invoked when the text file finishes loading. By default, the onData () method goes through the text file and finds property-value pairs in the form 'property=value', where each pair separated is separated by an ampersand. These property-value pairs are then stored as variables in the LoadVars object. When that is all said and done, the onData () method invokes the onLoad () method, telling it whether the parsing was successful or not.
This makes the programmers job very easy because the onData () method executes silently, in the background. Since the LoadVars class does all this automatically and then invokes the onLoad () method when everything is finished, the programmer simply needs to wait until the onLoad () method fires, and then she knows everything is ready to go. We want to take advantage of this. Since our Stylesheet class inherits from the LoadVars class, our class too can silently parse the loaded text file and then invoke the onLoad () method when everything is finished. The programmer can then simply wait until the onLoad () method fires to know that everything is ready to go. The only thing we don't want is the way LoadVars parses the text file: we don't want to look for property-value pairs separated by ampersands. Our text file is a CSS stylesheet, and so we need to parse it differently.
Fortunately, it is easy to override the LoadVars parsing mechanism and write our own parsing mechanism. As was said, when the text file completes loading, the onData () method is invoked, and then the text file is parsed from there. To override the default onData () method, we simply need to write our own onData () method. If an onData () method is defined in the Stylesheet class, then it will be invoked when the text file loads rather than the LoadVars onData () method. So let's add our own onData () method to our class:
[as]_global.Stylesheet = function (stylesheet) {
this.init.apply (this, arguments);
} // end constructor
Stylesheet.prototype.__proto__ = LoadVars.prototype;
Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// if 'stylesheet' has been defined,
// then save it as 'this.stylesheet'
// and then load it. If it hasn't been
// defined, do nothing and the user can
// invoke the load (stylesheet) method themselves.
if (stylesheet != undefined) {
this.stylesheet = stylesheet;
this.load (stylesheet);
} // end if (stylesheet != undefined)
} // end init () method
Stylesheet.prototype.onData = function (src) {
// the variable 'src' contains the text of the loaded document
} // end onData () method
[/as]
Now when the stylesheet fully loads, this onData () method will be invoked, and then we can parse our stylesheet how we need. Notice that the onData () method takes one argument: src (for 'source'). This variable contains the text loaded from the document. In our case, this is the plain text of our stylesheet. If you trace src , you will see the full text contained in the stylesheet.
To keep things more compartmentalized, let's do all the parsing in a method called parseStylesheet () , and we can make our onData () method invoke the parseStylesheet () method, passing the text of the loaded file as a parameter:[as]Stylesheet.prototype.onData = function (src) {
// invoke the parseStylesheet () method
this.parseStylesheet (src);
} // end onData () method
Stylesheet.prototype.parseStylesheet = function (src) {
// parse the stylesheet here
} // end parseStyles () method
[/as]
Now we are ready to step through the text of the stylesheet separate out the styles and their properties, and then store these styles and their properties in the styles dictionary. The first stage is to break up the stylesheet into its different styles. We know that each style terminates with a closing curly bracket ("}"), and so we can treat the closing curly bracket as a delimiter between styles. With the String.split (delimiter) method we can split up a string into an array according to a specified delimiter. In this case, our delimiter is "}":
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
} // end parseStyles () method
[/as]
This splits up the text into an array, placing each segment of text between closing curly braces as an item in the array named styles_arr . This will always give us one empty item at the end of the array because the split () method will treat the empty space after the last "}" as an item, albeit an undefined one. We can pop this last empty item off our array with the pop () method:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
} // end parseStyles () method
[/as]
Now we have the text from our stylesheet for each style as separate bits of text in the styles_arr array. The next step is to loop through each of these items in the array and separate the name of the style from its property-value pairs. We can set up a loop to go through each item in the array:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up the style and its properties
} // end looping through the array
} // end parseStyles () method
[/as]
Inside the loop, we can again use the split () method to split the style name and its properties into distinct array items. The stylename is marked off from the properties by the opening curly brace ("{"), and so we can use the opening curly brace as our delimiter:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
} // end looping through the array
} // end parseStyles () method
[/as]
Here we create an array named style_arr (notice that this array is in the singular -- style _arr -- while the previous array is in the plural -- styles _arr). This style_arr has two items, the first of which is everything before the opening curly brace (namely, the style name), the second of which is everything after the opening curly brace (namely, the list of properties and values). Now that we have isolated the name of the style, we simply need to trim the extra white spaces on either side with trimWhite () and we can then use our setStyle () method to save the style as an object in the styles dictionary:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
} // end looping through the array
} // end parseStyles () method
[/as]
The second item in each style_arr is the list of properties and values. What we need to do is split this list up into property-value pairs. Again, we can use the split () method to break up the list into an array. This time we will use the semicolon as the delimiter, since each property-value pair ends with a semicolon:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
} // end looping through the array
} // end parseStyles () method
[/as]
Here again we will always end up with an extra empty item in our array, because Flash treats the empty space after the last semicolon of the list as an array item. Again, then, we need to pop the last item from the array:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
} // end looping through the array
} // end parseStyles () method
[/as]
Now we have each of our property-value pairs in an array called properties_arr . All that remains is to step through each of these and separate the property off from its value and then store these in the styles dictionary. Again, we can loop through the items of each properties_arr :
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair into property and value
} // end looping through properties
} // end looping through the array
} // end parseStyles () method
[/as]
Each property-value pair is in the form 'property: value', and that means that each property value can be split by the colon separating each property from its value. So again we can use the split () method to break the property-value pair into an array:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair at the ":"
var property_arr = properties_arr[j].split (":");
} // end looping through properties
} // end looping through the array
} // end parseStyles () method
[/as]
The first item of the property_arr will be the name of the property, and the second item will be the value of the property (notice again that here the array has a singular name while the previous array had a plural name). We simply need to trim the white space from these values and we'll have our distinct property and values:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair at the ":"
var property_arr = properties_arr[j].split (":");
// the first item will be the property name,
// the second item will be the property value,
// so trim any white spaces
var property = property_arr[0].trimWhite ();
var value = property_arr[1].trimWhite ();
} // end looping through properties
} // end looping through the array
} // end parseStyles () method
[/as]
Now that we have our property and its corresponding value, we can store it in the styles dictionary with our setProperty () :
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair at the ":"
var property_arr = properties_arr[j].split (":");
// the first item will be the property name,
// the second item will be the property value,
// so trim any white spaces
var property = property_arr[0].trimWhite ();
var value = property_arr[1].trimWhite ();
// assign the property and value to the style
this.setProperty (style, property, value);
} // end looping through properties
} // end looping through the array
} // end parseStyles () method
[/as]
And with that our parsing is done. This method steps through the whole text of the stylesheet, breaking it up into smaller and smaller pieces until it has all the properties and values assigned to style objects in the styles dictionary. Now these style properties are available for use to other Flash entities.
Now that the parsing is finished, we need to invoke the onLoad () method to tell the programmer that the parsing is complete. We'll pass a value of 'true' as a parameter to say that everything went okay:
[as]Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
styles_arr.pop ();
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair at the ":"
var property_arr = properties_arr[j].split (":");
// the first item will be the property name,
// the second item will be the property value,
// so trim any white spaces
var property = property_arr[0].trimWhite ();
var value = property_arr[1].trimWhite ();
// assign the property and value to the style
this.setProperty (style, property, value);
} // end looping through properties
} // end looping through the array
// now that all the styles have been parsed into objects
// stored in this.stylesDictionary, we can invoke the
// onLoad () method with a value of 'true'.
this.onLoad (true);
} // end parseStyles () method
[/as]
Now the user can simply invoke the load (stylesheet) method (or let the init () method invoke it for them) and then just wait until the onLoad () method fires, just as they would with a LoadVars object or an XML object. And when the onLoad () method is invoked, the styles are available for use, all stored neatly as objects and properties in the styles dictionary. By using the getStyle () and getProperty () methods, the programmer can easily access any of the styles or properties. Using this Stylesheet class to load our styles is this easy:
[as]var myStylesheet = new Stylesheet ();
myStylesheet.onLoad = function (ok) {
trace ("Stylesheet loaded and ready to go!");
} // end onLoad ()
myStylesheet.load ("myStyles.css");
[/as]
We have now completed the core of our Stylesheet class. From this point you can extend it however you like. In a moment we'll look at a way to extend it to TextFields to make styling text with CSS as easy as A-B-C. Here's the class in full, with documentation:
[as]// ------------------------------------------------------------- //
// Some String Methods for stripping white space //
// ------------------------------------------------------------- //
String.prototype.trimL = function () {
for (var i = 0; i < this.length; i++) {
if (this.charCodeAt (i) > 32) {
return this.substr (i, this.length);
} // end if (this.charCodeAt (i) > 32)
} // end looping through characters
return this;
} // end String.trimL ()
String.prototype.trimR = function () {
for (var i = this.length; i > 0; i--) {
if (this.charCodeAt (i) > 32) {
return this.substring (0, i + 1);
} // end if (this.charCodeAt (i) > 32)
} // end looping through characters backwards
return this;
} // end String.trimR ()
String.prototype.trimWhite = function() {
this = this.trimL ();
return this.trimR ();
} // end String.trimWhiteThoth ()
// End String Methods //
// ------------------------------------------------------------- //
// ************************************************************* //
// ************************************************************* //
//
// Stylesheet class
//
// ************************************************************* //
//
// This class loads a CSS stylesheet, parses it, and stores
// the styles with all their corresponding property-value pairs
// as objects. The objects can then be retreived and easily
// applied to whatever you like.
//
// ************************************************************* //
// ************************************************************* //
// ------------------------------------------------------------- //
// Constructor
// ------------------------------------------------------------- //
//
// stylesheet: the name of the stylesheet to load
//
// ------------------------------------------------------------- //
_global.Stylesheet = function (stylesheet) {
this.init.apply (this, arguments);
} // end constructor
// ------------------------------------------------------------- //
// inheritance
// ------------------------------------------------------------- //
//
// This class inherits from LoadVars. It inherits LoadVars'
// methods for loading text files. This makes it so the user
// can invoke the standard load () and onLoad () methods to load
// their stylesheets. However, this class overrides the default
// LoadVars parsing mechanism because we aren't parsing the
// standard LoadVars text files. Instead, we are parsing
// CSS stylesheets. So this class inherits the loading
// methods from LoadVars, but performs its own parsing.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.__proto__ = LoadVars.prototype;
// ------------------------------------------------------------- //
// init
// ------------------------------------------------------------- //
//
// Initializes the object and loads the stylesheet.
//
// stylesheet: the name of the stylesheet to load.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.init = function (stylesheet) {
// create an object to hold all the styles
this.stylesDictionary = new Object ();
// if 'stylesheet' has been defined,
// then save it as 'this.stylesheet'
// and then load it. If it hasn't been
// defined, do nothing and the user can
// invoke the load (stylesheet) method themselves.
if (stylesheet != undefined) {
this.stylesheet = stylesheet;
this.load (stylesheet);
} // end if (stylesheet != undefined)
} // end init () method
// ------------------------------------------------------------- //
// setStyle
// ------------------------------------------------------------- //
//
// This method creates a new style object with the name 'style'.
//
// style: the name for the new style object.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.setStyle = function (style) {
this.stylesDictionary[style] = new Object ();
} // end setStyle () method
// ------------------------------------------------------------- //
// getStyle
// ------------------------------------------------------------- //
//
// This method retrieves a style object by the name 'style'
// from this.stylesDictionary.
//
// style: the name of the style object to retreive.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.getStyle = function (style) {
return this.stylesDictionary[style];
} // end getStyle () method
// ------------------------------------------------------------- //
// setProperty
// ------------------------------------------------------------- //
//
// This method sets the value of a property for a
// specified style.
//
// style: the name of the style whose property you wish to set.
// property: the name of the property to set.
// value: the value to which the property should be set.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.setProperty = function (style, property, value) {
this.stylesDictionary[style][property] = value;
} // end setProperty () method
// ------------------------------------------------------------- //
// getProperty
// ------------------------------------------------------------- //
//
// This method retreives the value of a property for a
// specified style.
//
// style: the name of the style whose property you want to retreive.
// property: the name of the property to retreive from the style.
//
// ------------------------------------------------------------- //
Stylesheet.prototype.getProperty = function (style, property) {
return this.stylesDictionary[style][property];
} // end getProperty () method
// ------------------------------------------------------------- //
// onData
// ------------------------------------------------------------- //
//
// This method overrides the LoadVars.onData method. By doing so,
// we can stop the class from parsing the text file into
// LoadVars property-value pairs. Instead, we will parse the
// text file (the CSS stylesheet) on our own.
//
// src: the data received
//
// ------------------------------------------------------------- //
Stylesheet.prototype.onData = function (src) {
// invoke the parseStylesheet () method
this.parseStylesheet (src);
} // end onData () method
// ------------------------------------------------------------- //
// parseStylesheet
// ------------------------------------------------------------- //
//
// This method goes through the text file and parses the styles,
// making each style into an object in this.stylesDictionary,
// and making each property (and its value) a property of
// that style object. When all is said and done, the
// onLoad () method is invoked with a value of 'true'.
//
// src: the text of the loaded stylesheet
//
// ------------------------------------------------------------- //
Stylesheet.prototype.parseStylesheet = function (src) {
// split the src up into an array
// by splitting at every "}"
var styles_arr = src.split ("}");
// pop the last item, since it's always empty
styles_arr.pop ();
// loop through the array, isolating the style name
// and its properties.
for (var i = 0; i < styles_arr.length; i++) {
// split up at the "{"
var style_arr = styles_arr[i].split ("{");
// the first item will be the style name,
// trim any white space on the left and right
// so we are left with just the style name
var style = style_arr[0].trimWhite ();
// set a new style by this name
this.setStyle (style);
// the second item will be the list of properties.
// split the properties up at each ";"
var properties_arr = style_arr[1].split (";");
// pop the last item, since it will always be empty
properties_arr.pop ();
// loop through each property
for (var j = 0; j < properties_arr.length; j++) {
// split each property-value pair at the ":"
var property_arr = properties_arr[j].split (":");
// the first item will be the property name,
// the second item will be the property value,
// so trim any white spaces
var property = property_arr[0].trimWhite ();
var value = property_arr[1].trimWhite ();
// assign the property and value to the style
this.setProperty (style, property, value);
} // end looping through properties
} // end looping through the array
// now that all the styles have been parsed into objects
// stored in this.stylesDictionary, we can invoke the
// onLoad () method with a value of 'true'.
this.onLoad (true);
} // end parseStyles () method
// end Stylesheet class //
// ************************************************************* //
[/as]
Before moving on, I should mention that one feature of this class is that you can build a 'stylesheet', as it were, solely with actionscript, without any external stylesheets. Since the styles and properties are simply stored as objects and properties in the class's styles dictionary, there is nothing preventing you from using the setStyle () and setProperty () methods to build your own set of style objects and properties in the styles dictionary. These styles could then be made available just as if they were loaded from a stylesheet. This might be useful if you need to add or modify some styles which weren't included in a stylesheet or which came from a different source. With the setStyle () and setProperty () methods you can modify and build the styles dictionary however you see fit. Then you can extract those styles and make them available to other Flash entities just as if all those properties were in the original stylesheet.
Now that we have a working Stylesheet class, we can look at ways to apply those styles to Flash objects. There are many different ways you can do this, it's limited only by your imagination. Here I will look at how to apply styles to two different Flash objects. First I will discuss one way to apply styles to a TextField. Second I will briefly mention ways to apply styles to non-textual entities such as strokes and fills. Applying styles to strokes and fills shows how useful this technique can be, for both Actionscript 1.0 and 2.0. Our Stylesheet class can load any property in a stylesheet and make it available for use in your Flash movie. Thus, you can style not just your text but also strokes, fills, colors, and even animation with external stylesheets.
The most obvious object to which we would want to apply these styles is the TextField. This is a simple task once we have our styles and properties stored in our styles dictionary. As I mentioned earlier, the CSS properties Flash supports corresponds more or less to its TextFormat properties. To use our styles to format a TextField, we simply need to match up our CSS properties with TextFormat properties, build a TextFormat object with those properties and values, and then apply that TextFormat to our TextField.
To make this an easy task, we'll add a method to our Stylesheet class which will build a TextFormat object from a style in the style dictionary. The programmer can then simply apply that TextFormat object to the relevant TextField. We'll name this method getTextFormat (style) , where style is the name of the style from which to build the TextFormat. For example, if style is 'style1', this method will build a TextFormat out of the properties and values defined by the style1 style object. In our myStyles.css stylesheet, style1 has two properties: font-family and font-size. The returned TextFormat will thus have two corresponding properties: font and size.
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create and return a TextFormat object from 'style'
} // end getTextFormat () method
[/as]
First we need to create an empty TextFormat which we will fill with properties and values and return:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// add properties to the TextFormat
return tf;
} // end getTextFormat () method
[/as]
Next we need to loop through all the properties in style . For each CSS property we want to find the corresponding TextField property and assign to it the property's value. First, the loop:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
// match CSS properties up to TextFormat properties
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Now we need to try and match up the properties. There are many properties available here, so it will be more efficient to use a switch statement rather than a series of if statements:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
// each case goes here
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Inside the switch statement, we need to create a series of case statements. It is these case statements which match CSS properties up to TextFormat properties. Each case statement should test for a CSS property, and if true, it should create the corresponding TextFormat property. Let's start with a simple example. The CSS property font-family corresponds to the TextFormat property font . So our first case should test for font-family , and if true, it should set the TextFormat's font property to the value of the style's property:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
case "font-family":
tf.font = this.getProperty (style, x);
break;
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Let's step through this case statement bit by bit. First of all, we are looping through each property in the style object. That property is represented as x in this loop. The switch statement then takes x and tests if there is any case which matches x . If x is 'font-family', then our code sets tf.font to the value of the property as it is stored in the styles dictionary, and we use getProperty to retreive value of x from style .
If you run test this code by loading myStyles.css, then getTextFormat ("style1") will return a TextFormat object with the font property set to 'Arial'. If you then applied that textFormat object to a TextField, the TextField would be rendered in an Arial typeface.
We can create other case statements to handle other properties. For example, we can write a 'font-size' case :
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
case "font-family":
tf.font = this.getProperty (style, x);
break;
case "font-size":
tf.size = Number (this.getProperty (style, x));
break;
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Notice that we converted the property value to a number with Number () . The reason is that all style values are strings, since the styles all came from a text file. If you need to convert a style value, you must do it here in the case statement.
Other properties require a little more work. The CSS font-weight property can have 'bold' or 'normal' as its value, but the corresponding TextFormat bold value must have 'true' or 'false' as its value. To assign the proper TextFormat value, we must check if the CSS value has a value of 'bold'. If it does, we can set the TextFormat value to 'true':
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
case "font-family":
tf.font = this.getProperty (style, x);
break;
case "font-size":
tf.size = this.getProperty (style, x);
break;
case "font-weight":
if (this.getProperty (style, x) == "bold") tf.bold = true;
break;
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Other properties might require some reformatting. In CSS, hexadecimal colors are prefixed with a hash (for example, #006699 ), but in Flash they are prefixed with a '0x' (for example, 0x006699 ). Thus, to get the right value from a stylesheet, we can just extract the last 6 characters, and then add a '0x' to the beginning:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
case "font-family":
tf.font = this.getProperty (style, x);
break;
case "font-size":
tf.size = this.getProperty (style, x);
break;
case "font-weight":
if (this.getProperty (style, x) == "bold") tf.bold = true;
break;
case "color":
tf.color = "0x" + this.getProperty (style, x).substr (1);
break;
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
Similarly, you might have CSS font-size values with a 'pt' or 'px' value (e.g. font-size: 14pt; ). Since Flash treats points and pixels the same, you can just strip the 'pt' or 'px' from the value before assigning it to the TextFormat.size property:
[as]Stylesheet.prototype.getTextFormat = function (style) {
// create a TextFormat object
var tf = new TextFormat ();
// loop through every property in the style object,
// matching the CSS property up with the
// TextFormat property
for (var x in this.getStyle (style)) {
switch (x) {
case "font-family":
var font_size = this.getProperty (style, x);
if (font_size.indexOf ("p") > -1) font_size = font_size.substring (0, font_size.indexOf ("p"));
tf.font = font_size
break;
case "font-size":
tf.size = this.getProperty (style, x);
break;
case "font-weight":
if (this.getProperty (style, x) == "bold") tf.bold = true;
break;
case "color":
tf.color = "0x" + this.getProperty (style, x).substr (1);
break;
} // end switch (x)
} // end looping through properties in the style object
return tf;
} // end getTextFormat () method
[/as]
As you can see, you can tailor these case statements to convert values from CSS to TextFormat properties however you like. There are many more properties to implement, so I won't write case statements for them all here. You can look through the list of TextFormat properties and CSS properties in MX 2004 and see which properties you can implement (you can implement almost all of them). By filling out this getTextFormat () method, you can provide robust CSS capabilities for your Flash MX movies.
Before moving on, we'll look at a brief example of how to use a stylesheet and the getTextFormat () method to style a TextField. Suppose we have a TextField named myTextField . We can use the following code to apply a style from our myStyles.css stylesheet to the TextField:
[as]var myStylesheet = new Stylesheet ();
myStylesheet.onLoad = function (ok) {
myTextField.setTextFormat (myStylesheet.getTextFormat ("style1"));
} // end onLoad ()
myStylesheet.load ("myStyles.css");
[/as]
First we create a new instance of the Stylesheet class. Then we define an onLoad () method which will be triggered when the stylesheet is completely loaded and parsed. Inside that onload () method we use setTextFormat () to apply the TextFormat to myTextField . The getTextFormat () method is used to retreive the TextFormat, which is built from the properties stored in the style1 style. Finally, we invoke the load () method. This sets the whole thing in motion, the Stylesheet class loads the stylesheet, parses the styles and stores them in the styles dictionary, then invokes the onLoad () method. The getTextFormat () method is then used to retreive a TextFormat object (built from the CSS properties of style1 ). By using this technique, you can easily use external CSS stylesheets to style TextFields in Flash MX.
This technique is not, however, limited to TextFields in Flash MX 2004. Since the Stylesheet class does not care which properties you define in your stylesheets, you can define any property you like. You might define some CSS properties not supported by Flash MX 2004 such as stroke , stroke-opacity , and fill , or you might create your own set of properties for use with styling a component. The list can be endless.
For example, you might write a method called getStrokeProperties () which operates much like our getTextFormat () method. Rather than retreive TextFormat properties, however, getStrokeProperties () might return an object with line width, line color, and line opacity values, and these values were derived from CSS stroke-width , stroke , and stroke-opacity properties in a stylesheet. You could then use those properties when you draw any shapes or run any components. You could also write a similar method called getFillProperties () which returned fill color and opacity properties derived from CSS fill and fill-opacity . By defining your stroke and fill properties in an external stylesheet and then using those values at runtime can give you stylesheet control over not just text but also graphics. For two examples which have implement CSS in Flash MX to style text and graphics in this fashion, see Thoth and DENG .
Further, you could define all sorts of other things with stylesheets. You might define _x, _y, and _rotation properties in stylesheets. You might even define animation properties such as acceleration constants or gravity. You could even define non-textual and non-graphical properties in your stylesheets and use those too. Perhaps you want to set some data properties for use in a datagrid component, or perhaps you want to set the number of visible rows and columns for a table component, or perhaps you want to define a list of movieclips in the Library to use as skins for yet another component. All of this is easily possible with a class such as our Stylesheet class, because it simply parses a stylesheet and makes those styles and properties available as objects which are easily retreivable. And all of this is applicable to Actionscript 1.0 and Actionscript 2.0.
In this article we have examined a way to bring CSS into Flash MX. Our Stylesheet class provides a simple way to load a stylesheet, parse it, and make those styles available as objects and properties. Once we have that much, we can implement methods to apply those styles to different sorts of Flash entities. We looked at how to apply CSS styles to TextFields, and we suggested ways to apply CSS to strokes, fills, and other sorts of Flash entities, both visual and non-visual. You can further build on this technique too. Perhaps you want to introduce support for CSS classes and IDs. You can easily extending this Stylesheet class or even build your own with a slightly different approach. Be creative. The potential of stylesheets in Flash is only beginning to be tapped.