> Spinning wheel on webkit for iPhone/iPod touch

Slot Machine

I’m more and more amazed by the power of mobile webkit. I don’t love the iPhone that much, but as a web developer the beauty of CSS transitions, animations and transforms can’t pass unnoticed. This time I’m giving away a widget that resembles the native Picker View (UIPickerView) functionality but entirely built on javascript.

First things first. Have a look at the demo page or, if you don’t have your device at hand, watch the screencast I baked for you.

Please note that the script is in beta phase, I am publishing it to receive feedback and suggestions. In the next few days we should have a stable version.

Update 2009/03/16: We are out of beta, the script is now stable and ready for production (hope so :) ). In the zip I also included a minified (9kb) version of the script to save some bits of precious bandwidth.

Update 2009/04/19: the script is now compatible with full screen mode apps. It seems that in full screen mode preventDefault() and stopPropagation() placed in touchStart event are not enough to block the propagation of the touch event. Adding preventDefault() to touchMove event solved the problem.

Update 2009/06/18: the script has been updated for OS3.0 compatibility.

Update 2009/07/09: it turned out that Apple is well aware of the webkit refresh bug, fortunately they are kind enough to offer a workaround. All we have to do is to use translate3d instead of translate and all our animations will be fluid as never before! Go get the latest version of the script!

How to use the script

The widget is composed of two parts: the stylesheet and the javascript. No HTML is needed as all the elements are created by the script on the fly. Include both the JS and the CSS into your page and you are ready to spin. You’ll be also surprised to see that the spinning wheel itself is built with just two images, while other three images are needed for the header and buttons. The PNGs altogether are 4.3kb.

The code does not need initialization on window load. You cannot have more than one picker at a time, so the SpinningWheel object is unique and it is created as soon as you include the JS file.

The first thing you need to do is to define the slots with:

SpinningWheel.addSlot(obj values, str styles, str defaultValue)

values is in the form of: { key: value, key2: value, ... }. Keys are the identifiers that won’t be shown in the picker (think of them as the value parameter in the <option value="foo">bar</option> tag). Values are the labels printed on the slots.
styles is a list of space separated predefined styles to be applied to the slot. The available values are:

  • right, align text inside the slot to the right;
  • readonly, the slot can’t be spun;
  • shrink, shrink the slot width to the minimum possible.

The first element of the slot will be selected if no defaultValue is defined.

When all the slots have been created, set the default actions for the cancel and done buttons.

SpinningWheel.setCancelAction( function(){ } );
SpinningWheel.setDoneAction( function() { } );

Finally show the picker:

SpinningWheel.open();

Voila, the Picker View is ready for countless hours of spinning pleasure.

To get the actual selected values call:

var result = SpinningWheel.getSelectedValues();

result.keys will be filled with an array of the selected keys while result.values will hold the list of the selected values (or labels).

Let’s wrap everything together.

function swExample() {
	var numbers = { 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9 };
	SpinningWheel.addSlot(numbers, 'right');
	SpinningWheel.addSlot(numbers, 'right');
	SpinningWheel.addSlot({ separator: '.' }, 'readonly shrink');
	SpinningWheel.addSlot(numbers, 'right');
	SpinningWheel.addSlot({ Kg: 'Kg', Lb: 'Lb', St: 'St' }, 'shrink');

	SpinningWheel.setCancelAction(cancel);
	SpinningWheel.setDoneAction(done);

	SpinningWheel.open();
}

function done() {
	var results = SpinningWheel.getSelectedValues();
	alert('values:' + results.values.join(', ') + ' - keys: ' + results.keys.join(', '));
}

function cancel() {
	alert('cancelled!');
}

Look at the demo for more examples.

Create custom styles

I preconfigured for you three styles for the slots, but you can add as many as you need. Say you want a slot with center aligned text. Add the following to the stylesheet:

#sw-slots .sw-center { text-align:center; }

To apply the style create the slot like this:

SpinningWheel.addSlot(values, 'center');

A piece of cake.

By default the slots try to fit their content. Slots with long text will be wider than ones with short text. (Same as the <table /> cell elements). With custom style you can override this behavior. If you have two slots and you want them to be exactly the same width you may add the following style:

#sw-slots .sw-50percent { width:50%; }

and create the slots with:

SpinningWheel.addSlot(values, '50percent');
SpinningWheel.addSlot(values);

You don’t need to apply the style to both slots as the second will fit the remaining space (or the other 50% of the screen width).

Limitations

None that I can tell if not those imposed by the device small CPU. All animations are hardware accelerated, the “birth date” example in the demo creates more than one hundred elements and all animations are pretty fluid.

The script is also compatible with both landscape and portrait mode and you can freely switch from one to the other while the spinning wheel is opened. (That is more than what the native Picker View has to offer).

Note that once closed the spinning wheel is completely inaccessible and all variables will be null or undefined. So basically you can’t programmatically query the SpinningWheel object while it is not visible.

I’m now working on code optimization to reduce memory usage, I hope to release a stable version as soon as possible. The code is now stable, please leave your comments and suggestions.

As always released under MIT license for all your coding needs.

Download the script

/Share the joy

/Reactions

  • I’ve searched for this!

    It’s exactly what I need!

    I’m gonna include on my project Sencha Touch + PhoneGap.

    Cheers

    • Author: Nathaniel
    • Posted on: 2012/05/17
    • At: 19:04

    Any way to get this to take images? Like small icons on one spinner?

    Thanks! This is awesome!

    • Author: Raman
    • Posted on: 2012/06/13
    • At: 12:00

    Does it work the same was on android?

    • Using PhoneGap build on the example, it works well on my Samsung Galaxy Note

    • Author: Janaka
    • Posted on: 2012/06/14
    • At: 15:22

    HI Matteo,
    I want to disable selecting dates beyond today. How can we do that. Assume I want to disallow user selecting 2012 Dec. 12. (Today is 2012 June 14).
    Thanks in advance.

  • @Janaka: look at my blog. I have posted a modified spinningwheel that allows you to specify dates you want to block.

    • Author: destiny
    • Posted on: 2012/06/19
    • At: 05:32

    When i open the spinningwheel,i can’t find the “cancel” and “done” text,and the button is so small,could some one tell me why,please.

    • Author: Philip
    • Posted on: 2012/07/04
    • At: 03:42

    Why January appears as Gen? How can I fix this?

      • Author: spoulin23
      • Posted on: 2012/07/04
      • At: 19:27

      @Philip : can’t you just read the code before asking questions ?

      This code is totally awesome. Thanks for this!

    • @Philip: are you asking about the code I posted on my blog?

    • Author: Shayne
    • Posted on: 2012/07/23
    • At: 22:39

    I’m trying to implement the spinning wheel phone gap app with the UI designed using codiqa and I’m having some problems. The first being that the text in the spinning wheel is white like the rest of the text in the app and the second is that the done and cancel buttons give me the error “Cannot set property ‘innerHTML’ of null at file :///android_asset/www/codiqaAppName.html:58″
    How do I fix these?

      • Author: Shayne
      • Posted on: 2012/07/24
      • At: 19:26

      I’ve found away around the font colours, but would still appreciate help with the cancel and done button errors.

        • Author: Shayne
        • Posted on: 2012/07/25
        • At: 00:53

        Fixed everything… Disregard this post.

    • Author: Alwaysdeadfred
    • Posted on: 2012/08/08
    • At: 23:04

    I have a problem running this on a galaxy tab. Go to http://stackoverflow.com/questions/11872461/spinning-wheel-only-half-works-on-galaxy-tab for full information. Please help.

    • Author: Ron S.
    • Posted on: 2012/08/26
    • At: 16:04

    Hi Matteo,

    I see quite a few people have taken your code and developed with it, as have I. Thank you for giving your knowledge to the community. I’ve used this control in my own project at work and hope some of the work I’ve done will also be useful. Here’s what I’ve done:

    - Added mouse events.
    - Added the ability to tap to select an item within a slot.
    - Refactored and created two new methods:
    resetSlots – Removes all slots from the DOM and resets the slotData.
    createSlots – Adds the slots to the DOM and sets the defaultValue. This was simply moved from the create method into a new method.
    - Added an onchange event, which will only fire if data for any of the slots have changed.

    Happy coding all!

      • Author: Kris
      • Posted on: 2012/09/10
      • At: 04:26

      Hi Ron,
      Is it possible to get a copy of your changes via public download/website?

      Thanks

        • Author: Ron S.
        • Posted on: 2012/09/11
        • At: 06:33

        Hi Kris,

        Absolutely, just click on my name above and it will take you to a blog entry that has links to a demo and the modified source. Hope it saves you a little time but of course all credit to Matteo for the excellent code!

    • Author: Julio Rivera
    • Posted on: 2012/09/12
    • At: 22:54

    Hi, I am having an issue on Android 4.0 where if I am in a input and the keyboard is up then I hit a DIV where I have the spinning wheel to be opened, it appends the Spinning wheel ontop of the keyboard as the keyboard slides down so it ends up being halfway on the browsers window. Anyone experience this or have a fix? Thanks!

      • Author: Ron S.
      • Posted on: 2012/09/13
      • At: 16:52

      Try making the input readonly, i.e.:

      Don’t have an Android to test it out, though :-). The other option is to instead of using an input, use a div.

      • Author: Ron S.
      • Posted on: 2012/09/13
      • At: 16:53

      Sorry:

      <input id=”input-date” name=”input-date” type=”text” readonly />

    • Author: ralph
    • Posted on: 2012/09/17
    • At: 21:55

    Hi, great plugin!!!
    If there is an “addSlot” Method, is it possible to delete specific slots and re add new slots while spinning weel is open?
    the idea is for example by selecting a value in slot 1 to change the slots and values on the fly in slot 2. anybody an idea?

    • Author: Sherif elalfy
    • Posted on: 2012/09/20
    • At: 10:27

    i Had an UI issue in spinning wheel when working on iOS 6 ,can anyone check this

      • Author: Ron S.
      • Posted on: 2012/09/22
      • At: 03:37

      I think iOS6 removed all mouse events. Make sure you also add touchend to your code that shows the SpinningWheel control.

    • Author: nirav
    • Posted on: 2012/09/24
    • At: 12:02

    Hi , i have problem loading this “index.html” page. I have loaded it in UIWebview but it is not displaying me date picker instead it is displayin me that index.html in Webview.I can’t find any way how to load that html page in webview and display date picker instead that html page.
    Thanks

    • Author: Jon Perl
    • Posted on: 2012/10/02
    • At: 19:06

    Is this the official GitHub?

    • Author: Kevin
    • Posted on: 2012/10/21
    • At: 21:38

    Hi Matteo, on iOS6 devices there are some css problems. While scrolling the wheel the text reach over the blue borders. Kevin

      • Author: Peter Roos
      • Posted on: 2012/10/22
      • At: 13:42

      Same here with IO 6, list items are no longer clipped by the #sw-slots or #sw-slots-wrapper element(s).
      I read somewhere that this may have to do with hardware acceleration, which seems to invalidate the z-index stacking.

      • Author: Nono
      • Posted on: 2012/10/23
      • At: 23:08

      My solution was to add:
      -webkit-transform: translateZ(0);

      to the
      #sw-frame {

      -webkit-transform: translateZ(0);
      }

      prop in spinning wheel css file.
      This forces the rendering engine to apply 3d hardware acceleration from the get go to all the spinner elements.
      Working properly under older IOS, Android and Playbook.

        • Author: Kevin
        • Posted on: 2012/10/25
        • At: 20:22

        yeah this works – thanks a lot