tutorial: customizing the yui button widget
You may recall that I posted a tutorial about customizing the YUI TabView control to generate rounded tab corners a couple of months ago. Having spent some time recently tweaking the YUI Button widget, I thought I should post something about my experience customizing it for use on Protagonize.com.
First things first: the Button widget in the Yahoo! User Interface library can be both very simple to use and very difficult to figure out if you haven’t spent the time to read the documentation. The implementation and usage is not obvious unless you’ve taken the time to look at at the available examples. I learned this the hard way.
YUI buttons are actually quite handy and make for a really nice user experience if used in the right situations. On top of that, it saves you writing it yourself, and the YUI button sits on top of existing HTML button controls pretty transparently.
I’m not going to cover all of the varieties of YUI button types here (which range from simple push buttons to menu and split buttons for more advanced functionality.) The situation I had was that I was adding a new feature to Protagonize: linear stories. I wanted a quick way to toggle a new story from the existing format, the addventure, to the new linear story format. Logically, this would entail a radio button. Of course, as we all know, regular HTML radio buttons are pretty bland and boring, and I wanted something a little more legible and clear to the user.
Enter the YUI button. Since the YUI button works with all forms of input buttons, it adapts itself quite well to radio buttons. For a quick working example that doesn’t require you to register on Protagonize, take a look at the YUI radio button demo. Implementation is pretty straightforward — to quote the documentation:
The ButtonGroup class creates a set of Buttons that are mutually exclusive; checking one button in the group will uncheck all others in the group. The ButtonGroup class is defined by
YAHOO.widget.ButtonGroupand a ButtonGroup’s root HTML element is a A ButtonGroup can be instantiated three different ways:
Pretty simple, eh? Really not much to it. However, my situation entailed using ASP.NET input controls and not plain elements. I wasn’t sure how well this would tie in to styling the controls as both the YUI and .NET modify the markup somewhat between what you’re looking at in the code view and what actually gets rendered to the user. I hoped they’d play well together, and with a little tweaking, it worked out quite nicely.
My original .NET markup looked something like this (apologies if my code viewer munges this at all — if something’s out of whack, just leave a comment and let me know):
<div id="storyTypes" class="yui-buttongroup"> <asp :radiobutton id="radStoryAddventure" value="Addventure" checked="true" groupname="storyType" runat="server" /> <asp :radiobutton id="radStoryLinear" value="Linear" groupname="storyType" runat="server" /> </div>
As you can see, I just wrapped my usual controls in a ButtonGroup wrapper DIV and left the .NET markup the same as usual.
I then inserted a bit of JavaScript afterwards to handle capturing the radio buttons’ client-side click events. Attaching these in the codebehind wouldn’t work since the HTML is manipulated by the YUI Button widget initialization code during the creation of the ButtonGroup.
varr storyTypes = new YAHOO.widget.ButtonGroup( “storyTypes” ); var buttons = storyTypes.getButtons(); for( var i = 0; i < buttons.length; i++ ) { buttons[ i ].addListener( "click", onButtonClick ); } function onButtonClick( e ) { // handle checked action here }
var storyTypes = new YAHOO.widget.ButtonGroup( "storyTypes" );
var buttons = storyTypes.getButtons();
for( var i = 0; i < buttons.length; i++ ) {
buttons[ i ].addListener( "click", onButtonClick );
}
function onButtonClick( e ) {
// handle checked action here
}
The CSS in order to style these buttons properly is actually the most complicated part. Using the recommended core/skin method (see the YUI skinning best practices documentation for more info), I included the default button-core.css as well as the necessary scripts in my page header (I’ve changed the paths to match the way I’ve got the YUI deployed on my server, but the first CSS include is the button core CSS, and the second one is my customizations.)
<link rel="stylesheet" type="text/css" href="/_css/yui/button-core.css" /> <link rel="stylesheet" type="text/css" href="/_css/button.css" /> <script type="text/javascript" src="/_js/yui/element-beta-min.js"></script> <script type="text/javascript" src="/_js/yui/button-min.js"></script>
Since this was a special case and I didn’t want to break any existing YUI buttons on Protagonize, I had to override most of the skin specifically for the type of tile-format button I wanted to build. In order to do this, my button.css was pretty much entirely custom (though based on the original YUI SAM skin CSS.) I also removed button states that I didn’t need in my implementation, so you may want to compare with the original if you need those extra states.
/* customizations */
#storyTypes .yui-button {
border-width: 1px 0;
border-style: solid;
border-color: #bbb;
background: url(/_images/sprite.jpg) repeat-x 0 0;
margin: auto .25em;
}
#storyTypes .yui-button .first-child {
border-width: 0 1px;
border-style: solid;
border-color: #bbb;
margin: 0 -1px;
*position: relative; /* Necessary to get negative margins working in IE */
*left: -1px;
}
#storyTypes .yui-button button,
#storyTypes .yui-button a {
padding: 0 10px;
/*font-size: 93%;*/ /* 12px */
line-height: 2; /* ~24px */
*line-height: 1.7; /* For IE */
min-height: 2em; /* For Gecko */
*min-height: auto; /* For IE */
color: #666;
}
#storyTypes .yui-button button,
#storyTypes .yui-button a:active {
outline: none !important;
}
/* get rid of link focus borders in firefox */
#storyTypes .yui-button button,
#storyTypes .yui-button a:focus {
-moz-outline-style: none !important;
}
/* Focus state */
#storyTypes .yui-button-focus {
border-color: #7D98B8;
background-position: 0 -000px;
}
#storyTypes .yui-button-focus .first-child {
border-color: #7D98B8;
}
#storyTypes .yui-button-focus button,
#storyTypes .yui-button-focus a {
color: #000;
}
/* Hover state */
#storyTypes .yui-button-hover {
border-color: #999;
background-position: 0 -200px;
}
#storyTypes .yui-button-hover .first-child {
border-color: #999;
}
#storyTypes .yui-button-hover button,
#storyTypes .yui-button-hover a {
color: #000;
}
/* Active state */
#storyTypes .yui-button-active {
border-color: #666;
background-position: 0 -300px;
}
#storyTypes .yui-button-active .first-child {
border-color: #666;
}
#storyTypes .yui-button-active button,
#storyTypes .yui-button-active a {
color: #000;
}
/* Checked state */
#storyTypes .yui-radio-button-checked,
#storyTypes .yui-checkbox-button-checked {
border-color: #666;
background-position: 0 -100px;
}
#storyTypes .yui-radio-button-checked .first-child,
#storyTypes .yui-checkbox-button-checked .first-child {
border-color: #666;
}
#storyTypes .yui-radio-button-checked button,
#storyTypes .yui-checkbox-button-checked button {
color: #000;
}
/* Disabled state */
#storyTypes .yui-button-disabled {
border-color: #bbb;
background-position: 0 -200px;
}
#storyTypes .yui-button-disabled .first-child {
border-color: #bbb;
}
#storyTypes .yui-button-disabled button,
#storyTypes .yui-button-disabled a {
color: #A6A6A6;
cursor: default;
}
As you can see, I’ve also used my own small, custom sprite.jpg file instead of the default YUI SAM skin’s sprite.png file. This file includes all of the CSS background-images used specifically by my custom YUI button, and using what is effectively a variant on the sliding doors technique, the button background flips to a different XY coordinate inside the sprite file. Since there’s only a single image being used, it reduces flicker and image load times and makes the transitions between the disabled / hover / active / clicked states quite seamless.
Finally, I have a couple of very specific formatting changes to the content of the YUI buttons that I didn’t want to include in the main custom button.css file. These are relative only to the container housing these buttons, handling margins and font/sizing information for the button face, and in my case didn’t need to be part of the more general customizations (though the font and button dimensions information could be.)
#storyDetail #edit #storyTypes {
float: right;
width: 220px;
padding-top: 26px;
margin-right: 3px;
margin-left: 6px;
}
#storyDetail #edit #storyTypes .yui-button {
margin: 10px 0 0 10px;
}
#storyDetail #edit #storyTypes .yui-button button {
width: 100px !important;
height: 100px !important;
font: 16px georgia, times, "times new roman";
letter-spacing: -1px;
overflow: hidden;
padding-top: 72px;
}
Since the converted HTML looks completely different after first going through the initial ASP.NET input control to plain HTML conversion, then again passing through the YUI button initialization — which manipulates your HTML in the DOM to change the output once again — you have to be careful in your styling. The YUI button documentation provides a few good examples demonstrating styling the button control, since the contents of the generated button markup is quite different from what you’re looking at when editing the raw HTML, it’s a good thing to use for reference.
I’d love to hear back if people think this is useful. Enjoy!



No Comments »
No comments yet.
RSS feed for comments on this post. TrackBack URI
Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>