継承というか

JavaScriptをまともに勉強したのは初めてかもしれん。とりあえず prototype.js はいろいろうんざりさせられた過去があるので jQuery 使ってる。
継承てどう実装するんだろうと思っていろいろ調べてた。JavaScriptだと日本語の情報が結構あるなー。
最初はObject.prototypeを汚染してしまってて、途中で困ったので結局こんな感じで落ち着いた。細かいところは気にしない。

core.js

var Class = new function(){};
Class.extend = function(target, base) {
  for (var i = 1 ; i < arguments.length ; i++) {
    var cls = arguments[i]
    for (property in cls) {
      if(cls.hasOwnProperty(property)){
        target[property] = cls[property];
      }
    };
  };
};

最初からjQueryですべて完結するのもいいけれど、まずは基礎から。
早速、アイテムの表示と編集をするためのコンポーネントが必要だったので、割と汎用なものを作った。

listeditor.js

var ListEditor = function(prefix) {
  this.prefix = prefix;
  this.items = [];
  this.is_edit = false;
};
Class.extend(ListEditor.prototype, {
  getTemplate : function(name) {
    if (name == 'edit') {
      return '<ul>{for item in items}<li>${item.name} <a href="javascript:void(0);" class="${prefix}_remove_item" id="${prefix}_remove_item_${item_index}">x</a></li>{/for}</ul><form method="post" action="" id="${prefix}_add_form"><div><input type="text" name="name" />&nbsp;<button type="submit" id="${prefix}_add_item">add</button></div></form><div><a href="javascript:void(0);" id="${prefix}_cancel">cancel</a></div>';
    } else {
      return '<p>{for item in items}${item.name}&nbsp;{/for}</p><div><a href="javascript:void(0);" id="${prefix}_edit">edit</a></div>';
    }
  },
  getTarget : function() {
    return $('#'+this.prefix);
  },
  render : function() {
    var context = {items : this.items, prefix : this.prefix};
    var template_name = '';
    if (!this.is_edit) {
    } else {
      template_name = 'edit';
    }
    var template = TrimPath.parseTemplate(this.getTemplate(template_name));
    this.getTarget().html(template.process(context));
    this.bind();
  },
  removeItem : function(index) {
    this.items.splice(index, 1);
  },
  addItem : function(item) {
    this.items.push(item);
  },
  findItem : function(name) {
    for (var i = 0; i < this.items.length; i++) {
      if (this.items[i].name == name) {
        return i;
      }
    }
    return (-1);
  },
  showEditor : function() {
    this.is_edit = true;
    this.render();
    this.setFocus();
  },
  hideEditor : function() {
    this.is_edit = false;
    this.render();
  },
  setFocus : function() {
    $('#' + this.prefix + '_add_form').find('input[name=name]').focus();
  },

  // actions
  handleEditClick : function(elem) {
    this.showEditor();
  },
  handleCancelClick : function(elem) {
    this.hideEditor();
  },
  handleremoveClick : function(elem) {
    this.removeItem(elem.id.replace(this.prefix + '_remove_item_', ''));
    this.render();
  },
  handleAddClick : function(elem) {
    var val_name = $('#' + this.prefix + '_add_form').find('input[name=name]').val();
    if (val_name != '') {
      var item = {
        name : val_name
      };
      this.addItem(item);
      this.render();
    }
  },

  // bind actions
  bind : function() {
    var th = this;
    if (this.is_edit) {
      $('#' + this.prefix + '_cancel').bind('click', function() {
        th.handleCancelClick(this);
      });
      $('.' + this.prefix + '_remove_item').bind('click', function() {
        th.handleremoveClick(this);
      });
      $('#' + this.prefix + '_add_form').bind('submit', function() {
        th.handleAddClick(this);
        return false;
      });
    } else {
      $('#' + this.prefix + '_edit').bind('click', function() {
        th.handleEditClick(this);
      });
    }
  }
});

使う場合はこんな感じ。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link type="text/css" rel="stylesheet" href="sample.css" />

  <script type="text/javascript" src="jquery-1.2.6.min.js"></script>
  <script type="text/javascript" src="trimpath-template-1.0.38.js"></script>
  <script type="text/javascript" src="core.js"></script>
  <script type="text/javascript" src="listeditor.js"></script>
  <script type="text/javascript">
  var listeditor1 = new ListEditor('listeditor1');
  listeditor1.addItem({name : 'Test'});
  listeditor1.addItem({name : 'テスト'});
  listeditor1.addItem({name : 'ほげほげ'});
  listeditor1.render();
  </script>
  <title>ListEditor</title>
</head>
<body>
  <h1>ListEditor</h1>
  <div id="listeditor1" class="listeditor"></div>
</body>
</html>

getTemplateやらrenderをすげ替えれば見た目は割と自由が利く。

  listeditor1.getTemplate = function(name) {
    if (name == 'edit') {
      return '<ul>{for item in items}<li><span class="item">${item.name}</span> <a href="javascript:void(0);" class="${prefix}_remove_item" id="${prefix}_remove_item_${item_index}">x</a></li>{/for}</ul><form method="post" action="" id="${prefix}_add_form"><div><input type="text" name="name" />&nbsp;<button type="submit" id="${prefix}_add_item">追加</button></div></form><div><a href="javascript:void(0);" id="${prefix}_cancel">キャンセル</a></div>';
    } else {
      return '<p>{for item in items}<span class="item"><a href="http://www.google.co.jp/search?q=${item.name|encodeURI}" target="_blank">${item.name}</a></span>&nbsp;{/for}</p><div><a href="javascript:void(0);" id="${prefix}_edit">編集</a></div>';
    }
  };
  listeditor1.render = function() {
    var context = {items : this.items, prefix : this.prefix, _MODIFIERS : {encodeURI : encodeURI}};
    var template_name = '';
    if (!this.is_edit) {
    } else {
      template_name = 'edit';
    }
    var template = TrimPath.parseTemplate(this.getTemplate(template_name));
    this.getTarget().html(template.process(context));
    this.bind();
  }


trimpath-templateがいい感じに使いやすいなー。contextのところはgetContextにすべきだったかな。
ちなみに実際に使ってるのは、ListEditorを継承して作ったAjaxListEditorだったりする。