Twitterクライアントでつぶやく（form編） 1/2

いつも説明が長ったらしくなる傾向にあるので、今回こそは要点だけスパスパッとかいつまんで書いてみます。

[angularjs で Twitter クライアントを作成し AWS.S3 で動かす](http://qiita.com/Kurom96/items/59f540619d5cd651dbaf)
[Twitterクライアントにプロフィールページを追加](http://qiita.com/Kurom96/items/1b95bb15a0e5d2cb42ac)
の続きです。

プロフィール画面まで作ってあるブランチは[こちら](https://github.com/KazuhikoKuroda/mytwitter/tree/profile)。

## やりたいこと

* Twitterクライアントでつぶやく
 * formを使う
 * バリデーションとか
* 投稿結果をダイアログで表示してみる

最終的にこうなります。

![tweet.gif](https://qiita-image-store.s3.amazonaws.com/0/86201/e0104709-c874-1d66-b4b0-44a3b91323c7.gif)



## サービス追加

投稿機能を `twitterService` に追記します。

```js:mytwitter/app/scripts/services.js
postTweet: function(message) {
	var deferred = $q.defer(),
		url = '/1.1/statuses/update.json',
		params = {
			data: {
				status: message
			}
		};

	authResult.post(url, params)
		.done(function(result) {
			deferred.resolve(result);
		}).fail(function(err) {
			deferred.reject(err);
		});
	return deferred.promise;
}
```


## ルート作成

いつもどおり新しいルートを作成し、ナビを追加します。

```bash
$ yo angular:route tweet
```

```html:index.html
<div class="collapse navbar-collapse" id="js-navbar-collapse">

	<ul class="nav navbar-nav">
		<li><a href="#/">Home</a></li>
		<li><a ng-href="#/profile">Profile</a></li>
		<li><a ng-href="#/tweet">Tweet</a></li>
		<li><a ng-href="#/about">About</a></li>
		<li><a ng-href="#/">Contact</a></li>
	</ul>
</div>
```

追加したら `grunt serve` で動作確認することをお勧めします。

## 投稿画面作成

### モジュール追加

以下のモジュールを追加します。

* angular-messages（フォームのメッセージ表示）
* angular-bootstrap（`bootstrap` コンポーネントのディレクティブを扱う）

```bash
$ bower install angular-messages angular-bootstrap --save
```

モジュール参照を `app.js` に追加します。

```js:mytwitter/app/scripts/app.js
angular
	.module('mytwitterApp', [
		'ngAnimate',
		'ngCookies',
		'ngResource',
		'ngRoute',
		'ngSanitize',
		'ngTouch',
		'mytwitterApp.services',
		'ngMessages',
		'ui.bootstrap'
	])
```

### 画面作成

前回のように [layoutit](http://www.layoutit.com/) で粗方の HTML を作成し、ディレクティブを記述します。

```html:mytwitter/app/views/tweet.html
<div class="container-fluid">
	<div class="row">
		<div class="col-md-12" ng-show="tweet.showTweet">
			<form class="well" name="tweetForm" ng-submit="tweet.submit()" novalidate role="form">
				<div class="form-group">
					<label for="tweet">Tweet Text</label>
					<textarea class="form-control" name="tweet" ng-model="tweet.inputText" placeholder="tweeeeeeeeeeeeeeeeeeet"></textarea>
				</div>
				<button class="btn btn-primary" type="submit">つぶやく</button>
			</form>
		</div>
	</div>
</div>
```

`submit` 時の振る舞いは `form` タグ内に `ng-submit` で記述する方法と、`submit` ボタンに `ng-click` で記述する方法があります。両方に書くと多重実行してしまう恐れがあるため、どちらかに書きましょう。

### バリデーションとボタン制御

`Twitter` クライアントなので、0 文字と 140 文字を超える入力をエラーにしたいです。テキストエリアに入力上限 `ng-maxlength` と 必須 `ng-required` を追加します。

```html:mytwitter/app/views/tweet.html
<textarea class="form-control" name="tweet" ng-maxlength="140" ng-model="tweet.inputText" ng-required="true" placeholder="tweeeeeeeeeeeeeeeeeeet"></textarea>
<div ng-if="tweetForm.tweet.$dirty" ng-messages="tweetForm.tweet.$error" role="alert">
	<div ng-message="required">入力は必須です。</div>
	<div ng-message="maxlength">入力は１４０文字までです。</div>
</div>
```

`form` ディレクティブはテキストエリアのバリデーション結果を `tweetForm.tweet.$error` に格納します。`ng-messages` は `$error` の内容に基づき出力するメッセージを選択するディレクティブです。
参考：https://docs.angularjs.org/api/ngMessages
メッセージの `<div>` タグに `ng-if="tweetFrom.tweet.$dirty"` を指定しているのは、値変更時のみバリデーション結果を表示したいからです。画面表示直後に必須入力のエラーが表示されたらイヤですもの。

`ng-disabled` でフォームの入力が有効な場合のみ「つぶやく」ボタンを有効にします。

```html:mytwitter/app/views/tweet.html
<button class="btn btn-primary" ng-disabled="tweetForm.$invalid" type="submit">つぶやく</button>
```

`tweetForm.$invalid` は、フォームの入力に不備がある場合のみ `true` が入っているため、ここでは、テキストエリアが未入力または 140 文字を超える入力がある場合は「つぶやく」ボタンが押下不可になるわけです。今回は入力コンポーネントが１つですが、複数ある場合に便利ですね。

`tweet.html` は以下のようになります。

```html:mytwitter/app/views/tweet.html
<div class="container-fluid">
	<div class="row">
		<div class="col-md-12" ng-show="tweet.showTweet">
			<form class="well" name="tweetForm" ng-submit="tweet.submit()" novalidate role="form">
				<div class="form-group">
					<label for="tweet">Tweet Text</label>
					<textarea class="form-control" name="tweet" ng-maxlength="140" ng-model="tweet.inputText" ng-required="true" placeholder="tweeeeeeeeeeeeeeeeeeet"></textarea>
					<div ng-if="tweetForm.tweet.$dirty" ng-messages="tweetForm.tweet.$error" role="alert">
						<div ng-message="required">入力は必須です。</div>
						<div ng-message="maxlength">入力は１４０文字までです。</div>
					</div>
				</div>
				<button class="btn btn-primary" ng-disabled="tweetForm.$invalid" type="submit">つぶやく</button>
			</form>
		</div>
	</div>
</div>
```



angularjs はフォームを作成すると内部的にコントローラを作成します。フォーム内のコンポーネントには `formのname属性.コンポーネントのname属性` でアクセスします。フォームはいろいろな情報を持っています。[ng-inspector](http://ng-inspector.org/)（Chromeプラグイン）を使えば、スコープ内の要素、関数を参照可能です。べんり。

<img width="500" alt="ng.png" src="https://qiita-image-store.s3.amazonaws.com/0/86201/4b37f262-851d-a1bd-5353-3ec57824206e.png">

### CSS

入力に NG がある場合にコンポーネントをピンクにしてみましょう。ナウい感じになります。

```css:mytwitter/app/styles/main.css
form .ng-invalid.ng-dirty {
  background-color: lightpink;
}
form .ng-valid.ng-dirty {
  background-color: lightcyan;
}
```

## コントローラ作成

`TweetCtrl` を作成します。

```js:mytwitter/app/scripts/controllers/tweet.js
'use strict';

function TweetController(twitterService) {
	var vm = this;
	vm.inputText = '';
	vm.showTweet = false;

	twitterService.initialize();
	if (twitterService.isReady()) {
		vm.showTweet = true;
	}

	vm.submit = function() {
		twitterService.postTweet(vm.inputText)
			.then(function(result) {
				console.log(result);
				alert('Twitter投稿完了！');
			}, function(err) {
				console.log(err);
				alert('Twitter投稿に失敗しました。');
			});
	};
}

angular.module('mytwitterApp')
	.controller('TweetCtrl', TweetController);
```

Twitterへの投稿ができました！ :clap:
では、投稿結果画面作ります。

と思ったけど、やっぱり長くなっちゃったので次回に続きます。。。Orz