Showing posts with label javascript. Show all posts
Showing posts with label javascript. 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!

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!

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!

Friday, December 14, 2007

Anonymous Function Fun

Yahoo!'s Julien Lecomte wrote an interesting piece regarding the perils of using innerHTML.

One of the "bad things" that can happen are memory leaks caused by circular references. He provides this inner function which breaks the reference --
     (function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
arguments
.callee(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}

})(el
);

If you're new to JavaScript and if the syntax isn't familiar, it's
worth a closer look.

First, the function has no name. It's an anonymous function
that accepts one argument, the object 'o'.

Because it's an anonymous function and since there's no
reference to the function whatsoever, you can't use "this" to
refer to the function, but you can always do this --

arguments.callee(c);

All functions implicitly have the property "arguments" which
behaves somewhat like an array but isn't ( you can iterate
arguments like an array and get its length, but that's where the
similarities end). More about the arguments property is found
here.

Arguments themselves have properties and one of those is
"callee."

Think of the callee as the currently executing function which
substitutes for the "this" object.

Notice that once the function is defined, it's immediately
executed. We see this at the very end --

(function (o) {
...
})(el);

If all of this is still puzzling, we can rewrite Julien's function to
look like --

var breakCircularReference = function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
this
(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}
}

breakCircularReference(el);


We've given the once anonymous function a name -
breakCircularReference.
This allows us to reference it which means that "this"
refers to the function breakCircularReference. We no longer
have to use arguments.callee.

The name also allows us to call it --

breakCircularReference(el);

which I think is easier to read and understand for new
JavaScripters.

Have fun and happy holidays!

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!

Monday, November 26, 2007

JavaScript String :: Primitive or Object?

A colleague and I were chatting about JavaScript strings and he noted that they're not objects. He's half right.

JavaScript strings are primitives much like numbers and booleans, but they can also be objects ( like Numbers and Booleans ).

