Showing posts with label yui. Show all posts
Showing posts with label yui. Show all posts

Monday, May 09, 2011

Oh the Scrollable Fixed Element in IE6!

After two years of not writing a post, it's funny that I'm writing about IE6. For goodness sakes, it's 2011! The good news is that there's a really concerted effort to encourage people to move away from IE6. Yes, it's not secure. Yes, it doesn't support HTML5. Yes, it has huge memory leaks, but IE6 still persists.

Look at Internet Explorer 6 Countdown. Look closely at Asia. As of this writing, 24% of South Korean users use IE6 and look at China. 35%! These are not insignificant numbers. If you do any business in Asia, IE6 is still an important browser to support.

Which takes me to today. At Yahoo!, we have graded browser support. Which means that we degrade gracefully to support "lower" end browsers. For example, if we're going to support HTML5 rounded corners via border-radius, they'll appear nice and round in Firefox 3.5+, Google Chrome and Safari but not in IE6, IE7 or IE8. But sometimes, there are compelling reasons to support a feature that's not natively supported in a browser. Take the CSS property
position with the value fixed.

It's an incredibly useful property. You can fix an element to the page and all the other elements scroll past it. It's great for using as a docked element at the top or the bottom of the page. Unfortunately, it doesn't work in IE6, but thankfully, people like Ryan Fait has come up with a nice CSS solution. The general idea is that if you want your element to be fixed, you make it an absolutely positioned element. A fixed - like an absolutely positioned element - takes the element out of the document flow. Then, all the other elements - those that aren't fixed - are relatively positioned. If you provide the containing body with a height of 100%, voila! You have an instant fixed element working in IE6. Congratulations and thank Ryan!

Sometimes though, you want to do a little bit more with a fixed element. "Ah, a fixed element is dull, dull, dull and it doesn't impress anyone." So to impress even your dog ( or whatever pet you have ), you'll hide and then show the element after you scroll down the page and then you'll reverse the process when you scroll up. In fact, it's exactly like what Uxmatters does, but ours will work in IE6.

Here's the example. Open that in another window. Scroll down and at 250px, you'll see the banner appear. Scroll up and it'll disappear. Now view the source. The JavaScript is near the end and is pretty simple. We'll use our trusty YUI 3.3.0 with the "node" and "event" modules.

There are a couple of key points. The first is that we show the fixed banner when the scrollTop position is greater than the SHOW or trigger position which in our case is 250px. The second is how we calculate scrollTop. For non-IE6 browsers, we use the YUI DOM method docScrollY() which returns an integer value of how much we've vertically scrolled the page. For IE6, we get the "scrollTop" from the IE6Wrapper element which is the relatively positioned element or in other words, the element that isn't fixed. ie6Wrapper.get(SCROLL_TOP) retrieves the distance between the top of the object and the topmost portion of the content currently visible in the window.

