LoginSignup
0
0

More than 5 years have passed since last update.

[monaca][ui-router]Back!Back!Back!?

Posted at

ui-routerには、デフォルトでは戻る機能はないので、onsenuiと組み合わせて、それっぽく実装してみました。

特徴

Monaca + ui-routerだけですと、AndroidのBackボタンでアプリが閉じてしまいます。
そのため、onsenuiのons-navigatorタグを使って、ページ情報をスタックしていきます。
実際の画面スタック&ポップ処理は、ui-routerの遷移情報を蓄積して、それを元に行き来します。

ベース

[monaca][onsenui][AngularJS][ui-router]onsenui/ui-router最小限プロジェクト

html

index.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com; style-src * 'unsafe-inline'; script-src * 'unsafe-inline' 'unsafe-eval'">
  <script src="components/loader.js"></script>
  <script src="script.js"></script>

  <link rel="stylesheet" href="components/loader.css">
  <link rel="stylesheet" href="css/style.css">
</head>
<body ng-app="MyApp">

    <ons-page>
        <!-- ツールバー -->
        <ons-toolbar>
            <!-- ツールバー左 -->
            <div class="left" ng-controller="BackController">
                <!-- 戻るボタン -->
                <!-- 画面スタックが1件以上ある場合のみ、アイコンを表示する -->
                <ons-back-button ng-show="$root.pages.length > 0" ng-click="backNavi();">
                    Back
                </ons-back-button>
            </div>
            <!-- ツールバー真ん中 -->
            <div class="center" style="text-align: center;">
                [Back]
            </div>
            <!-- ツールバー右 -->
            <div class="right">
                <!-- 何も記載しない場合は、空文字を入れる。(入れないとセンタリングレイアウトがずれる) -->
                &nbsp;
            </div>              
        </ons-toolbar>
        <!-- コンテンツ部分 -->
        <div ui-view></div>
        <!-- ナビゲーター(実際には表示しない) -->
        <ons-navigator var="navi">
        </ons-navigator>
    </ons-page>

    <!-- 初期表示画面 -->
    <ons-template id="main.html">
        <ons-page ng-class="contents">
            <ons-button ui-sref="page-A">pageA</ons-button>
        </ons-page>
    </ons-template>

    <ons-template id="pageA.html">
        <!-- navigatorに詰めるHTMLはons-pageタグで囲む -->
        <ons-page ng-class="contents">
            <div>
                pageAだよ

                <ul>
                    <li><a ui-sref="page-A-1">pageA-①</a></li>
                    <li><a ui-sref="page-A-2">pageA-②</a></li>
                </ul>

                pageAの入れ子だよ

                <ul>
                    <li><a ui-sref=".page-A-sub1">pageA-sub①</a></li>
                    <li><a ui-sref=".page-A-sub2">pageA-sub②</a></li>
                </ul>

                <div ui-view></div>
            </div>
        </ons-page>
    </ons-template>

    <ons-template id="page-A-sub1.html">
        pageAの入れ子①だよ
    </ons-template>

    <ons-template id="page-A-sub2.html">
        pageAの入れ子②だよ
    </ons-template>

    <ons-template id="pageA-1.html">
        <ons-page ng-class="contents">
            <div>
                pageA-①だよ

                <ul>
                    <li><a ui-sref="page-A-1-1">pageA-①-①</a></li>
                    <li><a ui-sref="page-A-1-2">pageA-①-②</a></li>
                </ul>
            </div>
        </ons-page>
    </ons-template>

    <ons-template id="pageA-2.html">
        <ons-page ng-controller="PageA2Controller" ng-class="contents">
            値受け渡し

            <p>
                <ons-input modifier="underbar" ng-model="sample" placeholder="テストデータ" float></ons-input>
            </p>

            <hr>
            pageA-②だよ

            <ul>
                <li><ons-button ng-click="jump('page-A-2-1');">pageA-②-①</ons-button> そのまま表示</li>
                <li><ons-button ng-click="jump('page-A-2-2');">pageA-②-①</ons-button> 計算表示</li>
            </ul>

        </ons-page>
    </ons-template>

    <ons-template id="pageA-1-1.html">
        <ons-page ng-class="contents">
            pageA-①-①だよ        
        </ons-page>
    </ons-template>

    <ons-template id="pageA-1-2.html">
        <ons-page ng-class="contents">
            pageA-①-②だよ        
        </ons-page>
    </ons-template>

    <ons-template id="pageA-2-1.html">
        <ons-page ng-controller="PageA21Controller" ng-class="contents">
            pageA-②-①だよ

            <p>{{sample}}</p>
        </ons-page>
    </ons-template>

    <ons-template id="pageA-2-2.html">
        <ons-page ng-controller="PageA22Controller" ng-class="contents">
            pageA-②-②だよ        

            <p>{{result}}</p>
        </ons-page>
    </ons-template>

