2012年6月26日火曜日

スマホアプリとWEBサーバの連動



スマホアプリ開発で、WEBのサーバと連動したい場合、HTML5でやりぬくパターンと、インターフェースをアプリ開発してサーバーと連動するケースと両方ある。サーバー連動方法はまあ何かしらあるはずだが、どちらかというと、HTML5でやりぬけた方が楽だ。でもメニューボタンを使いたくなるのは人間の性であり、これが機能しないアプリというのは残念である。例えばアプリをつくるけどもブラウザ表示機能を全面に使いつつ、メニューボタンは各URLに紐づけるということが出来るとお手軽である。ログイン機能なんかもHTML5様がやってくれるので新たにスマホの為に頭を使うことは非常に少ない。気になるのはWEBサイト用のapiだった場合に、勝手にきちんとモバイル画面用の表示になってくれるのかという点があるが、恐らくなってくれるに一票。すくなくともpaypalは自動認識して自動切替すると書いてあった。これなら簡単そうだし、今つくってみてるWEBページがそのまま使えるし、まあcssとかjavascriptとかで調整は必要だけど、アプリにもなる。これの簡易アプリを試しに作ってみよう。

追記
2012年6月27日現在、BaaSだとかモバイルバックエンドだとかいうサービスのことを初めて知りました。これはロリポップにお世話にならなくてもアマゾンにお世話にならなくても、サーバ側のデータベースから、ソーシャル連携から、ユーザ管理から、プッシュ通知から、何から何までパッケージ化して提供してくれる、クラウドのサービスプラットフォームでした。これはモバイルバックエンドということでモバイルを想定しているものの、iPhoneとAndroidでしか使えないわけではなく、HTML5でも使えるのでPC用でも使えるわけであります。果たして私が微妙に慣れ親しんだCakePHPやMySQLは過去の遺産になるのだろうか。tiggziでドラッグ&ドロップでインターフェースを作り、BaaSをAPI連携させればもう本格アプリができてしまうのでないか?BaaSがどの程度柔軟にサーバ側の処理をいじることができるのか試してみよう。ちなみにtiggziで用意されているMongoDBとかいうやつは確かNoSQLとかいうやつだった気がするが、まあはっきりいって詳細はわかっていない。簡単にいうと従来のSQLではないことは確かだ。これもtiggziでの使われ方はBaaSなんだろう。

よって、スマホアプリとサーバ連携に関しては、HTML5でやりぬくパターン(インターフェースをHTML5で作りCSSとJavascriptでスマホに最適化するだけ)、インターフェースをアプリ開発し、サーバと連動させるパターン(これのお手軽バージョンがBaasなんだろう)、インタフェースの大枠だけアプリ開発しつつ、中身はHTML5で作成したWEBページを埋め込むパターンの大きく3つあるのだ。

2012年6月24日日曜日

cakePHP - formヘルパーのメモ

cakePHP1.3使ってます。

formヘルパーは便利です。何でもAjaxでやりたいのであれば便利さは限定されるのかもしれませんが、それでもエラーがある場合のチェック機能などを使うとそれはそれで便利に使えるのかもしれません。今回はform登録をAjax機能を使わずに実施する想定で、Userテーブルに名前と生年月日と性別を登録する超シンプルな構成を想定します。formヘルパー自体今までほとんど無視していましたので、便利な機能を覚えるためにメモります。

テーブル - users

フィールド種別照合順序NULLデフォルト値その他
idint(11)いいえNoneauto_increment
namevarchar(30)utf8_unicode_ciいいえNone
birthdateはいNULL
gendervarchar(2)utf8_unicode_ciはいNULL

モデル - user.php

<?php 
class User extends appModel{
    var $name = 'User';
    
    public $validate = array(
        'name' => array(
            'rule' => 'notEmpty',
            'message' => 'nameは必ず入力してください'),
        'birth' => 'date',
        'gender' => array(
            'rule' => array('inList', array('男性','女性')),
            'message' => '「男性」か「女性」を入力してください。')
    );
}
?>

コントローラー - formtests_controller.php

<?php 
class FormtestsController extends AppController{
    public $name = 'Formtests';
    public $uses = array('User');
    public $layout = 'formtests';
    
    function index(){
        if(!empty($this->data)){
            if($this->User->save($this->data)){
                $this->redirect('./');
            }
        }
    }

}
?>

ビュー - index.cpt

<h1>Form Test</h1>

