X-Git-Url: http://git.osdn.jp/view?p=pettanr%2FclientJs.git;a=blobdiff_plain;f=0.6.x%2Fjs%2F02_dom%2F30_XTextRange.js;fp=0.6.x%2Fjs%2F02_dom%2F30_XTextRange.js;h=a2fc5520798be1256b7b8937897eadae1193465d;hp=d6f88374229fd995cece6b10627efab37fa3bf6a;hb=ef25747bebf1799d49f9bd0d64e339da9ea61d13;hpb=d56e8cc1e13089eb6cbc9dcc6900d7f1828b93df diff --git a/0.6.x/js/02_dom/30_XTextRange.js b/0.6.x/js/02_dom/30_XTextRange.js index d6f8837..a2fc552 100644 --- a/0.6.x/js/02_dom/30_XTextRange.js +++ b/0.6.x/js/02_dom/30_XTextRange.js @@ -2,13 +2,16 @@ * tr = X.Doc.createRange('selection') * X.Doc.createRange({from : num, to : num}) * tr = xnode.createRange( from, to ), - *    ( 'selection' ) docment の slection のうち xnode の配下のもの - * ( 'char', from, to ) - * ( 'line', index ), - * ( 'point', x, y ) | ( 'point', e ) - * tr.move( startIndex, endIndex ) - * tr.getRect() { width, height, x, y } - * tr.getOffset() { from, to } + *    ( 'selection' ) docment の slection のうち xnode の配下のもの, textarea, input, iframe[desineMode] の場合、選択範囲またはカーソル位置 + * ( 'select', from[, to] ) 選択する + * ( 'char', from[, to] ) + * ( 'lineAt', index ), + * ( 'line', x, y ), | ( 'line', pointerEvent ) + * ( 'point', x, y ) | ( 'point', pointerEvent ) + * tr.move( from, to ) + * tr.select( true | false ) + * tr.getRect() { width, height, x, y } -> tr.mesure() + * tr.getOffset() { from, to } * tr.text() * * naming は mozilla に寄せる @@ -27,6 +30,8 @@ var X_TextRange_range, var X_TextRange = X_Class_create( 'X.TextRange', + // TODO コールバックの最後に破棄されるクラス 1刻みの間存在するクラス. X.XML も + /** @lends X.TextRange.prototype */ { xnode : null, @@ -57,9 +62,15 @@ var X_TextRange = X_Class_create( if( arg2 !== 'selection' ){ this.v1 = arg3 || 0; this.v2 = arg4 || 0; + } else { + this[ 'getOffset' ](); }; }, + 'move' : X_TextRange_move, + + 'select' : X_TextRange_select, + 'getRect' : X_TextRange_getRect, 'getOffset' : X_TextRange_getOffset, @@ -87,7 +98,7 @@ function X_TextRange_collectTextNodes( elm, ary ){ }; }; -function X_TextRange_getRawRange( tr ){ +function X_TextRange_getRawRange( tr, createFrom ){ var xnode = tr.xnode, // range = 10 <= X_UA[ 'IE' ] /* || X_UA[ 'iOS' ] */ ? document.createRange() : @@ -101,11 +112,20 @@ function X_TextRange_getRawRange( tr ){ elm = xnode[ '_rawObject' ]; - switch( tr.createFrom ){ + switch( createFrom || tr.createFrom ){ case 'selection' : if( X_TextRange_isW3C ){ selection = window.getSelection(); - return selection.rangeCount && selection.getRangeAt( 0 ); + + if( selection.getRangeAt ){ + return selection.rangeCount && selection.getRangeAt( 0 ); + }; + // http://d.hatena.ne.jp/dayflower/20080423/1208941641 + // for Safari 1.3 + range = document.createRange(); + range.setStart( selection.anchorNode, selection.anchorOffset ); + range.setEnd( selection.focusNode, selection.focusOffset ); + return range; } else { switch( document.selection.type ){ case 'text' : @@ -139,10 +159,10 @@ function X_TextRange_getRawRange( tr ){ }; if( rect.left <= x && x <= rect.right && rect.top <= y && y <= rect.bottom ){ return { - hitRange : range, - rect : rect, - offset : offset, - text : text + 'hitRange' : range, + 'rect' : rect, + 'offset' : offset, + 'text' : text }; }; }; @@ -153,14 +173,14 @@ function X_TextRange_getRawRange( tr ){ // 未チェック! range.setEnd( elm, l < tr.v2 ? l : tr.v2 ); range.setStart( elm, tr.v1 ); - return { hitRange : range }; + return { 'hitRange' : range }; }; } else { // !save && ( text = text.split( '\r\n' ).join( '\n' ) ); textarea用 if( isPoint ){ // ie11 の ie10モード で moveToPoint がないといわれる. よって isW3C:false で動作するのは ie9 以下 range.moveToPoint( tr.v1, tr.v2 ); - if( !range.expand( 'character' ) ) range = null; + if( !range.duplicate().expand( 'character' ) ) range = null; } else { range.moveToElementText( elm ); //range.collapse( true ); @@ -179,14 +199,16 @@ function X_TextRange_getRect(){ if( result ){ if( X_TextRange_isW3C ){ - rect = result.hitRange.getBoundingClientRect(); - ret = { - 'x' : rect.left, - 'y' : rect.top, - 'width' : rect.width, - 'height' : rect.height + if( result.hitRange ){ + rect = result.hitRange.getBoundingClientRect(); + ret = { + 'x' : rect.left, + 'y' : rect.top, + 'width' : rect.width, + 'height' : rect.height + }; + //range.detach && range.detach(); }; - //range.detach && range.detach(); } else { ret = { 'x' : result.boundingLeft, @@ -216,46 +238,68 @@ function X_TextRange_collectXTexts( xnode, ary ){ }; function X_TextRange_getOffset(){ - var result = X_TextRange_getRawRange( this ), - range, ret, all, from, xtexts, n, i, l, xtext; + var xnode = this.xnode, + elm, result, range, + ret, from, xtexts, n, i, l, xtext; - if( result ){ + if( xnode[ '_tag' ] === 'TEXTAREA' ){ + elm = xnode[ '_rawObject' ]; + + if( elm && xnode[ '_flags' ] & X_NodeFlags_IN_TREE ){ + if( X_UA[ 'IE' ] < 9 ){ + + + return cursorPosition.call( this, elm ); + + } else if( elm.setSelectionRange ){ + if( X_UA[ 'IE' ] < 12 ){ + l = elm.value.length; + ret = { + 'from' : this.v1 = elm.selectionStart < l ? elm.selectionStart : l, + 'to' : this.v2 = elm.selectionEnd < l ? elm.selectionEnd : l + }; + } else { + ret = { + 'from' : this.v1 = elm.selectionStart, + 'to' : this.v2 = elm.selectionEnd + }; + }; + }; + }; + } else + if( result = X_TextRange_getRawRange( this ) ){ if( X_TextRange_isW3C ){ range = result.hitRange; ret = { 'offset' : result.offset, - 'from' : range.startOffset, - 'to' : range.endOffset, + 'from' : this.v1 = range.startOffset, + 'to' : this.v2 = range.endOffset, 'text' : X_Node_getXNode( result.text ) }; // range.detach && range.detach(); } else { // http://www.studio-freesky.net/programming/javascript/3/ - range = X_TextRange_range2; - //var _rang = X_elmBody.createTextRange(); + range = X_TextRange_range2; + range.moveToElementText( xnode[ '_rawObject' ] ); + range.setEndPoint( 'EndToStart', result ); + from = range.text.length; - //_rang.moveToElementText( this.xnode.getChildAt(1)[ '_rawObject' ] ); - range.moveToElementText( this.xnode[ '_rawObject' ] ); - range.setEndPoint( 'EndToStart', result ); //_rang )// - from = range.text.length;// - result.text.length; - - X_TextRange_collectXTexts( this.xnode, xtexts = [] ); + X_TextRange_collectXTexts( xnode, xtexts = [] ); if( xtexts.length ){ - // 改行が入ると正しく startIndex を取ることができない... - for( n = l = 0, i = -1; xtext = xtexts[ ++i ]; ){ - l = xtext[ '_text' ].length; + for( n = 0, i = -1; xtext = xtexts[ ++i ]; ){ + l = xtext[ '_rawObject' ].data.length; if( from < n + l ){ break; }; n += l; }; - + ret = { - 'offset' : n, - 'from' : from - n, - 'to' : from - n + result.text.length, + 'offset' : n, // elm の何個目の node か? + 'from' : this.v1 = from - n, + 'to' : this.v2 = from - n + result.text.length, 'text' : xtext }; }; @@ -266,10 +310,222 @@ function X_TextRange_getOffset(){ }; function X_TextRange_text( v ){ + var xnode = this.xnode, elm, val, offset, from, to; + if( v === undefined ){ } else { + if( xnode[ '_tag' ] === 'TEXTAREA' ){ + elm = xnode[ '_rawObject' ]; + val = X_UA[ 'IE' ] < 9 ? X_Node_Attr_getValueForIE( elm ) : elm.value; + + if( this.createFrom === 'char' ){ + xnode.attr( { + 'value' : val.substr( 0, this.v1 ) + v + val.substr( this.v2 ) + } ); + } else { + offset = this[ 'getOffset' ](); + + from = offset[ 'from' ]; + to = offset[ 'to' ]; + + if( X_UA[ 'IE' ] < 9 ){ + range = document.selection.createRange(); + // TODO check textarea + range.text = v; + // ここには range.text がいない https://msdn.microsoft.com/ja-jp/library/cc427934.aspx + } else { + val = val.substr( 0, from ) + v + val.substr( to ); + elm.value = val; + }; + + if( to !== from ){ + // カーソル位置を挿入した文字列の最後へ + to = from + v.length; + } else { + // カーソル位置を挿入した文字列の後ろへ + to += v.length; + }; + this.move( to, to ); + }; + }; + }; +}; + +function X_TextRange_move( from, to ){ + var xnode = this.xnode, + elm = xnode[ '_rawObject' ], + len, range; + + if( 0 <= from ){ + this.v1 = from; + } else { + this.v1 = this.v1 + from; + this.v1 < 0 && ( this.v1 = 0 ); + }; + + if( X_Type_isNumber( to ) ){ + if( 0 <= to ){ + this.v2 = to; + } else { + this.v2 = this.v2 + to; + this.v2 < this.v1 && ( this.v2 = this.v1 ); + }; + }; + + if( xnode[ '_tag' ] === 'TEXTAREA' ){ + // http://blog.enjoyxstudy.com/entry/20060305/p1 + if( X_UA[ 'IE' ] < 9 || X_UA[ 'Opera' ] ){ + len = ( X_UA[ 'IE' ] < 9 ? X_Node_Attr_getValueForIE( elm ) : elm.value ).length; + + if( X_UA[ 'Opera' ] ){ + X_EventDispatcher_ignoreActualEvent = 'focus'; + elm.focus(); // Operaの為(IEでは無くても大丈夫) + X_EventDispatcher_ignoreActualEvent = ''; + }; + + range = elm.createTextRange(); + + if( this.v1 === this.v2 && this.v1 === 0 ){ + range.collapse( true ); // 先頭に移動 + } else { + if( this.v1 !== this.v2 || this.v1 < len ){ + range.collapse(); // おまじない? + + if( this.v1 === this.v2 ){ + range.move( 'character', this.v1 ); + } else { + range.moveEnd( 'character', this.v2 ); + range.moveStart( 'character', this.v1 ); + }; + } else { + range.collapse( false ); // 末美に移動 + }; + }; + range.select(); + + } else if( elm.setSelectionRange ){ + elm.setSelectionRange( this.v1, this.v2 ); + }; }; }; +function X_TextRange_select( v ){ + +}; + +// http://www.studio-freesky.net/programming/javascript/3/ +// それは、IEのTextRangeオブジェクトで取得した範囲にもしラストに改行コード¥r¥nがあった場合それが含まれないのです。(視覚的な選択範囲には含まれています) + +// https://web.archive.org/web/20090904134938/http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ + // https://web.archive.org/web/20090904183807/http://www.dedestruct.com/cursorPosition.html + function cursorPosition( textarea ){ + + var selection_range = document.selection.createRange().duplicate(); + + + if (selection_range.parentElement() !== textarea) { + // TODO 正しくはカーソル位置・選択範囲の復帰 + + X_EventDispatcher_ignoreActualEvent = 'focus'; + textarea.focus(); + X_EventDispatcher_ignoreActualEvent = ''; + + // BODY要素のテキスト範囲を作成する + selection_range = X_elmBody.createTextRange(); + + // BODY要素のテキスト範囲をeのテキスト範囲に移動する + // これはe.createTextRange()とほぼ同等 + selection_range.moveToElementText( textarea ); + + selection_range.collapse( true ); // 末美に移動 + selection_range.select(); + }; + + //if (selection_range.parentElement() == textarea) {// Check that the selection is actually in our textarea + // Create three ranges, one containing all the text before the selection, + // one containing all the text in the selection (this already exists), and one containing all + // the text after the selection. + var before_range = X_elmBody.createTextRange(); + before_range.moveToElementText(textarea); + // Selects all the text + before_range.setEndPoint('EndToStart', selection_range); + // Moves the end where we need it + + var after_range = X_elmBody.createTextRange(); + after_range.moveToElementText(textarea); + // Selects all the text + after_range.setEndPoint('StartToEnd', selection_range); + // Moves the start where we need it + + var before_finished = false, selection_finished = false, after_finished = false; + var before_text, untrimmed_before_text, selection_text, untrimmed_selection_text, after_text, untrimmed_after_text; + + // Load the text values we need to compare + before_text = untrimmed_before_text = before_range.text; + selection_text = untrimmed_selection_text = selection_range.text; + after_text = untrimmed_after_text = after_range.text; + + // Check each range for trimmed newlines by shrinking the range by 1 character and seeing + // if the text property has changed. If it has not changed then we know that IE has trimmed + // a \r\n from the end. + do { + if (!before_finished) { + if (before_range.compareEndPoints('StartToEnd', before_range) == 0) { + before_finished = true; + } else { + before_range.moveEnd('character', -1); + if (before_range.text == before_text) { + untrimmed_before_text += '\r\n'; + } else { + before_finished = true; + } + } + } + if (!selection_finished) { + if (selection_range.compareEndPoints('StartToEnd', selection_range) == 0) { + selection_finished = true; + } else { + selection_range.moveEnd('character', -1); + if (selection_range.text == selection_text) { + untrimmed_selection_text += '\r\n'; + } else { + selection_finished = true; + } + } + } + if (!after_finished) { + if (after_range.compareEndPoints('StartToEnd', after_range) == 0) { + after_finished = true; + } else { + after_range.moveEnd('character', -1); + if (after_range.text == after_text) { + untrimmed_after_text += '\r\n'; + } else { + after_finished = true; + } + } + } + + } while ((!before_finished || !selection_finished || !after_finished)); + + // Untrimmed success test to make sure our results match what is actually in the textarea + // This can be removed once you're confident it's working correctly + /* + var untrimmed_text = untrimmed_before_text + untrimmed_selection_text + untrimmed_after_text; + var untrimmed_successful = false; + if (textarea.value == untrimmed_text) { + untrimmed_successful = true; + } */ + // ** END Untrimmed success test + + var startPoint = untrimmed_before_text.split( '\r' ).join( '' ).length; + // alert(startPoint); + return { + 'from' : this.v1 = startPoint, + 'to' : this.v2 = startPoint + untrimmed_selection_text.split( '\r' ).join( '' ).length + }; + //} + } +