Sunday, October 26, 2008

Don't Ever Put Block Inside Inline Elements

It's been incredibly busy at work so I haven't blogged at all in over two months.

I did discover something which may seem obvious to you, but is worth repeating. Don't ever put block elements inside inline elements. If you do, in most cases, nothing bad will happen. If you do, watch out. It's better not to do it. Here's what the W3C spec says.
Probably many of us do it anyways and it seems to work, but it's "go
to hell" incorrect. Block elements visually begin on a new line.
They can contain inline and other block-level elements ( div, p, ul,
etc. ). Inline elements visually don't begin on a new line. They
contain only text and other inline elements ( span, a, img, etc. ).

Take this example of the two Colas --



Visually, they look identical, but study the HTML and you'll see that they're constructed differently.

Block in Inline Element
<a href="#">
<img src="./captainColaHeadShot.jpg">
<div class="title">Cola the Yorkie BAD</div>
<div>Inline containing a block element. This is bad!</div>
</a>

Inline in Block Element
<a href="#">
<img src="./captainColaHeadShot.jpg">
<span class="title">Cola the Yorkie GOOD</span>
<span>Inline containing an inline element. This is good!</span>
</a>
So, what's the big deal? After all, it works. Well, sort of.

You'll need a couple of things to see what I mean. First, you'll need to run the example in Firefox 2.x or 3.x, but before you do that make sure you've installed Firebug. Now, using Firebug, inspect and edit the text "Inline containing a block element. This is bad!" that's found in the div. Just type in some additional text like "Hello world!" or just anything. Now, inspect the div. You'll notice that the text is now contained inside an anchor tag which is inside the div! What happened?

I'm not sure, but I think Firefox attempts to correct the HTML by putting an inline inside a block-level element ( here's a really good writeup on invalid markup funkiness among the browsers ). If you try the same thing ( editing the text for "Inline containing a block element. This is good!" ), you won't observe this problem. This is because the markup is correct -- we've got a block-level element containing an inline element.

So, in my real job I saw this funkiness happen. Every once in a blue moon on page refresh, the text would be found in an mysterious anchor tag creating an ugly "blown" up visual. Correcting the markup fixed this problem for good.

You might be wondering what's the difference between a block-level element ( <div> ) and using CSS to make an inline element a block-level element ( <span style="display:block;"> ).

If an element by default -- like a div -- is a block-level element then it cannot be placed inside an inline element. However, an inline element made into a block-level element via CSS can be placed inside an inline element. This is because the style that's applied is irrelevant to the correctness of the markup. In other words, styling is our stated intention for what the element should look like and that's completely separate from valid markup.

Have fun!

Thursday, July 10, 2008

Mashup Redo!

My first mashup was way back in 2006 and like everyone else, I did one with Google maps. It was really in vogue to do a mashup with maps. It meant you got to play with AJAX. You were hip! My version looked like this.

I integrated the Google Map API with data from a Zip Realty feed. I wasn't building a competitor to Zillow or Trulia. I was just building a prototype for fun. So, I focused on homes in the Silicon Valley. When you clicked on a city, the list of homes for sale would load and when you clicked on a home, the information on the property would show up on the map in a big bubble pointing to a little house on the map.

I implemented my own drag and drop and asynchronous access to the feed. It all worked OK, but like all things there were things that could be improved. So, I updated it to this. It looks the same, but internally it's vastly different.

I'm using the YUI library. It has all the basic things that any web developer would want and it's free. There are other libraries of course, but with so many, why build your own? I use YUI for event handling, drag and drop and AJAX. Fundamentally, using a library abstracts all the complexity of doing those kinds of things ( and fixing bugs that go along with it! ).

I'm using event delegation. Minimizing the number and attaching events at the highest level of the DOM is important. First, reducing the number of events affords better code maintainability. Second, attaching events at the highest level allows you to make innerHTML swaps without reattaching events ( not that in the mashup I need to do this -- I don't -- , but in other cases, reattaching events is a big headache and not doing that is awesome ). Keep in mind that to do event delegation requires you to use DOM2 event handling. So, I've removed all DOM0 event badness in the updated example. In fact, we attach only one click handler to the entire mashup!

I was "over div-ing". I'm sure "div-ing" isn't a word, but how about "overly marked up"? I removed a number of div elements and instead used list elements. List elements are more meaningful. It looks like a list so it should be treated like a list. Most of these lists contain links.

