まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2014/11

HTML5 の File API を使った、OS からウェブ画面へのファイルドラッグ&ドロップを実現する方法を紹介します。

まず、何も準備しないでウェブ画面へファイルをドラッグ&ドロップすると、デフォルト挙動によって、そのファイルがブラウザで開かれます。例えば画像ファイルをウェブ画面にドラッグ&ドロップすると、その画像ファイルがブラウザ内で開かれて表示されます。

ドラッグ&ドロップを実現するには、まずブラウザのこのデフォルト挙動を上書きして、別の挙動にするよう指定する必要があります。そのためにファイルドロップのエリア(<div>)への drop イベントに対して、preventDefault(); を指定しておく必要があります。これでファイルをドロップしてもブラウザでは開かなくなります。

また、ドロップイベントのパラメータである event の event.dataTransfer.files プロパティにはドラッグ&ドロップしたファイルの情報が配列で入ってきます。このオブジェクトの内容が画像であるかどうかを調べた上で、画像と判別できたら Dynamic JavaScript を使って <img> タグを追加し、innerHTML でその画像を表示する、という挙動を実装しています。こうすることで動的に(リロードなしに)そのドロップされたファイルをブラウザ内に表示することができるようになります。

この挙動を実装したのが以下です。ピンクの枠(<div id="droparea">)がドラッグ&ドロップのドロップイベントを制御するエリアになっていて、この中に画像ファイルを(ドラッグ&)ドロップすると、その画像をリロードなしにその直下の <ul id="files"></ul> の中で表示します:


ここにファイルをドロップ

    この HTML および JavaScript は以下になります:
    <script type="text/javascript">
    window.onload=function(){
     var URL_BLANK_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
     var elDrop = document.getElementById('droparea');
     var elFiles = document.getElementById('files');
    
     elDrop.addEventListener('dragover', function(event) {
      event.preventDefault();
      event.dataTransfer.dropEffect = 'copy';
      showDropping();
     });
    
     elDrop.addEventListener('dragleave', function(event) {
      hideDropping();
     });
    
     elDrop.addEventListener('drop', function(event) {
      event.preventDefault();
      hideDropping();
    
      var files = event.dataTransfer.files;
      showFiles(files);
     });
    
     document.addEventListener('click', function(event) {
      var elTarget = event.target;
      if (elTarget.tagName === 'IMG') {
       var src = elTarget.src;
       var w = window.open('about:blank');
       var d = w.document;
    
       d.open();
       d.write('<img src="' + src + '" />');
       d.close();
      }
     });
    
     function showDropping() {
      elDrop.classList.add('dropover');
     }
    
     function hideDropping() {
      elDrop.classList.remove('dropover');
     }
    
     function showFiles(files) {
      elFiles.innerHTML = '';
    
      for (var i=0, l=files.length; i<l; i++) {
       var file = files[i];
       var elFile = buildElFile(file);
       elFiles.appendChild(elFile);
      }
     }
    
     function buildElFile(file) {
      var elFile = document.createElement('li');
    
      if (file.type.indexOf('image/') === 0) {
       var elImage = document.createElement('img');
       elImage.src = URL_BLANK_IMAGE;
       elFile.appendChild(elImage);
    
       attachImage(file, elImage);
      }
    
      return elFile;
     }
    
     function attachImage(file, elImage) {
      var reader = new FileReader();
      reader.onload = function(event) {
       var src = event.target.result;
       elImage.src = src;
       elImage.setAttribute('title', file.name);
      };
      reader.readAsDataURL(file);
     }
    }
    </script>
    
    <div  effectallowed="move" id="droparea">ここにファイルをドロップ</div>
    <ul id="files"></ul>
    
    

    参考にしたのはこちら:
    http://jsfiddle.net/ginpei/bSF9z/
     

    例えば HTML (の一部)など、複数行に渡るようなテキストファイルの中身をまとめて1つの文字列変数内に格納したい場合にヒアドキュメントと呼ばれる構文を使うのが便利です。例として PHP であればこんな感じで定義できます:
    <?php
    $head = <<< EOT
    <head>
    <title>テスト</title>
    <script>
      :
    </script>
    </head>
    EOT;
    ?>
    

    この例の場合は <head> から </head> までの6行部分がまとめて文字列変数として $head に代入できます。

    このヒアドキュメント、残念ながら JavaScript ではサポートされていません。が、ヒアドキュメントもどきのようなことはできます。具体的には jQuery を使ってこんな感じ:
    var head = (function(){/*
    <head>
    <title>テスト</title>
    <script>
      :
    </script>
    </head>
    */}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
    

    この方法で同じ内容が文字列変数として head に代入できます。JavaScript 上でコメント扱いとなってる箇所(/* ~ */)の "/*" と "*/" で挟まれた部分を match 関数で取り出して代入する、というアイデア賞ものの方法です。

    テンプレート的な HTML を JavaScript 内に直書きして扱いたい場合に便利です。



    (参考文献)
    http://qiita.com/_shimizu/items/837b529de9f3302e315c



     

    少し前の話になりますが、IBM Bluemix のハッカソンに参加してきました。

    限られた時間の中で4~5人のチームをビルドして、アイデアを出し、実現可能性を調べた上で実装する、というハッカソン。今回は PaaS である IBM Bluemix を使うという前提で「旅をハックする」という制作テーマが与えられていました。

    Bluemix は PaaS とはいえ、CloudFoundry をベースとしていることからかなりの自由度があり、また Bluemix から提供されている各種サービスを使うことでシステム構築の手間を大幅に減らすことができます。加えて DevOps によるチーム開発環境も提供されています。 実は私自身、このようなチーム制のハッカソンに参加したのは初体験でしたが、DevOps 環境にもすぐに馴染めたこともあり、Bluemix は非常に相性のいい環境であると感じました。

    各チームに与えられた時間は8時間プラスアルファ。チーム内で役割分担して開発するとはいえ、必ずしも充分な時間とはいえません。そのため出されたアイデアのどの部分を実装するのか(どの部分は諦めるのか)、という判断も必要になります。


    我々のチームが開発したアプリケーションがこちらです(いちおう動きます):
    http://tabihack2014teame.mybluemix.net/

    独立した環境下で Twetter Streams API を使い、特定エリア(日本)の位置情報をリアルタイムに集めてDBに格納します。その情報を使い、地図上に表示されたエリア内で最近ツイートされたのはどんな情報か、を可視化するウェブアプリケーションです。

    上記 URL にアクセスすると、まず位置情報 API によって、現在地が特定され、その周辺の地図が表示されます。同時に地図内の青みがかったエリア内で最近ツイートされた内容が画面右部に表示されます:
    2014111702


    また画面を下にスクロールすると、そのエリア内の宿泊施設や、周辺のマンホール(笑)の情報が表示される、というものです:
    2014111703


    地図をドラッグして青部分が移動すると、ツイート/宿泊施設情報/マンホール情報も、そのエリア内や周辺の情報に切り替わります。
    2014111704


    加えて、画面上部のファイル指定ボタンで位置情報が埋め込まれた画像を指定すると、その画像に含まれた位置まで地図が移動し、そのエリア周辺のツイートや宿泊施設の情報が表示されます。


    今回のハッカソンで作れたのはここまでです。ひたすらに位置情報 API を組み合わせて感じ(苦笑)。本当はツイートを検索してもっと絞り込んだり、位置情報に付随した別の情報を表示したり、UIをより見やすくしたり、といったこともやりたかったのですが、優先度の判断で落としました。チームビルドやアイデア出しを含めて8時間で作ったにしてはまあまあの出来だと思っていますし、実行/開発環境やサービスをその場で手配して用意できる Bluemix 環境のすごさを目の当たりにした結果でもあると感じています。


    このハッカソンで Twitter Streams API を使っての印象として、やはりこれだけの情報を集めることができるようになったことは、非常に面白いと感じました。地図上で可視化する、というアイデア1つにしてももっと別のアプローチもあるでしょうし、またほとんどが位置とは関係のない情報ばかりである一方で、たまに隠れている「その地域の役立つ情報」を如何に探し出して表示するのか、という新しい課題も浮き彫りになりました。 限られた時間の中での作業だからこそ、見えてくるものもあります。

    ハッカソンは終わってしまいましたが、このアプリを今後少し改良してみるつもりです。ハッカソンにはこういう「終わった後の楽しみ」もあっていいですね。








     

    以前にこの記事を書きました:
    CentOS に Apache Tomcat を導入する


    上記ページの下の方で、Apache Tomcat を(一般的な HTTP のポートである)80 番ポートで実行するための指定方法も書き加えています。簡単に言うと、Apache Tomcat 自体は 8080 番ポートで動かしながら 80 番ポートへのリクエストを 8080 番に転送することで、外部からは 80 番ポートで動いているように見せる、というものでした。

    もちろんこの方法は今でも有効で、比較的簡単に Apache Tomcat を 80 番ポートで動かすことができるようになります。ただ1つ、「事実上 80 番ポートを専有してしまう」という難点もあります。つまり実際には 8080 番ポートでしか動いていないのに、80 番ポートも含めて専有してしまうということです。

    この結果、どんな不都合があるでしょう? 例えば Apache Tomcat が 8080 番ポートのままであれば、同じシステムに Apache HTTPD を導入して 80 番ポートで平行して動かすことができます。例えば PHP と MySQL と WordPress まで導入した上で、80 番ポートでは Apache HTTPD で WordPress を動かし、8080 番ポートでは Apache Tomcat で Java アプリケーションサーバーを同時に動かす、ということが可能でした。 上記の 80 番ポートを 8080 番ポートに転送するという方法を取ってしまうと、80 番ポートで Apache HTTPD を動かすことができなくなってしまいます。つまり2つの HTTP サーバーを同時に動かすことはできなくなってしまいます。

    とはいえ、Apache Tomcat が 8080 番ポートのままですと、Java アプリケーションサーバーにアクセスさせるには
      http://xxx.xxx.xxx:8080/hello
    という、一般的とは言えないちょっと冗長な URL をユーザーに指定させる必要が出てきます。普通の HTTP(80番)リクエストが許可されていない環境は珍しいかもしれませんが、8080 番ポートとなると会社レベルのファイアウォールが許可していない可能性も出てきます。


    これを解決する方法もあります。例えば WordPress が
     http://xxx.xxx.xxx/wordpress
    という URL で動いていて、同時に Java アプリケーションが
     http://xxx.xxx.xxx:8080/hello
    という URL で動いている場合であれば、以下の様なルールを作ります:

    ・原則的には、全てのリクエストを Apache HTTPD (80番ポート)で処理する
    ・但し /hello という URL パターンだった場合のみ、Apache Tomcat に渡して処理してもらう

    これにより、http://xxx.xxx.xxx/hello というリクエストがあった場合には内部的に http://xxx.xxx.xxx:8080/hello に転送※して Apache Tomcat 側で処理をする、という作業分担が可能になります。 また同時に全てのリクエストをパフォーマンスのいい Apache HTTPD がいったん処理した上で分担処理することになるとか、サーバーのファイアウォールは 80 番ポートのみ開けておけばよくなる(Apache Tomcat の 8080 番ポートと停止することもできる)、という副次的なメリットも生まれます。

    ※実際には 8080 番ではなく、内部的な処理をするために 8009 番ポートを使います


    この作業分担を実現するのが Apache Tomcat に付属している AJP(Apache Java Protocol) です。AJP は 8009 番ポートを使います。

    具体的な作業の前に Apache Tomcat の設定を元に戻しておきます。つまり上記リンク先のような 80 番ポートへのリクエストを 8080 番ポートに転送するような指定がされている場合は無効にして、普通に Apache Tomcat が 8080 番ポートで動いているような状態にします。

    続いて /etc/httpd/conf.d/proxy_ajp.conf というファイルを、以下の内容で作成します:
    ProxyPass /hello/ ajp://localhost:8009/hello/
    

    これで Apache HTTPD を再起動(# /etc/init.d/httpd restart)します。すると 80 番ポートへのリクエストは Apache HTTPD が受けるのですが、この AJP の設定により /hello/ というパスへのリクエストに関しては 8009 番ポートへの /hello/ に変換されます。そして内部的な 8009 番ポートで待ち構えている Apache Tomcat の AJP によって処理されるようになります。

    結果的に、ユーザーからは WordPress へは
      http://xxx.xxx.xxx/wordpress/
    Java アプリケーションへは
      http://xxx.xxx.xxx/hello/
    それぞれの URL で利用できるようになる、ということです。

    なお、この記述をすることで Apache Tomcat を 8080 番ポートで動かす必要はなくなります。消してしまうのであれば Apache Tomcat の server.xml から該当箇所を削除してください。


    注意点としては、Java アプリケーションへの転送を proxy_ajp.conf で記述することになるため、複数の Java アプリケーションがあって、その全てを転送したいのであれば、全てを proxy_ajp.conf に(複数行で)記述する必要が出てきます。

    もう1点、実は私自身はこの AJP をあまり信用していないというか、AJP コンテナ自身がそれほどでもない負荷を捌ききれなかった経験をしています。原因調査についてはこちらの記事を参考にしていますが、AJP を使う前提での根本解決はできていないので、ちとヤな感じではあります:
    http://m97087yh.seesaa.net/article/212767022.html









     

    ある意味でよくある話ですが、「データベースサーバーの情報をエクセルで見たい」という要望をいただきます。

    この「データベース」が何なのかにもよりますが、原則的にはそのデータベースサーバーにあった ODBC ドライバをそのパソコンに入れて設定した上で、エクセルから外部データ呼び出しを行うことになります。

    仮に「データベース=MySQL」という前提で、この手順を紹介します。

    まずはデータベースサーバーに合った ODBC ドライバが必要です。MySQL であればこちらからダウンロードします:
    MySQL :: Download Connector/ODBC

    Zip 版と Msi 版があります。どちらでもいいのですが Msi 版であればダウンロードしたものをダブルクリックすればインストーラーが起動して簡単なのでこちらがおススメです。

    また 32bit 版と 64bit 版があります。使っている Windows にあった方を選んでダウンロードするのですが、よく分からない場合はとりあえず 64bit 版を選んでみて、インストール時に「環境があってない」といった旨のメッセージが出る場合は改めて 32bit 版を選ぶ、という方法もあります。

    インストーラが起動したら基本的には全て Next や「はい」を選んで標準インストールします。

    これで ODBC ドライバのインストールは完了です。MySQL 以外のデータベースを使っている場合は若干異なる所もありますが、正しい ODBC ドライバを入手してインストール、という点では全て共通です。


    次に導入した ODBC ドライバを使ってデータソースを定義します。簡単に言うと、目的のデータベースサーバー上のデータに接続するための設定を行います。

    コントロールパネルを起動して、 管理ツール > データソース(ODBC) を選びます:
    2014111101


    「ODBC データソース」管理のダイアログが開きます。「ユーザーDSN」タブの「追加」ボタンをクリックします:
    2014111102


    新規に追加するデータソースで使うドライバ(接続先の種類)を選びます。先程インストールした ODBC ドライバから選択します。MySQL の場合は "ANSI Driver" と "Unicode Driver" の2つが導入されているはずですが、ここでは "ANSI Driver" を選択(データに UTF-8 の日本語が含まれている場合はこちら)して「完了」ボタン:
    2014111103


    MySQL の接続先に関する情報を入力するダイアログが表示されます。Data Source Name には接続先を識別するための名前を入力します。Description はその説明ですが空欄でも構いません。MySQL サーバーがリモートの場合は TCP/IP Server: でその IP アドレスやホスト名を指定します。ポート番号は特に変更していない限りは 3306 のままで大丈夫です。そしてデータベースに接続するためのユーザー名とパスワードを入力。ここまでの指定内容が正しければ、その下の Database セレクションボックスの中に、この MySQL サーバーにこのユーザーからアクセスできるデータベースの一覧が入っているので、そこから実際に接続するデータベースを選びます:
    2014111104


    ここまで指定できたら「Test」ボタンをクリックします。全ての情報が正しければ "Connection Successful" というメッセージが表示され、データベースに正しく接続できたことが分かります。「OK」ボタンをクリックして1つ前の画面に戻ります:
    2014111105


    先程の画面に、いま作成したデータソースが追加されたことが確認できるようになったはずです。これでデータソースの追加もできました:
    2014111106


    ここまでの設定ガできていれば、後はエクセル上での作業になります。エクセルを起動し、データを表示したいワークシートを開いた状態で、メニューの データ > 外部データの取り込み > その他のデータソース取り込み > データ接続ウィザード を選択します(エクセルのバージョンによっては データ > 外部データの取り込み > データの取り込み > 新しいデータソースへの接続):
    2014111108


    データ接続ウィザードが表示されます。まずデータソースの種類として "ODBC DSN" を選択します:
    2014111109


    次に ODBC データソースを指定します。先ほど作成した "MySQL" (作成したデータソースの名前)を指定します:
    2014111110


    データソースの情報に基づいて情報を取得し、データベースとその中のテーブルの選択画面に移ります。エクセル内での表示に使いたいデータベースとテーブルを選択します:
    2014111111


    テーブル内のデータをキーワード検索で絞り込みたい場合などはここでキーワードの指定などを行うこともできます。ここまでの指定ができたら「完了」ボタンをクリックします:
    2014111112


    最後にどのような方法で指定データをインポートするかを指示します。普通にテーブルとしてインポートするのか、ピボットテーブルとしてインポートするのか、既存のワークシートの特定位置に入れるのか、新規にワークシートを作成するのか、といった指定を行います:
    2014111113


    すると指定内容に従った形で MySQL サーバーのデータがエクセルにインポートされます。インポート後はエクセルデータとして利用できるので、例えばグラフ化などもエクセルでの作業として簡単に行えます:
    2014111114


    なお、この設定を行ったエクセルシートを保存して再度開いた場合にセキュリティ警告が表示されることがあります。この警告が表示された場合、保存した際のシートの情報は残っていますが、データベースサーバーとの接続は切れています。保存後にデータベースに追加したり変更された情報はエクセル上では反映されていません:
    2014111112


    データベースサーバーの最新情報を取り込みたい場合はメニューの データ > すべて更新 を選択します。これで該当シート内のデータはデータベース内の最新情報に更新されます:
    2014111113


    このページのトップヘ