CSS borderの仕組み

いままで知らなかったので備忘録として…

borderって, 実は両端の部分が斜めに切れているんですね.

 

これを, 応用すると, こんなデザインができたり,

 

border-topとborder-bottomをtransparentにして, border-leftを定義すると,

 

シンプルなリストマーカーがCSSで自由にデザインできたりするんですね.

AngularJS ファイルアップロードのディレクティブ

AngularJSでは, <input type=”file” />にng-changeでイベントハンドラを設定してもうまくいかない.

そこで, ディレクティブを作成するという解決方法がセオリー (らしい)

ポイントは, directiveの作成で, コールバック関数が返すオブジェクトにlinkプロパティを設定することです.
ここで, Fileオブジェクトを取得するためのイベント処理を定義しておきます.

あとは, <input type=”file”>の属性にディレクティブ名を指定するだけです.

このとき注意すべきことは, 属性名にハイフン (-)  を使うのはOKですが, その場合, ディレクティブ名はハイフンを除いて, ローワーキャメル形式 (先頭の単語以外の1文字目を大文字にしてつなぐ命名形式) にしないとダメです.

 

AngularJS コントローラの入れ子

AngularJSでコントローラーを複数作成していると, どうしてもコントローラーが入れ子 (ネスト) になってしまいます.

もっとも, 入れ子になっても別段対策とかは不要です. 各コントローラーの$scopeが上書きされてしまう…なんてことはありませんし, 子のコントローラーから親のコントローラーの$scopeにアクセスするには,

$scope.$parent

これでコントローラー間でのやりとりも簡単にできます.

ちなみに, コントローラーを入れ子にした場合, 内側にあるコントローラーは外側にあるコントローラーをプロトタイプ継承します.

したがって, 共通に利用するプロパティやメソッドを外側にあるコントローラー, つまり, スーパークラスに定義しておくことで, 差分プログラミングが可能になります. ただし, プロトタイプ継承なので, 値の更新や削除に関しては注意してください.

例えば, 内側のコントローラー (サブクラス) で外側のコントローラー (スーパークラス) で定義されているプロパティに代入をした場合, それは内側のコントローラーで新たに値を定義したことになります. それを削除すると, 値がundefiendになるのではなく, 今度は外側のコントローラーの値を参照することになります.

jQuery when

昨日の記事で, JavaScriptにおける非同期処理をあたかも同期処理のように記述可能にするDeferredオブジェクトに関して記載しましたが, Deferredオブジェクト (と後続関数) とともに, もう1つおさえておくと便利なのが, when関数です.

when関数は, 複数の非同期処理を引数として指定し, それらの非同期処理すべてが完了するまで後続関数の処理を遅延させることが可能です. つまり, 並列処理を簡単に記述することを可能にします.

when関数に渡す関数は必ず, Promiseオブジェクトを返し, Deferredオブジェクトがresolvedかrejectedに必ず遷移するようにしておきます.
引数に渡した関数のDeferredオブジェクトがすべてresolvedになれば, 後続関数はresolvedに対応する関数が実行されます (doneやthenの第1引数の関数).

どれか1つでも, rejectedになれば, 後続関数はrejectedに対応する関数が実行されます (failやthenの第2引数の関数).

jQuery Deferredオブジェクト

クライアントサイドJavaScriptでは, イベントドリブンプログラミングのため, 非同期処理が多くどうしてもコールバック関数を多用することになってしまいます (まあ, Node.jsもそうですが…).

いわゆる, 「コールバック地獄」になってしまい, 可読性が非常に悪くなるという問題をかかえてしまいます.

jQueryのDeferredオブジェクトはそれを解決するためのオブジェクトで, 非同期処理を同期処理のように記述することが可能になり, 可読性が向上します.

サンプルコードにあるように, まずは, Deferredオブジェクトを生成して, Promiseオブジェクトを返す関数を定義し, また, そのなかに非同期処理を記述します.


var getPromise = function(timeout) {
    var deferred = $.Deferred();

    window.setTimeout(function() {
       //Asynchronously
    }, timeout);

    var promise = deferred.promise();

    return promise;
};

そして, 本来ならば, タイムアウトした時点で実行したい関数をPromiseオブジェクトのdoneメソッドに指定します.


getPromise(1000).done(function() {console.log('done'}});

あとは, 非同期処理のなかで, Deferredオブジェクトの状態を遷移させるメソッドの1つである, resolveメソッドを実行します.


    window.setTimeout(function() {
       //Asynchronously
       d.resolve();
    }, timeout);

これで, タイムアウト時間が経過したあとで, doneメソッドに指定した関数が実行されます.

本来であれば, getPromise関数に, doneメソッドに指定する関数をコールバック関数として渡す必要があるのですが, Deferredオブジェクトを利用することによって, まるで同期処理のように記述することができました(もちろん, jQueryの内部ではコールバック関数として処理しているのですが…).

