IEでテキストエリアの文字列をタグで囲む

ブログ投稿画面などのエディタで、選択文字列を`<strong>強調</strong>`とかって囲んだりする機能がありますが、それをIEでやるときの話です。

例えばこのはてなブログの見出しボタンとかもそうですね。
h1とかで囲んでくれます。
![00c5d2d0b9eb143833996b448b704c9c.png](https://qiita-image-store.s3.amazonaws.com/0/28556/85dc80bd-b65e-ca1c-7778-7123b160458f.png)
BとかIとかもです。

[textareaの選択範囲を取得し、前後に文字列を挿入する](http://archiva.jp/web/javascript/getRange_in_textarea.html)

検索すると参考サイトとして上のようなページがあるのですが、2007年の記事でして、しかもうまく動きません。
![2.png](https://qiita-image-store.s3.amazonaws.com/0/28556/6c61dc4b-9a8e-3d8d-80b5-a4d19d2d7288.png)
「筍子」を押してstrongボタンを押すと、その後ろの「。学」が囲まれちゃうようです。

テキストエリアの１行目だと動いていて、２行目、３行目と１文字ずつずれてしまうようです。
ということは改行コードですね。

[テキストエリアの選択範囲の位置を特定する](http://www.studio-freesky.net/programming/javascript/3/)

そこで検索してこの記事を見つけました。2006年の記事で、IEに対比するものとしてNNとか書いてあります。
NNに続くMozilla系だとテキストエリアの改行コードが¥nなんですが、IEだと¥r¥nになっちゃうようです。

ということで、1件目の記事のコードを2件目の記事を参考に書き換えてみました。
変更箇所が下記になります。選択文字列の地点を取るgetAreaRangeメソッドの、if(isIE)の辺りです。

```js:enclose.js
...
            obj.focus();
            var range = document.selection.createRange();
            var len = range.text.replace(/\r/g, "").length;
            var clone = range.duplicate();
             
            clone.moveToElementText(obj);
            
            var all_len = clone.text.replace(/\r/g, "").length;
            clone.setEndPoint( 'StartToStart', range );
            
            var s = all_len - clone.text.replace(/\r/g, "").length;
            
            pos.start = s;
            pos.end = s + len;
...
```
変更点としては、text.lengthを全て、text.replace(/\r/g, "").lengthにしたのと、
setEndPoint("EndToEnd" をsetEndPoint("StartToStart" にしました。

デモページを作ったところ、正常に選択範囲が囲まれました。
![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/28556/28b71823-3262-7a74-a73d-5577598947fa.png)


デモのHTML全文は下記のようになります。

```html:demo.html
<html>
<head>
<script type="text/javascript">
window.onload = function() {

    var enc = document.getElementById("enclose");
    enc.onclick = function() {
        surroundHTML("div", "txt");
    };



    function getAreaRange(obj) {
        var pos = new Object();
         
        if (isIE) {
            obj.focus();
            var range = document.selection.createRange();
            var len = range.text.replace(/\r/g, "").length;
            var clone = range.duplicate();
             
            clone.moveToElementText(obj);
            
            var all_len = clone.text.replace(/\r/g, "").length;
            clone.setEndPoint( 'StartToStart', range );
            
            var s = all_len - clone.text.replace(/\r/g, "").length;
            
            pos.start = s;
            pos.end = s + len;
            
        }
         
        else if(window.getSelection()) {
            pos.start = obj.selectionStart;
            pos.end = obj.selectionEnd;
        }
         
        return pos;
    }
    var isIE = (navigator.appName.toLowerCase().indexOf('internet explorer')+1?1:0);

    function surroundHTML(tag, obj) {
        var target = document.getElementById(obj);
        var pos = getAreaRange(target);
         
        var val = target.value;
        var range = val.slice(pos.start, pos.end);
        var beforeNode = val.slice(0, pos.start);
        var afterNode = val.slice(pos.end);
        var insertNode;
         
        if (range || pos.start != pos.end) {
            insertNode = '<' + tag + '>' + range + '</' + tag + '>';
            target.value = beforeNode + insertNode + afterNode;
        }
         
        else if (pos.start == pos.end) {
            insertNode = '<' + tag + '>' + '</' + tag + '>';
            target.value = beforeNode + insertNode + afterNode;
        }
    }

}
</script>
</head>
<body>
<div>
<input type="button" id="enclose" value="enclose" />
</div>

<div>
<textarea id="txt" rows="20" cols="100">
即興ソング
カメルーン
ブランチ
発送商品の場合、良品と交換いたします。不良品の返品にかかる送料は、弊社が負担いたします。ただし、良品在庫のない場合及び数量限定品の場合については個別にご相談させていただきます。 デジタルコンテンツ(PDF版書籍等)の場合は、再ダウンロード方法についてご案内いたします。 
</textarea>
</div>


</body>
</html>
```