113

wemaの魅力に迫る〜JavaScriptとWikiの融合 ふしはらかん

皆さん,はじめまして.ふしはらかんです.体重以外はどこにでもいる平凡なプログラマなのですが,昨年の4月に「変なもの」を作り始めたことがきっかけで「Wikiばな」に参加し,それが縁で今年の「Wiki博」での発表,この連載での1記事を担当させて頂くことになりました.今回は一見関係なさそうに見えるWikiとJavaScriptの強力なタッグについてご紹介します.

突然ですが,JavaScriptは好きですか?プログラマにとってはブラウザ間の互換性の問題で,ユーザにとっては突然出てくるアラートやいわゆる「右クリック禁止処理」などの悪質な使用方法によって必ずしも良いイメージを持たれているとは言い難かったJavaScriptですが,最近復権の兆しがあります.GoogleMapを象徴とするGoogleの最近の新サービスがFlashやJavaAppletではなく,JavaScriptを駆使して動作していること,またそこで使われている「Ajax」技術等を使った便利なサービスがどんどん登場してきたことにより「JavaScriptって,ひょっとして凄い便利なの?」というポジティヴなイメージが広がりを見せ始めています.そして,その波はWikiにも確実にやってきています.

今回は拙作wema(後述)を中心に紹介させて頂くことになりますが,現在wema以外にもJavaScriptを使って従来のWikiとは一味違ったインターフェース,使い勝手を実現しているWikiが幾つも登場し始めています.ここでは少しづつですがそれらのWikiを紹介していきます.

MirrorMan(注1)はmala氏が開発しているWikiです.Wiki記法からHTMLへの変換にもJavaScriptを使うなど,徹底してJavaScriptを用いてクライアントサイドで処理するコンセプトで作られています.多彩な表示モードやRSS配信といったスタンダードな機能だけでなく,マウスをポイントするだけで自動スクロールする機能や,行単位で部分的に編集する機能,リアルタイムで更新されるチャット風の表示欄など,インターフェース面を中心に意欲的な機能が多数盛り込まれています.

mala氏はAjax(後述)の名前が広まる以前からJavaScriptによる非同期通信処理を研究されており,氏のブログ(注2)ではJavaScriptの実用的・実験的な活用例が多数紹介されています.MirrorManは残念なことに現時点でソース公開されていませんが,運用サイトによるといづれ公開する用意があるようですので早期の公開が期待されるところです.

注1)http://ma.la/mirrorman/wiki.cgi/FrontPage 注2)http://la.ma.la/blog/

既に本連載の第2回でyomoyomo氏が触れられていますが,KamiWikiは「紙」シリーズやNOTAなどの作者である洛西一周氏がTiddlyWikiをベースに開発しているWikiです. TiddlyWikiは従来のWikiがページ単位で情報を記録・表示していたのに対し,JavaScriptを用いて同じ画面に複数のパネルのように同時に表示する斬新なインターフェースを採用しています.

KamiWikiはTiddlyWikiの特長を引き継ぎつつ,ページ変更の履歴を管理してAutoLinkが消滅することを防いだり,リンク元の表示,画像の埋め込み,メニューの日本語ローカライズ,UTF8化など更に便利に使う為の機能が多数追加されています.

図[mirrorman.bmp] 図[kamiwiki.bmp]

『wemaは、ウェブページ上に付箋を貼り付けていく変わったソフトウエアです。付箋の背景色を選んだり、付箋同士に線を引くことも出来ます。日常の簡単なメモボードや、グループの掲示板、マインドマップのような使い方も出来たりするなど、自由度の高さがあります。』(wema配布サイトより引用)

wemaは私が2004年4月から開発を始めたWikiEngineで、最新のバージョンは2.0.1です。wemaは従来のWikiとは相当見た目が異なるため「Wikiじゃ無い気がする」と言われることが多いのですが、私の考えとしては以下の機能を持っていれば「広義のWiki」と呼んでいいと思います。

  • web上から内容を編集することが出来る
  • 何らかの形でリンクを簡単に貼る事が出来る
  • 内容の検索機能を持つ

wemaも勿論、上記の機能を持っています。

以下ではwemaの特徴的な機能と、それを実現しているJavaScriptの使用法を説明していきたいと思います。

