PageRenderTime 73ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/dowsing/mobile/app.js

https://github.com/OpenHamilton/Alpha-site
JavaScript | 440 lines | 279 code | 58 blank | 103 comment | 26 complexity | d78e8bb52cbb0910e1514e43636e83b8 MD5 | raw file
  1. // Copyright 2011 OpenHamilton. All Rights Reserved.
  2. /**
  3. * @fileoverview Mobile version of Dowsing that relies on zepto.js, backbone.js and handlebars.js
  4. * Uses hash-bangs to track application state.
  5. * @author gavin.schulz@gmail.com (Gavin Schulz)
  6. */
  7. /**
  8. * Register some helpers that are used in templating
  9. */
  10. $(document).ready(function() {
  11. Handlebars.registerPartial('header', $("#header-template").html());
  12. /**
  13. * Returns a properly decimalled cost
  14. * @param {number} cost The cost number to format
  15. * @return {number} The cost with 2 decimal places gauranteed
  16. */
  17. Handlebars.registerHelper('showCost', function(cost) {
  18. return cost.toFixed(2);
  19. });
  20. /**
  21. * Returns the html for showing a static google map
  22. * @param {object} context The handlebars context that this helper is called from
  23. * @return {string} The html need for showing the static map
  24. */
  25. Handlebars.registerHelper('staticMap', function(context) {
  26. return '<img src="http://maps.googleapis.com/maps/api/staticmap?center=' +
  27. context.row.Lat + ',' + context.row.Long +
  28. '&zoom=15&size=283x240&maptype=roadmap&sensor=false&markers=color:red%7C' +
  29. context.row.Lat + ',' + context.row.Long + '" class="map" />';
  30. });
  31. });
  32. /* Main app object */
  33. var Dowsing = {
  34. /**
  35. * ID of the Google Fusion Table that stores the swimming data
  36. * @type {number}
  37. */
  38. fusionTableId: 1203335,
  39. /**
  40. * The google fusion table api url
  41. * @type {string}
  42. */
  43. fusionTableAPIUrl: 'https://www.google.com/fusiontables/api/query?sql=',
  44. /**
  45. * Stores the last query we did to determine if back to results link should be displayed
  46. * @type {string}
  47. */
  48. lastQuery: '',
  49. /**
  50. * Keeps track of which page of the search results we're on
  51. * @type {number}
  52. */
  53. page: 1,
  54. /**
  55. * Number of results to return from Google Fusion Table query
  56. * @type {number}
  57. */
  58. results: 5,
  59. /**
  60. * Backbone.js application objects storage
  61. */
  62. Views: {},
  63. Routers: {},
  64. Collections: {},
  65. init: function() {
  66. this.router = new Dowsing.Routers.Spots();
  67. Backbone.history.start();
  68. }
  69. };
  70. // Create a new model
  71. var Spot = Backbone.Model.extend();
  72. // Creates a collection of `Spot`s
  73. var Spots = Backbone.Collection.extend({ model: Spot });
  74. /* Application routing object */
  75. Dowsing.Routers.Spots = Backbone.Router.extend({
  76. _spots: null,
  77. /**
  78. * Defines all the possibles routes for the app.
  79. */
  80. routes : {
  81. "" : "index",
  82. "home" : "index",
  83. "info" : "info",
  84. "search/:address" : "search",
  85. "search/:address/:page" : "search",
  86. "display/:tag" : "display"
  87. },
  88. /**
  89. * Binds functions that are called by other parts of the application.
  90. */
  91. initialize: function() {
  92. _.extend(this, Backbone.Events);
  93. _.bindAll(this, "search");
  94. _.bindAll(this, "processData");
  95. _.bindAll(this, "processDetails");
  96. },
  97. index: function() {
  98. this.navigate("home");
  99. var indexView = new Dowsing.Views.Index();
  100. // Makes a call to the search function accessible from the view
  101. indexView.bind("index_view:search", this.search);
  102. indexView.render();
  103. },
  104. /**
  105. * Shows the info pane when the info icon is clicked
  106. */
  107. info: function() {
  108. this.navigate("info");
  109. var infoView = new Dowsing.Views.Info();
  110. infoView.render();
  111. },
  112. /**
  113. * Makes a call to the Google Fusion Table API to make a search request.
  114. * @param {string} address The address for which to find close-by water spots for.
  115. * @param {number} page The page of the results that is being requested.
  116. */
  117. search: function(address, page) {
  118. if (page == undefined) { page = 1; }
  119. this.navigate("search/" + address + "/" + page);
  120. // Record this as the last query we did
  121. Dowsing.lastQuery = address;
  122. Dowsing.page = page;
  123. // Show the loading gif
  124. $("#content").append("<div class=\"loading\"></div>");
  125. var self = this;
  126. var geocoder = new google.maps.Geocoder();
  127. geocoder.geocode({ address: decodeURIComponent(address) }, function(results, status) {
  128. if (status == google.maps.GeocoderStatus.OK) {
  129. $.getJSON(self.constructQueryURL(results[0].geometry.location, page) + "&jsonCallback=?", self.processData);
  130. } else {
  131. // Render error
  132. self.navigate("home", true);
  133. }
  134. });
  135. },
  136. /**
  137. * Constructs the query url for returning the neccesary data
  138. * Query: SELECT * FROM {Dowsing.FusionTableId} ORDER BY
  139. * ST_DISTANCE(Lat, LATLNG({location.lat()},{location.lng()}))
  140. * OFFSET {start} LIMIT {Dowsing.Results};
  141. * @param {google.maps.LatLng} location Location as returned by the google geocoder.
  142. * @param {number} start The row from which to start returning results
  143. * @return {string} Returns the full url that needs to be loaded to perform the query
  144. */
  145. constructQueryURL: function(location, start) {
  146. var url = Dowsing.fusionTableAPIUrl;
  147. url += "SELECT+%2A+FROM+" + Dowsing.fusionTableId;
  148. url += "+ORDER+BY+ST_DISTANCE%28Lat%2CLATLNG%28";
  149. url += encodeURIComponent(location.lat()) + "%2C" + encodeURIComponent(location.lng()) + "%29%29";
  150. url += "+OFFSET+" + (start - 1) * Dowsing.results + "+LIMIT+" + Dowsing.results;
  151. return url;
  152. },
  153. /**
  154. * Constructs the query url for returning data for a specific water spot
  155. * Query: SELECT * FROM {Dowsing.FusionTableId} WHERE
  156. * Lat = {{tag[0]}} AND Long = {{tag[1]}}
  157. * @param {string} tag A string containing the latitude and longitude of the water spot
  158. * joined via an underscore (_)
  159. * @return {string} Returns the full url that needs to be loaded to perform the query
  160. */
  161. constructDisplayURL: function(tag) {
  162. tag = decodeURIComponent(tag).split("_");
  163. var url = Dowsing.fusionTableAPIUrl;
  164. url += "SELECT+%2A+FROM+" + Dowsing.fusionTableId;
  165. url += "+WHERE+Lat%3D" + encodeURIComponent(tag[0]) + "+AND+Long%3D" + encodeURIComponent(tag[1]);
  166. return url;
  167. },
  168. /**
  169. * Processes the results returned by a search call
  170. * Creates the Spots collection using the returned data
  171. * Finally, renders the results view
  172. * @param {object} results The resulting JSON returned from the Google Fusion Table query
  173. */
  174. processData: function(results) {
  175. var _Spots = [];
  176. for (var i = 0, row; row = results.table.rows[i]; i++) {
  177. var _row = {};
  178. for (index in results.table.cols) {
  179. /* Only get the fields we really need for the result display */
  180. if (results.table.cols[index] == "Icon" ||
  181. results.table.cols[index] == "Lat" ||
  182. results.table.cols[index] == "Long" ||
  183. results.table.cols[index] == "Facility Name" ||
  184. results.table.cols[index] == "Address" ||
  185. results.table.cols[index] == "City" ||
  186. results.table.cols[index] == "Type") {
  187. // Add to row information
  188. _row[results.table.cols[index].split(' ').join('_')] = row[index];
  189. }
  190. }
  191. _Spots.push(new Spot(_row));
  192. }
  193. this.spots = new Spots(_Spots);
  194. var resultsView = new Dowsing.Views.Results({collection: this.spots});
  195. resultsView.render();
  196. },
  197. /**
  198. * Performs the call to display a single water spot
  199. * @param {string} tag The tag is in the format {{lat}}_{{long}}
  200. */
  201. display: function(tag) {
  202. this.navigate("display/" + tag);
  203. if (Dowsing.lastQuery != '') {
  204. $("#content").append("<div class=\"loading\"></div>");
  205. }
  206. $.getJSON(this.constructDisplayURL(tag)+"&jsonCallback=?", this.processDetails);
  207. },
  208. /**
  209. * Process the results of a display query and renders a single water spot display
  210. * @param {object} results The resulting JSON returned by the Google Fusion Table API
  211. */
  212. processDetails: function(results) {
  213. row = results.table.rows[0];
  214. var _row = {};
  215. for (index in results.table.cols) {
  216. // Don't copy the info window html that is also stored in the tables
  217. if (results.table.cols[index].indexOf("Window") == -1) {
  218. // Add to row information
  219. _row[results.table.cols[index].split(' ').join('_')] = row[index];
  220. }
  221. }
  222. var detailsView = new Dowsing.Views.Details({model: new Spot(_row)});
  223. detailsView.render();
  224. }
  225. });
  226. /* Backbone view for the index page */
  227. Dowsing.Views.Index = Backbone.View.extend({
  228. el: $("#content"),
  229. events: {
  230. "click #home": "home",
  231. "click #info": "info",
  232. "submit form": "search"
  233. },
  234. initialize: function() {
  235. _.extend(this, Backbone.Events);
  236. _.bindAll(this, 'render');
  237. this.render();
  238. return this;
  239. },
  240. search: function(e) {
  241. e.preventDefault();
  242. this.trigger("index_view:search", encodeURIComponent($("#address").val()));
  243. return false;
  244. },
  245. info: function(e) {
  246. e.preventDefault();
  247. Dowsing.router.navigate("info", true);
  248. return false;
  249. },
  250. home: function(e) {
  251. e.preventDefault();
  252. Dowsing.router.navigate("home", true);
  253. return false;
  254. },
  255. render: function() {
  256. var homeTemplate = Handlebars.compile($("#home-template").html());
  257. this.el.html(homeTemplate({title: "Dowsing"}));
  258. window.scrollTo(0, 1);
  259. }
  260. });
  261. // Backbone View object for the info page
  262. Dowsing.Views.Info = Backbone.View.extend({
  263. el: $("#content"),
  264. events: {
  265. "click #home": "home",
  266. "click #info": "info"
  267. },
  268. initialize: function() {
  269. _.bindAll(this, 'render');
  270. this.render();
  271. return this;
  272. },
  273. home: function(e) {
  274. e.preventDefault();
  275. Dowsing.router.navigate("home", true);
  276. return false;
  277. },
  278. info: function(e) {
  279. e.preventDefault();
  280. return false;
  281. },
  282. render: function() {
  283. var homeTemplate = Handlebars.compile($("#info-template").html());
  284. this.el.html(homeTemplate({title: "About Dowsing"}));
  285. window.scrollTo(0, 1);
  286. }
  287. });
  288. // Backbone view for displaying the results
  289. Dowsing.Views.Results = Backbone.View.extend({
  290. el : $("#content"),
  291. events : {
  292. "click #home" : "home",
  293. "click #info" : "info",
  294. "click .panel" : "showDetail",
  295. "click .prev" : "prev",
  296. "click .next" : "next"
  297. },
  298. initialize : function() {
  299. _.bindAll(this, 'render');
  300. this.render();
  301. return this;
  302. },
  303. showDetail : function(e) {
  304. e.preventDefault();
  305. Dowsing.router.navigate("display/" + $(e.currentTarget).attr("tag"), true);
  306. return false;
  307. },
  308. home : function(e) {
  309. e.preventDefault();
  310. Dowsing.router.navigate("home", true);
  311. return false;
  312. },
  313. info : function(e) {
  314. e.preventDefault();
  315. Dowsing.router.navigate("info", true);
  316. return false;
  317. },
  318. prev : function(e) {
  319. $(".nav_btns").empty();
  320. e.preventDefault();
  321. $(".prev").css("display", "none");
  322. Dowsing.router.navigate("search/" + Dowsing.lastQuery + "/" + (parseInt(Dowsing.page) - 1), true);
  323. return false;
  324. },
  325. next : function(e) {
  326. $(".nav_btns").empty();
  327. e.preventDefault();
  328. $(".next").css("display", "none");
  329. Dowsing.router.navigate("search/" + Dowsing.lastQuery + "/" + (parseInt(Dowsing.page) + 1), true);
  330. return false;
  331. },
  332. render : function() {
  333. var resultTemplate = Handlebars.compile($("#result-template").html());
  334. this.el.html(resultTemplate({
  335. prev: (Dowsing.page > 1) ? 1 : null,
  336. next: (this.collection.length >= Dowsing.results) ? 1 : null,
  337. title: "Search Results",
  338. results: this.collection.toJSON()
  339. }));
  340. window.scrollTo(0, 1);
  341. }
  342. });
  343. /* Backbone view for the details page */
  344. Dowsing.Views.Details = Backbone.View.extend({
  345. el : $("#content"),
  346. events : {
  347. "click #home": "home",
  348. "click #info": "info",
  349. "click .back": "back"
  350. },
  351. initialize : function() {
  352. _.bindAll(this, 'render');
  353. this.render();
  354. return this;
  355. },
  356. back : function(e) {
  357. e.preventDefault();
  358. Dowsing.router.navigate("search/" + Dowsing.lastQuery + "/" + Dowsing.page, true);
  359. return false;
  360. },
  361. home : function(e) {
  362. e.preventDefault();
  363. Dowsing.router.navigate("home", true);
  364. return false;
  365. },
  366. info : function(e) {
  367. e.preventDefault();
  368. Dowsing.router.navigate("info", true);
  369. return false;
  370. },
  371. render : function() {
  372. var self = this;
  373. var detailTemplate = Handlebars.compile($("#detail-template").html());
  374. var content = detailTemplate({
  375. title: "Details",
  376. canGoBack : (Dowsing.lastQuery == '') ? "none" : "block",
  377. row : this.model.toJSON()
  378. });
  379. self.el.html(content);
  380. window.scrollTo(0, 1);
  381. }
  382. });