タグ別アーカイブ: JavaScript

AngularJSでUI BootstrapのTooltipをValidationに連動させて表示させる

| コメントをどうぞ

AngularJSには強力なフォームのバリデーション機能があり、任意のタイミングでエラーメッセージを表示することができます。
このエラーメッセージをバリデーションの状態に連動してUI BootstrapのTooltipに表示したいとき、どうすればよいでしょうか。

まずは思い付く方法で

まずは最初に思い付いた方法で実装してみます。
ツールチップはtooltip-enableディレクティブで有効/無効を切り替えられるので、バリデーションに失敗したときだけ有効にして表示するようにしてみました。

index.html

...

<form name="form" style="width:500px;" ng-class="{'has-error': form.value1.$dirty && form.value1.$invalid}">
  <table class="table">
    <tr>
      <th>お名前(20文字まで)</th>
      <td>
        <input type="text" class="form-control input-sm" name="value1" ng-required="true" ng-model="value1" tooltip="20文字以下で入力して下さい" tooltip-enable="form.value1.$dirty && form.value1.$invalid" ng-maxlength="20">
      </td>
    </tr>
  </table>
</form>

...

app.js

angular.module('app', ['ui.bootstrap']);

結果

バリデーションエラー時にinputにマウスカーソルを乗せることでツールチップが表示されるようになりました。

tooltip1

しかし、これではinputにマウスカーソルを乗せるまでエラーの内容が分かりませんし、マウスカーソルを離すとツールチップが消えてしまうのでイマイチです。
これは、ツールチップの表示を切り替えるデフォルトのトリガーイベントがmouseenter/mouseleaveとなっているためです。

tooltip.js

...

  // Default hide triggers for each show trigger
  var triggerMap = {
    'mouseenter': 'mouseleave',
    'click': 'click',
    'focus': 'blur',
    'none': ''
  };

...

.directive('tooltip', [ '$tooltip', function($tooltip) {
  return $tooltip('tooltip', 'tooltip', 'mouseenter');
}])

...

tooltipのカスタムトリガーとカスタムディレクティブで実現する

Plunker
今回はバリデーションの状態変化に応じて自動的にツールチップを表示したいので、自由に使えるカスタムトリガーが必要になります。

まずは、適当なconfigセクションで$tooltipProvider.setTriggers()を使ってカスタムトリガーを定義しましょう。$tooltipProvider.setTriggers()の引数には、表示イベント名をプロパティ名、非表示イベントをそのプロパティの値として持つオブジェクトを指定します。

app.js


var app = angular.module('app', ['ui.bootstrap', 'app.directives']);

// 追加
app.config(['$tooltipProvider', function ($tooltipProvider) {
  $tooltipProvider.setTriggers({
    'show': 'hide'
  });
}]);

これで要素がshowイベントを受け取った時にツールチップが表示されるようになりました。

次に、このトリガーを発火するための仕組みを作ります。

AngularJSのバリデーション機能は、バリデーション対象として指定したモデルの値の変化に応じてリアルタイムに実行され、実行結果がスコープにセットされるようになっています。上記index.htmlの例だと、form.value1.$invalidform.value1.$validのboolean値を読むことでバリデーションの状態を取得することができます。

今回はtooltipディレクティブを拡張するカスタムディレクティブを用意して、その中でform.value1.$invalidを監視することで、バリデーションの状態が切り替わった時にshowイベントまたはhideイベントを発生させてツールチップの表示を切り替えるようにしました。

directives.js


angular.module('app.directives', [])

// ツールチップ本体に付加されるディレクティブ
// デフォルトのものをそのまま流用
.directive( 'myTooltipPopup', function () {
  return {
    restrict: 'EA',
    replace: true,
    scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
    templateUrl: 'template/tooltip/tooltip-popup.html'
  };
})

// tooltipディレクティブを拡張するカスタムディレクティブ
.directive('myTooltip', ['$tooltip', '$timeout', function ($tooltip, $timeout) {
  // tooltipのインスタンスを作成する
  var tooltip = $tooltip( 'myTooltip', 'myTooltip', 'show' );
  var originalCompile = angular.copy(tooltip.compile);
  tooltip.scope = {myTooltipShow : "&"}

  // compileを拡張
  tooltip.compile = function(element, attrs, scope){
    // オリジナルのLink function
    var originalLink = originalCompile(element, attrs);

    // linkを拡張
    var link = function(scope, element, attrs) {
      // my-tooltip-show属性の値を監視する
      scope.$watch(scope.myTooltipShow, function (val) {
        if (val) { // 評価値がtrueならばshowイベントを発生させる
          $timeout(function() {
            element.triggerHandler('show');
          });
        }else{ // 評価値がfalseならばhideイベントを発生させる
          $timeout(function() {
            element.triggerHandler('hide');
          });
        }
      });
      // オリジナルのlinkを実行する
      originalLink(scope, element, attrs);
    }
    return link;
  }
  return tooltip;
}])