Notice how we determine scrollWindow. We test - using Y.UA.ie - to determine whether we're using IE6 and if so, scrollWindow is a reference to the ie6Wrapper. Otherwise, it's Y.config.win ( this is in fact the reference to the active window attached to each YUI instance. You can use "window", but it's recommended that you use Y.config.win. Dav Glass discusses that here. ). Now, because the scrollWindow is a reference to the ie6Wrapper or to Y.config.win, we can simply use a single "on" method to attach the "scroll" event.

Feel free to use this. Hopefully, it won't be another two years before I post something. Good day.

Monday, March 02, 2009

Super Easy Fast-Forward Reverse Carousel with YUI 3!

I've been mucking with YUI 3 and playing around with carousels using their Animation Utility. If you don't know what a carousel is, here's a suggested reading. Our carousel is a little different. Ours rewinds when we reach the end or fast-forwards when we're on the first and go to the last element. We come up with something like this --



Play with it. It'll work on all the big browsers -- IE, FF, Safari and Opera. Think of the carousel as a queue with a start and an end. So, all we're doing is traversing the queue from the front to the end and back again. It's a simple exercise which we probably did in Data Structures 101!

Go ahead and view the source. The code is easy to understand and it'll just be another YUI 3 example.


There are several important points to remember. We'll use an HTML template to represent the carousel. Our JavaScript will reference this for the animation --
            <div class="view-port">
<ul class="carousel">

<li>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</li>

<li>
<div>5</div>
<div>6</div>
<div>7</div>
<div>8</div>
</li>

<li>
<div>9</div>
<div>10</div>
<div>11</div>
<div>12</div>
</li>

<li>
<div>13</div>
<div>14</div>
<div>15</div>
<div>16</div>
</li>

<li>
<div>17</div>
<div>18</div>
<div>19</div>
<div>20</div>
</li>

<li>
<div>21</div>
<div>22</div>
<div>23</div>
<div>24</div>
</li>

</ul>
</div>
We've grouped our items -- this is the unordered list -- and each group represents the "viewable" area and each list contains content. Content of course can be anything -- a div, another list, an image, a table or a collection of all those things. It's the unordered list with the class name "carousel" that we'll be animating. The important thing to remember is that we can easily calculate the width of each list by using the element's "offsetWidth" property. There's no need to hardcode anything. This is the width of the viewable area and this is also the distance which we'll move by. If we view the source, this is the variable WINDOW_WIDTH.

Another thing that we're doing is that we never set the animation object's "from" attribute. This is because "from" is implicit. Once we move to a position, we know where we're at ( obviously! ). We just need to specify where we want to move and so throughout the animation, we just set the "to" attribute. In reality, we can always get our current position by calling the method getX() on the element that we're moving.
                function scrollLeft() {

if (_currentIndex === _startIndex) {

_currentXPosition = -1 * WINDOW_WIDTH * (TOTAL_PAGES - 1) + _carousel.getX();
_currentIndex = TOTAL_PAGES - 1;

} else {

_currentXPosition = _carousel.getX() + WINDOW_WIDTH;
--_currentIndex;

}

}

where _carousel is the unordered list that we're moving.

That's it. It's pretty straightforward and easy. Feel free to use the code and 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!

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!

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!

Monday, December 03, 2007

Bindows Gauges

Recently, Bindows released a set of very functional gauges. These are "provided free of charge." They're built on top of their excellent Bindows component library.

The gauges are built from their BiGauge2 component.

One of the great things about these gauges is that it not only gives you a flavor of what Bindows can do, but that at least with these gauges, you don't need Bindows to manipulate them.

All you really need is the ability to set the value of the needle and the labels.

Here's an example. ( Make sure that you're running this in IE5.5+ or Firefox 2. The gauges won't show up in Safari ).

The two gauges respond to the mousemove event. The x or the e.clientX position and the y or the e.clientY position are updated on the gauges.

First, you'll need to get the gauges. Bindows has made this really easy. You need to download the gauge library and then follow these instructions.

Once you have them, you can manipulate the XML that defines the gauges. You can change the label, the start point, the end point, the ticks, etc.

However, those changes are static. What's more useful is to make changes at runtime. So, now, it's a matter of adding an event handler to manipulate the gauges.

In our example, we do two things --

1) Update the value of the needle with the new x/y position
2) Update the labels with the actual x/y position

The first is easy and is documented by Bindows. In our case, we do --

var xPosGauge = bindows.loadGaugeIntoDiv("gauges/gauge1.xml", "xPosGauge");
var yPosGauge = bindows.loadGaugeIntoDiv("gauges/gauge2.xml", "yPosGauge");

var xNeedle = xPosGauge.needle, yNeedle = yPosGauge.needle;

var handleMousemove = function(e) {
var x = parseInt(e.clientX);
...
xNeedle.setValue(x);
var y = parseInt(e.clientY);
...
yNeedle.setValue(y);
}

We get a reference to the gauge object, get the property "needle" and then at runtime ( in the mousemove event handler ) set it's value. The needle moves!

The second part is a little bit more difficult. I wasn't able to find a way to get to the labels without doing a "hack."

Through the magic of Firebug ( or Microsoft's Developer Toolbar ), we'll find out that the label is a BiGauge2Label.

Unfortunately, knowing that isn't really too useful. What's more useful is knowing where the second label appears in the structure of the gauge. By knowing that, we get a reference and can manipulate it.

This is why there's this strange looking code which locates the second label --

var xKids = xPosGauge.__gauge._gauge2Group.getChildren();
var len = xKids.length;
var ct = 0, xPxLabel, yPxLabel;

for (var i=0;i
if (xKids[i] instanceof BiGauge2Label) {
if(ct==1) {
xPxLabel=xKids[i];
break;
}
++ct;
}
}


Once we get the label, it's just a matter of setting it in the event handler --

var handleMousemove = function(e) {
var x = parseInt(e.clientX);
xNeedle.setValue(x);
xPxLabel.setText(x);
var y = parseInt(e.clientY);
yNeedle.setValue(y);
yPxLabel.setText(y);
}

The example uses the YUI Library to add the mousemove event, but you don't have to. You can use any library you want ( or not ) to handle the event.

To run my example, on your systems, you'll need --

1) The Bindows gauge library
2) My two gauges ( gauge1.xml and gauge2.xml )
3) My code ( and view the source )