一般的なWikiでは1画面(1ページ)の文章を編集する場合、画面全体の編集を行います。一部のWiki(WalWikiなど)では段落ごとに文章を編集することの出来るWikiもありますが、wemaの場合、段落1つ1つが付箋の形で独立していて、自由に動かすことが出来ると思ってもらうとイメージしやすいと思います(図1参照)。この付箋はページ内の好きな場所に移動することが出来、特に保存ボタンを押さなくても移動した場所は記憶されていて、ブラウザでページをリロード(再読み込み)しても位置が再現されます(注3)。

それでは、この付箋がHTMLではどのように表現されてるか見てみましょう。

  • ソースここから----------------------------------------------------
   <div id="1125277850632" class="sticky" style="left: 183px; top: 141px; z-index: 1;"
        onmousedown="clickElement='1125277850632';mdown(event); return false"
        onmousemove="settingLine(event)" onmouseup="clickItem='sticky'">
   <table><tr><td style="border: #faa 2px solid; filter:alpha(opacity=70); -moz-opacity:0.7;">
       <a href="javascript:void(0)" onclick="showMenu(event,'1125277850632')"><img src="./image/menu.png" id="mi1125277850632" alt="■" /></a>

<p>このように、付箋の形でメモを残せます<br/></p>

   </td></tr></table>
   </div>
  • ソースここまで----------------------------------------------------

wemaの付箋自体はただのDIV要素です。「sticky」クラスのDIV要素にはCSSで「position:absolute」が指定されていて、画面内の自由な位置に表示できるようになっています。画面内のどの座標に表示するかstyle属性でleft, topを指定しています。

wemaのHTMLを見てみると分かりますが、DIV要素の中にTABLE要素があります。これはFireFoxをはじめとするMozilla系のブラウザでは、画面からはみ出した場所(スクロールしないと表示されない場所)に付箋を移動すると、画面からはみ出した付箋の部分が潰れて表示されてしまうことを避けるためのテクニックです。

TABLE要素(の中のTD要素)ではstyle属性で付箋の概観を定義しています。「border: #faa 2px solid」はパステルカラー風の赤色で外枠を囲うという設定です。後半が少し特殊ですが「filter:alpha(opacity=70); -moz-opacity:0.7;」は付箋を(背景が)透けて見えるようにするための設定です。前半の「alpha〜」はIE用の設定で、後半の「-moz-〜」はMozilla系ブラウザ用の設定になります。付箋を透過させると付箋同士が重なったときの視認性が良くなるので好んで使用しています(注4)。

それでは、どうやって付箋を動かしているのかを見ていきましょう。まず、付箋を動かすために付箋上でマウスをクリック(正確には押し下げる)します。すると、付箋のDIV要素に定義している「onmousedown="clickElement='1125277850632';mdown(event); return false"」により

1. clickElementという変数にIDを代入 2. mdownという関数を呼び出す

という順番で処理が実行されます。一目瞭然ですが、ここではmdwon関数の中の処理が重要になってきます。mdown関数は、他の付箋の移動(ドラッグ)処理のための関数とともに、drag.jsというファイルに定義されています(注5)。

  • ソースここから----------------------------------------------------

function mdown(e) {

 if(navigator.userAgent.indexOf('Gecko')!=-1)   //n6,m1!)
   if(e.currentTarget.className != 'sticky') return
   else clickElement = e.currentTarget.id
 var selLay = getLayOj(clickElement)
 if (selLay){
     offsetX = getMouseX(e) - getLEFT(selLay.id)
     offsetY = getMouseY(e) - getTOP(selLay.id)
 }
 return false

}

  • ソースここまで----------------------------------------------------

最初の3行でMozilla系のブラウザ用にclickElement変数を再設定しています。 以降の処理を簡単に書くと

1. clickElement変数に入っているIDから付箋のDIV要素を取得する 2. 要素が取得できれば(IDが正しいものであれば) 3. マウスポインタの座標と付箋の座標からオフセットを割り出して変数に記録しておく

ということをやっています。2のようにまだるっこしいことをやっているのは、取得できるマウスのX,Y座標が付箋のDIV要素の左上の座標からの相対的な値になっているからです。

