JavaScriptでWysiwygなHTMLエディタを作ってみる

久しぶりにJavaScriptの勉強、ということでWysiwygエディタを作ってみました。

サンプルも公開してみちゃいます。(一応IE,FF両方動くはず・・!)
フォントサイズ変更、太字・斜字・下線、文字色、背景色など基本的な機能を実装してみました。


MDCとMSDNなどを見てみると、ブラウザ上でHTM文書を編集する場合、流れは基本的に

  1. iframeを作る
  2. iframeに、desigmMode="on"を設定する
  3. ボタンか何かを作って、iframeに対してdocument.execCommand()を実行する

てな感じです。

execCommandで実行できる操作は、以下のサイト見ればIEとFFはオッケーです。
MDC リッチテキスト編集 仕様
MSDN execCommand Method

HTMLは極力シンプルにしたかったので、編集エリア用のdiv要素を用意して

  new RTE("div_id");

とするだけで使えるようにしました。


実行できる操作は、panelsConf変数にコマンドとボタンに使う画像パスを突っ込んでオプションとして指定するようにしているので、操作の追加・削除もサクっとできます。
セレクトメニューやピッカー系はボタン画像の指定はせずに、要素を生成するときに文字列を入れています。

var panelsConf = [
  {title : "フォントサイズ変更",
   action: "SelectFontSize"
  },
  {title : "太字",
   action: "bold",
   styles: {backgroundImage: "url(./images/text_bold.png)"}
  },

 〜省略〜

  {title : "リンク作成",
   action: "Link",
   styles: {backgroundImage: "url(./images/link.png)"}
  },
  {title : "文字色",
   action: "PickerFontColor"
  },
  {title : "背景色",
   action: "PickerBgColor"
  }
}

ボタン一発で実行するものはactionにコマンド名をそのまま指定、セレクトメニューを出すものはactionの頭に「Select」を、ピッカーを出すものはactionの頭に「Picker」を付けてそれぞれクラスを作る。
メニューとかは元クラスを継承してるので、個々のメニューを出すところだけ実装すればおk。


以下は作成中のメモ。

IEでセレクトメニューやピッカーを表示してクリックしても、書式がウントモスントモ変わってくれなくて悩みました。
FFでは動いてるのに!execCommandの返り値はtrueが返ってるのに!とかうーんという感じでしたが、IEの場合はセレクトメニューとか出したら、execCommandを実行する前にフォーカスをiframeに戻さないとダメなんですね。unselectable属性を"on"にしてるだけじゃダメだった。
どこにも載ってないからすげー悩んだよ。。。

あとはIEでexecCommand("fontsize")した時に、focus()実行するとカーソルが一番最初に戻るところと、IEで改行したときにpタグが入る謎の挙動を何とかすれば割と使い物になるかも?(全部IEやん・・・)


今回ひそかに勉強出来たのがJavaScript継承について。
最初は

Class.extend = function(src, opt) {
  for(var prop in opt) {
    src.prototype[prop] = opt[prop];
  }
  return src;
}

としていて、メニュー要素で元クラスを継承してたんだけど、同じプロトタイプを参照しちゃうので、継承すると他の継承したクラスに影響が出るという素人でした。。。

で、継承したクラス同士が継承元のクラスに影響を与えないようにこうした。

Class.extend = function(src, opt) {
  var f = function(){
    this.initialize.apply(this, arguments);
  };
  f.prototype = new src();
  for(var prop in opt) {
    f.prototype[prop] = opt[prop];
  }
  return f;
}

newしているので、継承元クラスの初期実行処理はinit()にまとめ、initialize()は空にしとく。
で、継承したクラスのinitialize()でinit()を実行すればいい。
継承させる場合、JavaScriptのコンストラクタが使いづらいと感じるのは自分だけでしょうか。。
クラス関係なんかはFCKEditorを参考にしてみました。

とはいえ、こんな感じで継承とか不慣れなもので、メニューのクラス継承とかまだまだスマートじゃないところがいっぱいです。後で少しソース書き直すかも。