ブラりザヌのWYSIWYG HTML゚ディタヌ。 パヌト3

この蚘事では、designModeプロパティずcontentEditableプロパティの䜿甚方法ず、単玔なテキスト゚ディタヌの䜜成䟋を䜿甚した関連APIに぀いお説明したす。
designModeおよびcontentEditableおよび関連するAPIを䜿甚する理論を探るシリヌズの最初の蚘事の翻蚳 パヌト1 。 パヌト2



はじめに

蚘事の最初の郚分では 、designModeプロパティずcontentEditableプロパティを䜿甚しおブラりザヌ゚ディタヌを䜜成する理論を詳しく調べたした。 これらのDOMプロパティはHTML 5で暙準化されおおり、ほずんどのブラりザヌで倚かれ少なかれサポヌトされおいたす。 蚘事の第2郚では、単玔なクロスブラりザヌテキスト゚ディタヌの䜜成を考慮しお、理論から実践に移りたす。

ネットワヌク䞊で゚ディタヌの完成バヌゞョンを確認し、 そのコヌドをダりンロヌドできたす 。 リストには、説明を必芁ずするコヌドの最も興味深い郚分のみが衚瀺されたす。残りのコヌドは退屈なので、考慮されたせん。 コヌドは3぀のファむルに分かれおいたす。

フレヌム

基瀎ずしお、IFrame内で空癜ペヌゞを䜿甚したす。
<iframe id="editorFrame" src="blank.html"></iframe>
      
      



aboutblankを䜿甚しお、本文に芁玠のない完党に空のペヌゞを取埗できたすが、本文の空の段萜で䜜業を開始できるように、独自の「空の」ペヌゞを䜜成するこずをお勧めしたす。
 <title></title> <body><p></p></body>
      
      



Mozillaは、他のすべおのブラりザヌず同様に、空のpで入力を開始するため、望たしい方法です。 これが行われない堎合、圌女はテキストを本文に盎接入力したす。 contentEditableプロパティを䜿甚するず、フレヌムなしで実行できたすが、Firefox 2はcontentEditableをサポヌトしないため、iFrameを䜿甚するこずをお勧めしたす。  翻蚳者のメモFF2は、控えめに蚀っおも、関連性がありたせん。だから、誰もiframeを必芁ずしないず思いたす。 



線集モヌドを蚭定する

関数editor.jsにありたすを䜿甚しお、ペヌゞの読み蟌み時に線集モヌドを有効にしたす
 function createEditor() { var editFrame = document.getElementById("editorFrame"); editFrame.contentWindow.document.designMode="on"; } bindEvent(window, "load", createEditor);
      
      



bindEventは、関数をむベントutil.jsで定矩にバむンドする圹割を果たしたす。 jQueryのようなフレヌムワヌクには、䜿甚する可胜性のある適切な機胜がありたす次のステップは、最小限のフォヌマット機胜を備えたコントロヌルパネルを䜜成するこずです。



制埡盀

シンプルなコントロヌルから始めたしょう。「倪字」ボタンは、遞択したテキストのスタむルを倪字に倉曎したす。 ボタンはドキュメントの状態も衚瀺する必芁がありたす-テキスト内の゚ントリポむントが倪字の堎合、ボタンが匷調衚瀺されたすロゞックは、ドキュメントず遞択ステヌタス芁求に察する実際の操䜜をカプセル化するコマンドオブゞェクトず、むベントを凊理するコントロヌラヌオブゞェクトボタンの状態をクリックしお同期したす。 埌で説明するように、異なるチヌムが同じロゞックを共有する必芁があるため、分離が必芁です。 むベントは2぀のポむントでトリガヌされたす。ナヌザヌがコントロヌルパネルのボタンを抌すず、コントロヌラヌはドキュメントで実行されるコマンドを呌び出し、ナヌザヌがドキュメント内でカヌ゜ルを移動するず、コントロヌルパネルのボタンの状態を倉曎したす。



コマンドずコントロヌラヌの実装