</body> 
</html>

javascript

script.js
var myApp = angular.module('MyApp', ['onsen', 'ui.router']);

myApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
        // 初期表示設定
        $urlRouterProvider.otherwise("/");

        // メイン画面(初期表示)
        $stateProvider.state('main', {
            url: '/',
            templateUrl: 'main.html'
        });

        // 各画面
        $stateProvider
            .state('page-A', {
                url: '/pageA',
                templateUrl: 'pageA.html'
            })
            .state('page-A-1', {
                url: '/pageA-1',
                templateUrl: 'pageA-1.html'
            })
            .state('page-A-1-1', {
                url: '/pageA-1-1',
                templateUrl: 'pageA-1-1.html'
            })
            .state('page-A-1-2', {
                url: '/pageA-1-2',
                templateUrl: 'pageA-1-2.html'
            })
            .state('page-A-2', {
                url: '/pageA-2',
                templateUrl: 'pageA-2.html',
                // 戻ってきた値を受け取る可能性があるため、params指定
                params: {
                    sample: null
                }                
            })
            .state('page-A-2-1', {
                url: '/pageA-2-1',
                templateUrl: 'pageA-2-1.html',
                // 引数を受け取る
                params: {
                    sample: null
                }
            })
            .state('page-A-2-2', {
                url: '/pageA-2-2',
                templateUrl: 'pageA-2-2.html',
                // 引数を受け取る
                params: {
                    sample: null
                }                
            })
            // 入れ子
            .state('page-A.page-A-sub1', {
                url: '/pageA/page-A-sub1',
                templateUrl: 'page-A-sub1.html'
            })
            .state('page-A.page-A-sub2', {
                url: '/pageA/page-A-sub2',
                templateUrl: 'page-A-sub2.html'
            })
            ;

    }]);


// 戻る機能のコントローラの割り当て
myApp.controller('BackController', ['$rootScope', '$scope', '$state', function($rootScope, $scope, $state) {

    // ツールバーの戻るアイコン押下時の処理
    $scope.backNavi = function() {
        // 戻る処理実行
        console.log("backNavi");
        stateBack($rootScope, $state, null);
    };

}]);

// Page2の値受け渡し処理
myApp.controller('PageA2Controller', ['$rootScope', '$scope', '$state', '$stateParams', function($rootScope, $scope, $state, $stateParams) {

    // 値が設定されている場合、スコープに再設定する
    if ($stateParams.sample) {
        $scope.sample = $stateParams.sample;
    }

    // 画面遷移の処理
    $scope.jump = function(to) {
        console.log("jump sample="+ $scope.sample);
        // パラメータ付で次に渡す
        $state.go(to, {"sample": $scope.sample});
    };
}]);

// Page2の値受け取り処理①
myApp.controller('PageA21Controller', ['$rootScope', '$scope', '$state', '$stateParams', function($rootScope, $scope, $state, $stateParams) {

    // 値が設定されている場合、スコープに再設定する
    if ($stateParams.sample) {
        $scope.sample = $stateParams.sample;
    }
}]);

// Page2の値受け取り処理②
myApp.controller('PageA22Controller', ['$rootScope', '$scope', '$state', '$stateParams', function($rootScope, $scope, $state, $stateParams) {

    console.log("sample: "+ $stateParams.sample);

    // 値が設定されている場合、スコープに再設定する
    if ($stateParams.sample) {      
        if (isNumber($stateParams.sample)) {
            // 数値の場合、計算して表示する
            var num = eval($stateParams.sample);
            $scope.result = num + " * 2 = "+ (num * 2);
        }
        else {
            // 数値以外の場合、エラーメッセージ
            $scope.result = '数値以外が受け渡されました: '+ $stateParams.sample;
        }
    }
}]);

// 数値/数字文字列チェック
function isNumber(x){ 
    if( typeof(x) != 'number' && typeof(x) != 'string' )
        return false;
    else 
        return (x == parseFloat(x) && isFinite(x));
}

// 画面遷移時の処理
myApp.run(['$rootScope', '$transitions', '$state', function($rootScope, $transitions, $state){
    // main 画面への遷移(初期表示)
    $transitions.onSuccess({to: 'main'}, function(trans){
        // main画面読み込み成功

        // 画面スタック初期化
        $rootScope.pages = [];
    });

    // main 画面以外への遷移(初期表示以外)
    $transitions.onBefore({to: function(state){ return state != 'main' }}, function(trans){
        // main以外の場合
        console.log("$transitions.onBefore: "+ trans.$from() +" -> "+ trans.$to() +" / "+ trans.params());

        if (trans.$from() != trans.$to()) {
            // 遷移情報(from→toの両方+パラメータを保持)を画面スタック
            $rootScope.pages.push(trans);

            // 遷移HTMLをnavigatorに追加する
            $rootScope.navi.pushPage(trans.$to().self.templateUrl);
        }
    });

    // Android の戻るキー押下時処理
    document.addEventListener("backbutton", function(e){
        // 戻る処理実行(イベントを引き継ぐ)
        console.log("backbutton");
        stateBack($rootScope, $state, e);
    }, false);

}]);


