如何在可满足内容的元素的文本中重复选择给定字符串
How to select a given string repeatedly within the text of a contenteditable element
我已经做了几个小时的搜索来回答这个问题,但我没有找到任何合适的答案,所以我决定尝试编写自己的JQuery插件来完成这项工作。
要求如下:
- 选择任意字符串(即'abc'),它从已经存在于contentitable元素中的文本中传递给函数。
- 选择应该从活动插入符号位置后元素中该字符串的第一个实例开始,并在下次调用该函数时移动到同一字符串的下一个实例,无论第一个选择的实例是否被编辑为其他内容(即:'abc'被用户更改为'xyz'。
- 字符串应该在文本节点中找到,而不是在元素节点或其属性节点中找到,以便使用类名和带有id的引用进行适当的样式化。
- 应该选择整个字符串
- 搜索应该仅限于可满足内容的元素。
例如,如果我有
<div id='main' class='main' contenteditable>
<p><span class='lead'>Date:</span>abc def abc 123 abc</p>
</div>
和我在"def"上有插入符号,然后运行函数,第二个"abc"应该被选中。如果我不移动插入符号并再次运行该函数,则应该选中第三个"abc"。
尽管我的插件的目标更广泛一些,但我惊讶地发现,甚至在一个可满足内容的div中选择给定的字符串都没有通用的解决方案。
我将在下面发布我当前的JQuery插件的工作版本。运行代码片段,将插入符号放入可内容编辑的潜水中,然后按F9运行函数并选择文本。正如您所看到的,该解决方案不是很优雅,并且仅当光标位于特定位置时才有效。我必须假设有一个更优雅的解决方案,理想情况下甚至可以接受RegEx而不是字符串。我尝试用递归实现这个功能,但是我找不到一种方法在找到文本字符串后可靠地退出递归循环。有没有更好的办法?我欢迎你的想法。
document.getElementById('main').onkeydown = function(e) {
switch (e.keyCode) {
case 120: {
// F9 to select next %fill%
$('#main').selecttarget('%fill%');
}
}
};
//JQuery plugin
jQuery.fn.selecttarget = function(target) {
// get collection of all elements of the object passed (this)
// de-jQuery into its DOM object by using this[0]
var items = this[0].getElementsByTagName("*");
// get the node at which the cursor currently resides
var startNode = null;
if (window.getSelection) {
startNode = window.getSelection().getRangeAt(0).commonAncestorContainer;
startNode = (startNode.nodeType===1) ? startNode : startNode.parentNode;
}
// get the startNodes ancestry and iterate up through it until the passed element is found
// once the passed element is found, we can step back down a level to find the ancestor
// that is the immediate child of the passed element
var parentEls = $(startNode).parents();
var level = 0;
for (var i = 0; i < parentEls.length; i++) {
if (parentEls[i] == this[0]) {
level = i - 1;
break;
}
}
// the above method works somewhat well except if the caret is on the first span, then
// it misses the first %fill%
// get the index of the node at which the cursor currently resides amongst its siblings
var index = Array.prototype.indexOf.call(items, parentEls[level]);
// iterate through each child of the passed element looking for one that contains the
// text we're looking for in target
for (var i = index; i < items.length; i++) {
var position = items[i].innerText.indexOf(target);
if (position >= 0) {
// if an appropriate element is found, iterate through
// its child nodes to look for a text node with the
// text we're looking for in target
for (var j = 0; j < items[i].childNodes.length; j++) {
var node = items[i].childNodes[j];
if (node.nodeType == 3) {
position = node.data.indexOf(target);
if (position >= 0) {
// if a text node with the appropriate text
// is found, create a range and set its boundaries
var range = document.createRange()
range.setStart(node, position);
range.setEnd(node, position + target.length);
// create a selection based on the range
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
// exit everything
return true;
}
}
}
}
}
};
div.main {
text-align: left;
width: 90%;
height: 500px;
padding: 5px;
overflow: scroll;
border: solid thin cornflowerblue;
}
span.lineheader {
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='main' class='main' contenteditable>
<p><span class='lineheader'>Date:</span> %fill%</p>
<p><span class='lineheader'>Item 1:</span> %fill%</p>
<p><span class='lineheader'>Item 2:</span> %fill%</p>
<p><span class='lineheader'>List 2:</span>
<ul>
<li>%fill%</li>
<li>%fill%</li>
<li>%fill%</li>
</ul>
</p>
<p><span class='lineheader'>Item 2:</span> Sed convallis massa augue. Vivamus a enim vitae eros mattis dignissim ac non velit. Donec porta %fill% tellus in justo viverra rhoncus. Nullam et sapien dapibus, eleifend elit ac, commodo est. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum sagittis quis massa vitae bibendum. Maecenas placerat mi eget arcu aliquam, id %fill% accumsan nisi dictum.
Vestibulum consequat porttitor nisl eu ultrices. Donec non blandit tortor. Aliquam erat volutpat. Suspendisse id ante et felis porttitor convallis. Sed.</p>
</div>
经过更多的时间研究,我偶然发现了Tim Down的Rangy JavaScript范围和选择库(https://github.com/timdown/rangy)。事实证明,这个库非常有用,它有助于在给定元素的所有子节点中查找字符串的下一次出现。
首先,我使用条件语句来处理在可满足元素中按F9键时选择/插入符号状态的下列可能性:
- 搜索字符串的实例已经被选中
- 已经选择了其他内容,但没有选择与搜索字符串不匹配
- 未选择任何内容,但插入符号位于可内容元素 中的某个位置。
- 插入符号不在contentitable元素中,但是contentitablediv有焦点(即用户选择了滚动条)
:
document.getElementById('main').onkeydown = function (e) {
switch (e.keyCode) {
case 120: {
// F9 to select next fill_me
var sel = window.getSelection();
// this should refer to document.getElementById('main')
var target = 'fill_me';
if (sel.toString().search(target) > -1) {
// target is already selected; go to end of selection
findOne(target, this, sel.focusNode, sel.focusOffset);
}
else if (sel.toString().length > 0) {
// something is already selected but it's not target
// so go to start of selection
findOne(target, this, sel.anchorNode, sel.anchorOffset);
}
else if (sel.rangeCount) {
// nothing is selected, start search at cursor
findOne(target, this, sel.anchorNode, sel.getRangeAt(0).endOffset);
}
else {
// caret is not in the contenteditable element, but the contenteditable div has focus
findOne(target, this, null, null);
}
break;
}
}
};
在上面的每个结果中,使用Rangy的findText函数调用findOne函数来查找搜索字符串的适当实例:
function findOne(target, within, startNode, startPos) {
if (rangy.supported) {
var range = rangy.createRange();
var searchScopeRange = rangy.createRange();
var caseSensitive = true;
// assign the search scope range to the contents of
// the element passed as a parameter, within
searchScopeRange.selectNodeContents(within);
if (startNode != null && startPos != null) {
// set the start of the search scope range if
// passed parameters are not null; otherwise
// its start is the beginning of the range
searchScopeRange.setStart(startNode, startPos);
}
var options = {
caseSensitive: true,
wholeWordsOnly: true,
withinRange: searchScopeRange
};
range.selectNodeContents(within);
if (target !== "") {
target = new RegExp(target, caseSensitive ? "g" : "gi");
// Find first match
range.findText(target, options)
// translate Rangy nodes & offsets to use for selection
selectRange(range.startContainer, range.endContainer,
range.startOffset, range.endOffset);
}
}
function selectRange(startNode, endNode, startPos, endPos) {
// this function takes parameters and selects an appropriate
// within the DOM
var range = document.createRange()
range.setStart(startNode, startPos);
range.setEnd(endNode, endPos);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
}
整个工作项目包含在下面的代码片段中。感谢Tim Down出色的工作!
rangy.init();
document.getElementById('main').focus();
document.getElementById('main').onkeydown = function(e) {
switch (e.keyCode) {
case 120:
{
// F9 to select next %fill%
//$('#main').selecttarget('%fill%');var node;
var sel = window.getSelection();
var target = 'fill_me';
// this should refer to document.getElementById('main')
if (sel.toString().search(target) > -1) {
// target is already selected; go to end of selection
findOne(target, this, sel.focusNode, sel.focusOffset);
} else if (sel.toString().length > 0) {
// something is already selected but it's not target
// so go to start of selection
findOne(target, this, sel.anchorNode, sel.anchorOffset);
} else if (sel.rangeCount) {
// nothing is selected, start search at cursor
findOne(target, this, sel.anchorNode, sel.getRangeAt(0).endOffset);
} else {
// this should never really happen, but it's a catch-all
findOne(target, this, null, null);
}
break;
}
}
};
function findOne(target, within, startNode, startPos) {
if (rangy.supported) {
var range = rangy.createRange();
var caseSensitive = true;
var searchScopeRange = rangy.createRange();
// assign the search scope range to the contents of
// the element passed as a parameter, within
searchScopeRange.selectNodeContents(within);
if (startNode != null && startPos != null) {
// set the start of the search scope range if
// passed parameters are not null; otherwise
// its start is the beginning of the range
searchScopeRange.setStart(startNode, startPos);
}
var options = {
caseSensitive: true,
wholeWordsOnly: true,
withinRange: searchScopeRange
};
if (target !== "") {
target = new RegExp(target, caseSensitive ? "g" : "gi");
// Find first match
range.findText(target, options)
// translate Rangy nodes & offsets to use for selection
selectRange(range.startContainer, range.endContainer,
range.startOffset, range.endOffset);
}
}
function selectRange(startNode, endNode, startPos, endPos) {
// this function takes parameters and selects an appropriate
// within the DOM
var range = document.createRange()
range.setStart(startNode, startPos);
range.setEnd(endNode, endPos);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
}
div.main {
text-align: left;
width: 90%;
height: 500px;
padding: 5px;
overflow: scroll;
border: solid thin cornflowerblue;
}
span.lineheader {
font-weight: bold;
}
<script type="text/javascript" src="http://rangy.googlecode.com/svn/trunk/currentrelease/rangy-core.js"></script>
<script type="text/javascript" src="http://rangy.googlecode.com/svn/trunk/currentrelease/rangy-textrange.js"></script>
<p><span class='lineheader'>Instructions:</span> Press F9 to skip between instances of <i>'fill_me'</i> in the contenteditable div below</p>
<div id='main' tabindex='0' class='main' contenteditable>
<p><span class='lineheader'>Date:</span> fill_me.</p>
<p><span class='lineheader'>Item 1:</span> fill_me</p>
<p><span class='lineheader'>Item 2:</span> fill_me</p>
<p><span class='lineheader'>List 1:</span>
<ul>
<li>fill_me</li>
<li>fill_me</li>
<li>fill_me</li>
</ul>
</p>
<p><span class='lineheader'>Paragraph 1:</span> Sed convallis massa augue. Vivamus a enim vitae eros mattis dignissim ac non velit. Donec porta fill_me tellus in justo viverra rhoncus. Nullam et sapien dapibus, eleifend elit ac, commodo est. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum sagittis quis massa vitae bibendum. Maecenas placerat mi eget arcu aliquam, id fill_me accumsan nisi dictum. Vestibulum consequat porttitor nisl eu ultrices. Donec
non blandit tortor fill_me. Aliquam erat volutpat. fill_me id ante et felis porttitor convallis. Sed.</p>
</div>
相关文章:
- 如何使用javascript选择字符串的部分,添加html标记或删除部分
- 正在分析HTML字符串并从中进行选择
- jQuery在两个字符串标识符之间选择HTML
- 选择“字符串”并使用SeleniumJava双击
- Javascript选择元素到字符串的比较
- 循环选择标记并创建数组或字符串
- 日期选择器更改事件抛出”;TypeError:字符串不是函数;
- .val() 选择文本而不是值字符串,当网页动态编辑源 html
- 使用本机 Javascript 从 DOM 元素获取选择器字符串
- 如何使用RegExp选择字符串的一部分并在变量中重用它
- 在数组中存储 JQuery 字段与仅存储选择字符串相比如何
- 如何选择字符串的最后两个字符
- 如何在两个特定字符之间选择字符串
- 下拉选择字符串字体样式更改
- 使用jQuery选择字符串中的第一个单词
- 选择字符串(当前)以及使用Javascript的年份
- 当用户在表单中选择字符串时,我需要颜色的十六进制代码
- 如何复制带有TextFormat信息的HTML选择字符串
- 扩展jquery自动完成:从文本输入中选择字符串的一部分
- Javascript选择字符串的一部分