<?php echo $form->create('Formtest',array('action'=>'index')); ?>
<?php echo $form->input('User.name',array('label'=>'名前')); ?>
<?php echo $form->input('User.birth',array('label'=>'生年月日','dateFormat'=>'YMD','minYear'=>date('Y')-80,'maxYear'=>date('Y'),'separator'=>'/','monthNames'=>false)); ?>
<?php echo $form->input('User.gender',array('label'=>'性別','options'=>array('男性'=>'男性','女性'=>'女性'))); ?>
<?php echo $form->submit('add'); ?>
<?php echo $form->end(); ?>

css

body{
    background-color:#999;
    font-family:'Trebuchet MS', Trebuchet, sans-serif;
    font-size:12px;
    margin:10px;
}
h1{
    font-size:30px;
}
.error-message{
    color:#f00;
}
label{
    display:block;
    width:70px;
    float:left;
}

2012年6月17日日曜日

ストレス無しでGoogleマップを使ったサイトが作れる「gmaps.js」を使ってみる

ストレス無しでGoogleマップを使ったサイトが作れる「gmaps.js」で知った、gmaps.jsを使ってみます。
まずここで、ダウンロードします。ソースコードをコピペしてgmaps.jsを作成しました。ところで、google map APIはあまり使ったことがないので、とりあえずそもそもの使い方を確認しよう。

Google Maps APIのそもそもの使い方

GoogleマップのAPIは正式には今はGoogle Maps JavaScript API V3というらしい。これのチュートリアルを見ながらそもそもの使い方を大体覚えたい。チュートリアルにあるHTMLコードをコピペ(コピペした後で、titleを加えて、sensor=set_to_true_or_falseというところを、sensor=falseに変更して、スタイルは別ファイルで定義した。)したところ、地図が表示された。どうもid登録なんかは不要なようだ。その代わり使い方によっては有料になるようだ。
<!DOCTYPE html>
<html>
<head>
<title>gmaps test</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var myOptions = {
      zoom: 8,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(document.getElementById("map"),
        myOptions);
  }
</script>
</head>
<body onload="initialize()">
  <div id="map"></div>
</body>
</html>

このように地図が表示された。中心の経度・緯度情報と、ズームレベルと、地図のタイプを決めて初期化しているようだ。地図のタイプは、ROADMAP、SATELLITE、HYBRID、TERRAINなどがあるようだ。タイプは色々他にも設定ができるようだ。

住所を経度・緯度情報に変換することをジオコーディングというらしいが、そういうこともお手軽に出来るようだ。

これでGoogle Maps APIのイメージがつかめたので、いざgmaps.jsを使ってみよう。

gmaps.js で地図を表示する

gmaps.jsはjQueryを使っているのでjQueryを読み込まないといけない。当然最初につくったgmaps.jsも読み込まないといけない。gmaps.jsで上記と同じ地図を表示するには、下記のようになります。
<!DOCTYPE html>
<html>
<head>
<title>gmaps test</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="./js/gmaps.js"></script>
<script type="text/javascript">
    var map;
    $(document).ready(function(){
        map = new GMaps({
            div: '#map',
            zoom: 8,
            lat: -34.397,
            lng: 150.644
        });
    });
</script>
</head>
<body>
  <div id="map"></div>
</body>
</html>
zoomはデフォルトで15になっているようだ。15のままでよければzoomは記載しなくてよい。マップタイプはデフォルトでROADMAPになっているようだ。ROADMAP以外にしたい場合は、mapTypeId: google.maps.MapTypeId.HYBRIDなどと追記すればよい。

gmaps.jsでジオコーディングする

下記のようにするとジオコーディングが使える。確かにお手軽だ。下記のコードだとマーカーまでつけている。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>gmaps test</title>
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="./js/gmaps.js"></script>
<script type="text/javascript">
    $(document).ready(function(){
      map = new GMaps({
        div: '#map',
        lat: -34.397,
        lng: 150.644
      });
      $('#geocoding_form').submit(function(e){
        e.preventDefault();
        GMaps.geocode({
          address: $('#address').val().trim(),
          callback: function(results, status){
            if(status=='OK'){
              var latlng = results[0].geometry.location;
              map.setCenter(latlng.lat(), latlng.lng());
              map.addMarker({
                lat: latlng.lat(),
                lng: latlng.lng()
              });
            }
          }
        });
      });
    });
</script>

</head>
<body>
    <form method="post" id="geocoding_form">
        <label for="address">住所:</label>
        <div class="input">
            <input type="text" id="address" name="address" />
            <input type="submit" class="btn" value="Search" />
        </div>
    </form><br />
    <div id="map"></div>
