ionic frameworkでヘッダーの戻るボタンを表示しない方法

SelfieGirlionic frameworkを使っていると以前の投稿で書きましたが、なかなかノウハウがまとまっていないと思うのでこのブログにまとめていこうと思います。

ionicを使っていると、戻るボタンを簡単に表示することができます。
画面遷移したタイミングで自動的に左上に戻るボタンを表示してくれます。これはありがたい。
下記の例では、ボタンのタイプと戻るボタンを示すアイコンを指定しています。
(以下はテンプレートとなるタグ設定)

<body ng-app="SelfieGirl">
  <nav-bar animation="nav-title-slide-ios7"
           type="bar-calm"
           back-button-type="button-icon"
           back-button-icon="ion-arrow-left-c">
  </nav-bar>
  <nav-view></nav-view>
</body>

これはありがたいのですが、入り口が、トップページではないページになってしまった場合で、次のページとしてトップページを表示した場合、トップページにも関わらず戻るボタンが表示されてしまいます。これはダサい…。

これの解決法が、以下の通りになります。
viewにhide-back-buttonを設定しておくと、表示されません。

<view title="title"
      hide-back-button="true">
  <content has-header="true" padding="true" id="TopCtrl">
    <p>ここに本文を記述します。</p>
  </content>
</view>

トップページにはこの設定をしておくのがよいでしょう。


SelfieGirlで使っている技術の紹介とOAuth.ioについて

twitter上ではもう宣伝していますが、SelfieGirlをリリースしました。

SelfieGirlは、twitter上で話題になっている#グラドル自画撮り部のハッシュタグを元に検索して、スマートフォンで見やすいように整形したWebサービスです。Androidアプリはちょこちょこ出ているようなので、Androidじゃなくて別の方法でやってみようかなと思っていました。

SelfieGirlを作っている技術は以下になります。

Ruby on Rails
Ruby製フルスタックフレームワーク
AngularJS
JavaScript MVCフレームワーク
ionic framework
AngularJSを前提にしたスマートフォン用に最適化されたhtml5フレームワーク
heroku
Ruby on Railsを簡単にデプロイできるPaaS。様々な言語をサポートしている。
oauth.io
OAuth Provider。面倒なOAuthの実装がすごく簡単になる。
GehirnDNS
DNS管理が楽。herokuでルートドメイン定義をするのに使える。

ionicはこの前、モバイルアプリ向けUIフレームワークionicがSassで作られている&AngularJSに最適化されていて俺得すぎるという記事を読んで気になっていたので調査をしていました。AngularJSも勉強できそうだし、スマートフォンに最適化されたサイトを作るのに便利そうだし、という理由で選びました。実際のところ、ionicはAngularJSをつかったjQuery Mobileみたいなものだと思います。サンプルを見てみればわかると思います。

SelfieGirlでは、AngularJSを使っていますが、ほとんどionicで拡張されたものを使うので、そこまで難しいことはありませんでした。そのかわり、ionicでちょっと難しいことをやろうとすると大変でした。あまりサンプルコードがないので。今後はionicのサンプルコードもブログに書いていこうと思います。

Railsを使った理由は、特にありませんが(笑)、herokuにデプロイするのが楽だからですかね。SelfieGirlではRailsの役割はCoffeeScript, Sass, Slimのコンパイル環境という感じです(Slimはhtmlを簡単に書くためのライブラリ)。今のところは特にデータベースも使っていません。

DNS管理はGehirnDNSを使っています。herokuはサブドメイン(www.seiflegirl.netみたいなの)を使うのは簡単なのですが、ルートドメイン(selfiegirl.net)を使おうとしたら、対応しているDNSサービスを使わなければなりません。仕事のときに色々調査したのですが、GehirnDNSならばherokuでルートドメインを使うことができます。2ドメインまでは無料で使えます(私は有料にしているけど)。

Gehirn DNS、ホスト名無しのドメインにエイリアス機能をサポート

GehirnDNSとherokuの相性はかなりいいと思います。

