のらねこの気まま暮らし

Perlについてとか、創作についてとか、発展途上の自分と向き合う記録。

WindowsでWebアプリ開発

何やるかを考える。

前提

  • 開発機はWindows7
  • エディタはIDEを主に使っていた
  • ネイティブ言語
  • バージョン管理はgitではない何か

学んでもらうこと

  • Unixベースの開発
  • IDEではないエディタ
  • gitによるversion管理
  • HTTP通信周りの基礎知識と動き
  • WEBアプリケーションの基礎

開発環境構築

開発OS準備

以下のものを用意してもらう。

準備

  1. https://www.virtualbox.org/wiki/Downloads からVirtualBoxのexeファイルをダウンロードして、インストールする。
  2. https://www.ubuntulinux.jp/download/ja-remix-vhd から Ubuntu 12.4 のイメージディスクをダウンロードする。
  3. ダウンロードした圧縮ファイルを解凍し、中のvhdファイルをC:\Users\$USER\.VirtualBoxのディレクトリに移動する。

イメージの作成

  1. VirtualBoxを起動
  2. 新規(N)から作成。名前ubuntu(とりあえず)、タイプLinuxバージョンUbuntu(32bit)とそれぞれ設定して次へ
  3. メモリ割り当ての確認があるがひとまずデフォルトのままでよい。
  4. ハードドライブの割り当てもデフォルトですすめる。
  5. ドライブの割り当てはすでにあるドライブを選択し、先ほどダウンロードしたvhdファイルを選択する。
  6. 設定完了。

開発OSのインストール

  1. ホーム画面から、ubuntuのイメージを選択して起動する。
  2. 初回だとCreating process for virtual machine "ubuntu"...というメッセージがでて初期化処理が走るためしばらく時間がかかるので注意。
  3. あとはノリで進める FIXME
  4. ひと通りインストールが終わると、ログインを要求されるのでログインする
  5. ログインできたら完了。

インストール後にやる設定

  • terminalを開く
  • sudo apt-get update && sudo apt-get upgrade

開発ツールのインストール

ssh接続を可能にする

vmubuntuにminttyやputtyからアクセスできるようにする。

Ubuntu側での作業

sudo apt-get install openssh-server

Windows側での作業 1. VirtualBox設定を開く 2. ネットワーク 3. ポートフォワーディング 4. 新規ルールを追加 5. ルール名(ex: SSH)、ホストポート(ex: 50022)、ゲストポート(22)を追加 6. ssh user@localhost -p 50022putty等でsshできるか確認 7. 鍵通す。

zshのインストール

shellは慣れてるヤツで。

$ curl -L -O http://sourceforge.net/projects/zsh/files/zsh/5.0.5/zsh-5.0.5.tar.bz2
$ tar xjvf zsh-5.0.5.tar.bz2
$ cd zsh-5.0.5/
$ ./configure --enable-multibyte
$ make
$ sudo make install

login shellの変更。

$ which zsh
/usr/local/bin/zsh
$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/dash
/bin/bash
/bin/rbash
# echo `which zsh` >> /etc/shells
$ chsh
パスワード:
新しい値を入力してください。標準設定値を使うならリターンを押してください
        ログインシェル [/bin/zsh]: /usr/local/bin/zsh
$ exit

gitのインストール

https://code.google.com/p/git-core/downloads/list から最新版を取得。

curl -L -O 'https://git-core.googlecode.com/files/git-1.9.0.tar.gz'
tar xzvf git-1.9.0.tar.gz
cd git-1.9.0
./configure
make
sudo make install

依存系のライブラリをインストール

sudo apt-get install zlib1g-dev
sudo apt-get install tcl
sudo apt-get install gettext

vimのインストール

curl -L -O ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2
tar xjvf vim-7.4.tar.bz2
cd vim74
 ./configure --enable-multibyte --enable-luainterp --enable-perlinterp --enable-pythoninterp --enable-rubyinterp
make
sudo make install

to be continued

node-scheduleという予定を登録して実行するnpmライブラリの紹介

node.jsを使っていて出会った、使い勝手の良さそうなライブラリ(俺基準)を紹介していくシリーズを初めてみました。

node-schedule

https://www.npmjs.org/package/node-schedule

