19

我在使用 html 和 javascript 时遇到了困难。我的 html 页面允许用户选择文本并用颜色突出显示它。现在我想将状态保存到数据库中,以便稍后为该用户显示。当然,我可以在用户编辑后保存整个 html。但我只想保存一些参数,结合原始html以用户上次看到的状态显示页面。我们可以使用这个功能:

var index = innerHTML.indexOf(text);

突出显示该索引处的文本。但是如果页面中有很多相同的文本,我想突出显示之前用户突出显示的单词。

任何人都可以指导我如何使用 javascript 完成此任务?

我非常感谢你的帮助。

4

8 回答 8

12

Range对象并document.execCommand允许很容易地操纵选择。您的情况的主要问题是以文本格式保存范围对象。

基本上,您需要的是获取创建 Range 对象所需的值startContainer、和。是数字,所以它非常简单。容器是节点,不能直接保存为字符串,所以这是主要问题。您可以做的一件事是将密钥添加到您的 DOM 并保存密钥。但是,由于范围内的容器是文本节点,因此您需要保存文本节点的索引。像这样的东西应该允许使用递归函数用键标记 DOM:startOffsetendContainerendOffsetOffsets

function addKey(element) {
  if (element.children.length > 0) {
    Array.prototype.forEach.call(element.children, function(each, i) {
      each.dataset.key = key++;
      addKey(each)
    });
  }
};

addKey(document.body);

完成此操作后,您可以将范围对象转换为可以保存为字符串的对象。像这样:

function rangeToObj(range) {
  return {
    startKey: range.startContainer.parentNode.dataset.key,
    startTextIndex: Array.prototype.indexOf.call(range.startContainer.parentNode.childNodes, range.startContainer),
    endKey: range.endContainer.parentNode.dataset.key,
    endTextIndex: Array.prototype.indexOf.call(range.endContainer.parentNode.childNodes, range.endContainer),
    startOffset: range.startOffset,
    endOffset: range.endOffset
  }
}

使用它,您可以将用户创建的每个选择保存到数组中。像这样:

document.getElementById('textToSelect').addEventListener('mouseup', function(e) {
  if (confirm('highlight?')) {
    var range = document.getSelection().getRangeAt(0);
    selectArray.push(rangeToObj(range));
    document.execCommand('hiliteColor', false, 'yellow')
  }
});

要保存亮点,请将每个对象保存为 JSON。要对此进行测试,您可以从范围对象数组中获取 JSON 字符串。像这样(这是使用顶部的获取选择按钮):

document.getElementById('getSelectionString').addEventListener('click', function() {
  alert('Copy string to save selections: ' + JSON.stringify(selectArray));
});

然后在加载空 HTML 时,您可以使用反向函数,该函数将从您保存在 JSON 中的对象创建范围。像这样:

function objToRange(rangeStr) {
  range = document.createRange();
  range.setStart(document.querySelector('[data-key="' + rangeStr.startKey + '"]').childNodes[rangeStr.startTextIndex], rangeStr.startOffset);
  range.setEnd(document.querySelector('[data-key="' + rangeStr.endKey + '"]').childNodes[rangeStr.endTextIndex], rangeStr.endOffset);
  return range;
}

因此,您可以将字符串中的范围数组转换为对象,然后转换为可以添加的范围对象。然后使用 execCommand,设置一些格式。像这样(这是使用顶部的设置选择按钮,您在刷新小提琴后执行此操作):

document.getElementById('setSelection').addEventListener('click', function() {
  var selStr = prompt('Paste string');
  var selArr = JSON.parse(selStr);
  var sel = getSelection();
  selArr.forEach(function(each) {
    sel.removeAllRanges();
    sel.addRange(objToRange(each));
    document.execCommand('hiliteColor', false, 'yellow')
  })
});

见:https ://jsfiddle.net/sek4tr2f/3/

请注意,在某些情况下这不起作用,主要的问题情况是用户在已突出显示的内容中选择内容。这些情况可以处理,但你需要更多的条件。

于 2018-01-13T03:54:55.003 回答
3

您需要捕获节点的路径才能知道它的位置。
这可以通过多种方式完成。
最简单的方法是遍历 dom 直到 body 并创建一个选择器。

