アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

温泉マーク問題

この記事はOnsen UI Advent Calendar 2016向けの記事として書きました。

アシアルのマーケティング担当の塚田です。

先日報道されたOnsen UIのマーケティング上、非常に重要なニュースについて書きたいと思います。

2013年9月にプレビュー版リリースをしたOnsen UIは、現在ではCordovaプロジェクトの公式サイトに掲載されたり、GitHubスターも4000弱つくなど、国内外の多くのエンジニアのみなさんに使っていただけるUIフレームワークとして成長してきました。

イタリアでは人気の自転車レースGiro d'Italiaの公式アプリに採用されたり、日本では金融機関やテレビ局のアプリなどにも多数採用されています。

認知度もあがり、素晴らしいアプリも多数生まれ、我々の努力も少しは報われてきたかと思っていた最中、2016年7月にとんでもないニュースが飛び込んできました。


温泉マークが変更になる???



リオオリンピックが始まる直前の2016年7月7日、経済産業省が2020年の東京オリンピックに向けて、公共施設などで使われる案内用のピクトグラムを外国人でもわかりやすくするため変更を検討しているという発表がされました。

その変更の対象として取り上げられた一つが「温泉マーク」です。従来の温泉マークは外国人には「温かい料理」に見えるとのこと。

ニュースの映像では、温泉マークを見た外国人観光客が「コーヒー」だと答えていました。

新たなマークの候補として発表されたのがこちら。



はい、、、

湯船に人が入っています。


みなさんご存知の通り、我々のOnsen UIのロゴマークも温泉マークをオマージュして作ったもので湯気の部分が「UI」となっています。



もし、温泉マークが変更になってしまったら、Onsen UIのロゴマークにも湯船に人を入れるように経済産業省から指導が入るかもしれません。

そうなると、Webサイトも作り直し、たくさんつくったTシャツなどのノベルティなども全て無駄に、、、

そもそも温泉マークが「コーヒー」に見えていたとすると、CoffeeScript開発チームのみなさんは苦々しく思っていたのではないか、、、

これまで海外向けのプロモーションに費やしてきた努力がお湯の泡、いや水の泡になってしまいます。

マーケティング担当の私の責任問題になること必至、、、

これはまずい。


Onsen UIの名前には意味がある



皆さんにはご理解いただきたいのですが、Onsen UIという名前は適当につけた訳ではないのです。

Onsen UIはモバイル向けのシングルページアプリケーションを開発するためのUIフレームワークです。シングルページアプリケーションは通称「SPA」とも言われます。勘の良い方は、この時点でピンっと来たかもしれませんが、SPA→スパ→温泉→Onsenです。シングルアプリケーション向けのUIフレームワークなのでOnsen UIなのです。

そして、日本に来る外国人旅行者が楽しみにしていることの第5位がなんと温泉なのです。和食や買い物、景勝地の観光と並んで多くの外国人が温泉を求めて日本に来るのです。日本発のオープンソースプロジェクトを世界に広めるのにこれ以上の名前があったでしょうか。

そしてロゴも温泉マークをオマージュしたしたロゴを作成したのですが、この時にはこのロゴが変更に追い込まれるかもしれないことになると思いもよりませんでした。


温泉地が立ち上がった



経済産業省に意義を申し立てようにも、温泉とは直接関係のないOnsen UIの意見を聞いてくれる余地は無さそう、、、

新温泉マークは不評ではあるものの、変更することを規定路線にただただ時間だけが過ぎて行きました。

ここで別府温泉で有名な大分県別府市が立ち上がりました。別府市が温泉がある全国の自治体に調査を実施し、調査協力をした57自治体のうち過半数を軽く超える36自治体が現温泉マークを支持する(新マークは16自治体)と発表しました。

そして2016年12月6日に開かれた経済産業省が主催する有識者会議では、湯布院観光協会や磯部観光温泉旅館協同組合の代表などが出席し、温泉マーク変更案の撤回を強く求めました。これまで約50件の反対意見が寄せられていることもあり、経済産業省の担当者も「慎重に検討して決めたい」と話し、風向きが変わってきました。


日本の温泉マークを世界に広めよう



先日の有識者会議で、出席委員の一人が「変えずに日本の温泉マークを世界に広めてはどうか」との意見がでたようですが、私もこの意見に大賛成です。前述の通り外国人の多くの方は日本に温泉を求めてやってきます。そこで温泉マーク自体を日本の観光PRなどに積極的に使うことで、誰もが認識できるものとして広げていくべきではないでしょうか。

ついでの温泉マークがスパ(SPA)だということが認知されれば、Onsen UIがSPA用のフレームワークだということが嬉しいですね。

とりあえずOnsen UIのロゴが変更に追い込まれることがなくなりそうなので、2017年はOnsen UIをもっと世界の開発者に知ってもらえるように頑張りたいと思います。

皆さんもOnsen UIの応援をよろしくお願いします。
製品へのフィードバックとか、GitHubスターとかをいただけるともっと頑張れますので、是非よろしくお願いします。

Onsen UI 2.0の紹介と始め方

ハイブリッドによるスマートフォンアプリ開発を手軽なものにしてくれるUIフレームワークとして開発しているのがOnsen UIです。現在も開発が継続されており、間もなく2.0が正式リリースとなります(執筆時点でβ)。



特に大きな転換ポイントと言えるのが、AngularJSとの切り離しになります。そこで今回はOnsen UI 2.0の使い方を紹介します。



インストール方法は4つ



Onsen UIのインストール方法は主に4つ用意しています。いずれか使いやすいものを選んでください。



Node.js/npmを使う方法



Node.js/npmを使ってインストールする場合、次のようにコマンドを打ちます。





npm install onsenui@2.0.0-beta.7 --save // β版の現在の場合。
npm install onsenui --save // 2.0の正式版リリース後。現在は1.3系がインストールされます。


また、このままですと node_modules 以下に配置されて使いづらいので、browserifyをインストールします。





npm install -g browserify


そしてコードを書きます。例えばファイルを index.js とします。






require('onsenui');

// 自分のWebアプリケーションのコード


後は最後に browserify を実行します。





browserify index.js -o app.js


これで onsenuiが入った状態でJavaScriptコードが生成されます。JavaScript側ではapp.jsを読み込むようにすれば問題ありません。



Bowerを使う方法



BowerもNode.js/npm同様に進めることができます。Monaca IDEはBowerを使ったプロジェクトのインポートに対応していますので、さらに手軽と言えそうです。ただしBower自体はnpmを使ってインストールします。





npm instlal bower -g


そしてアプリを開発するプロジェクトのルートで bower initを実行します。





bower init


次にOnsen UIをインストールします。現在はバージョンを指定する必要があります。





bower install onsenui#2.0.0-beta.7 --save


ファイルは bower_components/onsenui/ 以下にインストールされます。



ファイルをダウンロードする



3つ目はJavaScript/スタイルシートファイルを直接ダウンロードする方法です。Releases · OnsenUI/OnsenUI-distから最新版がダウンロードできます。解凍したフォルダの中にあるjs/cssフォルダを好きな場所に配置してください。



Monacaのテンプレートを使う



最後にMonacaのテンプレートを使う方法です。Monacaのプロジェクトテンプレートの中に、Onsen 2.0 クイックスタートというテンプレートを用意していますので、これを選択するという方法になります。これは予めOnsen UI 2.0が組み込まれていますので手軽にはじめることができます。





こちらがOnsen UI 2.0クイックスタートプロジェクトです。



Reactと組み合わせたデモも内包されています。



実際に使ってみる



では実際にOnsen UI 2.0を使ってみたいと思います。例えばHTMLは次のようになります。ファイル名は index.html とします。





<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="mobile-web-app-capable" content="yes" />
    <title>Onsen UI 2.0 Quickstart</title>
    <script src="node_modules/onsenui/js/onsenui.min.js"></script>

    <link rel="stylesheet" href="node_modules/onsenui/css/onsenui.css" type="text/css" media="all" />
    <link rel="stylesheet" href="node_modules/onsenui/css/onsen-css-components.css" type="text/css" media="all" />
</head>

<body>
  <ons-page>
  </ons-page>
  <ons-tabbar>
  <ons-tab page="page1.html" label="Page 1" icon="square" active="true"></ons-tab>
    <ons-tab page="page2.html" label="Page 2" icon="square"></ons-tab>
    <ons-tab page="page3.html" label="Page 3" icon="square"></ons-tab>
    <ons-tab page="page4.html" label="Page 4" icon="square"></ons-tab>
    <ons-tab page="page5.html" label="Page 5" icon="square"></ons-tab>
  </ons-tabbar>
</body>


見て分かる通り、onsenui.min.jsしかJavaScript側では読み込んでいません。スタイルシートはベースになるonsenui.cssと、各種コンポーネント用のonsen-css-components.cssを読み込んでいます。



bodyタグの中身はを使ったOnsen UIの定義になります。さらにpage1.htmlを次のような内容で作成します。





<h3 class = "title-h3">iOS Switch</h3>
<p>
  <ons-switch></ons-switch>
</p>
<h3 class = "title-h3">Material Switch</h3>
<p>
  <ons-switch modifier="material" checked></ons-switch>
</p>


こちらはヘッダーもなく、いきなりbodyタグ内の内容を記述します。さらにpage2.htmlを次のように定義します。





<ons-list>
  <ons-list-header>Page 2</ons-list-header>
  <ons-list-item>Item</ons-list-item>
  <ons-list-item>Item</ons-list-item>
</ons-list>


こちらはを使っています。いずれのファイルもJavaScriptの定義であったり、AngularJSの記述は一切ありません。



このファイルを開くと、次のように表示されます。





まさにスマートフォンアプリ風のUIになっているのが分かるかと思います。さらにons-tabbarの一番左のページがデフォルト表示になります。今回はpage2.htmlまでしか作っていませんが、タブ1とタブ2がタップで表示切り替えできる点も確認できるかと思います。






このように外部ライブラリへの依存性が減ったことで、自由度が高まったり、すでに多数あるJavaScriptフレームワークと組み合わせた開発ができるようになっています。Onsen UI 2.0は間もなく正式リリースになります。ぜひお試しください!



Onsen: HTML5 Hybrid App Framework & UI Components

