Menu

Big Lists in Small Spaces

May 4, 2005

Fabio Arciniegas A.

Many interfaces on the web are just lists of elements. You click one, and you are taken to a subsequent "card" page with the properties of the item. Simple enough, right? Well...not always. While this approach works for web pages on your 21-inch monitor, it is less effective on a cell phone, where both navigation and real estate are more limited.

In this article we explore an alternative SVG-based interface for displaying summary lists and detail pages.

The Goal

Our goal is to combine the context provided by summary lists with the detail provided by a "properties" view. The main issues we will have to tackle are screen size and limited navigation. In other words, our goal is to be able to read a UBL invoice in a small cell phone screen, while we have one hand on the wheel.

Important Considerations

Some important considerations that influenced the design are worth mentioning:

  • Invoices and other documents have deeply-nested representations on XML, but as humans we don't need to think of them (or represent them) deeply nested.

  • Numbered lists and breadcrumbs are the bread and butter of context-keeping in web interfaces, but they need not be the only way. A particularly good mechanism for keeping a sense of position within the list is to keep collapsed titles of items before and after the "expanded" item. That is the essential mechanism we use in this article.

  • While you can just point and click on a web list, you have to sequentially go through the items on your cell phone before you get to the one you want. This emphasizes the need to be able to get back to the original point after you explore an item. Coming back just to the list (as we do on the web) is not acceptable.

  • Finally, a disclaimer: In this article I concentrate on explaining the SVG and JavaScript of the interface, assuming we already have blocks of SVG text with the right data to display. We will not deal with where the data comes from now; maybe we'll do that in the next article.

Arranging and Rotating Items

The way we achieve the effect of items moving up and down through an arc is by placing them around a circle. We control the effect by varying the diameter and center of the circle as well as the separation angle between items.

circle
Figure 4. Impact of diameter/position/separation angle

The code for moving an item around in a circle with JavaScript is conceptually simple:

  • Get the element using getElementByID

  • Calculate the next intermediate position by modifying the angle at which the item is standing

  • Translate the object to the next intermediate position

  • Sleep for a while and repeat the process until the element reaches the final desired angle

The actual code is a little more complicated because of JavaScript idosyncrasies, such as having to implement the wait with a recursive call to setTimeOut(circle(),10) instead of the more Java-like Thread.sleep(10);. In any case, the code (Listing 1) is simple enough.

// Get the SVG element to rotate around

summaries[0] = root.getElementById("summary_1");



var angle = 0;



circle(360);



function circle (degreesLeft) 

{

    if (degreesLeft-- == 0) 

	return;



   angle += 1; 

   var matrix = target.getCTM();

   offset.x = center.x + radius*Math.cos(angle*Math.PI/180);

   offset.y = center.y + radius*Math.sin(angle*Math.PI/180);

   target.setAttribute('transform', 

	  	      'translate(' + offset.x + ',' + offset.y + ')');

   window.setTimeout("circle("+ --iterations +")",10);

}

Listing 1. Rotate around a circle

By the way, in this article I'm sticking to JavaScript for the animations, but they can certainly be done declaratively with SVG. An example of how to do an equivalent rotation declaratively can be found on the color wheel example of my SVG and Type article.

Moving Items Up and Down

To move items up and down, we just have to loop through each one of them, changing their angles. The next version of our circle function looks like Listing 2. Note that we've added a clockwise argument to control the direction of the spin.

function circle(iterations,clockwise) 

{

    if (iterations == 0)

	return;



    angle = angle + (clockwise ? stepSize : -stepSize);



    for(var i=0; i <= 5; i++)

    {

	var matrix = summaries[i].getCTM();

	var itemAngle = angle + separation*i;

offset.x =center.x + radius*Math.cos(itemAngle*Math.PI/180);

offset.y =center.y + radius*Math.sin(itemAngle*Math.PI/180);



	moveSVGItemTo(summaries[i],offset.x,offset.y);

    }

    window.setTimeout("circle("+ --iterations +","+

		      clockwise+")",10);

}

Listing 2. Rotate all items

Gathering Elements

As you may have noticed in Listing 2, we are now getting the SVG elements from an array called summaries. This array depends on the SVG document that contains elements like Listing 3:

<g id="summary_0">

  <text x="0" y="0">

    <tspan style="fill:#888888;">Invoice #</tspan>

    00645</text>

</g>

<g id="summary_1">

  <text x="0" y="0">

    <tspan style="fill:#888888;">Buyer</tspan>

  Samuel Fisher</text>

</g>

<g id="summary_2">

  <text x="0" y="0">

   <tspan style="fill:#888888;">Seller</tspan>

  HBO Store</text>

</g>

<g id="summary_3">

  <text x="0" y="0">

    <tspan style="fill:#888888;">Transport</tspan>

