【问题标题】:Save selection text and show it later in html and javascript保存选择文本并稍后在 html 和 javascript 中显示
【发布时间】:2013-09-27 14:25:30
【问题描述】:

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

var index = innerHTML.indexOf(text);

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

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

非常感谢您的帮助。

【问题讨论】:

  • 为什么不将突出显示的单词的索引及其长度保存在一个数组中?
  • 我同意@mohammedessam,创建一个数组,以便突出显示“xyz”的用户将“xyz”添加到数组中,并包含一个索引来指示它是哪个特定的“xyz”实例(即,如果它是 'xyz' 的第三个实例,那么索引将为 3。
  • 我怀疑innerHTML.indexOf(text) 会提供任何东西,更不用说索引了。 ReferenceError 的可能性更大。
  • 添加一些关于如何实现突出显示的参考将不胜感激,因为我们实际上应该修改突出显示功能的代码以包含文本的索引,以便您真正知道您应该实际突出显示文本的哪个实例,而不是知道突出显示的文本本身,这将是无用的!
  • 您可以使用以下内容来计算子字符串的实例数,也许您可​​以以某种方式使用它来得出检测哪个突出显示的答案,然后使用它来遍历它们并进行计数从开始到“查找”的字符总数从哪里开始突出显示并基于字符串的长度,知道在哪里结束突出显示? var count = $("#textblock").html().match(/YOURSTRING/g);警报(计数。长度);

标签: javascript jquery html


【解决方案1】:

Range 对象和document.execCommand 允许非常轻松地操作选择。您的主要问题是以文本格式保存范围对象。

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

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/

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

【讨论】:

    【解决方案2】:

    您需要捕获节点的路径才能知道它的位置。
    这可以通过多种方式完成。
    最简单的方法是遍历 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 中为您创建了一个示例。

    【讨论】:

    • 你的 jsfiddle 工作得很好。但我确实注意到,如果我选择 12,然后尝试选择 23,它无法将选择范围扩展到 123。对此有什么想法吗?
    • 请注意,数字选择并不是真正的文本选择,而是所选文本的换行,其范围与选择具有相同的颜色。我并没有尝试创建整个解决方案,因为这不是问题,而是举一个例子,说明可以从元素中获取选择和路径。为了完成文本选择,您可以在此处阅读:developer.mozilla.org/en-US/docs/Web/API/Selection。
    【解决方案3】:

    第一个例子:

    <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>

    【讨论】:

      【解决方案4】:

      我的想法是在所选文本的开头和结尾添加&lt;span &gt;,然后当您保存文档时,整个 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中添加元素会很舒服

      【讨论】:

      • 因为我们不知道 op 的上下文 - 如果有多个用户在同一页面上这样做,这可能会导致您的 html 中出现跨度沙拉...
      • @errand 是的,但是,您可以为不同的用户保存在数据库中。它为每个用户显示正确的页面
      【解决方案5】:

      由于您使用插件进行文本突出显示,因此使用 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 antipattern:

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 是的。我怀疑如何做到这一点是个问题。
        • @DavidThomas 问题是 OP 没有说明他管理应用程序突出显示功能的方式,只有他知道如何实现它!
        • javascript 中的 mousedown() 和 mouseup() 事件在选择开始时使用 mousedown() 并且当它结束选择 mouseup() 并将所选文本保存在数组中!
        • @AshishMishra 还有selectionstart() 和selectionend(),您可以使用stackoverflow.com/questions/6251937/… 中的答案来获取实际突出显示的文本;)
        【解决方案7】:

        您可以使用.serialize() 方法以标准 URL 编码表示法返回文本字符串。它选择了单独的表单元素,例如&lt;input&gt;, &lt;textarea&gt; 等。因此,使用$(form).serialize(); 将序列化的返回字符串推送到数据库中,并使用新的$(form).serialize(); 返回值来突出显示更改检查旧$(form).serialize(); 返回值。

        【讨论】:

          【解决方案8】:

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

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

          类似:

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

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

          【讨论】:

            猜你喜欢
            • 2012-11-16
            • 1970-01-01
            • 2018-08-08
            • 1970-01-01
            • 2014-07-06
            • 2020-05-14
            • 2023-03-16
            • 1970-01-01
            • 2015-08-24
            相关资源
            最近更新 更多