Onsen UI活用インタビュー「オハログで快眠生活を ! 」

こんにちは、Onsen UIチームのFranです。

今回は、オハログ開発チームの、田口敬氏、伊藤新助氏、鈴木邦章氏の3名にインタビューしてきました。

オハログは、累計30万人利用した大人気「ウンログ」の睡眠版です。

オハログはOnsen UIで作られたスマートデバイス用アプリケーションで、利用者は睡眠を記録・解析し、より良い休息に最適のコンディションを見つけることができます。

オハログのプロジェクトは帝人株式会社とからウンログ株式会社の共同事業として展開されています。




オハログの前身のプロジェクトであるウンログは、AndroidiOS用にそれぞれネイティブ言語で開発されたアプリであり、Google PlayApp Storeで累計30万に利用されています。

しかし、今回は短い時間でAndroidiOS両方のアプリを開発する必要があったため、HTML5ハイブリッドアプリ開発を選択することにしました。

HTML5ハイブリッドアプリ開発を行うにあたって、オハログ開発チームではPolymerやIonicなど他のフレームワークも検討しましたが、最終的にはOnsen UIを選ばれました。

デザイン、ドキュメント、デモ、Monaca上で簡単にテストできることなどが採用理由だったそうです。

オハログ開発チームにとって、OnsenUIやCordovaを本格的に使うのは今回が初めてでしたが、容易に思い通りのアプリケーションを開発することができたそうです。

オハログの開発にあたっては、10程度のCordovaプラグインが必要になったそうです。多くはCordova Plugin Registryで必要なものを見つけることができたものの、いくつかのプラグインはアプリの要件に合うように書き直したり、一からカスタムプラグイを開発したりする必要はあったそうです。

実際にオハログのアプリを操作すると実感しますが、UIの細部にまでこだわって作られており、非常に操作性の高いアプリに仕上がっていました。



CordovaとOnsen UIの評価は高く、今後も積極的に活用をする方針で、すでに現在着手している新しいアプリの開発でもOnsen UIを使用して開発をしているそうです。

今回のインタビューでは、Onsen UIフレームワークについて多くのフィードバックやリクエストも得ることもできまして、幾つかはすでに先日のバージョンアップに反映することができました。

アシアルは今後もOnsenUIを改良し、利用者の皆様からご要望のあった修正や新機能をリリースしていきたいと考えていますので、このようなフィードバックは大歓迎です。

もしみなさんがOnsen UIでアプリを開発されましたら、ぜひご意見などをお寄せください。
お待ちしております!


そしてもちろん、今夜から睡眠を改善するために、是非オハログも試してみてください!

オハログについては以下をご覧ください。
http://unlog.me/ohalog/

AngularJSとOnsen UI で作るGoogle Mapsのサンプルアプリ

今回の記事は、Onsen UI blogで2月に公開した"Creating Google Maps Sample App with AngularJS and Onsen UI"の翻訳記事です。



以下のツールを使用して、このサンプルを構築します。

Onsen UI ( HTML5 を使用したハイブリッドアプリを作成するためのフレームワーク )
AngularJS ( Google 社が開発した JavaScript のフレームワーク )
Google Maps JavaScript API v3

ここでは、Monacaを使用して、アプリを開発します。Monaca は、クロウド型のアプリ開発環境です。このツールを使用すれば、マルチプラットフォーム ( iOS/Android/Windows8 ) に対応するハイブリッドアプリを開発できます。Monaca には、Onsen UI のフルサポート、テンプレートの提供など、開発を容易にしてくれる作り込みが多数してあります。これらの点が、今回、Monaca を使用して、開発を行う理由です。

このサンプルアプリに実装している機能は、上記ツールの解説に必要となる、最低限に抑えてあります。また、ここでは、複雑・大規模アプリの構築ではなく、拡張可能な、ひな形となるアプリの構築に焦点を当てています。よって、アプリ内の機能のいくつかは、表示されているだけで、機能しません。開発者側で自由に書き換えれるように、枠組みだけ、提供しています。



実装している機能を、以下に記します。


  • マーカーの表示 ( 画面上で長押し )

  • マーカーの削除 ( 1 件、画面上でタップ )

  • マーカーの削除 ( 全件、<ons-button> を使用)

  • マーカー間の距離 ( 2 点間の距離、総距離 ) の計算 ( <ons-button> を使用 )

  • メニューの表示 ( <ons-sliding-menu> を使用 )



<ons-sliding-menu> を使用して、メインのインターフェイス ( ページ選択の画面 ) を配置し、その上に、map.html と settings.html へのリンクを置いています。map.html には、map 関連の要素と Onsen UI の要素 ( <ons-button> など ) を記述しています。スライティングメニュー上から、map 画面または settings 画面へ遷移できます。

上のサンプルアプリは、実際に操作できます。または、こちらのリンクからブラウザー表示も行えます。GitHub上にも、コードを置いていますので、開発者の環境で検証もできます。

また、上述の HTML コンテンツの他にも、controller.js と style.css の 2 つの重要な部品から、このアプリは構成されています。

コントローラー

コントローラーには、アプリのロジックを記述しています。このアプリには、2 つのコントローラーを使用しています。1 つは、ons-sliding-menu の制御用で、もう 1 つは、地図の制御用です。どちらのコントローラーも、controller.js 内に記述されています。

SlidingMenuController

ons-sliding-menu 要素と Google Maps 関連の要素を、同時に使用する際の問題点として、スクロール処理のイベントに、どちらも影響されることが挙げられます ( ここでは、水平方向のスワイプ )。よって、スライディングメニューのスワイプ時の挙動を、コントローラー側で制御する必要があります。
ここでは、この問題を解消するために、 'postopen' または 'postclose' イベントの制御を行う要素に、リスナーを登録します。スライディングメニューのスワイプは、デフォルトでは、無効化されていますが、ons-toolbar-button をクリックして、メニューを開くと有効化され、メニューが閉じるまで ( スワイプまたは ons-toolbar-button の再実行 )、そのまま有効化されています。このリスナーは、スライディングメニューを初回に開いた後に、設定されます。

MapController

このコントローラーには、map オブジェクト関連のロジックと Onsen UI 関連のロジック ( Google Maps と Onsen UI との連携部分 ) を記述しています。最初の処理は、maker 用の配列の作成 ( 後から使用 ) と map 要素の作成です。ここでは、地図
の初期化時に、AngularJS の $timeout を使用して、地図の読み込みを、100ms ほど、遅延させます。DOM を読み込む前に、AngularJS のコントローラーの初期化が行われるため、この処理が必要となります。

地図の読み込み後、タップイベント ( マーカーの表示 ) に対して、リスナーを設定します。ここでは、Hammer.JS ライブラリーを使用します。Hammer.JS は、Onsen UI に、初めから組み込まれています。

地図の初期化後、上述した機能が使用できます。上述の機能には、それぞれ、対応するメソッドがあります ( $scope.addOnClick()、$scope.deleteAllMarkers()、$scope.calculateDistance() )。

$scope.addOnClick() は、上述したリスナーと紐付けされています。画面のクリック/タップ位置 ( X値、Y値 ) を取得して、経緯・緯度の座標に変換します。Google Maps では、このような形式でエンコード化が行われているため、この変換処理が必要となります。この処理には、以下のような関数を使用します。



$scope.overlay.getProjection().fromContainerPixelToLatLng(point);


地図を入れる div の左上の地点に、座標の原点があります ( 端末の画面の左上ではありません )。これが、Y 座標の取得時に、(-) 44px 分だけ、差し引く理由です ( 44px は、画面上部に置かれた <ons-toolbar> 要素の高さです )。座標の変換後、地図上に、マーカーを表示できます ( マーカー用の配列を使用 )。

次に、マーカーに対するクリック/タップに備えて、リスナーを設定します。マーカーが押された場合、ons.notification.confirm 要素を使用して、マーカーを削除するか否か、ユーザー側に確認します。次に、ユーザーの選択肢に応じて、ons.notification.alert 要素を表示します。



// controller.js