Deferredオブジェクトの基本的な使い方は上記のような感じですが, もう少し, 詳細に踏み込んだことも記載しておきます.

Deferredオブジェクトの状態

Deferredオブジェクトは内部的に3つの状態をもちます.

  • unresolved
  • resolved
  • rejected

Deferredオブジェクトを生成した時点での状態はunresolvedです. そして, 先ほどの例では, resolvedメソッドによって, 状態をresolvedに遷移させましたが, rejected状態に遷移させるにはrejectメソッドによって, rejectedに遷移させることが可能です.

ちなみに, 1度, resolveかrejectedに遷移すればそれ以上状態遷移することはありません. また, 状態遷移はすべてDeferredオブジェクトが司るので, Promiseオブジェクトには状態遷移のためのメソッドはありません.

後続関数

Deferredオブジェクトを利用した非同期処理では, 後続関数とよばれるDeferred (Promise) オブジェクトのメソッドに実行したい関数を指定します. 先ほどは, Deferredオブジェクトの状態がresolvedに遷移した場合に実行されるdoneメソッドを利用しましたが, 状態がrejectedに遷移した場合に実行されるfailメソッドもあります.

後続関数
Method Description
done(function callback() {}) resolvedに遷移した場合に実行される関数を登録する
fail(function callback() {}) rejectedに遷移した場合に実行される関数を登録する
then(function callback1() {}, function callback2() {}) 第1引数に, resolvedに遷移した場合に実行される関数, 第2引数にrejectedに遷移した場合に実行される関数を登録する
always(function callback() {}) resolved・rejectedのいずれに遷移しても実行される関数を登録する

ちなみに, 後続関数で指定する関数には, 引数を渡すことも可能で, 状態遷移の際, すなわち, resolve・rejectメソッドに引数を指定するだけです. また, resolveWith・rejectWidthメソッドを利用すれば, 状態遷移とともに, 後続関数で指定している関数におけるthis参照を指定することができます. 引数のしていは, applyメソッドと同じで, 第1引数にthis参照のオブジェクト, 第2引数に引数として渡したい値の配列を指定します.

メソッドチェーン

上記の表に記載した後続関数はすべてPromiseオブジェクトを返すので, メソッドチェーンで記述することが可能です. さらに, 後続関数に指定した関数で戻り値を返すと, 次の後続関数で指定した関数にその値を引数として渡すことが可能です (イメージとしては, Arrayインスタンスのreduceメソッドのような感じです).


