> 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

    • Author: Viji
    • Posted on: 2010/01/20
    • At: 21:34

    Hi ska_iit:
    Could you please send me the code modification you did for it to work with iWebKit?

    Reply
    • Author: Michael
    • Posted on: 2010/01/22
    • At: 22:52

    http://stackoverflow.com/questions/2040042/javascript-array-iteration-using-for-in-with-mootools-included
    Since I also use mootools, I had to use this fix in order for the “getSelectedValues” portion of the code to work (run after you click ‘done’).

    Reply
    • Author: i o
    • Posted on: 2010/02/11
    • At: 09:29

    looks very good. Comparing with the native picker, its lacking to loop a certain slot option. (Like hours going from 1-12 and continuing back to 1 in the same slot)
    Do you plan to add this in the near future? Thanks again.

    Reply
    • Author: Mark
    • Posted on: 2010/02/12
    • At: 18:04

    I thinks this is great, I would like to know how to use it for multiple date elements on the same html form without repeating the code for each date element.

    Thanks

    Reply
      • Author: Lee
      • Posted on: 2010/11/22
      • At: 11:19

      What I did Mark was to set some vars up to elements I wanted the data to go to. Then within the click event of the function i set the element names.

      So within the done function I could easy set where i wanted the data to go ie,
      var elementDate = ‘checkindate’;

      function checkoutDate()
      {
      elementDate = ‘the_element_id’;
      spinningWheel……………..
      SpinningWheel.open();
      }

      function done()
      {
      var results = SpinningWheel.getSelectedValues();
      $(“#”+elementDate).val(results.keys[0] + ‘/’ + results.keys[1] + ‘/’ + results.keys[2]);
      }

      Hope this helps someone

      Reply
    • Author: philipp
    • Posted on: 2010/02/13
    • At: 02:15

    if i use it inside -tags it the wheel-div disappears shortly after the beginning of the animation. am i missing someting?

    thanks for your help!

    Reply
    • Author: mike
    • Posted on: 2010/02/14
    • At: 14:07

    @Brian S.

    Did you find a solution for suppressing the keyboard on text input?
    My workaround is to use a DIV with an event handler showing the formatted date and a hidden input holding the date value for form submission.

    Reply
    • Author: Mark
    • Posted on: 2010/02/15
    • At: 10:03

    You can suppress the keyboard by setting the input tag to readonly

    Reply
    • Author: Christopher
    • Posted on: 2010/02/18
    • At: 14:54

    This looks like a great component, I was thrilled to find it!

    Is it possible to decide how many slots should be shown? As it is now it seems to be set to 5. In my case 3 would be more suitable.

    Is it possible to decide where on the screen the SW should be shown? As is it is now it’s always located at the bottom I think. In my case I would like it to be positioned higher up (in conjunction with a button the user will press).

    I’ve looked around a bit at the code but not yet managed to solve these two issues. Any pointers would be appreciated.

    Reply
    • Author: Davy
    • Posted on: 2010/02/19
    • At: 04:20

    I’m trying to get this code to work with iWebKit and have gotten it to work I think, but cannot figure out how to submit the result. Any help would be appreciated. davynixon at g mail.

    Reply
    • Author: Dan
    • Posted on: 2010/03/05
    • At: 16:14

    Ska_lit,
    Can you post or send the mod to make the date valid?
    or email me at danbodenstein at g mail dot com

    Reply
    • Author: ray
    • Posted on: 2010/03/06
    • At: 01:35

    hahahhahaaaaaaaaaaaa!!!
    just what i needed.
    wonderful.
    i love you.

    Reply
    • Author: Hans
    • Posted on: 2010/03/07
    • At: 20:15

    hi,
    same problem like #105:

    if i include it inside my form-tags, it disappears immediately after the click and the form “autosubmits”.

    any suggestions?

    björn

    Reply
  • I have a fix for android devices:

    change lines:
    30 this.tapDown to this.tapUp(e);

    comment out the following lines

    38 // this.tapCancel(e);
    44 // this.tapUp(e)

    The android devices are triggering a touchMove event after clicking on the done and cancel buttons. In the default implementation touchMove will cancel the action as it is meant to catch people who drag their finger off the done event .

    I haven’t tested this on iPhone yet but I imagine it will work as well.

    Reply
    • Author: Dan
    • Posted on: 2010/05/30
    • At: 20:09

    Would be nice to have a date picker demo for use in a form. Can anyone provide? Thanks!

    Reply
      • Author: Dan
      • Posted on: 2010/06/06
      • At: 20:22

      I’ve seen a few comments regarding this, so it seems like it can be done….can anyone help? I just want the results to be put into an input or some element the form will be able to send vs. a tag……Anybody?

      Reply
    • Author: Paul
    • Posted on: 2010/06/08
    • At: 10:36

    I have a HTC Desire and followed the information regarding Android mods in posts by Vivek 09/01/10 and your post here but the ‘Cancel’ or ‘Done’ buttons are VERY unresponsive the majority of the time only chance of getting them to work is to tap really quickly and firmly with top of finger tip.

    Can we have a Android 2.0+ fully working version?

    Cheers

    Reply
    • Author: HumbleGuy
    • Posted on: 2010/06/08
    • At: 14:16

    Hi all

    I am unable to implement this along with JQTouch. Does not give an error, but does not appear either. Though I am able to see the html generated using web inspector.

    Please help. If possible please upload the code where it is implemented with JQTouch

    Reply
      • Author: newbie
      • Posted on: 2010/12/20
      • At: 15:49

      Hi,
      did you find any solution for that problem? I also want to use it with jQTouch, but even if I copy and paste the example in my project, it does not work. How did you fix that? I´m stuck on it, please help…

      Thanks!

      Reply
      • Author: R'spindel
      • Posted on: 2011/01/14
      • At: 17:23

      spinningwheel wrapper div gets added as a child of body, so it litterally is a jQT page on its own when it is crated, not as a child of the current page, so, just navigate to it by using the jQT public object goTo function:

      SpinningWheel.open();
      jQT.goTo(“#sw-wrapper”);

      in your close/done events, you can then do

      close() { jQT.goBack(); }

      for example to navigate away from the spinner, also you can grab the usual jQT pageAnimationEnd and pageAnimationStart events by binding to the wrapper:

      $(“#sw-wrapper”).bind(“pageAnimationStart”, function() {} );

      If you want the spinner to appear within another page, then you can detach and append it with jquery

      mySpinner = $(“#sw-wrapper”).detach();
      $(“#mySpinnerPage”).append( mySpinner );

      Hope that helps!

      Reply
    • Author: RF
    • Posted on: 2010/06/11
    • At: 06:50

    I’m having a problem using the data from a SpinningWheel. It seems that whenever I call SpinningWheel.getSelectedValues() my JS stops processing. I can do whatever I want with the Cancel and Done actions, so long as I don’t try to see what I have selected.

    Reply
      • Author: spizza
      • Posted on: 2010/08/27
      • At: 03:01

      I am also having the same problem. I found that if I remove from the HTML file in the the following:
      …then the getSelectedValues() works.
      This is not a workaround however, as I need to include the parts.js

      Reply
      • Author: spizza
      • Posted on: 2010/08/27
      • At: 03:05

      I have the same problem. I narrowed it down to the reference to parts.js in the HTML file. if you remove the reference then the getSelectedValues works. So, something in parts.js is killing the script.

      Reply
  • The spinning wheel need a complete rewrite. I’ll work on it soon, promised.

    Reply
      • Author: Paul
      • Posted on: 2010/06/13
      • At: 15:03

      Hi,

      It would be absolutely fantastic if you can get the spinning wheel to work on the HTC Desire….somewhere I read you had one of these phones too.

      Look foward to the next release.

      Reply
      • Author: HumbleGuy
      • Posted on: 2010/08/15
      • At: 13:45

      Hi Matteo

      Any updates on the new version? I am eagerly waiting for this.

      Regards
      HumbleGuy

      Reply
    • Author: Dan
    • Posted on: 2010/06/11
    • At: 16:38

    I have a form with 4 text fields that need to capture a date. I got this script working when I click on the first text field it’ll bring up the spinning wheel, I select the date and it puts it back into my text field. How can I use the same function (I adapted the openBirthDate() function) to output in the 4 different text fields? Someone at JQTouch offered this solution, which makes sense, but after I bring up the spinning wheel, clicking Done does nothing.

    First change openBirthDate() to openDate()

    Then Add the variable to find which ID to associate it to. My example is:

    var $which_date = null;
    function openStartDate()
    {
    which_date = $(“#startdate”);
    openDate();
    }
    function openEndDate()
    {
    which_date = $(“#enddate”);
    openDate();
    }

    And then change function done() to:

    function done() {
    var results = SpinningWheel.getSelectedValues();
    $which_date.val(results.values.join(‘,’));
    }

    My text fields are:

    Can anyone help?

    Reply
      • Author: Dan
      • Posted on: 2010/06/11
      • At: 22:16

      Ah. Missing a $ before which_date…

      Reply
  • Hey, you should pre-load the images in js so you don’t get the ugly flicker of text-only sw before the actual sw.

    Reply
    • Author: Corey
    • Posted on: 2010/06/23
    • At: 09:28

    Is it possible to use the Spinning wheel module with the iScroll module? I can’t seem to get the two working together. When i try to add the Spinning Wheel module to iScroll, it displays briefly, then disappears before I can select anything from the spinning wheels.

    Reply
    • Author: Kris
    • Posted on: 2010/06/27
    • At: 21:13

    Do you have plans to or a timeframe for making the images high res for the retina display on iPhone 4?

    Reply
    • Author: HumbleGuy
    • Posted on: 2010/06/29
    • At: 08:02

    I have a problem. I am using it for checkin and checkout dates on the same form. I’ve created different events to fire those.

    But while the sw is on for checkin date and i fire the event for checkout date, it does not clear the data, rather adds more slots to the existing sw. I tried closing the sw before opening it again but all in vain.

    Will appreciate any help.

    Reply
    • Author: Kiran
    • Posted on: 2010/07/13
    • At: 09:21

    Hi,

    I am trying to create a picker using HTML 5 and Javascript which can be run on Webbrowser and then can be turned into a webapplication for android phones using phonegap.

    Any ideas on how to create this? Code would be helpful.

    Reply
    • Author: ziang
    • Posted on: 2010/07/15
    • At: 05:21

    hey there, how do I test it after i download the script file?

    sorry im quite green in iphone app..

    Reply
    • Author: Hari
    • Posted on: 2010/08/20
    • At: 07:44

    Hi,

    Thank you very much for such a wonderful plug-in :)

    But in our application, window.innerHeight is returning document height instead of window height. So spinning wheel is always displaying at bottom of document and its not visible. Could you advise any fix for this?

    Regards,
    Hari

    Reply
    • no easy solution. I’d need to see the code. You probably have to find different ways to position the wheel.

      Reply
    • Author: Brian
    • Posted on: 2010/08/24
    • At: 06:43

    Ok, probably a complete noob question, but do I have to have webkit for this plugin to work?

    Reply
    • Author: Hari
    • Posted on: 2010/08/25
    • At: 09:37

    Thanks Matteo. After opening spinning wheel, I’m trying to dynamically set the “sw-wrapper” position dynamically as follows.

    document.getElementById(“sw-wrapper”).style.top = 420 + window.pageYOffset + ‘px’;

    So that it will work fine in “Portrait mode”. But window.pageYOffset is always returning “0″ irrespective of page scoll position. Any clue on this?

    Regards,
    Hari

    Reply
    • Author: imran
    • Posted on: 2010/08/25
    • At: 10:12

    It is not working with JQTouch Framework becuase of its css.

    Reply
    • Author: Hari
    • Posted on: 2010/08/25
    • At: 11:28

    Hi Matteo,

    It seems like “window.pageYOffset” does not have any effect on iPhone and its always returning “0″. You may check this and you may modify the code.

    Since our pages are long, by default we are not able to see the spinning wheel. So after opening spinning wheel, I’m scrolling to spinning wheel using following code.

    location.href=”#sw-wrapper”;

    I’m also grabbing the element id which has initiated the spinning wheel and I’m storing it in JS variable. Once the user click on Done or cancel, again I’m scrolling back to ‘the element’ using following code.

    location.href=”#”+vEelementId;

    I’m just sharing this as it can help others who are facing similar issue.

    Thanks and Regards,
    Hari

    Reply
    • Thanks for sharing, Hari. I’ll soon revamp this script, so stay tuned!

      Reply
    • Author: VP
    • Posted on: 2010/08/26
    • At: 17:33

    To get it working with jQTouch, just add the following to your own css file:

    #sw-wrapper { display: block; min-height: 0 !important; }
    #sw-slots ul { font: normal normal bold 16px/normal Helvetica; color: black; border: none; }
    #sw-slots ul li { color: black; background-image: none; border: none; }

    I used Chrome to compare the final CSS of the main elements of the demo with the one produced when using jQTouch and the jqt theme. If you’re using another theme, you may (or may not) need to override other jQTouch styles.

    Reply
      • Author: Patrick Schläpfer
      • Posted on: 2010/09/16
      • At: 10:47

      Thanks for your tip how to make it work wit jQTouch.

      Reply
      • Author: mandy
      • Posted on: 2010/12/17
      • At: 08:37

      thanks a TON!!! :-)

      Reply
    • Author: schneida
    • Posted on: 2010/09/08
    • At: 14:28

    This is a really cool plugin though I’m missing a feature to close the wheel. I tried

    SpinningWheel.close();

    but it seems to destroy the whole spinning wheel. Such a feature would come very handy when you have two different slides on one page and the user selects the other slide while the first one is still opened. Currently the new slots will just get added to the already existing slots (you have that problem in your demo too!).

    Reply
    • Could you not add it inside a div, or move it inside a div after opening it, then simply show / hide that div without “closing” the spinner? That should work…

      Best from Prague ;)

      Jamie

      Reply
    • Author: cbehan
    • Posted on: 2010/09/15
    • At: 15:14

    Any news on the updated date picker?

    Also, I’m interested in backfilling single digit date and month fields so that I can use the results in a mysql date field. Has anyone done this?

    Thanks, and good work here!

    Reply
    • Author: Jonas Christensen
    • Posted on: 2010/09/30
    • At: 11:31

    Is it totally impossible to change the values and make it to a calendar who have:

    This day, this month and this year

    and update it self automatic?

    I tryed but it won’t work!

    Reply
    • Author: mkso
    • Posted on: 2010/10/12
    • At: 20:19

    Thank You for this widget. I want to know how we can make one slot dependent on another. For example if I have 2 slots and if I change the value on slot 1, slot 2 should load new set of values.

    Reply
  • I needed a way for this to update UI cues as the user is selecting a value, without them having to click Done (like the native spinner does). So I added a callback procedure to the script to allow this to happen. Essentially, add this to the handleEvent function, right after the call to this.scrollEnd(e):

    if (typeof this.valueChangedCallback == “function”)
    this.valueChangedCallback.call(this, e);

    Then add in a valueChangedCallback property somewhere:

    valueChangedCallback: function(e)
    { }

    Reply
    • Author: idrivefastlane
    • Posted on: 2010/10/20
    • At: 15:00

    you’re a life-saver!! it took a while to format your one-liner js file dough, my vs2008 wouldn’t hack it.

    and i would suggest defining the wrapper div in the target side instead of in the js, so that device-frameworks that use transitions between divs could display the sw without altering the js.

    thanks from norway!

    Reply
    • Author: idrivefastlane
    • Posted on: 2010/10/20
    • At: 15:32

    i’ve altered the open method to include my container div-id as parameter (instead of adding and removing sw-wrapper div to the body element), and it works perfectly! :)

    Reply
    • Author: hfeist
    • Posted on: 2010/10/27
    • At: 18:10

    when i try to add the iui.css file to the index.html file in the demo the demo button does not display. only the text.

    the offending text in iui.css seems to be here

    body > *:not(.toolbar) {
    display: none;
    position: absolute;
    margin: 0;
    padding: 0;
    left: 0;
    top: 45px;
    width: 100%;
    min-height: 372px;
    -webkit-transition-duration: 300ms;
    -webkit-transition-property: -webkit-transform;
    -webkit-transform: translateX(0%);
    }

    if i comment out all but the last 3 lines everything displays

    Reply
    • Author: Björn
    • Posted on: 2010/11/02
    • At: 09:03

    Hi!

    I’m interested in the same feature as Jann (http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/comment-page-2#comment-290). Is it possible to add this feature myself just by adding some .click-function somewhere? :) Or are you working on a implementation already?

    Best wishes and a big thanks for this awesome script,
    Björn

    Reply
  • Great work – thanks for such a necessary plugin for phoneGappers ;)
    Could you give us an idea when the new script might be ready?
    And will the upgrade be seamless? I mean, if I implement this version, will I be able to do a straight swap with the next version and just gain more functionality / stability, or would you advise me to wait for the new script?
    Thanks again

    Regards

    Jamie

    Reply
      • Author: Tony Danza
      • Posted on: 2010/11/03
      • At: 10:11

      Seconded! :)

      Reply
    • I don’t know yet actually, but I don’t think the next version will be a drop-in replacement, sorry.

      Reply
    • Author: Tony Danza
    • Posted on: 2010/11/04
    • At: 16:11

    Well that´s ok for me. I just made a small datetimepicker and it works fine. Thanks for the code. Much appreciated.

    Reply
    • Author: Joe Casper
    • Posted on: 2010/11/07
    • At: 08:18

    Is there any way to make this work with a web form? I would like the results to replace the value of a text input.

    ETA on a new version of the script?

    Reply
    • Author: Alan
    • Posted on: 2010/11/18
    • At: 02:50

    I am hoping to use this and then set a localStorage.variable. I’m new to this…can anyone help with hints/suggestions please?

    Thanks!

    Reply
    • Author: Jim Maguire
    • Posted on: 2010/12/02
    • At: 22:30

    Does it work on iPad? I have a user that says nothing pops up.

    Reply
    • Author: mandy
    • Posted on: 2010/12/17
    • At: 16:48

    The following patch avoids the problem of having multiple nodes with same id populating over and over again in the page when the new pages are loaded using ajax:
    [code]
    // //Remove any existing node with the same id.
    if (document.getElementById($node.attr('id'))) {
    $("#" + $node.attr('id')).empty().remove();
    }
    //
    $node.appendTo($body);
    [/code]

    Reply
    • Author: mandy
    • Posted on: 2010/12/17
    • At: 17:12

    using jQuery a better way to do the same (above)

    //[patch]
    //Remove any existing node with the same id.
    if ($(“#”+$node.attr(‘id’)).length > 0) {
    var data = $(“#”+$node.attr(‘id’)).html();
    console.log(“Removing ” + data.length + ” bytes of flab!”);
    $(“#” + $node.attr(‘id’)).empty().remove();
    }
    //[/patch]
    $node.appendTo($body);

    Reply
    • Author: Taras
    • Posted on: 2010/12/28
    • At: 16:40

    You have spared me one quite a long coding session.

    Thanks a bunch.

    P.S. For those in doubt, yes, it works well on the iPad.

    Reply

/Leave a reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>