</body>
</html>

gmaps.jsでジオコーディングする2

今度は、渋谷のヒカリエの場所を最初から出すようにしよう。ヒカリエの場所にはマーカーもつけよう。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>gmaps test</title>
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="./js/gmaps.js"></script>
<script type="text/javascript">
    var rikkyo = '東京都 豊島区西池袋3-34-1';
    $(document).ready(function(){
      map = new GMaps({
        div: '#map',
        lat: 1,
        lng: 1
      });
      GMaps.geocode({
          address: rikkyo,
          callback: function(results, status){
            if(status=='OK'){
              var latlng = results[0].geometry.location;
              map.setCenter(latlng.lat(), latlng.lng());
              map.addMarker({
                lat: latlng.lat(),
                lng: latlng.lng()
              });
            }
          }
      });
    });
</script>
</head>
<body>
    <div id="map"></div>
</body>
</html>

ヒカリエがうまく表示されないので立教大学を表示してみた。ページを読み込むと、立教大学の住所を経度・緯度情報に変換し、立教大学の経度・緯度情報を地図の中央にセットしている様です。

gmaps.jsでジオコーディングとオーバーレイする

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>gmaps test</title>
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="./js/gmaps.js"></script>
<script type="text/javascript">
    var rikkyo = '東京都 豊島区西池袋3-34-1';
    $(document).ready(function(){
      GMaps.geocode({
          address: rikkyo,
          callback: function(results, status){
            if(status=='OK'){
              var latlng = results[0].geometry.location;
              var map = new GMaps({
                  div: '#map',
                  lat: latlng.lat(),
                  lng: latlng.lng()
                });
              map.drawOverlay({
                  lat: map.getCenter().lat(),
                  lng: map.getCenter().lng(),
                  content: '<div class="overlay">Rikkyo<div class="overlay_arrow above"></div></div>',
                  verticalAlign: 'top',
                  horizontalAlign: 'center'
              });
            }
          }
      });
    });
</script>
</head>
<body>
        <div id="map"></div>
</body>
</html>
スタイルシートは下記です。
#map{
  width:300px;
  height:300px;
}
.overlay{
  display:block;
  text-align:center;
  color:#fff;
  font-size:15px;
  line-height:17px;
  opacity:0.8;
  background:#905;
  border:solid 3px #905;
  border-radius:4px;
  box-shadow:2px 2px 10px #333;
  text-shadow:1px 1px 1px #666;
  padding:0 4px;
}

.overlay_arrow{
  left:50%;
  margin-left:-8px;
  width:0;
  height:0;
  position:absolute;
}
.overlay_arrow.above{
  bottom:-7px;
  border-left:8px solid transparent;
  border-right:8px solid transparent;
  border-top:8px solid #905;
}
.overlay_arrow.below{
  top:-7px;
  border-left:8px solid transparent;
  border-right:8px solid transparent;
  border-bottom:8px solid #905;
}

これなら企業のホームページのアクセスマッップなんかをgoogle maps apiで作るのも超お手軽ですね。

gmaps.jsでポリゴンとインフォウィンドウする

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>gmaps test</title>
<link rel="stylesheet" type="text/css" href="./css/cake.gmaps.css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="./js/gmaps.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    var map;
    var ll = path = g = [];
    ll[0] = [35.7305222 , 139.7039449];
    ll[1] = [35.7127036 , 139.76327730000003];
    ll[2] = [35.70935559999999 , 139.71944410000003];
    path = [ll[0],ll[1],ll[2]];
    g = [(ll[0][0]+ll[1][0]+ll[2][0])/3,(ll[0][1]+ll[1][1]+ll[2][1])/3];
    map = new GMaps({
        div: '#map',
        zoom: 13,
        lat: g[0],
        lng: g[1],
     });
    polygon = map.drawPolygon({
        paths: path,
        strokeColor: '#f09',
        strokeOpacity: 0.3,
        strokeWeight: 0.3,
        fillColor: '#f09',
        fillOpacity: 0.3
    });
    for(var i=0;i<ll.length;i++){
        map.addMarker({
            lat: ll[i][0],
            lng: ll[i][1],
            icon: './img/sugi.png',
            infoWindow: {
              content: sugi(i)
            }
        });
    }
});
function sugi(idx){
    if(idx == 0){
        return '<p>立教大学</p><p>白いワイシャル着てナポリタン食べてやったぜ〜</p>';
    }else if(idx == 1){
        return '<p>東京大学</p><p>白いワイシャル着てミートソーススパゲッティー食べてやったぜ〜</p>';
    }else{
        return '<p>早稲田大学</p><p>白いワイシャル着てトマトスパゲッティー食べてやったぜ〜</p>';
    }
}
</script>
</head>
<body>
    <div id="map"></div>
