Slider: Creating a Slider from existing markup
This example illustrates how to create a Slider using existing markup. The boundingBox
and contentBox
are included in the markup and passed to the constructor. Standard class names are assigned to the DOM elements inside the contentBox
that will result in them being discovered and automatically used.
The visualization of the Slider is based on the volume control in Mac OS X 10.5, with additional controls included for illustration. Click on the speaker icon to show the Slider.
Things to note about this example:
- The Slider is rendered into a hidden container, and the
syncUI
method called when it is made visible - Some default Sam skin style is overridden to support the implementation
minGutter
andmaxGutter
configuration is used to limit the thumb movement inside the larger rail element and image.- There is no whitespace in the markup around the thumb's
<img>
element to avoid an IE layout bug.
Nulla facilisi. In vel sem. Morbi id urna in diam dignissim feugiat. Proin molestie tortor eu velit. Aliquam erat volutpat. Nullam ultrices, diam tempus vulputate egestas, eros pede varius leo, sed imperdiet lectus est ornare odio.
Phasellus wisi purus, interdum vitae, rutrum accumsan, viverra in, velit. Sed enim risus, congue non, tristique in, commodo eu, metus. Aenean tortor mi, imperdiet id, gravida eu, posuere eu, felis.
Start with markup
For complete control of the DOM structure used by Slider, we'll start with markup that includes the boundingBox
and contentBox
that wrap all YUI widgets.
<!-- boundingBox --> <div id="volume_slider"> <!-- contentBox --> <div id="volume_slider_content"> <!-- rail --> <div> <!-- thumb and thumb image (see note above about whitespace) --> <div><img src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div>
<!-- boundingBox --> <div id="volume_slider"> <!-- contentBox --> <div id="volume_slider_content"> <!-- rail --> <div> <!-- thumb and thumb image (see note above about whitespace) --> <div><img src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div>
Slider is set up to inspect the DOM inside its contentBox
for rail, thumb, and thumb image elements. It does this by searching for specific class names assigned to elements. Add these classes to the markup and Slider will use those elements rather than create its own.
<div id="volume_slider"><!-- boundingBox --> <div id="volume_slider_content"><!-- contentBox --> <div class="yui-slider-rail"> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div>
<div id="volume_slider"><!-- boundingBox --> <div id="volume_slider_content"><!-- contentBox --> <div class="yui-slider-rail"> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div>
Instantiate the Slider
With the markup in place, all that's left to do is instantiate the Slider with references to the boundingBox
and contentBox
elements. It will automatically discover the nodes already in the markup.
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var volume = new Y.Slider({ boundingBox: '#volume_slider', contentBox : '#volume_slider_content', railSize : '117px' }); });
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var volume = new Y.Slider({ boundingBox: '#volume_slider', contentBox : '#volume_slider_content', railSize : '117px' }); });
Anchoring the Slider in a menu bar
Now we place the Slider markup inside the markup for the rest of the menu bar and wire up the speaker button UI and interaction. You can see the full CSS and JavaScript for the other controls in the Full Code Listing below.
<div id="volume_control" class="volume-hide"> <label for="volume">volume</label><input type="text" size="3" maxlength="3" name="volume" id="volume" value="50"> <div id="volume_slider"> <div id="volume_slider_content"> <div class="yui-slider-rail"> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div> <button type="button" id="volume_icon" class="level_2" title="Open volume slider"><p>Open</p></button> <button type="button" title="Mute" id="mute"><p>mute</p></button> </div>
<div id="volume_control" class="volume-hide"> <label for="volume">volume</label><input type="text" size="3" maxlength="3" name="volume" id="volume" value="50"> <div id="volume_slider"> <div id="volume_slider_content"> <div class="yui-slider-rail"> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div> <button type="button" id="volume_icon" class="level_2" title="Open volume slider"><p>Open</p></button> <button type="button" title="Mute" id="mute"><p>mute</p></button> </div>
We'll use the following sprite background image to show the appropriate icon for the volume level (quiet to loud) managed by a class applied to the contentBox
.
Below is the CSS we'll need to create the appearance. Note how some Sam skin styles for Slider have been overridden with more specific selectors.
/* Override some default Sam skin styles */ #volume_control .yui-slider { display: block; position: absolute; top: 22px; vertical-align: top; } #volume_control .yui-slider-rail { background: url("assets/images/rail.png") no-repeat 0 0; height: 117px; width: 17px; padding: 0 7px; } /* Use a sprite for the speaker icon */ #volume_icon { background: url("assets/images/volume_icon.png") no-repeat -30px 0; border: none; height: 22px; overflow: hidden; width: 31px; } /* * adjust the speaker icon sprite in accordance with volume level and * active state */ #demo .volume-hide .level_0 { background-position: -31px 0; } #demo .volume-hide .level_1 { background-position: -31px -22px; } #demo .volume-hide .level_2 { background-position: -31px -44px; } #demo .volume-hide .level_3 { background-position: -31px -66px; } #demo .level_0, #demo .level_0:hover { background-position: 0 0; } #demo .level_1, #demo .level_1:hover { background-position: 0 -22px; } #demo .level_2, #demo .level_2:hover { background-position: 0 -44px; } #demo .level_3, #demo .level_3:hover { background-position: 0 -66px; }
/* Override some default Sam skin styles */ #volume_control .yui-slider { display: block; position: absolute; top: 22px; vertical-align: top; } #volume_control .yui-slider-rail { background: url("assets/images/rail.png") no-repeat 0 0; height: 117px; width: 17px; padding: 0 7px; } /* Use a sprite for the speaker icon */ #volume_icon { background: url("assets/images/volume_icon.png") no-repeat -30px 0; border: none; height: 22px; overflow: hidden; width: 31px; } /* * adjust the speaker icon sprite in accordance with volume level and * active state */ #demo .volume-hide .level_0 { background-position: -31px 0; } #demo .volume-hide .level_1 { background-position: -31px -22px; } #demo .volume-hide .level_2 { background-position: -31px -44px; } #demo .volume-hide .level_3 { background-position: -31px -66px; } #demo .level_0, #demo .level_0:hover { background-position: 0 0; } #demo .level_1, #demo .level_1:hover { background-position: 0 -22px; } #demo .level_2, #demo .level_2:hover { background-position: 0 -44px; } #demo .level_3, #demo .level_3:hover { background-position: 0 -66px; }
We'll also set the default volume to 50 and reverse the Slider's min
and max
so the top corresponds to higher values. minGutter
and maxGutter
are also configured to limit the movable range of the thumb on the larger background.
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var control = Y.one('#volume_control'), icon = Y.one('#volume_icon'), open = false, level = 2, volume; // Notice the chained call to render() volume = new Y.Slider({ boundingBox: sliderBox, contentBox : '#volume_slider_content', axis : 'y', min : 100, max : 0, value : 50, railSize : '117px', minGutter : 9, maxGutter : 11 }).render(); // Initialize event listeners volume.after('valueChange', updateIcon); icon.on('click', showHideSlider); Y.on('click', handleDocumentClick, 'document'); /* * Support functions */ // Adjust the class responsible for displaying the correct speaker icon function updateIcon(e) { var newLevel = e.newVal && Math.ceil(e.newVal / 34); if (level !== newLevel) { volume.get('boundingBox').replaceClass('level_'+level, 'level_'+newLevel); level = newLevel; } } // Show or hide the Slider in response to clicking on the speaker icon function showHideSlider(e) { control.toggleClass('volume-hide'); open = !open; if (open) { // Needed to correctly place the thumb volume.syncUI(); } } // Close the Slider when clicking elsewhere on the page function handleDocumentClick(e) { if (open && !icon.contains(e.target) && !volume.get('boundingBox').contains(e.target)) { showHideSlider(); } }
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var control = Y.one('#volume_control'), icon = Y.one('#volume_icon'), open = false, level = 2, volume; // Notice the chained call to render() volume = new Y.Slider({ boundingBox: sliderBox, contentBox : '#volume_slider_content', axis : 'y', min : 100, max : 0, value : 50, railSize : '117px', minGutter : 9, maxGutter : 11 }).render(); // Initialize event listeners volume.after('valueChange', updateIcon); icon.on('click', showHideSlider); Y.on('click', handleDocumentClick, 'document'); /* * Support functions */ // Adjust the class responsible for displaying the correct speaker icon function updateIcon(e) { var newLevel = e.newVal && Math.ceil(e.newVal / 34); if (level !== newLevel) { volume.get('boundingBox').replaceClass('level_'+level, 'level_'+newLevel); level = newLevel; } } // Show or hide the Slider in response to clicking on the speaker icon function showHideSlider(e) { control.toggleClass('volume-hide'); open = !open; if (open) { // Needed to correctly place the thumb volume.syncUI(); } } // Close the Slider when clicking elsewhere on the page function handleDocumentClick(e) { if (open && !icon.contains(e.target) && !volume.get('boundingBox').contains(e.target)) { showHideSlider(); } }
Full Code Listing
Here is the full markup, CSS, and JavaScript for the entire example, including the volume input and mute controls.
Markup
<div id="demo"> <div id="volume_control" class="volume-hide"> <label for="volume">volume</label><input type="text" size="3" maxlength="3" name="volume" id="volume" value="50"> <div id="volume_slider"> <div id="volume_slider_content"> <div class="yui-slider-rail"> <!-- IE expands whitespace around img tags into padding --> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div> <button type="button" id="volume_icon" class="level_2" title="Open volume slider"><p>Open</p></button> <button type="button" title="Mute" id="mute"><p>mute</p></button> </div> <div class="demo-content"> <p>Nulla facilisi. In vel sem. Morbi id urna in diam dignissim feugiat. Proin molestie tortor eu velit. Aliquam erat volutpat. Nullam ultrices, diam tempus vulputate egestas, eros pede varius leo, sed imperdiet lectus est ornare odio.</p> <p>Phasellus wisi purus, interdum vitae, rutrum accumsan, viverra in, velit. Sed enim risus, congue non, tristique in, commodo eu, metus. Aenean tortor mi, imperdiet id, gravida eu, posuere eu, felis.</p> </div> </div>
<div id="demo"> <div id="volume_control" class="volume-hide"> <label for="volume">volume</label><input type="text" size="3" maxlength="3" name="volume" id="volume" value="50"> <div id="volume_slider"> <div id="volume_slider_content"> <div class="yui-slider-rail"> <!-- IE expands whitespace around img tags into padding --> <div class="yui-slider-thumb"><img class="yui-slider-thumb-image" src="assets/images/thumb.png" height="17" width="17"></div> </div> </div> </div> <button type="button" id="volume_icon" class="level_2" title="Open volume slider"><p>Open</p></button> <button type="button" title="Mute" id="mute"><p>mute</p></button> </div> <div class="demo-content"> <p>Nulla facilisi. In vel sem. Morbi id urna in diam dignissim feugiat. Proin molestie tortor eu velit. Aliquam erat volutpat. Nullam ultrices, diam tempus vulputate egestas, eros pede varius leo, sed imperdiet lectus est ornare odio.</p> <p>Phasellus wisi purus, interdum vitae, rutrum accumsan, viverra in, velit. Sed enim risus, congue non, tristique in, commodo eu, metus. Aenean tortor mi, imperdiet id, gravida eu, posuere eu, felis.</p> </div> </div>
JavaScript
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var control = Y.one('#volume_control'), sliderBox = Y.one('#volume_slider'), volInput = Y.one('#volume'), icon = Y.one('#volume_icon'), mute = Y.one('#mute'), open = false, level = 2, beforeMute = 0, wait, volume; sliderBox.setStyle('left',icon.get('offsetLeft')+'px'); volume = new Y.Slider({ boundingBox: sliderBox, contentBox : '#volume_slider_content', axis : 'y', min : 100, max : 0, value : 50, railSize : '117px', minGutter : 9, maxGutter : 11 }).render(); // Initialize event listeners volume.after('valueChange', updateInput); volume.after('valueChange', updateIcon); mute.on('click', muteVolume); volInput.on({ keydown : handleInput, keyup : updateVolume }); icon.on('click', showHideSlider); Y.on('click', handleDocumentClick, document); // Support functions function updateInput(e) { if (e.src !== 'KEY') { volInput.set('value',e.newVal); } } function updateIcon(e) { var newLevel = e.newVal && Math.ceil(e.newVal / 34); if (level !== newLevel) { icon.replaceClass('level_'+level, 'level_'+newLevel); level = newLevel; } } function muteVolume(e) { var disabled = !volume.get('disabled'); volume.set('disabled', disabled); if (disabled) { beforeMute = volume.getValue(); volume.setValue(0); this.set('innerHTML','unmute'); volInput.set('disabled','disabled'); } else { volume.set('value', beforeMute); this.set('innerHTML','mute'); volInput.set('disabled',''); } } function handleInput(e) { // Allow only numbers and various other control keys if (e.keyCode > 57) { e.halt(); } } function updateVolume(e) { // delay input processing to give the user time to type if (wait) { wait.cancel(); } wait = Y.later(400, null, function () { var value = parseInt(volInput.get('value'),10) || 0; if (value > 100) { volInput.set('value', 100); value = 100 } volume.setValue(value, { src: 'KEY' }); }); } function showHideSlider(e) { control.toggleClass('volume-hide'); open = !open; if (open) { volume.syncUI(); } if (e) { e.preventDefault(); } } function handleDocumentClick(e) { if (open && !icon.contains(e.target) && !volume.get('boundingBox').contains(e.target)) { showHideSlider(); } } });
// Create a YUI instance and request the slider module and its dependencies YUI({base:"../../build/", timeout: 10000}).use("slider", function (Y) { var control = Y.one('#volume_control'), sliderBox = Y.one('#volume_slider'), volInput = Y.one('#volume'), icon = Y.one('#volume_icon'), mute = Y.one('#mute'), open = false, level = 2, beforeMute = 0, wait, volume; sliderBox.setStyle('left',icon.get('offsetLeft')+'px'); volume = new Y.Slider({ boundingBox: sliderBox, contentBox : '#volume_slider_content', axis : 'y', min : 100, max : 0, value : 50, railSize : '117px', minGutter : 9, maxGutter : 11 }).render(); // Initialize event listeners volume.after('valueChange', updateInput); volume.after('valueChange', updateIcon); mute.on('click', muteVolume); volInput.on({ keydown : handleInput, keyup : updateVolume }); icon.on('click', showHideSlider); Y.on('click', handleDocumentClick, document); // Support functions function updateInput(e) { if (e.src !== 'KEY') { volInput.set('value',e.newVal); } } function updateIcon(e) { var newLevel = e.newVal && Math.ceil(e.newVal / 34); if (level !== newLevel) { icon.replaceClass('level_'+level, 'level_'+newLevel); level = newLevel; } } function muteVolume(e) { var disabled = !volume.get('disabled'); volume.set('disabled', disabled); if (disabled) { beforeMute = volume.getValue(); volume.setValue(0); this.set('innerHTML','unmute'); volInput.set('disabled','disabled'); } else { volume.set('value', beforeMute); this.set('innerHTML','mute'); volInput.set('disabled',''); } } function handleInput(e) { // Allow only numbers and various other control keys if (e.keyCode > 57) { e.halt(); } } function updateVolume(e) { // delay input processing to give the user time to type if (wait) { wait.cancel(); } wait = Y.later(400, null, function () { var value = parseInt(volInput.get('value'),10) || 0; if (value > 100) { volInput.set('value', 100); value = 100 } volume.setValue(value, { src: 'KEY' }); }); } function showHideSlider(e) { control.toggleClass('volume-hide'); open = !open; if (open) { volume.syncUI(); } if (e) { e.preventDefault(); } } function handleDocumentClick(e) { if (open && !icon.contains(e.target) && !volume.get('boundingBox').contains(e.target)) { showHideSlider(); } } });
CSS
#demo { background: #fff; border: 1px solid #999; color: #000; } #volume_control { height: 22px; line-height: 22px; background: url("assets/images/bg.png") repeat-x 0 -22px; position: relative; } #volume_control label { font-weight: bold; height: 22px; margin: 0 1ex 0 1em; vertical-align: top; zoom: 1; } #volume { border: 1px inset #999; height: 16px; margin: 2px 1ex 0 0; padding: 0 3px; text-align: right; vertical-align: top; width: 2em; } /* Override some default Sam skin styles */ #volume_control .yui-slider { display: block; position: absolute; top: 22px; vertical-align: top; } #volume_control .yui-slider-rail { background: url("assets/images/rail.png") no-repeat 0 0; height: 117px; width: 17px; padding: 0 7px; } /* Support open/close action for the slider */ #demo .volume-hide #volume_slider { display: none; } /* Use a sprite for the speaker icon */ #volume_icon { background: url("assets/images/volume_icon.png") no-repeat -30px 0; border: none; height: 22px; overflow: hidden; width: 31px; } /* move the button text offscreen left */ #volume_icon p { text-indent: -9999px; } #mute { background: url("assets/images/bg.png") repeat-x 0 -22px; border: none; height: 22px; vertical-align: top; } #mute p { margin: 0; } #mute:hover { background-position: 0 0; color: #fff; } /* * adjust the speaker icon sprite in accordance with volume level and * active state */ #demo .volume-hide .level_0 { background-position: -31px 0; } #demo .volume-hide .level_1 { background-position: -31px -22px; } #demo .volume-hide .level_2 { background-position: -31px -44px; } #demo .volume-hide .level_3 { background-position: -31px -66px; } #demo .level_0, #demo .level_0:hover { background-position: 0 0; } #demo .level_1, #demo .level_1:hover { background-position: 0 -22px; } #demo .level_2, #demo .level_2:hover { background-position: 0 -44px; } #demo .level_3, #demo .level_3:hover { background-position: 0 -66px; } #demo .demo-content { padding: 1ex 1em; }
#demo { background: #fff; border: 1px solid #999; color: #000; } #volume_control { height: 22px; line-height: 22px; background: url("assets/images/bg.png") repeat-x 0 -22px; position: relative; } #volume_control label { font-weight: bold; height: 22px; margin: 0 1ex 0 1em; vertical-align: top; zoom: 1; } #volume { border: 1px inset #999; height: 16px; margin: 2px 1ex 0 0; padding: 0 3px; text-align: right; vertical-align: top; width: 2em; } /* Override some default Sam skin styles */ #volume_control .yui-slider { display: block; position: absolute; top: 22px; vertical-align: top; } #volume_control .yui-slider-rail { background: url("assets/images/rail.png") no-repeat 0 0; height: 117px; width: 17px; padding: 0 7px; } /* Support open/close action for the slider */ #demo .volume-hide #volume_slider { display: none; } /* Use a sprite for the speaker icon */ #volume_icon { background: url("assets/images/volume_icon.png") no-repeat -30px 0; border: none; height: 22px; overflow: hidden; width: 31px; } /* move the button text offscreen left */ #volume_icon p { text-indent: -9999px; } #mute { background: url("assets/images/bg.png") repeat-x 0 -22px; border: none; height: 22px; vertical-align: top; } #mute p { margin: 0; } #mute:hover { background-position: 0 0; color: #fff; } /* * adjust the speaker icon sprite in accordance with volume level and * active state */ #demo .volume-hide .level_0 { background-position: -31px 0; } #demo .volume-hide .level_1 { background-position: -31px -22px; } #demo .volume-hide .level_2 { background-position: -31px -44px; } #demo .volume-hide .level_3 { background-position: -31px -66px; } #demo .level_0, #demo .level_0:hover { background-position: 0 0; } #demo .level_1, #demo .level_1:hover { background-position: 0 -22px; } #demo .level_2, #demo .level_2:hover { background-position: 0 -44px; } #demo .level_3, #demo .level_3:hover { background-position: 0 -66px; } #demo .demo-content { padding: 1ex 1em; }