(function() {
    var app = angular.module('myApp', ['onsen']);

    //Sliding menu controller, swiping management
    app.controller('SlidingMenuController', function($scope){

        $scope.checkSlidingMenuStatus = function(){

            $scope.slidingMenu.on('postclose', function(){
                $scope.slidingMenu.setSwipeable(false);
            });
            $scope.slidingMenu.on('postopen', function(){
                $scope.slidingMenu.setSwipeable(true);
            });
        };

        $scope.checkSlidingMenuStatus();
    });

    //Map controller
    app.controller('MapController', function($scope, $timeout){

        $scope.map;
        $scope.markers = [];
        $scope.markerId = 1;

        //Map initialization  
        $timeout(function(){

            var latlng = new google.maps.LatLng(35.7042995, 139.7597564);
            var myOptions = {
                zoom: 8,
                center: latlng,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };
            $scope.map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); 
            $scope.overlay = new google.maps.OverlayView();
            $scope.overlay.draw = function() {}; // empty function required
            $scope.overlay.setMap($scope.map);
            $scope.element = document.getElementById('map_canvas');
            $scope.hammertime = Hammer($scope.element).on("hold", function(event) {
                $scope.addOnClick(event);
            });

        },100);

        //Delete all Markers
        $scope.deleteAllMarkers = function(){

            if($scope.markers.length == 0){
                ons.notification.alert({
                    message: 'There are no markers to delete!!!'
                });
                return;
            }

            for (var i = 0; i < $scope.markers.length; i++) {

                //Remove the marker from Map                  
                $scope.markers[i].setMap(null);
            }

            //Remove the marker from array.
            $scope.markers.length = 0;
            $scope.markerId = 0;

            ons.notification.alert({
                message: 'All Markers deleted.'
            });   
        };

        $scope.rad = function(x) {
            return x * Math.PI / 180;
        };

        //Calculate the distance between the Markers
        $scope.calculateDistance = function(){

            if($scope.markers.length < 2){
                ons.notification.alert({
                    message: 'Insert at least 2 markers!!!'
                });
            }
            else{
                var totalDistance = 0;
                var partialDistance = [];
                partialDistance.length = $scope.markers.length - 1;

                for(var i = 0; i < partialDistance.length; i++){
                    var p1 = $scope.markers[i];
                    var p2 = $scope.markers[i+1];

                    var R = 6378137; // Earth’s mean radius in meter
                    var dLat = $scope.rad(p2.position.lat() - p1.position.lat());
                    var dLong = $scope.rad(p2.position.lng() - p1.position.lng());
                    var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                    Math.cos($scope.rad(p1.position.lat())) * Math.cos($scope.rad(p2.position.lat())) *
                    Math.sin(dLong / 2) * Math.sin(dLong / 2);
                    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
                    totalDistance += R * c / 1000; //distance in Km
                    partialDistance[i] = R * c / 1000;
                }


                ons.notification.confirm({
                    message: 'Do you want to see the partial distances?',
                    callback: function(idx) {

                        ons.notification.alert({
                            message: "The total distance is " + totalDistance.toFixed(1) + " km"
                        });

                        switch(idx) {
                            case 0:

                                break;
                            case 1:
                                for (var i = (partialDistance.length - 1); i >= 0 ; i--) {

                                    ons.notification.alert({
                                        message: "The partial distance from point " + (i+1) + " to point " + (i+2) + " is " + partialDistance[i].toFixed(1) + " km"
                                    });
                                }
                                break;
                        }
                    }
                });
            }
        };

        //Add single Marker
        $scope.addOnClick = function(event) {
            var x = event.gesture.center.pageX;
            var y = event.gesture.center.pageY-44;
            var point = new google.maps.Point(x, y);
            console.log(x + " - " + y);
            var coordinates = $scope.overlay.getProjection().fromContainerPixelToLatLng(point);

            console.log(coordinates.lat + ", " + coordinates.lng);

            var marker = new google.maps.Marker({
                position: coordinates,
                map: $scope.map
            });

            marker.id = $scope.markerId;
            $scope.markerId++;
            $scope.markers.push(marker);

            $timeout(function(){
                //Creation of the listener associated to the Markers click
            google.maps.event.addListener(marker, "click", function (e) {
                ons.notification.confirm({
                    message: 'Do you want to delete the marker?',
                    callback: function(idx) {
                        switch(idx) {
                            case 0:
                                ons.notification.alert({
                                    message: 'You pressed "Cancel".'
                                });
                                break;
                            case 1:
                                for (var i = 0; i < $scope.markers.length; i++) {
                                    if ($scope.markers[i].id == marker.id) {
                                        //Remove the marker from Map                  
                                        $scope.markers[i].setMap(null);

                                        //Remove the marker from array.
                                        $scope.markers.splice(i, 1);
                                    }
                                }
                                ons.notification.alert({
                                    message: 'Marker deleted.'
                                });
                                break;
                        }
                    }
                });
            });
            },1000);


        };
    });
})();


$scope.deleteAllMarkers() で行う処理は、シンプルです。マーカーの配列を確認して、地図上から、すべてのマーカーを削除します。次に、配列から、すべてのデータを削除して、配列のインデックスを初期化します。

$scope.calculateDistance() では、地図上のマーカー間の距離を計算します。ここでは、Web 上で見つけた計算式を使用しています。この計算処理の結果と座標は、同じ形式を使用しているため、変換処理は必要ありません。
計算後、ons.notification.confirm 要素を使用して、特定のマーカー間の距離 ( Partial Distance ) を表示するか否か、メッセージを表示します。ユーザーの選択肢に応じて、ここでも、ons.notification.alert 要素を使用します。

CSS

Onsen UI がデフォルトで提供するスタイルシート ( CSS ) とは別に、DOM 要素用のスタイルを、新たに追加します。CSS の内容は、style.css をご確認ください。ここでは、Google Maps JavaScript API v3 関連の要素と Onsen UI 関連の要素の表示処理を、適当に行うため、スタイルを新たに追加します。Google Maps JavaScript API v3 と Onsen UI の提供元は異なるため、これらの要素を完全に連携させることはできません。よって、これらの要素を表示するため、2 層のレイヤーを使用します。

下位のレイヤーには、Google Maps の要素 ( 地図、マーカー ) を置き、上位のレイヤーには、Onsen UI の要素を置くことにします。
ここでは、スタイルシートに追加した z-index を使用して、各要素を振り分けます。Maps 関連の要素には、-1 を設定して、下位のレイヤーにプッシュし、Onsen UI 関連の要素には、1 を設定して、上位のレイヤーにプッシュします。

Onsen UI 側の要素の位置を、適切に指定して、Google Maps 側の要素との重複表示を防ぎます。また、要素を適切に配置するためには、画面の解像度も考慮に入れます。要素自体のサイズも重要です。たとえば、div が大きすぎでも、使いにくいマップとなります。



#map_canvas {
    position: absolute; 
    width:100%; 
    height: 100%; 
    margin: 0; 
    padding: 0;   
    z-index: -1
}

.search-input-map{
    width:60%;
    z-index:1;
    margin-left: auto;
    margin-right:auto;
}

.par-search{
    margin-top:10%
}

.par-buttons{
    position: absolute; 
    text-align: center; 
    width:100%; 
    bottom:0px
}

.btn-delete{
    z-index:1
}

