2015年4月23日 星期四

Hybrid Apps開發系列之4.4:多頁面App程式 (天氣app-非線上版)

前文(Hybrid Apps開發系列之4.1:多頁面App製作與頁籤(Tabs)簡介)介紹的範例有許多codes重複出現,例如頁籤<ion-tabs>便同時出現在兩個.html檔內:

    <ion-tabs class="tabs-icon-top">
        <ion-tab href="/home" icon="ion-home" title="首頁"></ion-tab>
        <ion-tab href="/order" icon="ion-compose" title="訂位"></ion-tab>
    </ion-tabs>
此外,<ion-nav-bar>等指令也同樣重複出現。為了讓程式碼共用,必須採用「共用states」的方式。

共用states

UI-Router可定義abstract state-也就是操作過程中不能直接瀏覽的頁面-讓多個states共用;而abstract state是parent state;其他共用abstract state的頁面則為child states。因此,4.1的頁籤範例修改的部分如下圖所示。

圖1.頁籤頁面的內容定義為abstract state(如tab state),便可讓其他state共用。
  1. 加入新的abstract state,該state和一般state不同,必須加入屬性"abstract : true"例如:
    ...[略]...
    .state('tab',{
        url: '/tab',
        abstract: true,
        templateUrl: 'templates/tabs.html'
     })
    ...[略]...
  2. 修改其他要共用abstract state的頁面定義,加入views屬性,並給予名稱:
    ...[略]...
    .state('tab.home',{
        url: '/home',
        views: {
             'tab-home': {
                  templateUrl: 'templates/home.html',
                  controller: 'homeCtrl'
             }
        }       
     })
    ...[略]...
    和原先state定義不同除了名稱(由home改為tab.home,tab為abstract state名稱)之外,還加入了views屬性,並給予名稱(例如tab-home),將頁面檔案、controller的設定放在其中。views的名稱相當重要,會出現在parent state的頁面檔的<ion-nav-view>之name屬性,以便將此名稱所代表的子頁面,帶入parent state中。
  3. 將頁籤共用的部份獨立出來另存新檔,注意每個<ion-tab></ion-tab>內都要加入<ion-nav-view>指令,並設定其name屬性為對應子頁面views屬性所定義的名稱,例如:
    1. <ion-tabs>
    2. <ion-tab href="‘/tab/home’">
    3. <ion-nav-view name="‘tab-home’">
    4. </ion-nav-view>
    5. </ion-tab>
    6. <ion-tab href="‘/tab/order’">
    7. <ion-nav-view name="‘tab-order’">
    8. </ion-nav-view>
    9. </ion-tab>
    10. </ion-tabs>
    此處'tab-home', 'tab-order'便分別定義於兩個state的views屬性內。而第3行第6行href的網址須加上parent state的url,例如/home變為 /tabs/home。此乃因為各頁面的url定義雖然不變,但state位階變為abstract state的children,故各頁面完整的url必須加上parent state的url。
  4. 其他如<ion-nav-bar>指令可直接移至index.html,做為app本身layout的一部分。例如:
    <ion-nav-bar>
    <ion-nav-back-button>
    </ion-nav-back-button>
    </ion-nav-bar>
    <ion-nav-view>
    </ion-nav-view>

天氣app(非線上版)

目前網路有許多開放資料,只要透過http連線便可取得JSON格式的資料。JSON是JavaScript object格式,故直接可用於ionic專案內。此處便以open weather api的天氣資料為例,先將回傳的JSON資料貼在專案內使用,製作一頁籤式的天氣app。下一篇文章再運用線上取得JSON資料重製同一專案。取得資料的方式可參考:http://openweathermap.org/current;而回傳JSON資料格式則參考:http://openweathermap.org/weather-data#current
圖2. 天氣App介面
點選上圖頁面項目,便會顯示該城市的天氣概況,此部份便是運用前文Hybrid Apps開發系列之4.3:多頁面App程式 (清單/細節瀏覽)介紹的方法。左邊兩張圖則是兩個頁籤。整個專案結構如下圖:
圖3.專案結構

檔案內容