付箋を動かす場合、こうして付箋上でマウスのボタンを押し下げたままマウスを動かします。すると、今度は「onmousemove="settingLine(event)"」が実行されるのですが、これは線を引く機能に関連したものなので今回は説明を省略します。実はこのとき、drag.jsのmmove関数も実行されます。

  • ソースここから----------------------------------------------------

function mmove(e) {

 if(!window.clickElement) return
 if (getLayOj(clickElement)) {
    movetoX = getMouseX(e) - offsetX
    movetoY = getMouseY(e) - offsetY
    var oj=(!!document.layers)?getLayOj(clickElement)
                              :getLayOj(clickElement).style
   moveLAYOJ(oj,movetoX,movetoY)
   sticky[clickElement].x = movetoX
   sticky[clickElement].y = movetoY
   moveLine(clickElement)
   return false
 }

}

  • ソースここまで----------------------------------------------------

まず、clickElementに有効な値が入っているか(マウスボタンを押し下げ中か)を判定しています。その後、mdown関数で記録していたマウスポインタの座標と、現在のそれを比較して移動量を割り出し、moveLAYOJ関数で付箋のDIV要素を移動しています。 moveLine関数は、付箋に繋がっている線の再描画を行います(線の描画については後述します)。

何故この関数が実行されるかというと、drag.jsの前半に以下のような記述があるからです。

  • ソースここから----------------------------------------------------

document.onmousemove = mmove document.onmouseup = mup if(navigator.userAgent.indexOf('Gecko')!=-1) //m1,n6!)

 document.onmousedown = mdown              <--(1)

if(document.layers){ //n4!)

 document.captureEvents(Event.MOUSEMOVE)         <--(2)
 document.captureEvents(Event.MOUSEUP)          <--(2)

}

  • ソースここまで----------------------------------------------------

documentオブジェクトのイベントハンドラに関数を追加することで、Web画面上でマウスポインタを動かしたときは必ずmmove関数が呼ばれます。同様にマウスボタンを押し上げた時にはmup関数が呼ばれるようになっています。 (1)はMozilla系ブラウザのために(付箋のDIVタグ内で指定したのとは別に)マウスボタン押し下げ時のイベント処理を追加しています。 (2)はNetscape4.X系のための処理です。この辺りの環境依存の対応については後述のコラムを参考にしてください。

この一連の処理によって、ユーザがwemaの付箋をマウスでドラッグすると、付箋はリアルタイムでマウスポインタを追ってきます。また、付箋に繋がる線も追随して変形します。

付箋を動かし終わったらマウスボタンを離します。この時、上述のdrag.js内で指定されているようにmup関数が実行されます。

  • ソースここから----------------------------------------------------

function mup(e) {

 if(lMode) saveLine();
 if(!window.clickElement) return false
 if (getLayOj(clickElement)) {
   zindexLAYOJ((!!document.layers)?getLayOj(clickElement)
                  :getLayOj(clickElement).style,zcount++)
   autoSetPosition()
   clickElement=null
 }

}

  • ソースここまで----------------------------------------------------

前半部分はmmoveでの処理と殆ど同じです。clickElementに入っているIDが有効だった場合、

1. z-indexを変更して動かした付箋を一番前に表示 2. 付箋の位置を保存 3. clickElement変数をクリア

の処理が順に行われます。z-indexは要素同士が重なったときにどちらを前面に表示するか決めるために必要な値で、大きいほうが前面に表示されます。 3の処理をすることでmmove変数内で付箋をマウスポインタに追随して動かす処理を行わなくなるります。この処理がないとマウスボタンを離しても延々付箋がマウスポインタについてくるという鬱陶しい状態になります。

さて、ここまで付箋の仕組みを説明してきましたが、実はこのテクニックは割りと一般的で、付箋という使い方に限らず色々なWebアプリケーションで利用されています。wemaの独自性(注6)といえるのはむしろ、これから説明する線と矢印かもしれません。

図2を参照していただくと分かるように、wemaでは付箋同士を線で結ぶことが出来、線の向きを示せるように矢印をつけることが出来ます。また、線に見出しをつけることも出来ます。これらは全て、画像を使わずCSSとJavaScriptを組み合わせることで実現しています。