.btn-distance{
    z-index:1; margin-left: 5px;


HTML

index.html、menu.html、map.html、settings.html の 4 つのファイルから、このアプリは構成されています ( settings.html の解説は割愛 )。これらのページには、HTML、Onsen UI、AngularJS の要素が記述されています。アプリの大枠には、Onsen UI 提供のスライディングメニューのテンプレートを使用しています。こちらのテンプレートは、Monacaから入手できます。どのテンプレートを使用するかは、開発者側の自由です。DOM の取り扱い方法などに、他のアイデアがある方は、他のテンプレートをご使用ください。

index.html

こちらが、アプリのメインページとなります ( Onsen UI のテンプレートの使用時には、デフォルトで作成されます )。HTML の宣言タグの後に、ng-app="myApp" が記述されています。このディレクティブ ( directive ) を使用して、AngularJS アプリの、いわゆる、自動初期化 ( bootstrap ) を行います。また、このディレクティブは、その役割から、アプリの root 要素を指し示し、通常、ページの root 要素 ( <body> タグ、<html> タグなど ) 付近に置かれます。

スクリプトとシートを記述後、地図を表示するため、Google Mapsスクリプトを記述します。前のバージョンの Google Maps JavaScript API 以降、キーの取得・使用は、任意となりましたが、 Maps API の使用量の監視、追加の割り当ての購入の際には、必要となります。無料の Google Maps API キーの取得・記述方法に関しては、こちらのリンクをご確認ください。

以下のタグを使用して、地図を表示します。



<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>


body の内容を見ます。body の中には、 <ons-sliding-menu> 要素が、1 つ置かれています。こちらが、アプリ内で使用されている Onsen UI 要素の root になります。また、ここでは、属性をいくつか設定します。 var="slidingMenu" は、AngularJS 内部の 双方向 データ バインディング ( two way data binding ) の設定です。menu-page="menu.html" は、スライディングメニュー用の HTML ページです。main-page="map.html" は、最初に表示するメインページです。また、上述のように、デフォルトでは、メニューのスワイプ表示はできません ( swipeable="false" )。



<!DOCTYPE HTML>
<html ng-app="myApp">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
        <script src="components/loader.js"></script>
        <link rel="stylesheet" href="components/loader.css">
        <link rel="stylesheet" href="css/style.css">
        <script type="text/javascript"
            src="https://maps.googleapis.com/maps/api/js">
        </script>
        <script src="scripts/controller.js"></script>
    </head>
    <body>
        <ons-sliding-menu swipeable="false" var="slidingMenu" menu-page="menu.html" main-page="map.html" side="left" type="overlay" max-slide-distance="200px">
        </ons-sliding-menu>
    </body>
</html>


menu.html

このページには、Monaca 側で自動作成した、<ons-sliding-menu> 要素用のコードが記述されています。<ons-list> が記述され、その中には、2 つの <ons-list-item> が置かれています。この 2 つの <ons-list-item> を使用して、map.html と settings.html へのページ遷移を、それぞれ行っています。これ以外のページも追加したい場合には、適宜、<ons-list-item> を追加して、ページ遷移を行います。



<ons-page style="background-color: white">
    <ons-list>
        <ons-list-item
            modifier="tappable" class="list__item__line-height"
            onclick="slidingMenu.setMainPage('map.html', {closeMenu: true})">
            <i class="fa fa-home fa-lg" style="color: #666"></i>
             &nbsp; Map
        </ons-list-item>
        <ons-list-item
            modifier="tappable" class="list__item__line-height"
            onclick="slidingMenu.setMainPage('settings.html', {closeMenu: true})">
            <i class="fa fa-gear fa-lg" style="color: #666"></i>
             &nbsp; Settings
        </ons-list-item>
    </ons-list>
</ons-page>


map.html

こちらが、アプリの核となるページです。地図本体と複数の Onsen UI 要素から構成されています。ページ上部で、コントローラーの宣言 ( ng-controller="MapController" ) をしています。コントローラーの宣言は、root 要素で通常行われるので、それ以降の要素は、その影響 ( 設定 ) 下に置かれることが、一目でわかります。

また、その直下には、<ons-toolbar> が設定され、ページのタイトル部の設定と <ons-toolbar-button> が記述されています。<ons-toolbar-button> には、スライディングメニューの開閉を行う記述をします。このため、<ons-toolbar> 内で、別のコントローラー ( ng-controller="SlidingMenuController" ) を宣言しています。

残りのコードは、地図、検索ボックス、2 個のボタン ( <ons-button> ) を表示するためのものです。



<ons-navigator ng-controller="MapController">
   <ons-page>
        <ons-toolbar fixed-style ng-controller="SlidingMenuController">
            <div class="left">
                <ons-toolbar-button ng-click="slidingMenu.toggleMenu()"><ons-icon icon="bars"></ons-icon></ons-toolbar-button>
            </div>
            <div class="center">Map</div>
        </ons-toolbar>

        <div id="map_canvas"></div>

        <p class="par-search">
            <input type="search" class="search-input search-input-map">
        </p>

        <p class="par-buttons">
            <ons-button  class="btn-delete" ng-click="deleteAllMarkers()">
                Delete all markers        
            </ons-button>
            <ons-button class="btn-distance" ng-click="calculateDistance()">
                Distance      
            </ons-button>
        </p>   
    </ons-page>
</ons-navigator>


結論

Onsen UI、Google Maps JavaScript API v3、AngularJS の使用方法を、このサンプルアプリでお見せしました。
このサンプルアプリをひな形として、後は、自由にカスタマイズしてください。Google Maps API では、上述以外でも、たくさんの機能を提供しています。
自作する前に、Google Maps API の詳細を読み、使用できるものがないか、確認しましょう。

Onsen UI を使用して、HTML5ハイブリッドアプリを作ってみよう

今回の記事は、Onsen UI blogで2月に公開した"Developing hybrid mobile applications with Onsen UI"の翻訳記事です。

ハイブリッドアプリ開発のお話を、最近はあちらこちらで耳にするようになりました。プログラム知識が乏しい初心者マークの方、手っ取り早くアプリを開発したい方には、ネイティブアプリ開発のハードルは、高いのが現状です。ネイティブアプリを開発するためには、各プラットフォーム専用のプログラム言語を学び、かつ、開発対象の端末側の機能も学ぶ必要があります。
もちろん、パフォーマンスが良い、端末側のリソースが利用できるなど、ネイティブアプリの開発にも、利点はたくさんあります。



一方、ハイブリッドアプリで使用するテクノロジーは、Web アプリで使用するもの ( HTML、CSSJavaScript ) と同様であり、プラットフォームには依存しません。Web アプリのテクノロジーに関するノウハウは、膨大で、かつ、簡単に、インターネットから入手できます。
また、ハイブリッドアプリの開発時には、Web 開発で使用していたツールやフレームワークを、そのまま流用できます。jQuery や AngularJS が良い例です。よって、Web アプリ開発者も、ハイブリッドアプリ開発に転向しやすいのではないでしょうか。
ハイブリッドアプリは、ネイティブアプリよりも性能が劣ると、一般的に思われていますが、以前よりもパワフルになった、現在の端末では、ユーザーがその差に気付くことはありません。

ここでは、ハイブリッドアプリのサンプルとして、メモ帳アプリを構築し、併せて、Onsen UIの使用方法も学びます。Onsen UI を使用すれば、洗練されたユーザーインターフェイス ( UI ) を簡単に構築できます。また、アプリの開発には、Monaca クラウド IDEを使用します。Monaca クラウド IDE は、Onsen UI をサポートし、かつ、デバッガーも実装されています ( 機能制限なしの無料アカウントを、こちらで作成できます )。Monaca クラウド IDE 上では、アプリの確認・修正を行え、実機上では、アプリの確認を行えます。こちらから、無料で、端末用のデバッガーを入手できます。また、アプリ開発に不慣れな方でも、アプリへのプラグインのインポート、デバッグ、ビルドは、Monaca 側で行ってくれるので安心です。他の IDE を使用する場合には、必要なプラグインを手動で組み込んでください。

Onsen UI と AngularJS



Onsen UI は、HTML5 フレームワークです。Onsen UI を使用すれば、モダンで、見栄えのするユーザーインターフェイスを作成できます。これにより、UI 開発に費やしていた時間を縮小でき、
その分、アプリ本体の性能・機能の充実に、時間を充てることができます。Onsen UI は、AngularJS の使用を前提に設計されていますが、jQuery や他のフレームワークとも併用できます。ここでは、AngularJS を使用しますが、Onsen UI に焦点を当てるため、AngularJS に関しては、込み入った箇所のみ、解説します。

ここで作成するアプリは、メモ帳アプリです。このアプリでは、作業の一覧の保存、作業カテゴリー別のソート、作業詳細の編集・削除を行います。以下の Onsen UI コンポーネントを使用して、アプリを構築します。


  • ons-navigator

  • ons-toolbar

  • ons-sliding-menu

  • ons-list

  • ons-carousel

  • ons-template

  • ons-popover



これらのコンポーネント ( 要素 ) の使用方法も、順次、解説します。スライディングメニュー表示を行うページを指定したり、または、カルーセルを使用して、複数ある表示用コンテンツをまとめるなど、表示機能のオプションも充実しています。Onsen UI コンポーネントのドキュメントは、こちらでご確認ください。また、こちらも併せてご確認ください。

ここでは、Onsen UI 1.2.1 を使用します。Monacaをご利用の方は、ダッシュボードに表示されている 「 Onsen UI 最小限のテンプレート 」 を開いてください。このテンプレートでは、AngularJS は、ライブラリーとして、デフォルトで組み込まれています。Monaca を利用しない場合には、Onsen UI のドキュメントを確認して、必要なライブラリーを組み込んでください。

最初に、このアプリにおける、AngularJS の取り扱い方法を解説します。AngularJS では、アプリを構成する各ページまたはメインとなる各要素 ( <ons-XXX> ) に、それぞれ、1 個のコントローラーを紐付けます。コントローラーでは、紐付けされたページで使用するスコープ ( Scope ) の定義、および、必要な変数と関数の定義を行います。
たとえば、登録した作業の一覧をメインページに表示する場合、メインページ用のコントローラーを作成して、スコープを定義して、作業データを格納した配列を、そのスコープに代入する処理を行います。また、このコントローラー内に、配列データの編集・削除用の関数なども置けます。しかし、作業別にコントローラーを作成して、処理を行った方が良い場合もあります。このサンプルアプリでは、使用しているページの役割がそれぞれ異なるため、処理を分け、データのみ、コントローラー間で共有します ( 作業の 「 登録 」 ページ、「 詳細表示 」 ページなど、処理は異なりますが、いずれも作業詳細のデータを共有します )。



よって、データ共有の際には、コントローラー間の交信が必要となります。このアプリでは、serviceを 1 個使用して、登録済み作業の保存、および、コントーラー間で共通する処理の制御を行います。このようにすることで、各コントローラーから、必要に応じて、この service を呼び出せます。
登録された作業と作業一覧は、オブジェクトであり、models/Memo.js 内で定義されています。各作業のオブジェクトには、必要な情報 ( 名前、カテゴリー、説明、作成日、進捗状態 ) が格納されます。このような設計 ( model ディレクトリーの設置とオブジェクトの定義 ) になっていますが、これは、Onsen UI 使用時のお手本ではなく、一例ですので、設計は、開発者が自由に行ってください。サンプルコードは、GitHub から入手できます。

前置きが長くなりました。ここからは、実際にコードを確認してみましょう。

メモ帳アプリのコード

このアプリのコードは、GitHubから入手できます。不明確な点がある場合には、コードの中をご確認ください。また、コードを確認する前に、以下で、アプリを実際に操作してみましょう。



スタイルとデザインは、自由に、カスタマイズできます。

デザインとコード

Onsen UI 要素と起動時のページ

Onsen UI では、メインとなる要素は、1 つに絞ることを推奨します。このメインの要素は、アプリの 「 型 ( pattern ) 」となり、ページ制御に適用・使用されます。
たとえば、ページ遷移の制御を行うなら、スライディングメニュー型、タブバー型などがあります。ここでは、最も頻繁に使用されるナビゲーション型を使用します。この型を使用すると、親子関係がページ間に設定されます ( つまり、スタック内にページが置かれ、それぞれを行き来します )。
よって、ここでは、ons-navigator を、メインの要素として、index.html に置きます。



<ons-navigator var="myNavigator">
  <ons-page>
    <ons-toolbar fixed-style>
      <div class="center">Memo Onsen UI App Example</div>
    </ons-toolbar>
        <div style="margin: auto; width: 95%;">
          <div class="margins">
            <span style="color:#666"><ons-icon icon="fa-check-square-o" size="22em"></ons-icon></span>
          </div>
        </div>
    <ons-button modifier="large--cta" onclick="myNavigator.resetToPage('slidingmenu.html')">Start</ons-button>
  </ons-page>
</ons-navigator>


var=”myNavigator” を指定して、ナビゲーター ( ons-navigator ) の名前を宣言します。これにより、グローバル変数として、後々、名前で参照できます。ナビゲーター内では、ons-page を使用して、起動時のページを指定します。このページ上では、ons-toolbar を使用して、アプリのタイトルを表示します。

Tip 1 : class=”center” を使用して、タイトルを設定しても、アプリを実行する OS 側が、スタイルを決定します。iOS では、中央揃えとなり、Android では、左揃えとなります。タイトルを中央に固定する場合には、ons-toolbar 内で、fixed-style を設定します。

表示イメージは自由に設定できますが、ここでは、ons-icon を使用して、アイコンを表示します。Onsen UI では、IoniconsFontawesomeのアイコンも使用でき、どの端末上でも支障なく表示できます。アプリ起動時のページで使用する最後の要素は、スタートボタンです。スタートボタンをクリックすると、アプリのメインページに遷移します。ここでは、ボタンに関しては、ons-button を使用して、また、アプリの外見の選択に関しては、modifierを使用します。ボタンのクリック時、ナビゲーターが、ページを遷移させます。ここでは、pushPage( … ) の代わりに、resetToPage( … ) を使用して、前のページをスタックからすべて削除して、slidingmenu.html 内で指定されているページを表示します ( 0 から開始 )。このような処理をすることで、メインページに遷移した後、戻るボタンを押しても、起動時のページに戻ることはありません。

メニュー

メインページを解説します。こちらのメインページ上で、アプリに登録された作業の一覧が表示されます。スライディングメニューをこのページ上に置くので、メインページの親として、このメニューの要素を設定します。slidingmenu.html を作成して、このページ上に、スライディングメニュー本体を設定します。Onsen UI では、テンプレート ( ons-template ) を使用すれば、index.html 内に、別の HTML ページを作成して、配置でき、通常の HTML と同様に動作します。各 HTML のページがコンパクトであれば、1 ページ上にすべてのページが収まります。



<!-- SLIDING MENU -->
<ons-template id="slidingmenu.html">
  <ons-page>
    <ons-sliding-menu var="slidingMenu" swipeable="false" menu-page="menu.html" main-page="memo.html"  side="left" type="overlay" max-slide-distance="200px">
    </ons-sliding-menu>
  </ons-page>
</ons-template>

ons-sliding-menu を使用して、menu-page=”menu.html” から、メニュー用コンテンツを取得します。また、スライディングメニューは、main-page=”memo.html” 上に表示されます。ここでは、作業一覧に設定されているカルーセルの誤作動を避けるため、swipeable=”false” にします。

menu.html の内容を見てみましょう。



<!-- MENU -->
<ons-template id="menu.html">
  <ons-page fixed-style style="background-color: white">
    <ons-toolbar fixed-style>
      <div class="center">Categories</div>
      <div class="right" style="margin-top:5px"><ons-button modifier="quiet" ng-click="slidingMenu.closeMenu()"><ons-icon icon="fa-chevron-left" size="25px" fixed-width="false" style="color: #5087c3"></ons-icon></ons-back-button></div>
    </ons-toolbar>

        <!-- List of items -->
    <ons-list id="categoryList" ng-controller="categoryController">


こちらは、ons-list-item の静的な部分です。



  <ons-list-item
    modifier="tappable" class="list__item__line-height"
    ng-click="setViewRefresh('*'); slidingMenu.closeMenu();">
    <i class="ion-home fa-lg" style="color: #666"></i>
     &nbsp; All
    <span class="item-label">{{countAll}}</span>
  </ons-list-item>
  <ons-list-item
    modifier="tappable" class="list__item__line-height"
    ng-click="setViewRefresh('~'); slidingMenu.closeMenu();">
    <i class="ion-checkmark" style="color: #666"></i>
     &nbsp; Completed
    <span class="item-label">{{completedCount ? completedCount : 0}}</span>
  </ons-list-item>
  <ons-list-header>
    <div style="text-align: center;"><ons-icon icon="ion-minus-round"></ons-icon></div>
  </ons-list-header>
  <ons-list-item
    modifier="tappable" class="list__item__line-height"
    ng-click="setViewRefresh('=', ' '); slidingMenu.closeMenu();">
    <i class="ion-qr-scanner fa-lg" style="color: #666"></i>
     &nbsp; No category
    <span class="item-label">{{countCategories[' '] ? (countCategories[' '].total - countCategories[' '].completed) : 0}} ({{countCategories[' '] ? countCategories[' '].total : 0}})</span>
  </ons-list-item>


こちらは、ons-list-item の動的な部分です ( AngularJS の ng-repeat を使用 )。



      <ons-list-item
        modifier="tappable" class="list__item__line-height"
        ng-click="setViewRefresh('=', item); slidingMenu.closeMenu();" ng-repeat="item in categoryList">
        <i class="ion-folder fa-lg" style="color: #666"></i>
         &nbsp; {{item}}
        <span class="item-label">{{countCategories[item].total - countCategories[item].completed}} ({{countCategories[item].total}})</span>
      </ons-list-item>
    </ons-list>
  </ons-page>
</ons-template>


ons-template を使用しているので、こちらも index.html 内に置けます。また、ons-toolbar を使用して、メニューのタイトルと閉じるボタンを設定しています。このメニューでは、作業に紐付けされたカテゴリーに応じて、該当する作業を一覧化します。
カテゴリー一覧の内容は、ng-controller="categoryController" から取得します。静的な部分には、3 つの固定メニューがあります ( すべて表示、完了した作業を表示、未カテゴリーの作業の表示 )。こちらは、メニューの例なので、自由に編集してください。

固定メニューの表示後に、AngularJS の ng-repeat を使用して ( 最後の ons-list-item を参照 )、ons-list-item を繰り返します。この設定で、すべてのカテゴリーを、メニュー上に表示できます。また、ここでは、各カテゴリーに該当する作業数を表示するため、categoryController から、その情報を取得します。このサンプルでは、countAll と呼ぶカウンター変数を使用して、メモ帳アプリ内に登録されている作業の総数を取得します。この変数は、service ( memoService ) 内に置かれ、コントローラー経由で変更 ( 作業の登録・削除時 ) できます。また、ここでは、categoryController 内に、この変数に対して、リスナーを設定します。これにより、作業数に変更があれば、AngularJS 側で、自動的に値を更新してくれます ( View の更新 )。



$scope.$watch(function() {
  return memoService.countRawMemo();
}, function(newValue) {
  $scope.countAll = newValue;
}, true);


categoryController の setViewRefresh(...) メソッドを使用して、表示内容を変更します ( View の更新 )。

メインページ

メインページ ( memo.html ) の内容を確認しましょう。ここで、登録済み作業が一覧化されます。



<ons-page ng-controller="memoController">
  <ons-toolbar fixed-style ng-controller="slidingMenuController">
    <div class="left"><ons-toolbar-button ng-click="slidingMenu.openMenu(); checkSlidingMenuStatus();"><ons-icon icon="bars"></ons-icon></ons-toolbar-button></div>
    <div class="center">My tasks</div>
    <div class="right">
      <ons-button modifier="quiet" onclick="myNavigator.pushPage('additem.html')">
          New <ons-icon icon="fa-plus-circle "></ons-icon>
      </ons-button>
    </div>
  </ons-toolbar>


作業を一覧化します。カルーセル表示 ( 各作業の表示時 ) を使用します。



<p style="text-align: center; color: #999; font-size: 14px;">{{category_label}}</p>
  <div class="list-action-item" ng-hide="countFiltered">Nothing found</div>
  <ons-list>
    <ons-list-item modifier="tappable" ng-repeat="item in filteredMemo track by $index">
      <ons-carousel var="{{'carousel.id' + $index}}" swipeable style="height: 72px; width: 100%;" initial-index="1" auto-scroll>


ルーセルのインデックス番号 0 には、3 つの処理 ( 3 つのボタン ) を設定します。



        <ons-carousel-item class="list-action-menu">
          <!-- ACTIONS -->
          <div class="main-container">
            <div class="fixer-container">
              <div class="blockInline">
                <ons-button ng-click="deleteItem($index); carousel['id'+$index].setActiveCarouselItemIndex(1);">
                  Delete
                  <ons-icon icon="ion-trash-a"></ons-icon>
                </ons-button>
              </div>
              <div class="blockInline">
                <ons-button ng-click="carousel['id'+$index].setActiveCarouselItemIndex(1); completeItem($index);" ng-hide="item.completed">
                  Complete
                  <ons-icon icon="ion-checkmark-round"></ons-icon>
                </ons-button>
              </div>
              <div class="blockInline">
                <ons-button ng-click="setSelected($index); myNavigator.pushPage('itemdetails.html');">
                  Details
                  <ons-icon icon="ion-clipboard"></ons-icon>
                </ons-button>
              </div>
            </div>
          </div>
        </ons-carousel-item>


ルーセルのインデックス番号 1 には、作業詳細の表示設定をします。



        <ons-carousel-item class="list-action-item" ng-click="setSelected($index); myNavigator.pushPage('itemdetails.html');">
          <div class="name">
            {{item.name}} <span class="desc"><ons-icon icon="ion-checkmark-round" ng-show="item.completed"></ons-icon></span>
          </div>
          <div class="desc">
            {{item.date.getFullYear() + "/" + (item.date.getMonth() + 1) + "/" + item.date.getDate()}}
          </div>
        </ons-carousel-item>
      </ons-carousel>
    </ons-list-item>
    </ons-list>
</ons-page>


このページは、重要で、大きいため、ons-template を使用して、index.html 内に置くのではなく、別個のページを用意します。index.html 内に、まとめて置く場合には、上述したスライディングメニューなど、比較的、短い記述に限定します。いずれの場合でも、ons-toolbar を使用して、
タイトル、新規のページへのリンク ( additems.html の説明は後ほど )、スライディングメニューの表示ボタンを設定します。

Tip 2 : スライディングメニューに設定してある、ちょっとした作り込みをご紹介します。スライディングメニューは、表示されているときだけ、スワイプでき、非表示のときは、スワイプ不可になっています。これにより、作業一覧に組み込まれたカルーセルと衝突しません。ここでは、スライディングメニューの動作を変更するため、以下のように、2 つのイベントを用意します。



myApp.controller('slidingMenuController', function($scope){
   $scope.slidingMenu.on('postclose', function(){ $scope.slidingMenu.setSwipeable(false); });
   $scope.slidingMenu.on('postopen', function(){ $scope.slidingMenu.setSwipeable(true); });
});


また、表示する作業がない場合には、ng-hide を使用して、メッセージを表示します。また、ons-list を動的に使用して、かつ、ons-list 内の item ( ons-list-item ) 毎に、ons-carousel 設定を行って、作業の一覧を表示します。ここでは、ng-repeat を使用して、memoController 側から受け取った、作業を一覧表示します。また、各作業 ( ons-list-item ) には、ons-carousel が設定されています。カルーセルには、インデックス番号 ( 0、1 ) があり、1 には作業詳細、0 には処理ボタン ( ons-carousel-item ) を設定しています。作業一覧の表示時には、インデックス番号 1 の内容が表示され、右方向へスワイプすると、0 の内容が表示されます。

Tip 3 : インデックス番号 0 のボタンの縦位置・横位置は、その数に関わらず、中央揃えになります。



CSS の記述を、以下に記します。



.main-container {
    float: left;
    position: relative;
    left: 50%;
}

.fixer-container{
    float: left;
    position: relative;
    left: -50%;
}

.blockInline{
    margin-left: 3px;
    margin-right: 3px;
    display: inline;
    position: relative;
    top: 50%;
    transform: translateY(-50%);
}


作業一覧から、作業を削除する場合は、memoController 内に記述された、削除用の関数を実行します。また、作業内容の変更や作業完了のマーク付けも、対応する関数を実行して行います。作業完了の処理を行う関数では、作業一覧の再表示時に、完了作業の横に、チェックマークアイコンを表示するように、かつ、インデックス番号 1 の内容を表示するように、「 対応する 」 カルーセルを更新しています。

Tip 4 : カルーセルを更新して、表示を元に戻すには、一覧上のカルーセル毎に、一意の名前を、動的に割り振ります。これにより、後から、その名前を参照でき、また、インデックス番号も更新できます ( よって、上述の 「 対応する 」 カルーセルを更新できます )。ここでは、var="{{'carousel.id' + $index}}" を使用して、carousel.idX 形式で、カルーセルに名前を割り振ります。この X は、ons-list 内の item ( ons-list-item ) のインデックス番号です。静的な方法 ( 例 : var=”myCarousel” ) で、item ( ons-list-item ) の名前を振った場合、ng-repeat 下では、旧 item ( ons-list-item ) の名前がすべて上書きされ、よって、myCarousel を使用して呼び出せるのは、最後の item だけになってしまいます。また、setActiveCarouselItemIndex(1) 関数を呼び出す際には、carousel['id'+$index].setActiveCarouselItemIndex(1); のように、ドット記法ではなく、ブラケット記法を使用します。

作業詳細の表示と変更

作業詳細の表示と変更に関して、解説します。itemdetails.html の内容を見てみましょう。ここでは、コントローラーを 1 つ設定しています。



<ons-page ng-controller="detailsController">
  <ons-toolbar fixed-style>
    <div class="left"><ons-back-button>Back</ons-back-button></div>
    <div class="center"> Task details </div>
        <div class="right">
      <ons-button modifier="quiet" ng-click="modifyItem();">
      Save <ons-icon icon="ion-checkmark"></ons-icon>
      </ons-button>
        </div>
  </ons-toolbar>
  <div style="text-align: center">
    
    <ons-list id="itemList">
      <ons-list-item class="item">
        <ons-row>
          <ons-col width="60px">
            <div class="item-thum"></div>
          </ons-col>
          <ons-col>
            <header>
              <span id="item-name" class="item-name"><input type="text" ng-model="item_name" placeholder="Name" class="text-input text-input--transparent" style="margin-top:8px; width: 100%;"></span>
              <span id="item-category" class="item-name"><input type="text" ng-model="item_category" placeholder="Category" class="text-input text-input--transparent" style="margin-top:8px; width: 100%;"></span>
              <span id="item-description" class="item-name"><textarea type="text" ng-model="item_description" placeholder="Description"  class="textarea textarea--transparent" style="margin-top:8px; width: 100%;"></span>
            </header>
          </ons-col>
        </ons-row>
      </ons-list-item>
    </ons-list>
  </div>
</ons-page>


このページは、myNavigator.pushPage( … ) を使用して、メインページから呼び出されます。myNavigator.pushPage( … ) が行う処理は、ons-navigator 内のスタックへ、新たなページを追加することです。追加後は、myNavigator.popPage() または ons-back-button を使用すれば、スタックに置かれている、前のページに戻れます。

このページ上では、指定された作業の内容を、HTML の input 内に表示します。HTML の input を使用することにより、表示と同時に、変更も行えます。また、各 input には、ng-model を設定 ( 紐付け ) して、コントローラーから新しい値を参照できるようにします。たとえば、ここでは、作業名と ng-model=”item_name” を紐付けして、detailsController 内から、$scope.item_name を使用して、作業名を参照しています。

新規作業の追加とポップアップ表示



新規作業の追加処理は、additem.html で行います。非常に簡単な処理です。前述のように、HTML の input と ng-model の紐付けを行い、addItemController 内で、入力値を確認した後、memoService の作業一覧へ新規作業を追加します。
また、ここでは、名前用の input が未入力の場合、警告を出し、新規作業の作成を行えないようにします。この処理を行う場合、最初に、index.html 内に、ポップアップ ( ons-popover ) を、以下のように追加します。



<!-- POPOVER -->
<ons-template id="popover.html">
  <ons-popover direction="up down" cancelable>
    <div style="text-align: center; opacity: 0.5;">
      <p>Name input is empty!</p>
      <p><small>Enter a name for your task to create or modify it.</small></p>
    </div>
  </ons-popover>
</ons-template>


次に、addItemController と detailsController のコントローラーから、変更の保存時に、ポップアップを呼び出せるように処理します。



ons.createPopover('popover.html').then(function(popover) {
  $scope.popover = popover;
});
$scope.popover.show('#item-name');


最後の行では、HTML 要素 の '#item-name' 上に、ポップアップを表示します。ここでは、input が未入力の場合に、ポップアップを表示しますが、表示のタイミングは、制御する必要があります。

備考

ここで使用したサンプルアプリでは、Onsen UI と AngularJS に焦点を当てたため、window.localStorage、Web SQL、IndexedDB などを使用した、データの保存方法に関しては、触れていません。もちろん、実装できますが、ここでは、固定のサンプルデータを使用して、アプリの動作を即検証できるようにしています。

結論

ここでは、Onsen UI が提供してくれる、使えるユーザーインターフェイスを使用して、簡単なハイブリッドアプリを開発しました。また、開発に役立つ Tip も併記しました。AngularJS の使用は、必須ではありませんが、Onsen UI と相性が良いため、使用した方が効率が良くなります。前述のように、ここで使用したコードは、GitHub上に置いてありますので、自由に、触ってみてください。

以上ですが、開発を簡単にしてくれる、Onsen UI のようなツールがあれば、ハイブリッドアプリの開発も難しくはありません。質問がある方は、このブログにコメントを残すか、スタックオーバーフロー ( onsen-ui タグ下 )に寄稿してください。Onsen UI の使用例に関しては、今後もブログにアップしていく予定です。

Onsen UIをWebサイトで使ってみよう

HTML5のUIフレームワークであるOnsen UIはMonacaとの相性を考えて開発されていますが、必ずしもハイブリッドアプリ専用という訳ではありません。Webサイト開発にも利用が可能です。



GoogleがWebサイトのモバイル対応が行われているかどうかを検索順位に反映することを発表していたり、HTML5によってWebもどんどん多機能になっている中、Onsen UIを使うことで高速な描画で使いやすいWebサイトが構築できるはずです。



そこで今回はOnsen UIをWebサイト開発に利用する方法について紹介したいと思います。



準備



Onsen UI自体はJavaScript/スタイルシートで出来ていますのでダウンロードして配置するだけですが、今回はbowerを使って行いたいと思います。Onsen UIはbowerからコマンド一つでインストール可能です。bowerはnode.jsのパッケージ管理、npmを使ってインストールします。




  1. node.jsのインストール(npmもインストールされます)
  2. Gitのインストール
  3. bowerのインストール



node.jsはNode.js Downloadからダウンロード/インストールができます。GitはMac OSXであればHomebrewやMacPortsから、WindowsであればGit for Windowsをダウンロード/インストールしてください。終わったら続いてbowerをインストールします。





$ npm install -g bower


これでインストールは完了です。



作業用ディレクトリの作成



続いてWebサイトを作るディレクトリ(フォルダ)を作成します。今回は onsenui_for_website としています。そのディレクトリの中でbowerコマンドを実行します。





$ bower install onsenui
bower cached        git://github.com/OnsenUI/OnsenUI.git#1.2.2
bower validate      1.2.2 against git://github.com/OnsenUI/OnsenUI.git#*
bower cached        git://github.com/angular/bower-angular.git#1.3.15
bower validate      1.3.15 against git://github.com/angular/bower-angular.git#~1.3.0
bower install       onsenui#1.2.2
bower install       angular#1.3.15

onsenui#1.2.2 bower_components/onsenui
└── angular#1.3.15

angular#1.3.15 bower_components/angular


Onsen UIがダウンロードされて、 bower_components 以下に展開されます。





Onsen UIのファイル一覧



Onsen UIを使う上で関連付いているAngularJSも一緒に展開されます。これでほぼ準備は完了です。



Gulpの準備



そのまま使っても良いのですが、 bower_components というフォルダに入ってしまっていたり、実際の運用時には不要なファイルも多いので Gulp を使ってまとめることにします。既にnpmは入っていますので、インストールは簡単です。まず最初にnpmで必要なパッケージを管理するようにします。





$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (onsenui_for_website) 
version: (1.0.0) 0.0.1
description: Web site that use the Onsen UI
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: Atsushi Nakatsugawa
license: (ISC) MIT
About to write to /Users/nakatsugawa/Dropbox/DevRel/Monaca/onsenui_for_website/package.json:

{
  "name": "onsenui_for_website",
  "version": "0.0.1",
  "description": "Web site that use the Onsen UI",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\"  &amp; &amp; exit 1"
  },
  "author": "Atsushi Nakatsugawa",
  "license": "MIT"
}