index.html

...

<input type="text" class="form-control input-sm" name="value2" ng-required="true" ng-model="value2" my-tooltip-show="form.value2.$dirty && form.value2.$invalid" my-tooltip="20文字以下で入力して下さい" ng-maxlength="20">

...

これで、バリデーションの状態がエラーになった時点でツールチップが表示され、正常になった時点で非表示になるようになりました。
my-tooltip-showform.value2.$dirtyを加えているのは初期状態でツールチップが表示されるのを抑制するためです。

今回作成したものはPlunkerにデモを用意していますので、ご利用ください。
http://plnkr.co/edit/ynywEEpNvlx0iSA2bUpI?p=preview

HTMLとJavaScriptでEC2インスタンスを起動する

| コメントをどうぞ

AWSのSDKは様々な言語向けに用意されていますが、サーバーサイドの実装なしでAWSのAPIを叩く場合の選択肢として、AWS SDK for JavaScriptがあります。
http://aws.amazon.com/jp/tools/

ただ、他のSDKと違って対応しているサービスが限られています。
利用可能なサービスはS3、SQS、SNS、DynamoDBなどで、EC2は対象ではありませんでした。

EC2をサポート

6月24日のリリースで、In the BrowserにEC2がこっそり追加されていました。
http://aws.amazon.com/releasenotes/SDK/JavaScript/5873934951898767

6月18日のリリースにはEC2はありませんね。
http://aws.amazon.com/releasenotes/SDK/JavaScript/3577030144306744

HTMLファイルだけでEC2を起動

続きを読む

Grunt の同名タスクをファイル分割する

| コメントをどうぞ

12月ですね。
12月と言えば、そう私たち会社人にとって避けては通れない忘年会のシーズンですね。
γ-GTPの上昇には気をつけて!

・・・くりすます?
なんですかそれは?

どうもDiceK Mikamiです。
デジャブ?

ここ最近はGruntを使ってグルグルしています。
(@д@)←こんな状態。
なので、Gruntに関して書いてみたいと思います。
続きを読む

AngularJS の ng-grid を動的に変化させてみる

| コメントをどうぞ

12月ですね。
12月と言えば、そう私たち会社人にとって避けては通れない忘年会のシーズンですね。
肝臓には気をつけて!

・・・くりすます?
なんですかそれは?

どうもDiceK Mikamiです。

今日はAngularUIのひとつであるng-gridを動的に変更させる方法について書いてみます。
続きを読む

Google Apps Scriptでデッドリンクチェッカーを作ってみる

| コメントをどうぞ

最近 Google Apps Script (GAS)  を触り始めまして。

勉強を兼ねて簡単アプリを組んでみました。以下仕様です。

  • 指定サイト内のデッドリンクを検出(href/@srcをチェック)
  • 発見されたら通知メールを出す
  • 指定間隔で定期実行
  • サイトの階層までは辿らない

続きを読む

ダブルMVCルーティングを追え!

| コメントをどうぞ

クライアントサイドのフレームワークが使いたい。
とくに、AngularJSなどは素敵だ。
使おうとなってみたところで、おい待てよと。
クライアントにもルーティングがあるよと。
サーバでルーティングしている場合どうなるんですかと。

よし、今日はレッドオクトーバーダブルMVCのルーティングを追ってみることにしよう。
どうも、DiceK Mikamiです。

 

0. 検証理由

