Ajax.InPlaceEditor でさまざまなフォームを表示する
趣旨
Rails の in_place_editor_field メソッドの本体は、prototype 系ライブラリ script.aculo.us の controls.js にある Ajax.InPlaceEditor である。InPlaceEditor は、フォーム表示に関して、実はあまり柔軟性がない。任意のフォームは使えないだろうかと考えて、InPlaceEditor を改造してみた。
どう使うかというと・・・。
<%= javascript_include_tag :defaults %> <%= javascript_include_tag "controls_ext" %> <form class="inplaceeditor-form" id="editor_form" style="display:none"> <input type="text" name="value" id="editor_field" class="editor_field"/><br /> <input type="button" value="保存" id="editor_ok_button" class="editor_ok_button"/> <input type="button" value="キャンセル" id="editor_cancel_button" class="editor_cancel_button"/> </form> name: <%= in_place_editor_field :book, :name, {}, :external_form_id => 'editor_form' %>
あらかじめ、編集モード時に表示すべきフォームを用意しておいて、その ID を in_place_editor_field に渡す。こうやって、さまざまなフォームが表示できるようになる。フォームの構成要素は3つあり、
- テキストフィールド(あるいはテキストエリア)
- 保存ボタン
- キャンセルボタン
である。それらの要素の id はデフォルトでそれぞれ editor_field, editor_ok_button, editor_cancel_button であるが、
name: <%= in_place_editor_field :book, :name, {}, :external_form_id => 'editor_form', :editor_field_id => 'my_editor_field', :ok_button_id => 'my_ok_button', :cancel_button_id => 'my_cancel_button' %>
というふうに変更することもできる。
ソースコード
Rails 側(environment.rb)
module InPlaceMacrosHelper def in_place_editor(field_id, options = {}) function = "new Ajax.InPlaceEditor(" function << "'#{field_id}', " function << "'#{url_for(options[:url])}'" js_options = {} js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text] js_options['okText'] = %('#{options[:save_text]}') if options[:save_text] js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text] js_options['savingText'] = %('#{options[:saving_text]}') if options[:saving_text] js_options['rows'] = options[:rows] if options[:rows] js_options['cols'] = options[:cols] if options[:cols] js_options['size'] = options[:size] if options[:size] js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control] js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url] js_options['ajaxOptions'] = options[:options] if options[:options] js_options['evalScripts'] = options[:script] if options[:script] callback = if options[:with] "function(form) { var params = #{options[:with]};" else "function(form) { var params = Form.serialize(form); " end callback += "params = (params ? params + '&' : '') + '#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}'); return params;}" js_options['callback'] = callback js_options['clickToEditText'] = %('#{options[:click_to_edit_text]}') if options[:click_to_edit_text] js_options['externalFormId'] = options[:external_form_id] if options[:external_form_id] js_options['editorFieldId'] = options[:editor_field_id] if options[:editor_field_id] js_options['okButtonId'] = options[:ok_button_id] if options[:ok_button_id] js_options['cancelButtonId'] = options[:cancel_button_id] if options[:cancel_button_id] function << (', ' + options_for_javascript(js_options)) unless js_options.empty? function << ')' javascript_tag(function) end end
Javascript 側(controls_ext.js)
Object.extend(Ajax.InPlaceEditor.prototype, { createForm : function() { this._form = $(this.options.externalFormId); this._form.style.display = "inline"; this._form.remove(); this.element.parentNode.insertBefore(this._form, this.element); var editorFieldId = this.options.editorFieldId || 'editor_field'; var okButtonId = this.options.okButtonId || 'editor_ok_button'; var cancelButtonId = this.options.cancelButtonId || 'editor_cancel_button'; this._controls.editor = $(editorFieldId); this._controls.editor.value = this.getText(); var okButton = $(okButtonId); okButton.onclick = this._boundSubmitHandler; var cancelButton = $(cancelButtonId); cancelButton.onclick = this._boundCancelHandler; }, enterEditMode: function(e) { if (this._saving || this._editing) return; this._editing = true; this.triggerCallback('onEnterEditMode'); if (this.options.externalControl) this.options.externalControl.hide(); this.element.hide(); this.createForm(); if (!this.options.loadTextURL) this.postProcessEditField(); if (e) Event.stop(e); }, removeForm: function() { if (!this._form) return; this._form.style.display = "none"; this._controls = { }; } });
つぶやき
微妙に投げやり感がただようコードとなっている。というのも prototype.js 系のライブラリはつくづく垢抜けないので、いじっていて楽しくないのだ。おそらくは jQuery 系に乗り換えるべき潮時なのだろうな・・・