2

I am trying to make good use of HTML definition lists, to serve as a glossary with tooltips. Please see this fiddle for the intended result (at the bottom).

If parts of the text in an article match the terms in the definition list, a tooltip bubble is shown with the matching definition. This is done by a surrounding A-tag. So far so good.

Now the question is how to parse the article so that all the parts that can be found in the glossary will get automatically surrounded by A-tags. The terms can be for instance:

  • group
  • social group
  • group behavior
  • research

The parsing has to be 'greedy' so it parses the longest definition first.

var arr = []; /* Array of terms, sorted by length */
$('#glossary dt').each( function() { arr.push($(this).text()); });
arr.sort(function(a,b) { return b.length - a.length; });

Next it has to surround occurrences of the first array-item in #article.html by A-tags, unless they are already within A-tags. Also this should be case-insensitive.

/* don't know how to approach this */

Finally move to the next array item and repeat.

/* ok, i can figure the loop out myself */

My problem is with checking if a certain string is already within A-tags and also with placing A-tags around a part of text. Replace should be avoided to keep the upper/lowercase the same. Check the JSfiddle for the intended result.


edit: Building on the solution of plalx below, I came up the following code:

/*
This script matches text strings in an article with terms in a defintion list (DL);
the DL acts as a glossary. The text strings are wrapped in '<span>' tags,
with the 'title' attribute containing the definition. This allows for easy custom tooltips.

- This script is 'greedy' the longest terms are matched first;
- This script preserves capitalization and escapes HTML in the definitions
 */
var $article = $('#container-inhoud');
var $terms = $('#glossary dt');
//clone to avoid multiple DOM reflows
var $clone = $article.clone();

//create a regex that matches all glossary terms (not case-sensitive), sorted by length
var rx = new RegExp('\\b(' + $terms.map(function () {
            return $(this).text();
        }).get().sort(function (term1, term2) {
                    return term2.length - term1.length;
                }).join('|') + ')\\b', 'ig');


var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;'
};

function escapeHtml(string) {
    return String(string).replace(/[&<>"'\/]/g, function (s) {
        return entityMap[s];
    });
}

//wrap any text string that corresponds with a definition term in a 'span' tag
//the title contains the connected definition.
function replacer(match){
        var definition = $terms.filter(function() {
            return $(this).text().toLowerCase() == match.toLowerCase();
        }).next('dd').text();
        definition = escapeHtml(definition);
        return '<span class="tooltip" title="' + definition + '">' + match + '</span>';
}

//call the replace function for every regex match
$clone.html($clone.html().replace(rx , replacer));

//unwrap the terms in the glossary section (to avoid tooltips within the glossary itself)
//only needed if the #glossary is within the #article container, otherwise delete the next line.
$clone.find('#glossary .tooltip').contents().unwrap();

$article.replaceWith($clone);

The following code displays the tooltips.

/*
 This script displays a tooltip for every 'span' with class 'tooltip'.
 The 'title' attribute will be displayed as the tooltip.
 The tooltip is displayed directly above and in line with the left side of the element.
 Any offsetting is done by manipulation the 'left' and 'top' attributes in the CSS.
 */
$("span.tooltip").hover(
        function () {
            var bubble = $("#bubble");
            bubble.text($(this).attr('title'));
            var ypos = $(this).offset().top - bubble.height();
            var xpos = $(this).offset().left;
            bubble.css({"left":xpos+"px","top":ypos+"px"});
            bubble.show();
        },
        function () {
            $("#bubble").hide();
        }
);

I used to following CSS to format the tooltip bubble:

#bubble{
            -webkit-border-radius: 4px;
            -moz-border-radius: 4px;
            border-radius: 4px;
            border: 1px solid #888;
            color: #ee6c31;
            background-color: rgba(255, 255, 255, 0.85);
            position:absolute;
            z-index:200;
            padding:5px 8px;
            margin: -15px 5px 5px -10px;
            display:none;
        }
4

2 回答 2

2

我为你设计了一个小解决方案。我认为这就是你想要的:

HTML 示例:

<div id="glossary">
    <dt>test</dt>
    <dt>Testing</dt>
    <dt>something</dt>
    <dt>some other thing</dt>
    <dt>long test</dt>
</div>

<div id="article">
    this is a test, to see if Testing will correctly be wrapped by something, or some other thing.
    This <a data-glossary-term href="#test">test</a> should not be wrapped again however, <span>something</span> should!
    Very long test should also be wrapped first.
</div>

JS:

$(function () {
    var $article = $('#article'),
        //clone to avoid multiple DOM reflows
        $clone = $article.clone(),
        //create a regex that matches all glossary terms (not case-sensitive)
        rx = new RegExp('\\b(' + $('#glossary dt').map(function () {
            return $(this).text();
        }).get().sort(function (term1, term2) {
            return term2.length - term1.length;
        }).join('|') + ')\\b', 'ig');

    //unwrap previously wrapped glossary terms to avoid issues
    //this would not be needed if your initial markup doesn't have terms wrapped already
    $clone.find('[data-glossary-term]').each(function () {
        $(this).replaceWith(this.childNodes);
    });

    //wrap terms in the 'a' tag
    $clone.html($clone.html().replace(rx, '<a data-glossary-term="$1" href="#$1">$1</a>'));

    $article.replaceWith($clone);
});
于 2013-09-05T15:48:41.563 回答
0

与其将 dt 文本收集到数组中,不如收集 dom 项本身,然后创建如下函数:

function add_tags(td) {
    var dd = td.next(),
        q = td.text()
        txt = dd.text().replace(q, '<a href="#">'+q+'</a>');

    dd.html(txt);
};

并循环它。您可能想要添加一些检查功能以查看您的单词周围是否已经有任何 a-标签,但这是一个很好的起点

于 2013-09-05T13:41:51.047 回答