</body>
</html>

東京大学、早稲田大学、立教大学の場所をすぎちゃんが指し示し、この3地点を赤色の三角形で囲った様。

すぎちゃんをクリックすると、ねたをつぶやく様。

cakePHP1.3 - 画像のアップロードの研究

cakePHP1.3で画像をアップロードできるようにします。 参考にするサイトはツチノコラボです。

研究用にUptestsコントローラーをつくりました。
Uptestsコントローラーのindexアクションにアップロード処理をつくります。ちなみに、今回はデータベースにファイルを保存するのではなく、フォルダに格納します。最終的には、ファイル名をデータベースで管理できるようにしたいと思います。

Viewの設定

まずViewにformをつくります。index.ctpを下記のように作成しました。
<h1>Update Test</h1>

<?php
    $options = array(
        'action'=>'index',
        'type'=>'file'
    );
    echo $form->create('Uptest', $options);
?>
<?php echo $form->file('file_name') ?>
<?php echo $form->submit('送信'); ?>
<?php echo $form->end(); ?>
画面イメージは下記のようになります。

ちなみに、作成されるHTMLソースは下記のようになります。
<h1>Update Test</h1>

<form action="/cake/uptests" id="UptestIndexForm" enctype="multipart/form-data" method="post" accept-charset="utf-8">
<div style="display:none;"><input type="hidden" name="_method" value="POST" /></div>
<input type="file" name="data[Uptest][file_name]" id="UptestFileName" /><br />
<div class="submit"><input type="submit" value="送信" /></div>
</form>

Controllerの設定

次にUptestsコントローラーをつくります。下記コードでファイルのアップロードは出来るようになります。
<?php 
Configure::write('upload.path', 'img/upload');

class UptestsController extends AppController{
    public $name = 'Uptests';
    public $uses = array();
    public $layout = 'uptests';
    
    //画像アップロード
    public function index(){
        //データチェック
        if (empty($this->data)) return;
        if(!(is_uploaded_file($this->data['Uptest']['file_name']['tmp_name']))) return;

        //アップロードするファイルの場所
        $uploaddir = Configure::read('upload.path');
        $uploadfile = $uploaddir.DS.basename($this->data['Uptest']['file_name']['name']);
        
        // 同じ名前のファイルがすでに存在すれば、別名に変える
        $info = pathinfo($uploadfile);
        $i = 1;
        while(file_exists($uploadfile)){
            $i++;
            $file_name = basename($info['basename'],'.'.$info['extension']).
                '_'.$i.'.'.$info['extension'];
            $uploadfile = $info['dirname'].DS.$file_name;
            $this->data['Uptest']['file_name']['name'] = $file_name;
        }
        
        //画像をテンポラリーの場所から、正式な置き場所へ移動
        if (move_uploaded_file($this->data['Uptest']['file_name']['tmp_name'], $uploadfile)){
            chmod($uploadfile, 0666);
        }else {
            $this->Session->setFlash("ファイルのアップロードに失敗しました。");
        }
    }
}
?>
一番上の行の、Configure::write('upload.path', 'img/upload');というのは、upload.pathにimg/uploadと設定している様です。Configureメソッドは、ここに説明が書いてあります。
upload.pathは、ファイルをアップロードするパスのことで、app/webrootフォルダを基準にしたパスになります。よって、app/webroot/img/uploadにファイルをアップロードすると設定していることになります。ちなみに、uploadフォルダは、このコードですと事前に作成しておく必要がありますし、フォルダの権限設定も書き込み可能にしておく必要があります。

これで、フォームでファイルを選択して送信ボタンを押すと、ファイル名の重複を回避しながら、uploadフォルダにファイルが保存できるようになりました。

アップデートファイルのチェック

次に、ファイル名、ファイルのサイズ、拡張子をチェックして、条件に合うファイルのみアップロードするようにします。

ファイル名は半角英数字(英語は小文字のみ)とアンダーバーで30文字以内をルールにします。
if(!preg_match('/^[a-z0-9_]{1,30}$/',$this->data['Uptest']['file_name']['name'])) return;

