TypeScriptで書くAngularJSフィールドのキー操作処理

7 min read読了の目安(約4400字

2014-02-24 に Qiita に投稿した記事のアーカイブです。本文中のリンクは動作しないことがあります。

(2014/10/25 追記)この記事は古いです。キー操作などのロジックは Directive 毎にまとめるべきで、以下はノウハウが少ない頃の内容です。記録として残します。

--

前回(TypeScript で書く AngularJS の MVC)の記事がご好評頂けたようで、ありがとうございました。今回も TypeScript+AngularJS ネタです。

input フィールドの値をキー入力で処理する

最近のブラウザでは<input type="number">のときにインクリメントボタン(上下三角)が表示されフォーカスが当たっていると上下キーで数値の増減が出来たりしますが、この実装をどう独自に書けるか考えました。

AngularUI の ui.utilにはキーイベントを捌くモジュールが含まれているのでこれを使えば済むと思いきや、上下キーについては反応が無いようでこの案は失敗。そこで TypeScript で一から書くことにしました。

動作する例

JavaScript ですが動作する例を書きました。フィールド内で上下キーを押してみてください。Value フィールドは Shift キーを押しながらで別の動作をします。(jsfiddle

Module の実装

この例ではプレフィクスをmyとしています。(ここの 1 文字目を大文字にすると読み込まず、最初けっこうハマりました。)

/// <reference path="../vendor/angular.d.ts" />

angular.module("my.keyFocusHelper", []).directive("myKeyFocus", () => {
  return {
    restrict: "A",
    link: (scope, element, attr) => {
      var thisScope = scope;
      element.bind("keyup", (event) => {
        scope.$parent[attr.myKeyFocus](event, thisScope);
      });
    },
  };
});

##app.ts の例
用意したモジュールの使用を宣言します。

/// <reference path="/vendor/angular.d.ts" />

interface MyApp extends ng.IModule {}
var app: MyApp = angular.module("myApplication", [
  "ngResource",
  "my.keyFocusHelper",
]);

##Controller での書き方
前回の記事と同様の Controller を例に使っています。詳しくは前回の記事を参照してください。

/// <reference path="../vendor/angular.d.ts" />
/// <reference path="../app.ts" />

app.controller("IndexCtrl", ($scope) => {
  return new IndexCtrl($scope);
});

interface MainScope extends ng.IScope {
  entities: string[];
  keyFocusColor: Function;
  keyFocusValue: Function;
}

class IndexCtrl {
  private scope: MainScope;

  constructor($scope: MainScope) {
    this.scope = $scope;

    $scope.keyFocusColor = angular.bind(this, this.keyFocusColor);
    $scope.keyFocusValue = angular.bind(this, this.keyFocusValue);
  }

  public keyFocusColor(event: MouseEvent, myScope): void {
    // up arrow
    if (event.keyCode == 38) {
      myScope.e.color += 1;
      $scope.$apply(); // 書き忘れると操作しても表示が更新されない
    }
    // down arrow
    else if (event.keyCode == 40) {
      myScope.e.color -= 1;
      $scope.$apply();
    }
  }

  public keyFocusValue(event: MouseEvent, myScope): void {
    // up arrow with shift
    if (event.keyCode == 38 && event.shiftKey) {
      myScope.e.value += 10;
      $scope.$apply();
    }
    // down arrow with shift
    else if (event.keyCode == 40 && event.shiftKey) {
      myScope.e.value -= 10;
      $scope.$apply();
    }
    // up arrow
    else if (event.keyCode == 38 && !event.shiftKey) {
      myScope.e.value += 1;
      $scope.$apply();
    }
    // down arrow
    else if (event.keyCode == 40 && !event.shiftKey) {
      myScope.e.value -= 1;
      $scope.$apply();
    }
  }
}

View での使い方

my-key-focusという属性が今回のポイントです。

<!--略-->
<div ng-controller="IndexCtrl">
  <table class="records">
    <thead>
      <tr class="table-header">
        <th class="head-color">COLOR</th>
        <th class="head-value">VALUE</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="e in entities">
        <td class="cell-color" ng-class="'color-'+[e.color]">
          <input ng-model="e.color" my-key-focus="keyFocusColor" />
        </td>
        <td class="cell-value">
          <input ng-model="e.value" my-key-focus="keyFocusValue" />
        </td>
      </tr>
    </tbody>
  </table>
</div>
<!--略-->

解説

キー入力イベントを受け取って処理するメソッドは Controller 内にも書けますが、複数の Controller で用いる場合は Module としてまとめたほうがいいでしょう。

冒頭でmy.keyFocusHelperModule を作成しましたが、ここでは directivemyKeyFocusを宣言しています。これで View の HTML 内でmy-key-focusが設定された要素に対して処理が行えます。(directive が何かについては割愛します)
my-key-focus="someValue"の値は、directive 内ではattrの引数に渡されます。ここにキー入力によって実行したいメソッド名を()抜きで表記します。(例:my-key-focus="keyFocusColor"

Controller で実装したkeyFocusColor()は directive のscope.$parent[attr.myKeyFocus]にて呼び出せます。これでキー入力を受け取る実装とキー入力後の処理を分けることができます。あとは Controller 内にメソッドを実装して$scope.func = angular.bind(this, this.func);の形式でバインドすれば完成。

以上です。TypeScript や Module を駆使してスマートに書きましょう!

注意点

directive でのscope.$parent[...の部分はng-repeatの外だとscope[...と書く必要があります。まだ抽象化が足りず、今後の課題です。

おまけ

今回の件とは関係ないですが、ng-class="'color-'+[e.color]"とするとクラス名を動的に設定できます。文字列連結には+を使うようです。ng-classに慣れると UI 実装が簡単になり面白いです。