指定時間に登録したタスクを実行してくれるライブラリです。 Dateオブジェクト、日時指定、時刻指定、CRON形式での指定等に対応し、非常に使い勝手が良いです。

installation

npm install 

usage

scheduleJobメソッドを呼び出すことでタスクが登録されます。

第一引数に実行タイミング、第二引数にジョブ名(省略可)、第三引数に実行したいタスクを登録します。 返り値はJobオブジェクトです。

var schedule = require("node-schedule");
var job = schedule.scheduleJob(timing, name, fn);

JobオブジェクトはEventEmitterを利用しており、いくつかのフックポイントが提供されています。

  • scheduled: タスクの登録時
  • run: タスク実行
  • cahceled: 予定のキャンセル時
job.on("scheduled", function () {
  console.log(this.name + "の予定が登録されました");
});
job.on("run", function () {
  console.log(this.name + "の予定が実行されました");
});
job.on("canceled", function () {
  console.log(this.name + "の予定がキャンセルされました");
});

Dateオブジェクトを使う場合

下記は2014/07/05 22:30:00に処理を実行する場合の例です。

var job = schedule.scheduleJob(new Date(2014, 07, 05, 22, 30, 0), function () {
  console.log("登録されたジョブの実行");
});

日時を指定する場合

以下の項目をオブジェクトで定義します。

  • year
  • month
  • day
  • dayOfWeek
  • hour
  • minute
  • second

下記は毎日18:30に処理を実行する場合の例です。

var job = schedule.scheduleJob({
  hour   : 18
  minute:  30
}, function () {
  console.log("実行");
});

CRON形式

下記は毎日18:30に処理を実行する場合の例です。 -による範囲指定は可能ですが、/による指定はできないようです。(たぶん)

var job = schedule.scheduleJob('30 18 * * *', function () {
  console.log("実行");
}); 

まとめ

node-scheduleはご覧の通り、特定日時や特定時間での繰り返し処理に向いています。 記法が多岐に渡り、様々なタイマー処理に適応できます。

ちなみに僕はIRCBotでミーティングや予定のリマインドに利用しています。 ぜひ使ってみてください

AngularJSのfactoryとserviceを読み解く(後編)

前編: ngularJSのfactoryとserviceを読み解く(前編)

前回のAngulaJS!(なんちゃらライブ的な) factoryやserviceといったAngularJSの機能をドキュメントを読んだりぐーぐる先生に聞いたりしてもまったくさっぱりよくわからなかったのでAngularJSのコードを追ってみた。

factoryは初期化処理をInjectorで一度だけ実行してその結果をAngularJSのcacheFactoryを使ってキャッシュしているところまではわかった。

しかし、serviceはよくわからなかった。 よくわからないので振り返りから。

.service(name, fn)

  1. serviceが呼ばれる
  2. $injector.instantiateにfnが渡される
  3. $injector.instantiateで別のクラス(仮にAとする)を作成してprototypeを継承する
  4. $injector.instantiateでクラスAのインスタンスを生成して$injector.invokeにserviceに指定したfnと共に渡す
  5. $injector.invokeでDIに指定したDependenciesを解析、取得
  6. $injector.invokeで、引数をdependenciesに、コンテキストをクラスAのインスタンスにしていしてfnを実行する
  7. $injector.invokeの返り値がobjectまたはfunctionであればそれをfactoryに渡す
  8. $injector.invokeの返り値がobjectでもfunctionでもなければクラスAのインスタンスをfactoryに渡す

つまり、こういうことだ!

// serviceに登録するfunction(コンストラクタ)
var User = function (options) {
    this.name = options.name || null;
    this.age    = options.age    || null;
    
    this.set = function (column, value) {
        this[column] = value;
    };
    this.get = function (column) {
        return this[column];
    };
};

// $injector.instantiate
var Constructor= function () {};
Constructor.prototype = User.prototype;
var instance = new Constructor();

// $injector.invoke
User.apply(instance, [{name: "tarou", age: 10}]);
var user = instance;

・・・実にわかりにくい。

気をつけなければならないのは、serviceの第二引数は初期化コールバック関数ではなく、クラスのコンストラクタであること。