チームで開発すると明確なローカル規約や仕様書が存在しない場合、ソース構成が驚きの汚さになります。
また、内容も属人的になってしまい、「これ作った奴、ちょっと俺に謝れ」となります。(なりませんか?
こうした問題を解決するため、構成の見通しの良さからサーバサイド、クライアントサイドとフレームワークを使いたくなるのも人情と言うもの。
で、その前段階として今回の検証をしてみようと思った次第であります。
 

1. 準備

今回は、サーバをSpring Bootで作り、クライアントをAngularJSで作ります。
サーバをRESTサーバにして、クライアントと完全に分離すると検証の意味がないので、サーバに内包する形でAngularJSを使います。
(というか、それが今回の肝。サーバのVがクライアントに繋がっていないとルーティングの謎を追えない)
イメージとしては、Spring BootのView部分がAngularJSで出来ていると言う感じ。

2. 仮説

仮説として、以下のようなフローになると考えています。

  1. ブラウザからの要求からサーバコントローラーが起動する。
  2. サーバコントローラーに紐づいているサーバビューが返却される。
  3. サーバビューがJSP(HTML)とした場合、まずビューが起動する。
  4. ビューに紐づいているクライアントコントローラーが動く。
  5. クライアントコントローラーによって計算された値が、クライアントビューに返却される。
  6. クライアントビューが表示が完了する。

ポイントは、サーバビューとはクライアント側の基底HTML(JSP)であること。
また、クライアントビューは、この基底HTMLの上にインジェクトされるHTMLであることです。
ちょっと煩雑ですね。
 

3. 実装

まずは、Spring Bootでサーバ側をさくっと作ります。

doublemvc02

できました。
Sample Controllerがサーバコントローラーで、index.jspがサーバコントローラーに紐づいているビューです。
上図は、まだAngularJSを導入していない状態です。
これにAngularJSのクライアントを投入します。

doublemvc03

できました。
webapp/sample 配下が、AngularJSでできています。
また、WEB-INF/jsp/sample/dist 配下のindex.jspがAngularJS用のサーバビューになります。
ここでポイントは、「 /sample/dist/index.jsp = サーバビュー 」であり、「 /sample/dist/index.jsp = AngularJSの基底HTML 」だということです。
ですので、サーバコントローラーからは、このindex.jspを呼び出すことになります。
 

4. 実験

さて、いよいよ実験です。

doublemvc04

最終的な表示画面に各所の通過タイム(new Date()によるミリ秒表示)を表示してみました。
仮説の通りになりました。
client viewの時間は、AngularJSの基底HTMLを通過した時間です。
時間を見る限りやはりここがエントリーポイントのようです。
 

5. さいごに

時間検証を行うことで、ダブルMVCであってもそれぞれの干渉することなく動作させることが出来ることがわかりました。
しかし、クライアントは基底HTMLによって閉じることになりますので、例えば、サーバ側で複数のルーティングを持つ場合、クライアントではそれぞれの関連を持つことが出来ないことになります。
これが開発を行っていく上で余計な手間を増やさないかが気になるところです。
反面、接合点さえ決めておけば、パラレルに作業を進めることができ、またそれぞれでテストを行うことができるのは魅力的ではないでしょうか?
実際に適用してみた事例がもっと出てきてくれれば、こうした事例におけるベストプラクティスが見つかるかもしれませんね。

これにて任務完了。
 

おまけ

今回、検証に利用したソースはGitHubにあげておきました。
corestrike/RoutingSample

[Windows]node.js(0.8)+express(3.0)+ejs+socket.ioを使ったサンプルアプリを動かすまでのメモ

| 1件のフィードバック

調べる機会があったのでメモ。

expressが3になってから微妙に仕様が変わっているようで、過去のサンプルが動かなかったりしたのでまとめておきます。

以下環境。

  • Windows XP
  • node.js(0.8.4)
  • express(3.0.0rc1)
  • socket.io(0.9.8)

node.jsのダウンロード・インストール

公式サイトからWindows Installerをダウンロード。インストーラを起動します。

やることはライセンスに同意するだけ。インストール先を選ぶ余地もありません。

コマンドプロンプトで以下を入力し、インストールされたことを確認します。node.exeは環境変数pathに勝手に追加されています。

> node -v

v0.8.4

expressのインストール

コマンドプロンプトで以下を入力。最新版がインストールされます。-gを忘れずに。

> npm install express -g

以下のコマンドでインストールされたことを確認します。

> express –version

3.0.0rc1

バージョンを指定したい場合は、express@バージョン のように指定してやるといいです。

expressでアプリの雛形を作る

どこか適当なディレクトリに移動し、コマンドプロンプトで以下を入力。アプリケーションの雛形が作成されます。

> express -tejs mynodeapp

-tejsは、使用するテンプレートエンジンの指定です。HTMLライクに書けるejsを指定しています。(デフォルトはjade)

-tとejsの間にスペースは入れないでください。スペースが入るとjadeになります。どうもこれはWindowsだけっぽい。

mynodeappは作成するアプリケーション名です。

利用するnodeモジュールのインストール

mynodeappフォルダに移動し、コマンドプロンプトで以下を入力。アプリで利用するモジュールをインストールします。

mynodeapp > npm install express ejs socket.io

socket.ioはここでインストールされます。

サンプルアプリの作成

この時点でアプリを起動可能ですが、Welcome to Express!しか出ない味気ものなのでサンプルアプリを作ってみます。まあよくある簡単なチャットアプリです。

以下の2ファイルを修正します。

mynodeapp/app.js

/**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes')

//*************(中略) *************//

/* コメントアウトします
http.createServer(app).listen(app.get('port'), function(){
 console.log("Express server listening on port " + app.get('port'));
});
*/

/** 以下を追記します **/
var server = http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});
var socket = require('socket.io').listen(server);
socket.on('connection', function(client) {
 client.on('message', function(event){
   // クライアントからのメッセージをコンソールに出力
   console.log(event.message);
   // 送信元へメッセージ送信
   client.emit('message', event.message);
   // 送信元以外の全てのクライアントへメッセージ送信
   client.broadcast.emit('message', event.message);
  });
});