Is this ok? (yes) yes


そうすると package.json というファイルが作成されて、ライブラリを管理できるようになります。後はGulpと必要なライブラリをインストールしていきます。





$ npm install --save-dev -g gulp
$ npm install --save-dev gulp-changed # ここから下はGulpのプラグインです。
$ npm install --save-dev gulp-concat
$ npm install --save-dev gulp-coffee
$ npm install --save-dev gulp-uglify
$ npm install --save-dev gulp-sourcemaps
$ npm install --save-dev gulp-webserver
$ npm install --save-dev del


後はGulp用の設定ファイル、gulpfile.js を作成します。





// gulpfile.js
var gulp = require('gulp');
var coffee = require('gulp-coffee');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var webserver = require('gulp-webserver');
var sourcemaps = require('gulp-sourcemaps');
var del = require('del');

var paths = {
  scripts: ['app/js/**/*.coffee', '!app/external/**/*.coffee']
};

// Webサーバの機能です
gulp.task('webserver', function() {
  gulp.src("./")
    .pipe(webserver({
      livereload: true,
    }));
});

// スクリプトの結合と配置を行っています
gulp.task('scripts', function() {
  return gulp.src([
      './bower_components/onsenui/build/js/angular/angular.min.js',
      './bower_components/onsenui/build/js/onsenui_all.min.js'
  ])
    .pipe(concat('all.js'))
    .pipe(gulp.dest('./javascripts/'));
});