getPromise(1000).done(function() {return 100;})
                .done(function(value) {return value * 100;});
                .always(function(value) {console.log(value);  //-> 10000});

上記のことをまとめたサンプルコードはこちらです.

ネイティブとAngularJSでの実装

JavaScriptの仕様であるECMAScript the 6th Editionでは, このような機構をネイティブで実装することが定義されており, 既にChromeなどでは実装されています.

また, AngularJSでも$qサービスというのが同じような機能を提供します.

メソッド名とかは少し異なってきますが, 根本的な使い方は同じです.

フォーム入力でAngularJSの基本を少し習得 2

昨日の続き.

昨日の記事に書いたように, フォームの入力を即座にモデルに反映するだけであれば, コントローラーが不要です.

しかし, もう少し複雑な処理, 制限文字数を超えたら指定のclass属性を付加する, placeholder属性に対応していないブラウザでplaceholderを実装する…

といった場合, コントローラーを作成することになります.

コントローラーの作成や, ディレクティブに関しては高度なことではないので, Webサイトやテキストを参考にしていただきたいですが,
重要な点は, 同じコントローラーを作成するといっても,

従来のようにDOMを取得してイベントリスナーを記述するというスタイルとは異なります.

従来であれば, 以下のようにDOMの操作がメインでした.

  • イベントリスナーを設定するDOMを取得
  • イベントリスナーで更新対象のDOMを取得
  • DOMに対して何かする

ところが, AngularJSのコントローラーでは,

  • ディレクティブによって, DOMの取得が必要がない
  • コントローラーでも, モデル (データ) の更新に集約できる
  • 双方向データバインディングにより, コントローラーでモデルを更新すれば, 即時にビューに反映される

この, イベント系のディレクティブを記述するのは, JavaScriptが世に出始めた頃のHTMLの属性としてイベントハンドラを記述することに似ています.

そして, AngularJSを象徴する機能である, 双方向データバインディングによって, ビューが更新 (ユーザーの入力など) されればモデルが更新され, モデルを更新 (コントローラー) すればビューが更新されるというなんとも素敵なことをやってのけてくれるのです.

 

フォーム入力でAngularJSの基本を少し習得 1

AngularJSの最初のサンプルコードでよくとりあげられる. テキストボックの入力値を即時にDOMに反映するコード.

<!DOCTYPE html>
<html ng-app>
<head>
<title>AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js" type="text/javascript"></script>
<style type="text/css">
 dl dd {color : #F00;}
</style>
<head>

<body>
 <dl>
 <dt><label>Input : <input type="text" ng-model="input" /></label></dt>
 <dd>{{ input }}</dd>
 </dl>
</body>
</html>

jQueryを利用したとしても, この程度の処理でもイベントリスナーのコード, もう少し抽象化すればコントローラーとなるコードが必要になってくる. つまり,

  1. DOM要素の取得
  2. イベントリスナーの記述

しかし, この程度の処理であれば, AngularJSのデータバインディングによって, コントローラーが不要 (JSのコードが不要になります. 実際には, コントローラーに相当する部分をAngularJSが隠蔽しているのでしょうが)

さらに, コントローラーが不要 (あるいは, 最小限) になることで, ビュー (テンプレート (HTML)) とモデル (データ) の関係に集約してアプリケーションを開発することが可能になります.

まだまだ, 習得を始めたばかりですが, なんだかいままでのJSを (良い意味で) くつがえしてしまうような予感を思わせるフレームワークですね.

ActionScript 3.0 ExternalInterface

ExternalInterfaceは, ActionScriptとJavaScriptを連携させるためのActionScript側から利用するインターフェースです.

まずは, ActionScript側の下準備.

import flash.external.ExternalInterface;
import flash.system.Security;

// JavaScriptからのアクセスを許可する
flash.system.Security.allowDomain('*');

また, swfを埋め込むに<object>の<param>に,

<param name="allowScriptAccess" value="always" />

を指定します.

これで下準備はOKです.

ActionScript側からJavaScriptの関数を呼び出す

JavaScript側で以下のような関数が定義されているとします.

var callFromActionScript = function(num) {
    return 2 * num;
};

これをActionScript側から呼び出すには,

// ExternalInterfaceが利用できるかのチェック
if (ExternalInterface.available) {
    var num:Number = ExternalInterface.call('callFromActionScript', 2);
    trace(num); // -> 4
}

重要なポイントは3つです.

  • ExternalInterfaceのクラスプロパティavailableをチェックする
  • ExternalInterfaceのクラスメソッドのcallメソッドでJavaScript側で定義している関数名を文字列で渡す
  • 引数の指定や戻り値をうけとることも可能

JavaScript側からActionScriptの関数を呼び出す

まずは, ActionScript側で呼び出したい関数の定義とコールバックの登録をしておきます.

function callFromJavaScript(num:Number) {
    return 2 * num;
}

if (ExternalInterface.available) {
    // 第1引数には, トリガーとなるJavaScript側から呼び出す際の任意の関数名
    // 第2引数には, 呼び出したいActionScriptの関数オブジェクト
    ExternalInterface.addCallback('callActionScript', callFromJavaScript);
}

そして, JavaScript側は以下のような感じになります.

// swfオブジェクトの取得
// 'swf'は <object>に付加しているid属性ととらえてください
var swf = document['swf'] ||
                  document['swf']  ||
                  window['swf'];

var num = swf.callActionScript(2);

console.log(num);  // -> 4

重要なポイントは3つです.

  • ExternalInterfaceのクラスプロパティavailableをチェックする
  • ExternalInterfaceのクラスメソッドのaddCallbackメソッドでJavaScript側から呼び出す際の関数名を指定し, 呼び出したいActionScriptの関数オブジェクトを指定する
  • 引数の指定や戻り値をうけとることも可能

ただし, JavaScript側からActionScriptの関数を呼び出す場合は注意点があります. それは, JavaScript側から呼び出しのタイミングによっては, Flash側の準備ができていなくてエラーが発生してしまうことがあります.

そこで, JavaScript側からActionScriptの関数を呼び出したい場合でも, まずは, JavaScriptの関数をActionScript側から呼び出すという方法を使います.

こんな感じです.

JavaScript

var callFromActionScript = function(isReady) {
    if (!isReady) {
        // Flash側の準備ができていないので何もしない
        return;
    }

    // Flash側の準備ができている
    var swf = document['swf'] ||
                     document['swf']  ||
                     window['swf'];

    // ActionScriptの関数を呼び出し
    var num = swf.callActionScript(2);

    console.log(num);  // -> 4
}

ActionScript

function callFromJavaScript(num:Number) {
    return 2 * num;
}

if (ExternalInterface.available) {
    ExternalInterface.addCallback('callActionScript', callFromJavaScript);
    ExternalInterface.call('callFromActionScript', true);  // -> 準備ができているのでtrueを渡す
}

ExternalInterface, とりわけ, JavaScript側からActionScriptの関数を呼び出すという方法は, HTML5 APIのポリフィルを作成するために欠かせないことなので, 難なく実装できるようにしておきます.