まず、線の部分にあたるHTMLを見てみることにしましょう。しかし、wemaの出力するHTMLを見ても線を描画しているHTMLは見当たりません。wema.jsに定義されているdrawLineオブジェクトをロードしている記述が見つかる程度です。

  • ソースここから----------------------------------------------------

line_data['112533707628071-112533708628076'] = new drawLine('112533707628071','112533708628076',,'#000','1','solid','0','1')

  • ソースここまで----------------------------------------------------

実は、このdrawLineオブジェクトのロード時に線自体の描画処理も行われているのです。

  • ソースここから----------------------------------------------------

function drawLine(src, target, label, color, width, lstyle, asflg, aeflg){

 id = src + '-' + target
 if(line[src]){
   line[src].push(target)
 }else{
   line[src] = new Array()
   line[src].push(target)
 }
 if(rline[target]){
   rline[target].push(src)
 }else{
   rline[target] = new Array()
   rline[target].push(src)
 }
 this.src = src
 this.target = src
 this.label = label
 this.color = color
 this.width = width
 this.lstyle = lstyle
 this.style = color + ' ' + width + 'px ' + lstyle
 this.asflag = asflg
 this.aeflag = aeflg
 ml = new makeLine(src, target)
 if(ml.left == undefined) return "";
 style = getLineStyle(ml.mode, this.style)  
 document.write('<div id="' + id + '" ' +
                     'class="line" style="' + style + ' left:' + ml.left + 'px;'
                                                     + 'top:' + ml.top + 'px;'
                                                     + 'width:' + ml.width + 'px;'
                                                     + 'height:' + ml.height + 'px"></div>')
 if(this.aeflag == '1'){
   style = getArrowStyle(ml.amode, this.color)
   document.write('<div id="' + id + '-a" ' +
                       'class="arrow" style="z-index: 0; ' + style + 'left:' + ml.aleft + 'px;'
                                                  + 'top:' + ml.atop + 'px"></div>')
 }
 if(this.asflag == '1'){
   style = getArrowStyle(ml.ramode, this.color)
   document.write('<div id="' + id + '-ra" ' +
                       'class="arrow" style="z-index: 0; ' + style + 'left:' + ml.raleft + 'px;'
                                              + 'top:' + ml.ratop + 'px"></div>')
 }
 
 document.write('<div id="' + id + '-l" ' +
                       'class="label" style="left:' + ml.lleft + 'px;'
                                                  + 'top:' + ml.ltop + 'px">' + label + '</div>')

}

  • ソースここまで----------------------------------------------------

ちょっと長いですが、要約すると

1. 二つの付箋の位置から線を引く座標を割り出す 2. 線を表示するDIV要素を出力する 3. 矢印を表示するDIV要素を出力する 4. 見出しを表示するDIV要素を出力する

ということになります。線も矢印も、それぞれ一つのDIV要素を使って表示しています。線の場合は種を明かせば割と簡単な話で、例えば┌という形の線を引く場合、DIV要素の上端と左端にのみ線を引くことで実現しています。線を引く二つの付箋の位置関係によって線の座標も形も変わってきますが、それはdrawLineオブジェクトの作成中に作成されているmakeLineオブジェクトで計算されます。

矢印はもう少しトリッキーな手法を使っています(注7)。DIV要素のスタイルとしてCSSで以下のように指定すると、下向きの矢印になります。

  • ソースここから----------------------------------------------------

font-size: 0px; line-height: 0%; width: 0px; box-sizing: content-box; -moz-box-sizing: content-box; border-top: 20px solid #77c; border-left: 10px solid white; border-right: 10px solid white;

  • ソースここまで----------------------------------------------------

この、border-top(left|right|ここにはないけどbottom)の部分を入れ替えれば、上向き、左向きなどの矢印も作ることが出来ます。borderの太さの部分(20pxなど)を変更すれば矢印の大きさも調節できるというわけです。別に透過GIF等でも矢印を実現できるのに、どうしてこんなトリッキーな技法を用いているかといえば、このようにJavaScriptを用いて動的に変更しやすいからだったりします。

図1[wema1.bmp] 図2[wema2.bmp]