factoryとおんなじ用に使うのは不適切で、angularとは別に定義したクラスを指定する(app.service("name", ["$rootScope", MyServiceClass])のように)か、コンストラクタで完結するクラスを定義すべきだ。

var User = function (options) {
    this.name = options.name || null;
    this.age    = options.age    || null;
    
    this.set = function (column, value) {
        this[column] = value;
    };
    this.get = function (column) {
        return this[column];
    };
};
app.service('user', [User]);
app.service('user', [function () {
    this.name = options.name || null;
    this.age    = options.age    || null;
    
    this.set = function (column, value) {
        this[column] = value;
    };
    this.get = function (column) {
        return this[column];
    };
}]);

serviceはどういう時に使うべき?

factoryとの違いとして、serviceはインスタンスの生成を保証し、かつ同一の名前空間で他のインスタンスは作られない。

デザインパターンで言うところのSingletonであり、アプリケーション内で整合性を管理する用途にもちいられるのだと想定できる。

たとえば、複数のController間で一つのリソースを管理したい場合などがそれに該当すると思われる。

下手に$rootScopeに値を入れたりするよりは固く実装できるのではないだろうか。

まとめ

serviceはSingletonなので、それなりの扱いを用意しよう。 serviceメソッドの第二引数はコンストラクタなのでそれを念頭に置いて利用しよう

AngularJSのfactoryとserviceを読み解く(前編)

AngularJSfactoryserviceがどうにも覚えられないのでまとめてみた。

まとめ

  • factoryはobjectをキャッシュしておく
  • serviceはインスタンス化してキャッシュしておく
  • providerの謎が深まった

factory

providerにfunctionらしきものを渡している。 $getはInjectorに指定されたときに一度だけ呼ばれる。

function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L666

ここまでだとまだ実行されない。

var app = angular.module('app', ['ngResource']).factory('User', function ($resource) {
  return $resource('/api/user/:id', {id: "@id"}, {});
});

ここで実行され、以降の呼び出しはreturn $resource('/api/user/:id', {id: "@id"}, {});の評価結果がキャッシュされたヤツが呼び出される。

app.controller("UserCtrl", function (User) {
  $scope.user = User.$get(id: 1);
});

service

serviceに指定された無名関数はconstructorという名で$injector.instantiateの引数に渡され、その処理をfactoryへと渡している。

  function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
  }

  function value(name, val) { return factory(name, valueFn(val)); }

  function constant(name, value) {
    assertNotHasOwnProperty(name, 'constant');
    providerCache[name] = value;
    instanceCache[name] = value;
  }

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L668-L680

$injector.instantiateは与えられた引数を、あたらにConstructorクラスが作られ、serviceに登録した関数のprototypeを継承させいる。 なぜArrayの場合は末尾を取り出しているのかはよくわかってない。 ['$rootScope', function () { ... }]的なアレの為に、Arrayの判定してる。 Constructorクラスをインスタンス化し、invoke(省略)に渡す。

その返り値がObjectまたはFunctionであればそのままをfactoryにわたすが、どちらでもない場合はConstructorインスタンスをfactoryに渡している。

    function instantiate(Type, locals, serviceName) {
      var Constructor = function() {},
          instance, returnedValue;

      // Check if Type is annotated and use just the given function at n-1 as parameter
      // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
      Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
      instance = new Constructor();
      returnedValue = invoke(Type, instance, locals, serviceName);

      return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
    }

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L804-L815

なお、invokeでは(重要なところだけを挙げれば)Constructorクラスのインスタンスをコンテキストとしてserviceで指定した関数を実行しその結果を返しているだけである。

    function invoke(fn, self, locals, serviceName){
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = [],
          $inject = annotate(fn, strictDi, serviceName),
          length, i,
          key;

      for(i = 0, length = $inject.length; i < length; i++) {
        key = $inject[i];
        if (typeof key !== 'string') {
          throw $injectorMinErr('itkn',
                  'Incorrect injection token! Expected service name as string, got {0}', key);
        }
        args.push(
          locals && locals.hasOwnProperty(key)
          ? locals[key]
          : getService(key)
        );
      }
      if (!fn.$inject) {
        // this means that we must be an array.
        fn = fn[length];
      }

      // http://jsperf.com/angularjs-invoke-apply-vs-switch
      // #5388
      return fn.apply(self, args);
    }