app.js關於config設定的部份如下:
  1. angular.module('starter', ['ionic', 'starter.controllers'])
  2. .config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
  3. $ionicConfigProvider.tabs.position('bottom');
  4. $urlRouterProvider.otherwise('/tab/home');
  5. $stateProvider
  6. .state('tab', {
  7. url: '/tab',
  8. abstract: true,
  9. templateUrl: 'templates/tabs.html'
  10. })
  11. .state('tab.home', {
  12. url: '/home',
  13. views: {
  14. 'tab-home': {
  15. templateUrl: 'templates/home.html',
  16. controller: 'homeCtrl'
  17. }
  18. }
  19. })
  20. .state('tab.settings', {
  21. url: '/settings',
  22. views: {
  23. 'tab-settings': {
  24. templateUrl: 'templates/settings.html',
  25. controller: 'settingsCtrl'
  26. }
  27. }
  28. })
  29. .state('weather', {
  30. url: '/tab/home/:id',
  31. templateUrl: 'templates/weather.html',
  32. controller: 'weatherCtrl'
  33. });
  34. })

  • 第2-3行:為了讓頁籤在不同行動平台上都出現在頁面下方,使用$ionicConfigProvider服務,以.tab.position('bottom')設定頁籤固定於下方(Android預設位置在頁面上方)。
  • 第6-9行:共用的parent state定義,注意第8行"abstract:true"為必要屬性設定。
  • 第11-19行:圖2左邊頁面的定義。注意第14行views名稱設為'tab-home',此名稱要用於第9行tabs.html內。
  • 第20-28行:圖2中間頁面的定義。第21行views名稱設為'tab-settings',此名稱必須用於第9行parent state的html檔內。
  • 第30行:最後一個'weather' state設定與前幾個均不相同,因為weather並未與其他頁面共用'tab' state。此外,由於weather的用途是顯示某城市的天氣概況,城市序號會由'tab.home' state傳來,因此url必須設定為'/tab/home/:id',而非'/home/:id',因為tab.home的完整url須結合parent state的url,而成為'tab/home'。

templates/tabs.html檔案內容如下:
  1. <ion-tabs class="tabs-icon-top">
  2. <ion-tab title="天氣" icon="ion-home" href="#/tab/home">
  3. <ion-nav-view name="tab-home"></ion-nav-view>
  4. </ion-tab>
  5. <ion-tab title="設定" icon="ion-settings" href="#/tab/settings">
  6. <ion-nav-view name="tab-settings"></ion-nav-view>
  7. </ion-tab>
  8. </ion-tabs>

  • 第2, 5行:href設定值必須包含parent state的部分-'#/tab'。
  • 第3, 6行:每個<ion-tab>各加入自己的<ion-nav-view>,name屬性值必須與state設定的views名稱相同。

starter.controllers定義於js/controllers.js,檔案內容如下:
  1. angular.module('starter.controllers',[])
  2. .controller('homeCtrl',function($scope,$location){
  3. $scope.citys = citys;
  4. $scope.showWeather = function(index){
  5. $location.path('/tab/home/'+index);
  6. }
  7. })
  8. .controller('settingsCtrl',function($scope){
  9. $scope.citys = citys;
  10. })
  11. .controller('weatherCtrl',function($scope,$stateParams){
  12. $scope.weather = weather[$stateParams.id];
  13. $scope.img_base_url = image_base_url;
  14. })
  15.  
  16. var citys = [
  17. {name: "台北", q: "Taipei", on: true},
  18. {name: "台中", q: "Taichung", on: true},
  19. {name: "台南", q: "Tainan", on: true},
  20. {name: "高雄", q: "Kaohsiung", on: true},
  21. {name: "花蓮", q: "Hualian", on: true},
  22. ]
  23. var image_base_url = "http://openweathermap.org/img/w/"; // open weather api icon
  24. var weather = [
  25. {"coord": {"lon": 121.53, "lat": 25.05},
  26. "sys": {"message": 0.0418, "country": "TW","sunrise": 1429565168, "sunset": 1429611544},
  27. "weather": [{"id": 500, "main": "Rain", "description": "light rain", "icon": "10n"}],
  28. "base": "stations",
  29. "main": {"temp": 292.18, "temp_min": 292.18, "temp_max": 292.18, "pressure": 1022.92,
  30. "sea_level": 1031.39, "grnd_level": 1022.92, "humidity": 100},
  31. "wind": {"speed": 3.75, "deg": 41.0005},
  32. "clouds": {"all": 92},
  33. "rain": {"3h": 0.13},
  34. "dt": 1429625933,
  35. "id": 1668341,
  36. "name": "Taipei",
  37. "cod": 200},
  38. //...略...
  39. ]

  • 第4-6行:定義showWeather()事件處理器,給頁面使用。下一個檔案home.html便會說明因為捨棄之前用超連結<a href="指定url">的方式,改用ng-click,所以提供此事件處理器做轉址動作。Ionic/AngularJS之$location提供轉址服務,故第2行函式定義處加上$location參數,第5行再呼叫$location.path()進行轉址。
  • 第11-14行:和Hybrid Apps開發系列之4.3:多頁面App程式 (清單/細節瀏覽)範例相同,天氣概況頁面的城市參數也是透過$stateParams傳遞進來,$stateParams.id之'id'係因.config裡weather state定義了"'tab/home/:id"而得。
  • 第16-22:自定資料陣列。on屬性係用來記錄settings頁面各項目的開關(toggle)狀態。
  • 第24-37行:是從open weather api取得的台北市資料,其他城市資料則省略。

