ボクココ

個人開発に関するテックブログ

JavaScript SPA 周りの議論で出た私なりの答え

ども、@kimihom です。

ここ最近、ずっと React.js か jQuery かみたいな話題が定期的に持ち上がってきている。事実、私も最近の JS ライブラリの人気に疑問を呈した人物の一人である。この記事を書いたのも1年前か。

なんだかんだで SPA から jQuery に戻った話

そして、現在も jQuery(厳密に言えば jquery-rails) でアプリケーションを書いている。そしてその決断を特に後悔したことはない。本記事では、よく聞くReact とかに移りたい理由をもとに、私なりの回答を記すことにする。

jQuery の DOM 地獄から解放されたい

「jQuery で DOM 操作なんてもうしたくないんです」これこそ React や Angular を使う最大の理由であることが多いかと思う。確かに jQuery の DOM 機能は頑張ればいろいろなことができるので、無法地帯になりやすいという性質を持っている。

だが私が思うのは、 jQuery で複雑な DOM 操作なんてする必要がないということである。そもそもの発想として、JavaScript で状態管理をするという考え方自体に問題があるのではないか。状態を保っている根幹はサーバーにあるDBであり、それを無理にフロント側で持とうとするから大変なことになる。Web の特徴として、"常にオンラインであること前提でいい"というのがあると思う。オフライン対応の Webアプリケーションを作るという話はなしということにとりあえず同意していただいたとして、そうすれば常に最新情報が取ってこれる前提で物事を考えてよいことになる。

となると大活躍する Rails の View オプションは、remote: true だ。これを a タグや button タグ、 form タグなどで追加するだけで Ajax 化できる。さらにそのイベントは ajax:successajax:beforeSend, ajax:error などで結果をキャッチできる。

するとどのような実装ができるかというと、レンダリングは常にサーバーサイドで行うことができる。jQuery でやるべきことは、 Ajax でリクエストを送り、帰って来た HTML をそのまま html(response.body) と言った感じで描画すればいいのだ。そうすれば面倒なDOM操作なんて一切必要なく、 Rails を使っているのであれば html.erb で動的なレンダリングを実現できる。今回のサンプルは Turbolinks を使わない場合のパターンだが、 Turbolinks でもほぼ同等のことが可能だ。Turbolinks を使えばデフォルトで HistoryAPIも備わっているので、戻るボタンも簡単に実現できる。ちなみに私は自前で History API を使って実装した。

Rails を使っている人たちはぜひ一度、なぜ Rails は jQuery を今でも採用し続けているのか、なぜ Controller を生成した時にCSS, JS ファイルも一緒にできるのかを考えてみて欲しい。私なりの答えを書くとRails のレールに乗りながら SPA を作るなら、以下のようなコードになるのではないかと思っている。頭の中で書いただけなので動作保証はしないので注意。

- layout
<div id="base">
   <%= link_to "Ajax!", remote: true, class: "getUsers" %>
    <div class="wrapper"></div>
</div>

- controller
class UsersController < ApplicationController
  layout false

  def index
    @users = User.all
  end
end

- view
<div id="userCtrl">
  <ul>
    <% @users.each do |u| %>
        <li><%= u.name %></li>
     <% end %>
  </ul> 
</div>

- css
#userCtrl {
  # userCtrl だけに適用する CSS
}

- js
$(function() {
  $(document).on("ajax:success", '#userCtrl .getUsers', function(q, data, xhr){
    $("#base .wrapper").html(data);
  });
});

私はこの開発方法を見つけた時、実に美しいと感じた。それぞれの影響を最小限に抑えつつ、 Rails の View を完全に活用して jQuery としての SPA を実現できた、と思った。そして今もなおこの方法で DOM 操作をほとんど行わず、 Rails の View をうまく活用することで Rails のレールに乗っている。こうすれば Rails のレールに乗りながら SPA を簡単に作れる。DOM なんてのは一切出てこないし、フロントエンドでの状態管理も存在しない。複雑な JavaScript コードとはおさらばできる。

Rails で AssetPipeline を使わなかったり、 jQuery じゃなくて React-Rails みたいなものを使っている方は、上記の jquery-rails の使い方を知った上で そのような選択をしたのだろうか? 自ら進んで Ruby on Rails からの レールを外れるという選択をした方には、それなりの苦労が必ず発生する。 Rails の思想と反する行為をしているわけだからね。

スマホとかタブレット、IoT 機器とかと統一した APIを中心としたアーキテクチャにしたい

私もこの考え方がすごい好きだった。 Web ってのはあくまで端末の1つに過ぎず、 API がすべての中心となりサービスの根幹となるものであると考えていた。確かにその考え方だと上記方法は利用することができず、 SPA フレームワークを使わないといけない。

ただ、 Web とその他で圧倒的に違う点が一つある。

それが先ほど申し上げた「Webはオンラインであることが保証できている」という点である。だからこそ常に最新のHTMLコードは ブラウザ側でなくサーバー側で持っていられるようになるわけだ。これがスマホとかだとそうはいかないので、スマホ側でうまくアプリないDBとかに保存して描画したりする必要が出てきてしまう。

API に関して言えば、そんなに機能が複雑になることはないだろう。だから そこはそもそも Rails である必要もないだろうし、別で作るべきだと思っている。同時接続とか多くなるだろうし。

終わりに

今回の話は Rails に限った話であり、例えばそもそもバックエンドがシンプルな Node.js の Express などを使っているのであれば、 SPA を選択してフロントエンドゴリゴリで頑張るという考え方もあると思う。本記事はあくまで Rails にレールを乗って SPA を作るための提案の1つである。

AWS Lambda の Node.js で連続で外部APIを叩く作法

ども、@kimihom です。

今回は AWS Lambda における Node.js のコードの書き方について。

実装したいこと

例えば、id を複数持った配列があるとして、その配列を 一個一個 HTTP リクエストで叩きたい、ということがあるだろう。id単位でしかリソースを削除できないような API があった場合などは必ずそんな場面に出くわす。

これを AWS Lambda で実装するには、どうすれば良いだろうか。ここに AWS Lambda の落とし穴が潜んでいる。何も考えずに書くとこんな感じになるだろう。

var request = require('request');

exports.handler = function(event, context) {
  var host = "https://api.awesomeapp.com";
  var ids = [1,2,3,4];
  ids.forEach(function(val) {
    request.delete({
      uri: host + "/records/" + id,
      json: true
    }, function(err, response) {
      context.done(); //?
    });
  });
};

これだと当然、期待した動作にならないのはわかるだろう。 AWS Lambda は、最後まで処理を終えた時点で context.done() を呼ばなければならず、これだと処理の途中で呼ばれてしまう。じゃあと言って、コールバック地獄の Node.js コードを書いてしまったら負けだ。非同期前提の JavaScript で頭を悩ますことになる。さて、どうすべきか。

Async を使う

AWS Lambda のドキュメントにもあるが、Async を使ってこの問題を解消させよう。最初は特殊な書き方に手こずるかもしれないが、慣れればなんとかなる。

今回は、 async.mapSeries を利用してこの問題を解決する。

var request = require('request');
var async = require('async');

exports.handler = function(event, context) {
  var host = "https://api.awesomeapp.com";
  var ids = [1,2,3,4];

  async.mapSeries(ids, function(id, callback) {
    request.delete({
      uri: host + "/records/" + id,
      json: true
    }, callback);
  }, function(err, res) {
    context.done();
  });
};

mapSeriesは第一引数に連続処理させたいアイテムを指定する。そんで第二引数の function の id にそれぞれが渡るようになっている。 function の中で callback を非同期に呼ぶような処理を書き、すべての ids の配列ループが終わったら、 第三引数の function が呼ばれる。