https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L801

ここまで追ってみたけどよくわからないので、続きは明日。

Constructorで空のクラスをprototypeだけ継承し、インスタンス化する。そののちにinvokによってDependenciesを引数としてserviceで指定したfunctionに渡すことで、DIとしての動きを作っているっぽい。

どう使うのかはまだ曖昧だけど、輪郭が見えてきた。

後編: AngularJSのfactoryとserviceを読み解く(後編)

AngularJSでDOMの更新を監視する with iScroll4

iScrollをAngularJSと使いたい案件があったので、それを使うためにいろいろ調べた。

DOMを動かしてScroll

position:fixed等、モバイルでは一部バグがあったり非サポートだったりして、その大体にiScrollやscrollerjs等といったライブラリを用いる。

これらはDOMをCSSとJSで動かしてあたかもスクロールしているかのように見せるライブラリだ。

いろいろライブラリがあるけれど、とりあえずiScroll4を使った。 一度使ったことがあったし、ng-iscrollも使っていたので。

ng-iScroll

Angular x iScrollを使うとなった時、google先生に問うと真っ先にでてくるのだけど、個人的な都合で利用を避けた。

DOMの変更を監視

ng-iScrollのやり方に沿えば、実際iScroll自体は動いたのだけれど、そのままだと、Ajaxで取得したHTML系のコンテンツが非同期的に投入されたときにiscroll.refresh()してくれない。

そこでどうしたかっていうのが本題。

$watchを使う

以下のコードを見てくれ、どう思う?

scope.$watch(function () {
  scroller.refresh()
});

・・・実はこれでも、XHRで取得したDOMを追加したときにrefreshしてくれる。 でもこの使い方間違っているので注意。

ドキュメントを見てもらえればわかるけれど、$watchの第一引数は、監視する値を示していて、変更時に走らせる処理は第2引数に指定しなければならない。

一応、第1引数であるwatchExpressionは、functionを渡すことも可能で、値の変更時に呼ばれるのでこれでも動くが、明らかにBKであり、間違っている。

ではどうすればいいのか。 それを知るには、$watchを含めたscopeのライフサイクルを理解しなければならない。

$watchと$digest

$watchの仕組みや動くタイミングは以下の記事が丁寧にかかれているので参考になった。

AngularJS のデータバインドを支える $watch

$watch$digestで呼ばれており、XHRや値の変更時等にdigestが呼ばれる。

以下の記事を併せて読むとより理解が深まるんじゃないかと。

AngularJS の $watch, $digest, $apply について書く

DOMの高さの更新を検知したい

さて、$watchで登録したfunctionがいつどこで呼ばれるかがなんとなくわかったと思う。

今回僕が知りたかったのはiScrollの対象としたDOMの高さが変わったタイミングだ。 つまり、こうすれば取れるはず。

angular.module("myApp").directive("ngScrollable", function () {
  return {
    restrict: "A",
    link: function (scope, elements, attr) {
      var element = elements[0];
      var content = element.children[0]; // スクロールさせたいコンテンツ。適宜セレクタは書き換えるべし。

      var scroller = new iScroll(element, {});

      scope.$watch(function () { return content.clientHeight }, function {
        scroller.refresh();
      });
    }
  }
});

ここまでで2時間かかった・・・ Angular奥が深い

AngularJS x UI-RouterでpushStateを使う