I'm using the CSS hover pseudoclass. My first mashup handled background changes with JavaScript. We're in 2008 and there's no need to do that because modern browsers all support a:hover ( keep in mind a:hover won't work in IE6 ). So, we've removed a small chunk of JavaScript that's doing something unnecessary and replaced it with a simple CSS selector rule. JavaScript is great, but using it to make background changes is probably overkill. If you can do something in JavaScript that can be done in CSS, take the CSS approach. It's a lot easier to maintain and understand.

So, feel free to study the old versus the new.

Have fun!

Monday, June 30, 2008

On the Importance of Clearing Floats

I've been doing a lot of work with the Big Three of web development -- HTML, CSS and JavaScript. Prior to Yahoo!, I had done a lot of work with Bindows, a powerful "do everything" JavaScript framework. You built your entire web application with Bindows. You never manipulated HTML or CSS directly. It was all abstracted for you with their API.

I loved working with Bindows. It allowed me to improve my JavaScript skills and to build some really cool prototypes. Unfortunately, one of the niceties of Bindows -- that of abstraction and protecting you from the details of HTML and CSS -- turned out to be a real detriment for me.

My HTML and CSS skills suffered, but more importantly, to really fine tune the layout, you have to have access to the underlying structure and presentation layer.

Even some of the most basic and important concepts such as the importance of using and clearing floats weren't etched into my brain until I stepped foot at Yahoo!. I had read about it, understood enough to recognize it ( at least pass the interview ), but it wasn't until I really started doing hardcore development that I realized that to control layout means to control how things flow on the page and that meant understanding how to float and clear elements.

In fact, understanding floats is probably THE most important thing that you need to know when using HTML and CSS. Arguably, there are a bunch of other things that are also important such as knowing how to invoke hasLayout for IE, but I've found that a lot of the layout problems can be fixed by knowing when to clear floats.

When you float an element, you position an element to the left or right of another element. It's a very effective and fundamental way to position elements. Here's an example.

We're floating only two elements. The first is the entire "Left Side" div. It's floated to the left of the "Right Side" div. The second, is the image of Cola, the Yorkshire Terrier. That's floated to the left of the descriptive text. Everything is nicely formatted in Firefox, Safari, Opera and IE7 ( I haven't tried IE6, but it should work there as well ).

If you use Firebug, you'll see that we "clear floats" in three places --
  • At the container level -- this is the div with class "container" that contains the "Left Side" and the "Right Side" divs
  • The "Right Side" div -- this is the div with the class "right-stories"
  • The list element found inside the "Right Side" div
When we clear floats we put elements back into the flow so that they don't flow outside of the layout. There are many ways to clear floats, but the easiest way that I've found is to set the CSS property overflow to hidden. A good writeup of that technique is found here. It's found near the end.

So using Firebug, look for "overflow:hidden" and disable all of it. The elements overrun their borders and resembles a jumbled mish-mash of elements and seem to defy the box model. Here it is.

Whenever you observe strange layout like that, make sure that you're clearing your floats. Remember, floated elements come out of the flow so adjacent or containing elements need to set overflow so that all elements fall back into position. Think of setting overflow:hidden as getting all the misbehaving elements back into line.

Note that you can observe this visually, but you can also use Firebug and inspect the element. Hover over the element and if that element overlays another, that element is out of the flow. If you can identify and fix float issues, you'll be King for a Day!

Have fun!

Wednesday, May 21, 2008

Code Review Goodness

I can honestly say that prior to Yahoo!, I never had good code reviews. I think there were several reasons for this.

First, my code reviews weren't emphasized as being important. Sure, when a new project starts, there's the initial enthusiasm to discuss code reviews, but once it gets going and milestones are missed, reviews become secondary and it's far more important to just crank out and release something. The code works so it must be good!

The second is that some people just don't like to have their code reviewed. Maybe it's because they're oversensitive or maybe it's because they're considered "too smart" and having constructive criticism is a huge needle to their ego. These people never have their stuff reviewed or when it's reviewed, it's given a cursory look and a quick nod. I think these people don't go through the review process because they don't feel that it's worthwhile -- it's a waste of time and "cuts" into their productivity.

The third is the whole ambiguous nature of "code review." Really, what's a code review? Well, you're reviewing the parts that makeup your application and evaluating whether those parts are "good enough" to be a part of the whole. We're still ambiguous because what is "good enough"? "Good enough" allows you to set the bar as high or as low as you want and that's the problem. It's arbitrary, but in fact, good code reviews are well defined in scope and purpose.

Code reviews are full of goodness.