ファイルのサイズは、$this->data['Uptest']['file_name']['size']に入っていますので、これをチェックするロジックを作ればOKです。
if($this->data['Uptest']['file_name']['size']>500000) return;

拡張子は、$this->data['Uptest']['file_name']['type']に入っていますので、これをチェックするロジックを作ればOKです。拡張子は、jpg,png,gifのみOKにします。
if($this->data['Uptest']['file_name']['type'] != 'image/jpeg' &&
    $this->data['Uptest']['file_name']['type'] != 'image/png' &&
    $this->data['Uptest']['file_name']['type'] != 'image/gif') return;

まとめ

あとは、データベースでファイル名を管理すればいいのですが、テストの為にユーザテーブル作るのめんどくさいのでやめましょう。ユーザテーブルがあったとして、イメージカラムつくって格納すればいいだけだし、例えばイメージを3つまで登録できるようにするのであれば、カラム3つ作ればよいです。アップデート前に3つすでに登録されているか確認して登録済みであればエラーを出すか、どれかと入れ替えるか確認する画面を表示したりすればいいかと存じます。

最後にまとめのコードを記載して終了します。

↓ビュー
<h1>Update Test</h1>

<?php
    $options = array(
        'action'=>'index',
        'type'=>'file'
    );
    echo $form->create('Uptest', $options);
?>
<?php echo $form->file('file_name') ?>
<?php echo $form->submit('送信'); ?>
<?php echo $form->end(); ?>

<?php if(!empty($err)) echo $err;?>
↓コントローラー
<?php 
Configure::write('upload.path', 'img/upload');

class UptestsController extends AppController{
    public $name = 'Uptests';
    public $uses = array();
    public $layout = 'uptests';
    
    //画像アップロード
    public function index(){
        //データチェック
        if (empty($this->data)) return;
        if(!(is_uploaded_file($this->data['Uptest']['file_name']['tmp_name']))) return;
        
        //ファイル名チェック
        if(!preg_match('/^[a-z0-9_]{1,30}$/',$this->data['Uptest']['file_name']['name'])){
            $this->set('err','ファイル名は半角小文字英数字とアンダーバーで30文字以内にしてください。');
            return;
        }
            
        //ファイルサイズチェック
        if($this->data['Uptest']['file_name']['size']>500000){
            $this->set('err','ファイルサイズは500KB未満にしてください。');
            return;
        }
        
        //拡張子チェック
        if($this->data['Uptest']['file_name']['type'] != 'image/jpeg' &&
           $this->data['Uptest']['file_name']['type'] != 'image/png'  &&
           $this->data['Uptest']['file_name']['type'] != 'image/gif'){
                $this->set('err','ファイルはJPG,PNG,GIFのいずれかにしてください。');
                return;
        }
        
        //アップロードするファイルの場所
        $uploaddir = Configure::read('upload.path');
        $uploadfile = $uploaddir.DS.basename($this->data['Uptest']['file_name']['name']);
        
        // 同じ名前のファイルがすでに存在すれば、別名に変える
        $info = pathinfo($uploadfile);
        $i = 1;
        while(file_exists($uploadfile)){
            $i++;
            $file_name = basename($info['basename'],'.'.$info['extension']).
                '_'.$i.'.'.$info['extension'];
            $uploadfile = $info['dirname'].DS.$file_name;
            $this->data['Uptest']['file_name']['name'] = $file_name;
        }
        
        //画像をテンポラリーの場所から、正式な置き場所へ移動
        if (move_uploaded_file($this->data['Uptest']['file_name']['tmp_name'], $uploadfile)){
            chmod($uploadfile, 0666);
        }else {
            $this->set('err','ファイルのアップロードに失敗しました。');
        }
    }
    
}
?>

2012年6月2日土曜日

JavaScriptでアーランCによりコールセンターの必要席数を計算する

アーランC式は下記になります。


アーランC式はこのページのように、別の計算方法もありまして、別の方法だと待ち時間をインプットするようですが、上記の式は待ち時間のインプット不要バージョンです。アーランC式がなぜこのような式になるのかは全然分かりませんが、コールセンターの必要席数試算でアーランC式がよく使われています。

アーランC式をJavaScriptで計算できるようにしたのが下記になります。大体アーランが700を超えるとJavaScriptの数字の取り扱いキャパシティーの関係上エラーになりますので、アーラン700を上限にしました。アーランはコール数×平均処理時間÷3600で出します。

1時間当たり着信数
平均処理時間
応答率%