That's it! Please update and improve. Enjoy!

Sunday, November 18, 2007

iGoogle and Newsvine Dashboards

I've always been partial to dashboards and often write about it. I've done a number of prototypes just to experiment with visualizations.

Dashboards allow you to organize and see the information you want without adding too much clutter.

Here's my implementation. It works in IE, Firefox, Safari and Opera.

I particular like the visualizations found in the iGoogle and Newsvine dashboards. They provide nice feedback on the container that you're moving and where you'll drop it. So, I've copied that.

When you move a container, everything is **real** size -- not a sliver for where you'll drop the container. For me, the argument that you cause too drastic of a visual change to the layout doesn't hold water.

You're only moving one container at a time and it's not a common operation. You basically move a container, get it into the fold and view it.

You're also only dealing with a **few** containers. This is a dashboard.

The key to the implementation is controlling the container's drag over behavior. Each container needs to react to the container that's being dragged over it. So, pick a library that the supports such behavior ( you can pick Bindows, but keep in mind that if you do, you won't be able to see the dashboard if you turn JavaScript off; of course, with any library, turning it off means that there's no behavior at all! ).

My implementation uses YUI 2.3 which easily handles drag over with the drag and drop library.

In this case, a drag over, inserts a temporary marker above the container.

When you drag out, the marker is removed and the container list collapses ( each container is statically positioned ).

All of the JavaScript is really short and pretty much self explanatory. Run the example and view the source. Use and improve it.

Have fun!

Sunday, November 11, 2007

Select and Scroll

A colleague asked how I would implement this --
  1. Click on an item in an list
  2. Vertical scroll to it
I can up with this --



I think the only way that this can be done is if the item ( in this case a div ) is absolutely positioned. For each item, you can get the value for CSS style "top" from the item's parent ( container ).

If the item uses default positioning ( i.e. static ), "top" is auto which is pretty much useless for moving the vertical scroll bar. If position is relative that's also not useful because "top" is relative to the previous element.

The idea is that we'll increment the parent's "scrollTop" property until that's equal to the top position of the item. This causes vertical animated scrolling ( if you don't want animation, you could just set "scrollTop" to "top"; however, keep in mind that this "jumps" rather than transitions to the item ).

We end up with a some simple JavaScript --

var doScroll = function(e){
var target = YAHOO.util.Event.getTarget(e);
var container = document.getElementById("container");
var targetTop = parseInt(YAHOO.util.Dom.getStyle(target, "top"));

var animateScroll = function() {
var maxScrollTop;
if (container.scrollTop !=
maxScrollTop = container.scrollTop; container.scrollTop+=2;
container.scrollTop == maxScrollTop ? clearInterval(intervalID) : "";
} else {
clearInterval(intervalID);
}
var info = "container.scrollTop = " + container.scrollTop;
document.getElementById("info").innerHTML = info;
}
var intervalID = setInterval(animateScroll, 1);
}
YAHOO.util.Event.on("container", "click", doScroll);

Note that we're using YUI, but that's not critical. YUI is used to handle events in a cross-browser manner as well as getting the CSS style ( you can do this with target.style.top, but I chose to use YAHOO.util.Dom.getStyle() ).

maxScrollTop tells us whether the scroll button is at it's maximum value. We know this when we increment the container's scrollTop and it doesn't change. When that happens, we stop the animation by clearing the timer.

You can see the full example
here.

If you can do this with static or relative positioning, I'd love to see your solution!

Have fun!

Saturday, October 06, 2007

Stixy Dialog

I think one of the best things to do is to imitate UI controls. It's a great way to test your front-end skills and the toolkits that you're using. It's also incredibly fun.

Continuing along our previous DnD and resizing examples built in part with YUI, here's an imitation of a dialog found in Stixy, a web-based free-form collaborative and file sharing application --



There's nothing really special here ( other than how nicely the green color matches with the pink ). The DnD and resizing are basic "things" that you can build from YUI ( we're using YUI 2.3 ).

In the dialog, you can type in text. Note that both the date and time update asynchronously.

Go ahead and review the code. You'll see the full example here.

Have fun!

Wednesday, October 03, 2007

Resizing with YUI

Here's a really simple example of using YUI's Drag and Drop component to build a resizer. It's not a reusable component, but you'll see just how little JavaScript you need to do something like this.

Here's the example --



The JavaScript looks like --



I'm using YUI 2.3, but the version really shouldn't matter.

The HTML is even simpler --



and then we style it with some CSS --



That's all there is to it. This illustrates how you can use YUI's "helper" components to enhance your application.

Use and enhance this example as much as you like.
Have fun!