このように書けば、すっきりと連続した 外部 API のリクエスト処理を AWS Lambda で動かすことができる!

あと たいていの Node.js ライブラリは、 コールバック引数に (error, response) の順番で入ってくることを意識しよう。この慣習を守ることで、 Async の処理をより簡潔に記述することができる。上記コードの callback もその仕組みを利用して省略して書くことができている。より冗長的に書くと、以下のように書くことも可能である。

    request.delete({
      uri: host + "/records/" + id,
      json: true
    },  function(err, res) {
        callback(err, res);
    });

async には、mapSeries の他に、シンプルなコールバック地獄を防ぐための waterfall も用意されている。 AWS Lambda を Node.js で扱うには、これらのメソッドを使いこなし、すべての非同期処理が完全に終わったタイミングで、 context.done() を呼ばなければならない。Asyncにはその他様々なコールバック操作の仕組みが用意されている。詳しくはドキュメントを参照していただきたい。

終わりに

普段 Node.js をあまり書かない人にとって AWS Lambda を使いこなすには、 Async のような非同期処理のライブラリの知識が必須とも言える。私も普段は Ruby でプログラムを書いているし、Webフロントエンドの JavaScript ではあまり複雑に非同期処理を書いたりしないため、なかなか慣れずに苦労した。本記事が AWS Lambda で Node.js を扱う方にとって有益な記事になれば幸いだ。

JavaScript で画像をリサイズする方法

ども、@kimihom です。

前回の記事で画像のリサイズはサーバーサイドでって話だったんだけど、調べてみると Canvas でリサイズまでできてしまうという衝撃の事実が判明し、それで簡単にリサイズを実装できてしまった。

またもや HTML5 の技術に驚かされることになったので、ここに記す。

JavaScript でリサイズするのが理想的である理由

まず何故 JavaScript 側でリサイズするのがいいのかというと、アップロード時のネットワーク負荷が劇的に下げられるからだ。サーバーサイドでリサイズするとなると、まずそのでかい画像をアップロードしなければならない。この時点で重い画像だとアップロードに数秒かかってしまう。

これを JavaScript、つまりフロントエンドでリサイズできれば、ネットワーク負荷を抑えてアップロードが可能だ。これも HTML5 から登場した Canvas のおかげではあるが、そのメリットを存分に享受しよう。

縦横比を合わせた画像のリサイズ

リサイズするならそのままの縦横比で小さくしたいところ。てことで以下のような感じで canvas タグを作って当てはめる感じでソースを書いていく。

  var resizeImage = function(base64image, callback) {
    const MIN_SIZE = 1000;
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var image = new Image();
    image.crossOrigin = "Anonymous";
    image.onload = function(event){
      var dstWidth, dstHeight;
      if (this.width > this.height) {
        dstWidth = MIN_SIZE;
        dstHeight = this.height * MIN_SIZE / this.width;
      } else {
        dstHeight = MIN_SIZE;
        dstWidth = this.width * MIN_SIZE / this.height;
      }
      canvas.width = dstWidth;
      canvas.height = dstHeight;
      ctx.drawImage(this, 0, 0, this.width, this.height, 0, 0, dstWidth, dstHeight);
      callback(canvas.toDataURL());
    };
    image.src = base64image;
  };

前回の記事で画像をクロップする方法を書いたが、できればそのクロップをする前にこの処理を書くことが望ましい。やはり画像がでかいとその分 クロップの処理にも時間がかかり、カクカクになってしまうことがあるためである。

てことで3記事連載で書いてきた画像の流れとしては、以下の通りとなる。

  1. 画像をドラッグアンドドロップで File オブジェクト取得
  2. File オブジェクトの content-type や ファイルサイズの検証
  3. File オブジェクトを readAsDataURL で Base64 エンコード
  4. Canvas を用いて画像のリサイズ
  5. cropper.js で画像の切り取り
  6. 画像を Blob 化
  7. S3 に画像をアップロード

割とこの手順を踏む必要のあるウェブアプリケーションは多いように思う。

この中では AWS-SDK, Canvas, File API などたくさんの技術を扱うが、それぞれしっかりと理解してコードを書いていこう。

関連記事

www.bokukoko.info

www.bokukoko.info

終わりに

最近の3連載で、HTML5による画像のアップロードを扱った。なかなか体系的にまとまっているドキュメントがなかったりするので、ファイル選択だけで満足してたようなウェブアプリケーションを所有している読者がいれば、この機会にドラッグアンドドロップやリサイズ、クロップなど学んでみてはいかがだろうか。

HTML5 Canvas

HTML5 Canvas

Amazon

画像の縦横比を合わせてアップロードするまで

ども、@kimihom です。

前回の記事で、画像ファイルのドラッグ&ドロップの手法を紹介した。これで File オブジェクトを取得することができる。

www.bokukoko.info

さて、今回はその持ってきた画像の縦横比を合わせてS3にアップロードしてみよう。

画像の縦横比の重要性

まぁ説明なんていらないと思うけど、Webの画像はできるだけ縦横比を合わせた方が扱いやすいことが多い。例えばアイコンであれば正方形の形が望まれるし、記事であれば長方形っぽい写真の方がいい。縦横比をしっかりと合わせないと統一感のないWebサイトとなってしまう。(縦横比を合わせなくても上手くやった Pinterest はやっぱすごい)

そんなこんなで縦横比を合わせるわけだが、それをサーバー側で処理するとなると、どの部分を切り取ればいいかユーザーが指定できない。ページ上(JavaScript)でどの部分を切り取ればいいのかを指定できるようにしたいところだ。

縦横比を合わせる処理(クロップ)を実現しよう

f:id:cevid_cpp:20160326203802p:plain

今回利用したのは Cropper っていう JS ライブラリ。かなりよく出来てた。具体的にどう使うのってのはGithubのドキュメントを読めばわかるけど一応紹介。

<div>
  <img id="image" src="picture.jpg">
</div>

$('#image').cropper({
  aspectRatio: 16 / 9,
  crop: function(e) {
    console.log(e);
  }
});

こんな感じ。 cropper を任意のタイミングで呼べば、divのエリアがクロップできるようになる。んでここで前回のファイルアップロード方式の際に問題が起きる。画像 src に URL を指定できないのだ。でもこれも Base64 エンコードを使えばうまくいく。Base64エンコードってのはまぁ基本的なことなんだけども、画像のようなバイナリを文字列で表現しちゃおうってもの。生画像より容量は大きくなるけど、文字列で表現できるのがGood。

では、早速 クロップした画像を取得できるようにし、S3にアップロードする手順に移っていこう。

クロップデータの取得