function getPathFromElement(element) {
    var stack = [];

    while (element.parentNode != document.documentElement) {
        var sibCount = 0;
        var sibIndex = 0;
        var childNodes = element.parentNode.childNodes;
        var childLength = childNodes.length;

        for (var i = 0; i < childLength; i++) {
            var sib = childNodes[i];

            if (sib.nodeName == element.nodeName) {
                if (sib === element) {
                    sibIndex = sibCount;
                }

                sibCount++;
            }
        }

        if (element.hasAttribute("id") && element.id !== "") {
            stack.unshift(`${element.nodeName.toLowerCase()}#${element.id}`);
        }
        else if (sibCount > 1) {
            stack.unshift(`${element.nodeName.toLowerCase()}:eq(${sibIndex})`);
        }
        else {
            stack.unshift(element.nodeName.toLowerCase());
        }

        element = element.parentNode;
    }

    return stack.join(" > ")
}

让我们假设您想为您的用户提供两个选项来选择文本。

  1. 页面中的简单文本。
  2. 在输入文本或文本区域内选择文本。

对于第一个选项,您可以使用带有单击处理程序或 mouseup 事件的按钮。
为简单起见,我将使用一个按钮。

function sendDataToServer(data) {
}

document.querySelector("#button").addEventListener("click", function (e) {
    var { target, text } = getSelectionTextAndContainerElement();

    var path = getPathFromElement(target);

    sendDataToServer({
        path: path,
        text: text 
    }); 
});

getSelectionTextAndContainerElement函数基本上选择文本和元素。

function getSelectionTextAndContainerElement() {
    var text;
    var containerElement = null;

    if (typeof window.getSelection !== "undefined") {
        var selection = window.getSelection();

        if (selection.rangeCount) {
            var node = selection.getRangeAt(0).commonAncestorContainer;
            containerElement = node.nodeType == 1 ? node : node.parentNode;
            text = selection.toString();
        }
    }
    else if (typeof document.selection !== "undefined" && document.selection.type !== "Control") {
        var textRange = document.selection.createRange();

        containerElement = textRange.parentElement();
        text = textRange.text;
    }

    return {
        text: text,
        target: containerElement
    };
}

对于第二个选项,您可以使用select事件处理程序。

document.addEventListener("select", onSelect, false);

function onSelect(e) {
    var { text } = getSelectionTextAndContainerElement();
    var path = getPathFromElement(e.target); 

    sendDataToServer({
        path: path,
        text: text 
    });       
}

对于输入文本或文本区域,最好使用select事件处理程序。
如果您将使用第一个选项来获得选择,您将无法获得正确的目标节点,因为输入文本和 textarea 是使用Shadow DOM构建的。
所以最好忽略从getSelectionTextAndContainerElement函数中返回的目标节点,并使用select事件的目标属性。

我在jsfiddle中为您创建了一个示例。

于 2018-01-12T11:59:52.593 回答
2

第一个例子:

<textarea id="quote" cols="50" rows="5">
The above properties are especially useful in getting any user selected text from a form field where the indices of the selection isn't already known. The following demo echoes what the user has selected from a TEXTAREA using these properties:
</textarea>
 
<div id="output"></div>
 
<script>
 
var quotearea = document.getElementById('quote')
var output = document.getElementById('output')
quotearea.addEventListener('mouseup', function(){
    if (this.selectionStart != this.selectionEnd){ // check the user has selected some text inside field
        var selectedtext = this.value.substring(this.selectionStart, this.selectionEnd)
        output.innerHTML = selectedtext
    }
}, false)
 
</script>

第二个例子

<head>
    <script type="text/javascript">
        function GetSelectedText () {
            var selText = "";
            if (window.getSelection) {  // all browsers, except IE before version 9
                if (document.activeElement && 
                        (document.activeElement.tagName.toLowerCase () == "textarea" || 
                         document.activeElement.tagName.toLowerCase () == "input")) 
                {
                    var text = document.activeElement.value;
                    selText = text.substring (document.activeElement.selectionStart, 
                                              document.activeElement.selectionEnd);
                }
                else {
                    var selRange = window.getSelection ();
                    selText = selRange.toString ();
                }
            }
            else {
                if (document.selection.createRange) {       // Internet Explorer
                    var range = document.selection.createRange ();
                    selText = range.text;
                }
            }
            if (selText !== "") {
                alert (selText);
            }
        }
    </script>