// ファイルの変更を監視しています(今回は使っていません)
gulp.task('watch', function() {
  gulp.watch(paths.scripts, ['scripts']);
});

gulp.task('default', ['webserver', 'scripts', 'watch']);


これで $ gulp scripts と実行すると、AngularJSとOnsen UIのJavaScriptファイルがくっついた状態で javascripts/all.js として生成されます。なおスタイルシートについても同様の操作が可能ですが、今回は bower_components/onsenui/build/css/ を プロジェクトルート直下に stylesheets という名称でコピーしています。



その結果、次のようなファイル構成になっていればOKです。





Onsen UIのファイル構成



index.htmlの編集



では実際に表示を行うindex.htmlを作成しましょう。まず最初に次のようにHTMLファイルを作成します。





<!DOCTYPE html>
<html class="">
  <head>
    <meta charset="UTF-8">
    <meta name="robots" content="noindex">
    <link rel="stylesheet prefetch" href="stylesheets/onsenui.css">
    <link rel="stylesheet prefetch" href="stylesheets/onsen-css-components.css">
  </head>
  <body>
	<script src="javascripts/all.js"></script>
	<script>
      ons.bootstrap();
      //@ sourceURL=pen.js
    </script>
  </body>
</html>


この形が Onsen UIの基本形になります。後はこの <body>〜</body>の間にコンテンツを書くだけです。JavaScript | Onsen UIには多くのコンポーネントを用意しています。例えばチェックボックスを表示します。内容はPattern: Checkbox List | Onsen UIにもありますが、bodyタグの中に次のように記述します。