クロップデータは getCroppedCanvas を呼べば取得できる。今回はたまたま jcanvas を使っていたので、それを使ってのサンプル。jcanvas を使わないで実現する方法は調べていない。cropImage メソッドの引数にある file はファイルのドラッグ&ドロップでで取得した Fileオブジェクトだ。ちょっと長いけどこんな感じ。だいぶ自分の環境で動いている元のコードを修正しているので、動作保証はしない。

  var cropImage = function(file) {
    var fr = new FileReader();
    fr.onload = function(evt) {
      // evt.target.result に Base64エンコードされた画像が入る
      $(".crop-image").attr("src", evt.target.result);
      $('.crop-image').cropper({
        aspectRatio: 1,
        minContainerWidth: 360,
        minContainerHeight: 230,
        crop: function(e) {
          var base64img = $($('.crop-image').cropper('getCroppedCanvas')).getCanvasImage('png');
          uploadtoS3(base64img);
        }
      });
    };
    fr.readAsDataURL(file); // base64へ変換
  };

  var uploadtoS3 = function(base64img) {
    var s3 = AWS. // AWS SDK から S3 オブジェクト取得
    var filename = "";
    s3.putObject({
      'Key': filename,
      'ContentType': "image/png",
      'Body': toBlob(base64img),
      'ACL': "public-read"
    }, function(err, data){
      // after upload
    });
  };

  // base64 -> blob
  var toBlob = function(base64) {
    var bin = atob(base64.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    return new Blob([buffer.buffer], { type: 'image/png' });
  };

結構めんどいんだけど、ファイルオブジェクトのBlobを一旦 Base64 に変換して Cropper へ渡し、その結果(Base64)をまた Blob に戻すってやり方をやっている。 S3 へ Base64形式でアップロードしても、正しく画像を表示してくれなかったので、こんな対応になった。

ちなみに画像の圧縮に関してはどうするのか意見が分かれるところだ。一番手っ取り早いのは File オブジェクトからファイルサイズが取得できるので、例えば 3M バイト以上のファイルをアップロードできないようにすることがある。より丁寧にやるにはどんな画像でもアップロードできるようにして、サーバー側で圧縮してあげる方法もある。ただそれでもファイルサイズの限度を定めないと、とんでもないファイルをアップロードされる可能性もあるので注意しよう。私なら AWS Lambda を利用して圧縮する方法を選ぶ。これはググって調べてみてほしい。

終わりに

Web上の画像処理は割と難しいように見えるが、うまくライブラリを駆使するともろもろうまくいく。なかなかこうしたテクニックは体系的に解説されていないので、本記事が参考になれば幸いだ。

より良い画像を扱うWebサービスが増えることを祈る。

HTML5 時代のファイルアップロード方法

ども、@kimihom です。

今までファイルのアップロードといえば、 input type="file" な要素を作って、 multipart なフォームで送信、というやり方が一般的だった。が、最近はAWSやHTML5の登場により、よりクールで使いやすいファイルアップロードを実現できるようになっている。

今回はその方法をご紹介したい。

File API によるファイル選択とドラッグアンドドロップ

最近のウェブサービスなら一般的になりつつあるファイルのドラッグアンドドロップ。今後は むしろドラッグ&ドロップのないサイトは古臭いし使いにくい というような所まで一般的な方法となるだろう。最近は HTML5 の提供する API のおかげでとても簡単に実装できるようになっている。

ファイル選択とドラッグ&ドロップの両方でキーになってくるのが、Fileオブジェクト。どちらの手法でやっても最終的に得られるのが File オブジェクトになる。

まずはファイル選択から見ていこう。

ファイル選択で File オブジェクトを取得

ファイル選択は、一般的な input type="file" なフォームを作って、そのchangeイベントを取得すると、Fileオブジェクトを取得できる。

  $(document).on("change", ".image-field .file-upload", function(e) {
    var file = e.target.files[0];
    // file オブジェクトが取得できた
  });

ファイルを複数選択した場合は配列で files が格納されているので、1つ1つ処理していけばよいことになる。

ドラッグ&ドロップで File オブジェクトを取得

続いてドラッグ&ドロップ。drop をはじめとした見慣れないイベントを定義してあげる。 dragover はファイルが対象のエリアに入った時点で呼ばれるイベント、dragleaveはファイルが対象のエリアから外れた時点で呼ばれるイベントである。

  // body dragover
  $(document).on("dragover", "body", function(e) {
    e.preventDefault();
    $(".image-field .droparea").addClass("droppable");
  });
  // body draglave
  $(document).on("dragleave", "body", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("droppable");
  });
  // imagefield dragover
  $(document).on("dragover", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").addClass("hover");
  });
  // imagefield dragleave
  $(document).on("dragleave", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("hover");
  });
  // imagefield drop
  $(document).on("drop", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("hover droppable");
    var file = e.originalEvent.dataTransfer.files[0];
    // file オブジェクトが取得できた
  });

たくさんイベントを定義したが、大事なのは一番最後の drop イベントだけだ。他は UI をよくするためだけのイベント定義となっている。なぜ body にも dragover, dragleave イベントを定義しているのかというと、後察知の通り画面に画像などを持ってきた時にここにドロップしてね!とより視覚的に分かりやすくするためである。もちろん Slack のように body 全体をドロップできるようにしてもよいだろう。

e.preventDefault() はとても重要だ。これを書かないと画像だけがブラウザに表示されるようになってしまう。

ファイルをアップロード

あとは Amazon S3 にファイルを直接アップロードしよう。アップロード方法の詳細に関しては、以下に記述してある。 Amazon Cognito を用いたファイルアップロード方法だ。

www.selfree.co.jp

より発展的な実装

それでは画像を縮小させる、クロップ(縦横比を合わせる)にはどうすればいいか。

縮小に関しては AWS Lambda を使って、 S3 にアップロードしたタイミングをトリガとして、AWS 側で縮小させる方法がある。これに関しては別記事に譲るとして、クロップに関しては今度書く記事でご紹介したいと思う。

あわせて読みたい

www.bokukoko.info

www.bokukoko.info

終わりに

HTML5のおかげでファイル操作が非常に簡単に行えるようになってきた。ファイルアップロードは File API を用いて実装することが一般的になってくるだろうから、ぜひここで基本を抑えておきたいところだ。

今更ながら jQuery の trigger の魅力について語らせてもらう

ども、@kimihom です。

今更ながら jQuery で搭載されている機能の紹介。その名も、trigger。 これはあまりメジャーではないけど、クールな Web アプリケーションを作りたい場合にかなりよく使う便利なメソッドだ。

trigger って何?

trigger は主に2つの使い方で使われる。それぞれの用途について紹介しよう。

既存イベントを発生させるパターン

trigger とは、対象オブジェクトに対し、手動でイベントを発生させるメソッドだ。例えば、clickイベントってのはブラウザでマウスでクリックしたら起こせるイベントだけど、JavaScript からそのclick イベントを発生させたい場合に triggerが使える。

$(function() {
    $(document).on("click", "#header .tab", function() {
        console.log("clicked!!");
    });
    
    $(document).on("keypress", function(e) {
        if (e.which == '13') {
             $("#header .tab").trigger("click");
        }
    });
});

一つ目のサンプルはこんな感じ。例えば他のイベント(今回はエンターキーを押した)タイミングで、JavaScript で指定要素をクリックしたことにしたい場合に trigger が使える。

カスタムイベントを発生させるパターン

$(document).on における click イベントってのはブラウザで定義されているイベントだが、そうじゃなくて自分で好きにイベントを定義することができる。 Android で言えば EventBus、iOS で言えばNSNotificationCenterのような通知基盤がマサの jQuery の trigger を使うことで実現できてしまうのだ! Rails で言えば、ajax:successとかそういうの。あれも jquery-rails が作ったカスタムイベントの一つだ。

さて、サンプル。

$(function() {
    $(document).on("custom:fired", function() {
        console.log("clicked!!");
    });

    // どこかのコード
    $(document).trigger("custom:fired");
});

今回は document にカスタムイベント custom:fired という適当なイベントを登録したけど、DOMのどこかの要素にカスタムイベントを定義してもOKだ。これにより、jQuery でも一種のオブサーバーパターンを実装することが可能である。何かの変更を受け取る custom:fired のようなイベントをリスナとして定義しておき、各地あらゆるイベントが発生した時に、それを trigger してあげれば良いのだ。

使いどき?

正直なところいろんなところで使える。 jQuery で SPA っぽいことをしていたら、使う日が必ず来るので知っておくだけでもいいと思う。

あらゆるページのイベントを集約してまとめて表示したいときとか、エクセルのようなあらゆる場所で変更が加わるけど、最終的にエクセルの=を使って関数でまとめたいとかそういう場合に一つイベントの受け皿を定義し、 trigger でカスタムイベントを発生させるとコードがすっきりする。