</head>
<body onmouseup="GetSelectedText ()">
    Some text for selection.
    <br /><br />
    <textarea>Some text in a textarea element.</textarea>
    <input type="text" value="Some text in an input field." size="40"/>
    <br /><br />
    Select some content on this page!
</body>

第三个例子:

<head>
    <script type="text/javascript">
        function GetSelection () {
            var selection = "";

            var textarea = document.getElementById("myArea");
            if ('selectionStart' in textarea) {
                    // check whether some text is selected in the textarea
                if (textarea.selectionStart != textarea.selectionEnd) {
                    selection = textarea.value.substring  (textarea.selectionStart, textarea.selectionEnd);
                }
            }
            else {  // Internet Explorer before version 9
                    // create a range from the current selection
                var textRange = document.selection.createRange ();
                    // check whether the selection is within the textarea
                var rangeParent = textRange.parentElement ();
                if (rangeParent === textarea) {
                    selection = textRange.text;

                }
            }

            if (selection == "") {
                alert ("No text is selected.");
            }
            else {
                alert ("The current selection is: " + selection);
            }
        }
    </script>
</head>
<body>
    <textarea id="myArea" spellcheck="false">Select some text within this field.</textarea>
    <button onclick="GetSelection ()">Get the current selection</button>
</body>

于 2018-01-18T12:30:22.463 回答
1

由于您使用插件进行文本突出显示,因此使用 jQuery 获取突出显示的单词:

var words = $('.highlight').map(function() { return $(this).text(); });

然后将它们放在一个数组中

var saved = [ ];
for (var word in words) {
    if (-1 === saved.indexOf(word)) {
        saved.push(word);
    }
}

最后,您可以将它们保存在数据库中。一种不好(但很快)的方法是将列表保存为逗号分隔,这是一种著名的SQL 反模式

var wordList = saved.join(',');

检索值时,将其拆分为单词,并为每个单词调用突出显示插件。

如果任何文本包含逗号,这将不起作用。在这种情况下,您最好单独保存每个单词,这最终可以省去其他几个麻烦,而不是找出一个“不太可能”在用户文本中弹出的分隔字符。

于 2018-01-15T16:12:15.623 回答
1

我的想法是<span >在所选文本的开头和结尾添加,然后当您保存文档时,整个 html 将保存到数据库中,因此当他检索记录时,突出显示的文本将保留。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>this is a paragraph creted to demonstrate highlighting selected text</p>
<script>
$(document).ready(function(){

$("p").on("mouseup",function() {
    oldtxt = chosenText();
    
        var newtxt = '<span  style="color:red;">' + oldtxt +'</span>';
$(this).html($(this).html().replace(oldtxt,newtxt));
    
});

//Grab selected text
function chosenText(){
    if(window.getSelection){
        return window.getSelection().toString();
    }
    else if(document.getSelection){
        return document.getSelection();
    }
    else if(document.selection){
        return document.selection.createRange().text;
    }
}
});
</script>

在 jquery 中添加元素会很舒服

于 2018-01-13T14:26:49.243 回答
0

您可以使用数组来保存用户的选择!!之后,您将整个数组保存到数据库中!当用户再次查看该站点时,函数会比较数组中的字母和单词并突出显示它..

于 2013-09-22T17:33:00.310 回答
0

您可以使用.serialize()以标准 URL 编码表示法返回文本字符串的方法。它已经选择了单个表单元素,例如<input>, <textarea>etc..so 在 DB 中推送序列化返回字符串,并使用$(form).serialize();和突出显示更改检查旧$(form).serialize();返回值和新$(form).serialize();返回值。

于 2018-01-12T06:07:57.537 回答
0

从测试的角度来看,如果可以在不调整高光的情况下更改原始 html,则无法存储分离的高光。

我的解决方案是序列化整个彩色 html。然后做一个scrub函数来移除所有的颜色高光并返回到基线html。这允许 db 中的 html 包含颜色突出显示,并且仍然可以在保留突出显示的情况下进行编辑。

就像是:

function unhighlight() {
  $('.highlighted').each(function(index, el) {
    $(el).replaceWith($(el).html());
  });
}

jsfiddle:https ://jsfiddle.net/tobtedsc/5/

于 2018-01-13T03:06:55.153 回答