Developing with HTML5, CoffeeScript and Twitter's Bootstrap
Join the DZone community and get the full member experience.
Join For Free- integrating scalate and jade with play 1.2.3
- trying to make coffeescript work with scalate and play
-
integrating html5 boilerplate with scalate and play
developing features
after getting
my desired infrastructure setup, i started coding like a madman. the
first feature i needed was a stopwatch to track the duration of a
workout, so i started writing one with coffeescript. after spending 20
minutes playing with dates and settimeout, i searched and found a
stopwatch jquery plug-in
. i added this to my app, deployed it to
heroku
, brought up the app on my iphone 3g, clicked
start
and started riding my bike to work.
when i arrived, i unlocked my phone and discovered that the time had stopped. at first, i thought this was a major setback. my disappointed disappeared when i found a super neat javascript stopwatch and kåre byberg's version that worked just fine. this stopwatch used settimeout, so by keeping the start time, the app on the phone would catch up as soon as you unlocked it. i ported kåre's script to coffeescript and rejoiced in my working stopwatch.
# created by kåre byberg © 21.01.2005. please acknowledge if used # on other domains than http://www.timpelen.com. # ported to coffeescript by matt raible. also added hours support. flagclock = 0 flagstop = 0 stoptime = 0 refresh = null clock = null start = (button, display) -> clock = display startdate = new date() starttime = startdate.gettime() if flagclock == 0 $(button).html("stop") flagclock = 1 counter starttime, display else $(button).html("start") flagclock = 0 flagstop = 1 counter = (starttime) -> currenttime = new date() timediff = currenttime.gettime() - starttime timediff = timediff + stoptime if flagstop == 1 if flagclock == 1 $(clock).val formattime timediff, "" callback = -> counter starttime refresh = settimeout callback, 10 else window.cleartimeout refresh stoptime = timediff formattime = (rawtime, roundtype) -> if roundtype == "round" ds = math.round(rawtime / 100) + "" else ds = math.floor(rawtime / 100) + "" sec = math.floor(rawtime / 1000) min = math.floor(rawtime / 60000) hour = math.floor(rawtime / 3600000) ds = ds.charat(ds.length - 1) start() if hour >= 24 sec = sec - 60 * min + "" sec = prependzerocheck sec min = min - 60 * hour + "" min = prependzerocheck min hour = prependzerocheck hour hour + ":" + min + ":" + sec + "." + ds prependzerocheck = (time) -> time = time + "" # convert from int to string unless time.charat(time.length - 2) == "" time = time.charat(time.length - 2) + time.charat(time.length - 1) else time = 0 + time.charat(time.length - 1) reset = -> flagstop = 0 stoptime = 0 window.cleartimeout refresh if flagclock == 1 resetdate = new date() resettime = resetdate.gettime() counter resettime else $(clock).val "00:00:00.0" @stopwatch = { start: start reset: reset }
the scalate/jade template to render this stopwatch looks as follows:
script(type="text/javascript" src={uri("/public/javascripts/stopwatch.coffee")}) #display input(id="clock" class="xlarge" type="text" value="00:00:00.0" readonly="readonly") #controls button(id="start" type="button" class="btn primary") start button(id="reset" type="button" class="btn :disabled") reset :plain <script type="text/coffeescript"> $(document).ready -> $('#start').click -> stopwatch.start this, $('#clock') $('#reset').click -> stopwatch.reset() </script>
next, i wanted to create a map that would show your location. for this, i used merge design's html 5 geolocation demo as a guide. the html5 geo api is pretty simple, containing only three methods:
// gets the users current position navigator.geolocation.getcurrentposition(successcallback, errorcallback, options); // request repeated updates of position watchid = navigator.geolocation.watchposition(successcallback, errorcallback); // cancel the updates navigator.geolocation.clearwatch(watchid);
after rewriting the geolocation example in coffeescript, i ended up with the following code in my map.coffee script. you'll notice it uses google maps javascript api to show an actual map with a marker.
# geolocation with html 5 and google maps api based on example from maxheapsize: # http://maxheapsize.com/2009/04/11/getting-the-browsers-geolocation-with-html-5/ # this script is by merge database and design, http://merged.ca/ -- if you use some, # all, or any of this code, please offer a return link. map = null mapcenter = null geocoder = null latlng = null timeoutid = null initialize = -> if modernizr.geolocation navigator.geolocation.getcurrentposition showmap showmap = (position) -> latitude = position.coords.latitude longitude = position.coords.longitude mapoptions = { zoom: 15, maptypeid: google.maps.maptypeid.roadmap } map = new google.maps.map(document.getelementbyid("map"), mapoptions) latlng = new google.maps.latlng(latitude, longitude) map.setcenter(latlng) geocoder = new google.maps.geocoder() geocoder.geocode({'latlng': latlng}, addaddresstomap) addaddresstomap = (results, status) -> if (status == google.maps.geocoderstatus.ok) if (results[1]) marker = new google.maps.marker({ position: latlng, map: map }) $('#location').html('your location: ' + results[0].formatted_address) else alert "sorry, we were unable to geocode that address." start = -> timeoutid = settimeout initialize, 500 reset = -> if (timeoutid) cleartimeout timeoutid @map = { start: start reset: reset }
the template to show the map is a mere 20 lines of jade:
script(type="text/javascript" src="http://www.google.com/jsapi") script(type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false") :css .demo-map { border: 1px solid silver; height: 200px; margin: 10px auto; width: 280px; } #map(class="demo-map") p(id="location") span(class="label success") new | fetching your location with html 5 geolocation... script(type="text/javascript" src={uri("/public/javascripts/map.coffee")}) :javascript map.start();
the last two features i wanted were 1) distance traveled and 2) drawing the route taken on the map. for this i learned from a simple trip meter using the geolocation api . as i was beginning to port the js to coffeescript, i thought, "there's got to be a better way." i searched and found js2coffee to do most of the conversion for me. if you know javascript and you're learning coffeescript, this is an invaluable tool.
i tried out the trip meter that night evening on a bike ride and noticed it said i'd traveled 3 miles when i'd really gone 6. i quickly figured out it was only calculating start point to end point and not taking into account all the turns in between. to view what was happening, i integrated my odometer.coffee with my map using google maps polylines . upon finishing the integration, i discovered two things, 1) html5 geolocation was highly inaccurate and 2) geolocation doesn't run in the background .i was able to solve the first problem by passing in {enablehighaccuracy: true} to navigator.geolocation.watchposition(). below are two screenshots showing before high accuracy and after. both screenshots are from the same two-block walk.
the second
issue is a slight show-stopper.
phonegap
might be able to solve the problem, but i'm currently using a
workaround → turning off auto-lock and keeping safari in the foreground.
making it look good
after i got all my desired features developed, i moved onto making the app look good. i started by using
sass
for my css and installed
play's sass module
. i then switched to
less
when i discovered and added
twitter's bootstrap
to my project. at first i used
play's less module
(version 0.3), but ran into
compilation issues
. i then tried
play's greenscript module
, but gave up on it when i found it was incompatible with the
coffeescript module
. switching back to the less module and using the "0.3.compatibility" version solved all remaining issues.
you might remember that i integrated html5 boilerplate and wondering why i have both bootstrap and boilerplate in my project. at this point, i don't think boilerplate is needed, but i've kept it just in case it's doing something for html5 cross-browser compatibility. i've renamed its style.css to style.less and added the following so it has access to bootstrap's variables.
/* variables from bootstrap */ @import "libs/variables.less";
then i made my app look a lot better with layouts, stylish forms , a fixed topbar and alerts . for example, here's the coffeescript i wrote to display geolocation errors:
geolocationerror = (error) -> msg = 'unable to locate position. ' switch error.code when error.timeout then msg += 'timeout.' when error.position_unavailable then msg += 'position unavailable.' when error.permission_denied then msg += 'please turn on location services.' when error.unknown_error then msg += error.code $('.alert-message').remove() alert = $('<div class="alert-message error fade in" data-alert="alert">') alert.html('<a class="close" href="#">×</a>' + msg); alert.insertbefore($('.span10'))
then i set about styling up the app so it looked good on a smartphone with css3 media queries . below is the less code i used to hide elements and squish the widths for smaller devices.
@media all and (max-device-width: 480px) { /* hide scrollbar on mobile */ html { overflow-y:hidden } /* hide sidebar on mobile */ .home .span4, .home .page-header, .topbar form { display: none } .home .container { width: 320px; } .about { .container, .span10 { width: 280px; } .span10 { padding-top: 0px; } }
tools
in the process of developing a stopwatch, odometer, displaying routes
and making everything look good, i used a number of tools. i started out
primarily with
textmate
and its bundles for
less
,
coffeescript
and
jade
. when i started writing more scala, i installed the
scala textmate bundle
. when i needed some debugging, i switched to
intellij
and installed its scala plugin. coffeescript, less and haml plugins
(for jade) were already installed by default. i also used james ward's
setup play framework with scala in intellij
.
issues
i think it's obvious that my biggest issue so far is the fact that a
webapp can't multitask in the background like a native app can. beyond
that, there's accuracy issues with html5's geolocation that i haven't
seen in native apps.
i also ran into a caching issue when calling getcurrentposition(). it only worked the first time and i had to refresh my browser to get it to work again. strangely enough, this only happened on my desktop (in safari and firefox) and worked fine on my iphone. unfortunately, it looks like phonegap has issues similar to this.
my workaround for no webapp multitasking is turning off auto-lock and leaving the browser in the foreground while i exercise. the downside to this is it really drains the battery quickly (~ 3 hours). i constantly have to charge my phone if i'm testing it throughout the day. the testing is a real pain too. i have to deploy to heroku (which is easy enough), then go on a walk or bike ride. if something's broke, i have to return home, tweak some things, redeploy and go again. also, there's been a few times where safari crashes halfway through and i lose all the tracking data. this happens with native apps too, but seemingly not as often.
if you'd like to try the app on your mobile phone and see if you experience these issues, checkout play-more.com .
summary
going forward, there's still
more html5 features i'd like to use. in particular, i'd like to play
music while the fitness tracker is running. i'd love it if cloud music
services (e.g. pandora or spotify) had an api i could use to play music
in a webapp.
soundcloud
might be an option, but i've also thought of just uploading some mp3s and playing them with the <audio> tag.
source: http://raibledesigns.com/rd/entry/developing_with_html5_coffeescript_and
Opinions expressed by DZone contributors are their own.
Comments