注3)この機能はAjaxを用いて実現されています。Ajaxは次章以降で説明します 注4)透過の程度を強くしすぎると逆に見辛くなります 注5)drag.jsは高橋登史朗氏(http://jsgt.org/mt/01/)が公開されている同名のライブラリを改造して使用しています。この場をお借りして高橋氏に御礼申し上げます 注6)とはいえ、この機能も世界のどこかで実装している人がいそうな気はしますが 注7)http://www.howtocreate.co.uk/tutorials/slopes.html で紹介されているものを使用しました。

インクリメンタル検索はwemaの諸機能の中でもプレゼン等でデモしたときに一番反応のいい見栄えのする機能です。また簡単な仕組みの割に実用度が高いのも特徴です。

この機能は一言で言うと検索キーワードを入力するとそのキーワードが含まれる付箋のみが表示され、他の付箋が消えていく、というものです。「インクリメンタル」とあるとおり、キーワード入力に追随してどんどん表示が変化していきます。例えば最初の状態(図3)では全ての付箋が表示されていますが、「あ」と入力すると"あ"が含まれる付箋以外が画面から消え、続けて「あ・る・よ」と入力するごとにどんどん対象外の付箋が消えていき、最終的に「あるよ」が含まれる付箋のみが表示されます(図4)。

インクリメンタル検索のベースになっているのは高林哲氏(注8)が2004年11月に氏のブログで公開した「JavaScriptでインクリメンタルgrep検索」(注9)です。これを応用してほそのひでもと氏(注10)がWikiばなVol.3で発表した「Hikiのページ一覧をインクリメンタル検索するデモ」をヒントに、wemaの付箋検索に使用してみました。

  • ソースここから----------------------------------------------------

function sticky_grep (pattern) {

 try {
   regex = new RegExp(pattern, "im");
   for (i = 0; i < sticky_index.length; i++) {
     idx = sticky_index[i]
     if (sticky[idx].src.match(regex)) {
       showLAYER(sticky[idx].id)
     } else {
       hideLAYER(sticky[idx].id)
     }
   }
 } catch (e) {}

}

  • ソースここまで----------------------------------------------------

特に難しいことをやっているわけではありません。forループでsticky_index変数の各要素を参照し、マッチングの際もsticky変数を用いています。もちろんgetElementなどによって付箋のDIV要素を直接取得して処理することも可能ですが、こうして変数にキャッシュするとかなり処理が早くなります。wema2では数十個から100個程度の付箋でも割とスムーズにインクリメンタル検索を行うことが出来ることを書き添えておきます。

なお、ここでも環境依存を吸収するために付箋の表示・非表示を切り替えるために別途定義した関数を使っています。

  • ソースここから----------------------------------------------------

function page_grep (pattern) {

 try {
   regex = new RegExp(pattern, "i")
   spans = document.getElementsByTagName('span')
   for (i = 0; i < spans.length; i++) {
     e = spans[i]
     if (e.className == "page") {
       if (e.innerHTML.match(/(.*)<a.*>(.*)<\/a>/)) {
         str = RegExp.$1 + " " + RegExp.$2
         if(str.match(pattern)){
           e.style.display = "inline"
         } else {
           e.style.display = "none"
         }
       }
     }
   }
 } catch (e) {}

}

  • ソースここまで----------------------------------------------------

付箋のインクリメンタル検索と違ってあまり知られていない機能ですが、wemaのページ一覧画面では、ページ名をインクリメンタル検索することが出来ます。高林氏のソースに近く、こちらではキャッシュを行わず、getElementsByTagNameで取得したSPAN要素からinnerHTMLで直接ページ名を取得して検索しています。ページ名の表示・非表示もdisplayの「inline」と「none」の切り替えで実現しています。

図3[wema3.bmp] 図4[wema4.bmp]

注8)http://namazu.org/~satoru/ 注9)http://namazu.org/~satoru/blog/archives/000008.html 注10)http://www.h12o.org/

Wikiの特長の一つに冗長なHTMLを記述しなくても簡易記法で手軽に構造化した文章を書けるということがあります(注11)。これはとても便利なのですが、今書いている文章が実際にどう見えるかすぐには分からないという弱点があります。

例えばHikiではこれを解消するためにプレビュー機能がありますが、これは一度入力した内容をサーバにPOSTして、プレビュー画面を再描画する必要があります。

  • 図----------------------------------------------------