First, define what "good" means and stick to it. Focus on best practices or use recognized standards as a guideline for meaning "good". So, if you're a front-end developer, and you're coding for HTML 4.01 strict mode, you'd only want to follow that standard. You don't want to write well-formed XHTML! Likewise, if you're writing JavaScript, you'd want to avoid using eval for security or the with statement for performance reasons. You'll want to cache your variables and reduce function scope. There's of course a whole lot more, but you get the picture.

"Good" also means adhering to standards dictated by your system. For example, I'm working on a project that loads pages in a particular way. So, I have to write JavaScript in a way that optimizes page load. While other ways will work, those aren't the best ways to do it in this system.

Second, it's really important that developers learn from code reviews. Besides creating good software, the purpose of any code review is to teach and inspire. It's not to criticize. It's more of a round table discussion between passionate people wanting to write good code and producing good software. People should come into a review knowing that they'll leave with something useful.

In all code reviews, there should be "experts." I don't mean having a Douglas Crockford or a Nicholas Zakas at your side although they wouldn't hurt. In your organization, find your best HTML, CSS or JavaScript people and have them attend the sessions. They need to be humble, active and leading participants. Whenever a point is brought up, it must be explained why it should be done that way and why that's the best way.

Code reviews are egalitarian. Everyone should have their code reviewed and have a voice in the review. You may be really good at JavaScript, but are you really that good at semantic markup? In my experience as a front-end developer, I've found that most people focus on certain areas of web development while not focusing on others. So, it's always great to get everyone involved.

The other day a colleague was looking over some markup that I had written and commented on how I was using the alt attribute in the image tag and how in this case, it probably wasn't necessary. The text was already in the link next to the image and using alt would probably be more confusing than anything else. I was focusing on the markup and he was thinking accessibility! People will look at your code and think about it differently. It's those extra "eyes" that get you to good quality code.

I've rambled long enough about code review goodness! Now get to it!

Besides, it's also a long weekend!

Monday, April 14, 2008

Cross Browser Rounded Borders Using Sprites

I was reviewing techniques for rounded borders and came across this. It uses four background images for each of the corners; however, they're not using a sprite. So, we'll do it with a single sprite. Once we're done, we'll have something like this that works on all the major browsers ( IE6/IE7, Firefox, Safari and Opera ) --



First, we'll take the four corner GIF images that they have, make them transparent ( using a bitmap editor ) and then use a sprite generator to create our sprite as well as the supporting CSS. I like to use the one from Website Performance.

Next, we'll take the layered approach which means that we'll nest absolutely positioned elements in a relatively positioned container. So, the four corners, the two horizontal and vertical lines are absolutely positioned.

Here's the structure --
    <div class="b2">
<div class="border-container">
<div class="sprite-tl2"></div><div class="sprite-tr2"></div>
<div class="horz-line top-line1"></div>
<div class="horz-line top-line2"></div>
</div>
<div class="content">
We're using sprites for rounded borders. This implementation works in IE6/IE7, Firefox 2.0.x, Safari 3.1 and Opera 9.27. <a href="http://skypoetsworld.blogspot.com/">Read more.</a>
</div>
<div class="border-container">
<div class="sprite-bl2"></div><div class="sprite-br2"></div>
<div class="horz-line bottom-line1"></div>
<div class="horz-line bottom-line2"></div>
</div>
<div class="vert-line left-line1"></div>
<div class="vert-line left-line2"></div>
<div class="vert-line right-line1"></div>
<div class="vert-line right-line2"></div>
</div>

and here's the CSS --
      .b2{width:20em;margin-bottom:5px;position:relative;}