今回すごく楽だなーと思ったのは、OAuth.ioです。
OAuth.ioを使うとfacebookやtwitterなど、80ものプラットフォームへのOAuthログインが無茶苦茶簡単になります。OAuthって実は実装するのが結構面倒なのですが、OAuth.ioはJavaScriptのライブラリを使って2つメソッドを呼ぶだけで完了します。
ただし、OAuth時に使うconsumer key, consumer secretをOAuth.ioに預けることになるので、セキュリティ的にやや不安が残ります。しかし、セキュリティにはすごく気を遣ってる!というふうに書いてあったので、信用して使ってみることにしました。
OAuth.ioを使うには有料・無料のプランがあり、無料プランはOAuthユーザー数が月間1,000人、アプリ数2つまでなどの制限がありますが、とりあえず小さく始めるには無料プランで十分じゃないかなと思います。1,000人超えるサービスならうまくマネタイズすることもできるんじゃないかなと。

今回はOAuth.ioを使った部分だけ紹介しようと思います。
AngularJSのコントローラーも使いますが。

  1. OAuthの結果のキャッシュを有効にします。

    (function() {
      "use strict";
      var config = {
        oauthd_url: 'https://oauth.io',
        oauthd_api: '/api',
        version: 'web-0.1.5',
        options: {cache: true}
      };
    ...
    
  2. AngularJSのルーティングの設定をしておきます。

    angular.module('Hoge', ['ionic', 'Hoge.controllers']).config(
      function($stateProvider, $urlRouterProvider){
        $stateProvider
          .state('top', {
            url: '/',
            templateUrl: '/home/top',
            controller: 'TopCtrl'
          })
          .state('cards', {
            url: '/home/cards',
            templateUrl: '/home/cards',
            controller: 'CardsCtrl'
          })
        $urlRouterProvider.otherwise('/');
      }
    );
    
  3. ボタンをクリックしたらtwitterにOAuth認証するようにします。
    ng-click=”twitterLogin()”はAngularJSでのclickイベント設定です。今回はわかりやすくhtml(erb)にしておきます。

    <button ng-click="twitterLogin()">
    Twitter Login
    </button>
    

    そして、ng-clickのイベントを定義します。このコントローラーをTopCtrlとします。
    基本的にはOAuth.initializeメソッドとOAuth.redirectメソッドかOAuth.popupメソッドを呼ぶという2つだけ!

    OAuth.initialize("OAuth.ioのpublic key");
    angular.module('Hoge.controllers', [])
      .controller('TopCtrl', function($scope){
        $scope.twitterLogin = function() {
          // twitter認証後に#/home/cardsにリダイレクトする
          OAuth.redirect('twitter', "#/home/cards");
        };
      })
      .controller('CardsCtrl', function($scope, $rootScope) {
        // 後で説明する
      });
    

    クリックすると早速twitter認証ができます。簡単。

  4. リダイレクト後にアクセスされるurlに適したコントローラーを定義します。
    それがCardsCtrlとします。

      .controller('CardsCtrl', function($scope, $rootScope) {
        OAuth.callback('twitter', function(error, success) {
          // エラーの場合はalertをあげる
          if (error) return alert(error);
          // コールバックの結果を保存しておく
          $rootScope.twitter = success;
        });
        if ($rootScope.twitter == undefined) {
          OAuth.redirect('twitter', '#/home/cards');
        } else {
          var params = {
            q: 'テスト'
          };
          var queries = [];
          for(key in params) {
            queries.push(key + '=' + encodeURIComponent(params[key]));
          }
          // コールバックで受け取ったオブジェクトからAPIをコールする
          $rootScope.twitter.get('/1.1/search/tweets.json?' + queries.join('&')).done(function(json){
            // twitterの検索結果をここで処理
          });
        }
      });
    

    twitter apiを呼び出すのは、コールバックで受け取ったオブジェクトになります。OAuthで受け取ったトークンを保存して自分でなんとかして…とかしなくてもよいです。これがとても楽です。APIを読んだあとの処理もdoneで処理すればいいのでわかりやすいです。

  5. 最初のところでキャッシュをtrueにしたと思うのですが、こうしておくと、#/home/cardsを開いた状態でページを更新すると、
        if ($rootScope.twitter == undefined) {
          OAuth.redirect('twitter', '#/home/cards');
        } else {
          // ...
        }
    

    のところで認証結果のキャッシュが使われるので、毎回ログインを求められることがありません。

以上のように、twitter認証からAPIの呼び出しまで簡単にJavaScriptで行うことができます。OAuth.ioの無料枠の制限はやや厳しいですが(1,000ユーザーまで、2アプリまで)、とりあえず試しにやってみるには十分だと思うし、ユーザーが増えてきたら課金してもいいと思います。有料でも月に2000円くらいからです。個人でもまぁなんとか払える額かなぁと。


Deviseでパスワード変更した場合のログアウトを防ぐ

またまたDeviseです。
どうもDeviseは自分自身のパスワードを変更した場合、勝手にログアウトするようです。まぁそれはそれで正しい動作な気はしますが、そうしない方法はないかなーと思って探していたら、ありました。

class UsersController < ApplicationController
  load_and_authorize_resource

  def update
    @user.skip_reconfirmation! # メールアドレス変更確認しない
    result = if current_user.id == @user.id
               # 自身の更新ならパスワード入力を求める
               @user.update_with_password(user_params)
             else
               @user.update(user_params)
             end
    if result
      # パスワード変更でログアウトするのを防ぐ
      sign_in(@user, bypass: true) if current_user.id == @user.id
      redirect_to user_path(@user), notice: '更新しました'
    else
      render action: :edit
    end
  end
end

無理矢理、再ログインさせるって感じですかね。


Deviseでメールアドレスの確認をスキップする

既に登録されているユーザーのメールアドレスを変更した場合、新しく登録されたメールアドレスは確認が取れるまでメインではないところ保存され、確認が取れ次第メインに昇格という感じっぽい。しかしそこまでしたくはない場合もあるのではなかろうか?と思ったので、とりあえずメールアドレス変えたら即時に変わる方法を探していたら、あった。

class UsersController < ApplicationController
  load_and_authorize_resource

  def update
    @user.skip_reconfirmation! # メールアドレス変更確認しない
    if @user.update_with_password(user_params)
      redirect_to user_path(@user), notice: '更新しました'
    else
      render action: :edit
    end
  end
end

Deviseは色々とハマりそうなポイントが多いので、これからもTipsを蓄積していきたいと思います。


AngularLocalStorageを使う

ちょっとハマったのでメモを残します。
今作っているアプリで、ローカルストレージにデータを保存できたらいいのかなと思って調べていたら、AngularJS側で使いやすいようにモジュール化されたのがありました。

AngularLocalStorage

その使い方の記事
AngularJS の localStorage モジュール angularLocalStorage

これでよさそうなのかなと思ったのですが、いまいち使い方がわからなかったので、試行錯誤していたところ、記事を書いていた本人に声をかけていただいて、疑問が解決したので、メモっておきます。

var yourApp = angular.module('yourApp', [..., 'angularLocalStorage']
yourApp.controller('yourController', function($scope, storage) {
// hogeをキーにしてlocalStorageに保存される
storage.bind($scope, 'hoge'); // 既存の変数を指定すると、$scope.varName になる。
console.log($scope.hoge); // => 空

// * defaultValue: デフォルト値。既に値があったら上書きしない。
// * storeName: storeNameをキーとしてlocalStorageに保存される
storage.bind($scope, 'piyo', {defaultValue: 'default', storeName: 'fuga'});

console.log($scope.piyo); // => 'default'
console.log(storage.get('fuga')); // => 'default'
// つまり$scope.piyo == storage.get('fuga');

storeNameを変えることで、1つのブラウザを複数ユーザーが使うときに値が上書きされるのを避けることができる、ということだそうです。

// userの属性は人それぞれ
var user = {email: 'hoge@example.com', name: 'hoge', age: 30};
storage.bind($scope, 'user', {storeName: user.email, defaultValue: user});
// しかし、扱う側は$scope.userを扱うだけでよい。
// storeNameを元にlocalStrageから値を取得して$scope.userとバインドしてくれる。

バインドが非常に簡単なので、管理も楽かなと思う。
やっとわかってきたー。