You can create a String object ( I've capitalized String ) by calling it's constructor --

var aStr = new String("I'm a String object");

You can also create a string like this without calling the constructor ( that is without the "new" operator ) --

var aStr = String("I'm a string primitive");

This is the same as calling --

var aStr = "I'm a string primitive";

which results in a string primitive or as I typically call it, a string constant or a string literal.

Typically, you don't have to worry about whether you're dealing with a string primitive or a String object because JavaScript automatically converts the primitive to a String object.

So, you can do things like getting the length of a string primitive --

var myStr = "I'm a string primitive";
alert(myStr.length);

or calling methods --

alert("string primitive".substring(0,7));

All this means is that you can access any property found in the String object from a primitive.

The difference between a primitive and a String object is when you're adding functionality to a String. You can't do it to primitives.

So, if you wanted to add a function to reverse the characters in a string, you'd do something like this by adding it to the String object's prototype property --

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

If you called it,

"cola".reverseString();

it's the same as

var cola = new String("cola");
cola.reverseString();

Though you've added the method to the String object, you can use it with a primitive!

Note that JavaScript strings are immutable ( you can't change them ). This is why the method reverseString returns a new string from the array.

All of this is pretty obvious, but I thought I'd share my thoughts.
Enjoy!

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!

Thursday, June 07, 2007

Bubble Dialogs for the Summer

It's summer and it's time to play with bubbles -- bubble dialogs. Here they are in action.



Derived from the Bindows BiComponent, they're totally reusable.

Download the bubble dialog here. Learn how to use the bubble dialog here by studying the test driver.

Use as you please.

Have fun!

Friday, May 25, 2007

Bindows Wonderful dispose() Method

Almost anything useful you do with Bindows requires you to add components to other components. This is because you build visual components by creating parent child relationships.

So, if you wanted to build a custom dialog, MyDialog, you might start off with a BiComponent and use that as a container. Then add a BiToolBar to the BiComponent. Of course, you'll need toolbar buttons and menus so you add BiToolBarButtons and BiToolBarMenuButtons to the BiToolBar, etc.

When you're done, you add the BiComponent
container to the BiApplicationWindow.

We've effectively created a hierarchy where every component with the exception of the BiApplicationWindow is a child to another.

One reason why this is significant is when you dispose MyDialog.

All derivatives of BiComponents have access to a dispose() method. It may come directly from the component ( for example BiLabel has its own dispose method ) or directly from BiComponent or from BiObject ( read about dispose() ). As a front-end developer, you don't really care. You just have to remember to call it when you want to mark MyDialog for garbage collection.

So, calling dispose() traverses the children, removes their properties and sets them to null ( actually BiComponent's dispose() calls another method, disposeFields() which makes this happen, but it's all nicely abstracted for you ).

In cases where you only have a hierarchical relationship between your components, you don't need to implement dispose(). This is cool because all you do is call dispose() and the world is your oyster. Most of the time this is all you need to do.

However, if you create new objects in a custom component like MyDialog ( perhaps, we have an array of strings in MyDialog that we're using to temporarily store information ), then we'll need to implement our own dispose().

If we do, we need to make sure that we --

  • Call dispose() in the superclass ( for MyDialog, the superclass is BiComponent and can be done like this - BiComponent.prototype.dispose.call(this) ); this effectively propagates the disposal up the prototype chain
  • Call disposeFields(); disposeFields accepts a variable number of string arguments where each string is a property name; calling disposeFields removes ( i.e. deletes ) the property and disposes them if it's a Bindows objects; you can read about it here.
That's it. dispose() is pretty wonderful.
Have fun!

Saturday, May 19, 2007

Simple Fun Faders

If you're using pre-Bindows 3.0 Beta, here's a simple way to do component fading in Bindows.

When Bindows 3.0 is released, it'll do a lot more including resize and relocation animations. Hopefully, this little fader will wet your appetite for that release.

For a preview of the animation capabilities found in Bindows 3.0, check out my earlier writeup.

Here's an example of what the fader object, MyFader, can do. We're cycling through a six photo collection of 1/24 model cars from Tamiya --




Fading is achieved by dynamically changing a component's opacity.
One of the great things about using a toolkit like Bindows is that you don't have to worry about browser specifics like setting filter ( IE ) or -moz-opacity ( Firefox ).

All of that is abstracted for you.
With Bindows, this is easy to do by repeatedly calling a BiComponent's setOpacity() method.

You basically setup a timer and then at intervals call setOpacity() incrementing by 0.1 until you reach 1 ( "fade in" ) or decrementing by 0.1 until you reach 0 ( "fade out" ).

There's not much coding behind it. Here's what the reusable object, MyFader looks like --



MyFader isn't a component. It doesn't need to be because MyFader doesn't need to be rendered, but it does need to dispatch events.

So, we make it a BiEventTarget. When a fade starts, MyFader dispatches the "fadestart" event and when it ends, MyFader dispatches the "fadeend" event.

Use these two events to coordinate fading. This is what we're doing when we're swapping the six model cars. You can view that source here.

Not all fading is aesthetic. We can also use fading to provide visual feedback. Here's an example of a cascading menu with fade-ins when a list is updated. To see it in action, select an item --



Note that the cascading menus are lists and each item is a BiListItem, but we don't fade each item. Instead we fade the entire list. It's always better to animate the container/parent rather than each child ( much like event delegation where you handle events at the parent level ).

It's much easier to coordinate by fading one component rather than many. JavaScript after all uses a single threaded model.

You can get all the code here including the MyFader object.

To see the model car example, go here.
The cascading list example is here.

Have fun!

Sunday, May 06, 2007

On-demand JavaScript :: IE6/7 and Firefox 2.x

Last year, there was a lot of talk about on-demand JavaScripting. A good bit is found on Michael Mahemoff's Ajax Patterns Wiki. If you've never heard of it, you can read Michael's primer here.

Basically, on-demand JavaScripting helps you lazy load objects. When you need it, load it. It's simple.



You can run it here.

When you click on the "Load JavaScript" button, we're dynamically loading a JavaScript library by creating a script tag and then setting the src.

Then, the library loads and all the JavaScript is implicitly evaluated ( no eval is called ). You'll see an alert message appear saying that you've successfully loaded the JavaScript.

Close it and then the "Call method from new load" button is enabled. Click it and you'll see another alert message showing you the current page's HTML.

The whole exercise is to demonstrate that --

  • You can load JavaScript dynamically by creating a script tag and then setting src
  • You don't need to "eval" it

In fact, you can use the technique to "hack around" cross-domain issues and avoid using a proxy altogether. You can load JavaScript from any domain.

Though we can have lively debates on whether that's a good or bad thing, we're more interested in how the JavaScript was loaded.

From all the on-demand JavaScript examples that I've seen, they involve loading an external library, but what if we don't have an external library?

What if we just want to modify the body of the script tag to include new JavaScript? We might be loading the JavaScript from an iframe or some other external source and then slap it between script tags.

As it turns out, we can load JavaScript this way too, but we'll have to do it a specific way or it won't work in IE6 or IE7 ( I didn't try it for earlier versions of IE ) and like using "src", you don't need to explicitly evaluate the JavaScript.

Naturally, you might think that this works ( especially, since there's a general agreement that using innerHTML is the best and fastest way to dynamically update HTML ) --



Here's the example.

Try it in Firefox 2.x and it'll work. Then, try it in IE. We'll get the fabulously terse error message, "Unknown runtime error."

So, we try something along the same lines, but slightly different. Instead of updating with innerHTML, we'll create a text node using the string of JavaScript and then append that node as a child of the script element.



Here's the example.

IE chokes again, but this time with a bit more detail, "Unexpected call to method or property access". Sigh. It's beginning to look like one of those "many hours to solve" IE problems.

Fortunately, after a few more efforts, there's a solution. We'll use the "text" property found in the script node. Apparently, this is a Microsoft extension to the W3C DOM which works in Firefox 2.x. The best reference that I could find was here.



Here's the example that works in IE6/7 and Firefox 2.x.

We're done!

By the way, it's stuff like this that make using a JavaScript toolkit like Bindows worthwhile.

Have fun!