<ons-page>
  <ons-toolbar>
    <div class="center">Checkboxes</div>
  </ons-toolbar>
  <ons-list>
    <ons-list-header>Favorite Sports</ons-list-header>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--list-item">
        <input type="checkbox">
        <div class="checkbox__checkmark checkbox--list-item__checkmark"></div>
        Baseball
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--list-item">
        <input type="checkbox">
        <div class="checkbox__checkmark checkbox--list-item__checkmark"></div>
        Soccer
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--list-item">
        <input type="checkbox" checked="checked">
        <div class="checkbox__checkmark checkbox--list-item__checkmark"></div>
        Basketball
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--list-item">
        <input type="checkbox" checked="checked">
        <div class="checkbox__checkmark checkbox--list-item__checkmark"></div>
        Golf
      </label>
    </ons-list-item>

    <ons-list-header>Favorite Food</ons-list-header>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--noborder checkbox--list-item">
        <input type="checkbox" checked="checked">
        <div class="checkbox__checkmark checkbox--noborder__checkmark checkbox--list-item__checkmark"></div>
        Hamburger
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--noborder checkbox--list-item">
        <input type="checkbox">
        <div class="checkbox__checkmark checkbox--noborder__checkmark checkbox--list-item__checkmark"></div>
        Beefsteak
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--noborder checkbox--list-item">
        <input type="checkbox" checked="checked">
        <div class="checkbox__checkmark checkbox--noborder__checkmark checkbox--list-item__checkmark"></div>
        Spaghetti
      </label>
    </ons-list-item>

    <ons-list-item modifier="tappable">
      <label class="checkbox checkbox--noborder checkbox--list-item">
        <input type="checkbox">
        <div class="checkbox__checkmark checkbox--noborder__checkmark checkbox--list-item__checkmark"></div>
        Natto
      </label>
    </ons-list-item>
  </ons-list>
</ons-page>


この内容を書いたら、 gulp コマンドを実行します。そうすると8000番ポートでWebサーバが立ち上がるので、 http://localhost:8000/ にアクセスします。





チェックボックス一覧



プロフィール風画面を作る



同じようにbodyタグの内容を次のように変更します。





<ons-page>
  <ons-toolbar>
    <div class="left"><ons-back-button>Back</ons-back-button></div>
    <div class="center">Profile</div>
    <div class="right">
      <ons-toolbar-button><ons-icon icon="ion-gear-a" style="vertical-align: -4px; font-size: 28px;"></ons-icon></ons-toolbar-button>
    </div>
  </ons-toolbar>

  <div class="profile-card">
    <a href="https://www.asial.co.jpimages/profile-image-01.png &mode=1" class="popupimg"><img src="https://www.asial.co.jpimages/profile-image-01.png"></a>
    <div class="profile-name">Dave Graham</div>
    <div class="profile-id">@davegraham</div>
    <div class="profile-desc">Freelance designer, software engineer and cyclist</div>
  </div>

  <ons-list>
    <ons-list-item>
      <ons-row>
        <ons-col class="info-col">
          <div class="info-num">87</div>
          <div class="info-unit">Comments</div>
        </ons-col>
        <ons-col class="info-col">
          <div class="info-num">40</div>
          <div class="info-unit">Following</div>
        </ons-col>
        <ons-col class="info-col">
          <div class="info-num">38</div>
          <div class="info-unit">Followers</div>
        </ons-col>
      </ons-row>
    </ons-list-item>
  </ons-list>

  <ons-list modifier="inset" style="margin-top: 10px">
    <ons-list-item modifier="chevron">
      Write a comment
    </ons-list-item>
    <ons-list-item modifier="chevron">
      See details
    </ons-list-item>
    <ons-list-item modifier="chevron">
      Save to the list
    </ons-list-item>
  </ons-list>
  <br>
</ons-page>



この場合は、別途スタイルシートが必要です。stylesheets/profile.cssなどとして次の内容でファイルを作成してください。また、HTML側でlinkタグを使って読み込み指定してください。





.profile-card {
  width: 100%;
  text-align: center;
  color: white;
  padding: 30px 0;
  line-height: 1.4;
  background-color: #33393c;
  text-shadow: rgba(0, 0, 0, 0.4) 0px 1px 0px;
}

.profile-image {
  height: 100px;
  width: 100px;
  border-radius: 50%;
  -webkit-border-radius: 50%;
  background-color: black;
  border 1px solid white;
  box-shadow:rgba(0, 0, 0, 0.2) 0px 2px 0px 0px;
  -webkit-box-shadow:rgba(0, 0, 0, 0.2) 0px 2px 0px 0px;
}

.profile-name {
  margin: 20px 0 0 0;
  font-weight: 600;
  font-size: 17px;
}

.profile-id {
  margin: 0 0 5px 0;
  font-size: 14px;
  opacity: 0.6;
}

.profile-desc {
  font-size: 15px;
  opacity: 0.6;
  width: 90%;
  text-align: center;
  margin: 0 auto;
}

.info-col {
  height: 60px;
  line-height: 1;
  padding: 12px 0 12px 4px;
}

.info-num {
  font-size: 16px;
  font-weight: 500;
  opacity: 0.8;
}

.info-unit {
  margin-top: 6px;
  font-size: 14px;
  opacity: 0.4;
}


後、こちらの画像をダウンロードして、 images/profile-image–01.png として保存してください。作業が完了すると、次のように表示されるはずです。





Onsen UIを使ったプロフィールページ



他にもサムネイル付きリストページのテンプレートパターンもあります。こういったデザインはブログやメディアサイトの記事一覧でも利用できるのではないでしょうか。





サムネイル付きリストページ






Onsen UIのアイコンはFont AwesomeやIoniconsを使えるようになっていますので、フラットUIなWebサイトを構築する際にも便利に使えます。よりスタイリッシュで高パフォーマンスなモバイルWebサイトを実現するためにOnsen UIをぜひご利用ください!



Onsen UI - A Custom Elements-Based HTML5 UI Framework | Onsen UI

Monaca公式ガイドブック出版記念「アプリ開発セミナー全国キャラバン」を開始しました

こんにちは塚田です。

2月17日に翔泳社より出版されたMonaca公式ガイド『クラウドでできるHTML5アプリ開発』は、お陰様を持ちましてAmazonのモバイルプログラミングのカテゴリで1位にランキングされるなど、大変ご好評を頂いております。ありがとうございます。

昨日、本書の発売を記念して開催するアプリ開発セミナーの全国キャラバンの記念すべき第一回が東京で開催されました。



会場は、TAMさんのコワーキングスペースをお借りしたのですが、開放感があって非常に心地のよい会場でした。



セミナーではアシアルの岡本が最近のMonacaやCordova関連の動向の解説を加えつつ、MonacaとOnsen UIを使ったアプリ開発手法に関してポイント解説をさせていただきました。



満席の会場からは質問やご意見なども多く頂きまして、非常に熱いセミナーとなりました。

セミナー終了後は、参加者の皆さんと記念撮影。



「キャラバン頑張ってくださいね!」
等の暖かいお声も頂きました。

皆さん本当にありがとうございました。

今後の予定ですが、北は札幌、南は沖縄まで全国7都市、計10回のセミナーを開催致します。

若干ですがまだお席の残っている会場もございますので、是非ご参加ください。

・大阪 3月6日 19時〜(満員)
沖縄 3月11日 19時〜(残席あり)
福岡 3月12日 19時〜(残席あり)
札幌 3月13日 19時〜(残席あり)
・東京 3月17日 19時〜(満席)
・名古屋 3月19日 19時〜(満席)
・京都 3月20日 19時半〜(満席)
東京 3月28日 10時〜(残席あり)
東京 3月28日 14時〜(残席あり)

Onsen UIとjQueryを組み合わせてスマートフォンWebサイト/ハイブリッドアプリを作ろう

Onsen UIはHTML5モバイルアプリを高速化し、かつネイティブアプリのようなUIを提供するフレームワークになっています。技術的にはカスタムエレメントとAngularJSを使って作られていますが、その利用に際してAngularJSの習得が必須という訳ではありません。現在のOnsen UIはAngularJS以外の様々なJavaScriptフレームワークと組み合わせて使えるようになっています。



今回はWeb開発で最も使われているであろうjQueryを使ってOnsen UIの操作を説明したいと思います。



ベースになるHTML



まずはベースのHTMLファイルの内容です。





<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="mobile-web-app-capable" content="yes">
  <title>My App</title>  
  <link rel="stylesheet" href="lib/onsen/css/onsenui.css">  
  <link rel="stylesheet" href="styles/app.css"/>
  <link rel="stylesheet" href="styles/onsen-css-components-blue-basic-theme.css">  
  <script src="lib/onsen/js/angular/angular.js"></script>    
  <script src="lib/onsen/js/onsenui.js"></script>    
  <script src="lib/jquery/jquery-2.1.3.min.js"></script>    
  <script src="js/app.js"></script>  
</head>

<body>
  <ons-navigator title="Navigator" var="myNavigator">
    <ons-page>
      <ons-toolbar>
	<div class="center">Onsen + jQuery デモ</div>
      </ons-toolbar>
      <p>
	<div style="text-align: center">
	  <p><ons-button id="next_page">次のページ</ons-button></p>
	  <p><div id="my-content">書き換えられるコンテンツ</div></p>
	</div>
      </p>
    </ons-page>
  </ons-navigator>
</body>  
</html>


