ラベル JavaScript HTML 5 の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript HTML 5 の投稿を表示しています。 すべての投稿を表示

2010年9月10日金曜日

データベースと表形式

データベースではデータを表にして利用する場合が多いと思います。
この場合に、表計算ソフトなどのように入力したデータを枠の長さに合わせてクリップ処理したいと思う人も多いと思います。

表で入出力をするには<table>タグの中に<input>タグを入れる方法もあるのですが、以下の二つに理由でどうも私の好みに合いません(笑)
  • 上下左右に余白ができて場所を取りすぎる
  • HTML文またはJavaScript命令が長くなって読みにくくなる
<table>タグにJavaScriptのsetAttribute()でcontenteditableのスイッチを付けて直接編集できないかを試してみましたが、以下のようになって改行処理とスクロール処理が思うようにできません。

元の表が以下のような物とします。
    • 一つの英単語が長いと、その長さに合わせて枠が横に長くなります。
    • 枠に入りきらない英文または和文を入力すると、文章が改行されて枠が縦に長くなります。

    以下のCSS文を試してみましたが、思うような結果を得られませんでした。

    試したCSSの一覧。(命令にwebkitとあるのはwebkit独自の仕様で、この記事を書いた時点ではHTML5に採用されていません)

    width: 35px;
    -webkit-column-width: 35px;
    text-overflow: clip;
    white-space: nowrap;
    word-wrap: break-word
    word-wrap: normal;
    overflow: hidden;
    overflow-x: hidden;
    overflow-y: hidden;
    overflow-y: scroll;
    -webkit-user-modify: read-write-plaintext-only;
    -webkit-user-modify: read-write;

    今のところ、<table>タグの中に<input>タグを入れるか、<div> タグで四角を縦横に並べるしか方法が思い付きません。

    Safariのtransaction

    「SafariのopenDatabase」の項目に引き続いて、以下のURLにあるSafariのデータベースの解説を見ていきたいと思います。

    Safari Client-Side Storage and Offline Applications Programming Guide: Introduction
    http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html

    Safariのデータベースで面倒なのが"Listing4-2 CreatingaSQLtable"にあるようなトランザクション処理の関数です。"Listing4-2 CreatingaSQLtable"にある最初のトランザクションは「SafariのopenDatabase」の項目にあるように他の関数に移し、すでにデータがある場合の処理は別にしたとして、データベースに初期データを入れる部分を抜き書きして、さらに初期データを一つだけにすると以下のようになります。

    function createTables()
    {
     db.transaction(
      function (transaction) {
       transaction.executeSql('insert into people 
        (name, shirt) VALUES ("Joe", "Green");',
        [], nullDataHandler, errorHandler);
      }
     );
    }
    

    関数内の最初の二行で、データベースのオブジェクト db のメソッドである transaction に関数を渡しています。渡した関数の引数がオブジェクトになっていて、そのオブジェクトのメソッドである executeSql でSQL命令を発効しています。
    これだけでもチョット面倒ですが、さらに面倒なことに executeSql メソッドの引数が四つあって、その内の二つが関数で、その二つの関数がくせ者です(苦笑)

    そのくせ者の前に注意しなくてはならないのが transaction です。 db.transaction とその後の transaction は別物です。 function(transaction) と transaction.executeSql の transaction は同じ変数で、その名前は transaction である必要はなく、 myTransaction などの他の変数名でも動作します。ここで同じ名前を使用しなくてはならない理由が私には思い付かず、サンプルコードとしては誤解を招きやすい解説だと思います。私は実際のコードを書く場合でも、後から調べることを考えて別名にしています。

    閑話休題、もとの「くせ者」の話に戻しましょう。この二つの関数は一方通行で、行ったまま戻ってきませんし、引数を渡すこともできません。呼び出される関数の引数は二つのオブジェクトと決まっています。マウスなどのイベントを処理するメソッド addEventListener に近い感じです。また、話しを脱線させると・・・個人的な経験を元にした推測にしか過ぎませんが、おそらくトランザクション処理をするために非同期コールを組み込む必要が出てこのような面倒なことをしているのではないか?と思っています。

    さて、 executeSql メソッドの第一引数と第二引数はC言語の printf 関数のようなイメージで使用しますが、変数が多くなると高い確率で間違います・・・つまり、私は間違いました(笑)また、初期化に限らず似たような処理を繰り返し実行することは良くあります。そこで私は「SafariのopenDatabase」の項目にあるSQL文の書き方にもう少し手を加えた方法を利用しています。一つの参考としてご紹介します。

    function createTables()
    {
     var nameShirt = {    
      "Joe": "Green",
      "Mark": "Blue",
      "Phil": "Orange",
      "jdoe": "Purple"
     }
     db.transaction(
      function (trans) {
       for(var index in nameShirt) {
        var mySQL = 'insert into people (name, shirt) VALUES ("' +
         index +
         '", "' +
         nameShirt[index] +
         '");';
        trans.executeSql(mySQL, [], nullDataHandler, errorHandler);
       }
      }
     );
    }
    

    上記の方法をさらに手を加えると、複数のテーブルを扱う時に便利になります。私は、object literalとArrayオブジェクトを組み合わせて、データベースの構造を宣言して、SQL文のテーブルの宣言部分を書いています。

    以上のように関数の中に関数が多重で入っており書きにくく、読みにくく、間違いやすい構文になっています。このために、"Listing 4-5 SQL insert query example"にあるような書き方は、面白いとは思いますが・・・関わりになるのは避けたいです(苦笑)

    SafariのopenDatabase

    数年前にSafariにSQLiteのデータベースがついて一時期話題になりましたが、その後も色々と改善が続いているようです。その解説が以下のURLにあります。

    Safari Client-Side Storage and Offline Applications Programming Guide: Introduction
    http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Introduction/Introduction.html

    ちなみに、この資料の日付は2010/01/20となっていますが、PDF版の作成日にはいくつかのバージョンがあるようです。

    この資料では"HTML 5 Offline Application Cache"や"Key-Value Storage"の解説が追加されています。

    データベースに関する解説も手が加えられています。"Relational Database Basics"はSQLの初心者を対象にデータベースの解説があり、"Using the JavaScript Database"でJavaScriptからデータベースを利用する方法が解説されています。"APPENDIX A: Database Example: A Simple Text Editor"にはサンプルコードを元に解説があります。

    解説中のサンプル コードはわかりやすい書き方になっていますが、まだ完成には時間が必要ではないかという気がします。例えば"Listing 4-1 Creating and opening a database"にあるデータベースを開く時の処理です。

    try {
     if (!window.openDatabase) {
      alert('not supported');
     } else {
      var shortName = 'mydatabase';
      var version = '1.0';
      var displayName = 'My Important Database';
      var maxSize = 65536; // in bytes
      var db = openDatabase(shortName, version, displayName, maxSize);
      // You should have a database instance in db.
     }
    } catch(e) {
     // Error handling code goes here.
     if (e == 2) {
      // Version number mismatch.
      alert("Invalid database version.");
     } else {
      alert("Unknown error "+e+".");
     }
     return;
    }
    alert("Database is: "+db);
    

    この書き方であれば、データベースの名前やバージョンなど後から調べる時に便利そうです。

    ただし、このコードは関数内に入っていません。「最初に実行されるコード」の項目の1stの方法と同じようにすればこのままでも動作します。しかし、最初に一度しか実行しないコードをdeleteせずにメモリ上に置いておくのは無駄です。関数の中に入れて最初に実行させ最後にdeleteするのが妥当だと思います。ただし、この時には変数宣言のvar dbは式の外に置いてグローバル変数としないと他の関数でトランザクション処理ができなくなります。

    また、サンプルにある// You should have a database instance in db.というコメントは具体的に何をしようとしているのかが分かりません。データベースと同時にtaransaction()メソッドで'CREATE TABLE'を使ったテーブルの処理も考えているのかもしれませんが、この解説ではテーブルの処理は別の関数で処理しています。

    古いサンプルコードと比べるとopenDatabaseでデータベースが開けない時の処理がなくなり、try-catchのcatchでエラー処理ができるようになったようです。エラー処理は"Handling Errors"と言う項目で解説されていますが、この通りには動いていないように思います。例えば上記のデータベースを開く時のcatchでの処理です。試しにversionを1.1や2.0にすると、アラートに"Invalid database version."が表示されずに、以下のようなエラーとなりました。

    "Unknown error Error: INVALID_STATE_ERR: DOM Exception 11."

    さらに、"APPENDIX A: Database Example: A Simple Text Editor"にあるエラー処理では2の代わりに変数"INVALID_STATE_ERR"を置いていますが未定義のエラーになります。

    エラーのメッセージやエラーコードの処理が紹介されています。ただ、後半部分が何を意味するのか分かりません。
    function errorHandler(transaction, error)
    {
     // error.message is a human-readable string.
     // error.code is a numeric error code
     alert('Oops.  Error was '+error.message+' (Code '+error.code+')');
    
     // Handle errors here
     var we_think_this_error_is_fatal = true;
     if (we_think_this_error_is_fatal) return true;
     return false;
    }
    

    先にも書いた'CREATE TABLE'を使ったテーブルの処理は"Listing 4-2 Creating a SQL table"に書かれています。しかし、'CREATE TABLE'のSQL文は長文になりやすく、読みにくいものです。以下のような書き方の方が後から調べやすいと思います。また、複数のテーブルを扱う場合には、この方法を発展させると便利になります。

    db.transaction(
     function (transaction) {
      var mySQL = 'CREATE TABLE people(' +
       'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
       'name TEXT NOT NULL DEFAULT "John Doe", ' +
       'shirt TEXT NOT NULL DEFAULT "Purple");';
      transaction.executeSql(mySQL, [], nullDataHandler, errorHandler);
     }
    );
    

    以上の内容をまとめると、今のところデータベースを開く関数は以下のような書き方が便利そうです。

    function initDB() {
     try {
      if (!window.openDatabase) {
       alert('not supported');
      } else {
       var shortName = 'mydatabase';
       var version = '1.0';
       var displayName = 'My Important Database';
       var maxSize = 65536; // in bytes
       db = openDatabase(shortName, version, displayName, maxSize);
       // You should have a database instance in db.
       db.transaction(
        function (transaction) {
         var mySQL = 'CREATE TABLE people(' +
          'id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, ' +
          'name TEXT NOT NULL DEFAULT "John Doe", ' +
          'shirt TEXT NOT NULL DEFAULT "Purple");';
         transaction.executeSql(mySQL, [], nullDataHandler, errorHandler);
        }
       );
      }
     } catch(e) {
      // Error handling code goes here.
      if (e == 2) {
       // Version number mismatch.
       alert("Invalid database version.");
      } else {
       alert("Unknown error "+e+".");
      }
      return;
     }
     // alert("Database is: "+db);
     delete initDB;
    }
    

    最初に実行

    最初に実行されるコード
    AppleのSafari 5.0.1とMac OS X版 Firefox 3.6.8でしか試していないのですが、JavaScriptで最初に実行されるコードを四つの方法で試してみました。

    試したコードは以下のような物です。
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
            "http://www.w3.org/TR/html4/loose.dtd">
    <html lang="en">
    <head>
     <meta http-equiv="content-type" content="text/html; charset=utf-8">
     <title>Untitled</title>
    <script type="text/javascript" language="javascript">
    <!--
    // 1st
    alert('without any function');
    
    // 2nd
    function init() {
     alert('called from body');
    }
    
    // 3rd
    window.onload = function() {
     alert('called from window.onload');
    }
    
    // 4th
    function loaded() {
     alert('called from addEventListener');
    }
    
    addEventListener('load', loaded, false);
    
    //-->
    </script>
    </head>
    <body onload='init()'>
    </body>
    </html>
    
    上記のコードをSafari 5.0.1で実行すると1st、4th、2ndの順で実行され、3rdは実行されません。

    また、上記のコードのbodyタグの引数onloadをなくすと、1st、3rd、4thの順で実行され、2ndは実行されません。

    Mac OS X版のFirefox 3.6.8で実行すると、1st、2nd、4thの順で実行され、3rdは実行されません。

    Safariと同様にbodyタグの引数onloadをなくすと、1st、3rd、4thの順で実行され、2ndは実行されません。

    3rdと4thは一般的に良く使用されるコードですが、1stと4thはAppleのエンジニアが書いたサンプルコードで見つけました。1stの方法はSafariに付属しているSQLiteのデータベースをJavaScriptから開く時に利用し、データベースのテーブルを開く時に4thの方法を利用していました。データベースを一番最初に開くという意味では1stの方法は便利かもしれませんが、メモリのフットプリント(占有領域)を小さくすると言う意味では以下の内容を考慮した方が良いと言えるでしょう。

    もったいない
    Appleの資料"Apple JavaScript Coding Guidelines"に、onload関連してメモリのフットプリント節約方法が書かれていました。

    http://developer.apple.com/safari/library/documentation/ScriptingAutomation/Conceptual/JSCodingGuide/Introduction/Introduction.html

    初期化コードなど一度しか実行しないコードは実行後にdeleteしてフットプリントを小さくすることを推薦しています。この資料では起動時の初期化コードを例として以下のようなコードを紹介しています。
    var foo = function()
    { 
     // code that makes this function work 
     delete foo; 
    } 
    window.addEventListener('load', foo, false);
    
    おそらくはiPhoneなどメモリを多く積んでいない装置を想定した話しでしょうが、不用意にメモリを浪費するのは問題を引き起こしやすいですし、「もったいない」です(笑)

    Safariのデータベース

    Mac OS XにはFileMakerなどの便利で高性能なデータベースがありますが、SafariのSQLiteのようにオマケでついてくるデータベースもあります。

    SafariのSQLiteにはFileMakerのようなGUIによるわかりやすい開発環境やデバッグ環境はなく、HTMLとJavaScriptをテキストエディタで書かなくてはなりません。おそらくは、開発途上にあるデータベースと言っても良いと思います。

    Safariがデータベースとして使用しているSQLiteには以下のような利点があると思います。

    • 1.オープンソースである
    • 2.仕事として利用しても無料である
    • 3.デーモンを走らせる必要がない
    • 4.起動 終了が早い

    1.のおかげでSQLiteのデータベース ファイルを参照し、編集できるツールがいくつか公開されています。2.は個人使用だけでなく、仕事として利用しても無料であると言う意味です。3.はデータベースを利用する度に逐一デーモンを起動や終了しなくてもよいので便利です。デーモンを起動したままにするのも一案ですが、そのためにわずかでもシステム起動時間が遅くなり、メモリなどを無駄にするのは私には納得できません。ただし、トランザクション処理のために呼び出しが非同期コールのようになり、初めての人には分かりにくく使いにくかもしれません。4.はSafariの起動も速いことも相まって、チョットしたメモや参照に便利です。

    簡単な動作検証として小遣い帳を作り半年ほど利用してみました。小遣い帳のような小さなデータベースであれば十分に実用できます。ただし、SafariはWebブラウザのため、検索できても置換できません。日記(Blog)などの文章を入力するデータベースではJavaScriptやJavaなどによる検索置換のプログラムが別途に必要です。

    2010年7月6日火曜日

    オブジェクトのメンバを見る

    色々とJavaScriptで書いていると、オブジェクトのメンバを見たくなることがあります。他のことをやっていて偶然気付きました。他にも紹介されていると思いますが、メモしておきます。

    for(var i in this) {
     alert(i);
    }
    

    ただし、注意しないと大量のメンバが表示されて、OKボタンを押すだけで飽きてしまいます(笑)
    また、Mac OS X上のSafari 5でしか動作確認していません。ご注意ください。

    もう一つの表<table>の描き方

    表の描画はinsertRowやinsertCellを使わずに以下のようにもできます。Model、View、Controllerを考えるともう少し工夫が必要ですが、こんな事もできるという意味で紹介します。ただし、Mac OS X上のSafari 5とFirefox 3.6.3でしか動作確認していません。ご注意ください。

    createElementはJavaScriptでHTMLを生成する時に良く使用される方法ですが、当然<table>でも利用できます。また、この方法であれば<th>を直接生成できます。この例では先頭行で<th>を使用してその後に<td>を置いています。

    また、ここでは表の定義に配列を使用しています。for( i in fooArray )を使って行を生成していますので、testTable関数に行数と表のタイトルとセル幅を与えるだけで作表します。CSSの設定はclassName、その他の各行やセルの設定はsetAttributeが使えます。たとえば、setAttribute('contenteditable', true)とすれば、表の中身を書き換えられるようになります。さらに、addEventListenerでイベント駆動もできます。

    window.onload = function(){
     argTable = {
      numberRow: 5,
      myMenue: {
       "key": 35,
       "input date": 100,
       "item name": 200,
       "item number": 100
      }
     }
     myTable = new testTable(argTable);
     delete argTable
    }
    
    function testTable(arg) {
     var myTable = document.createElement('table');
     myTable.setAttribute('border', '1');
     var myTr = document.createElement('tr');
     for (var index in arg.myMenue) {
      var myTh = document.createElement('th');
      myTh.innerHTML = index;
      myTh.setAttribute('width', arg.myMenue[index]);
      myTr.appendChild(myTh);
     }
     myTable.appendChild(myTr);
    
     for (var i = 0 ; i < arg.numberRow ; ++i) {
      var myTr = document.createElement('tr');
      for (var index in arg.myMenue) {
       var myTd = document.createElement('td');
       myTd.innerHTML = 'test';
       myTd.setAttribute('width', arg.myMenue[index]);
       myTr.appendChild(myTd);
      }
      myTable.appendChild(myTr);
     }
     document.body.appendChild(myTable);
    }
    

    insertRowとinsertCell

    JavaScriptで表を扱う時の関数insertRowやinsertCellですが、よく紹介されている方法とは少々異なる使い方のメモです。ただし、Mac OS X上のSafari 5とFirefox 3.6.3でしか動作確認していません。ご注意ください。

    insertRowやinsertCellはJavaScriptで表を描画する時に利用される命令の一つで、insertRowにより表に一行追加でき、insertCellではセルを一つ追加できます。例えばinsertCell(n)としてnに数値を渡すと、nの位置にセルを挿入します。このnをinsertCell()またはinsertCell(-1)とすると行の一番最後にセルを追加します。サンプルコードなどで表を描画する時にループ文を利用しますが、この時に逐一nの値を与える必要はなく、insertCell(-1)とするだけで描画できます。これはinsertRow()でも同じです。

    さらに、insertCell()では<td>タグが生成されます。しかし、先頭項目などを表す<th>タグを直接生成する命令は見あたりません。代案としてinsertCell()で<td>タグを生成して、その中身を設定する時にouterHTMLを使用することで<th>タグを間接的に生成できます。

    上記の方法をまとめて例を書くと以下のようになります。

    testData = new Array(10, 12, 15, 43);
    --- some codes ---
    for (var index in testData) {
     var myTh = myTr.insertCell(-1);
     myTh.outerHTML = "<th>" + index + "</th>";
     --- some codes ---
    }
    delete testData;
    

    ただし、Safari 5ではinsertCell(-1)としないとindexの順が逆になる場合があるようです。

    2010年6月9日水曜日

    Safari 5の互換性

    データベースは変更せずに動きましたが、以下のようなタグで表示されるテキスト フィールドの幅が変わりました。
    < input id="xxx" size="12" type="text" />

    Safari 5のデータベース

    Safari 5をダウンロードしてデータベースを試した結果。少なくとも、私がSafari 4を使って試しに書いたデータベースを開く事が出来ました。

    また、[環境設定]の[セキュリティ]で[データベースを表示]ボタンを押して表示されるデータベースのリストが改善されました。今までは開いたデータベースのみを表示したのですが、Safari 5では開かなくても表示されるようになりました。Safari 4ではこれが不便でした。

    [開発]メニューの[エラーコンソールを表示]で表示されるウィンドウで、データベースを表示する機能は今までと同様です。データベースを開いた状態で、上欄から[ストレージ]を選択して、左欄から[データベース]を選択すると、データベース名やテーブル名が表示され、ウィンドウ内にデータが表示されます。

    ただし、[ストレージ]に便利な機能が追加されていました。開いているページのCOOKIEの詳細を表示してくれます。これが標準で付いているのは便利そうです。

    2010年6月6日日曜日

    Apple - HTML5

    私が2007年の資料に手こずっている間に、Appleは現在のHTML 5などの技術を使ったデモを発表しています。

    Apple - HTML5
    http://www.apple.com/html5/

    Appleお得意の見た目のきれいさと、そのための技術には唖然とします。

    2010年6月5日土曜日

    8. 後日談

    <最初にもどる> <pre>
    謎などと大袈裟な事を書きましたが、どうしても定義が見つからなかった構文があったのです。以下のオブジェクトのメンバ宣言です。何処かにあるはずなのですが、見つかりません。

    オブジェクトのメンバ宣言をするときに以下の例ようにしてSettingとGettingを簡単に宣言し、利用方法も簡単になります。
    function MyObject() {
    }
    
    MyObject.prototype = {
     get id() {
      return this._id;
     },
     set id(x) {
      this._id = x;
     },
    }
    
    function myCode() {
     myObject = new MyObject();
     myObject.id = 3;
     alert('id is ' + myObject.id);
    }
    
    上の式でメンバ変数を宣言できますし、WebKitのサンプルコードsticky-notesのようにメソッドも宣言できます。

    この便利な点はメンバ変数とそのset getが羅列するだけで簡単に宣言できる点です。また、下のtest()のように簡単にメンバ変数への代入と呼び出しができます。ただし、上の関数を使えるのは一度だけのようです。同じオブジェクトに追加宣言はできず、追加できるのはメソッドのMyObject.prototype.methodNameだけのようです。

    ストレステストはしていませんが、上記のサンプルコード程度ならFirfox 3.6.3でも動作しました。

    ECMAScriptにおける「オブジェクトリテラル」と言う書式では、配列を拡張してオブジェクトを宣言できます。
    myObject = {
     myVar: -- numbers,
     myVar: '-- words',
     myFunc: function() {
      -- definition
     }
    }
    
    似てはいるのですがどうも違うように思います。
    <最初にもどる> <pre>

    7. 恐怖の仮面this

    <pre> <next>
    myHandlerに渡される引数のeはイベントが発生した位置などを含む情報が入っています。しかし、サンプルコード2のaddEventListenerに渡されるthis.myHandlerには引数がありません。さらに、試しに、サンプルコード1のfunction(e) { self.myHandler(e) を function() { self.myHandler()とするとエラーになります。これでも良いような気もするのですが、どうもSafariの実装が
    怪しいような気も・・・(笑)

    いずれにしても、最初の二行は同じですし、動作も同じです。その後も変数が一カ所違うだけだと思って見逃すと・・・
    探偵失格です(大笑)

    それでは事件解明にかかりましょう。

    犯人はthisなのです。二つのサンプルにあるthisは別人…いや、別のオブジェクトを指しています。

    addEventListenerの使い方として良く紹介されているのですが、サンプルコード2のthisはHTMLの<div>を指しています。JavaScriptの仕様では、イベント駆動の元になるオブジェクトがJavaScriptのオブジェクトではなく、HTMLの<div>であると考えているようです。

    ところがサンプルコード1のthisではMyObjectから作られたmyRectオブジェクトを指しています。たとえば、サンプルコード1では以下のようになっています。
    alert(this.myObject.offsetLeft);
    
    これはthis.myObjectで表す四角の左端の座標offsetLeftを表しています。ではthis.myObjectとは?と言うと、MyObject()内にある以下のコードで以下のように表しています。
    this.myObject = myObject;
    
    そう、例の問題のコードです。この左辺のthisがmyRectになるわけです。だからこそサンプルコード1では以下の式でmyRectのプロパティが呼び出せたのです。逆に、サンプルコード2ではHTMLの<div>にはthisというプロパティがないので「undefined」になってしまったわけです。
    alert(this.myVar);
    
    サンプルコード2ではHTMLの<table>などHTMLの入れ子構造(ネスト構造)になっているものを扱うには分かりやすく便利そうです。しかし、上記のように親となった関数を指したくなる場合がある事も事実です。

    実際にWebKitのサンプルコードsticky-notesでは、addEventListenerで呼び出されたハンドラから、さらにaddEventListenerでハンドラを登録してそれぞれで変数を共有しています。グローバル変数という手もありますが、オブジェクトが繰り返し生成される(四角が作られる)たびにグローバル変数を生成していては後始末が大変です。

    個人的には明示的にどちらを選択するのかを引数を追加するか、関数名で明示したほうが分かりやすいように思うのですが・・・。

    閑話休題、一件落着したと思ったのですが、まだ謎が残っていました。それは・・・つづく(笑)
    <pre> <next>

    6. addEventListenerの浮気

    <pre> <next>
    二つのサンプルコードの違いは以下の通りです。二つのウィンドウにそれぞれのサンプルコードを左右に並べて表示してじっくりと比較してみてください。
    サンプルコード1
    myObject.addEventListener('mousedown', function(e) { self.myHandler(e) }, false);
    
    サンプルコード2
    myObject.addEventListener('mousedown', this.myHandler, false);
    
    サンプルコード2が一般的によく紹介されている書き方です。this.myHandlerは生成されたオブジェクトのmyHandler関数です。サンプルコード1ではfunction(e)でハンドラを定義しています。分かりやすいように改行を入れると以下のようになります。
      myObject.addEventListener(
        'mousedown', 
        function(e) { 
          self.myHandler(e) 
        }, 
        false
      );
    
    ここは関数定義の中ですので、ここでthisを使えば新規の関数定義となります。そこでコンストラクタ内のthisをselfに入れてself.myHandler(e)とすることでオブジェクト内のmyHandlerメソッドを指しています。

    なぜこのような面倒な事をしているのでしょう?また、この後のthis.myObject = myObject;という式も妙です。さらに、もう一つ気になるのは謎の引数eの存在です。実はthisがこの一件に強く絡んでいるのですが・・・三文探偵小説なら事件はいよいよ混迷を深めてきたというところでしょうか(苦笑)
    <pre> <next>

    5. サンプル コード2

    <pre> <next>
    サンプルコード1と同様に、このコードもhtmlのファイルにしてSafariで実行してみてください。

    実行すると灰色の四角が表示され、表示された灰色の四角をクリックするとクリックした位置のX座標と四角の左端の座標、そして謎の「undefined」がアラートボックスで表示されます。
    <html>
    <head>
    <style>
    .myCSS {
      background-color: rgb(240, 240, 240);
      height: 250px;
      width: 200px;
      position: absolute;
      left: 200px;
      top: 200px;
    }
    </style>
    <script>
    function MyObject() {
     var self = this;
     var myObject = document.createElement('div');
     myObject.className = 'myCSS';
     myObject.addEventListener('mousedown', this.myHandler, false);
     this.myObject = myObject;
     document.body.appendChild(myObject);
    }
    
    MyObject.prototype.myHandler = function(e) {
     alert(e.clientX);
     alert(this.offsetLeft);
     this.style.left = '100px';
    }
    
    function loaded() {
     myRect = new MyObject;
    }
    </script>
    </head>
    <body onload="loaded()">
    </body>
    </html>
    
    <pre> <next>

    4. addEventListenerとthis

    <pre> <next>
    謎は二つです。

      1.addEventListenerに書かれた謎のfunction()

      2.myHandler関数内のthisとはだれか?

    この二つが「奇妙な愛憎劇」のように絡んでいます(笑)

    addEventListenerは先にも書きましたように、マウスのボタンが押された時にシステムから渡されるイベントを取り込んで、そのイベントを扱う(ハンドルする)関数としてmyHandler()関数を登録しています。このmyHandler()関数はすぐには実行されずイベントが発生した時に(非同期に)実行されます・・・と言う事は他のサイトにも書かれていて、言わずもがなの話しです。

    構文は以下のようになります。

      addEventListener(イベント名, ハンドラ関数名, falseまたはtrue)

    イベント名はclickやkeyup、mousedownなどイベント駆動型のプログラミングでおなじみのもので、指定したイベントが発生するとハンドラ関数が呼び出されます。最後の引数はイベントが呼び出されるタイミング(登りか下りか)を表します。

    論より証拠です。実際にサンプルコード1を書き換えて、この構文に合わせてaddEventListenerを呼び出すとどのようになるか「現場検証」してみましょう(笑)
    <pre> <next>

    3. コードの流れ

    <pre> <next>
    コードの流れは以下のようになります。

    1.ファイルの読み込み
      最初に<body onload="loaded()">からloaded()関数を
      経由してMyObject()のコンストラクタが呼び出されます。

    2.コンストラクタ
      コンストラクタではcreateElement('div')で<div>タグが
      生成され、classNameで.myCSSのスタイルが指定され
      ます。

    3.スタイル
      .myCSのスタイルでは四角の色、高さと幅などが指定され
      ています。

    4.イベント登録
      addEventListenerでマウスのボタンが押された時のイベ
      ントをフックしてハンドラとしてmyHandler()関数を非同
      期で呼び出します。

    5.メンバ登録
      オブジェクト自身を自分のメンバとして登録します。

    6.オブジェクト生成
      最後にappendChildでオブジェクトを生成します。

    以上が起動時の動作ですが、その後に四角の上でマウスのボタンが押されると以下のようになります。

    7.ハンドラ関数myHandlerが起動します。

    8.座標などを表示します。

    さて、最初からもう少し詳しく見ていきましょう。オリジナルsticky-notesでは少々違和感のある起動コードが書かれていますが、1では分かりやすくするために良く知られた方法にしています。

    2.は6とともに、<div>タグをcreateElement()で生成し、スタイル シートや各種の属性など宣言してappendChild()で生成しています。この方法はJavaScriptから<div>や<table>などのタグをJavaScriptで生成する方法としてあちらこちらのサイトで紹介されてますので、特に問題はないと思います。

    3.は画面の好きなところに四角を描く方法です。種明かしされれば「ナンダ」という気が…(笑)ただし、leftとtopは絶対位置で指定しますので、position: absolute;で絶対位置を指定し忘れると四角が左上の隅に張り付いてしまいます。もちろん、この失敗をしたので気付きました(笑)

    4.これが難問です。5と7、8とも絡んで理解を阻んでいます。・・・と言う事で項目をあらためましょう。iptで生成する方法としてあちらこちらのサイトで紹介されてますので、特に問題はないと思います。

    3.は画面の好きなところに四角を描く方法です。種明かしされれば「ナンダ」という気が…(笑)ただし、leftとtopは絶対位置で指定しますので、position: absolute;で絶対位置を指定し忘れると四角が左上の隅に張り付いてしまいます。もちろん、この失敗をしたので気付きました(笑)

    4.これが難問です。5と7、8とも絡んで理解を阻んでいます。・・・と言う事で項目をあらためましょう。
    <pre> <next>

    2. サンプル コード1

    <pre> <next>
    まずは、以下のコードを見てください。このコードはファイルにしてSafariでローカルに実行できます。

    実行すると灰色の四角が表示され、表示された灰色の四角をクリックするとクリックした位置のX座標と四角の左端の座標、そしてmyRectオブジェクトのメンバであるmyVarの"this is myVar"がアラートボックスに表示されます。
    <html>
    <head>
    <style>
    .myCSS {
      background-color: rgb(240, 240, 240);
      height: 250px;
      width: 200px;
      position: absolute;
      left: 200px;
      top: 200px;
    }
    </style>
    <script>
    function MyObject() {
     var self = this;
     this.myVar = "this is myVar";
     var myObject = document.createElement('div');
     myObject.className = 'myCSS';
     myObject.addEventListener('mousedown', function(e) { self.myHandler(e) }, false);
     this.myObject = myObject;
     document.body.appendChild(myObject);
    }
    
    MyObject.prototype.myHandler = function(e) {
     alert(e.clientX);
      alert(this.myObject.offsetLeft);
      alert(this.myVar);
    }
    
    function loaded() {
     myRect = new MyObject;
    }
    </script>
    </head>
    <body onload="loaded()">
    </body>
    </html>
    
    <pre> <next>

    1. SafariのJavaScript

    AppleがSafariに(実験的に?)搭載しているHTML 5の仕様にはデータベースなど興味深いものがたくさんあります。そのデータベース機能などを利用したサンプルがWebKitのサイトに公開されています。

    http://webkit.org/demos/

    サンプルはこの下の各ディレクトリにまとめてありますので、Safariでソースコードをもとにファイルを一つずつ手元に保存すると、ローカルでも実行できます。wgetなどのツールをご存じの方はまとめてダウンロードすると良いでしょう。ちなみにこの項目を書いている2010/06/01の時点では以下の四つのデモが入っています。
    • calendar
    • editingToolbar
    • sticky-notes
    • transitions-and-transforms
    また、デモの一つeditingToolbarは、Mac OS Xに標準で付属しているDeveloper向けキットにもあります。深いディレクトリにあるので、"FancyEditingToolbar"で検索するとよいでしょう。

    このサンプルの中で一番古いsticky-notesは2007年10月19日公開されています。このsticky-notesのHTMLファイルを読み込むと、画面に黄色い四角が表示されています。この四角には文字を書く事ができます。HTMLで画面に四角を書くとなると、とりあえず<table>タグを使う事を考えてしまう私には驚きです(苦笑)

    しかも、この四角をクリック ドラッグするとウィンドウ内の好きなところに移動します。その移動は昔のMacintoshの用に枠をアニメーション表示して、ボタンを放したところに表れるというものではなく、パワフルなリアルタイム描画です・・・と書けば年寄り扱いされますね(笑)

    さらに、「New Note」ボタンを押すと黄色い四角が追加表示されます。黄色い四角の上にマウス カーソルをおくと左上にDashboardと同じボタンが表示され、クリックすると四角がアニメーションで閉じます。アニメーションの動きが良くわからない方はシフト キーを押してクリックしてみてください。Dashboardと同じゆっくりとしたアニメーションを楽しめます。このコード自体はDashboardのStickiesの簡易版と考えれば良さそうですが、これを発展させればWebブラウザ内にウィンドウ システムが出来ます。

    このコードのデータベース部分に関しては英語や日本語で紹介しているサイトがありますが、他の部分もなかなか難しい事をしています。他の部分に関してインターネットを調べてみたのですが、解説している資料が見つかりませんでした。やむなく、コードを書いて調べた結果が以下の項目です。きっと何処かに仕様書や解説があると思うのですが、枯れ木も山の賑わい と思い紹介します。
    データベース部分は、関してはSafariとSQLiteをキーワードにしてインターネットを検索すれば、さまざまな解説が日本語と英語で出てきますので、そちらを参照ください。これはこれで面倒で色々とある事は確かなのですが、今回は話しが長くなるのでパス!だれも年寄りの長話は聞きたくない、いや読みたくないでしょう(笑)

    なお、実験した環境はMac OS X 10.5.8、Safari 4.0.4 (5531.22.7)とBBEdit 9.5(2740)のPreviewですが、Firefoxでも動く事があります。
    <next>