+ geolocation.xhtml + unfiltered-scalate
authorAlexandre Bertails <bertails@w3.org>
Mon, 22 Aug 2011 10:52:48 -0400
changeset 2 8d1080efed42
parent 1 f6c749ea5576
child 3 208b86013524
+ geolocation.xhtml + unfiltered-scalate
project/Build.scala
src/main/resources/templates/geolocation.ssp
src/main/resources/templates/hello.ssp
src/main/scala/Main.scala
--- a/project/Build.scala	Mon Aug 22 10:32:32 2011 -0400
+++ b/project/Build.scala	Mon Aug 22 10:52:48 2011 -0400
@@ -8,6 +8,7 @@
   val dispatch = "net.databinder" %% "dispatch-http" % "0.8.4"
   val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % "0.4.1"
   val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % "0.4.1"
+  val unfiltered_scalate = "net.databinder" %% "unfiltered-scalate" % "0.4.1"
 }
 
 object BuildSettings {
@@ -39,7 +40,8 @@
 //      libraryDependencies += antiXML,
       libraryDependencies += dispatch,
       libraryDependencies += unfiltered_filter,
-      libraryDependencies += unfiltered_jetty
+      libraryDependencies += unfiltered_jetty,
+      libraryDependencies += unfiltered_scalate
     )
 
   lazy val virtual_trainer = Project(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/templates/geolocation.ssp	Mon Aug 22 10:52:48 2011 -0400
@@ -0,0 +1,538 @@
+<!DOCTYPE html>
+<html lang='en' xmlns='http://www.w3.org/1999/xhtml'>
+<!-- manifest='geolocation.manifest'> -->
+<head>
+ <meta charset='utf-8' />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+ <link rel='icon' type='image/png' href='geolocation-icon.png'/>
+ <title>HTML5 Track</title>
+ <script>
+<![CDATA[
+
+function buildMessage(data, boundary) {
+    var CRLF = "\r\n";
+    var parts = [];
+    var iter = 0;
+
+    while (iter < data.length) {
+        var part = "";
+        var type = "TEXT";
+
+        part = 'Content-Disposition: form-data; ';
+        part += 'name="' + data[iter][0] + '"' + CRLF + CRLF;
+
+        part += data[iter][1] + CRLF;
+
+        parts.push(part);
+        iter++;
+    }
+
+    var request = "--" + boundary + CRLF;
+        request+= parts.join("--" + boundary + CRLF);
+        request+= "--" + boundary + "--" + CRLF;
+
+    return request;
+}
+
+function send(user, data) {
+    var boundary = "AJAX-----------------------" + Date.now();
+    var xhr = new XMLHttpRequest;
+
+    if (!("withCredentials" in xhr)) {
+      return false;
+    }    
+
+    try {
+    xhr.open("POST", "http://teole.jfouffa.org/2011/07/html5track.php", true);
+    xhr.setRequestHeader('X-HTML5TRACK', 'true');
+    xhr.onreadystatechange = function() {
+        switch (xhr.readyState) {
+         case 4:
+            document.getElementById("log").textContent = "xhr done " + xhr.status + " " + xhr.statusText;
+            break;
+         case 0:
+            document.getElementById("log").textContent = "xhr unsent";
+            break;
+         case 1:
+            document.getElementById("log").textContent = "xhr opened";
+            break;
+         case 2:
+            document.getElementById("log").textContent = "xhr headers_received";
+            break;
+         case 3:
+            document.getElementById("log").textContent = "xhr loading";
+            break;
+        }
+    };
+    var contentType = "multipart/form-data; boundary=" + boundary;
+    xhr.setRequestHeader("Content-Type", contentType);
+
+    var data = buildMessage([ ["user", user], ["gps_coords", data] ], boundary);
+
+    xhr.sendAsBinary(data);
+
+    } catch (e) {
+      return false;
+    }
+   return true;
+}
+
+var storage = (function() {
+     try {
+       return !!window.localStorage.getItem;
+     } catch (e) {
+       return false;
+     }
+})();
+
+
+
+var watchId = 0;
+var running = false;
+
+var duration = 0;
+var start_time = 0;
+var intervalId = 0;
+
+// wait for at least 5 consecutive GPS data before declaring the GPS ready
+var MAX_ERRORS = 2;
+var got_error = MAX_ERRORS;
+
+var discarded = 0;
+var locations = 0;
+
+var coordinates = [];
+
+var dst_unit = 1;
+
+function formatNumber(n) {
+  return (n < 10)? "0" + n : n;
+}  
+
+function formatTime(time) {
+ t = parseInt(time);
+ hours = parseInt(t / 3600);
+ minutes = parseInt(t / 60);
+ seconds = t - ((hours * 3600) + (minutes * 60));
+ return ((hours > 0)?  formatNumber(hours) + ":": "") + formatNumber(minutes) + ":" + formatNumber(seconds);
+}
+
+
+
+
+function pushCoordinates(position) {
+   coordinates[coordinates.length] =
+    [ position.timestamp, position.coords.longitude, position.coords.latitude, position.coords.altitude, position.coords.speed ];
+//    [ position.timestamp, position.coords.longitude, position.coords.latitude, position.coords.altitude ];
+}
+
+function pause() {
+   coordinates[coordinates.length] = [ Date.now(), "pause", 0, 0, 0, 0 ];
+}
+
+function getLastCoordinates() {
+   return (coordinates.length > 0)? coordinates[coordinates.length - 1] : null;
+}
+
+function getCoordinates() {
+  if (coordinates.length > 0 && !running) {
+    var result = "";
+    var i = 0;
+    while (i < coordinates.length) {
+      var position = coordinates[i];
+      var j = 1;
+      result += position[0];
+      while (j < position.length) result += "," + position[j++];      
+      result += " ";
+      i++;
+    }
+    return result;
+  }
+  return null;
+}
+
+function calculateDistance(lat1, lon1, lat2, lon2) {
+ var R = 6371; // km
+ var dLat = (lat2-lat1) * Math.PI / 180;
+ var dLon = (lon2-lon1) * Math.PI / 180;
+ var lat1 = lat1  * Math.PI / 180;
+ var lat2 = lat2 * Math.PI / 180;
+ var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
+        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
+ var d = R * c;
+ return d;
+}
+
+function calculateDistanceCoordinates() {
+  var result = 0;
+  if (coordinates.length > 0) {
+    var i = 1;
+    while (i < coordinates.length) {
+      var position1 = coordinates[i-1];
+      var position2 = coordinates[i];
+      result += calculateDistance(position1[2], position1[1], position2[2], position2[1]);
+      i++;
+    }
+  }
+  return result;
+}
+
+function calculateDurationCoordinates() {
+  var result = 0;
+  if (coordinates.length > 0) {
+    var i = 1;
+    while (i < coordinates.length) {
+      var position1 = coordinates[i-1];
+      var position2 = coordinates[i];
+      result += (position2[0]- position1[0]);
+      i++;
+    }
+  }
+  return result;
+}
+
+function repaint() {
+  if (document.visibilityState == true && running) {
+    document.getElementById("locations").textContent = String(locations);
+    var d = calculateDistanceCoordinates() * dst_unit;
+    var t = duration + (Date.now() - start_time);
+    document.getElementById("distance").textContent = String(Math.round(d*100)/100);
+    if (t != 0 && d != 0) {
+      document.getElementById("speed").textContent = String(Math.round( (d / (t / 3600000)) * 100) / 100);
+      document.getElementById("pace").textContent = String(Math.round( ((t / 60000)/d) * 100) / 100);
+    }
+    if (coordinates.length > 0) {
+      document.getElementById("gps_text").style.display = "none";
+      document.getElementById("gps_data").style.display = "inline";
+    }
+  }
+}
+
+function logGPS(position, startMsg) {
+    document.getElementById("log").textContent = 
+      startMsg + position.timestamp 
+       + ": " + position.coords.latitude
+       + ", " + position.coords.longitude
+       + ", " + position.coords.altitude
+       + ", " + position.coords.accuracy
+       + ", " + position.coords.altitudeAccuracy
+       + ", " + position.coords.heading
+       + ", " + position.coords.speed;
+}
+
+function handleSuccess(position) {
+  if (running) {
+   logGPS(position, "GPS: ");
+
+    // discard positions that have no altitude since they're likely inacurate
+    if (position.coords.altitude != null) {
+      pushCoordinates(position);
+      locations++;
+      repaint();
+    } else {
+      document.getElementById("discarded").textContent = String(discarded++);
+    }
+   } else {
+
+
+     if (position.coords.altitude == null) {
+       got_error++;
+       if (got_error > MAX_ERRORS) got_error = MAX_ERRORS;
+           logGPS(position, "[ERROR] GPS: ");
+     } else {
+       got_error--;
+       logGPS(position, "GPS: ");
+     }
+     if (got_error <= 0) {
+       got_error = 0;
+       document.getElementById("gps_text").textContent = "ready";       
+       document.getElementById("waves").style.display = "block";       
+     } else {
+       document.getElementById("gps_text").textContent = "not ready";
+       document.getElementById("waves").style.display = "none";       
+     }
+   }
+}
+
+function handleError(error) {
+  var code = "unknown";
+  switch(error.code) {
+    case error.TIMEOUT:
+      code = "timeout";
+      break;
+    case error.PERMISSION_DENIED:
+      code = "permission denied";
+      break;
+    case error.POSITION_UNAVAILABLE:
+      code = "position unavailable";
+      break;
+  }
+  if (running) {
+    document.getElementById("errors").textContent = String(++got_error);
+    document.getElementById("log").textContent = "[ERROR] GPS: " + code; 
+  } else {
+    document.getElementById("gps_text").textContent = code; 
+    // wait for 5 non-consecutive errors
+    got_error = MAX_ERRORS;
+  }
+}
+
+function timer() {
+  if (document.visibilityState == true) {
+    document.getElementById("realtime").textContent = 
+      formatTime(Math.round((duration+(Date.now()-start_time))/1000));
+  }
+}
+
+function start() {
+  if (!running) {
+   running = true;
+   start_time = Date.now();
+   intervalId = setInterval(timer, 1000);
+   document.getElementById("stop").disabled = false;
+   document.getElementById("start").textContent = "Pause";
+  } else {
+   running = false;
+   clearInterval(intervalId);
+   duration += (Date.now() - start_time);
+   document.getElementById("start").textContent = "Restart";
+   pause();
+   document.getElementById("gps_text").style.display = "inline";
+   document.getElementById("gps_data").style.display = "none";
+  }
+}
+
+function stop() {
+  //  navigator.geolocation.clearWatch(watchId);
+  running = false;
+  clearInterval(intervalId);
+  got_error = 0;
+  locations = 0;
+  discarded = 0;
+  duration  = 0;
+  document.getElementById("start").textContent = "Start";
+  document.getElementById("stop").disabled = true;
+  document.getElementById("log").textContent = "";
+  var coords = getCoordinates();
+  if (coords != null && storage) {
+    try {
+      window.localStorage.setItem("track_gps_app", coords);
+    } catch (e) {
+      document.getDocumentById("log").textContent = "[ERROR] Can't store the value " + e.code;
+    }
+  }
+  if (coords != null) {
+    if (!send("teole",coords)) {      
+      document.getElementById("gps_coords").value = coords;
+      document.getElementById("xhr_failed").style.display = "inline";
+      document.getElementById("xhr_failed").disabled = false;
+    }
+  }
+  coordinates = [];
+}
+
+function init() {  
+  if (!!document.visibilityState)
+    document.addEventListener("visibilitychange", repaint, false);
+  else
+    document.visibilityState = true;
+
+  // @@ in the future, make sure this is empty
+   if (storage) {
+    window.localStorage.setItem("track_gps_app", "");
+
+    if (window.localStorage.getItem("track_gps_app_dst_unit") === "mile") {
+      dst_unit = 0.621371192;    
+    }
+    setUnit();
+   } else {
+     document.getElementById("log").textContent = "[ERROR] no local storage";
+   }
+   if (!!navigator.geolocation) {
+     watchId = navigator.geolocation.watchPosition(handleSuccess, handleError, {enableHighAccuracy:true, maximumAge:0, timeout:1000});
+   } else {
+     document.getElementById("gps_text").textContent = "not supported";
+     document.getElementById("start").disabled = true;
+   }
+
+    var xhr = new XMLHttpRequest;
+    if (!!xhr && !("withCredentials" in xhr)) {
+      document.getElementById("xhr_failed").style.display = "inline";
+      document.getElementById("xhr_failed").disabled = true;
+    }        
+}
+
+window.addEventListener("load", init, false);
+
+function km() {
+  if (storage) {
+    window.localStorage.setItem("track_gps_app_dst_unit", "km");
+  }
+  dst_unit = 1;
+  setUnit();
+  repaint();
+}
+
+function mile() {
+  if (storage) {
+    window.localStorage.setItem("track_gps_app_dst_unit", "mile");
+  }
+  dst_unit = 0.621371192;    
+  setUnit();
+  repaint();
+}
+
+function setUnit() {  
+  var text = (dst_unit == 0.621371192)? "mile" : "km";
+  var nodes = document.getElementsByClassName("dst_unit");
+  i = 0;
+  do {
+    nodes[i].textContent = text;
+  } while (++i < nodes.length);
+}
+
+]]>
+ </script>
+<style>
+body { background-color: #6699ff; }
+section table { margin: auto; }
+section p { text-align: center;}
+.text { font-size: 150%; }
+button {
+  font-size: 175%;
+  padding-left: 1ex;
+  padding-right: 1ex;
+  margin: 0;
+}
+#log { font-size: 75%; }
+#errors { color: red }
+#discarded { color: #f88 }
+table { border-collapse: collapse; }
+th { text-align: left; border-right: 1px solid #ccc; padding-right: 0.5ex;}
+td:nth-child(2) { text-align: right; width: 8ex; padding-right: 0.5ex;}
+h1 { text-align: right; padding-right: 1ex; font-family: cursive; margin: 0;}
+#gps_data { display: none }
+
+nav #atab0 { color: #ccc; cursor: default; }
+#vtab0:target ~ #tab0 { display: block }
+#vtab0:target ~ #tab1 { display: none }
+#vtab0:target ~ nav #atab1 { color: #6699ff; cursor: pointer; }
+#vtab0:target ~ nav #atab0 { color: #ccc; cursor: default; }
+
+#tab1 { display: none; }
+#vtab1:target ~ #tab0 { display: none }
+#vtab1:target ~ #tab1 { display: block }
+#vtab1:target ~ nav #atab0 { color: #6699ff; cursor: pointer; }
+#vtab1:target ~ nav #atab1 { color: #ccc; cursor: default; }
+
+section.tab_anchor { display: none; }
+
+nav {
+  border-bottom: 1px solid white;
+  padding-bottom: 2px;
+  margin-bottom: 5px;
+}
+nav a {
+  border: 1px solid white;
+  background: white;
+  color: #6699ff;
+  padding: 2px;
+}
+</style>
+</head>
+<body>
+<section id='vtab0' class='tab_anchor'></section>
+<section id='vtab1' class='tab_anchor'></section>
+
+<h1>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.6261 299.95209" width="36" height="40">
+  <g transform="translate(-317.72197,-178.00957)">
+      <g transform="matrix(2.8428417,1.4908353,-1.4908353,2.8428417,85.009337,-2057.1417)">
+
+      <g id='waves' style='display: none'>
+      <g transform="matrix(0.83109018,-0.84335752,0.84335752,0.83109018,-425.72131,478.52845)">
+        <path style="fill:#E54B26" d="m 443.65385,563.57293 c -1.05193,-0.0129 -2.10701,0.0598 -3.12744,0.18048 l 0.06,2.75094 c 0.99036,-0.12733 2.00989,-0.19417 3.0337,-0.18162 12.83055,0.15709 23.11001,11.57671 22.93914,25.53274 -0.002,0.17643 -0.001,0.35569 -0.007,0.53121 l 2.50057,-0.0319 c 0.004,-0.15699 0.004,-0.31113 0.006,-0.46872 0.18944,-15.473 -11.18011,-28.13898 -25.40528,-28.31315 z"/>
+        <path style="fill:#E54B26" d="m 442.89836,570.53348 c -0.79783,-0.01 -1.59802,0.0454 -2.37195,0.13688 l 0.0455,2.52833 c 0.75111,-0.0966 1.52436,-0.14725 2.30084,-0.13774 9.73108,0.11913 16.95911,8.33817 16.82952,18.92284 -0.002,0.1338 -10e-4,0.26976 -0.005,0.40289 l 2.46472,-0.0242 c 0.003,-0.11907 0.003,-0.23598 0.004,-0.35547 0.14368,-11.73519 -8.47933,-21.34145 -19.26811,-21.47354 z"/>
+        <path style="fill:#E54B26" d="m 442.20847,576.88946 c -0.56577,-0.007 -1.13323,0.0322 -1.68206,0.0971 l 0.0323,2.42658 c 0.53265,-0.0685 1.081,-0.10442 1.63165,-0.0977 6.90079,0.46331 10.91426,5.09 11.13804,12.78552 -10e-4,0.0949 -7.7e-4,0.19129 -0.003,0.28572 l 2.54446,-0.0171 c 0.002,-0.0845 0.002,-0.16735 0.003,-0.25208 0.10189,-8.32201 -6.01312,-15.13429 -13.66399,-15.22797 z"/>
+      </g>
+      </g>
+      <g transform="translate(4.3272904,0)">
+        <path style="fill:#E54B26" d="m 428.82958,632.50381 c 1.51739,8.39514 3.18843,16.70366 3.25039,25.86381 13.43389,-7.07008 16.24573,-8.60045 29.67957,-15.67062 -4.84405,-3.37683 -9.59166,-7.39353 -13.56307,-11.22903 -6.14118,1.98174 -12.37667,2.63171 -19.36689,1.03584 z m 3.03559,3.5588 2.90054,0.34654 c 1.65734,7.68989 1.24429,15.11973 -0.19508,18.63703 -0.5999,-6.11993 -0.9108,-10.44519 -2.70546,-18.98357 z"/>
+        <path style="fill:#E54B26" d="m 398.11826,602.8218 c 5.46403,15.3042 20.05405,26.26585 37.2409,26.32057 17.18753,0.0547 31.81943,-10.80285 37.38029,-26.07291 l -74.62119,-0.24766 z m 6.11343,4.37701 6.55964,0.18844 c 4.52018,8.23085 9.05564,11.77782 15.40988,16.82483 -9.13544,-2.75636 -16.94711,-7.80772 -21.96952,-17.01327 z"/>
+        <g transform="translate(2.1996971,2)">
+          <rect y="594.76306" x="431.58765" height="8.0812206" width="3.2829957" style="fill:#E54B26"/>
+          <path d="m 436.63842,594.00549 c 0,2.0921 -1.52638,3.78808 -3.40926,3.78808 -1.88289,0 -3.40927,-1.69598 -3.40927,-3.78808 0,-2.09209 1.52638,-3.78807 3.40927,-3.78807 1.88288,0 3.40926,1.69598 3.40926,3.78807 z" style="fill:#E54B26"/>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>
+ HTML5 Track</h1>
+
+<nav>
+<a id='atab0' href='#vtab0'>Tracker</a>
+<a id='atab1' href='#vtab1'>Settings</a>
+</nav>
+
+
+<section id='tab0'>
+<table class='text'>
+<tbody>
+<tr>
+<th>Distance</th> <td id='distance'>0</td><td class='dst_unit'>km</td>
+</tr>
+<tr>
+<th>Time</th><td id='realtime'>00:00</td><td></td>
+</tr>
+<tr>
+<th>Speed</th><td id='speed'>0</td><td><span class='dst_unit'>km</span>/h</td>
+</tr>
+<tr>
+<th>Pace</th><td id='pace'>0</td><td>min/<span class='dst_unit'>km</span></td>
+</tr>
+<tr>
+<th>GPS</th><td colspan='2'><span id='gps_text'>not started</span><span id='gps_data'><span id='errors'>0</span>/<span id='discarded'>0</span>/<span id='locations'>0</span></span></td>
+</tr>
+</tbody>
+</table>
+
+<section>
+<p>
+<button type='button' id='start'
+  onclick='start()'>Start</button>
+
+<button type='button' id='stop' disabled='disabled'
+  onclick='stop()'>Stop</button>
+
+<form action='http://teole.jfouffa.org/2011/07/html5track.php' method='post'>  
+ <input type='hidden' name='gps_coords' id='gps_coords' value='13,-71,42,-20 '/>
+ <input type='hidden' name='user' value='teole'/>
+
+ <button type='submit' id='xhr_failed' style='display:none'>Send</button>
+</form>
+
+</p>
+
+</section>
+
+</section>
+
+<section id='tab1'>
+
+<p class='text'>Current unit: <span class='dst_unit'>km</span></p>
+
+<p>
+<button type='button' id='start' onclick='km()'>KM</button>
+
+<button type='button' id='stop' onclick='mile()'>MILE</button>
+</p>
+
+</section>
+
+<p id='log'>
+</p>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/templates/hello.ssp	Mon Aug 22 10:52:48 2011 -0400
@@ -0,0 +1,3 @@
+<%@ var name: String = "Deron Williams" %>
+<%@ var city: String = "Salt Lake City" %>
+<h1>Hello, ${name} from ${city}</h1>
--- a/src/main/scala/Main.scala	Mon Aug 22 10:32:32 2011 -0400
+++ b/src/main/scala/Main.scala	Mon Aug 22 10:52:48 2011 -0400
@@ -1,3 +1,24 @@
 package org.w3.virtualtrainer
 
+import unfiltered.request._
+import unfiltered.response._
 
+import unfiltered.scalate._
+
+object VirtualTrainerApp {
+
+  def main(args: Array[String]) {
+
+    val echo = unfiltered.filter.Planify {
+      case Path(Seg(p :: Nil)) => ResponseString(p)
+    }
+
+    val test = unfiltered.filter.Planify {
+       case req @ Path(Seg("test" :: Nil)) => Ok ~> Scalate(req, "geolocation.ssp")
+    }
+
+    unfiltered.jetty.Http(2719).filter(test).filter(echo).run()
+
+  }
+
+}