【问题标题】:Search and highlight text on page while keeping html structure在保持 html 结构的同时搜索并突出显示页面上的文本
【发布时间】:2020-03-29 09:34:48
【问题描述】:

可能已经提出过类似的问题,但请仔细阅读详细信息。

我正在使用一种自制的自动完成功能,现在我想突出显示结果集中的搜索词。 到目前为止,这有效,但仅适用于纯文本。问题是:如果结果 div 中有一个,我需要保留 html 结构。请查看我的示例:目前我正在失去包含的跨度与类粗体。我怎样才能保留它们?

感谢您的建议!

$('#box').keyup(function () {
  const valThis = this.value;
  const length  = this.value.length;

  $('.objType').each(function () {
    const text  = $(this).text();
    const textL = text.toLowerCase();
    const position = textL.indexOf(valThis.toLowerCase());

    if (position !== -1) {
      const matches = text.substring(position, (valThis.length + position));
      const regex = new RegExp(matches, 'ig');
      const highlighted = text.replace(regex, `<mark>${matches}</mark>`);

      $(this).html(highlighted).show();
    } else {
    	$(this).text(text);
      $(this).hide();
    }
  });

});
input[type="text"] { 
    width: 50%;
    margin:10px;
    padding: 5px;
    float:left;
    clear:left;
}
div{
  float:left;
  clear:left;
  margin:10px 10px;
}
.bold {
  font-weight: 700;
}
table td {
  border: solid 1px #ccc;
  padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
<input placeholder="Filter results" id="box" type="text" />

<div class="objType" id="item1">
  <span class="bold">Accepted</span> Event Relation
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item2">
  Case <span class="bold">Status</span> Value
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item3">
  External <span class="bold">Data Source</span>
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item4">
  Navigation <span class="bold">Link</span> Set
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>

PS:另外一个 JSFiddle 可能会有所帮助 => https://jsfiddle.net/SchweizerSchoggi/6x3ak5d0/7/

【问题讨论】:

标签: jquery html autocomplete highlight


【解决方案1】:

这是一个仅使用 本机 javascript 的可能基础。这有点像 CTRL+F

这似乎保留了&lt;td&gt; 元素。

clear 函数将mark 元素替换为wbr 元素:

在 UTF-8 编码的页面上,&lt;wbr&gt; 的行为类似于 U+200B 零宽度空间 码点。 https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr

function mark(it){
  clearIt()
  if (it.length > 2){
    let c = new RegExp(it, "ig") 
    main.innerHTML = main.innerHTML.replace(c,"<mark>"+it+"</mark>")
  }  
}

function clearIt(){
  let b = new RegExp("mark>", "ig") 
  main.innerHTML = main.innerHTML.replace(b,"wbr>")
}

mark(search.value)
input[type="text"] { 
    width: 50%;
    margin:10px;
    padding: 5px;
    float:left;
    clear:left;
}
div{
  float:left;
  clear:left;
  margin:10px 10px;
}
.bold {
  font-weight: 700;
}
table td {
  border: solid 1px #ccc;
  padding: 3px;
}
<input onfocusout="clearIt()" oninput="mark(this.value)"  value="Lorem" id="search" placeholder="Lorem">
<button onclick="mark(search.value)">SEARCH</button>
<button onclick="clearIt()">CLEAR</button>

<div id="main">
<div class="objType" id="item1">
  <span class="bold">Accepted</span> Event Relation
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item2">
  Case <span class="bold">Status</span> Value
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item3">
  External <span class="bold">Data Source</span>
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>
<div class="objType" id="item4">
  Navigation <span class="bold">Link</span> Set
  <table>
  <tr>
    <td>Lorem</td>
    <td>ipsum</td>
  </tr>
  </table>
</div>

</div>

顺便说一句,恢复/清除的标记不是原始标记,要完全恢复它,您可能需要在标记之前复制整个 html。

【讨论】:

  • 这可能是我们在没有插件的情况下可以获得的最接近的。不幸的是,我无法搜索“接受的事件”(有机会解决这个问题吗?)。否则,这是一个不错的解决方案。谢谢
【解决方案2】:

为了简单起见,我在 vue 文件中创建了功能(因为这些功能很容易实现并且可以插入变量):

<template>
  <div class="container">
    <div class="position-relative">
      <input
        v-model="inputValue"
        type="search"
        autocomplete="off"
        class="form-control"
        @input="onInput" />
    </div>
    <pre v-html="results" />
  </div>
</template>

<script>
export default {
  name: 'Typeahead',
  data() {
    return {
      inputValue: '',
      items: [
        { value: '' },
        { value: '' },
        { value: '' },
        // and so on (Value is the field to be searched).
      ],
      results: [],
    };
  },

  created() {
    this.results = this.items; // initially assign all the items as results
  },

  methods: {
    onInput() { // input event (use event target value for vanilla js.)
      const value = this.inputValue;
      if (!value) {
        this.results = this.items;
        return;
      }
      const escapedQuery = this.escapeRegExp(value); // escape any special characters
      const queryReg = new RegExp(escapedQuery, 'gi'); // create a regular expression out of it
      this.results = this.matchItem(this.items, queryReg) // call match function
        .map(item => ({
          ...item,
          value: this.highlight(item.value, queryReg), // highlight the matched text range
        }));
    },

    escapeHtml(text) {
      return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    },

    escapeRegExp(text) {
      return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    },

    matchItem(items, queryReg) {
      return items
        .filter(i => i.value.match(queryReg) !== null)
        .sort((a, b) => {
          const aIndex = a.value.indexOf(a.value.match(queryReg)[0]);
          const bIndex = b.value.indexOf(b.value.match(queryReg)[0]);
          if (aIndex < bIndex) return -1;
          if (aIndex > bIndex) return 1;
          return 0;
        });
    },

    highlight(text, queryReg) {
      const escaped = this.escapeHtml(text);
      return escaped.replace(queryReg, '<b>$&</b>');
    },
  },
};
</script>

Check this fiddle I've created

基本上,它会从输入文本中转义任何特殊符号并从中创建一个正则表达式,然后过滤掉与该正则表达式匹配的记录。然后根据匹配的强度对元素进行排序。现在,匹配的部分(记录中的值)使用strongb html 标记(我在这里使用了粗体标记)突出显示,可以很容易地将其嵌入到 html 中,从而产生预期的突出显示输出。

我使用 pre 标签来显示结果。您可以根据需要创建表结构。

这些方法在原版 javascript 中,没有太多的 vue 内容(this 引用除外)。

希望对你有帮助:)

