Countdown iPhone Webapp
What is it?
Countdown is an iPhone app started at this year’s iPhoneDevCamp 3 which was held at Yahoo! from July 31st through August 2nd.
Countdown is a simple multi-color timer that charts how many days, hours, minutes and seconds are left until a selected target event arrives. For example, this may be a fun (or frustrating) way to count how long you must wait until your next birthday. Built-in events also include “Midnight” and “New Year’s”. There is a “Settings” screen that allows you to configure some of the parameters of the program. One of the most interesting configuration elements is a date picker that uses the hardware accelerated “SpinningWheel” from cubiq.org.

Figure 1: Main Screen

Figure 2: Settings Screen

Figure 3: SpinningWheel Date Picker
What does the app do?
Countdown illustrates
- Webkit techniques to build a “native-look-and-feel” iPhone Webapp using JavaScript and CSS,
- the latest stable iUI (iui-0.30), and
- integration of the latest (updated) SpinningWheel, a powerful JavaScript tool that resembles the native Picker View widget (UIPickerView) on the iPhone. The SpinningWheel tool provides a fast and flexible user interface for many kinds of data input and visualization. It allows multiple columns or slots (we are using 3) to be defined to represent complex values like dates and other tabular data. Interaction with the widget is fully hardware accelerated.
Also, we wanted to create a version of our webapp which could be installed as a stand-alone application that would not require access to a network to run. This means that all data items, including images, stylesheets and scripts, must be bundled with the application beforehand. Dynamic data, such as user-updated application settings, must be kept in local storage by the application across multiple launches and even across power cycles.
What issues did we face?
There were some challenges:
- Integration of iUI and SpinningWheel:
Integrating iUI with other packages requires some understanding of how iUI manages HTML containers used for displaying successive screens on the iPhone. Essentially the CSS rules of iUI turn off any unselected, top level elements within the body of an HTML page. In order to get SpinningWheel to work properly with iUI, our application needed to define a CSS rule to ensure display of the dynamically created primary container of the SpinningWheel widget. This container has the id
sw-wrapperand is added as a child of the screen’s body tag when the widget is opened. A short CSS rule in our application’s stylesheet (loaded after the iUI stylesheet) was enough to override iUI’s default behavior.#sw-wrapper { display:block; }
This simple addition to our stylesheet enabled SpinningWheel to be used with iUI without any modification to either package.
In addition, we had to reposition the SpinningWheel’s view frame,
sw-framefor our application’s layout. The values for portrait and landscape orientations that worked for Countdown werebody[orient="portrait"] #sw-frame { bottom:112px; } body[orient="landscape"] #sw-frame { bottom:8px; }
Using SpinningWheel with its hardware accelerated animations is a lot of fun and is a great way for a user to see and select tabular data like dates.
- Client-side storage using HTML5:
We used WebKit’s support for the
localStorageDOM attribute of HTML5. This is W3C’s new Web Storage abstraction for JavaScript managed user data.localStoragesupports persistence beyond the current session and is ideal (essential in the case of the stand-alone version of our application) for saving the application’s settings in a server independent manner. On the iPhone, the process of creating the stand-alone application changes the application’s domain origin to a null value. This value is then automatically used as the application’s Web Storage domain for all localStorage access, creating referential consistency. - Converting Countdown into a stand-alone application:
[ Note: the following section applies to this version of the Countdown webapp which was implemented using Data URLs. HTML5 Caching provides a better implementation strategy and is covered in the follow-on article "Countdown 2.0: Supporting HTML5 Cache" ]
Converting this webapp, when implemented using Data URLs, into a stand-alone application required modification of iUI in three ways. First, iUI navigates back to screens presented earlier by updating the application’s global window location attribute with URLs from its page history cache. Due to security constraints, updating the location attribute is not allowed by Safari when running in stand-alone mode. Second, iUI uses AJAX requests to update application content. This is also forbidden by the security context imposed on a stand-alone application. This functionality can therefore be removed to save space. Third, iUI preloads images to improve performance. This is not needed in a stand-alone application.
To address the first of these three issues, the standalone version of our application uses an iUI package modified to allow forward and backward navigation via document section ids (using the link’s hash attribute in same way the unmodified iUI does currently for forward navigation). Countdown has only two working screens, but applications that require more screens would need to enhance this approach with a simple history queue.
To prevent violation of the stand-alone security context, the predefined AJAX GET and POST requests were removed from iUI. This means that all resources needed by the stand-alone application have to be bundled with the program when it is written. This is a constraint that all stand-alone webapps on the iPhone are burdened with anyway, so the need to remove AJAX from iUI was not surprising.
Avoiding preloading of images required only simple changes to iUI’s JavaScript and CSS.
Finally, converting this webapp into a stand-alone application also required constructing a simple installer. Fortunately, we were able to reuse the installer tools discussed in our earlier article “How to Deploy an iPhone Web Application”.
It is important to note that these changes to iUI were only required for building the stand-alone version of our application. For the network connected version of Countdown, both SpinningWheel and iUI could be used without any modifications.
Try Out Countdown!
[ Please check out the new version of Countdown that uses HTML5 caching instead of Data URLs ]
There are two versions of the Countdown application.
Network connected version
Try out Countdown on your iPhone.
This network connected version of Countdown may also work in some other browsers (Safari 4+, Firefox 2+, Chrome 3.0.197+) with limited functionality. [EXPERIMENTAL]
Stand-alone version
Install Countdown as a “native application” on your iPhone Home Screen.
This installable version of Countdown may also work as a bookmark (Data URL) in some other browsers (in particular Safari 4+) with limited functionality. It does not work properly in any version of Firefox. [EXPERIMENTAL]
Note that Countdown works best on a real iPhone or simulator. However, the application can be run in a limited way on various modern browsers like Safari 4+, Firefox 3+ and Chrome/Chromium 3.0.197+. In these cases, instead of using the (iPhone specific) SpinningWheel interface, Countdown presents a standard select element for date picking. Countdown also uses persistent storage when the browser (e.g., Firefox 3.5, Safari 4+) supports Web Storage. Otherwise Countdown uses a private array which is recreated for each new instance of the application.
Listing 1: Countdown HTML – Network connected version (countdown.html)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/tr/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- Copyright (c) 2009 Technetra Corp Licensed under The MIT License Originally inspired by Danny Goodman's Countdown script in "JavaScript and DHTML Cookbook" --> <html> <head> <title>Countdown</title> <link rel="apple-touch-icon" href="TOUCH_ICON" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" /> <link rel="stylesheet" href="iui/iui.css" type="text/css" media="all" /> <link rel="stylesheet" href="spinningwheel/spinningwheel.css" type="text/css" media="all" /> <link rel="stylesheet" href="countdown.css" type="text/css" media="all" /> <script type="text/javascript" src="iui/iui.js"></script> <script type="text/javascript" src="countdown.js"></script> <script type="text/javascript" src="spinningwheel/spinningwheel.js"></script> </head> <body> <!-- MAIN --> <div class="toolbar"> <h1 id="pageTitle"></h1> <a class="button" id="backButton" href="#" onClick="Countdown.turnActionButtonOn('settingsButton','doMain');"></a> <a class="button" id='settingsButton' href="#setupForm" onClick="Countdown.turnActionButtonOff(this,'doSetup');">Settings</a> </div> <span selected="true"> <h1 class="ctr"> <span id="main_subtitles"> <span>Today: <span id="today"> </span><br /><span class="divider" id="divider"> ♦</span></span> <span>Start Date: <span id="start_date"> </span><span class="divider"> ♦</span></span> <span>Event Date: <span id="target_date"> </span></span> </span> </h1> <div class="unit" id="countdown_box"> <div id="days_box"> <span id="days"></span><span class="unit_label">Days</span> </div> <div class="overlay" id="days_overlay"></div> <div id="hours_box"> <span id="hours"></span><span class="unit_label">Hours</span> </div> <div class="overlay" id="hours_overlay"></div> <div id="minutes_box"> <span id="minutes" style=""></span><span class="unit_label">Minutes</span> </div> <div class="overlay" id="minutes_overlay"></div> <div id="seconds_box"> <span id="seconds"></span><span class="unit_label">Seconds</span> </div> <div class="overlay" id="seconds_overlay"></div> </div> <div id="main_footer"> <div id='update_interval_container' >(updated every <span id='update_interval_text'></span>)</div> </div> <br /><br /><br /><br /><br /> </span> <!-- SETUP --> <div id="setupForm" title="Settings" class="panel"> <h2>Target Event</h2> <fieldset> <div class=row> <label>Title</label> <input id="title_input_field" onChange="Countdown.setTitleFieldResources(this.value);" type="text" size=30 value="" /> </div> <div class=row> <label>Event</label> <select id="select_event" onChange="Countdown.updateEventType(this);" style="font-size:1.0em; text-align:left;"> <option value='newyears'>New Years</option> <option value='birthday'>Birthday</option> <option value='midnight'>Midnight</option> <option value='other'>Other</option> <option value='reset'>Reset</option> </select> </div> </fieldset> <h2>Countdown Details</h2> <fieldset> <div class=row> <label>Update Every</label> <select id="select_interval" onChange="Countdown.updateInterval(this);" style="font-size:1.0em; text-align:left;"> <option value='1000'>second</option> <option value='2000'>2 seconds</option> <option value='60000'>minute</option> <option value='3600000'>hour</option> <option value='86400000'>day</option> </select> </div> <div class=row> <label>Date Format</label> <select id="select_date_format" onChange="Countdown.updateDateFormat(this);" style="font-size:1.0em; text-align:left;"> <option value='mm/dd/yyyy'>MM/DD/YYYY</option> <option value='yyyy/mm/dd'>YYYY/MM/DD</option> <option value='dd/mm/yyyy'>DD/MM/YYYY</option> <option value='mm-dd-yyyy'>MM-DD-YYYY</option> <option value='yyyy-mm-dd'>YYYY-MM-DD</option> <option value='dd-mm-yyyy'>DD-MM-YYYY</option> </select> </div> <div class=row> <label>Number Padding</label> <select id="select_padding" onChange="Countdown.updatePadding(this);" style="font-size:1.0em; text-align:left;"> <option value=' '>none</option> <option value='0'>0</option> <option value='-'>-</option> <option value='♥'>♥</option> <option value='♦'>♦</option> </select> </div> <div id="spinning_wheel_date_container" class="row"> <label>Event Date: <span id="sw_date_picked"></span></label> <a class="button blueButton" href="#dummy" onClick="SpinningWheel_AI.openEventDate();">Change Date</a> </div> <div id="select_date_container" class="row"> <label>Event Date:</label> <table style="float:right;margin-right:-27%;"> <tr> <td align="left" class="setup_cell"> <select id="select_month" onChange="Countdown.updateMonth(this);"> <option value='-1'>Month</option> <option>1</option><option>2</option><option>3</option> <option>4</option><option>5</option><option>6</option> <option>7</option><option>8</option><option>9</option> <option>10</option><option>11</option><option>12</option> </select> <select id="select_day" onChange="Countdown.updateDay(this);"> <option value='-1'>Day</option> <option>1</option><option>2</option><option>3</option> <option>4</option><option>5</option><option>6</option> <option>7</option><option>8</option><option>9</option> <option>10</option><option>11</option><option>12</option> <option>13</option><option>14</option><option>15</option> <option>16</option><option>17</option><option>18</option> <option>19</option><option>20</option><option>21</option> <option>22</option><option>23</option><option>24</option> <option>25</option><option>26</option><option>27</option> <option>28</option><option>29</option><option>30</option> <option>31</option> </select> <select id="select_year" onChange="Countdown.updateYear(this);"> <option value='-1'>Year</option> <option>2009</option><option>2010</option><option>2011</option> <option>2012</option><option>2013</option><option>2014</option> </select> </td> </tr> </table> </div> </fieldset> </div> <!-- DONE! --> <a id="counter_done" class="button" onClick="Countdown.acknowledgeDone(this);" href="#dummy">Countdown is done!<br/>OK</a> </body> </html>
Listing 2: Countdown JavaScript Implementation (countdown.js)
/* Copyright (c) 2009 Technetra Corp Released under The MIT License */ var Countdown; /* BEGIN SpinningWheel Application Interface */ var SpinningWheel_AI = { openEventDate: function() { var obj = document.getElementById('select_event'); var event_title = obj.options[obj.selectedIndex].text; if (event_title == "Birthday" || event_title == "Other") { var now = new Date(); var days = { }; var years = { }; var months = { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' }; for( var i = 1; i < 32; i += 1 ) { days[i] = i; } for( i = now.getFullYear(); i < now.getFullYear()+4; i += 1 ) { years[i] = i; } SpinningWheel.addSlot(years, 'right', Countdown.get_selected_year()); SpinningWheel.addSlot(months, '', Countdown.get_selected_month()); SpinningWheel.addSlot(days, 'right', Countdown.get_selected_day()); SpinningWheel.setCancelAction(SpinningWheel_AI.cancel); SpinningWheel.setDoneAction(Countdown.updateSWDate); SpinningWheel.open(); } else { alert("Please select 'Birthday' or 'Other' Event Type To Customize Date"); } }, cancel: function() { //document.getElementById('sw_date_picked').innerHTML = 'cancelled!'; } } /* END SpinningWheel Application Interface */ /* Countdown Closure */ Countdown = function() { var $ = function(id) { return document.getElementById(id); } var $getInnerText = function(id) { var id = $(id); return (id.innerText? id.innerText : id.innerHTML) } var $setInnerText = function(id, val) { // hack for Firefox 3.5 id = $(id); if (id.innerText) id.innerText = val; else id.innerHTML = val; } var MSSEC = 1000; var MSMIN = 60 * MSSEC; var MSHR = 60 * MSMIN; var MSDAY = 24 * MSHR; var gMy_interval_timer; var gSelected_day, gSelected_month, gSelected_year; var gTitle_input_field = "Midnight"; var gEvent_type_text = "Midnight"; var gEvent_type_value = "midnight"; var gTarget_time; var gDone = false; var gTotal_yeardays = 365; var gStart_month, gStart_day, gStart_year; var gMark_month = -1, gMark_day = -1, gMark_year = -1; var gDate_format = "mm/dd/yyyy"; var gPad = " "; var gUpdate_date_mark = false; var gUpdate_interval = 2000; var gUpdate_interval_text = "2 seconds"; var gLocalStore = {}; var gIs_iPhone = (navigator.userAgent.toLowerCase().indexOf('iphone')!=-1); window.onorientationchange = function() { switch(window.orientation) { case 0: document.body.setAttribute('orient', 'portrait'); break; case 90: case -90: document.body.setAttribute('orient', 'landscape'); break; } setTimeout('Countdown.countDown()', 0); setTimeout(scrollTo, 100, 0, 1); } window.addEventListener("load", function() { initApp(); window.onorientationchange(); Countdown.countDown(); // set up initial timer values, fields and overlays gMy_interval_timer = window.setInterval('Countdown.countDown()', gUpdate_interval); }, false); var storeItem = function(item, val) { if (window.localStorage) { window.localStorage[item] = val; } else { gLocalStore[item] = val; } }; var retrieveItem = function(item) { var val; if (window.localStorage) { val = window.localStorage[item]; } else { val = gLocalStore[item]; } return val; }; var clearItems = function() { if (window.localStorage) { window.localStorage.clear(); } else { gLocalStore = {}; } }; var actionButton = function(btn, action, dsply) { if (typeof btn == 'string') { if (btn != '') $(btn).style.display = dsply; } else { btn.style.display = dsply; } if (action != '') Countdown[action](); }; var store_date = function(y, m, d) { storeItem('selected_year', y); storeItem('selected_month', m); storeItem('selected_day', d); }; var store_update_interval = function(i, t) { storeItem('update_interval', i); storeItem('update_interval_text', t); }; var format_date = function(date_fields) { var fmt = ""; switch (gDate_format) { case "mm/dd/yyyy": fmt = [ date_fields.m, date_fields.d, date_fields.y ].join('/'); break; case "yyyy/mm/dd": fmt = [ date_fields.y, date_fields.m, date_fields.d ].join('/'); break; case "dd/mm/yyyy": fmt = [ date_fields.d, date_fields.m, date_fields.y ].join('/'); break; case "mm-dd-yyyy": fmt = [ date_fields.m, date_fields.d, date_fields.y ].join('-'); break; case "yyyy-mm-dd": fmt = [ date_fields.y, date_fields.m, date_fields.d ].join('-'); break; case "dd-mm-yyyy": fmt = [ date_fields.d, date_fields.m, date_fields.y ].join('-'); break; } return fmt; }; var setOverlay = function(obj, tp, offset) { var mfactor, wfactor; offset = (offset < 0) ? 0 : offset; offset = (offset > 59) ? 59 : offset; if (window.orientation==0) { mfactor = 138/60; wfactor = 136; tp += 52; height = "58px"; } else { mfactor = 260/60; wfactor = 256; height = "36px"; } obj.style.top = tp+"px"; obj.style.height = height; obj.style.width = (wfactor-(offset*mfactor))+"px"; /* obj.innerHTML = obj.style.width; */ /* obj.innerHTML = tp+"px"; */ }; var formatNum = function(num, len) { var d, padding; var fmt = "…"; if (num < 0) return fmt; fmt = "" + num; d = len - fmt.length + 1; padding = new Array(d).join(gPad); return padding + fmt; }; var setFields = function(days, hrs, mins, secs) { if (gUpdate_interval_text.match(/minute/)) { secs = -1; } else if (gUpdate_interval_text.match(/hour/)) { secs = mins = -1; } else if (gUpdate_interval_text.match(/day/)) { secs = mins = hrs = -1; } setOverlay($("days_overlay"), $("days").offsetTop, Math.floor((days*60)/gTotal_yeardays)); setOverlay($("hours_overlay"), $("hours").offsetTop, Math.floor((hrs*60)/24)); setOverlay($("minutes_overlay"), $("minutes").offsetTop, mins); setOverlay($("seconds_overlay"), $("seconds").offsetTop, secs); $setInnerText("days", formatNum(days, 3)); $setInnerText("hours", formatNum(hrs, 3)); $setInnerText("minutes", formatNum(mins, 3)); $setInnerText("seconds", formatNum(secs, 3)); //alert("setFields, days="+days+", hrs="+hrs+", mins="+mins+", secs="+secs); }; var setupSelectElementByValue = function(el, val) { var i, o; o = el.options; for (i=0; i < o.length; i++) { if (o[i].value == val) { el.selectedIndex = i; } } }; var setupSelectElementByText = function(el, val) { var i, o; o = el.options; for (i=0; i < o.length; i++) { if (o[i].text == val) { el.selectedIndex = i; } } }; var setupDate = function(etype) { var targ_date, start_date; var now = new Date(); start_date = new Date(now); gStart_day = now.getDate(); gStart_month = now.getMonth(); gStart_year = now.getFullYear(); var selected_hour = 1, selected_minute = 1, selected_second = 1; switch (etype) { case "midnight": now.setDate(now.getDate() + 1); gSelected_day = now.getDate(); gSelected_month = now.getMonth() + 1; gSelected_year = now.getFullYear(); store_date(gSelected_year, gSelected_month, gSelected_day); break; case "newyears": now.setFullYear(now.getFullYear() + 1); gSelected_day = 1; gSelected_month = 1; gSelected_year = now.getFullYear(); store_date(gSelected_year, gSelected_month, gSelected_day); break; } targ_date = new Date(gSelected_year, gSelected_month-1, gSelected_day, selected_hour-1, selected_minute-1, selected_second-1); gTotal_yeardays = Math.floor((targ_date.getTime() - start_date.getTime())/(1000*60*60*24)); $setInnerText('start_date', format_date({m:gStart_month+1, d:gStart_day, y:gStart_year})); $setInnerText('target_date', format_date({m:gSelected_month, d:gSelected_day, y:gSelected_year})); if (!gIs_iPhone) { setupSelectElementByText($('select_day'), gSelected_day); setupSelectElementByText($('select_month'), gSelected_month); setupSelectElementByText($('select_year'), gSelected_year); } return targ_date; }; var initApp = function() { if (retrieveItem('pad')) gPad = retrieveItem('pad'); if (retrieveItem('event_type_text')) gEvent_type_text = retrieveItem('event_type_text'); if (retrieveItem('event_type_value')) gEvent_type_value = retrieveItem('event_type_value'); if (retrieveItem('title_input_field')) gTitle_input_field = retrieveItem('title_input_field'); if (retrieveItem('date_format')) gDate_format = retrieveItem('date_format'); if (retrieveItem('selected_day')) gSelected_day = parseInt(retrieveItem('selected_day')); if (retrieveItem('selected_month')) gSelected_month = parseInt(retrieveItem('selected_month')); if (retrieveItem('selected_year')) gSelected_year = parseInt(retrieveItem('selected_year')); if (retrieveItem('update_interval')) gUpdate_interval = parseInt(retrieveItem('update_interval')); if (retrieveItem('update_interval_text')) gUpdate_interval_text = retrieveItem('update_interval_text'); document.getElementsByTagName('body')[0].setAttribute('device', gIs_iPhone?'iphone':'unknown'); $setInnerText('update_interval_text', gUpdate_interval_text); $setInnerText('pageTitle', gTitle_input_field); $('title_input_field').value = gTitle_input_field; gTarget_time = setupDate(gEvent_type_value).getTime(); }; var setEventTypeResources = function(ett, etv) { storeItem('event_type_text', ett); storeItem('event_type_value', etv); gEvent_type_text = ett; gEvent_type_value = etv; }; return { get_selected_year: function() { return gSelected_year; }, get_selected_month: function() { return gSelected_month; }, get_selected_day: function() { return gSelected_day; }, doSetup: function() { var i, e; if (gMy_interval_timer != null) { window.clearInterval(gMy_interval_timer); gMy_interval_timer = null; } if (gSelected_day != -1 && gSelected_month != -1 && gSelected_year != -1) { if (gIs_iPhone) $setInnerText('sw_date_picked', format_date({m:gSelected_month, d:gSelected_day, y:gSelected_year})); } setupSelectElementByValue($('select_event'), gEvent_type_value); setupSelectElementByValue($('select_interval'), gUpdate_interval); setupSelectElementByValue($('select_date_format'), gDate_format); setupSelectElementByValue($('select_padding'), gPad); if (!gIs_iPhone) { setupSelectElementByText($('select_day'), gSelected_day); setupSelectElementByText($('select_month'), gSelected_month); setupSelectElementByText($('select_year'), gSelected_year); } if ($('title_input_field').value == "") Countdown.setTitleFieldResources(gEvent_type_text); if (gEvent_type_value == "other" || gEvent_type_value == "birthday") { $('title_input_field').disabled = false; } else if (gEvent_type_value == "midnight") { $('title_input_field').disabled = true; } else { $('title_input_field').disabled = true; } }, doMain: function() { $setInnerText('pageTitle', $('title_input_field').value); var etv = $('select_event').options[$('select_event').selectedIndex].value; gTarget_time = setupDate(etv).getTime(); gUpdate_date_mark = true; window.scrollTo(0, 1); if (gMy_interval_timer != null) { window.clearInterval(gMy_interval_timer); } gMy_interval_timer = window.setInterval('Countdown.countDown()', gUpdate_interval); window.setTimeout('Countdown.countDown()', 0); }, countDown: function() { var now, ms, diff, daysLeft, hrsLeft, minsLeft, secsLeft, today; now = new Date(); now_day = now.getDate(); now_month = now.getMonth(); now_year = now.getFullYear(); ms = now.getTime(); diff = gTarget_time - ms; if (diff <= MSSEC && !gDone) { daysLeft = hrsLeft = minsLeft = secsLeft = 0; if (gMy_interval_timer != null) { window.clearInterval(gMy_interval_timer); gMy_interval_timer = null; } $('counter_done').style.display = "block"; $('settingsButton').style.display = "none"; gDone = true; } else { daysLeft = Math.floor(diff / MSDAY); diff -= (daysLeft * MSDAY); hrsLeft = Math.floor(diff / MSHR); diff -= (hrsLeft * MSHR); minsLeft = Math.floor(diff / MSMIN); diff -= (minsLeft * MSMIN); secsLeft = Math.floor(diff / MSSEC); gDone = false; } setFields(daysLeft, hrsLeft, minsLeft, secsLeft); if (now_month != gMark_month || now_day != gMark_day || now_year != gMark_year || gUpdate_date_mark) { today = format_date({m:now_month+1, d:now_day, y:now_year}); if ($getInnerText('today') != today) $setInnerText('today', today); gMark_month = now_month; gMark_day = now_day; gMark_year = now_year; } }, setTitleFieldResources: function(et) { storeItem('title_input_field', et); gTitle_input_field = et; $('title_input_field').value = et; }, updateEventType: function(obj) { var ett = obj.options[obj.selectedIndex].text; var etv = obj.options[obj.selectedIndex].value; setEventTypeResources(ett, etv); // sets gEvent_type and related fields if (etv == "other" || etv == "birthday") { Countdown.setTitleFieldResources(ett); // sets title_input_field and related fields $('title_input_field').disabled = false; } else if (ett == "Midnight" || ett == "New Years") { Countdown.setTitleFieldResources(ett); $('title_input_field').disabled = true; } else if (etv == "reset") { ett = "Midnight"; etv = "midnight"; alert("Clearing local storage & setting up default event 'Midnight'. Please reload Countdown app to see changes."); clearItems(); Countdown.setTitleFieldResources(ett); setupSelectElementByValue($('select_event'), etv); setupSelectElementByValue($('select_interval'), "2000"); setupSelectElementByValue($('select_date_format'), "mm/dd/yyyy"); setupSelectElementByValue($('select_padding'), " "); Countdown.updateInterval($('select_interval')); Countdown.updateDateFormat($('select_date_format')); Countdown.updatePadding($('select_padding')); $('title_input_field').disabled = true; } gTarget_time = setupDate(etv).getTime(); if (gIs_iPhone) $setInnerText('sw_date_picked', format_date({m:gSelected_month, d:gSelected_day, y:gSelected_year})); window.scrollTo(0, 1); }, updateInterval: function(obj) { gUpdate_interval = parseInt(obj.options[obj.selectedIndex].value); $setInnerText('update_interval_text', obj.options[obj.selectedIndex].text); gUpdate_interval_text = obj.options[obj.selectedIndex].text; store_update_interval(gUpdate_interval, gUpdate_interval_text); window.clearInterval(gMy_interval_timer); gMy_interval_timer = window.setInterval('Countdown.countDown()', gUpdate_interval); window.scrollTo(0, 1); }, updateSWDate: function() { var results = SpinningWheel.getSelectedValues(); gSelected_year = results.keys[0]; gSelected_month = results.keys[1]; gSelected_day = results.keys[2]; store_date(gSelected_year, gSelected_month, gSelected_day); $setInnerText('sw_date_picked', format_date({m:gSelected_month, d:gSelected_day, y:gSelected_year})); window.scrollTo(0, 1); }, updateDateFormat: function(obj) { gDate_format = obj.options[obj.selectedIndex].value; storeItem('date_format', gDate_format); if (gIs_iPhone) $setInnerText('sw_date_picked', format_date({m:gSelected_month, d:gSelected_day, y:gSelected_year})); window.scrollTo(0, 1); }, updateDay: function(obj) { gSelected_day = obj.options[obj.selectedIndex].text; storeItem('selected_day', gSelected_day); }, updateMonth: function(obj) { gSelected_month = obj.options[obj.selectedIndex].text; storeItem('selected_month', gSelected_month); }, updateYear: function(obj) { gSelected_year = obj.options[obj.selectedIndex].text; storeItem('selected_year', gSelected_year); }, updatePadding: function(obj) { gPad = obj.options[obj.selectedIndex].value; storeItem('pad', gPad); window.scrollTo(0, 1); }, turnActionButtonOn: function(btn, action) { actionButton(btn, action, "inline"); }, turnActionButtonOff: function(btn, action) { actionButton(btn, action, "none"); }, acknowledgeDone: function(obj) { obj.style.display='none'; $('settingsButton').style.display='inline'; } } }(); /* END Countdown */
Listing 3: Countdown Stylesheet (countdown.css)
/* Copyright (c) 2009 Technetra Corp Released under The MIT License */ body { background: #cccccc scroll repeat 0 0; font-family:sans-serif; font-size:16px; } body[orient="portrait"] { height:460px; } body[orient="landscape"] { height:300px; } h1 { font-size:1.5em; font-weight:bold; } #main_subtitles > span > span:first-child { color:#356AA0; } body[orient="portrait"] #main_subtitles { font-size:0.56em; } body[orient="landscape"] #main_subtitles { font-size:0.60em; } body[orient="landscape"] #main_subtitles > span > br { display:none; } .divider { color:red; } body[orient="portrait"] #main_subtitles > span:first-child .divider { display:none; } #main_footer { position:absolute; margin-left:10px; } body[orient="portrait"] #main_footer { top:350px; } body[orient="landscape"] #main_footer { top:210px; } #update_interval_container { font-size:0.8em; float:left; margin-top:-4px; color:#356AA0; } table { border-spacing:0; width:100%; } body[orient="landscape"] table { width:50%; float:left; border:none solid gray; } body[orient="portrait"] .overlay { width: 136px; } body[orient="landscape"] .overlay { width: 256px; } #seconds_overlay { position:relative; left:0; background-color:#3F4C6B; -khtml-opacity:0.5; -moz-opacity:0.5; } #minutes_overlay { position:relative; left:0; background-color:#CDEB8B; -khtml-opacity:0.5; -moz-opacity:0.5; } #hours_overlay { position:relative; left:0; background-color:#FFFF88; -khtml-opacity:0.5; -moz-opacity:0.5; } #days_overlay { position:relative; left:0; background-color:#B02B2C; -khtml-opacity:0.5; -moz-opacity:0.5; } #counter_done { position:absolute; min-height:66px; width:50%; text-align:center; -khtml-opacity:0.8; -moz-opacity:0.8; } body[orient="portrait"] #counter_done { top:200px; left:80px; font-size:1.0em; } body[orient="landscape"] #counter_done { top:130px; left:115px; font-size:1.2em; } .ctr {text-align:center} #countdown_box { width:100%; position:absolute; } body[orient="portrait"] #countdown_box { top:30px; } body[orient="landscape"] #countdown_box { top:40px; } .unit > div { position:absolute; text-align:right; font-weight:bold; padding-right:10px; } body[orient="portrait"] .unit > div { right:160px; height:58px; } body[orient="landscape"] .unit > div { right:204px; height:36px; } body[orient="portrait"] #days_box { top:60px; } body[orient="portrait"] #hours_box { top:118px; } body[orient="portrait"] #minutes_box { top:176px; } body[orient="portrait"] #seconds_box { top:234px; } body[orient="landscape"] #days_box { top:10px; } body[orient="landscape"] #hours_box { top:46px; } body[orient="landscape"] #minutes_box { top:82px; } body[orient="landscape"] #seconds_box { top:118px; } .unit_label { position:absolute; padding-left:10px; border-left:2px solid #ddd; } .unit span { padding-right:10px; } body[orient="portrait"] .unit span { font-size:2em; height:58px; } body[orient="landscape"] .unit span { font-size:1.5em; height:36px; } #days_box span[class="unit_label"] { color:#B02B2C; } #seconds_box span[class="unit_label"] { color:#3F4C6B; } #minutes_box span[class="unit_label"] { color:#73880A; } #hours_box span[class="unit_label"] { color:#C79810; } #sw-wrapper { display:block; } body[orient="portrait"] #sw-frame { bottom:112px; } body[orient="landscape"] #sw-frame { bottom:8px; } /* alignment correction for Shiretoko */ .row > label { left:0; } div[class="row"] > input,select { margin-top:7px; } body[device="iphone"] #select_date_container { display:none; } body[device="unknown"] #spinning_wheel_date_container { display:none; }
Copyright © 2009 Technetra. This code is covered by the MIT License. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

October 1st, 2009 at 11:02 am (Pingback)
links for 2009-10-01 « sySolution Says:
[...] Technetra – Countdown iPhone Webapp (tags: iphone mobile) [...]