シンプルが故に、自分が使いたい時に使えるのが逆に怖い点かもしれない。あまりに乱用するとイベント定義とtrigger だらけになり、あっちこっち飛び回るソースコードができてしまう。それは Android の EventBus などを使った時にも同じ注意点である。

リアクティブ? それ jQuery でもできるよ。 今回の記事はそんなところ。

はてなブログで記事下に関連記事を出すカンタンな方法

ども、@kimihom です。

今回ははてなブログのカスタマイズについて。色々なブログで、記事の下に「関連記事」だとか「合わせて読みたい」だとか、そういう記事リンク集がある。これは、記事を読んだ後に同じブログ内を回遊してほしいからやっているわけだ。はてなブログではデフォルトで記事下に関連記事を表示する機能を提供していない。てことで何らかのテクニックを使ってやらないといけない。

調べてみると、外部のプラグインを使うやり方などが紹介されている。これらはその外部提供者に依存しているので、あまり好ましくない。以下の方法を使えば、はてなブログ内の設定だけで記事下に関連記事を出すことが可能だ。

ではその手順を紹介する。

サイドバーに関連記事モジュールを追加

はてなのサイドバーに関連記事がある。これを記事下に持っていこうってのが今回のやり方。

はてなブログの管理から、デザイン -> カスタマイズ -> サイドバー へ行こう。

ここでモジュールを追加を選択し、関連記事を選択。適当にいい感じにオプションをつけておこう。

サイドバーの関連記事を非表示

サイドバーには関連記事を出さないので、非表示にしよう。

デザイン -> カスタマイズ -> デザインCSS に以下を追加。

.hatena-module-related-entries {
  display: none;
}

記事下に関連記事を表示

そんで JavaScript で記事下にその関連記事を移そう。

デザイン -> カスタマイズ -> 記事 -> 記事下に以下を追加。

<div id="relateArticle"></div>

<script>
var timer = setInterval(function() {
    if (typeof jQuery != 'undefined') {
        $("#relateArticle").html($(".hatena-module-related-entries").html());
        clearInterval(timer);
    }
}, 1000)
</script>

仕上げ

このままだとサイドバーに表示される関連記事が記事下に移動するだけなので、あとは記事下の #relateArticle の CSS をいい感じに調整してあげよう。 メディアクリエイターやブロガーの方なら、そのくらいできるよね!

てことでブログ楽しみましょう。

Node.js(JavaScript) でのクラスの定義について考え直してみた

ども、@kimihomです。

毎度 JavaScript でオブジェクト指向プログラミングをやろうとすると、どうやって書けばいいか悶々としていたが、最近ようやく定まってきたのでまとめてみる。調べるとnewを使ったり、prototypeを使ってメソッドを定義したりする方法もあるようだが、個人的には以下の方法が一番書きやすいし理解しやすかった。

JavaScript でのクラス定義

JavaScript はご存知の通り classキーワードが存在しない。そのため function オブジェクトを使って定義することになる。この実装でキーとなるのはメソッドの返り値にオブジェクトを返すところにある。

var MyClass = function(args) {
  var pVar = "Private 変数";
  var args = args; // コンストラクタの初期化

  var privateFunc = function() {
    //プライベート関数
  };

  return {
    publicFunc: function() {
        // パブリック関数。 privateFuncや pVar, args など参照できる。
    },
    publicFunc2: function() {
        this.publicFunc();  // this が必要。
    }
  };
};

module.exports = MyClass;

//
// 別ファイルで
var myClass = require('./myClass')(args); // ()付き!

上記のコードを見ると、なぜパブリックなメソッドとプライベートなメソッドがこれで定義できるのかも理解しやすいと思う。MyClass()を呼び出した時、返ってくるのはJavaScript のオブジェクトになる。つまり、別ファイルで定義したmyClassにはpublicFuncを持ったオブジェクトが返ってくることになる。 JavaScript のクロージャの仕組みによって、publicFunc()のコードの中にprivateFuncなどのメソッドを参照することができる。これにより、private, public なメソッドを定義することが可能になる。

(余談) クロージャって言葉を書くとそれなりに中・上級者っぽく見えるw。JavaScriptのapplyとか使いこなせるようになると、そのさらに上ってイメージ。

この方法の注意点

注意する必要があるのが2つある。 1つ目は、 Node.js で require した時、最後にメソッド呼び出しとして () をつける必要がある点だ。これでメソッド呼び出しをした戻り値(オブジェクト)がmyClassに格納されることになる。

2つ目は、publicメソッドの中でpublicメソッドを呼ぼうとすると、"そんなメソッドねーよ"とエラーになる点だ。// this が必要というコメントの部分にあたる。これは publicFuncpublicFunc2 JavaScript の オブジェクトで定義された値でしかないので、直接名前だけで呼び出すことができないため、thisを明示的に示さないとメソッドを見つけることができないのが原因だ。

この this ってのも曲者で、例えばpublicメソッドの中で無名関数を呼ぶと、そのthisの指すオブジェクトも変わってしまう。以下のような場合だ。

    publicFunc2: function() {
        this.publicFunc();  // this が必要。
        [1, 2, 3].forEach(function(val, index) {
            // this.publicFunc();  
        });
    }

forEach で括られた中の this は、MyClassで返すオブジェクトではない。forEachの中のオブジェクトを指してしまうので上記コードは正しく動作しない。てことでここでもクロージャを使ってthisの別名を作成する必要がある。

    publicFunc2: function() {
        this.publicFunc();  // this が必要。
        var self = this;
        [1, 2, 3].forEach(function(val, index) {
            self.publicFunc();   //OK!
        });
    }

this は無名関数の中に入ると気ままに向き先を変えてしまうので、注意しよう。

終わりに

それなりに大きな JavaScript プログラミングをするなら、オブジェクト指向でやらないと大変な目にあうだろう。JavaScirptコードが大きくなってきて、リファクタリングが必要だと感じられるようになってきた時に、このやり方を知っておけば JavaScript でも効率的なオブジェクト指向プログラミングが可能になると思う。

クラスって意味では今回紹介した方法はわかりにくい(継承がない)けども、JavaScript の性質をうまく利用したクラス定義方法であると思う。むしろ継承しにくい方がいいと思う。移譲、移譲、移譲って言われてるし。

てことで JavaScript でも Let's オブジェクト指向プログラミング!

JavaScript で状態管理から部分更新にしたらハッピーになれた

ども、@kimihomです。

今回はプログラミングにおける考え方について。

JavaScript による状態管理について

JavaScriptを用いたリッチな Web アプリケーションを作ろうとすると、状態管理が結構難しくなってくる。「Aという部分が更新されると、BとCが変わる。Bが削除されると、AとCが変わる」みたいなUIを作ろうとした時、それらすべてをAjaxで更新後にJavaScriptでUIをいじる、というような操作をしなければならなくなる。これは結構大変で、バグを起こしやすい。

そこで最近はバインディングとか、リアクティブプログラミングとかが人気になっている。まさにエクセル関数のようなものだ。 "AはBとCの値の結果だ"とあらかじめ定義しておけば、BとCが変わった時に自動でAも変わるような仕組みを作ることができる。これは良いアプローチだとは思うが、JavaScriptに依存しすぎているので、個人的にはあまりやりたくない。

ちなみに jQuery でも、"trigger" という関数をうまく使えば、上記のような実装が可能になる。triggerはあまり知られてないようで、とても使える関数なので、知らなかった人はぜひ調べてみてほしい。iOS でいう Local Notification、AndroidでいうEventBus のような仕組みを作れるというとイメージしやすいか。

部分更新について