【讨论】:

    【解决方案3】:

    我已尝试改进您的方法,并提出了以下解决方案。它在大多数情况下都可以正常工作。

    /*
    This function will get all the indices of searched term in the html in array format
    e.g. if html is <span>Accept</span> and user types a
    Input: getAllIndicesOf(a, "<span>Accept</span>", false)
    Output: [3,6,16]
    */
    function getAllIndicesOf(searchStr, str, caseSensitive) {
        var searchStrLen = searchStr.length;
        if (searchStrLen == 0) {
            return [];
        }
        var startIndex = 0, index, indices = [];
        if (!caseSensitive) {
            str = str.toLowerCase();
            searchStr = searchStr.toLowerCase();
        }
        while ((index = str.indexOf(searchStr, startIndex)) > -1) {
            indices.push(index);
            startIndex = index + searchStrLen;
        }
        return indices;
    }
    
    /*
    What ever values I am getting from getAllIndicesOf, here I try to find if the searched value is not present inside html tag.
    e.g. if html is <span>Accept</span> and user types a
    getAllIndicesOf will output [3,6,16]
    Input: findValidMatches([3,6,16], "a")
    Output: [6]
    Logic: The valid matching text will lie between > and <. If it lies between < and >, it is a html tag. 
    */
    function findValidMatches(pseudoPosition, str) {
        const pos = []
        for (let i = 0; i<pseudoPosition.length; ++i) {
        	const splitText = str.substr(pseudoPosition[i])
          const indexOfLT = splitText.indexOf("<")
          const indexOfGT = splitText.indexOf(">")
          if (indexOfLT > -1 && indexOfGT > -1 && indexOfLT < indexOfGT) {
            pos.push(pseudoPosition[i])
          }
          else if (indexOfLT === -1 && indexOfGT > -1 && indexOfGT < 0) {
            pos.push(pseudoPosition[i])
          }
          else if (indexOfGT === -1 && indexOfLT > -1 && indexOfLT > 0) {
            pos.push(pseudoPosition[i])
          }
          else if (indexOfLT === -1 && indexOfGT === -1) {
            pos.push(pseudoPosition[i])
          }
        }
        return pos
    }
    
    /*
    This will replace the matched valid string with <mark>text</mark> to highlight
    if html is <span>Accept</span> and user types a
    getAllIndicesOf will output [3,6,16] -> findValidMatches will output [6] -> input to replaceText
    
    replaceText("<span>Accept</span>", [6], "a") will output <span><mark>A</mark></span>
    */
    function replaceText(text, correctPositions, valueToReplace) {
      let copyText = text
      for (let i = 0; i<correctPositions.length; ++i) {
        const upValue = correctPositions[i] + 13*i
        const firstPart = copyText.slice(0, upValue)
        const lastPart = copyText.slice(upValue + valueToReplace.length, copyText.length)
        const valueWithCase = copyText.substr(upValue, valueToReplace.length)
        copyText = firstPart + "<mark>" + valueWithCase +"</mark>" + lastPart
      }
      return copyText
    }
    
    $('#box').keyup(function () {
      const valThis = this.value;
    
      $('.objType').each(function () {
        const text  = $(this).html().replace(/<mark>/gi, "").replace(/<\/mark>/gi, "");
        const position = getAllIndicesOf(valThis, text) //Get all indices of valThis in the html
        const correctPositions = findValidMatches(position, text) //Filter only those indices which indicate that they are text and not html
        const updatedText = replaceText(text, correctPositions, valThis) //Get the updated text with mark tags
        if (correctPositions.length > 0) {
          $(this).html(updatedText)
          $(this).show();
        } else {
          if (valThis.length > 0) $(this).hide();
          else {
            $(this).html(text)
            $(this).show();
          }
        }
      });
    
    });
    input[type="text"] { 
        width: 50%;
        margin:10px;
        padding: 5px;
        float:left;
        clear:left;
    }
    div{
      float:left;
      clear:left;
      margin:10px 10px;
    }
    .bold {
      font-weight: 700;
    }
    table td {
      border: solid 1px #ccc;
      padding: 3px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <input placeholder="Filter results" id="box" type="text" />
    
    <div class="objType" id="item1">
      <span class="bold">Accepted</span> Event Relation
      <table>
      <tr>
        <td>Lorem</td>
        <td>ipsum</td>
      </tr>
      </table>
    </div>
    <div class="objType" id="item2">
      Case <span class="bold">Status</span> Value
      <table>
      <tr>
        <td>Lorem</td>
        <td>ipsum</td>
      </tr>
      </table>
    </div>
    <div class="objType" id="item3">
      External <span class="bold">Data Source</span>
      <table>
      <tr>
        <td>Lorem</td>
        <td>ipsum</td>
      </tr>
      </table>
    </div>
    <div class="objType" id="item4">
      Navigation <span class="bold">Link</span> Set
      <table>
      <tr>
        <td>Lorem</td>
        <td>ipsum</td>
      </tr>
      </table>
    </div>

    我遵循的方法是直接搜索 html 部分然后更新。

    接近

    1. 将整个html作为字符串(const text = $(this).html().replace(/&lt;mark&gt;/gi, "").replace(/&lt;\/mark&gt;/gi, "");)
    2. 查找所有出现的搜索词(使用getAllIndicesOf
    3. 只选择那些不在 html 标签内的匹配项(使用findValidMatches
    4. 通过在适当的位置插入&lt;mark&gt;&lt;/mark&gt;标签来重构html(使用replaceText
    5. 将字符串重新插入为 html

    可能会有很多相关的问题(例如,如果有事件处理程序,并且如果您尝试搜索带有 html 标记的文本,例如尝试搜索 Accepted,则搜索将失败Event)。我会尝试更新。

    希望对您有所帮助。

    【讨论】:

    • 这看起来不错,在第一次试用时它似乎可以按预期工作。您写道“它在大多数情况下都可以正常工作。”。您知道哪种情况不能正常工作?而且,好吧,我并不真正理解那里的所有部分...... :-D
    • 尝试搜索Accepted EventStatus Value。它在那里失败了,我仍在想办法解决这些情况
    • 聚会迟到了,但它失败的原因可能是由于替换功能将html标签作为文本读取。当您搜索Accepted Event 时,它只会看到&lt;strong&gt;Accepted&lt;/strong&gt; Event。有没有办法让它忽略标签,因为这似乎是你解决方案的唯一障碍?
    【解决方案4】:

    遵循@Sunil 解决方案导致问题的原因是您需要删除它的&lt;span&gt; 标签,所以我编辑了这部分代码:

    $('.objType').each(function () {
    
        const text  = $(this).html().replace(/<mark>/gi, "").replace(/<\/mark>/gi, "");
        const text2  = text.replace(/<span class=\"bold\">/gi, "").replace(/<\/span>/gi, "");
    
        const position = getAllIndicesOf(valThis, text2) //Get all indices of valThis in the html
        const correctPositions = findValidMatches(position, text2) //Filter only those indices which indicate that they are text and not html
        const updatedText = replaceText(text2, correctPositions, valThis) //Get the updated text with mark tags
        if (correctPositions.length > 0) {
          $(this).html(updatedText)
    
          $(this).show();
        } else {
          if (valThis.length > 0) $(this).hide();
          else {
            $(this).html(text2)
            $(this).show();
          }
        }
      });
    

    【讨论】:

      【解决方案5】:

      使用mark.js插件

      $('#box').on('input', function() {
        const valThis = this.value;
      
        const options = {
          filter: function(node, term, totalCounter, counter) {
            $(node).parents('.objType').show()
            return true
          }
        };
      
        $('.objType').unmark({
          done: function() {
            $('.objType')
              .toggle(valThis.length === 0)
              .mark(valThis, options);
          }
        })
      });
      input[type="text"] {
        width: 50%;
        margin: 10px;
        padding: 5px;
        float: left;
        clear: left;
      }
      
      div {
        float: left;
        clear: left;
        margin: 10px 10px;
      }
      
      .bold {
        font-weight: 700;
      }
      
      table td {
        border: solid 1px #ccc;
        padding: 3px;
      }
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.min.js" integrity="sha256-4HLtjeVgH0eIB3aZ9mLYF6E8oU5chNdjU6p6rrXpl9U=" crossorigin="anonymous"></script>
      
      <input placeholder="Filter results" id="box" type="text" />
      
      <div class="objType" id="item1">
        <span class="bold">Accepted</span> Event Relation
        <table>
          <tr>
            <td>Lorem</td>
            <td>ipsum</td>
          </tr>
        </table>
      </div>
      <div class="objType" id="item2">
        Case <span class="bold">Status</span> Value
        <table>
          <tr>
            <td>Lorem</td>
            <td>ipsum</td>
          </tr>
        </table>
      </div>
      <div class="objType" id="item3">
        External <span class="bold">Data Source</span>
        <table>
          <tr>
            <td>Lorem</td>
            <td>ipsum</td>
          </tr>
        </table>
      </div>
      <div class="objType" id="item4">
        Navigation <span class="bold">Link</span> Set
        <table>
          <tr>
            <td>Lorem</td>
            <td>ipsum</td>
          </tr>
        </table>
      </div>

      【讨论】:

      • 这里不能使用插件,抱歉
      • @JonSnow 没关系。它可能会帮助其他人
      • 这是最好的方法。其他方式有一定的局限性
      猜你喜欢
      • 2013-12-15
      • 2021-11-27
      • 2019-11-07
      • 2018-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-26
      • 2022-10-07
      相关资源
      最近更新 更多