PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/static/js/gmaptools.backbone.js

https://github.com/gotomypc/gmaptools
JavaScript | 638 lines | 525 code | 50 blank | 63 comment | 32 complexity | 22d363b6f216991c19fd7580ff2d970b MD5 | raw file
  1. /*!
  2. * gmaptools backbone JavaScript Library v0.1
  3. * http://.../
  4. *
  5. * Copyright 2012, Janos Gyerik
  6. * http://.../license
  7. *
  8. * Date: Sun Sep 16 23:00:33 CEST 2012
  9. */
  10. // the basic namespace
  11. // TODO: put in app.js
  12. var App = window.App = {};
  13. _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
  14. // classes
  15. // TODO: put in app/*.js
  16. App.MapInfoDetails = Backbone.View.extend({
  17. initialize: function() {
  18. this.model.on('change', this.render, this);
  19. },
  20. template: _.template($('#mapinfo-details-template').html()),
  21. render: function() {
  22. this.$el.html(this.template(this.model.toJSON()));
  23. return this;
  24. }
  25. });
  26. App.MapInfoQuickView = Backbone.View.extend({
  27. initialize: function() {
  28. this.model.on('change', this.render, this);
  29. },
  30. template: _.template($('#mapinfo-quickview-template').html()),
  31. render: function() {
  32. this.$el.html(this.template(this.model.toJSON()));
  33. return this;
  34. }
  35. });
  36. App.LocationFactory = Backbone.Collection.extend({
  37. getLocation: function(lat, lon) {
  38. return new google.maps.LatLng(lat, lon);
  39. }
  40. });
  41. App.MarkerFactory = Backbone.Collection.extend({
  42. initialize: function(map) {
  43. this.map = map;
  44. },
  45. getMarker: function(pos, icon) {
  46. var marker = new google.maps.Marker({
  47. position: pos,
  48. map: this.map,
  49. title: pos.toString(),
  50. icon: icon
  51. });
  52. //markers.push(marker);
  53. return marker;
  54. }
  55. });
  56. App.MarkerImageFactory = Backbone.Collection.extend({
  57. baseurl: 'http://maps.gstatic.com/intl/en_us/mapfiles/ms/micons',
  58. initialize: function() {
  59. this.presets = {
  60. search: this.baseurl + '/red-dot.png',
  61. latlon: this.baseurl + '/blue-dot.png',
  62. localSearch: this.baseurl + '/yellow-dot.png',
  63. geocode: this.baseurl + '/orange-dot.png'
  64. };
  65. },
  66. getPresetMarkerImage: function(name) {
  67. var src = this.presets[name];
  68. return new google.maps.MarkerImage(src);
  69. },
  70. getCustomMarkerImage: function(src, options) {
  71. if (options.size) {
  72. return new google.maps.MarkerImage(src, null, null, null, new google.maps.Size(options.size, options.size));
  73. }
  74. else {
  75. return new google.maps.MarkerImage(src);
  76. }
  77. }
  78. });
  79. App.Place = Backbone.Model.extend({
  80. defaults: {
  81. lat: null,
  82. lon: null,
  83. name: '',
  84. address: '',
  85. types: [],
  86. marker: null,
  87. icon: '', // derived from .marker
  88. typesStr: '' // derived from .types
  89. },
  90. initialize: function() {
  91. this.on('change:types', this.onTypesChanged, this);
  92. this.on('change:marker', this.onMarkerChanged, this);
  93. this.trigger('change:types');
  94. this.trigger('change:marker');
  95. },
  96. onTypesChanged: function() {
  97. var types = this.get('types');
  98. var typesStr = _.map(types, function(item) {
  99. return '<span class="badge">' + item + '</span>';
  100. }).join(' ');
  101. this.set({typesStr: typesStr});
  102. },
  103. onMarkerChanged: function() {
  104. this.set({icon: this.get('marker').icon.url});
  105. },
  106. destroy: function() {
  107. this.get('marker').setMap(null);
  108. }
  109. });
  110. App.Places = Backbone.Collection.extend({
  111. model: App.Place,
  112. initialize: function() {
  113. // TODO: I don't know why, but without this line
  114. // the trigger in PlaceView does not work...
  115. this.on('add', this.onChange, this);
  116. },
  117. onChange: function() {
  118. }
  119. });
  120. App.PlaceView = Backbone.View.extend({
  121. className: 'place',
  122. template: _.template($('#mapinfo-places-item-template').html()),
  123. events: {
  124. 'click .goto': 'goto',
  125. 'click a.destroy': 'clear'
  126. },
  127. goto: function() {
  128. var lat = this.model.get('lat');
  129. var lon = this.model.get('lon');
  130. this.map.trigger('gotoLatLon', lat, lon);
  131. },
  132. initialize: function() {
  133. this.$el.html(this.template(this.model.toJSON()));
  134. return this;
  135. },
  136. clear: function() {
  137. this.model.destroy();
  138. this.remove();
  139. }
  140. });
  141. App.PlacesView = Backbone.View.extend({
  142. initialize: function(options) {
  143. this.map = options.map;
  144. this.collection.on('add', this.add, this);
  145. },
  146. add: function(place) {
  147. var view = new App.PlaceView({model: place});
  148. view.map = this.map;
  149. this.$el.prepend(view.render().el);
  150. App.placesTab.activate();
  151. }
  152. });
  153. App.MapController = Backbone.Model.extend({
  154. defaults: {
  155. status: 'N.A.',
  156. lat: 35.68112175616982,
  157. lon: 139.76703710980564,
  158. zoom: 14,
  159. address: 'N.A.',
  160. sw_latitude: 'N.A.',
  161. sw_longitude: 'N.A.',
  162. ne_latitude: 'N.A.',
  163. ne_longitude: 'N.A.'
  164. },
  165. initialize: function(places) {
  166. this.places = places;
  167. // initialize helper factory objects
  168. this.locationFactory = new App.LocationFactory();
  169. this.markerImageFactory = new App.MarkerImageFactory();
  170. // initialize google map objects
  171. if (google.loader.ClientLocation) {
  172. var lat = google.loader.ClientLocation.latitude;
  173. var lon = google.loader.ClientLocation.longitude;
  174. this.defaults.lat = lat;
  175. this.defaults.lon = lon;
  176. this.set({lat: lat, lon: lon});
  177. }
  178. var center = this.locationFactory.getLocation(this.get('lat'), this.get('lon'));
  179. var options = {
  180. zoom: this.get('zoom'),
  181. center: center,
  182. mapTypeId: google.maps.MapTypeId.ROADMAP
  183. };
  184. this.map = new google.maps.Map(document.getElementById('map_canvas'), options);
  185. this.placesService = new google.maps.places.PlacesService(this.map);
  186. this.geocoder = new google.maps.Geocoder();
  187. this.markerFactory = new App.MarkerFactory(this.map);
  188. // make sure *this* is bound to *this* in the map event handlers
  189. _.bindAll(this, 'centerChanged', 'zoomChanged', 'dragend');
  190. // google maps event handlers
  191. google.maps.event.addListener(this.map, 'center_changed', this.centerChanged);
  192. google.maps.event.addListener(this.map, 'zoom_changed', this.zoomChanged);
  193. google.maps.event.addListener(this.map, 'dragend', this.dragend);
  194. // event handlers for latlon tool
  195. this.on('getCurrentLatLon', this.getCurrentLatLon, this);
  196. this.on('gotoHome', this.gotoHome, this);
  197. this.on('gotoLatLon', this.gotoLatLon, this);
  198. this.on('dropPin', this.dropPin, this);
  199. // event handlers for local search tool
  200. this.on('localSearch', this.localSearch, this);
  201. this.on('geocode', this.geocode, this);
  202. },
  203. centerChanged: function() {
  204. var center = this.map.getCenter();
  205. var params = {
  206. lat: center.lat(),
  207. lon: center.lng()
  208. };
  209. var bounds = this.map.getBounds();
  210. if (bounds) {
  211. var sw = bounds.getSouthWest();
  212. var ne = bounds.getNorthEast();
  213. _.extend(params, {
  214. sw_latitude: sw.lat(),
  215. sw_longitude: sw.lng(),
  216. ne_latitude: ne.lat(),
  217. ne_longitude: ne.lng()
  218. });
  219. }
  220. this.set(params);
  221. },
  222. zoomChanged: function() {
  223. this.set({zoom: this.map.getZoom()});
  224. },
  225. dragend: function() {
  226. this.updateAddress();
  227. },
  228. getCurrentLatLon: function(callback) {
  229. callback(this.get('lat'), this.get('lon'));
  230. },
  231. gotoHome: function() {
  232. this.gotoLatLon(this.defaults.lat, this.defaults.lon);
  233. },
  234. gotoLatLon: function(lat, lon) {
  235. this.set({lat: lat, lon: lon});
  236. this.map.setCenter(this.locationFactory.getLocation(lat, lon));
  237. this.map.panTo(this.locationFactory.getLocation(lat, lon));
  238. this.updateAddress();
  239. },
  240. dropPin: function(lat, lon) {
  241. if (!(lat || lon)) {
  242. lat = this.get('lat');
  243. lon = this.get('lon');
  244. }
  245. var pos = this.locationFactory.getLocation(lat, lon);
  246. var markerImage = this.markerImageFactory.getPresetMarkerImage('latlon');
  247. this.markerFactory.getMarker(pos, markerImage);
  248. },
  249. localSearch: function(keyword) {
  250. var request = {
  251. location: this.map.getCenter(),
  252. rankBy: google.maps.places.RankBy.DISTANCE,
  253. keyword: keyword
  254. };
  255. // note: could not make this work with a var callback = function ...
  256. // had to create this.localSearchCallback to have proper
  257. // bind of *this* using _.bindAll
  258. // also, I couldn't get it to work with _.bind either...
  259. _.bindAll(this, 'localSearchCallback');
  260. this.placesService.search(request, this.localSearchCallback);
  261. },
  262. localSearchCallback: function(results, status) {
  263. this.set({status: status});
  264. if (status === google.maps.places.PlacesServiceStatus.OK) {
  265. // this.errors.hide();
  266. for (var i = results.length - 1; i >= 0; --i) {
  267. var place = results[i];
  268. var markerImage = this.markerImageFactory.getCustomMarkerImage(place.icon, {size: 25});
  269. var marker = this.markerFactory.getMarker(place.geometry.location, markerImage);
  270. this.places.add({
  271. lat: place.geometry.location.lat(),
  272. lon: place.geometry.location.lng(),
  273. name: place.name,
  274. address: place.vicinity,
  275. types: place.types,
  276. marker: marker
  277. });
  278. }
  279. }
  280. else {
  281. // this.errors.clear();
  282. // this.errors.append('Local search failed');
  283. // this.errors.show();
  284. }
  285. },
  286. geocode: function(address) {
  287. var request = {
  288. address: address,
  289. partialmatch: true
  290. };
  291. // note: could not make this work with a var callback = function ...
  292. // had to create this.geocodeCallback to have proper
  293. // bind of *this* using _.bindAll
  294. // also, I couldn't get it to work with _.bind either...
  295. _.bindAll(this, 'geocodeCallback');
  296. this.geocoder.geocode(request, this.geocodeCallback);
  297. },
  298. geocodeCallback: function(results, status) {
  299. this.updateStatusFromGeocodeResults(results, status);
  300. var typeToWords = function(name) { return name.replace(/_/g, ' '); };
  301. if (status === google.maps.GeocoderStatus.OK) {
  302. for (var i = results.length - 1; i >= 0; --i) {
  303. var result = results[i];
  304. if (i === 0) {
  305. this.map.fitBounds(result.geometry.viewport);
  306. this.map.setCenter(result.geometry.location);
  307. }
  308. var markerImage = this.markerImageFactory.getPresetMarkerImage('geocode');
  309. var marker = this.markerFactory.getMarker(result.geometry.location, markerImage);
  310. this.places.add({
  311. lat: result.geometry.location.lat(),
  312. lon: result.geometry.location.lng(),
  313. address: result.formatted_address,
  314. types: _.map(result.types, typeToWords),
  315. marker: marker
  316. });
  317. }
  318. }
  319. },
  320. updateStatusFromGeocodeResults: function(results, status) {
  321. this.set({status: status});
  322. if (status === google.maps.GeocoderStatus.OK) {
  323. // this.errors.hide();
  324. for (var i = 0; i < results.length; ++i) {
  325. var result = results[i];
  326. this.set({address: result.formatted_address});
  327. break;
  328. }
  329. } else {
  330. // this.errors.clear();
  331. // this.errors.append('Local search failed');
  332. // this.errors.show();
  333. }
  334. },
  335. updateAddress: function() {
  336. var request = {
  337. latLng: this.map.getCenter()
  338. };
  339. // note: could not make this work with a var callback = function ...
  340. // had to create this.geocodeCallback to have proper
  341. // bind of *this* using _.bindAll
  342. // also, I couldn't get it to work with _.bind either...
  343. _.bindAll(this, 'updateStatusFromGeocodeResults');
  344. this.geocoder.geocode(request, this.updateStatusFromGeocodeResults);
  345. }
  346. });
  347. App.Tool = Backbone.View.extend({
  348. activate: function() {
  349. var id = this.$el.attr('id');
  350. var anchor = $('a[href=#' + id + ']');
  351. anchor.tab('show');
  352. if (this.fieldToFocus) {
  353. this.fieldToFocus.focus();
  354. }
  355. }
  356. });
  357. App.InfoTab = App.Tool.extend();
  358. App.PlacesTab = App.Tool.extend();
  359. App.LatlonTool = App.Tool.extend({
  360. el: $('#latlon-tool'),
  361. initialize: function(options) {
  362. this.lat = this.$('.lat');
  363. this.lon = this.$('.lon');
  364. this.map = options.map;
  365. this.$('.features').popover({
  366. content: $('#features-latlon').html(),
  367. placement: 'bottom',
  368. trigger: 'hover'
  369. });
  370. },
  371. fieldToFocus: this.$('.lat'),
  372. events: {
  373. 'click .btn-goto': 'gotoLatLon',
  374. 'click .btn-pin': 'dropPin',
  375. 'click .btn-here': 'getCurrentLatLon',
  376. 'click .btn-home': 'gotoHome',
  377. 'keypress .lat': 'onEnter',
  378. 'keypress .lon': 'onEnter'
  379. },
  380. gotoLatLon: function() {
  381. var lat = this.lat.val();
  382. var lon = this.lon.val();
  383. if (lat && lon) {
  384. this.map.trigger('gotoLatLon', lat, lon);
  385. }
  386. },
  387. dropPin: function() {
  388. var lat = this.lat.val();
  389. var lon = this.lon.val();
  390. if (lat && lon) {
  391. this.map.trigger('gotoLatLon', lat, lon);
  392. this.map.trigger('dropPin', lat, lon);
  393. }
  394. else if (!(lat || lon)) {
  395. this.map.trigger('dropPin');
  396. this.getCurrentLatLon();
  397. }
  398. },
  399. getCurrentLatLon: function() {
  400. var lat = this.lat;
  401. var lon = this.lon;
  402. var callback = function(lat_, lon_) {
  403. lat.val(lat_);
  404. lon.val(lon_);
  405. };
  406. this.map.trigger('getCurrentLatLon', callback);
  407. },
  408. gotoHome: function() {
  409. this.map.trigger('gotoHome');
  410. },
  411. onEnter: function(e) {
  412. if (e.keyCode === 13) {
  413. e.preventDefault();
  414. this.gotoLatLon();
  415. }
  416. }
  417. });
  418. App.LocalSearchTool = App.Tool.extend({
  419. el: $('#localsearch-tool'),
  420. initialize: function(options) {
  421. this.keyword = this.$('.keyword');
  422. this.map = options.map;
  423. this.$('.features').popover({
  424. content: $('#features-localsearch').html(),
  425. placement: 'bottom',
  426. trigger: 'hover'
  427. });
  428. },
  429. fieldToFocus: this.$('.keyword'),
  430. events: {
  431. 'click .btn-local': 'localSearch',
  432. 'keypress .keyword': 'onEnter'
  433. },
  434. localSearch: function() {
  435. var keyword = this.keyword.val();
  436. if (keyword) {
  437. this.map.trigger('localSearch', keyword);
  438. }
  439. },
  440. onEnter: function(e) {
  441. if (e.keyCode === 13) {
  442. e.preventDefault();
  443. this.localSearch();
  444. }
  445. }
  446. });
  447. App.GeocodeTool = App.Tool.extend({
  448. el: $('#geocode-tool'),
  449. initialize: function(options) {
  450. this.address = this.$('.address');
  451. this.map = options.map;
  452. this.$('.features').popover({
  453. content: $('#features-geocode').html(),
  454. placement: 'bottom',
  455. trigger: 'hover'
  456. });
  457. },
  458. fieldToFocus: this.$('.address'),
  459. events: {
  460. 'click .btn-geocode': 'geocode',
  461. 'keypress .address': 'onEnter'
  462. },
  463. geocode: function() {
  464. var address = this.address.val();
  465. if (address) {
  466. this.map.trigger('geocode', address);
  467. }
  468. },
  469. onEnter: function(e) {
  470. if (e.keyCode === 13) {
  471. e.preventDefault();
  472. this.geocode();
  473. }
  474. }
  475. });
  476. App.Toolbar = Backbone.View.extend({
  477. el: $('#toolbar'),
  478. events: {
  479. 'click a[href="#latlon-tool"]': 'openLatlonTool',
  480. 'click a[href="#localsearch-tool"]': 'openLocalSearchTool',
  481. 'click a[href="#geocode-tool"]': 'openGeocodeTool'
  482. },
  483. openLatlonTool: function() {
  484. App.router.openLatlonTool();
  485. },
  486. openLocalSearchTool: function() {
  487. App.router.openLocalSearchTool();
  488. },
  489. openGeocodeTool: function() {
  490. App.router.openGeocodeTool();
  491. }
  492. });
  493. App.Router = Backbone.Router.extend({
  494. routes: {
  495. 'tools/latlon': 'activateLatlon',
  496. 'tools/localSearch': 'activateLocalSearch',
  497. 'tools/geocode': 'activateGeocode',
  498. '*placeholder': 'activateLatlon'
  499. },
  500. activateTool: function(tool) {
  501. tool.activate();
  502. },
  503. activateLatlon: function() {
  504. this.activateTool(App.latlonTool);
  505. },
  506. activateLocalSearch: function() {
  507. this.activateTool(App.localSearchTool);
  508. },
  509. activateGeocode: function() {
  510. this.activateTool(App.geocodeTool);
  511. },
  512. openLatlonTool: function() {
  513. this.navigate('tools/latlon', {trigger: true});
  514. },
  515. openLocalSearchTool: function() {
  516. this.navigate('tools/localSearch', {trigger: true});
  517. },
  518. openGeocodeTool: function() {
  519. this.navigate('tools/geocode', {trigger: true});
  520. }
  521. });
  522. App.OfflineView = Backbone.View.extend({
  523. el: $('#main-content'),
  524. template: _.template($('#offline-template').html()),
  525. render: function() {
  526. this.$el.html(this.template());
  527. return this;
  528. }
  529. });
  530. function onGoogleMapsReady() {
  531. // instances
  532. // TODO: put in setup.js
  533. App.places = new App.Places();
  534. App.mapController = new App.MapController(App.places);
  535. App.latlonTool = new App.LatlonTool({map: App.mapController});
  536. App.localSearchTool = new App.LocalSearchTool({map: App.mapController});
  537. App.geocodeTool = new App.GeocodeTool({map: App.mapController});
  538. App.toolbar = new App.Toolbar();
  539. App.infoTab = new App.InfoTab({el: '#mapinfo-details'});
  540. App.placesTab = new App.PlacesTab({el: '#mapinfo-places'});
  541. App.detailedstats = new App.MapInfoDetails({
  542. el: $('#mapinfo-details'),
  543. model: App.mapController
  544. });
  545. App.quickstats = new App.MapInfoQuickView({
  546. el: $('#mapinfo-quickview'),
  547. model: App.mapController
  548. });
  549. App.placesView = new App.PlacesView({
  550. el: $('#mapinfo-places'),
  551. collection: App.places,
  552. map: App.mapController
  553. });
  554. App.router = new App.Router();
  555. // initialize the Backbone router
  556. Backbone.history.start();
  557. // debugging
  558. //App.latlonTool.activate();
  559. //App.latlonTool.getCurrentLatLon();
  560. //App.latlonTool.lat.val(2);
  561. //App.latlonTool.lon.val(3);
  562. //App.latlonTool.gotoLatLon();
  563. //App.latlonTool.dropPin();
  564. //App.latlonTool.gotoHome();
  565. //App.placesTab.activate();
  566. //App.localSearchTool.activate();
  567. //App.localSearchTool.keyword.val('pizza');
  568. //App.localSearchTool.localSearch();
  569. //App.placesTab.activate();
  570. //App.geocodeTool.activate();
  571. //App.geocodeTool.address.val('6 Rue Gassendi, 75014 Paris, France');
  572. //App.geocodeTool.geocode();
  573. // this is to force all views to render
  574. App.mapController.trigger('change');
  575. }
  576. function offlineMode() {
  577. var offlineView = new App.OfflineView();
  578. offlineView.render();
  579. }
  580. $(function() {
  581. if (typeof google !== 'undefined' && typeof google.maps !== 'undefined' && typeof google.maps.event !== 'undefined') {
  582. google.maps.event.addDomListener(window, 'load', onGoogleMapsReady);
  583. }
  584. else {
  585. offlineMode();
  586. }
  587. });
  588. // eof