空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

テキストをクリック→テキストボックスを表示→インラインで編集→テキスト更新を実現するクラスを作ってみた。

なるべく小さな部品をクラスという形で提供して,再利用しやすい俺ライブラリをこまめに作っていこうと思います。id:xoinu:20071231 でやろうとしていたこととは少し外れるので,エト記に書いておきます。

表題の通り,今日作ってみたのは,はてなグラフの編集画面にあるような UI で

  1. 始めの見た目は文字列
  2. 文字列をクリックすると,テキストボックスになり,編集できるようになる
  3. Enter キー,あるいは focus が外れると編集終了
  4. 編集が終わると,テキストボックスが文字列に戻って,編集後の文字列になる
  5. 編集後に hook 関数を起動する

というものです。

文字列は

<span id="hoge">hogehoge</span>

のような id 付きのブロックで囲まれていると想定しています。これに対して

var ie;
addEvent(window, "load", pageInit);
function pageInit()
{
  ie = new InlineEdit($("hoge"));
}

みたいな感じで,適応対象のテキスト要素に対して,1つインスタンスを作って使います。addEvent という関数は汎用のイベント登録関数です。prototype.js も使ってます。

Enter キーを押すとフォームが submit されてしまうので,preventDefault() 関数で回避しています。いちおう,SafariFirefox で動作確認済。参考にしたのは,はてなグラフの実装(Firebugで赤裸々に解析)ですが,スタイルシートを使用せず,インスタンスがDOM要素を保持して,イベントの度にホイホイすげ替えるようしていいます。IEだと多分動かないと思います。

クラスの定義は以下の通りです。

//
// text => <span>...text node...</span>
//
var InlineEdit = Class.create(); InlineEdit.prototype =
{
  text : null,
  form : null,
  onStartFinish : function(){},
  onEndFinish : function(){},
  onComplete : function(){},

  initialize : function(text, onComplete, onStartFinish, onEndFinish)
  {
    var parent = text.parentNode;
    var form = document.createElement("form");
    var input = document.createElement("input");

    input.setAttribute("type", "text");
    form.appendChild(input);
    form.setAttribute("autocomplete", "off");

    this.form = form;
    this.text = text;

    if (onStartFinish)
      this.onStartFinish = onStartFinish;

    if (onEndFinish)
      this.onEndFinish = onEndFinish;

    if (onComplete)
      this.onComplete = onComplete;

    var _this_ = this;
    addEvent(this.text, "click", function() { _this_.start(); });
    addEvent(this.form.firstChild, "blur", function() { _this_.finish(); });
    addEvent(this.form.firstChild, "keypress", function(e) { if (0 == e.charCode || 13 == e.charCode) {_this_.form.firstChild.blur(); e.preventDefault(); } });
    addEvent(this.form, "submit", function() { _this_.finish(); });

  },

  start : function()
  {
    var parent = this.text.parentNode;
    if (parent) {
      var tx = this.text.firstChild.nodeValue;
      this.form.firstChild.setAttribute("value", tx);
      this.form.firstChild.setAttribute("size", tx.length);
      parent.replaceChild(this.form, this.text);
      this.form.firstChild.focus();
      this.form.firstChild.select();
    }
  },

  finish : function()
  {
    this.onStartFinish();
    var parent = this.form.parentNode;
    if (parent) {
      this.onComplete();
      this.text.firstChild.nodeValue = this.form.firstChild.value;
      parent.replaceChild(this.text, this.form);
    }
    this.onEndFinish();
  }
};

コンストラクタの第2引数以降で,finish() メンバ関数内で呼ばれる hook を登録することができます。実際には例外処理もしなければいけませんね。