┌─┐             ┌―――┐ |PC| ―  入力 内容  → |サーバ| | | ← プレビュー画面 ― |   | └─┘             └―――┘


wemaではいくつかの利点からAjaxを用いてプレビュー機能を実現しています。既に本文中に何回か登場しましたが、wemaのプレビュー機能について説明する前にAjaxとは何なのか説明したいと思います。

AjaxはAsynchronous JavaScript + XMLの略称で、JavaScript(+CSS)を使った動的なHTMLの変更と、XMLHttpRequestというJavaScriptでWebサーバとデータのやり取りが出来るクラスを用いた非同期通信を組み合わせた技術に対しての呼び名です。Googleが最近リリースしたサービス(GoogleSuggestやGoogleMapsなど)もAjaxを用いて実装されていることから注目が高まっています。とはいえ、これらの技術自体は以前から存在していたものであり、名前がついたことによって具体的なトレンドとして注目されるようになった側面もあります。 ちなみにAjaxは正確には「エイジャックス」と発音するようですが、最初の頃は「アヤックス」などと呼ばれ、今は大体「アジャックス」が主流になっています。

さて、Ajaxを使ってプレビュー機能を実装する利点はwemaに関して言えば2つあります。 ひとつはプレビューの性能です。下図を参照していただきたいのですが、Hikiのプレビュー機能と比べてwemaのプレビュー機能は入力内容をAjaxで非同期に送信し、それをHTMLに変換したものだけを受け取っています。

  • 図----------------------------------------------------

┌─┬─┐          ┌―――┐ |PC|JS| ― 入力内容 → |サーバ| | | | ← HTML ― |   | └─┴─┘          └―――┘


これにより、サーバとやり取りするデータの量も少なくなり、画面で描画する部分もプレビューするHTMLの部分だけで済むことになります。

もっとも、JavaScript自体なかなか強力な文字列処理機能を持っていますから、サーバと通信しないでもJavaScript自体でWiki文法からHTMLに変換することは可能です。場合によってはそちらのほうが効率がいい場合もあると思います。が、wemaの特殊な事情からもう一つAjaxの利点が生まれてきます。

大抵のWikiはそれぞれ独自のWiki文法を持っています。似通ってはいますが方言のような大小の違いもあり、あるWikiのヘビーユーザが別のWikiを使おうとするとこの違いで苦戦することがままあります。そこでwemaではバージョン2から使用する文法を選択出来る様にしました。プラグイン式にいくつでも追加できるのですが、wemaインストール直後の時点では以下の4つから選ぶことが出来ます。

  • markdown
 MovableType(ブログツール)用の簡易記法として開発された。記述が簡単で見易い
  • hiki
 Hikiの書式
  • rd
 プログラミング言語Rubyの埋め込みドキュメント用の書式
  • wema1
 wemaのバージョン1の時の書式。YukiWikiの書式とPukiWiki,Hikiなどの書式が混じっている

このように、wemaではユーザがどういった書式を選ぶか分からないため、JavaScriptでWiki文法を変換することが事実上難しくなっています。Ajaxを使ってサーバと更新することで、JavaScript側では今どんな文法なのか意識せずにプレビューを行うことが出来ます。

それでは仕組みを説明していきます。図5は付箋の編集画面です。ここで「プレビュー」をクリックすると、ajax.jsに定義されたdoPreview変数が実行されます。

  • ソースここから----------------------------------------------------

function doPreview() {

   xmlhttp.open('POST','xhserv.cgi/pv',true)
   xmlhttp.onreadystatechange = function () {
     if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
       getLayOj('preview').innerHTML = xmlhttp.responseText
     }else{
       //alert('sts: ' + xmlhttp.status)
     }
   }
   xmlhttp.send('src=' + encodeURI(document.EditForm['MainPage.StickySourceField'].value))

}

  • ソースここまで----------------------------------------------------

まず最初の行のxmlhttp.openが実行され、xhserv.cgiというサーバのCGIプログラムにPOSTメソッドで値を送ることを決めています。3つ目の引数は、このPOST処理を非同期で行うかどうかで、当然行う(true)としています。