mynodeapp/views/index.ejs

<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
 <link rel='stylesheet' href='/stylesheets/style.css' />

 <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
 <script type="text/javascript" src="/socket.io/socket.io.js"></script>
 <script>
 $(function(){
 var socket = new io.connect("/");

 socket.on("connect", function(){
 $("#transportName").text("connect via " + socket.socket.transport.name);// 接続時に接続方式表示
 });

 socket.on("message", function(message){
 $("<li>").text(message).prependTo($("#messageArea ul"));// 受信メッセージをレンダリング
 });

 $("#submitButton").click(function(event){
 socket.emit("message", {message: $("#msg").val()});// 入力メッセージをサーバへ
 });
 });
 </script>
 </head>
 <body>
 <div id="transportName"></div>
 <input id="msg">
 <input id="submitButton" type="button" value="submit!">
 <div id="messageArea">
 <ul></ul>
 </div>
 </body>
</html>

サンプルアプリを動かす

mynodeapp直下で、コマンドプロンプトから以下を入力。アプリが起動します。

mynodeapp > node app

ポート3000で起動するので、ブラウザで http://localhost:3000 にアクセスします。

動いた

他のブラウザから参照しても入力メッセージが表示されるはず。

その他

  • express 2.5と違ってlayout.ejsが消えてる。部分テンプレートはexpress-partialsモジュールを使うらしい
  • node.jsのホスティングサービスまわりをちょっと調べているので、次はそれを書くかも。

JavaScriptの配列の要素を前後の要素と入れ替える

| コメントをどうぞ

この前、JavaScriptの配列のある要素を一つ前(一つ後ろ)の要素と入れ替えたい、という状況になりました。

Arrayオブジェクトのメソッドにはそれらしきものはありませんでしたが、spliceというメソッドを使って実現できそうでした。
spliceメソッドについては、以下を参照してください。
http://www.tohoho-web.com/js/array.htm#splice

以下、実行例です。

var array = new Array("あ", "い", "う", "え", "お");

// 入れ替える文字のindex
var index;

// "う"を一つ前の"い"と入れ替え
index = 2;
array.splice(index-1, 2, array[index], array[index-1]);
alert(array); // => あ,う,い,え,お

// さらに"え"を一つ後ろの"お"と入れ替え
index = 3;
array.splice(index, 2, array[index+1], array[index]);
alert(array); // => あ,う,い,お,え

前後に移動させたい文字のindexを使って、spliceの引数の与え方を2パターン作成してみましたが、
メソッドの実行前にindexの値を変更すれば、どちらか一方で充分ですね。

以下、後者の引数の与え方で実行した例です。

var array = new Array("あ", "い", "う", "え", "お");

// 入れ替える文字のindex
var index;

// "う"を一つ前の"い"と入れ替え
index = 2 - 1; // 一つ前の要素と入れ替えなのでindexを前にずらす
array.splice(index, 2, array[index+1], array[index]);
alert(array); // => あ,う,い,え,お

// さらに"え"を一つ後ろの"お"と入れ替え
index = 3;
array.splice(index, 2, array[index+1], array[index]);
alert(array); // => あ,う,い,お,え