UPS Ground</text>

</g>

Listing 3. SVG declaration for summaries

Here is what we do to populate the summaries array:

var listSize  = 10;

var summaries = new Array(listSize);

var contents  = new Array(listSize);



for(i=0;i < listSize;i++)

{

  summaries[i] = root.getElementById("summary_"+i);

  window.status = i;

  contents[i]  = root.getElementById("content_"+i);

}

Listing 4. Gathering all elements

Expanding and Collapsing

To expand an item we need, first, to make room for it by rotating up all of items before it by 30 degrees, or whatever our desired degree of separation is, and rotating all of the items after it down.

We can easily achieve this by adding two more parameters to the circle function, telling it the range of items to rotate. Then we can make two calls to achieve the effect:

circle(30,            // angle

          0,          // index in summaries[] of the first

                      // element to move

          itemIndex-1,// index in summaries[] of the last

                      // element to move

          false       // counter-clockwise

          );



   circle(30,itemIndex+1,listSize-1,true);

Listing 5. Rotate all items

Showing the Expanded Item

To show the expanded item, we just set its visibility to "visible" while hiding the summary. Since we are doing this from JavaScript, the syntax can be a little inelegant, but it's still understandable:

 function moveSVGItemTo(element,x,y)

{

   element.setAttribute("transform", 

                          "translate(" + x + "," + y + ")");

}



function SVGItemVisible(element,visible)

{

   element.setAttribute("style", visible ?

                "visibility:visible" : "visibility:hidden");

}

 

Listing 6. Altering the attributes of an element from JavaScript

Polishing and Refactoring

At this point we have all the building blocks of our strategy: we can rotate items up and down, we can expand and collapse, and we can show/hide the elements. From here on, the changes in the code (Listing 7) are mainly checks on boundaries, and a little polishing with helper functions. You can see the complete code for the SVG here and the complete code for the JavaScript here.

function oneClickDown()

{

    if(centralItem == lastItem)

	return;



    var wasExpanded = expanded;

    collapse();



    rotateItems(0,lastItem,false);	



    startAngle -= separation;

    centralItem++;

}





function rotateItems(start,end,clockwise)

{

    circle(separation/stepSize, start, end, clockwise,

                                            startAngle, 10);

}





function expand()

{

    if(expanded)

	return;



    if(centralItem > 0)

      circle(separation/stepSize, 0, centralItem-1, false,

                                 startAngle-separation, 10);

								 

    if(centralItem < lastItem)

      circle(separation/stepSize, centralItem+1,lastItem,

                           true, startAngle+separation, 10);



    SVGItemVisible(summaries[centralItem],false);

    SVGItemVisible(contents[centralItem],true);



    expanded = true;

}





function circle(iterations,startIndex,endIndex,clockwise,

                                              angle,timeout) 

{

    if (iterations == 0)

    {

	working = false;

	return;

    }



    working = true;

    angle = angle + (clockwise ? stepSize : -stepSize);



    for(var i=startIndex; i <= endIndex; i++)

    {

	var matrix = summaries[i].getCTM();

	var itemAngle = angle + separation*i;

	offset.x= center.x+radius*Math.cos(itemAngle*Math.PI/180);

  offset.y= center.y+radius*Math.sin(itemAngle*Math.PI/180);



	moveSVGItemTo(summaries[i],offset.x,offset.y);

    }

    window.setTimeout("circle("+ --iterations +","+

	              startIndex+","+

		      endIndex+","+

		      clockwise+","+	

		      angle+","+

		      timeout+")",timeout);

}

Listing 7. Final versions of circle and helper functions

Finally, we have an interesting result where detail and context can coexist in a small space.

The (Bittersweet) Icing on the Cake

The ideal icing on the cake would be mapping the keys on the numeric keyboard so we capture the event (note that this only works if the mouse is over the SVG window,) and simulate the whole phone experience instead of doing point-and-click on the "phone keys."

The problem is that the keys on the numeric keyboard are "flipped" compared with those of a phone. So, I will provide the code (Listing 8) but you will have to provide the suspension of disbelief and press 8 while thinking you are pressing 2.

 function getKey(evt) 

{

   var keyCode = evt.getCharCode();

   switch(keyCode)

   {

      case 56:

        oneClickUp();

	break;

      case 50:

        oneClickDown();

	break;

      case 52:

        collapse();

	break;

      case 54:

        expand();

	break;

   }

   var keyString=String.fromCharCode(keyCode).toLowerCase();

   window.status = keyCode;

}

Listing 8. Altering the attributes of an element from JavaScript

Also in Sacré SVG

SVG At the Movies

Mobile SVG

SVG and Typography: Animation

Going Mobile With SVG: Standards

SVG and Typography: Bells and Whistles