何も考えずにAngularJSを使うと、遷移のURLはハッシュフラグメント(#)を使うことになる。 しかし、ngRouterの$locationProviderにはhtml5Modeという機能があり、こいつを有効にすると、pushStateを使ってURLを構築することができる。

それをUI-Routerで使うよという話。

UI-Routerも$locationProviderをそのまま利用可能

ほんとうにまんま一緒に有効化できる。

ngRouterの場合:

angular.module("myApp").config(["$locationProvider", function ($locationProvider) {
  $locationProvider.html5Mode(true);
}]);

UI-Routerの場合:

angular.module("myApp").config(["$locationProvider", function ($locationProvider) {
  $locationProvider.htmlMode(true);
}]);

ui-srefの扱い

UI-Routerを使っていて感動したのは、ui-srefの扱い。 ui-srefはstateを指定し、そのstateで定義したURLをhrefの属性に変換してくれるDirectiveですが、html5Modeを有効にすると、ハッシュ(#)を取り除いてくれる。

つまり、URLの差し替えとかせずにhtml5Modeに移行できる。 なので、UI-Routerを使っているなら、hrefよりもui-srefを使ったほうが可用性が高いです。

grunt-contrib-connectと併せて使いたい

変更を最小限にhtml5Modeを利用できるのは素晴らしいが、pushStateを使う以上は当然、サーバ側にも手を入れなければならない。

基本的には/のアクセスは/index.htmlにリライトされているだけなので、/hogeとかのURLを(内部の遷移ではなく)叩いてしまうと、当然Not Foundになる。

わざわざテンプレートをRouterにまかせて管理しているのに、同じようなHTMLを大量に生産するのも無駄過ぎるし、一般的なプロラマなら違う方法があると考える。

connect-rewrite

grunt-pluginではないが、URLに対して特定コンテンツを返す様に設定できるnode-connectのプラグインがある。これをgrunt-contrib-connectのmiddlewareとして利用してやればいい。

/api, /css, /js, /tmpl, /fonts, /imgのHTTP-PATHを除くリクエストをすべて/index.htmlへリライトしたい場合は以下の様に書く。

connect: {
  server: {
    options: {
      host: "localhost",
      port: 8000,
      base: "www",
      middleware: function (connect, options) {
        if (!Array.isArray(options.base)) options.base =[options.base];
        directory = options.directory || options.base[options.base.length - 1];

        // node-moduleを読み込み
        modRewrite = require("connect-modrewrite");

        middlewares = [];
        middlewares.push(connect.directory(directory));
        // modRewriteの設定
        middlewares.push(modRewrite([["!/api|/css|/js|/tmpl|/fonts|/img /index.html"]);
      }
    }
  }
}

備忘録

pushState対応してて気づいた余談。

RouterのtemplateUrlは絶対パスで記述しよう

相対パスで指定してて、/hogeなURLでアクセスした時にテンプレートが404になって困った。 templateUrlとしてはhttp://localhost:8000/tmpl/fuga.htmlを期待してても、相対パスで書かれていると、/hogeから/fugaに遷移しようとしたときに読みに行くURLはhttp://localhost:8000/hoge/tmpl/fuga.htmlになる。

WEB屋としてはあまりに当たり前なコトなのだけど、サンプルで書かれてた相対パスをそのまま使っていた。まあ、ハッシュフラグメントでやっている限りは困らないけど。

URLの指定にHashfragment(#)ではなくHashbang(#!)を使いたい

$locationProvider.hashPrefix('!');ってすれば、#!になる。

Angular UI-Routerのviewsの定義を確認してみた

state.viewsの指定方法を理解してなかったので調べた。

基本

viewの名前はビュー名ステート名@で結合したviewName@stateNameで構成される。

無名のview

ui-viewは名前を与えず無名のviewとして定義ができる。

<div ui-view></div>

その場合、stateは""でviewを指定する

$stateProvider
  .state("example", {
    views: {
      "": "example.html"
    }
  });

viewのstateを省略した場合

viewsの定義文字列は@でビュー名とステート名を区切る。 ビュー名に@が含まれていない場合、ビュー名の末尾に@と親ステートの名前^1を結合してview名する。

$stateProvider
  .state("example", {
    views: {
      "main": {
        templateUrl: "example.html"
      }
  })
  .state("example.child", {
    views: {
      "main": {
        templateUrl: "example.child.html"
      }
    }
  });

このようなstatesを定義した場合、viewsは以下に変換される

views: {
  "main@": {
    templateUrl: "example.html"
  }
}
views: {
  "main@example": {
    templaetUrl: "example.child.html"
  }
}

viewsを省略した場合

viewsは省略することができる。

$stateProvider.state("example", {templateUrl: "example.html"});

上記の場合、viewsの指定は以下に変換される。

views: {
  "@": {
    templateUrl: "example.html"
  }
}

rootステートについて

親要素を$stateProviderに定義しないステートもrootという親をもっている。

root = registerState({
  name: '',
  url: '^',
  views: null,
  'abstract': true
});

これによって、例えば@だけをviewsに指定した場合は最上位に定義されている無名ui-viewを指定したことになる。