我最近发布了一个question通过以下方式寻求一种更智能地突出显示单词的方法:
单击突出显示整个单词(默认行为是双击)。
点击并拖动只会突出显示完整的单词/术语。
漂亮的解决方案是 posted阿尔曼。
jsFiddle 用于测试。
我做这道题的目的是让用户单击两个或多个连接词并突出显示它们(扩展突出显示的范围)。
演示。如果 world, 被光标选中:
Hello
world,lorem ipsum attack on titan.
然后用户点击 lorem,它应该像这样选择两个词:
Hello
world, loremipsum attack on titan.
如果用户单击 Hello,则行为相同。
因此它只会在单词连接时扩展突出显示。例如,如果选择了 worlds,,并且用户点击了 ipsum,它应该只选择 ipsum。
扩大亮点范围的方法是什么?
jsFiddle 中的代码是:
jQuery(document).ready(function(e){
(function(els){
for(var i=0;i<els.length;i++){
var el = els[i];
el.addEventListener('mouseup',function(evt){
if (document.createRange) { // Works on all browsers, including IE 9+
var selected = window.getSelection();
/* if(selected.toString().length){ */
var d = document,
nA = selected.anchorNode,
oA = selected.anchorOffset,
nF = selected.focusNode,
oF = selected.focusOffset,
range = d.createRange();
range.setStart(nA,oA);
range.setEnd(nF,oF);
// Check if direction of selection is right to left
if(range.startContainer !== nA || (nA === nF && oF < oA)){
range.setStart(nF,oF);
range.setEnd(nA,oA);
}
// Extend range to the next space or end of node
while(range.endOffset < range.endContainer.textContent.length && !/\s$/.test(range.toString())){
range.setEnd(range.endContainer, range.endOffset + 1);
}
// Extend range to the previous space or start of node
while(range.startOffset > 0 && !/^\s/.test(range.toString())){
range.setStart(range.startContainer, range.startOffset - 1);
}
// Remove spaces
if(/\s$/.test(range.toString()) && range.endOffset > 0)
range.setEnd(range.endContainer, range.endOffset - 1);
if(/^\s/.test(range.toString()))
range.setStart(range.startContainer, range.startOffset + 1);
// Assign range to selection
selected.addRange(range);
el.style.MozUserSelect = '-moz-none';
/* } */
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
});
/* This part is necessary to eliminate a FF specific dragging behavior */
el.addEventListener('mousedown',function(){
if (window.getSelection) { // Works on all browsers, including IE 9+
var selection = window.getSelection ();
selection.collapse (selection.anchorNode, selection.anchorOffset);
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
el.style.MozUserSelect = 'text';
});
}
})(document.getElementsByClassName('taggable'));
});
HTML:
<p class="taggable">
Hello world, lorem ipsum attack on titan.
</p>
<p>
JS doesn't affect this text.
</p>
奖励现有答案,因为它非常有用。无需发布更多解决方案,因为这个已经很完整了。
最佳答案
好吧,我把它放在最前面,因为它是一个重大更新,我相信,甚至可以被认为是对以前功能的升级。
请求是让之前的功能反向工作,即当再次点击突出显示的单词时,它将从总选择中删除。
挑战是,当 <p> 边缘的突出显示单词时和 </p>标签或 <b> 的边缘和 </b>段落内的标签被点击,startContainer或 endContainer必须将范围的一部分带入或带出它们所在的当前元素和startOffset或 endOffset也必须重置。我不确定这是否清楚地表达了问题,但简而言之,由于 Range 的方式对象工作,最接近 HTML 标签的词被证明是一个相当大的挑战。
解决方案是引入一些新的正则表达式测试,几个 if检查,以及用于查找下一个/ previous sibling 姐妹的本地函数。在此过程中,我还修复了一些以前没有引起我注意的事情。新功能如下,the updated fiddle is here .
(function(el){
// variable declaration for previous range info
// and function for finding the sibling
var prevRangeInfo = {},
findSibling = function(thisNode, direction){
// get the child node list of the parent node
var childNodeList = thisNode.parentNode.childNodes,
children = [];
// convert the child node list to an array
for(var i=0, l=childNodeList.length; i<l; i++) children.push(childNodeList[i]);
return children[children.indexOf(thisNode) + direction];
};
el.addEventListener('mouseup',function(evt){
if (document.createRange) { // Works on all browsers, including IE 9+
var selected = window.getSelection();
// Removing the following line from comments will make the function drag-only
/* if(selected.toString().length){ */
var d = document,
nA = selected.anchorNode,
oA = selected.anchorOffset,
nF = selected.focusNode,
oF = selected.focusOffset,
range = d.createRange(),
rangeLength = 0;
range.setStart(nA,oA);
range.setEnd(nF,oF);
// Check if direction of selection is right to left
if(range.startContainer !== nA || (nA === nF && oF < oA)){
range.setStart(nF,oF);
range.setEnd(nA,oA);
}
// Extend range to the next space or end of node
while(range.endOffset < range.endContainer.textContent.length && !/\s$/.test(range.toString())){
range.setEnd(range.endContainer, range.endOffset + 1);
}
// Extend range to the previous space or start of node
while(range.startOffset > 0 && !/^\s/.test(range.toString())){
range.setStart(range.startContainer, range.startOffset - 1);
}
// Remove spaces
if(/\s$/.test(range.toString()) && range.endOffset > 0)
range.setEnd(range.endContainer, range.endOffset - 1);
if(/^\s/.test(range.toString()))
range.setStart(range.startContainer, range.startOffset + 1);
// Store the length of the range
rangeLength = range.toString().length;
// Check if another range was previously selected
if(prevRangeInfo.startContainer && nA === nF && oA === oF){
var rangeTryContain = d.createRange(),
rangeTryLeft = d.createRange(),
rangeTryRight = d.createRange(),
nAp = prevRangeInfo.startContainer;
oAp = prevRangeInfo.startOffset;
nFp = prevRangeInfo.endContainer;
oFp = prevRangeInfo.endOffset;
rangeTryContain.setStart(nAp, oAp);
rangeTryContain.setEnd(nFp, oFp);
rangeTryLeft.setStart(nFp, oFp-1);
rangeTryLeft.setEnd(range.endContainer, range.endOffset);
rangeTryRight.setStart(range.startContainer, range.startOffset);
rangeTryRight.setEnd(nAp, oAp+1);
// Store range boundary comparisons
// & inner nodes close to the range boundary --> stores null if none
var compareStartPoints = range.compareBoundaryPoints(0, rangeTryContain) === 0,
compareEndPoints = range.compareBoundaryPoints(2, rangeTryContain) === 0,
leftInnerNode = range.endContainer.previousSibling,
rightInnerNode = range.startContainer.nextSibling;
// Do nothing if clicked on the right end of a word
if(range.toString().length < 1){
range.setStart(nAp,oAp);
range.setEnd(nFp,oFp);
}
// Collapse the range if clicked on last highlighted word
else if(compareStartPoints && compareEndPoints)
range.collapse();
// Remove a highlighted word from left side if clicked on
// This part is quite tricky!
else if(compareStartPoints){
range.setEnd(nFp,oFp);
if(range.startOffset + rangeLength + 1 >= range.startContainer.length){
if(rightInnerNode)
// there is a right inner node, set its start point as range start
range.setStart(rightInnerNode.firstChild, 0);
else {
// there is no right inner node
// there must be a text node on the right side of the clicked word
// set start of the next text node as start point of the range
var rightTextNode = findSibling(range.startContainer.parentNode, 1),
rightTextContent = rightTextNode.textContent,
level=1;
// if beginning of paragraph, find the first child of the paragraph
if(/^(?:\r\n|[\r\n])|\s{2,}$/.test(rightTextContent)){
rightTextNode = findSibling(rightTextNode, 1).firstChild;
level--;
}
range.setStart(rightTextNode, level);
}
}
else
range.setStart(range.startContainer, range.startOffset + rangeLength + 1);
}
// Remove a hightlighted word from right side if clicked on
// This part is also tricky!
else if (compareEndPoints){
range.setStart(nAp,oAp);
if(range.endOffset - rangeLength - 1 <= 0){
if(leftInnerNode)
// there is a right inner node, set its start point as range start
range.setEnd(leftInnerNode.lastChild, leftInnerNode.lastChild.textContent.length);
else {
// there is no left inner node
// there must be a text node on the left side of the clicked word
// set start of the previous text node as start point of the range
var leftTextNode = findSibling(range.endContainer.parentNode, -1),
leftTextContent = leftTextNode.textContent,
level = 1;
// if end of paragraph, find the last child of the paragraph
if(/^(?:\r\n|[\r\n])|\s{2,}$/.test(leftTextContent)){
leftTextNode = findSibling(leftTextNode, -1).lastChild;
level--;
}
range.setEnd(leftTextNode, leftTextNode.length - level);
}
}
else
range.setEnd(range.endContainer, range.endOffset - rangeLength - 1);
}
// Add previously selected range if adjacent
// Upgraded to include previous/next word even in a different paragraph
else if(/^[^\s]*((?:\r\n|[\r\n])|\s{1,})[^\s]*$/.test(rangeTryLeft.toString()))
range.setStart(nAp,oAp);
else if(/^[^\s]*((?:\r\n|[\r\n])|\s{1,})[^\s]*$/.test(rangeTryRight.toString()))
range.setEnd(nFp,oFp);
// Detach the range objects we are done with, clear memory
rangeTryContain.detach();
rangeTryRight.detach();
rangeTryLeft.detach();
}
// Save the current range --> not the whole Range object but what is neccessary
prevRangeInfo = {
startContainer: range.startContainer,
startOffset: range.startOffset,
endContainer: range.endContainer,
endOffset: range.endOffset
};
// Clear the saved range info if clicked on last highlighted word
if(compareStartPoints && compareEndPoints)
prevRangeInfo = {};
// Remove all ranges from selection --> necessary due to potential removals
selected.removeAllRanges();
// Assign the current range as selection
selected.addRange(range);
// Detach the range object we are done with, clear memory
range.detach();
el.style.MozUserSelect = '-moz-none';
// Removing the following line from comments will make the function drag-only
/* } */
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
});
/* This part is necessary to eliminate a FF specific dragging behavior */
el.addEventListener('mousedown',function(e){
if (window.getSelection) { // Works on all browsers, including IE 9+
var selection = window.getSelection ();
selection.collapse (selection.anchorNode, selection.anchorOffset);
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
el.style.MozUserSelect = 'text';
});
})(document.getElementById('selectable'));
存储最后一个range在object并检查之前选择的 range 是否毗邻新range每次做出新选择时,都会执行以下操作:
(function(el){
var prevRangeInfo = {};
el.addEventListener('mouseup',function(evt){
if (document.createRange) { // Works on all browsers, including IE 9+
var selected = window.getSelection();
/* if(selected.toString().length){ */
var d = document,
nA = selected.anchorNode,
oA = selected.anchorOffset,
nF = selected.focusNode,
oF = selected.focusOffset,
range = d.createRange();
range.setStart(nA,oA);
range.setEnd(nF,oF);
// Check if direction of selection is right to left
if(range.startContainer !== nA || (nA === nF && oF < oA)){
range.setStart(nF,oF);
range.setEnd(nA,oA);
}
// Extend range to the next space or end of node
while(range.endOffset < range.endContainer.textContent.length && !/\s$/.test(range.toString())){
range.setEnd(range.endContainer, range.endOffset + 1);
}
// Extend range to the previous space or start of node
while(range.startOffset > 0 && !/^\s/.test(range.toString())){
range.setStart(range.startContainer, range.startOffset - 1);
}
// Remove spaces
if(/\s$/.test(range.toString()) && range.endOffset > 0)
range.setEnd(range.endContainer, range.endOffset - 1);
if(/^\s/.test(range.toString()))
range.setStart(range.startContainer, range.startOffset + 1);
// Check if another range was previously selected
if(prevRangeInfo.startContainer){
var rangeTryLeft = d.createRange(),
rangeTryRight = d.createRange(),
nAp = prevRangeInfo.startContainer;
oAp = prevRangeInfo.startOffset;
nFp = prevRangeInfo.endContainer;
oFp = prevRangeInfo.endOffset;
rangeTryLeft.setStart(nFp,oFp-1);
rangeTryLeft.setEnd(range.endContainer,range.endOffset);
rangeTryRight.setStart(range.startContainer,range.startOffset);
rangeTryRight.setEnd(nAp,oAp+1);
// Add previously selected range if adjacent
if(/^[^\s]*\s{1}[^\s]*$/.test(rangeTryLeft.toString())) range.setStart(nAp,oAp);
else if(/^[^\s]*\s{1}[^\s]*$/.test(rangeTryRight.toString())) range.setEnd(nFp,oFp);
}
// Save the current range
prevRangeInfo = {
startContainer: range.startContainer,
startOffset: range.startOffset,
endContainer: range.endContainer,
endOffset: range.endOffset
};
// Assign range to selection
selected.addRange(range);
el.style.MozUserSelect = '-moz-none';
/* } */
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
});
/* This part is necessary to eliminate a FF specific dragging behavior */
el.addEventListener('mousedown',function(e){
if (window.getSelection) { // Works on all browsers, including IE 9+
var selection = window.getSelection ();
selection.collapse (selection.anchorNode, selection.anchorOffset);
} else {
// Fallback for Internet Explorer 8 and earlier
// (if you think it still is worth the effort of course)
}
el.style.MozUserSelect = 'text';
});
})(document.getElementById('selectable'));
JS fiddle here .
更新(在升级前完成):
如果你想让这个特性在点击而不是拖动时有效,你所要做的就是改变if(prevRangeInfo.startContainer)条件如下:
if(prevRangeInfo.startContainer && nA === nF && oA === oF){
// rest of the code is the same...
更新后的 JS Fiddle 是 here .
关于javascript - 如果突出显示一个词并且用户单击连接词,则同时突出显示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35390994/
我得到了一个包含嵌套链接的表单。编辑时链接字段为空的问题。这是我的表格:Editingkategori{:action=>'update',:id=>@konkurrancer.id})do|f|%>'Trackingurl',:style=>'width:500;'%>'Editkonkurrence'%>|我的konkurrencer模型:has_one:link我的链接模型:classLink我的konkurrancer编辑操作:defedit@konkurrancer=Konkurrancer.find(params[:id])@konkurrancer.link_attrib
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val
我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择