次に実際に実行されるのはxmlhttp.sendになります。xmlhttp.onreadystatechangeに定義された関数は、xmlhttpの状態が変わったとき(POSTを行う、POSTが完了する、エラーになるなど)に呼び出されます。xmlhttp.sendでは入力内容を「src」という名前でPOSTしています。

POSTが完了、あるいはエラーになるとonreadystatechangeで定義した関数が呼び出されます。POSTが成功していれば、xmlhttp.responseTextにはサーバの返却した値=入力内容を変換したHTMLが入っています。doPreviewでは「preview」というIDのDIV要素の中に変換したHTMLを挿入しています。図6は、プレビューが表示された状態です。

図5[wema5.bmp] 図6[wema6.bmp]

注11)逆に、特殊な記法がないことを売りにしているWikiもあります

今回はwemaの機能の中でもJavaScriptを使っているものを中心に紹介してきました。これらの機能を実現しているテクニックは他のWikiの機能向上にも転用できると思います。Ajaxプレビュー機能の他Wikiへの実装がもっとも分かりやすいパターンですが、例えばインクリメンタル検索も一般的なWikiに対して実装するとまた違った利便性が出てくるかもしれません。付箋を応用したポップアップメニューはwemaでも使われていますが、画面上の操作メニューを減らすことでWiki初心者の「なんだかメニューが多くて何をしたら良いのか分からない」という不安を緩和するもあるようです。

WikiのUIはまだまだ進化していく余地があると思っています。JavaScriptはきっとその進化の鍵になるでしょう。この文書がWikiとJavaScriptの新しいコラボレーションが生まれるきっかけになれば幸いです。

今回の記事中でも幾度か取り上げていますが、以前ほどではないにしろJavaScriptにはブラウザ間で動作が完全互換でないという問題があります。一例として、画面をダブルクリックした時に付箋の入力欄が開く、という処理を行う関数を比較してみます。

  • ソースここから----------------------------------------------------

function myClickIE(){ // IEでWクリック

 if(editing == false){
   editing = true
   myObj=document.all[myID].style;
   if(document.getElementById && (document.compatMode=='CSS1Compat')){
     myObj.left = window.event.clientX + document.documentElement.scrollLeft + "px";
     myObj.top  = window.event.clientY + document.documentElement.scrollTop + "px";
   }else{
     myObj.left = window.event.clientX + document.body.scrollLeft + "px";
     myObj.top  = window.event.clientY + document.body.scrollTop + "px";
   }
   setEditValue('', '', window.event.clientX + document.body.scrollLeft, window.event.clientY + document.body.scrollTop,'');
   myObj.visibility = "visible";
 }

}

  • ソースここまで----------------------------------------------------
  • ソースここから----------------------------------------------------

function myClickN6(myEvent){ // N6でWクリック

 if(editing == false){
   editing = true
   myObj=document.getElementById(myID).style;
   myObj.left = myEvent.clientX + window.pageXOffset + "px";
   myObj.top  = myEvent.clientY + window.pageYOffset + "px";
   setEditValue('', '', myEvent.clientX + window.pageXOffset, myEvent.clientY + window.pageYOffset,'');
   myObj.visibility = "visible";
 }

}

  • ソースここまで----------------------------------------------------

上がIE用の関数。下がMozilla系用の関数です。確かに大分違います。これに加えてNetscape4.X系にも対応するとなると頭が痛くなってきます。

ただ、幸いなことにJavaScriptの環境依存は殆どがDHTML絡みの部分(と、Ajaxで使われるXMLHttpRequestも環境依存)なので、wemaで不完全ながら実現しているように、環境の差異を吸収して使えるようその方面の環境依存を吸収するライブラリがあれば負担は大分少なくなります。最近はWebアプリケーションのフレームワーク(注12)がJavaScriptをサポートするケースが増えてきましたが、それらの場合も作成されるJavaScriptコードは環境依存に最大限配慮したものになっていることが多いようです。

Ajaxをきっかけに今再びJavaScriptが注目されています。昔環境依存で苦労してJavaScriptを毛嫌いするようになった方もWebアプリケーションの表現力を上げる有効な手段として検討項目の一つとしてみませんか。

注12)Ruby on Railsなどの、Webアプリケーションを構築するための一連のライブラリ、システム

Last modified: 2005-09-01 Attached files total: 48MB