.border-container{position:relative;}
.sprite-bl2{background: url(roundsSprite3.gif) 0 -30px no-repeat; position:absolute;width:11px;height:10px;top:0;left:0;}
.sprite-br2{background: url(roundsSprite3.gif) 0 -70px no-repeat; position:absolute;width:11px;height:10px;top:0;right:0;}
.sprite-tl2{background: url(roundsSprite3.gif) 0 -110px no-repeat; position:absolute;width:11px;height:10px;top:0;left:0;}
.sprite-tr2{background: url(roundsSprite3.gif) 0 -150px no-repeat; position:absolute;width:11px;height:10px;top:0;right:0;}
.content{padding:10px 10px;font-family:verdana;font-size:93%;}
.horz-line{position:absolute;border-top:1px solid #EC9E3C;width:50%;}
.top-line1{left:11px;top:0;}
.top-line2{right:11px;top:0;}
.bottom-line1{left:9px;top:9px;}
.bottom-line2{right:10px;top:9px;}
.vert-line{position:absolute;border-left:1px solid #EC9E3C;height:50%;_height:2em;}
.left-line1{top:10px;}
.left-line2{bottom:0;}
.right-line1{top:10px;right:0;}
.right-line2{bottom:0;right:0;}

It's all pretty self-explanatory. You can view the sample and the source here. Feel free to use and improve it!
Have fun!

Sunday, April 06, 2008

Persistent iGoogle Dashboards :: Redux

From time to time, I get questions about the iGoogle imitation example. The most often asked question is how to persist the layout once you've moved the containers. One way to do it is to use cookies. Because the original example used the YUI library to handle drag and drop and because YUI 2.5.1 now supports a beta cookie library, we'll take the cookie approach.

Keep in mind that this is very simple example. Our containers don't contain data and we don't have a backend ( i.e. PHP, JSP, etc. ). We're just going to persist layout and that's pretty easy to do with cookies.


Here's the
example.

The general idea is that if the layout cookie isn't set, we'll show the default positions ( the default layout is already there; we've just set the display to "none" ). Otherwise, we'll layout based on what's found in the cookie.

What does the cookie look like?

Well, all of our containers ( "Container 1", "Container 2", etc. ) have static positions and are found in three columns. So, we'll use three subcookies ( c1, c2 and c3 ) where each subcookie contains a string consisting of element IDs. The cookie's name is "igoogimi" and expires every 14 days. If we were to look at the cookie on April 6, 2008, we'd see something like --


Name :: igoogimi

Value :: c1=Rec1%2CRec2&c2=Rec3%2CRec4%2CAbout&c3=Rec5%2CRec6
Expires :: Sun, 20 Apr 2008 05:41:55 GMT

Setting and getting a subcookie is pretty straightforward and it's
well documented. Setting our subcookie is interesting because we use the method YAHOO.util.Dom.getChildrenBy to query the DOM looking for the column IDs. Here's our setCookies method --
        var setCookies = function() {
// BEGIN :: Calculate cookie expiration to 14 days from today
var date = new Date();
date.setTime(date.getTime()+(14*24*60*60*1000));
// END :: Calculate cookie expiration to 14 days from today
var getNode = function(node) {
return (node.id==="Rec1"||node.id==="Rec2"||node.id==="Rec3"||node.id==="Rec4"||node.id==="Rec5"||node.id==="Rec6"||node.id==="About");
}
var createString = function(colId) {
var nodes = YAHOO.util.Dom.getChildrenBy(document.getElementById(colId), getNode);
var list = [];
var l = nodes.length;
for(var i=0;i<l;i++) {
list[i] = nodes[i].id;
}
return list.toString();
}
YAHOO.util.Cookie.setSub("igoogimi", "c1", createString("Column1"), {expires: date});
YAHOO.util.Cookie.setSub("igoogimi", "c2", createString("Column2"), {expires: date});
YAHOO.util.Cookie.setSub("igoogimi", "c3", createString("Column3"), {expires: date});
}

Notice that the method
createString creates the subcookies values by looking for all children of the node with the matching column ID which pass the boolean test defined as the getNode method. In this method, we check to see if any of the children have IDs equal to the containers. If so, then those children ( these are nodes ) are returned. From those children, a string consisting of IDs in the order of their appearance in the DOM is created.

We set the igoogimi cookie when the unload event is fired. This occurs when we navigate to another page, close the browser, etc. This ensures that the cookie is always set.


When we read the cookie, a number of things happen.


The entire page is hidden through CSS ( the body's display is set to "none" ). We do this because the default layout is always there. We don't want it to appear and then be replaced by the layout found in the cookie.


Once we've determined whether the cookie exists, we remove all the nodes while keeping a reference to them. Then, we'll add them back based on the order that they're found in the igoogimi cookie --

          var containerRef = [];
var node;

// BEGIN :: Removing the nodes
var cArray1 = c1.split(",");
var len = cArray1.length;
for(var i=0;i<len;i++){
node = document.getElementById(cArray1[i]);
node?containerRef[cArray1[i]] = node.parentNode.removeChild(node):"";
}
var cArray2 = c2.split(",");
len = cArray2.length;
for(var i=0;i<len;i++){
node = document.getElementById(cArray2[i]);
node?containerRef[cArray2[i]] = node.parentNode.removeChild(node):"";
}
var cArray3 = c3.split(",");
len = cArray3.length;
for(var i=0;i<len;i++){
node = document.getElementById(cArray3[i]);
node?containerRef[cArray3[i]] = node.parentNode.removeChild(node):"";
}
// END :: Removing the nodes

// BEGIN :: Adding the nodes
len = cArray1.length;
var col = document.getElementById("Column1");
var tmpCR;
for(var i=0;i<len;i++){
tmpCR = containerRef[cArray1[i]];
tmpCR?col.appendChild(tmpCR):"";
}
len = cArray2.length;
var col = document.getElementById("Column2");
for(var i=0;i<len;i++){
tmpCR = containerRef[cArray2[i]];
tmpCR?col.appendChild(tmpCR):"";
}
len = cArray3.length;
var col = document.getElementById("Column3");
for(var i=0;i<len;i++){
tmpCR = containerRef[cArray3[i]];
tmpCR?col.appendChild(tmpCR):"";
}
// END :: Adding the nodes

Keep in mind that we're passing through the DOM twice -- once for removal and another for insertion. So, it's not terribly efficient. Note that the removal and the insertions dynamically update the DOM and if we didn't hide the body, then the page would be jumping all over the place ( this is known as reflow where DOM manipulations are done when rendering is completed ). Once the last node has been added, we set the body's display to "block" and all the containers appear.


You can see our
example here. To review the code, simply view the source.

Feel free to use and make it better.

Have fun!

Friday, March 21, 2008

Using Sprites :: Transparent PNGs in IE6


IE6 doesn't support transparent PNGs unless you use Microsoft's
alpha filters. You can't use alpha filters for background images which is a key part of using sprites.

Fortunately, you can "sort of" simulate it. Julien Lecomte has this good write-up.

The key is to use the CSS clip property and to replace background with that. Here's my implementation --
.sprite-s0 {
width:47px;
height:62px;
_width:62px; /* Width of the sprite */
_height:672px; /* Height of the sprite */
background:url(spaceShipSprite.png) no-repeat 0 -30px;
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src="spaceShipSprite.png", sizingMethod="scale");
_background:none; /* Clears the background */
_margin-top:-30px; /* We have to adjust for the position of the space ship */
_clip:rect(30px 47px 92px 0);
top:0;
left:0;
}

Here's the example.

The above selector rule applies different CSS properties depending on your browser. Of course, the underscore before the property only applies to IE6. Keep in mind that there's limitations to this approach.

First, clip isn't equivalent to background because unlike background, you're actually specifying the size of the sprite. When you use clip, you're showing the area that's not clipped. If you're not using animation, it looks identical to a background image. However, if you intend on animating the sprite, clip isn't effective because you're not moving the 47x62 sprite, but moving the entire 62x672 graphic.

Second, and this a big limitation, clip only works for absolutely positioned elements.

Knowing all of this, we probably won't be using this technique to animate our spaceship and its flying targets.

Sunday, February 24, 2008

How to Build and Fly a Spaceship

Well, not exactly. We'll build and fly a spaceship built with the YUI library ( I'm using the 2.3.0 library; I typically don't upgrade that frequently especially when things aren't broken ). Once again we'll explore event handling and extensively use the animation library.

For part 1, We'll focus on listening for keyboard events and then using the YAHOO.util.Motion object we'll fly the spaceship and fire our laser gun into virtual space. In part 2, we'll focus on listening and subscribing to "hits". After all, if you fire a gun there has to be a target!

Without further adieu, here's the flying spaceship --



The beautiful spaceship is from Everaldo Coelho
. I found it on iconfinder. To fly Everaldo's spaceship, use the ARROW keys to move in the direction that you want to go. If you want to change directions, hold down the SHIFT key and use the LEFT or RIGHT ARROW keys. We've made this example simple by only supporting directions and movement at 45 degrees ( 0, 45, 90, 135, 180, 225, 270, 315 and 360 ).

To fire the gun, just press the SPACE BAR. Bullets spew out and then disappear at a distance.

You'll notice that when the spaceship moves, it has two streams of light adding a propulsion effect which appear behind the wings. These, like the spaceship and the bullets, use the YAHOO.util.Motion object.

In addition, we should note a few important concepts.

First, the spaceship is a PNG sprite consisting of eight spaceships rotated at 45 degree intervals. The images are rotated with a bit map editor and then using the Web Performance CSS Sprite Generator the sprite image along with their selector rules are created.

When we change the spaceship's direction, what we're really doing is flipping through the eight spaceships in the sprite. Each spaceship is used as a background image ( not an image element i.e. <img> ). Sprites greatly benefit performance in that they're loaded once ( reduce the number of HTTPRequests ) and are then cached. We've discussed that before.

Second, it's important to understand how we determine where we want to move. Because we know the angle and the distance of each movement ( for each key press ), we just need to calculate the x and y coordinates of where we want to go. Fortunately, that's pretty easy to do by recalling the basic geometry of a circle.

We'll assume that the center of origin is always at (0,0) and if we do that, then we can use these two equations to determine the "to" point (x, y) --

x = a + rcos(t)
y = b + r sin(t)

a and b are the "from" point (a, b). t is the angle ( theta ) which in our case is always increments of 45 degrees. A good refresher about the circle is found on Wikipedia.

Third, when we fire a bullet or add a propulsion effect, we dynamically add the div element to either the body or the spaceship. In both cases, we add these elements to the DOM with the display set as "none". It's important that we do this to prevent reflow or the redrawing of the elements on the page. Reflows make the elements on the page flicker. Once we've added the element, then we change the display to "block".

Likewise, when we remove an element, we set the display to "none" and then call removeChild(). Removing an element while it's displayed also causes reflow.

Fourth, we listen for the key events through YAHOO.util.KeyListener. We listen for the ARROW keys, the SHIFT and ARROW keys, the SPACE key and the SHIFT and SPACE key. Then, we call the appropriate event handler. So, all the interaction begins with the KeyListener.

You can see the minutae by viewing the source. Feel free to use and improve on my effort. If you do use it, let them know that I was the original author and send me a note on what you did to improve it.

Have fun!

Sunday, February 17, 2008

Animated Horizontal MenuBar

The animated horizontal menubar isn't the most practical or useful menubar around, but it does allow us to explore events with complex workflows. We'll also explore how we fire events programatically.

Here's our horizontal menubar --



The menubar is based on the YUI MenuBar ( in fact, I've kept some of the original comments from the YUI examples ). There are many examples and it's well documented. So, it's a good choice to add to it's behavior. The workflow that we want is simple --

Mouseover a menu item and the "cap" moves toward the item. Once the cap arrives, the menu item changes background and the submenu appears. Mouse to another menu item and the current submenu hides. The new menu item behaves as before.

Having users explore by mouseover is easier than having users click on each item. Animation allows us to "control the pace" at which our users explore.

Our workflow involves a sequence of events --
  1. The container containing the menubar listens for the mouseover event
  2. When a mouseover event is fired, the "cap" moves towards the menu item
  3. With the animation, we subscribe and handle the onStart and onComplete events for the animated object
  4. When onComplete occurs, we fire a custom event to the menubar which then displays the submenu
Note that when we use the term "listen" we mean that we listen for non-custom or "normal" events. An example of these are mouseover, mouseout, click, etc. They're fired when the user interacts with our application. Essentially, these are the basic events that you deal with in an web environment.

When we use the term "subscribe" we mean custom events or events that are fired programmatically. We're using three of these --
  • Both onStart and onComplete are found in YAHOO.util.Motion, the object that moves
  • A "showmenu" custom event attached to the MenuBar, fired once the animation completes
Here's the mouseover event handler, moveThing. It's invoked when you mouseover the container containing the menubar --
 var moveThing = function(e) {
clearID>-1?clearInterval(clearID):"";
var target = YAHOO.util.Event.getTarget(e);
var id = target.id;
var menuBar = (id == "Com" || id =="Shop" || id == "Ent" || id == "Inf");
if (menuBar) {
(doingMenu && doingMenu !== target)?hideMenu(doingMenu):"";
doingMenu = target;
oMenuBar.myCustomEvent.unsubscribe(handleCustomEvent, oMenuBar);
oMenuBar.cfg.setProperty("autosubmenudisplay", false);
var x = YAHOO.util.Dom.getX(target);
var y = YAHOO.util.Dom.getY(target)-4;
!w?w = parseInt(YAHOO.util.Dom.getStyle(target, "width")) + parseInt(YAHOO.util.Dom.getStyle(target, "padding-right")) + parseInt(YAHOO.util.Dom.getStyle(target, "padding-left")) + "px":"";
YAHOO.util.Dom.setStyle(thingToMove, "width", w);

var attributes = { points: { to: [x, y] }};
var anim = new YAHOO.util.Motion(thingToMove.id, attributes, 1, YAHOO.util.Easing.easeOut);

var handleOnStart = function(e) {
var el = anim.getEl();
YAHOO.util.Dom.setStyle(el, "display", "block");
}

// BEGIN :: Create/subscribe custom event
var handleCustomEvent = function(type, args, me) {
if (args.length>0) {
var current = parseInt(args[0].getAttribute("index"));
var item = me.getItem(current);
item.cfg.getProperty("submenu").show();
}
}
oMenuBar.myCustomEvent.subscribe(handleCustomEvent, oMenuBar);
// END :: Create/subscribe custom event

var handleOnComplete = function(e) {
anim.onStart.unsubscribe(handleOnStart);
anim.onComplete.unsubscribe(handleOnComplete);
anim=null;
if (doingMenu === target) {
var parentNode = doingMenu.parentNode;
parentNode.className.indexOf(" showing-menu")==-1?parentNode.className += " showing-menu":"";
oMenuBar.myCustomEvent.fire(target);
}
}

anim.onStart.subscribe(handleOnStart);
anim.onComplete.subscribe(handleOnComplete);
anim.animate();
} else if (id == "Container") {
doingMenu?hideMenu(doingMenu, 1000):"";
}
}

Everytime you mouseover the container, we create a new animation object. We do this so that the animation doesn't stop when we consecutively mouseover other menubar items. So, we intentionally don't reuse the YAHOO.util.Motion object.

To prevent memory leaks and to "clean up" our event handling we unsubscribe our events in the onComplete handler --
var handleOnComplete = function(e) {
anim.onStart.unsubscribe(handleOnStart);
anim.onComplete.unsubscribe(handleOnComplete);
anim=null;
if (doingMenu === target) {
var parentNode = doingMenu.parentNode;
parentNode.className.indexOf(" showing-menu")==-1?parentNode.className += " showing-menu":"";
oMenuBar.myCustomEvent.fire(target);
}
}

The onComplete handler changes the background image of the MenuItem ( by appending the class "showing-menu" ) and fires the "showmenu" custom event. This only occurs if the menu item is the same one that was the target of the mouseover in which case the menubar object subscribes and handles the event by displaying the submenu --
 var handleCustomEvent = function(type, args, me) {
if (args.length>0) {
var current = parseInt(args[0].getAttribute("index"));
var item = me.getItem(current);
item.cfg.getProperty("submenu").show();
}
}

The attribute "index" is a custom attribute added to each link in the HTML. It's used to get the menu item. Every YUI MenuItem ( or for that matter every Menu ) has a configuration property ( cfg ). For menu items, we've set the "submenu" property and so, in the event handler, we'll call "show()" to explicitly display that.

There's also an equivalent hide() that we call when we need to hide the submenu. Once again, we see symmetry of operations --
 var hideMenu = function(menu,t) {
// t is the time that it waits to hide it
var current = parseInt(menu.getAttribute("index"));
var item = oMenuBar.getItem(current);
var parentNode = menu.parentNode;

var hideIt = function() {
parentNode.className = parentNode.className.indexOf(" showing-menu")>-1?parentNode.className.replace(" showing-menu",""):parentNode.className;
item.cfg?item.cfg.getProperty("submenu").hide():"";
}
clearID=(arguments.length==2)?setTimeout(hideIt,t):hideIt();
}

You can find the example here. Feel free to view, use and improve it.
Have fun!

Wednesday, January 30, 2008

Dashing All the Way... Not

In programming, nothing ever just happens. There aren't any gremlins or magical incantations that cause code to work. Programming is about logic and good sound reasoning. If your code doesn't work, there's a really good explanation. The challenge is finding that explanation.

As a web developer, you someti
mes see really weird problems like the one I saw today.

In HTML, how do you add a comment?

You'd do this --

<!-- This is a comment -->


and you'd be right of course, but what if you did this --

<!-- This is a comment -- at least I think it is! -->


Is that a comment? Well, sort of. In the HTM
L 4.0 specification, "--" or two hyphens is considered the comment close delimiter whereas ">" is considered to be the markup declaration close delimiter.

Here's the explanation directly from the spec --

White space is not permitted between the markup declaration open delimiter(""). A common error is to include a string of hyphens ("---") within a comment. Authors should avoid putting two or more adjacent hyphens inside comments.

Information that appears between comments has no special meaning (e.g., character references are not interpreted as such).

Note that comments are markup.

So, what actually happens when you use the hypens in the middle of a comment? Here's an example --



If you're using Firefox, you'd see this --

The comments are visible. On any other browser, IE, Safari or Opera, you'd see a normal page --

So, using hyphens in an HTML comment breaks pages on Firefox. Keep that in mind the next time you put in a comment.

Note that in our example, we're specifying a HTML 4.01 DTD. We're using a "strict" DTD, but the problem would still occur if you'd specify a "transitional" or "frameset" DTD.

If you don't specify the HTML 4.01 DTD, you won't see this problem in Firefox.

Additional information and discussion is found on Ben Buchanan's the 200ok weblog.

Have fun!

Sunday, January 13, 2008

Augmenting JavaScript's String Object

A popular question to ask in interviews is string manipulation. In particular, string reversal questions are very popular.

Given a string,
  1. Reverse the string
  2. Reverse the words in the string
  3. Reverse only the words in the string
With JavaScript, all three are easy to do. One way to solve it is to augment the String object. In other words, we'll add three new functions to String. We'll call them
  1. reverseString()
  2. reverseWords()
  3. reverseOnlyWords()
To add the methods to String, we'll add them to the String's prototype property so that every instance of a String object or a string primitive will have access to these three methods.

Our definitions look like this

String.prototype.reverseString = function() {
var len = this.length, strArray = [], ct = 0;
for (var i=len-1;i>=0;i--) {
strArray[ct++] = this.charAt(i);
}
return strArray.join("");
}

String.prototype.reverseWords = function() {
var arr = this.split(" ");
var s = "";
while (arr.length>0) {
var element = arr.pop();
s += element + " ";
}
return s;
}

String.prototype.reverseOnlyWords = function() {
var arr = this.reverseWords().split(" ");
var s = "";
while (arr.length>0) {
var element = arr.pop().reverseString();
s += element + " ";
}
return s;
}

Remember that Strings are immutable so calling these functions never changes the original string.

So, doing this --

var s = "now is the time to do the work that must be done";
alert(s.reverseString());


results in the alert dialog displaying "enod eb tsum taht krow eht od ot emit eht si won", but if you display the variable s, you'll still see the string "now is the time to do the work that must be done".

Keep in mind that you can also use a string primitive --

alert("now is the time to do the work that must be done".reverseString());

As we noted in a
previous post, the string primitive is converted to the String object.

Here's a test driver for the three functions. Simply type in a string and watch the functions do their thing.




Of course, you don't have to augment the String. You could instead create a singleton String utility that defines the three functions --

var MyStringUtil = {
reverseString:function(str) {

var len = str.length, strArray = [], ct = 0;

for (var i=len-1;i>=0;i--) {

strArray[ct++] = str.charAt(i);

}

return strArray.join("");
},
reverseWords:function(str) {
var arr = str.split(" ");
var s = "";
while (arr.length>0) {
var element = arr.pop();
s += element + " ";
}
return s;
},
reverseOnlyWords:function(str) {
var arr = this.reverseWords(str).split(" ");
var s = "";
while (arr.length>0) {
var element = this.reverseString(arr.pop());
s += element + " ";
}
return s;
}
}

Feel free to use the functions anyway you like. Here's the augmented string, the string utility and the test driver.

Have fun!

Saturday, January 05, 2008

Browser Debugging Tools

We'll start off 2008 and talk about web browser debugging tools. It's a topic that we haven't visited before.

At Yahoo!, we have graded browser support and so, as a frontend developer you tackle the problems found only in the major browsers.

“Major” means IE, Firefox, Safari and to a small degree Opera ( if you’re interested in browser market share, check this out. )

I code on a MacBook Pro running OSX 10.4 though I have an XP box to tackle issues on IE6/IE7. I also have access to a Vista box to take on that version’s IE7.

I don’t run multiple versions of IE or use a virtual PC image. I have the luxury of running all the browsers in their native environments.

Browser debuggers allow you to view and manipulate the three components that make up a web page --
  1. The structure ( HTML )
  2. The layout and "look and feel" ( CSS )
  3. The behavior ( JavaScript )
On Firefox, the defacto standard debugger is Joe Hewitt's Firebug. It's a great tool for debugging web pages on Firefox.


Firebug also supports add-ons like YSlow that analyzes your page and tells you why your page is slow based on Yahoo!'s 14 rules for high performance web sites.

I primarily use YSlow to see what I'm downloading. You'd be surprised at what you don't know you're bringing into the browser!

Similar to Firebug is the Developer Toolbar found for IE. Use it to inspect, change elements and styles on the page.


One of the features that I use a lot in the Developer Toolbar ( and in Firebug ) is the element inspector. With it, I can click on a DOM element and immediately see it's relationship with other elements and it's style. Understanding the structure and the CSS rules applied to it is critical to debugging a web page.

In Safari, you can use the Debug menu to inspect the DOM, but unlike Firebug or IE's Developer Toolbar, you can't live edit HTML or CSS. I suspect the Safari/Webkit folks will have this in the future. For now, it's the only choice you have for debugging web pages on Safari.


Starting with 9.0, Opera has the Developer Console. It's a DOM, JavaScript and HTTP header inspector as well as allows you to dynamically edit CSS.


A good list of browser debugging tools is found here though the list is always growing. Those found above are "must haves."

Have fun!