Google Map 搭配 BackboneJS, RequireJS

圖片
Using GMap in RequireJS, with Backbone

身為一名網頁開發者,我過去做過不少高度依賴 JavaScript 的網站。大多時候,我只是用單一一個 JavaScript 檔案,加上一堆 jQuery 外掛來完成工作。隨著專案越做越大,最後就變成一大坨難以維護的 JavaScript,整體結構非常混亂。

直到最近,我開始考慮使用框架或架構設計模式來整理我的程式碼。因此這是我的第一步:嘗試整合一些新興、有用的 JavaScript 工具,來建立一個以地圖為基礎的網頁應用程式。而我選用的工具包括:

一開始其實很簡單,只需要加入:

<script data-main="js/core" src="/js/libs/require.js"></script>

事實上,這會是 HTML 頁面中唯一需要引入的 JavaScript 檔案。

注意這裡的 js/core,它會作為核心腳本,負責初始化設定並啟動整個應用程式。

require.config({
   baseUrl: "js",
   paths: {
       jquery: 'libs/jquery-1.8.2.min',
       modernizr: 'libs/modernizr-2.6.1-respond-1.1.0',
       underscore: 'libs/underscore',
       backbone: 'libs/backbone',
       gmap:'libs/gmap',
       async: 'libs/async',
       text: 'libs/text',
       templates: '../templates'
   },
   'shim':{
       backbone:{
           'deps' :['jquery' ,'underscore'],
           'exports' : 'Backbone'
       },
       modernizr:{
           'exports' : 'Modernizr'
       },
       underscore: {
           'exports': '_'
       }
   },
   waitSeconds: 0,
   urlArgs: ''
});

require([
   'app'
], function(App){
   App.initialize();
});

將所有 JavaScript 函式庫集中放在 /js/libs 目錄下,並透過 paths 設定讓應用程式知道各個函式庫的位置。接著建立一個名為 app.js 的檔案,作為 MVC 元件的入口,並呼叫 initialize 函式。

define(['routers/home'], function(router){
   var initialize = function(){
       this.router = new router();
   }
   return { initialize: initialize};
});

接著設定應用程式的控制器(Router),並進行初始化。

define([
   'jquery',
   'backbone',
   'underscore',
   'views/menuview',
   'views/mapview'],
   function($, Backbone, _, menuView, mapView){
       var Router = Backbone.Router.extend({
           initialize: function(){
               this.menuView = new menuView;
               this.menuView.bind('collectionSelected',this.setLocation, this);

               this.mapView = new mapView;
               this.mapView.render();
               Backbone.history.start({pushState: true, root:'/'});
           },
           routes: {
               '':'setLocation',
               'places/*name': 'getLocation'
           },
          setLocation: function(e){
               if (e){Backbone.history.navigate('places/'+e.get('name').replace(' ','+'), false);}
               this.mapView.updateMap(e);
           }
           ,
           getLocation: function(e){
               this.menuView.updateModel(e);
           }
       });

       return Router;
   });

這個控制器(Router)可以監聽傳入的參數,並指示各個 View 去取得對應的資料集合,然後將資料分別傳給兩個 View:menuView(地點選擇清單)以及 mapView(內嵌 Google 地圖的視圖)。

define([
   'jquery',
   'underscore',
   'backbone',
   'models/model'
], function($, _, Backbone, mapModel){
   var mapCollection = Backbone.Collection.extend({
       initialize: function(){
       },
       model: mapModel,
       url: '/js/services/places.json',
       parse:function(response){
           return response;
       }
   });

   return mapCollection;
});

Collection 只是從一個靜態 JSON 檔案抓取資料,這裡我簡單放了我最近去過的四個國家,以及它們的經緯度。

define([
   'jquery',
   'underscore',
   'backbone',
   'gmap',
   'collections/collection',
   'text!templates/map.html'
], function($, _, Backbone, googleMap, mapCollection, mapTemplate){

   var MapView = Backbone.View.extend({
       el: '#map',
       template:_.template(mapTemplate),
       initialize: function(){
           var map;
       },
       updateMap:function(e){
           if (!e) {this.createMap();}
           else {map.setCenter( new google.maps.LatLng(e.get('lat'), e.get('lng') ));}
       },
       createMap:function(){
           if (navigator && navigator.geolocation) {
               navigator.geolocation.getCurrentPosition(
                   function(position) {
                       var pos = new google.maps.LatLng(
                           position.coords.latitude,
                           position.coords.longitude
                       );
                       map.setCenter(pos);
                   }, function(e) {
                       switch(e){
                           case 1:
                               console.log('使用者拒絕提供定位資訊。');
                               break;
                           case 2:
                               console.log('無法取得定位資訊。');
                               break;
                           case 3:
                               console.log('取得定位資訊逾時。');
                               break;
                           default:
                               console.log('預設錯誤');
                       }
                   });
           }
       },
       render: function(){
           this.$el.html(this.template);

           map = new google.maps.Map($('#map_canvas')[0], {
               zoom: 12,
               position: new google.maps.LatLng(null, null),
               mapTypeId: google.maps.MapTypeId.ROADMAP
           });
       }
   });
   return MapView;
});

我使用了 Miller Medeiros 提供的 RequireJS Plugins(這裡),來呼叫 Google Maps API 並渲染地圖。

define([
   'jquery',
   'underscore',
   'backbone',
   'collections/collection',
   'text!templates/menu.html'
], function($, _, Backbone, mapCollection, mainTemplate){
   var MenuView = Backbone.View.extend({
       el: '#menu',

       events:{
         'click a': "selectPlace"
       },

       initialize: function(){
           this.collection = new mapCollection();
           this.collection.fetch();
           this.collection.bind("reset", function(){this.render();}, this);
       },

       render: function(){
           var placeModels = {
               data: this.collection.models
           }

           this.$el.html(_.template(mainTemplate, placeModels));
       },

       updateModel:function(e){
           var place = e.replace('+', ' ');
           var self = this;
           this.collection.fetch({
               success:function(data){
                   _.find(data.models, function(el){
                       if(el.get('name')==place){            
                           $('li:nth('+self.collection.indexOf(el)+')').delay(500).addClass('active');
                           self.trigger("collectionSelected", el);
                       }
                   });
               }
           });
       },

       selectPlace:function(e){
           e.preventDefault();
           this.$el.find('li').removeClass('active');
           $(e.currentTarget).parent().addClass('active');
           var index = $('li a').index(e.currentTarget);
           this.trigger("collectionSelected", this.collection.models[index]);
       }
   });
   return MenuView;
});

Menu View 會抓取 collection,並透過加上 active class 來標示目前選取的項目。

以上就是一個可運作的地圖應用程式。

示範
下載原始碼