Opera mobile window chooser, recreated with jQuery

November 18, 2010

It’s always fun to play with jQuery especially when you are trying to recreate something that you enjoy using. One such thing is window chooser in Opera Mobile browser where opened windows are positioned below each other and only a portion of each window is visible. The one on the top is current window that is fully visible. Clicking on those windows is followed by some nice effect.

View demo

Our “windows” are nothing more than simple unordered list. Each LI element can have any content, as you can see in the demo. Initially the first LI element will be “the chosen window”.

<ul id="container">
    <li class="current"></li>
    <li></li>
    <li></li>
    <li></li>
</ul>

On the very beginning we define how much each window will be hidden and how much it will slide to the left and right. Next, we assign ID to each window that will be needed later in the process and position them below each other.

var itemWidth = $("#container li").width();
// hide 50% of each window
var itemPosition = itemWidth * 50 / 100;
// slide each window 60% if its width    
var itemMove = itemWidth * 60 / 100;        

// move windows below each other and assign ID to each
$("#container li").each(function(i) {
    $(this).attr("id", i).css("z-index", 100 - i).css("left", itemPosition * i);
});

Now the main part. When user clicks on a window, several things should happen. First we determine the position of a clicked window and the previsouly chosen one. If clicked window is on the left side of the chosen window, all windows before the clicked one will slide to the left. Analogously, if clicked one is on the right side of the chosen window, all windows after the clicked one will slide to the right.

$("#container li").click(function(e) {
    // Get id's of the current window and the one that user clicked on
    var currentID = parseInt($(".current").attr("id"));
    var clickedID = parseInt($(this).attr("id"));

    if (currentID != clickedID) {
        if (clickedID > currentID) {
            // slide windows to the left
        }
        else {
            // slide windows to the right
        }
    }
});

Here’s how we slide windows to the right. We take the ID of the clicked window (and add 1 to prevent clicked window to slide) and loop through the collection of windows to the end. We then first slide each windows to the right, move them behind clicked window (with z-index) and slide them back to the left.

var i = 1;
var total = $("#container li").length;
for (j = clickedID + 1; j <= total; j = j + 1) {
    $("#" + j).animate({ "left": "+=" + itemMove * i + "px" }, 500);
    $("#" + j).animate({ "left": "-=" + itemMove * i + "px" }, 300);
    $("#" + j).css("z-index", currentZ - i);
    i = i + 1;
}

Believe it or not, left sliding is more complex than right sliding. The reason for this is in the fact that windows will be sent to the back immediately upon clicking, so we need to postpone changing z-index for sliding windows to the moment when they are on the leftmost position. Then we can slide them back. We do this in the loop that will iterate from the first window one the left of the clicked one, all to the fist window in the collection.

var i = 1;
var total = clickedID - currentID + 1;
for (j = clickedID - 1; j >= 0; j = j - 1) {
    $("#" + j).animate({ "left": "-=" + itemMove * (i) + "px" }, 500);
    $("#" + j).animate({ "left": "+=" + itemMove * (i) + "px" }, 300);
    i = i + 1;
}
var i = 1;
setTimeout(function() {
    for (j = clickedID - 1; j >= 0; j = j - 1) {
        $("#" + j).css("z-index", total - i);
        i = i + 1;
    }
}, 500);

Now, before we slide windows, we need to remove class .current from the old “chosen one” and assign it to the clicked window. We’ll also postpone this to the moment when windows reach their leftmost/rightmost positions.

var currentZ = 99;
var current = $(this);
setTimeout(function() { $(".current").removeClass("current"); current.css("z-index", currentZ).addClass("current"); }, 500);

At the end, we have one issue here. If windows contain links user might think they can click on the link even ehen they are semi-hidden. In order to fix this issue we prevent default click action by adding e.preventDefault() just below the if statement.

if (currentID != clickedID) {
    e.preventDefault();
    ...

We can also style links as normal text so that there’s no doubt if there’s anything clickable in semi-hidden windows. This way users can access the content in chosen window only.

View demo

I am somehow sure that this can be optimized so if you know the way, shoot it! If I find some free time (which is unlikely) I will create a plugin. Above all it was fun to play with jQuery after such a long time.

Let's discuss this on twitter.

10 Comments

  • Jack (November 19, 2010)

    Very nice. It is a little bit different on Opera Mobile, though (at least for Android) :)

  • Janko (November 19, 2010)

    True. It slides and moves back only those windows that are "in front" of the clicked one. I simplified it here by sliding the entire left/right side.

  • Kevin (November 19, 2010)

    Android apps are on the rise….anyways….nice to see the slides… Lovin it :P

  • Horia Dragomir (November 20, 2010)

    Hey,

    Pluing or not, it’s cool!
    What are the odds of my using it in an app?

    Also, githib?

  • Peter (November 22, 2010)

    There is a minor bug (at least in the demo, on Firefox 3.6); when the last (right-most) slide is active (top-most), and then clicking on the first (left-most) slide, all slides FIRST change order before sliding out. While they should first slide out before changing order…

  • Wiredworx (November 22, 2010)

    Very nice! The issue stated above doesn’t affect Chrome (7) it all seems to be working great!

  • Peter (November 28, 2010)

    I see the bug also in Chrome (tested in v6 and v8). However, my report was not totally accurate.

    Say the four windows are named A, B, C and D. At startup, the top-to-bottom order is A-B-C-D. Clicking on D makes the windows slide out, and then slide in again in the order D-C-B-A. Now, clicking A again, the order is first changed into B-C-D-A for a fraction of a second, before the windows slide out and slide in again as A-B-C-D.

  • Marco (December 1, 2010)

    Looks very good Janko, well done!

    Only, I would not do this:
    $(this).attr("id", i)
    If you have an "ID" set inside your HTML, this will be overwritten by a number.

    Go with:
    $(this).data("windowHandle", i)
    for things like this :) .

    Also great to see yet another great article from you, after a while! Keep it up!

  • Janko (December 1, 2010)

    Good point Marco, thanks for the suggestion!

  • ted (December 12, 2010)

    Hello Janko!

    Sorry for posting on a different topic but comments are no longer accepted at

    Justify elements using jQuery and CSS
    http://www.jankoatwarpspeed.com/post/2008/07/09/Justify-elements-using-jQuery-and-CSS.aspx

    Thing is your CSS works only if every input is wrapped in a <fieldset>. But if I want to place several inputs in one <fieldset> it breakes. What could be the solution? Thanks!

    For your convenience I combined everything into one page:

    [quote]
    <html>
    <head>
    <script type="text/javascript" src="jquery.js"></script>

    <script type="text/javascript">
    $(document).ready(function() {
    var max = 0;
    $("label").each(function(){
    if ($(this).width() > max)
    max = $(this).width();
    });
    $("label").width(max);
    });
    </script>

    <style>
    label, input[type="text"]{
    float:left;
    display:block;
    }
    label
    {
    margin-right: 5px;
    }
    .field{
    width:100%;
    overflow:auto;
    margin:5px 0px;
    }
    </style>

    </head>

    <body>

    <form>
    <fieldset>

    <label for="txtName" class="fieldLabel">Name</label>
    <input type="text" ID="txtName" class="fieldInput" />

    <label for="txtNameSecond" class="fieldLabel">Name Second</label>
    <input type="text" ID="txtNameSecond" class="fieldInput" />

    </fieldset>
    </form>

    </body>
    </html>
    [/quote]