部分更新とは本ブログで便宜的に命名しているだけであるので注意していただきたい。部分更新が具体的にどういうことかというと、状態管理が複雑なUI部分については、すべてAjaxでHTMLを取得するようにし、その部分を適宜更新していくというやり方だ。これはとてもうまくいくし、シンプルに記述することができるようになる。

  var ajaxHtml = function(url, wrapper) {
    $.ajax({
      type: "GET",
      url: url,
      success: function(res){
        $(wrapper).html(res);
      }
    });
  };

  // 例
  ajaxHtml("/header", "#header");

部分更新したいパーツを指定し、更新したいときにajaxHtmlを呼ぶようにする。こうすると、状態管理はすべて サーバーサイド の View 側に集約されるので、JavaScript側で状態管理をする必要がなくなる。状態が変化したときは毎回 ajaxHtml を呼ぶだけでよくなる。このテクニックは、場合によっては一瞬パーツが切り替わってしまう。最近のWebサービスではその部分更新されるパーツは、ローディング中は何かしら画像を出すようにして待たせるようなUIにしているところも多い。

部分更新のテクニックにより、状態管理の必要がなくなり、しかもコードはとてもシンプルになる!

だが、もし、切り替わりなどの制約を受けたくないという残念な仕様な場合はJavaScriptで徹底的に状態管理をするしかなくなる。そういう場合は最近流行りのJSフレームワークを使ったほうがいいのかもしれない。

終わりに

JavaScript になんでもかんでもやらせるのは良くないと思う。できる限りDOMの操作はサーバー側でやらせて、イベントの管理などをJavaScript の本業として分担すると、快適なJavaScriptコーディングができるのではないか、と考えている。

値を更新するのに、結局Ajaxで通信しなければならないんだったら、JSONで返すのではなく、HTMLを返すようにすると、色々とハッピーになれる気がしている。

Twilio x AWS Lambda と API Gateway の連携

ども、@kimihom です。

このブログでは初登場の Twilio の話。 どんなことができるかは Twilio のリンク先に任せるとして、今回は実際に Twilio を利用している方のための TIPS を紹介しようと思う。

電話の可用性を高める

プログラムと連携した TwiML を書いていると、エラーが起きた時に心配になる。電話であるが故にエラーが起きてずっと利用できないとコミュニケーション手段が奪われるため、かなり痛い状況になってしまう。このような自体が起こりうるケースとしてはプログラムのバグで TwiML の書き方が間違えていたり、そもそもサーバー自体が落ちてしまっていた場合などだ。これらの問題が起きた時に迅速に検知し、対応できるようにするのはとても重要なことだ。

そこですぐにエラーが出たことを検知する仕組みを導入する必要があるのだけども、Twilio でデフォルトで提供している アラートトリガー は、期間を1日より細かく設定することができない。即座に対応することが困難である。また、連絡先としてメールアドレスかWebHookかを選べるのだが、このWebHookの形式と Slack の Incoming WebHook の形式は一致していないため、Slackに通知することもできない。

ということで Twilioの電話番号に紐付いた フォールバックURLというものを用いてエラーが起きた時に自動で通知する仕組みを構築する。

Twilioの仕組みとして各電話番号にはまず最初にリクエストを送る リクエストURLと、そのリクエストに失敗した場合に送るフォールバックURLの2つがあり、このどちらも自由に設定することが可能である。ここで大事なのは、リクエストURLとフォールバック URLの向き先は同じサーバーに向けてはいけない、という点にある。なぜなら、サーバー自体が落ちていたら、その時点でアウトだからだ。

ということで今回はフォールバックURLの指定先は Amazon API Gateway から AWS Lambda を起動するような構成にしようと思う。これにすればほとんど無料で通知の仕組みを導入できる。このような通知の仕組みを構築するのにはAmazon API Gateway と AWS Lambda のコンビネーションは理想の環境だ。以下にイメージ図を示す。

f:id:cevid_cpp:20151007193358p:plain

Slack の設定

Slack では Incoming Webhook を利用する。まずはこのインテグレーションをONにし、Webhook先のURLをコピーしておこう。https://hooks.slack.com/services/~~ のURLがそれだ。

AWS Lambda ファンクションの作成

本記事では Amazon API Gateway や AWS Lambda のセットアップなどの入門者的な内容は省略させていただいている。それらの内容はググったり公式ドキュメントを参照していただきたい。

今回は npm モジュールとして request を利用する。 index.js は以下のようになる。

var request = require('request');

exports.handler = function(event, context) {
  param = {
    text: "電話エラー発生!!\nAccountSid: " + event.param.AccountSid + ",\n ErrorUrl: <" + encodeURIComponent(event.param.ErrorUrl) + "|Link>,\nFrom: " + event.param.From + ",\nTo: " + event.param.To,
    icon_emoji: ":ghost:"
  };

  var url = "https://hooks.slack.com/services/T07777777/B0AAAAAAAAAAAAA";

  var options = {
    uri: url,
    form: "payload=" + JSON.stringify(param)
  };

  request.post(options, function(error, response, body){
    context.done();
  });
};

ここで重要なのは、Twilio はフォールバックURLに指定したサーバーに対して、 AccountSid をはじめ、エラーの起きたURLやFrom,To の電話番号など、重要な情報を投げてくれる点にある。これらをSlackに通知すれば、誰に影響が起きたのかすぐわかるし、問題解決が一気に早くなるだろう。

コードを保存したら後は通常通り index.js と node_modules をzip に固めて AWS Lambda にアップロードすればOK。

Amazon API Gatway の設定

問題はここからだ。フォールバック URL に URL をセットできるようにするために Amazon API Gatway を利用する。

まずは適当な名前でAPIを作成しよう。そして Create Method で GET を選択する。そして実行するのは 先ほど作成した AWS Lambda Function である。うまく設定できれば以下のような図に移るはずだ。

f:id:cevid_cpp:20151007195533p:plain

以下の手順で API Gateway のメソッドを設定していく。

  1. Integration Request をクリックし、Mapping Templatesのapplication/jsonを選択。その右側のInput Paththrough を Mapping Template に変えて以下のように記述する。
{
  "param": {
#foreach( $key in $input.params().querystring.keySet() )
    "$key": "$input.params().querystring.get($key)"#if( $foreach.hasNext ),#end
#end
  },
  "stage": "$context.stage"
}

f:id:cevid_cpp:20151007195852p:plain

  1. Integration Response を開き、200を開いてそこのMapping Template のContent-Type を application/xml に変更。さらにそこでエラー時の TwiMLを記載する。

f:id:cevid_cpp:20151007195955p:plain

  1. Method Response の Content Type を application/xml に変更する。

  2. Deploy API でデプロイする。

  3. そこで出来上がった URL をTwilio の電話番号のフォールバック URLに設定する。

以上で出来上がりだ。

デバッグ TIPS

バグの修正に役立つTIPSを最後に紹介する。

Twilio の管理ページではどのリクエストで失敗したのかをわかりやすく時系列で表示してくれているので、それをまず見てみよう。

具体的にはログイン後の ログ -> 通話のページだ。そのページ内にリクエストとレスポンスの結果がそれぞれ書かれているので、どこが悪かったのか把握できる。

AWS Lambda のログを確認しよう。

AWS Lambda で console.log で書き出した結果は、AWS Lambda のタブ内にある Monitoring の View logs in CloudWatch から参照できる。AWS Lambda側でのデバッグはここで console.log で確認しよう。

最終確認

最後に 通常の TwiMLを返すアプリケーションサーバー側でわざと例外を発生させるなどして、フォールバックURLにアクセスさせた時に、以下のようにSlack に通知がくれば完成だ。

f:id:cevid_cpp:20151007201141p:plain

終わりに

