textarea内のカーソル位置や行数を取得したり、指定行のみ置換したりなどをJavaScriptでサラリと出来るかな?と思ったら、それなりに大変だったという記録です。
一部jQueryオブジェクトですがjQueryの機能は使っていないので依存しない改造も簡単だと思います。
カーソル位置までの行数
テキストエリア内のカーソルはキャレットという名称です。キャレット位置の行数を取得する関数は用意されておらず、キャレット位置までの文字数のみが取得できます。日本語も1カウントです。その後、改行コードの数を調べることで行数を割り出します。
まず、文字数から行数を取得できる関数をStringクラスに追加しておきます。ブラウザによって異なる改行コードの差異も吸収します。
String.prototype.getLinefromCount = function(start){
// 文字数から行数を取得
var NotLF = /rn|r/g;
var region_byCaret = this.slice(0, start);
var CRLFs = region_byCaret.match(NotLF);
if (CRLFs) {
region_byCaret = region_byCaret.replace(NotLF, 'n');
}
var lines = region_byCaret.split('n');
return lines.length;
}
次にjQueryでtextareaからキャレット位置を取得・設定する関数 caretPos を定義します。 IE8,Firefox,Chromeで動作確認してあります。
selectionStart が使えないIEの為に getSelectionCount関数を定義しています。正確なスクロール位置の算出の為には、textarea の line-height を CSS で設定しておく必要があります。
- 使い方
-
var linenum = $("textarea").caretPos(); // キャレット位置の行数取得 $("textarea").caretPos( 先頭からの文字数 ); // キャレット位置を設定(スクロール対応)
(function($) {
var caretPos = function(pos) {
var item = this.get(0);
if (pos == null) {
return get(item);
} else {
set(item, pos);
return this;
}
};
var get = function(item) {
var CaretPos = 0;
if (item.selectionStart || item.selectionStart == "0") { // Firefox, Chrome
start = item.selectionStart;
} else if (document.selection) { // IE
start = getSelectionCount(item)[0];
}
if (isNaN (start)){
return;
}
return item.value.getLinefromCount( start );
};
var set = function(item, pos) {
if (item.setSelectionRange) { // Firefox, Chrome
item.setSelectionRange(pos, pos);
var lineNum = item.value.getLinefromCount( pos );
var lineHeight = item.style.lineHeight.slice(0,-2);
item.scrollTop = (lineNum-1) * parseInt(lineHeight);
item.focus();
} else if (item.createTextRange) { // IE
var range = item.createTextRange();
range.collapse(true);
range.moveEnd("character", pos);
range.moveStart("character", pos);
range.select();
}
};
$.fn.extend({caretPos: caretPos});
})(jQuery);
function getSelectionCount(textarea) {
var selectionRange = textarea.document.selection.createRange();
if (selectionRange == null || selectionRange.parentElement() !== textarea) {
return [ NaN, NaN ];
}
var value = arguments[1] || textarea.value;
var valueCount = value.length;
var range = textarea.createTextRange();
range.moveToBookmark(selectionRange.getBookmark());
var endBoundary = textarea.createTextRange();
endBoundary.collapse(false);
// endBoundary << range
if (range.compareEndPoints('StartToEnd', endBoundary) >= 0) {
return [ valueCount, valueCount ];
}
var normalizedValue = arguments[2] || value.replace(/rn|r/g, 'n');
var start = -(range.moveStart('character', -valueCount));
start += normalizedValue.slice(0, start).split('n').length - 1;
// range << endBoundary << range
if (range.compareEndPoints('EndToEnd', endBoundary) >= 0) {
return [ start, valueCount ];
}
// range << endBoundary
var end = -(range.moveEnd('character', -valueCount));
end += normalizedValue.slice(0, end).split('n').length - 1;
return [ start, end ];
}
[/code]
<h2>指定行のみ置換する</h2>
<p>JSのreplaceは最初にマッチしたものを置換しますが、指定行でマッチした場合のみ置換する関数を作りたいと思います。</p>
<p>まず指定行の文字列を取得する関数をStringクラスに追加しておきます。ブラウザによって異なる改行コードの差異も吸収します。</p>
[code]
String.prototype.getStrfromLine = function(line){
// 行数の文字列を取得
var body = this;
var NotLF = /rn|r/g;
var CRLFs = this.match(NotLF);
if (CRLFs) {
body = this.replace(NotLF, 'n');
}
var lines = body.split('n');
return lines[line-1];
}
次に正規表現文字をクオートする関数を追加しておきます。PHPのpreg_quoteと同じ仕事をしてくれます。
function preg_quote(str, delimiter){
// Quote regular expression characters plus an optional character
//
// version: 1107.2516
// discuss at: http://phpjs.org/functions/preg_quote
// + original by: booeyOH
// + improved by: Ates Goral (http://magnetiq.com)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + bugfixed by: Onno Marsman
// + improved by: Brett Zamir (http://brett-zamir.me)
// * example 1: preg_quote("$40");
// * returns 1: '$40'
// * example 2: preg_quote("*RRRING* Hello?");
// * returns 2: '*RRRING* Hello?'
// * example 3: preg_quote("\.+*?[^]$(){}=!<>|:");
// * returns 3: '\.+*?[^]$(){}=!<>|:'
return (str + '').replace(new RegExp('[.\\+*?\[\^\]$(){}=!<>|:\' + (delimiter || '') + '-]', 'g'), '\$&');
}
最後に指定行のみを置換する関数です。Stringクラスに追加しました。
String.prototype.replaceTargetLine = function(org, dest, line){
// 何行目の置換かを指定できる
var NotLF = /rn|r/g;
var preg = new RegExp(preg_quote(org));
if ( !line ) {
return this.replace(preg, dest);
}
var str = this.getStrfromLine(line);
if ( str ){
var body = this;
var CRLFs = this.match(NotLF);
if (CRLFs) {
body = this.replace(NotLF, 'n');
}
var lines = body.split('n');
lines[line-1] = str.replace( preg , dest );
return lines.join('n');
} else {
alert(line+"行目にn"+org+"nは存在しません。");
}
}
使い方は下のようになります。str内の文章の10行目の最初のbeforeをafterに置換します。
str.replaceTargetLine( 'before' , 'after' , 10 );
プログラミングで悩んだ時は
93%の回答率が売りのエンジニアのための無料Q&Aサイト「teratail」。長く悩んでも答えが出ない時の為に、登録しておけば助かるかもしれません。