// 戻る処理
var stateBack = function($rootScope, $state, e) {
    console.log("--------------------------");
    console.log("stateBack: $root="+ $rootScope +", $state="+ $state + ", e="+ e);

    if (e != null) {
        // イベントがある場合は一旦止める
        e.preventDefault();
    }

    if ($rootScope.pages.length < 1) {
        // キューがない場合、アプリを終了するか確認する
        ons.notification.confirm({
            title: "終了確認",
            message: "アプリを終了させてもよろしいですか?",
            callback: function(answer) {
                if (answer) {
                    navigator.app.exitApp();
                }
            }
        });

        return;
    }

    // 現在のステート
    console.log("current: "+ $state.current);
    console.log("current.name: "+ $state.current.name);
    console.log("current.url: "+ $state.current.url);
    console.log("$rootScope.pages.length: "+ $rootScope.pages.length);

    console.log("* * * *"); 

    // 最新の遷移情報(from→toの両方を保持)を取得する
    var nowPage = $rootScope.pages[$rootScope.pages.length - 1];
    // 画面スタックをポップする
    $rootScope.pages.pop();

    console.log("* back: "+ $state.current.name + " => "+ nowPage);
    console.log(nowPage.$from().name);
    console.log(nowPage.params());

    // 最新の遷移情報のfromを元に、戻る方向(前画面)の遷移を実行する
    // パラメータもそのまま再設定する
    $state.go(nowPage.$from().name, nowPage.params());
    // 遷移して追加されたので除去しておく
    $rootScope.pages.pop();

    // 戻る処理実行後のステート
    console.log("current: "+ $state.current);
    console.log("current.name: "+ $state.current.name);
    console.log("current.url: "+ $state.current.url);
    console.log("$rootScope.pages.length: "+ $rootScope.pages.length);
}
style.css
/** 実際には使わないので、navigatorを非表示にしておく。 */
ons-navigator {
    display: none;
}
/** コンテンツのスタイル */
div.page__content {
    padding: 20px 10px;
}

処理の流れ

  1. アプリを起動する
  2. $urlRouterProvider.otherwise("/")で、初期表示URL"/"に遷移する
    1. state=mainを表示する
    2. state=mainの表示成功時($transitions.onSuccess({to: 'main'} (略));)に、画面スタック($rootScope.pages)を初期化する
    3. state=mainの表示が完了する
  3. pageAボタンを押下する
    1. state=page-Aへの遷移を開始する
    2. state=page-Aへの遷移を開始する前($transitions.onBefore (略));)に、遷移元と遷移先が異なる(trans.$from() != trans.$to())場合、遷移情報を画面スタックにpush($rootScope.pages.push(trans))する
    3. 同様に、ons-navigatorに遷移先HTMLをpushPageする ※これでAndroidのBackボタンでも閉じなくなる
  4. pageAを表示する
    1. ons-back-buttonタグで戻るボタン表示判定
      1. $rootScope.pagesに1件以上遷移情報が入っている状態であるため、戻るボタン表示
    2. ons-navigatorは、cssでdisplay: none;としているため、HTMLが設定されていても非表示のまま。
  5. ツールバーの戻るボタンを押下する
    1. ons-back-buttonタグより、$scope.backNaviを実行し、stateBackを実行する
      1. $rootScope.pages[$rootScope.pages.length - 1]で、直近の遷移情報を取得する
      2. $rootScope.pagespopする
      3. 遷移情報から$fromより遷移元名称と、paramsから受け渡しパラメータを元に、元の画面に通常遷移させる
      4. 遷移させると、遷移情報が$rootScope.pagespushされるため、それをpopして戻る遷移を保持させない
    2. state=mainを表示する
  6. ハードの戻るボタンを押下する(Android限定)
    1. document.addEventListenerで、backbutton(戻るボタン)の機能をフックする
    2. 以降は、5と同様、stateBackを実行する
  7. 画面間の値の受け渡し方法
    1. $stateProviderの遷移元と遷移先に、受け渡すパラメータをparamsとして登録する
    2. 画面遷移時に、パラメータ付で遷移させる。($state.go(to, {"sample": $scope.sample}))
    3. 遷移先画面のコントローラで、$stateParamsから値を取得する
    4. 遷移元にも戻った場合に、値を画面上に表示する場合、$stateParamsから値を取得する

ソースコード

いいわけ…

このソースはとてもウツクシクないです。
でも、今の自分ではこれが精いっぱいでした。
もっと良い方法をご存じの方、どうぞご指摘・ご教授ください m(__;)m

Monaca UG TOKYO #3 へのお土産として、やっと投稿できましたw

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0