まだまだ Twilio と Amazon API Gateway を連携させた例は出回っていないが、本来 Twilio でTwiML を返すサーバーとして、Amazon API Gateway を選択する、というやり方はとても効果的だと考えている。今回はフォールバックURLとしてAmazon API Gateway を利用したが、通常のリクエストURLにAmazon API Gateway を利用する方法も全く問題はない。というよりわざわざ Twilio 用にサーバーを立てるくらいだったら、それで済ませた方がよっぽどいいケースの方が多いかと思う。

ぜひ今回の記事を参考に、 Twilio x Amazon API Gateway の組み合わせにチャレンジしてみていただけたら幸いだ。

リストのドラッグアンドドロップの決定版 - html5sortable -

ども。@kimihom です。

JavaScriptによるドラッグアンドドロップは、何か難しそうでとっつきにくい雰囲気があるかもしれない。そんな不安を一気に跳ね除けてしまうライブラリを紹介しよう。 GitHub - lukasoppermann/html5sortable: VanillaJS sortable lists and grids using native HTML5 drag and drop API.

html5sortable を使えば、tableul などでリスト化された項目に対して、簡単にドラッグアンドドロップの機能を追加してくれる。しかも各種イベントを提供してくれているため、それに伴ってサーバー側でデータを反映する、といったことも容易に行うことが可能だ。デモサイトでどんなことをできるか確認してみよう。

jQueryやAngularJSなどで便利なプラグインを提供してくれている。

最近はHTML5でドラッグアンドドロップのイベントを取得できるような取り決めがあり、html5sortableはその仕様をうまくラッピングしている。それ以前のドラッグアンドドロップのjQueryプラグインなどはHTML5に頼らずに全て自前で実装していたため、コード量が多くなってしまったり、これからのブラウザの対応が難しかったりするが、html5sortableならHTML5の仕様に沿ったドラッグアンドのラッパーであるため、これからも安心して利用できるだろう。

実装方法 (jQuery)

基本的なドラッグアンドドロップを実現するには、まずは普通に ul , li 要素を使ってリストを生成し、以下のような初期設定をするだけで済む。

$('ul.sortable').sortable({
  placeholder: '<li>&nbsp;</li>', 
  connectWith: 'connected'
}).bind('sortstart', function(e, ui) {
    /*
    ui.item contains the current dragged element
    ui.placeholder contains the placeholder element
    ui.startparent contains the element that the dragged item comes from
    */
}).bind('sortstop', function(e, ui) {
    /* 
    ui.item contains the element that was dragged.
    ui.startparent contains the element that the dragged item came from.
    */
}).bind('sortupdate', function(e, ui) {
    /*
    ui.item contains the current dragged element.
    ui.index contains the new index of the dragged element (considering only list items)
    ui.oldindex contains the old index of the dragged element (considering only list items)
    ui.elementIndex contains the new index of the dragged element (considering all items within sortable)
    ui.oldElementIndex contains the old index of the dragged element (considering all items within sortable)
    ui.startparent contains the element that the dragged item comes from
    ui.endparent contains the element that the dragged item was added to (new parent)
    */
})
  • placeholderを指定すると、ドラッグしたあとの要素をどんなように見せるかを指定できる。ここは何もない<li>要素を表示する、などが一般的かと思う。
  • connectWithを指定すると、別の位置にある複数のul要素を連携させることができる。別のところから別のところへドラッグアンドドロップが可能だ。
  • sortstart をバインドすると、ドラッグ開始時のイベントを取得できる。ここで例えばドロップ可能な位置に対してボーダーを入れたりなどする。
  • sortstop をバインドすると、ドロップした後のイベントを取得できる。ドロップ可能な位置に表示していたボーダーを消すなどする。
  • sortupdate は実際にドラッグアンドドロップが終わった時のイベントを取得できる。ここでサーバーにデータをアップロードして順番を適用などする。

uiで取ってこれる内容は英語でコメントしてあるが、適宜console.logなどで出力すれば、どんな値が取ってこれるのかわかることだろう。

後は CSS 勝負!

この原理さえわかってしまえば、後はCSSでどう見せるかだけだ。例えば float: left などを使って横並びにすれば、グリッド構成のドラッグアンドドロップも実現できるし、connectWith をうまく利用して、全く別のところからどこかにドロップさせて適用する、みたいなこともできてしまう。

ドラッグアンドドロップで要素の移動をさせたい、というのであればこのライブラリだけで何でもできるようになるだろう。

注意事項

  • ちなみに、ドラッグアンドドロップによるファイルアップロードはこれとはまた別の話なので注意していただきたい。 それはHTML5 の FileAPIなどを利用しないと実現できない。
  • Chrome, Firefox, Safari, Opera, IE9+ でのみ動くようである。

私のRails と jQuery のフロントエンド開発指針

Railsでの基本的な開発スタイルといえば、ページはリンクとフォーム送信、そしてリダイレクトの基本構成だろう。確かにこれでWebアプリケーションを作ることができる。

ただ今回はよりリッチなWebアプリケーション、具体的にはAjaxを駆使した開発について、Railsでどうやって開発していけばグチャグチャにならずに簡潔に書けるのか、私が心がけている点を紹介したいと思う。これを読めば、別にクールなJavaScriptフレームワークを使わずとも、シンプルなjQueryで作れることを知ることができるだろう。

そもそもなぜリッチにするのか

リッチなWebアプリケーションにすれば、以下のような点のメリットがある。

  • 毎回application.js や application.css、共通画像などを読み込む必要がなくなるため、サーバー負荷に優しい。
  • ユーザーはページ遷移を意識せずにWebアプリを利用できるため、UXが高まる。クールなWebアプリが作れる。

リッチWebアプリはユーザーにとっても開発者にとっても利点がある。しかし、このメリットの背景には以下のようなデメリットがある。

  • JavaScriptで状態を管理しなければならないため、JavaScriptコードが煩雑になる。
  • グチャグチャなソースコードになりがち。どこでAjax呼んでるの?など

上記デメリットを以下に簡潔に書くか。それをRails上でどのように実現するのか。今回はそこに焦点を当ててノウハウをまとめる。

まずはコントローラ毎にコンポーネントを分ける

これめっちゃ大事。そして全体の基本になる話だ。例えば、 UsersControllerを作成した場合、Railsでは users.css.scss, users.js, users/index.html.erb などが関連ファイルとして生成されることだろう。まずは以下のように<div>もしくは<section>タグで区切りをつけよう。

//scss
#userCmp {
  ...
}

// js
$(function() {
  $("#userCmp..").click(function() {
    ....
  })
});

// html
<div id="userCmp">
  ...
</div>

プログラミングの世界ではネームスペースとも言える概念だが、これをすると他のコントローラの影響を考えずにuserCmpに集中してソースを書くことができる。これをやらないと、なぞのイベントが発生したり、CSSがグチャグチャになったりする。

htmlを返すアクション、jsonだけを返すアクションなどを使い分ける

Ajaxというと、どうもJSONだけでやりとりする、という考えを持っておられる方も多いようだが、別にhtmlをAjaxのレスポンスにすることも可能だ。これを知っておくだけでかなり開発の幅は広がる。例えば、グローバルタブがあって、Ajaxでレスポンスをhtmlにし、そのhtmlをcontentの中身として書き換えてみよう。

//html
<ul class="tab">
  <li data="/items">Tab1</li>
  <li data="/carts">Tab2</li>
</ul>
<div id="contents"></div>

// js
var ajaxHtml = function(url) {
  $.ajax({
    type: "GET",
    url: url,
    success: function(content){
      $("#contents").html(content);
    }
  });
}
$(".tab li").click(function() {
  var url = $(this).attr("data");
  ajaxHtml(url);
});

// controller
def index
  render layout: false
end

// index.html
<div id="itemCmp">
  ....
</div>

