web-dev-qa-db-ja.com

CKEditorでカーソルを特定の位置に設定します

CKEditor内でカーソル位置を既知のインデックスに設定する方法はありますか?

これを実行したいのは、エディター内でhtmlを変更すると、挿入された要素の先頭にカーソルがリセットされるためです。これは、ユーザーが入力するときにコンテンツをその場で変更するため、問題になります。

カーソルを既知の文字位置、たとえばエディター内の100に戻したいことがわかっている場合、これは可能ですか?

(私は 関連する質問 を尋ねましたが、サンプルコードで問題を複雑にしすぎていたと思います。)

15
manannan

選択を設定する基本的な方法は、 作成 a 範囲 、その位置を設定し、 選択 それです。

:Range API(または少なくとも範囲の背後にあるアイデア)がわからない場合、selectionを使用することはできません。これはかなり良い紹介です--- DOM Range spec (はい、それは仕様ですが、それは良いです)。 CKEditorのRange API は非常に似ていますが、少し大きくなっています。

例えば:

// Having this HTML in editor:
// <p id="someId1">foo <em id="someId2">bar</em>.</p>

var range = editor.createRange();
range.setStart( editor.document.getById( 'someId1' ), 0 ); // <p>^foo
range.setEnd( editor.document.getById( 'someId2' ).getFirst(), 1 ); // <em>b^ar</em>

editor.getSelection().selectRanges( [ range ] );

// Will select:
// <p id="someId1">[foo <em id="someId2">b]ar</em>.</p>

または他の場合:

// Having this HTML in editor:
// <p>foo bar.</p>
var range = editor.createRange();
range.moveToElementEditablePosition( editor.editable(), true ); // bar.^</p>

editor.getSelection().selectRanges( [ range ] );

// Will select:
// <p>foo bar.^</p>

DOMを変更した後の選択の復元

しかし、多くの場合、新しい範囲を選択するのではなく、古い選択または範囲を復元する必要があります。最初に知っておく必要があるのは、それが制御されていないDOM変更を行った場合に選択を正しく復元することは不可能であるということです。コンテナと選択範囲の開始と終了のオフセットを追跡できる必要があります。

Rangeは、開始コンテナと終了コンテナへの参照を保持します(startContainerおよびendContainerプロパティ内)。残念ながら、この参照は次の方法で違反される可能性があります。

  • innerHTMLを上書きします、
  • dOMノードを移動し、
  • dOMノードを削除します。

オフセット(startOffsetおよびendOffsetプロパティ)でも同じことが起こる可能性があります-開始/終了コンテナの子ノードの1つを削除した場合、これらのオフセットを更新する必要がある場合があります。

そのため、状況によっては、選択位置を記憶したい場合に範囲インスタンスが役に立たないことがあります。この問題に対処するための3つの基本的な方法を説明します。

まず、これが私たちの計画です。

  1. 現在の選択位置を取得します。
  2. (どういうわけか)保管します。
  3. DOMの変更を行います。
  4. 選択を復元します。

注: Firefoxは複数の範囲選択をサポートしているため、今後は複数形で「範囲」を使用します。1つの選択に複数の範囲を含めることができます(たとえば、選択中にCTRLキーを使用してみてください)。

解決策1-範囲別

var ranges = editor.getSelection().getRanges();

// Make DOM changes.

editor.getSelection().selectRanges( ranges );

これが最も簡単な解決策です。これは、行ったDOMの変更で範囲が古くなっていないか、更新方法がわかっている場合にのみ機能します。

解決策2-邪魔なブックマークによる

var bookmarks = editor.getSelection().createBookmarks();

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

createBookmarks メソッドによって作成されたブックマークは、選択範囲の開始点と終了点に特別な属性(<span>を含む)を持つ非表示のdata-cke-bookmark要素を挿入します。

制御されていないinnerHTMLの変更を回避し、代わりに一部のノードを追加/削除/移動できる場合は、これらの<span>要素を保持する必要があることを覚えておいてください。この方法は、完全に機能します。変更によって選択が変更される場合は、ブックマークの要素を移動することもできます。

デフォルトでは、ブックマークは<span>要素への参照を保持しますが、truecreateBookmarksメソッドに渡すシリアル化可能なブックマークを作成することもできます。この種のブックマークはIDによるノードへの参照を保持するため、innerHTML全体を上書きできます。

注:このメソッドは、 Range API でも使用できます。

これは最も一般的な方法です。ブックマークのspansを処理する必要がありますが、選択を完全に制御でき、DOMを変更できるためです。

解決策3-邪魔にならないブックマークによる

var bookmarks = editor.getSelection().createBookmarks2();

// Make DOM changes.

editor.getSelection().selectBookmarks( bookmarks );

注:このソリューションでは、 createBookmarks2 メソッドを使用します。

ここでは、ブックマークオブジェクトの配列も作成しますが、DOMには要素を挿入しません。これらのブックマークは、アドレスごとに位置を保存します。 Address は、親の祖先のインデックスの配列です。

このソリューションはソリューション1と非常に似ていますが、ブックマークのノードのアドレスを変更しないため、innerHTML全体を上書きできます(ほとんどの場合;>)。ただし、このような場合は、truecreateBookmarks2に渡して正規化されたアドレスを取得する必要があります。これは、innerHTMLを設定すると、隣接するテキストノードが結合され、空のノードが削除されるためです。

総括する...

... DOMと選択の操作は簡単ではありません。あなたは自分が何をしているのかを知る必要があり、DOMを知る必要があり、そしてあなたの問題に対して正しい解決策を選ぶ必要があります。ほとんどの場合、2番目になりますが、場合によって異なります。

50
Reinmar

Reinmarからの回答が私をこの解決策に導きました

var selection = ed.getSelection();
var bookmarks = selection.createBookmarks(true);

//delete text from editor

var range = selection.getRanges()[0];
range.moveToBookmark(bookmarks[0]);
range.select();

注:moveToBookmark関数はAPIに記載されていませんが、非常に便利で、私にとって有効な唯一のソリューションでした。私は確かにckeditorの専門家ではなく、実用的な解決策を見つけるのに数日かかりました。したがって、moveToBookmarkは、非推奨の関数である可能性があります。

7
codeAline