首頁templates/home.html檔案內容如下:
  1. <ion-view title="目前天氣">
  2. <ion-content>
  3. <ion-list class="list">
  4. <ion-item class="item item-icon-left" ng-repeat="city in citys"
  5. ng-click="showWeather($index)"
  6. ng-show="city.on">
  7. <i class="icon ion-ios-home-outline"></i>
  8. {{city.name}}
  9. </ion-item>
  10. </ion-list>
  11. </ion-content>
  12. </ion-view>

  • 第4-9行:單一項目。第3行先使用<ion-list class="item">定義清單區塊,區塊內再用<ion-item class="item">定義每一個項目,如下所示:
    <ion-list class="item">
        <ion-item class="item"></ion-item>
        <ion-item class="item"></ion-item>
        ...
    </ion-list>
    此處以ng-repeat套用controller端$scope所設定的資料模型陣列,帶出城市資料,ng-click則取代之前的超連結做法,提供設定點選事件處理的函式呼叫。$index同樣是ng-repeat進行的次數,由0算起,故可代表城市資料陣列的序號。ng-show運用城市資料的on屬性值,決定是否顯示該項目。on屬性質透過下面settings.html頁面進行變更。
設定頁面templates/settings.html內容如下:
  1. <ion-view title="城市列表">
  2. <ion-content>
  3. <ion-list>
  4. <ion-item class="item item-toggle" ng-repeat="city in citys">
  5. {{city.name}}
  6. <label class="toggle toggle-assertive">
  7. <input type="checkbox" ng-model="city.on">
  8. <div class="track">
  9. <div class="handle"></div>
  10. </div>
  11. </label>
  12. </ion-item>
  13. </ion-list>
  14. </ion-content>
  15. </ion-view>

  • 第6-11行:開關(toggle)的CSS設定較為複雜,最外層以<label class="toggle">標示,僅跟著第7行設定input type為"checkbox",並以ng-model綁定controller端設定的變數;最後再以<div class="track">包住<div class="handle">顯示開關的外型。

天氣概況頁面templates/weather.html內容如下:
  1. <ion-view title="天氣概況">
  2. <ion-content>
  3. <div class="list card">
  4. <div class="item">
  5. <h2>City of {{weather.name}}</h2>
  6. </div>
  7. <div class="item item-avatar">
  8. <img src="{{img_base_url+weather.weather[0].icon+'.png'}}">
  9. <h2>{{weather.main['temp']-273.15 | number:1}}°C</h2>
  10. <h2>{{(weather.dt)*1000 | date:'yyyy-MM-dd HH:mm:ss'}}</h2>
  11. </div>
  12. <div class="item">
  13. <div class="row">
  14. <div class="col">濕度</div>
  15. <div class="col">{{weather.main.humidity}}%</div>
  16. </div>
  17. <div class="row">
  18. <div class="col">風速</div>
  19. <div class="col">{{weather.wind.speed}}哩/秒</div>
  20. </div>
  21. </div>
  22. </div>
  23. </ion-content>
  24. </ion-view>


index.html檔定義如下:
[...略...]
<!-- your app's js -->
        <script src="js/app.js"></script>
        <script src="js/controllers.js"></script>
<body ng-app="starter">
    <ion-nav-bar>
        <ion-nav-back-button></ion-nav-back-button>
    </ion-nav-bar>
    <ion-nav-view>
    </ion-nav-view>   
</body>
</html>

沒有留言:

張貼留言