in_place_editorの機能拡張
今まで、InPlaceEditorを導入しているにもかかわらず、通常のEditリンクを残していたのは、InPlaceEditorの挙動を完全にコントロールできないで悩んでいたからである。解決したい問題は以下の2点。
- htmlエスケープの処理方法
- ブランク項目に対する入力方法
- 現状では、externalControlオプションでid属性を指定したマークをクリックすることで実現している。しかし、以下のような問題がある。
- ブランクかどうかに関係なく、コメント項目の全てに表示されてしまう。
- 初期表示については、Ruby側でブランクかどうか判定して、マークの表示を制御することは出来る。しかし、InPlaceEditorで編集した後の表示を制御することが出来ない。(編集した結果ブランクになってしまっても、編集モードに移行するマークが表示できないので困る。)
- Safariで、InPlaceEditorからコメント項目をブランクにすると、Saving...状態が永遠と続いてしまう。
- 目に見えない半角スペース等の文字を誤って入力してしまうと、見かけ上はブランクであるのに、「空白である」フィルター条件にヒットしなくて悩むことになる。
- 現状では、externalControlオプションでid属性を指定したマークをクリックすることで実現している。しかし、以下のような問題がある。
長らく放置していた問題は、Ajax.In Place Editor in scriptaculous wikiをちゃんと読むことで、 結果、スマートに解決できたのであった...。(過程は試行錯誤)
htmlタグをそのまま表示する機能拡張
- public/javascripts/extensions.jsファイルを新規作成して、Ajax.In Place Editor in scriptaculous wikiに紹介されている以下のコードをコピーする。
Object.extend(Ajax.InPlaceEditor.prototype, { onLoadedExternalText: function(transport) { Element.removeClassName(this.form, this.options.loadingClassName); this.editField.disabled = false; this.editField.value = transport.responseText; Field.scrollFreeActivate(this.editField); } }); Object.extend(Ajax.InPlaceEditor.prototype, { getText: function() { return this.element.childNodes[0] ? this.element.childNodes[0].nodeValue : ''; } });Ajax.In Place Editor in scriptaculous wiki - Using HTML within the In Place Editor
- app/views/layouts/csvs.rhtml
- レイアウトファイル
<%= javascript_include_tag 'extensions' %>
これで、InPlaceEditorがhtmlタグを勝手に削除してしまうことは無くなった。エスケープされた文字も元通りに復元して表示してくれる。以下は、ビューとコントローラーの内容。
- app/views/csvs/_listd.rhtml
- ビュー(リストデータを表示する部分)
- 以前と変更なし。
...(途中省略)... <td <%= arranged_class("file_comment") %>> <% listd.file_comment = h(listd.file_comment) %> <% if management_section?(listd.management_section) %> <span class="edit_mark" id="file_comment<%= listd.id %>"><%= "»" %></span> <span class="in_place_edit"> <%= in_place_editor_field :csv, :file_comment, {}, :rows => 2, :save_text=>"保存", :cancel_text=>"キャンセル", :external_control=>"file_comment#{listd.id}" %> </span> <% else %> <%=h listd.file_comment %> <% end %></td> ...(途中省略)...
- app/controllers/csvs_controller.rb
- コントローラー
# in_place_edit_for :csv, :file_comment def set_csv_file_comment @csv = Csv.find(params[:id]) @csv.update_attribute('file_comment', params[:value]) render :inline => "<%=h @csv.file_comment %>" end
これで、htmlエスケープの問題は解決。
ブランクの時、'click to edit...'を表示する機能拡張
インストールすることで、ブランクの時だけ、編集モードに移行するための文字列を表示してくれる。
- 先ほど作成したextensions.jsに、Ajax.In Place Editor in scriptaculous wikiに紹介されている以下のコードをコピーする。
scriptaculous 1.8.0では、右記コードをsetOptions:に追記する必要あり。if (this.options.externalControl){this.options.externalControl = $(this.options.externalControl);}
setOptions:に、if (this.options.externalControl){this.options.externalControl = $(this.options.externalControl);}を追記済み。
/* * InPlaceEditor extension that adds a 'click to edit' text when the field is * empty. */ Ajax.InPlaceEditor.prototype.__initialize = Ajax.InPlaceEditor.prototype.initialize; Ajax.InPlaceEditor.prototype.__getText = Ajax.InPlaceEditor.prototype.getText; Ajax.InPlaceEditor.prototype.__onComplete = Ajax.InPlaceEditor.prototype.onComplete; Ajax.InPlaceEditor.prototype = Object.extend(Ajax.InPlaceEditor.prototype, { initialize: function(element, url, options){ this.__initialize(element,url,options) this.setOptions(options); this._checkEmpty(); }, setOptions: function(options){ this.options = Object.extend(Object.extend(this.options,{ emptyText: 'click to edit...', emptyClassName: 'inplaceeditor-empty' }),options||{}); if (this.options.externalControl) this.options.externalControl = $(this.options.externalControl); }, _checkEmpty: function(){ if( this.element.innerHTML.length == 0 ){ this.element.appendChild( Builder.node('span',{className:this.options.emptyClassName},this.options.emptyText)); } }, getText: function(){ document.getElementsByClassName(this.options.emptyClassName,this.element).each(function(child){ this.element.removeChild(child); }.bind(this)); return this.__getText(); }, onComplete: function(transport){ this._checkEmpty(); this.__onComplete(transport); } });Ajax.In Place Editor in scriptaculous wiki - Small extension to editor to add a text in case field is empty.
- これで、ブランクの時「click to edit...」が表示されると期待したが、自分の環境では動かなかった...。
- コードを確認してみると、_checkEmpty: function()で、Builderクラスを呼び出している。これが怪しいと予想して...
- script.aculo.us - downloadsでscript.aculo.us 1.7.0をダウンロードしてみると、builder.jsが同胞されている!
- バージョンの違いで悩みたくないので、以下のファイル全てを上書きインストールした。
- builder.js
- controls.js
- dragdrop.js
- effects.js
- prototype.js(1.5.0が同胞されていたので、control.modalとの互換性も問題なさそうだ。)
- app/views/layouts/csvs.rhtml
- レイアウトファイル
<%= javascript_include_tag :defaults %> <%= javascript_include_tag 'builder' %> <%= javascript_include_tag 'extensions' %>
これで、ブランクの時に'click to edit...'が表示されるようになった。
「click to edit...」を「編集>>」に変更したい。
InPlaceEditorのオプションで、emptyText: '編集>>'と設定してあげれば良いのだが、拡張した機能なのでRailsのヘルパメソッドが対応していない。拡張したin_place_editorメソッドを定義してみた。
- Railsのソースからin_place_editorの部分をコピーする。目的のソースのパスは深い。(/Applications/Locomotive2/Bundles/standardRailsSept2006.locobundle/i386/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_view/helpers/java_script_macros_helper.rb)
- application_helper.rbにコピーして、オレンジ色の部分を追記した。
- app/helpers/application_helper.rb
- ヘルパー
...(途中省略)... 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['emptyText'] = %('#{options[:empty_text]}') if options[:empty_text] js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_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] js_options['callback'] = "function(form) { return #{options[:with]} }" if options[:with] function << (', ' + options_for_javascript(js_options)) unless js_options.empty? function << ')' javascript_tag(function) end ...(途中省略)...
- これで、Railsのヘルパメソッドから:empty_textオプションが指定できるようになる。
-
- InPlaceEditorのオプションには、他にもヘルパメソッドから利用できないものがあるので、手軽なこの方法で同じように機能拡張が期待できる。
-
- app/views/csvs/_listd.rhtml
- ビュー(リストデータを表示する部分)
- 不要になった:external_controlオプションは削除して以下のように変更した。
...(途中省略)... <td <%= arranged_class("file_comment") %>> <% listd.file_comment = h(listd.file_comment) %> <% if management_section?(listd.management_section) %> <%= in_place_editor_field :csv, :file_comment, {}, :rows => 2, :save_text=>"保存", :cancel_text=>"戻る", :empty_text=>"編集>>" %> <% else %> <%=h listd.file_comment %> <% end %></td> ...(途中省略)...
ブランクの保存が出来ないSafariに対応する。
- Safariでは、ブランクの状態で保存ボタンを押すと、いつまでも処理中になってしまうため、以下のように変更してみた。何らかの文字列を返してあげれば、処理が正常終了するようだ。(Firefoxだとifなんか使わずに、ちゃんと動作するのだが...。)
- ついでに、空白文字しか入力がない場合は、保存するとブランクになるように変更した。
...(途中省略)... def set_csv_file_comment params[:value] = '' unless /\S/ =~ params[:value] @csv = Csv.find(params[:id]) @csv.update_attribute('file_comment', params[:value]) if @csv.file_comment.blank? render :inline => "編集>>" else render :inline => "<%=h @csv.file_comment %>" end end ...(途中省略)...
スタイルシートで見た目を調整
- InPlaceEditorの要素は、以下のように指定して、調整することが出来るようだ。
form.inplaceeditor-form { /* The form */ } form.inplaceeditor-form input[type="text"] { /* Input box */ width: 100%; font-size: 100%; font-family: verdana, arial, helvetica, sans-serif; } form.inplaceeditor-form textarea { /* Textarea, if multiple columns */ width: 100%; font-size: 100%; font-family: verdana, arial, helvetica, sans-serif; } form.inplaceeditor-form input[type="submit"] { /* The submit button */ font-size: 75%; margin-left: 0px; padding: 0px; } form.inplaceeditor-form a { /* The cancel link */ font-size: 75%; margin-left: 0px; padding: 0px 2px; } .inplaceeditor-empty{ /* a text in case field is empty */ font-size: 75%; font-style: italic; color: #aaa; }
これで、完全にInPlaceEditorに依存した環境に移行できそう。Editページは不要になった。
in_place_editorのオプション一覧(自分の理解)
以下、試行錯誤の中で自分が理解したオプション一覧。
オプション | 対応V | 初期値 | 説明 | 自分の理解 |
---|---|---|---|---|
okButton | V1.6 | “true” | If a submit button is shown in edit mode (true,false) | 送信(ok)ボタンを表示するかどうか。 |
okText | V1.5 | “ok” | The text of the submit button that submits the changed value to the server | 送信ボタンに表示する文字列。 |
cancelLink | V1.6 | “true” | If a cancel link is shown in edit mode (true,false) | キャンセル(cancel)リンクを表示するかどうか。 |
cancelText | V1.5 | “cancel” | The text of the link that cancels editing | キャンセルリンクに表示する文字列。 |
savingText | V1.5 | “Saving…” | The text shown while the text is sent to the server | 送信ボタンを押してから、その処理中に表示する文字列。 |
clickToEditText | V1.6 | “Click to edit” | The text shown during mouseover the editable text | 編集可能なテキストにマウスを乗せている間表示する文字列。 |
formId | V1.5 | id of the element to edit plus ‘InPlaceForm’ | The id given to the element | InPlaceEditorのフォームのid属性。 |
externalControl | V1.5 | null | ID of an element that acts as an external control used to enter edit mode. The external control will be hidden when entering edit mode and shown again when leaving edit mode. | ここで指定したid属性の表示をクリックすると編集モードに移行する。 |
rows | V1.5 | 1 | The row height of the input field (anything greater than 1 uses a multiline textarea for input) | フォームの行数。1行ならtextfield、2行以上はtextarea。 |
onComplete | V1.6 | “function(transport, element) {new Effect.Highlight(element, {startcolor: this.options.highlightcolor});}” | Code run if update successful with server | 更新が成功した時に処理される内容。 |
onFailure | V1.6 | “function(transport) {alert(“Error communicating with the server: ” + transport.responseText.stripTags());}” | Code run if update failed with server | 更新が失敗した時に処理される内容。 |
cols | V1.5 | none | The number of columns the text area should span (works for both single line or multi line) | フォームの横幅。 |
size | V1.5 | none | Synonym for ‘cols’ when using single-line (rows=1) input | rowsの別名。 |
highlightcolor | ? | Ajax.InPlaceEditor.defaultHighlightColor | The highlight color | ハイライトカラー。マウスを乗せたときの強調表示の色。 |
highlightendcolor | ? | ”#FFFFFF” | The color which the highlight fades to | ハイライトカラーはここで指定した色まで段階的に変化する。 |
savingClassName | V1.5 | “inplaceeditor-saving” | CSS class added to the element while displaying “Saving…” (removed when server responds) | 更新処理中に追加されるクラス名。 |
formClass Name? | V1.5 | “inplaceeditor-form” | CSS class used for the in place edit form | InPlaceEditorフォームのクラス名 |
hoverClass Name? | ? | ? | ||
loadTextURL | V1.5 | null | Will cause the text to be loaded from the server (useful if your text is actually textile and formatted on the server) | 編集モードに移行した時に、データを取得するURL。Railsでは”/controller/action/id”のように指定すれば、そのアクションを処理した結果がInPlaceEditorフォームにセットされる。指定がなければ、既にページ上で表示されている値がセットされる。 |
loadingText | V1.5 | “Loading…” | If the loadText URL option is specified then this text is displayed while the text is being loaded from the server | 上記データ取得中に表示する文字列。 |
callback | V1.5 | function(form) {Form.serialize(form)} | A function that will get executed just before the request is sent to the server, should return the parameters to be sent in the URL. Will get two parameters, the entire form and the value of the text control. | |
submitOnBlur | V1.6 | “false” | This option if true will submit the in_place_edit form when the input tag loses focus. | フォームからフォーカスが外れると常に更新処理が実行される。表計算ソフトのような操作性が期待できるかも。 |
ajaxOptions | V1.5 | {} | Options specified to all AJAX calls (loading and saving text), these options are passed through to the prototype AJAX classes. | |
emptyText | 拡張 | 'click to edit...' | 内容がブランクの時表示される文字列。これをクリックすることで編集モードへ移行可能になる。 | |
emptyClassName | 拡張 | 'inplaceeditor-empty' | 上記文字列のクラス名。スタイルシートで色をグレーにするなどの指定が可能。 |