これがリッチWebアプリのベースとなる部分だ。ItemsController側も同様に itemCmpを作成し、css, js はそれぞれitemCmpで予め限定しておこう。これだけでRailsのTurboLinksもどきの機能を実装できる(戻る機能を除く )。ちなみにタブの移動などは共通で呼び出すAjaxとなるので、本当ならちゃんとレスポンスコードによるエラー処理を書くべき。

場合によってはJSONを返すパターンももちろんあっていい。その際にHTMLをどこかに挿入したい、といった場合に全部jQueryでDOMを構築するのは超絶面倒なので、以下のようにdisplay: noneなテンプレートを用意してそれに当てはめるのがグッド。

<div id="template">
   <!-- 複雑なHTML -->
   <span id="name"></span>
</div>

var $template = $("#template").clone();
$template.find("#name").html(res.name);
$("#contents").content($template);

cloneしないと連続で読んだ時にtemplateが置き換わったグチャグチャのHTMLを再度取ってきてしまうので、この呼び出しが必要だ。

コンテンツ書き換え後のJavaScript処理に対応する

さて、ここまででタブによってAjaxでコンテンツを切り替える、というテクニックを見てきた。リッチアプリではコンテンツを切り替えた後のHTML要素もJavaScriptで動くように実装しなければならない。ただここで普通に $("#mybtn").click(..)と書くだけでは、ロード時にこの要素は無い要素なので正しく動作しない。イベントに関してはonを利用して以下のように書く必要がある。

//items.js
$(document).on("click", "#itemCmp .element", function() {
  // code
});

これで動的に読み込んだ要素にもjQueryのイベントを発火させることができるようになる。

ここでさらなる問題が出てくる。それは"Ajaxで取ってきたコンテンツの初期処理をしたい"というパターン。RailsでAjaxでhtmlを返してすぐにJavaScriptを実行したい場合だ。その場合、assets/javascripts/内に

$(function() {
  // 初期処理...
});

と書く方法は正しく動作しない。すでにドキュメントはページ全体で読み込まれた後にAjax呼び出しをするからだ。これを正しく動作するようにするには、Ajaxで取ってきたHTML内にscriptタグを書いて動作させる必要がある。ここでは trigger でイベントを発火させ、js内でそのカスタムイベントを補足するようなコードを書く。

<div id="itemCmp">
  ....
</div>
<script>
$("#itemCmp").trigger("itemCmp:loeaded")
</script>

//js
$(document).on("itemCmp:loeaded", function() {
    $("#itemCmp .item").append(...);

});;

再喝だが、このパターンを使わなければならないのはAjaxでHTMLを呼び出して呼び出し後にすぐJavaScriptを実行したい時だけ、ここに書くべきである。イベントの処理はここではなく、 $(document).onにしてassets内に書くべきだ。なぜかというと、ご存知のとおりRailsはassets内のJSはプリコンパイルによってメソッド名が書き換えられるので、そうした共通処理はこのHTML内で呼び出すことができなくなるためである。

Railsの変数をJavaScriptで取得したい

JavaScriptを書いていると、Railsの変数の値をJavaScriptで使いたい、というパターンが頻出する。これに対応する最も簡単な方法は、"display: none"なスタイルで変数をまとめたHTMLを書くことだ。

<div class="hidden">
  <span id="user_id"><%= @current_user.id %></span>
  <span id="status"><%= @current_user.status %></span>
</div>

//js
parseInt($("#user_id").text());

ただ、この書き方で注意しなければなら無いのは、悪意のあるユーザーがこのHTMLの内容を書き換えた後にJavaScriptを実行させる、ということもできてしまう点にある。そのためRails側にリクエストを投げる時は必ずそのリクエスト内容が正しいかどうか検証するコードをお忘れなく。

HTMLが汚れて醜い、という方には、RailsとJSを共存させるためのGem gon があるので調べてみるといいだろう。

HistoryAPIで戻る処理を実現する

ここまでで、サーバーとAjax通信をしてインタラクティブなページをjQueryだけで作成することができるようになった。最後の関門は "ブラウザバック"をJavaScriptに対応させることだ。これにはHTML5からのHistoryAPIを利用する。ていっても最近はHistoryJSがあるので、これで楽々だ。

HistoryAPIの基本的な考え方としては、Historyスタックに履歴をどんどんプッシュしていく感じだ。プッシュされた場合や、戻るボタンを押された場合には、共通のイベント(statechange)が呼ばれる。この時にAjaxを呼び出してその状態を表示するような実装を行う。

  // pushStateや戻るボタンが押された時に呼ばれる
  History.Adapter.bind(window,'statechange',function(){
    var State = History.getState();
    ajaxHtml("/" + State.data.action);
  });

// ページ移動時
History.pushState({action: "items"}, "Items Title", "?" + "items");

pushState メソッドは3つの引数を持つ。一つ目がその状態でのデータ(JSON)、二つ目がタイトル、三つ目が遷移後のURLだ。 データの中にAjaxで置き換えるべきurl情報を格納しておけば、戻る時にそのデータを読み込んでAjaxを送ってHTMLを書き換えることで、まるでページが戻った可能ような挙動を実現できる。遷移のたびにURLを変えたい場合は第三引数を正しくしていしてあげることで、たとえばそのリンクをブックマークしてまた来た時に、その状態をAjaxで予め表示しておくことができるようになる。

終わりに

今回はRails x jQuery で特に重要なAjax処理について詳しく説明した。AngularJSを使えば確かにこれらリッチなアプリを作るには簡単に実現できることはわかるが、自分で一から作る分にはjQueryで作った方がハマる確率は圧倒的に減る。というかDOM操作とAjaxだけなのでハマる要素がない。Railsで搭載されているTurbolinksに関してもそうだ。あれも仕組みをしっかり理解していないとハマりやすいが、jQueryで構築すればその心配もないというわけだ。

Rails は標準で jQuery を搭載してくれているが、Railsはこのような開発のためにコントローラごとにjsやcssを分けてくれているわけだ。その設計指針通りに開発すれば、依然としてRailsによる圧倒的開発効率をリッチWebアプリでも実現できる、と私は考えている。

長文に付き合っていただき、ありがとうございました。

自動電話応答サービスを作ってリリースした話

自動電話応答(IVR) を誰でも簡単に作れるサービスを目指してコールコネクトをリリースした。

www.callconnect.jp

自動電話応答(IVR)とは

自動電話応答(IVR)とは、例えばどこかの企業に電話をかけた時、「お電話ありがとうございます。〜に関するお問い合わせは1とシャープを、〜に関するお問い合わせは2とシャープを...」 みたいな感じでガイダンスが流れ、それに従ってボタンを押すと自動で指定の場所へ電話がリダイヤルされる仕組みだ。

これを使えば新人が電話をとって、誰かにとりつなぐといったロスが無くなるし、顧客としても話したい人にダイレクトにつながるので満足度が高まるだろう。

このような自動電話応答の仕組みは従来、一部の大企業がSIerなどに発注し、大掛かりなシステムとして何百万と予算をつぎ込まないと実現できなかった。低予算をアピールしているサービスでも、まずはヒアリングから開始し、それ向けにカスタマイズしたシステムを最低1週間を要するものであった。

コールコネクトについて

コールコネクト

コールコネクトなら、誰でも3分で導入できて、しかも月額9,800円からという破格の料金設定を実現した。それには一貫してこの自動電話応答(IVR)をクラウドで扱うということにより、運用コストを劇的に抑え、しかも早く導入できるシステムを構築した。より詳しい詳細については上記サイトを訪れてみていただきたい。

無料モニター募集中

