> Spinning wheel on webkit for iPhone/iPod touch
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.
Hi ska_iit:
Could you please send me the code modification you did for it to work with iWebKit?
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’).
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.
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
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
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!
@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.
You can suppress the keyboard by setting the input tag to readonly
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.
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.
Ska_lit,
Can you post or send the mod to make the date valid?
or email me at danbodenstein at g mail dot com
hahahhahaaaaaaaaaaaa!!!
just what i needed.
wonderful.
i love you.
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
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.
Would be nice to have a date picker demo for use in a form. Can anyone provide? Thanks!
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?
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
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
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!
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!
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.
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
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.
The spinning wheel need a complete rewrite. I’ll work on it soon, promised.
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.
Hi Matteo
Any updates on the new version? I am eagerly waiting for this.
Regards
HumbleGuy
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?
Ah. Missing a $ before which_date…
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.
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.
Do you have plans to or a timeframe for making the images high res for the retina display on iPhone 4?
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.
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.
hey there, how do I test it after i download the script file?
sorry im quite green in iphone app..
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
no easy solution. I’d need to see the code. You probably have to find different ways to position the wheel.
Ok, probably a complete noob question, but do I have to have webkit for this plugin to work?
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
It is not working with JQTouch Framework becuase of its css.
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
Thanks for sharing, Hari. I’ll soon revamp this script, so stay tuned!
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.
Thanks for your tip how to make it work wit jQTouch.
thanks a TON!!!
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!).
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
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!
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!
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.
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)
{ }
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!
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!
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
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
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
Seconded!
I don’t know yet actually, but I don’t think the next version will be a drop-in replacement, sorry.
Well that´s ok for me. I just made a small datetimepicker and it works fine. Thanks for the code. Much appreciated.
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?
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!
Does it work on iPad? I have a user that says nothing pops up.
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]
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);
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.