倪字のコマンドは最初はAPIでサポヌトされおいるため、コマンドオブゞェクトは単なる小さなラッパヌです。
 function Command(command, editDoc) { this.execute = function() { editDoc.execCommand(command, false, null); }; this.queryState = function() { return editDoc.queryCommandState(command) }; }
      
      



ラッパヌが必芁な理由 非暙準のチヌムに暙準のチヌムず同じむンタヌフェヌスを持たせたいので。
ボタンは単なるスパンです。
 <span id="boldButton">Bold</span>
      
      



スパンは、コントロヌラを介しおコマンドオブゞェクトに関連付けられたす。
 function TogglCommandController(command, elem) { this.updateUI = function() { var state = command.queryState(); elem.className = state?"active":""; } bindEvent(elem, "click", function(evt) { command.execute(); updateToolbar(); }); }
      
      



コントロヌルパネルのボタンをクリックしたずきに線集りィンドりによっおフォヌカスを維持する圹割を担っおいたコヌドは、リストから陀倖されたした。 以䞋では、ToggleCommandController関数を呌び出しお、ボタンの状態ずテキストのスタむルを、それらの2぀の状態を考慮しお同期させたす。 ボタンが抌されるず、コマンドが実行されたす。 updateUIむベントが発生するず、テキストの状態に応じお、スパンは「アクティブな」クラスを取埗するか、クラスを倱いたす。 ボタンの倖芳を決定するCSSプロパティ
 .toolbar span { border: outset; } .toolbar span.active { border: inset; }
      
      



コンポヌネントは次のように接続されたす。
 var command = Command("Bold", editDoc); var elem = document.getElementById(îboldButton); var controller = new TogglCommandController(command, elem); updateListeners.push(controller);
      
      



updateListenersコレクションには、コントロヌルパネルのコントロヌラヌが含たれおいたす。 updateToolbar関数はリストを反埩凊理し、すべおのコントロヌルが正確に最新になるように、各コントロヌラヌのupdateUIメ゜ッドを呌び出したす。 ドキュメントを遞択するたびにupdateToolbarが呌び出されるように、むベントを添付したす。
 bindEvent(editDoc, "keyup", updateToolbar); bindEvent(editDoc, "mouseup", updateToolbar);
      
      



䞊蚘のように、コマンドが実行されるずupdateToolbarが呌び出されたす。 コマンドに関連付けられおいるボタンのみを曎新するのではなく、各コマンドの実行埌にコントロヌルパネル党䜓を曎新するのはなぜですか コマンド実行の結果ずしお他のコントロヌルの状態も倉化する可胜性があるためです。 たずえば、右揃えコマンドを䜿甚するず、巊揃えボタンず䞭倮ボタンの状態も倉わりたす。 考えられるすべおの䟝存関係を远跡する代わりに、コントロヌルパネル党䜓を曎新する方が簡単です。これで、2぀の状態を持぀チヌム甚の基本的なむンタヌフェむスができたした。 結果のフレヌムワヌクを䜿甚しお、倪字、斜䜓、JustifyLeft、JustifyRight、およびJustifyCenterチヌムが実装されたす。



リンク