サービス開発に関して、今までは一人で全てサービスのデザインから文章まで作っていたが、今回は自分と合わせて計3人の優秀な仲間と作ったため、クオリティに関してかなりの自信がある。が、より精度を高めて顧客にサービスを届けるために現在は無料モニター募集でモニター利用していただける方を募集している。無料モニターは事前にサービスを無料で利用できるだけでなく、ローンチ後の20日間は無料で利用できるため、2ヶ月程度無料でサービスを利用することができる。とはいえ品質に関してはすでにリリース可能なレベルまであるので、ご安心していただきたい。無料モニター期間は、いただいた意見からさらに良いサービスにするための時間だ。もちろん、その期間に満足できなかったらサービスの利用は無料期間だけで終わらせても問題は無い。それは私たちが満足できるサービスを提供できなかったということで反省し、改善したいと考えている。

もちろんコールコネクトのお問い合わせも、コールコネクトを利用した自動電話応答(IVR)になっているので、試しにどんなものか確認したい場合はぜひお問い合わせいただきたい。

技術的な部分

より使いやすいサービスにするために様々なブラウザの最新技術を始め、俊敏なサービス開発を実現するための工夫をこらしている。以下は使用技術の一例。

Canvas

<canvas>要素を使うことで、HTML文書の中に図を作ることが可能になる。コールコネクトではcanvasを利用することで電話が来た際の分岐の作成をよりわかりやすく表現することに成功した。 + ボタンや x ボタンを押して分岐のボックスを生成する。

https://s3-ap-northeast-1.amazonaws.com/callcloud/assets/function01-abfd48c57218ea47448fbb3c6e343588.png

WebRTC

WebRTCはリアルタイムビデオチャット的な使い方が有名だが、これを使えばブラウザ上で音声の録音もできる。コールコネクトでは、ガイダンス音声は機械音声、音声録音、音声アップロードの3種類を提供している。WebRTCにより、手軽に声を録音し、実際の声で適切なガイダンスを流すことが可能だ。注意点として、IEは対応していないため、他のブラウザーで録音する必要がある。

サーバーサイド

サーバーサイドは一般的な構成だ。 Ruby on Rails, PostgreSQL, Redis, Unicorn で運用している。テストには馴染みの深いRspecを採用した。この構成が最も早く開発できるし、一般的なテクノロジーにすることでメンテナンスコストも下げている。

終わりに

よりコストを抑え、スマートに電話対応されたい方、ぜひコールコネクトのご利用をお待ちしている。

なんだかんだで SPA から jQuery に戻った話

最近は SPA とか React といった話題が尽きないが、自分は結局 フロントエンド JavaScript は jQuery が最もいいと感じている。それはそれら SPA の JavaScript をいじった経験を踏まえての感想。

理由としては、「 やりたいことができにくい 」これに尽きる。

最新を追うということ

自分が最初にSPAを触ったのは AngularJS だった。なるほど、この ng-repeat や $route, $scope などを使えば、今までサーバサイドでやってたようなレンダリングが表側で制御できるようになる!と感動したものだった。この気持ち良さはきっとサーバーサイドでごにょごにょやっていた経験のある人ならなおさら感動するものだ。

さて、じゃあといって「次作るのは SPA のサービスにしよう!」と意気込んで初めて見たとする。それで作っただけで話題になるし、エンジニアとしては誇らしい。 しかししばらくすると徐々に問題が起きてくる。。

例えば、 グラフを作りたいだとか、クールなUIを実装したいとなった時。調べてみると大抵 jQuery ~~ とあって、 Angular や React 用のがなかったりして、結局 jQuery と混在させなきゃいけなくなったりすることがよくある。それがやりたくなければ、自分で実装するしかない。(今ならあるかもしれないが、当時できたてのAngularJSにはもちろんそのようなものがなかった)

自分で実装しようとした時、いきなり問題に直面する。どうやってこの複雑な機能をこのフレームワーク内で実装するのだろうか。そこは勉強不足の点もあっただろうが、場合によってはフレームワークの奥まで知らなければならないことがある。そこまで頑張って追って実装したあと、それをメンテナンスするのは誰だろう?

そして頻繁なフレームワークのアップデート。それらに対応しなければならない。もしうまくいかなかった時は最悪だ。 StackOverflowを見渡して見つからなかければ1日以上かけて問題の解決のためにソースを読まなければならない。あなたはそこまでする覚悟があるだろうか?

乱立するフレームワーク

ちょっと前までは backbone.js が一番人気だった。これこそが次なるJavaScriptだ、と言われていた。が、今になってみれば全くそれ関連の記事が見当たらない。当時 backbone.js を触っていた人はそれでちょっとしたのをつくれるようになったかもしれないが、誰もメンテナンスできないサービスが完成したことだろう。

今は react.js だが、これも本当に続くものだか全くもっての疑問だ。そんなの気にせずに時間をかけてでもそのフレームワークを愛するというのであれば話は別だけども。

テスト?

SPAで処理をするということは、本来サーバ側でやっていたロジックをJavaScript側にやらせることが多くなる。そんな時はJavaScriptでテストを書きたくなってくる。ただページ遷移とかも全てJavaScriptで制御しているので、その状態まで持ってきた状態のテストを書くといったことが必要だ。これがなかなか大変。 そうなるとテストを書かないでいろいろ実装してしまうことになる。果たしてこれがいいのだろうか。

jQuery について

基本的にロジックはサーバーサイドで実行させて、細かい動きのところだけ jQuery をちょろっと使う。これがやはり本来あるべき JavaScriptの存在意義なのではないだろうか。それ以上に JavaScriptに仕事をさせようとすると、メンテナンスが一気に大変になる。

jQuery はやはり強力だ。 jQuery ~~ でググれば大抵のプラグインは手に入る。ちょろっと入れれば簡単に導入できる。そしてたくさんの知見がネット上に転がっている。

ReactやAngularのバインディングの仕組みは確かに素晴らしいし書いていてクールだ。だけどそれ以外のことに目を向けると、jQuery、やはりまだあなたが自分には必要だ。

Canvas を容易に扱えるプラグイン jCanvas

以下のようなハッカソンに参加した。

Salesforceハックチャレンジ2014 | Salesforce World Tour Tokyo

ここで、自動コールセンターを作成するコールクラウドを提出した。コールクラウドで分岐を手軽に作成できるようなユーザーインタフェースにこだわった。このようなインタフェースを作るために、 jCanvas | jQuery meets the HTML5 canvas を利用した。これを使うと、以下のようなグラフィック処理をウェブ上で実現できる。

こういうのって今まではかなりとっつきにくい感じでなかなか手が出なかったんだけど、意外とドキュメントを見ながら開発してなんとかなった。

特にjCanvas で重要な概念は「Layer」だ。これがHTMLでいう id の役割をしており、後からイベントが発生した時に getLayer でその要素を取ってくることができる。また「Group」の概念も重要だ。これはいわゆる class の役割をしており、ひとまとまりを一気に消したり、移動させたりするときに使える。

この2つさえそれなりに理解すれば、あとは線や四角形、テキストの表示方法をドキュメントを見ながら頑張れば、実現したいものが実現できるだろう。

今回のコールクラウドでは木構造を扱う割と複雑な描画だったため、さらに四角形のひとまとまりを「box」クラスとして別に定義し、それを木のノードとして扱うような作りにした。それらをどのx, y の位置に描画するのかいちいち計算して指定しなければならないため、普段のアプリ開発よりかなり頭を使う必要がある。ただそれも最初は数字でピクセル指定して場所を確認し、あとは動的に表示できるようにメソッドに切り分ければいいだけなので、頑張るだけだ。

今回は利用しなかったが、jCanvas を使えばドラッグも簡単だ。

所感

一歩先のユーザーエクスペリエンスを目指すウェブプログラマーの方は是非 jCanvas を使ってみることをおすすめする。やり始めると楽しいのでおすすめ。