HTMLのレベルではOnsen UIのカスタムエレメントをそのまま書きます。また、カスタムエレメントを通常のHTMLに変換するためにAngularJSとOnsen UIのJavaScriptは必須です。



通常のHTMLに展開する



カスタムエレメントを描画するのはまだ殆どのブラウザが対応していません。そこでOnsen UIのコンパイル機能を使います。





ons.bootstrap();


この bootstrap メソッドによって、以下のように描画されます。





初期表示



見ての通り、





<ons-button id="next_page">次のページ</ons-button>


はボタンの表示になっていますが、





<div id="my-content">書き換えられるコンテンツ</div>


は変わっていません。動的にUIを書き換える場合は次のようにします。





ons.ready(function() {
  var content = $("#my-content");
  content.html("<ons-button>追加されたボタン</ons-button>");
  ons.compile(content[0]);
});


jQueryを使って #my-content の内容を Onsen UIのカスタムエレメントに書き換えています。それを ons.compileに渡すことによってHTMLエレメントに変換しています。jQueryオブジェクトからDOMに変換する際には [0] を使います。$(function() {}) ではなく、 ons.ready を使うのが注意点です。





コンテンツを動的に書き換えた例



ボタンを押した時のイベント



ボタンを押した時のイベントは通常のjQueryとして記述ができます。





$("ons-button#next_page").on('click', function(e) {
  // ここに処理を書きます
});


ナビゲーション操作



ページを移動するスタック管理やナビゲーション機能を提供するコンポーネントに ons-navigator があります。こちらを使う場合には予め次のように var要素を書いておきます。





<ons-navigator title="Navigator" var="myNavigator">
  :
</ons-navigator>


こうすることで window.myNavigator にこのナビゲーションコンポーネントが定義されますので myNavigator としてアクセスが可能になります。



ボタンを押した時に画面遷移する



前述のクリックイベントとナビゲーションコンポーネントを組み合わせるとページ移動ができるようになります。





$("ons-button#next_page").on('click', function(e) {
  myNavigator.pushPage('page2.html');
});


pushPageはページ移動になります。page2.htmlは元々のHTMLファイルと同じ階層にあるpage2.htmlを読み込みます。例えば次のような記述です。





<ons-page>
  <ons-toolbar>
    <div class="center">Onsen + jQuery デモ</div>
    <div class="left">
      <ons-back-button onclick="myNavigator.popPage();">Back</ons-back-button>
    </div>
  </ons-toolbar>
  <div id="my-content">
  </div>
</ons-page>


こちらのようにonでイベントハンドリングするだけでなく、onclickで定義も可能です。popPageはページスタックから最前面のページが削除され、1つ前のページに戻る処理です。page2.htmlで記述している内容はコンパイル済みで、HTMLエレメントがそのまま表示されます。



表示する際の動的書き換え



画面を移動する際にコンテンツを動的に書き換えたいというニーズは多いと思います。この場合はナビゲーターのイベントハンドリングで行います。





myNavigator.on('postpush', function(e) {
  $(e.enterPage.element).find("#my-content").html('<p><div class="center" style="text-align:center">コンテンツ書き換え</div></p>');
});


postpushはpushが完了した時に呼ばれるイベントになります。 $(e.enterPage.element) にページの内容が入っていますので、そこから書き換えたい要素を検索して変更できるようになっています。



全体の動きは次のようになります。





動作デモ






このようにOnsen UIはAngularJSからだけでなく、jQuery(その他のJavaScriptフレームワークも)から利用が可能です。基本的な使い方さえ覚えてしまえば、スマートフォンアプリはもちろん、スマートフォンWebサイトに求められるアニメーションやUI/UXを簡単に手に入れることができます。これまでのナレッジを使って開発ができるようになるでしょう。



ぜひOnsen UIを使ってスマートフォンWebサイト/アプリを開発してください。



HTML5モバイルアプリをもっと速く、もっと美しく | Onsen UI

1.2リリース!新しく追加されたOnsen UIコンポーネントを紹介します

Onsen UIのバージョン1.2がリリースされました。いくつかの新機能がありますが、今回は特にUI周りで追加されたコンポーネントについて、その実装方法を含めて紹介します。



必要なもの



node.jsおよびnpmが必要です。



Onsen UI 1.2のダウンロード



まずはOnsen UI 1.2系(執筆時点で最新版は1.2.1)をダウンロードします。





Onsen UIの公式サイト



解凍したら、npmを実行します。





$ npm install


これで必要なライブラリがすべてインストールされます。そして、開発用のWebサーバを立ち上げます。





$ gulp serve



サーバを立ち上げると、 http://localhost:8901/index.html をブラウザが開くと思います。これはLiveReloadが入っていますので、HTMLやJavaScriptを編集すればその場で自動リロードが実行されます。





Google Chromeでの表示



アラートダイアログ ons-alert-dialog



アラートダイアログはJavaScript標準で用意されているalert/confirm/promptを置き換えるものです。よりネイティブアプリらしい、フラットなデザインのアラートが表示できます。



www/index.htmlを次のように編集します。





<ons-col width="60px"> 
  <div class="item-thum"></div>
</ons-col>
<ons-col ng-click="showAlert(item)"> <!-- ここに追加 -->
  <header>
    <span class="item-title">{{item.title}}</span>
    <span class="item-label">{{item.label}}</span>
    :



そして www/js/app.jsのDetailControllerを次のように修正します。





module.controller('DetailController', function($scope, $data) {
  $scope.item = $data.selectedItem;
  $scope.showAlert = function(item) {
    ons.notification.confirm({
    message: item.title,
      callback: function (index) {
        switch (index) {
        case 1:
          ons.notification.alert({message: "OKボタンが押されました"});
          break;
        case 0:
          ons.notification.prompt({message: "Cancelを押した理由を教えてください",
            callback: function (text) {
              ons.notification.alert({
                message: text
              });
            }
          });
          break;
        }
      }
    });
  };
});


こうすることで詳細ページにおいて、タイトル部分をタップすると確認ダイアログが出ます。





確認ダイアログ



OKをタップすると、OKボタンが押されたというアラートが出ます。





OKボタンを押した時のアラート



Cancelをタップすると理由を入力するプロンプトが表示された後、入力内容を表示するアラートが表示されるといった具合です。





Cancelをタップした時のプロンプト



メソッド含む詳細はアラートダイアログ ons-alert-dialogでご確認ください。



ダイアログ ons-dialog



アラートダイアログよりもさらにカスタマイズしたUIを実現できるのがons-dialogです。モーダルウィンドウのように使うことができます。



www/index.htmlの中にons-dialogのテンプレートを用意します。下記内容をの上に追加してください。





<script type="text/ons-template" id="alert.html">
  <ons-alert-dialog animation="default" cancelable>
    <div class="alert-dialog-title">Warning!</div>
    <div class="alert-dialog-content">
     An error has occurred!
    </div>
    <div class="alert-dialog-footer">
      <button class="alert-dialog-button">OK</button>
    </div>
  </ons-alert-dialog>
</script>


さらに詳細画面のサムネイル画像部分に対してイベントを設定します。





$scope.showDialog = function (item) {
  ons.createAlertDialog('alert.html').then(function(alertDialog) {
    alertDialog.show();
  });
};


こうすることで、詳細画面のサムネイルをタップするとカスタマイズされたダイアログが表示されます。





ダイアログ表示



メソッド含む詳細はダイアログ ons-dialogでご確認ください。



ポップオーバー ons-popover



ポップオーバーはタップした時にその機能の説明を表示するのに便利です。こちらもまた、www/index.htmlにテンプレートを追加します(の上あたり)。





<script type="text/ons-template" id="popover.html">
  <ons-popover cancelable>
    <p style="text-align: center; opacity: 0.5;">This popover will choose which side it's displayed on automatically.</p>
  </ons-popover>
</script>


さらにツールバー部分を修正します。





<div class="center" id="popover" ng-click="showPopover()">Master Details</div> <!-- ng-click と id を追加します -->


www/js/app.jsのAppControllerの修正をします。





module.controller('AppController', function($scope, $data) {
    :
  $scope.showPopover = function() {
    ons.createPopover('popover.html').then(function(popover) {
      popover.show('#popover');
    });
  };
});


これで準備は完了です。Master Detailsというラベルをタップすると、ポップオーバーが表示されるようになりました。





ポップオーバーの例



メソッド含む詳細はポップオーバー ons-popoverでご確認ください。





最後はカルーセルです。コンテンツをスワイプ操作で左右に切り替えて表示できます。こちらはデザインのみで試せます。 www/index.htmlに以下を追加します。





<ons-page>
  :
  </ons-list>
  <!-- 追加ここから -->
  <ons-carousel swipeable overscrollable auto-scroll var="carousel" style="height: 200px;">
	<ons-carousel-item style="background-color: gray;">
      <div class="item-label">GRAY</div>
    </ons-carousel-item>
    <ons-carousel-item style="background-color: #085078;">
      <div class="item-label">BLUE</div>
    </ons-carousel-item>
    <ons-carousel-item style="background-color: #373B44;">
      <div class="item-label">DARK</div>
    </ons-carousel-item>
    <ons-carousel-item style="background-color: #D38312;">
      <div class="item-label">ORANGE</div>
    </ons-carousel-item>
    <ons-carousel-cover><div class="cover-label">Swipe left or right</div></ons-carousel-cover>
  </ons-carousel>
  <!-- 追加ここまで -->
</ons-page>


このように追加すると、リストの下にカルーセルが表示されます。スワイプ操作でコンテンツが切り替わりますので、写真を見せたり、チュートリアルに使ったりできます。 next()prev()で前後のカルーセルに移動したり、first()last()で最初(または最後)のカルーセルを表示させることもできます。





ルーセル表示の例



メソッド含む詳細はカルーセル ons-carouselでご確認ください。






今回のコードは GitHub上にアップロード してあります。Onsen UIの公式サイトから1.2をダウンロードした後、www以下の内容を差し替えてもらえれば(または www/index.html と js/app.js)確認できるようになっています。



今後もOnsen UIには新しい機能が追加されていきます。ぜひご利用ください!



Onsen UI - A Custom Elements-Based HTML5 UI Framework | Onsen UI