StyleSheet: Adjusting a page theme on the fly
In this example, we'll use a snapshot of the W3C HTML 4.01 specification for Style Sheets and add a custom dynamic StyleSheet to apply some color and font size changes.
A Progressive enhancement strategy is used to extract a static form on the page into a draggable Overlay. Additionally, one of the form inputs is replaced with a Slider. Enter any valid CSS color value into the other inputs (e.g. #123456
, #135
, rgb(0,0,0)
, or red
).
Full code listing
Markup
The markup as stated above is a local snapshot of the HTML 4.01 spec, but with the following markup added to the end of the <body>
to show a progressive enhancement model.
<!-- The original body content is above --> <div id="form_container"> <form class="yui-widget-bd" id="theme_form" action="#" method="get"> <fieldset> <h3>Update Theme</h3> <label for="font_size">Font size:</label> <input type="text" size="3" id="font_size" value="16px"> <label for="heading_color">Heading color:</label> <input type="text" size="12" id="heading_color" value="#005A9C"> <label for="link_hover">Link hover backgound:</label> <input type="text" size="12" id="link_hover" value="#ffa"> </fieldset> <input type="submit"> </form> </div>
<!-- The original body content is above --> <div id="form_container"> <form class="yui-widget-bd" id="theme_form" action="#" method="get"> <fieldset> <h3>Update Theme</h3> <label for="font_size">Font size:</label> <input type="text" size="3" id="font_size" value="16px"> <label for="heading_color">Heading color:</label> <input type="text" size="12" id="heading_color" value="#005A9C"> <label for="link_hover">Link hover backgound:</label> <input type="text" size="12" id="link_hover" value="#ffa"> </fieldset> <input type="submit"> </form> </div>
JavaScript
// Create a new YUI instance, requiring stylesheet, overlay, slider, and the // dd-plugin to make the overlay draggable YUI({base:"../../build/", timeout: 10000}).use("stylesheet", "overlay", "slider", "dd-plugin", function (Y) { var myStyleSheet = new Y.StyleSheet(), overlayContent = Y.one('#form_container'), overlay, slider, fontSizeInput, // Create the Overlay, using the form container as the contentBox. // The form is assigned a class yui-widget-bd that will be automatically // discovered by Overlay to populate the Overlay's body section. // The overlay is positioned in the top right corner, but made draggable // using Y.Plugin.Drag, provided by the dd-plugin module. overlay = new Y.Overlay({ contentBox: overlayContent, width: '225px', align: { points: [ Y.WidgetPositionExt.TR, Y.WidgetPositionExt.TR ] }, plugins: [ Y.Plugin.Drag ] }).render(); // Slider needs a parent element to have the sam skin class for UI skinning overlayContent.addClass('yui-skin-sam'); // Progressively enhance the font-size input with a Slider fontSizeInput = Y.one('#font_size'); fontSizeInput.set('type','hidden'); fontSizeInput.get('parentNode').insertBefore( Y.Node.create('6 <div id="font_slider"></div> 36'), fontSizeInput); // Create a Slider to contain font size between 6px and 36px, using the // page's current font size as the initial value. // Set up an event subscriber during construction to update the replaced // input field's value and apply the change to the StyleSheet slider = new Y.Slider({ boundingBox: '#font_slider', railSize: '100px', thumbImage: Y.config.base + '/slider/assets/skins/sam/thumb-classic-x.png', min: 6, max: 36, value: parseInt(Y.one('body').getStyle('fontSize')) || 13, after: { valueChange: function (e) { var size = e.newVal + 'px'; this.get('thumb').set('title', size); fontSizeInput.set('value', size); myStyleSheet.set('body', { fontSize: size }); } } }).render(); // The color inputs are assigned keyup listeners that will update the // StyleSheet if the current input value is a valid CSS color value // The heading input affects all h1s, h2, and h3s Y.on('keyup', function (e) { var color = this.get('value'); if (isValidColor(color)) { myStyleSheet.set('h1, h2, h3', { color: color }); } }, '#heading_color'); // The link hover affects the background color of links when they are // hovered. There is no way other than via stylesheet modification to // change pseudo-class styles. Y.on('keyup', function (e) { var color = this.get('value'); if (isValidColor(color)) { myStyleSheet.set('a:hover', { backgroundColor: color }); } }, '#link_hover'); // Progressive form enhancement complete, now prevent the form from // submitting normally. Y.on('submit', function (e) { e.halt(); }, '#theme_form'); // A rudimentary validator to make sure we're not trying to set // invalid color values in StyleSheet. function isValidColor(v) { return /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(v) || /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(v) || /^[a-z]{3,}$/i.test(v); } });
// Create a new YUI instance, requiring stylesheet, overlay, slider, and the // dd-plugin to make the overlay draggable YUI({base:"../../build/", timeout: 10000}).use("stylesheet", "overlay", "slider", "dd-plugin", function (Y) { var myStyleSheet = new Y.StyleSheet(), overlayContent = Y.one('#form_container'), overlay, slider, fontSizeInput, // Create the Overlay, using the form container as the contentBox. // The form is assigned a class yui-widget-bd that will be automatically // discovered by Overlay to populate the Overlay's body section. // The overlay is positioned in the top right corner, but made draggable // using Y.Plugin.Drag, provided by the dd-plugin module. overlay = new Y.Overlay({ contentBox: overlayContent, width: '225px', align: { points: [ Y.WidgetPositionExt.TR, Y.WidgetPositionExt.TR ] }, plugins: [ Y.Plugin.Drag ] }).render(); // Slider needs a parent element to have the sam skin class for UI skinning overlayContent.addClass('yui-skin-sam'); // Progressively enhance the font-size input with a Slider fontSizeInput = Y.one('#font_size'); fontSizeInput.set('type','hidden'); fontSizeInput.get('parentNode').insertBefore( Y.Node.create('6 <div id="font_slider"></div> 36'), fontSizeInput); // Create a Slider to contain font size between 6px and 36px, using the // page's current font size as the initial value. // Set up an event subscriber during construction to update the replaced // input field's value and apply the change to the StyleSheet slider = new Y.Slider({ boundingBox: '#font_slider', railSize: '100px', thumbImage: Y.config.base + '/slider/assets/skins/sam/thumb-classic-x.png', min: 6, max: 36, value: parseInt(Y.one('body').getStyle('fontSize')) || 13, after: { valueChange: function (e) { var size = e.newVal + 'px'; this.get('thumb').set('title', size); fontSizeInput.set('value', size); myStyleSheet.set('body', { fontSize: size }); } } }).render(); // The color inputs are assigned keyup listeners that will update the // StyleSheet if the current input value is a valid CSS color value // The heading input affects all h1s, h2, and h3s Y.on('keyup', function (e) { var color = this.get('value'); if (isValidColor(color)) { myStyleSheet.set('h1, h2, h3', { color: color }); } }, '#heading_color'); // The link hover affects the background color of links when they are // hovered. There is no way other than via stylesheet modification to // change pseudo-class styles. Y.on('keyup', function (e) { var color = this.get('value'); if (isValidColor(color)) { myStyleSheet.set('a:hover', { backgroundColor: color }); } }, '#link_hover'); // Progressive form enhancement complete, now prevent the form from // submitting normally. Y.on('submit', function (e) { e.halt(); }, '#theme_form'); // A rudimentary validator to make sure we're not trying to set // invalid color values in StyleSheet. function isValidColor(v) { return /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(v) || /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(v) || /^[a-z]{3,}$/i.test(v); } });
CSS
This is the CSS added to the page to skin the Overlay and its content.
/* For supporting browsers, the overlay is rendered semi-transparent with * fancy rounded corners */ .yui-overlay { background: rgba(128,128,128,0.3); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 7px; cursor: move; } .yui-overlay-content { background: rgba(205,205,205,0.3); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 1px; } .yui-overlay form { background: #f2fbff url(gradient-promo.png) repeat-x scroll 0 0; border: 2px solid #fff; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; margin: 0; padding: 0; font-size: 13px; } .yui-overlay fieldset { border: 1px solid #bcd; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; margin: 0; padding: 20px; } .yui-overlay h3 { border-bottom: 2px solid #fff; color: #479; background: transparent; margin: 0; font-size: 175%; } .yui-overlay label { display: block; margin: 1.3em 0 0.5ex; font-weight: bold; color: #003; } .yui-overlay p { margin: 2em 0 0; } /* override the move cursor for the Slider */ .yui-overlay .yui-slider:hover { cursor: default; }
/* For supporting browsers, the overlay is rendered semi-transparent with * fancy rounded corners */ .yui-overlay { background: rgba(128,128,128,0.3); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 7px; cursor: move; } .yui-overlay-content { background: rgba(205,205,205,0.3); -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 1px; } .yui-overlay form { background: #f2fbff url(gradient-promo.png) repeat-x scroll 0 0; border: 2px solid #fff; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; margin: 0; padding: 0; font-size: 13px; } .yui-overlay fieldset { border: 1px solid #bcd; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; margin: 0; padding: 20px; } .yui-overlay h3 { border-bottom: 2px solid #fff; color: #479; background: transparent; margin: 0; font-size: 175%; } .yui-overlay label { display: block; margin: 1.3em 0 0.5ex; font-weight: bold; color: #003; } .yui-overlay p { margin: 2em 0 0; } /* override the move cursor for the Slider */ .yui-overlay .yui-slider:hover { cursor: default; }