A few months ago, I was working on a project that needed a CMS backend to create content that will be displayed in Flex. We used a RichTextEditor component to create appropriate HTML so that it would display like we wanted to on the frontend.
The problem with this approach is that we needed our application to be re-themeable between different clients that would be using this product, which meant that we also needed a way to style the html content without having to redo the html itself.
As much as I looked for it, I couldn’t find anyone else doing something similar to this, so I created my own. Essentially, I just extended TextArea which already handles htmlText and added my own html tag styling to it. I was surprised to see that you can actually set styles to the html within the TextArea, but you can only do it with Actionscript.
I looked at all the possible html tags that TextArea supports, and created CSS styles for it. I wanted to stick with the proper CSS convention of separating the words using a dash and used my own kind of standard ‘html-<tag>-<property>’, property being one of ‘color’, ‘weight’, ‘decoration’ or ‘style’. One exception to this rule is for the anchor tag, which can also do pseudo-classes (a:link, a:hover and a:active). If you need to reference those tags, you just need to use ‘alink’, ‘ahover’ and ‘aactive’. If someone has a better suggestion, I’m all ears.
package com.lab49.components
{
import flash.text.StyleSheet;
import mx.collections.ArrayCollection;
import mx.controls.TextArea;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;
// Set the style metadata - If adding another style/tag, add it here
// Style names are in the of 'html-<tag>-<property>' exception is for pseudo-classes
// which is attached directly to the tag name.
[Style(name="html-a-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-a-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-a-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-a-color", type="uint", format="Color", inherit="no")]
[Style(name="html-ahover-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-ahover-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-ahover-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-ahover-color", type="uint", format="Color", inherit="no")]
[Style(name="html-aactive-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-aactive-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-aactive-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-aactive-color", type="uint", format="Color", inherit="no")]
[Style(name="html-alink-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-alink-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-alink-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-alink-color", type="uint", format="Color", inherit="no")]
[Style(name="html-b-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-b-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-b-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-b-color", type="uint", format="Color", inherit="no")]
[Style(name="html-font-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-font-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-font-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-font-color", type="uint", format="Color", inherit="no")]
[Style(name="html-i-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-i-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-i-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-i-color", type="uint", format="Color", inherit="no")]
[Style(name="html-li-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-li-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-li-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-li-color", type="uint", format="Color", inherit="no")]
[Style(name="html-p-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-p-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-p-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-p-color", type="uint", format="Color", inherit="no")]
[Style(name="html-span-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-span-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-span-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-span-color", type="uint", format="Color", inherit="no")]
[Style(name="html-textformat-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-textformat-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-textformat-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-textformat-color", type="uint", format="Color", inherit="no")]
[Style(name="html-u-style", type="String", enumeration="italic, normal", inherit="no")]
[Style(name="html-u-weight", type="String", enumeration="bold, normal", inherit="no")]
[Style(name="html-u-decoration", type="String", enumeration="none, underline", inherit="no")]
[Style(name="html-u-color", type="uint", format="Color", inherit="no")]
/**
* RichTextArea Class, extends TextArea. Displays html text, but styles them uniformily for
* easy theming.
**/
public class RichTextArea extends TextArea
{
private var _stypePropChanged:Boolean = false; // Style property change flag. Set to true when one of the html styles is set
private static var _classConstructed:Boolean = classConstruct(); // Calls a static initializer function before anything else
// All possible styles. If adding a new one, must be added in this ArrayCollection with the proper metadata above.
private const _styles:ArrayCollection = new ArrayCollection([ "html-a-style",
"html-a-weight",
"html-a-decoration",
"html-a-color",
"html-ahover-style",
"html-ahover-weight",
"html-ahover-decoration",
"html-ahover-color",
"html-alink-style",
"html-alink-weight",
"html-alink-decoration",
"html-alink-color",
"html-aactive-style",
"html-aactive-weight",
"html-aactive-decoration",
"html-aactive-color",
"html-b-style",
"html-b-weight",
"html-b-decoration",
"html-b-color",
"html-font-style",
"html-font-weight",
"html-font-decoration",
"html-font-color",
"html-i-style",
"html-i-weight",
"html-i-decoration",
"html-i-color",
"html-li-style",
"html-li-weight",
"html-li-decoration",
"html-li-color",
"html-p-style",
"html-p-weight",
"html-p-decoration",
"html-p-color",
"html-span-style",
"html-span-weight",
"html-span-decoration",
"html-span-color",
"html-textformat-style",
"html-textformat-weight",
"html-textformat-decoration",
"html-textformat-color",
"html-u-style",
"html-u-weight",
"html-u-decoration",
"html-u-color" ]);
// Sets default properties of RichTextArea
[Inspectable(category="General", defaultValue="false")]
override public function set editable(value:Boolean):void
{
super.editable = value;
}
[Inspectable(category="General", defaultValue="true")]
override public function set selectable(value:Boolean):void
{
super.selectable = value;
}
[Inspectable(category="General", defaultValue="true")]
override public function set condenseWhite(value:Boolean):void
{
super.condenseWhite = value;
}
// Constructor
public function RichTextArea()
{
super();
// Removes background, border and focus highlight of the TextArea
this.setStyle('backgroundAlpha', 0);
this.setStyle('borderStyle', 'none');
this.setStyle('focusAlpha', 0);
}
/**
* This method is called a static initializer since it is called before the
* constructor since it's being referenced by a static variable.
* If anything needs to be set before the constructor runs, this is the place to do it.
*
* @return true
**/
private static function classConstruct():Boolean
{
// Check to see if style exists
if (!StyleManager.getStyleDeclaration("RichTextArea"))
{
// Set default empty style declaration
var style:CSSStyleDeclaration = new CSSStyleDeclaration();
style.defaultFactory = function():void {};
StyleManager.setStyleDeclaration("RichTextArea", style, true);
}
return true;
}
/**
* Overrides the htmlText property to set the '_stylePropertyChanged' flag
* since any new text being set will not be styled unless updateDisplayList()
* is called.
*
* @param value:String The html text to be set
**/
override public function set htmlText(value:String):void
{
super.htmlText = value;
this._stypePropChanged = true; // Set the flag to style the text
invalidateDisplayList(); // Call updateDisplayList() on next render
}
/**
* Override the styleChanged() method to detect changes in your new style.
* only set the _stylePropChanged tag to true if one of our custom styles has
* been changed. If a new style is added, it must be added here as well.
*
* @param styleProp:String The new property name that has been set
**/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
// Check to see if it's one of our custom styles
if(this._styles.contains(styleProp))
{
// Redo text styling
this._stypePropChanged = true;
invalidateDisplayList();
}
}
// Override updateDisplayList() to update the component
// based on the style setting.
/**
* Override the drawing function and resets the style on the html text if the _stylePropChanged flag is set to true.
*
* @param unscaledWidth:Number the width of the current component
* @param unscaledHeight:Number the height of the current component
**/
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Check to see if style changed.
if (this._stypePropChanged)
{
this._stypePropChanged = false; // set the flag back to false
/*
* Adds new styles to the CSS for specific html content.
* More can be added, just add the tag name in the 'tags' array and
* add the css property 'html-<tag name>-<text property name>' in your stylesheet
* For instance, I can could do 'html-p-weight:bold'.
* Possible text property names are 'style', 'weight', 'decoration' and 'color'
*
* Only exception to the tag name is for the 'a' tag. You can specify just 'a' or
* you can do the pseudo class 'hover', 'link' and 'active', but you need to specify the tag
* before, like so 'html-ahover-weight: bold;'.
*/
var style:Object, value:Object, tag:String, tagSplit:Array, ss:StyleSheet = new StyleSheet();
for(var i:int = 0, len:int = this._styles.length; i < len; i++)
{
style = {};
tag = this._styles.getItemAt(i) as String;
// Convert into AS3 stylename format (camelCase)
tagSplit = tag.split('-');
tag = tagSplit[0] + String(tagSplit[1]).charAt(0).toUpperCase() + String(tagSplit[1]).slice(1) +
String(tagSplit[2]).charAt(0).toUpperCase() + String(tagSplit[2]).slice(1);
// Get style if there
value = getStyle(tag);
if(value)
{
style = ss.getStyle(tagSplit[1]); // Get style if one already exists so you don't override the previous one
// Find the proper style name and save it
switch(tagSplit[2])
{
case 'style':
style.fontStyle = value;
break;
case 'weight':
style.fontWeight = value;
break;
case 'decoration':
style.textDecoration = value;
break;
case 'color':
style.color = '#'+('00000'+Number(value).toString(16).toUpperCase()).substr(-6);
break;
}
// Do the cases for the pseudo 'a' classes and set the style object
switch(tagSplit[1])
{
case 'ahover':
ss.setStyle('a:hover', style);
break;
case 'alink':
ss.setStyle('a:link', style);
break;
case 'aactive':
ss.setStyle('a:active', style);
break;
default:
ss.setStyle(tagSplit[1], style);
break;
}
}
}
styleSheet = ss; // Save stylesheet
}
}
}
}