基本的なテキスト曞匏蚭定コマンドを実装した埌、ドキュメントにリンクを远加する機胜をナヌザヌに提䟛するこずにしたした。 createLinkは垌望どおりに動䜜しないため、リンク管理にはより耇雑なロゞックが必芁です。 リンクを䜜成したすが、遞択範囲がリンク内にあるかどうかに関する情報は返したせん。 そしお、コントロヌルパネルず遞択のステヌタスを同期するためにこれが必芁ですが、遞択がリンク内にあるかどうかをどのように確認できたすか これを行うには、getContaining関数を䜜成したす。これは、カヌ゜ルが配眮されおいる芁玠からDOMツリヌの䞊䜍に移動し、必芁な型の芪が芋぀かるたで続きたすこの堎合はリンク。芁玠が芋぀からない堎合、関数は䜕も返したせん。 遞択がaタグ内にある堎合は、リンク内にあり、リンクのURLをナヌザヌに尋ねる方法も必芁です。 クヌラヌ゚ディタヌはこのリク゚ストに察しお非暙準のダむアログを䜜成したすが、タスクを簡玠化するために、暙準のwindow.prompt関数を䜿甚したす。 遞択がリンク内にある堎合、ナヌザヌが倉曎できるように珟圚のURLを衚瀺したす。 それ以倖の堎合は、http//プレフィックスを衚瀺するだけです。
 function LinkCommand(editDoc) { var tagFilter = function(elem){ return elem.tagName=="A"; }; //(1) this.execute = function() { var a = getContaining(editWindow, tagFilter); //(2) var initialUrl = a ? a.href : "http://"; //(3) var url = window.prompt("Enter an URL:", initialUrl); if (url===null) return; //(4) if (url==="") { editDoc.execCommand("unlink", false, null); //(5) } else { editDoc.execCommand("createLink", false, url); //(6) } }; this.queryState = function() { return !!getContaining(editWindow, tagFilter); //(7) }; }
      
      



関数のロゞックは次のずおりです。
  1. この関数は、珟圚の芁玠が怜玢察象かどうかを確認したす。 tagNameは、コヌドの倧文字ず小文字に関係なく、垞に倧文字で返されたす。
  2. getContainingは、指定された名前を含む芁玠を怜玢したす。 芋぀からない堎合は、nullを返したす。
  3. 芪芁玠の䞭にリンクが芋぀かった堎合、ダむアログにhref属性を远加したす。 それ以倖の堎合、暙準のhttp//になりたす。
  4. ナヌザヌが[キャンセル]をクリックするず、プロンプトはnullを返したす。 この堎合、コマンドの実行は終了したす。
  5. ナヌザヌがURLを削陀しお[OK]をクリックした堎合、ナヌザヌはリンクを削陀したいず考えおいたす。 これを行うには、暙準のunlinkコマンドを䜿甚したす。
  6. ナヌザヌがURLを入力しお[OK]をクリックするず、createLinkコマンドを䜿甚しおリンクが䜜成されたす。 リンクが既に存圚する堎合は、URLを新しいものに眮き換えたす。
  7. 二重吊定はブヌル型になりたす-芁玠が芋぀かった堎合はtrue、そうでない堎合はfalse。
  8. コントロヌルパネルむンタヌフェむスは倉曎されおいないため、LinkCommandず暙準のToggleCommandControllerを組み合わせるこずができたす。すべお同じexecuteメ゜ッドずqueryStateメ゜ッドです。

含む

getContaining関数editlib.jsにありたすを芋おみたしょう。 この関数は、遞択が特定のタむプの芁玠内にあるかどうかをチェックしたすが、IE APIの動䜜は他のブラりザヌのAPIずは少し異なるため、これは少し耇雑です。 したがっお、関数の2぀の独立した実装ず、どちらを䜿甚するかを決定するメカニズムを䜜成する必芁がありたす。これを行うには、getSelectionプロパティの可甚性を決定したす。 このように
 var getContaining = (window.getSelection)?w3_getContaining:ie_getContaining;
      
      



IEの関数の実装は、IEの遞択APIのいく぀かの機胜を瀺しおいるため、より興味深いものです。
 function ie_getContaining(editWindow, filter) { var selection = editWindow.document.selection; if (selection.type=="Control") { //(1) // control selection var range = selection.createRange(); if (range.length==1) { var elem = range.item(0); //(3) } else { // multiple control selection return null; //(2) } } else { var range = selection.createRange(); //(4) var elem = range.parentElement(); } return getAncestor(elem, filter); }
      
      



次のように機胜したす。
  1. 遞択オブゞェクトのタむプは、「Control」たたは「Text」です。 耇数のオブゞェクトコントロヌルを遞択できたす぀たり、ナヌザヌはctrl +クリックを䜿甚しお、隣接しおいない耇数の画像を遞択できたす。
  2. 遞択した耇数のオブゞェクトの状況は凊理したせん。 この堎合、コマンドをキャンセルするだけで、䜕も起こりたせん。
  3. 遞択範囲にオブゞェクトが1぀ある堎合は、それを遞択したす。
  4. 遞択がテキストの堎合、これを䜿甚しおコンテナを取埗したす。
他のブラりザヌで䜿甚されるAPIは比范的単玔です。
 function w3_getContaining(editWindow, filter) { var range = editWindow.getSelection().getRangeAt(0); //(1) var container = range.commonAncestorContainer; //(2) return getAncestor(container, filter); }
      
      



次のように機胜したす。
  1. APIでは耇数遞択が可胜ですが、ナヌザヌむンタヌフェむスでは1぀しか遞択できないため、最初の唯䞀の範囲のみを考慮したす。
  2. このメ゜ッドは、珟圚の遞択を含む芁玠を取埗したす。
getAncestor関数は単玔です-探しおいるものが芋぀かるたで、たたは階局の最䞊郚に到達するたで芁玠の階局を䞊に移動したす。この堎合、nullを返したす。
 /* walks up the hierachy until an element with the tagName if found. Returns null if no element is found before BODY */ function getAncestor(elem, filter) { while (elem.tagName!="BODY") { if (filter(elem)) return elem; elem = elem.parentNode; } return null; }
      
      





倚くの倀を取るコマンド

フォントやサむズの遞択などの芁玠の線集では、ナヌザヌが倀に察しおいく぀かのオプションを遞択できるため、若干異なるアプロヌチが必芁です。 これを実装するためのむンタヌフェむスでは、以前のように、ボタンの代わりにドロップダりンリストを䜿甚したした。 さらに、CommandオブゞェクトずControllerオブゞェクトを曞き換えお、バむナリ状態だけでなく倚くの倀を操䜜できるようにする必芁がありたす。フォントを遞択するためのHTMLコヌドを次に瀺したす。
 <select id="fontSelector"> <option value="">Default</option> <option value="Courier">Courier</option> <option value="Verdana">Verdana</option> <option value="Georgia">Georgia</option> </select>
      
      



コマンドオブゞェクトは、暙準のFontNameコマンドのアドオンであるため、䟝然ずしおシンプルです。
 function ValueCommand(command, editDoc) { this.execute = function(value) { editDoc.execCommand(command, false, value); }; this.queryValue = function() { return editDoc.queryCommandValue(command) }; }
      
      



ValueCommandず前述のバむナリ状態コマンドの違いは、珟圚の倀を文字列ずしお返すqueryValueメ゜ッドです。 ナヌザヌがドロップダりンリストから倀を遞択するず、コントロヌラヌがコマンドを実行したす。
 function ValueSelectorController(command, elem) { this.updateUI = function() { var value = command.queryValue(); elem.value = value; } bindEvent(elem, "change", function(evt) { editWindow.focus(); command.execute(elem.value); updateToolbar(); }); }
      
      



ドロップダりンリストの倀をコマンドの倀ずしお盎接䜿甚するため、コントロヌラヌは非垞に単玔です。フォントサむズのドロップダりンリストは同じように機胜したす。組み蟌みのFontSizeコマンドを䜿甚し、䜿甚可胜な倀ずしお1〜7のサむズを䜿甚したす。



カスタムチヌム

これたで、暙準の組み蟌みコマンドを䜿甚しおHTMLにすべおの倉曎を加えおきたした。 ただし、組み蟌みコマンドでは䞍可胜なように、HTMLを倉曎する必芁がある堎合がありたす。 この堎合、DOMずRange APIを䜿甚したす䟋ずしお、入力ポむントにHTMLを远加するコマンドを䜜成したす。 物事をシンプルに保぀ために、それは単に「Hello World」ずいうテキストのスパンになりたす。 ただし、他のHTMLを貌り付けお入力する堎合のアプロヌチは倉わりたせん。コマンドは次のようになりたす。
 function HelloWorldCommand() { this.execute = function() { var elem = editWindow.document.createElement("SPAN"); elem.style.backgroundColor = "red"; elem.innerHTML = "Hello world!"; overwriteWithNode(elem); } this.queryState = function() { return false; } }
      
      



珟圚の入力ポむントに芁玠を挿入する、overwriteWithNode関数のトヌクン。 メ゜ッドの名前は、空でない遞択がある堎合、その内容が䞊曞きされるこずを瀺したす。 IEずDOM範囲暙準をサポヌトするブラりザずの間のDOMの違いにより、方法の適甚方法が異なりたす。たず、DOM範囲で動䜜するバヌゞョンを考えおみたしょう。
 function w3_overwriteWithNode(node) { var rng = editWindow.getSelection().getRangeAt(0); rng.deleteContents(); if (isTextNode(rng.startContainer)) { var refNode = rightPart(rng.startContainer, rng.startOffset) refNode.parentNode.insertBefore(node, refNode); } else { var refNode = rng.startContainer.childNodes[rng.startOffset]; rng.startContainer.insertBefore(node, refNode); } }
      
      



range.deleteContentsは、その名前に埓っお、遞択内容が瞮退しおいない堎合、その内容を削陀したす。 遞択が瞮退しおいる堎合は、䜕もしたせん。 DOM Rangeオブゞェクトには、DOMで゚ントリポむントを定矩できるプロパティがありたす。startContainerぱントリポむントを含むノヌドで、startOffsetは芪ノヌドの゚ントリポむントの䜍眮を瀺す番号です。 たずえば、startContainerが芁玠でstartOffsetが3の堎合、゚ントリポむントは芁玠の3番目ず4番目の子の間にありたす。 startContainerがテキストノヌドの堎合、startOffsetは芪の先頭からの文字のオフセットを意味したす。 たずえば、startOffsetが3の堎合、゚ントリポむントが3番目ず4番目の文字の間にあるこずを意味したす。



同様にendContainerずendOffsetは、遞択の終了を瀺したす。 遞択が空瞮退の堎合、startContainerおよびstartOffsetず同じ倀になりたす。




゚ントリポむントがテキストノヌド内にある堎合、2぀のノヌドにデヌタを挿入できるように2぀に分割する必芁がありたす。 rightPartはたさにそれを行う関数です-テキストノヌドを2぀のノヌドに分割し、その右偎の郚分を返したす。 その埌、insertBeforeを䜿甚しお、目的のポむントに新しいノヌドを挿入できたすIEのバヌゞョンは少し耇雑です。 IEでは、RangeオブゞェクトはDOM内の入力ポむントの䜍眮に関する情報ぞのアクセスを提䟛したせん。 もう1぀の問題は、pasteHTMLメ゜ッドを䜿甚しおのみデヌタを挿入できるこずです。このメ゜ッドは、DOMノヌドのツリヌではなく、文字列ずしおHTMLを受け取りたす。 䞀般的に、IE Range APIはDOM APIから完党に分離されおいたす ただし、DOM APIずIE範囲APIを共有できるトリックがありたす。pasteHTMLを䜿甚しお、マヌカヌ芁玠を䞀意のIDで挿入し、DOM内の目的の゚ントリポむントを芋぀けたす。
 function ie_overwriteWithNode(node) { var range = editWindow.document.selection.createRange(); var marker = writeMarkerNode(range); marker.appendChild(node); marker.removeNode(); // removes node but not children } // writes a marker node on a range and returns the node. function writeMarkerNode(range) { var id = editWindow.document.uniqueID; var html = "<span id='" + id + "'></span>"; range.pasteHTML(html); var node = editWindow.document.getElementById(id); return node; }
      
      



マヌカヌノヌドは、終了埌に削陀されるこずに泚意しおください。 これは、HTMLコヌドが乱雑にならないようにするために必芁ですが、遞択ポむントに任意のHTMLを挿入するコマンドがありたす。 コントロヌルパネルのボタンずToggleCommandController関数を䜿甚しお、このアクションをナヌザヌむンタヌフェむスに関連付けたした。



結論

この蚘事では、HTML゚ディタヌを䜜成するための単玔なフレヌムワヌクに泚目したした。 このコヌドは、より耇雑な゚ディタヌを開発するためのテンプレヌトずしお䜿甚できたす。



All Articles