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