简体   繁体   中英

jquery filter script not ignoring diacritics and not highlighting string matches as user enters filter text into text input field

I have multiple vocabulary tables on the same html page.

Above each vocabulary table, I would like to enable users to type a word or phrase in a text input field to view only the table rows that contain the typed string (word or phrase). For example, if you type "good" in the text input field, the table rows that do not contain the string "good" will disappear. This is already working if you go to http://www.amanado.com/idioma-colombiano/ , click on "Vocabulario (oficial y de jerga) - palabras y frases comunes" to expand the accordion section, and then type "good" in the text input field below the words "Ingresa palabra o frase en el siguiente campo para filtrar la información de la tabla". After typing "good" into the text input field, all but 7 rows in the vocabulary table should disappear (7 rows remain).

I am having the following 3 issues:

1) I am unable to ignore accents (eg, é, ñ, ü) in the same way that case is already successfully ignored. For example, if a user enters "que" in the input field, rows that contain "que" and "qué" should not disappear. However, when you type "que", rows that contain "qué" do erroneously disappear. As you can see, if you type "que" into the input field (excluding the quotes), 2 records that contain "que" will remain. And, if you type or paste "qué" into the input field (excluding the quotes), 6 records that contain "qué" will remain.

2) I am trying to use a version of jquery.highlight.js to highlight the string matches in the rows that remain/do not disappear. For an example of how this should look visually, type "que" into the input field where instructed in the 2nd paragraph of this question summary and you will see the string "que" is highlighted in the 2 rows that remain/do not disappear. Note that this is not working correctly because I hardcoded the "que" highlight by inserting the script "$("table td").highlight("que");" into the "head" section of the html page for the purposes of demonstrating that (a) jquery.highlight.js is active/functioning and (b) providing a visual example of how the highlighted text is intended to appear.

3) In addition to the javascript that enables users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase not successfully ignoring accents (eg, é, ñ, ü), which is the desired behavior, the jquery.highlight.js script is also not successfully ignoring accents (eg, é, ñ, ü). For example, type "pues" in the input field where instructed in the 2nd paragraph of this question summary and you will multiple cases of the string "Qué" and "qué" not successfully highlighted in the rows that remain/do not disappear. Remember, I hardcoded the "que" highlight by inserting the script "$("table td").highlight("que");" into the section of the html page, so the strings "que", "qué", "Que" and "Qué" should all be highlighted in the table rows that remain/do not disappear if any of the strings "que", "qué", "Que" or "Qué" are entered into the input field given it is intended that (a) case and (b) accents (eg, é, ñ, ü) are ignored. It is interesting to note that functionionality to "ignoreAccents" is included in the version of jquery.highlight.js that I am using.

Below are:

(a) the input field as it appears in my html;

(b) the javascript I am using to enable users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase (for the purpose of brevity, this is referred to below as "filter javascript"); and

(c) the version of jquery.highlight.js javascript I am using to highlight text.

Please note: I am not a software engineer, but I do know how to implement a change if someone tells me what to do specifically (eg, make this exact change, then make this exact change, then make this exact change). I appreciate any assistance anyone can provide and literal instructions would be especially appreciated. And, It is always my intent to use the least amount of code (eg, javascript, css, html) to achieve the most.

Additional notes/considerations are included at the bottom of this question summary.

(a) input field starts here

<form class="live-search" action="" method="post">
<p>Ingresa palabra o frase en el siguiente campo para filtrar la información de la tabla</p>
<input class="input-text-tr" type="text" value="Mostrar sólo filas que contengan..." />
<span class="filter-count-tr"></span>
</form>

(a) input field ends here

(b) filter javascript starts here

$(function() {
$(".input-text-tr").on('keyup', function(e) {
var disallow = [37, 38, 39, 40];//ignore arrow keys    
if($.inArray(e.which, disallow) > -1) {
return true;
}
var inputField = this,
val = this.value,
pattern = new RegExp(val, "i"),
$group = $(this).closest(".group"),
$trs = $group.find(".myTable tbody tr"),
$s;
if(val === '') {
$s = $trs;
}
else {
$s = $();
$trs.stop(true,true).each(function(i, tr) {
if(val !== inputField.value) {//if user has made another     keystroke
return false;//break out of .each() and     hence out of the     event handler
}
$tr = $(tr);
if ($tr.text().match(pattern)) {
$s = $s.add(tr);
}
});
//$trs.not($s).fadeOut();
$trs.not($s).hide();
}
$group.find(".filter-count-tr").text("(" + $s.show        ().length + ")");
}).on('focus blur', function() {
if (this.defaultValue == this.value) this.value = '';
else if (this.value == '') this.value = this.defaultValue;
});

$(".group").each(function() {
$this = $(this);
$this.find(".filter-count-tr").text("(" + $this.find("tbody     tr").length + ")");
});
});

(b) filter javascript ends here

(c) jquery.highlight.js javascript starts here

jQuery.extend({
highlight: function (node, re, nodeName, className,     ignoreAccents) {
if (node.nodeType === 3) {

var nodeData = node.data;
if (ignoreAccents) {
nodeData = jQuery.removeDiacratics(nodeData);
}
var match = nodeData.match(re);
if (match) {
var highlight = document.createElement(nodeName || 'span');
highlight.className = className || 'highlight';    var wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);    
var wordClone = wordNode.cloneNode(true);
highlight.appendChild(wordClone);
wordNode.parentNode.replaceChild(highlight, wordNode);
return 1; //skip added node in parent
}
} else if ((node.nodeType === 1 && node.childNodes) && //     only element nodes that have children
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
!(node.tagName === nodeName.toUpperCase() &&
node.className === className)) { // skip if already     highlighted
for (var i = 0; i < node.childNodes.length; i++) {
i += jQuery.highlight(node.childNodes[i], re, nodeName, className, ignoreAccents);
}
}
return 0;
},

removeDiacratics : function(str) {
var rExps = [
{re:/[\xC0-\xC6]/g, ch:'A'},
{re:/[\xE0-\xE6]/g, ch:'a'},
{re:/[\xC8-\xCB]/g, ch:'E'},
{re:/[\xE8-\xEB]/g, ch:'e'},
{re:/[\xCC-\xCF]/g, ch:'I'},
{re:/[\xEC-\xEF]/g, ch:'i'},
{re:/[\xD2-\xD6]/g, ch:'O'},
{re:/[\xF2-\xF6]/g, ch:'o'},
{re:/[\xD9-\xDC]/g, ch:'U'},
{re:/[\xF9-\xFC]/g, ch:'u'},
{re:/[\xD1]/g, ch:'N'},
{re:/[\xF1]/g, ch:'n'}
];
for (var i = 0, len = rExps.length; i < len; i++) {
str = str.replace(rExps[i].re, rExps[i].ch);
}
return str;
}

});

jQuery.fn.unhighlight = function (options) {
var settings = { className: 'highlight', element: 'span' };
jQuery.extend(settings, options);

return this.find(settings.element + "." +     settings.className).each(
function () {
var parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
};

jQuery.fn.highlight = function (words, options) {
var settings = { className: 'highlight', element: 'span',     caseSensitive: false, wordsOnly: false, ignoreAccents : true };
jQuery.extend(settings, options);

if (words.constructor === String) {
words = [words];
}
words = jQuery.grep(words, function(word, i) {
return word != '';
});
words = jQuery.map(words, function(word, i) {
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
});
if (words.length == 0) {
return this;
}

var flag = settings.caseSensitive ? "" : "i";
var pattern = "(" + words.join("|") + ")";
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}

var re = [];
re.push(new RegExp(pattern, flag));

if (settings.ignoreAccents) {
var wordsNoAccents = jQuery.map(words, function(word, i) {
return jQuery.removeDiacratics(word);
});
var patternNoAccents;
if (settings.wordsOnly) {
// workaround for word separation using \\b
patternNoAccents = "( " + wordsNoAccents.join("|") + " )";
patternNoAccents = "\\b" + patternNoAccents + "\\b";
} else {
patternNoAccents = "(" + wordsNoAccents.join("|") + ")";
}
if (patternNoAccents!=pattern) {
re.push(new RegExp(patternNoAccents, flag));
}
}

return this.each(function () {
for (var i in re) {
jQuery.highlight(this, re[i], settings.element,     settings.className, settings.ignoreAccents);
}
});
};

(c) jquery.highlight.js javascript ends here

Additional notes/considerations start here

1) It is my intent to enhance, not depart from, the javascript I am already using to enable users to enter a word or phrase in a field to view only the table rows that contain the entered word or phrase because the javascript I am already using is working with the exception of the above issues (thanks to Beetroot's excellent contributions to a previous question I published).

2) javascript I've found that touches on the functionality I am trying to achieve includes the following 4 examples (note because stackoverflow does not allow me to use more than a couple links in a question, I replaced "http://" with "[http:// here]" in the below examples):

a) [http:// here]demopill.com/jquery-onpage-text-highlighter-and-filter.html [most closely resembles functionality I am trying to achive; seems to successfully filter and highlight as a user enters text into an input field; successfully ignores case, but does not successfully ignore accents (eg, é, ñ, ü)];

b) [http:// here]stackoverflow.com/search?q=jquery.highlight.js (dialogue on stackoverflow re: ignoring accented characters)

c) [http:// here]www.jquery.info/The-plugin-SearchHighlight (includes a highlight feature); and

d) [http:// here]docs.jquery.com/UI/Effects/Highlight (includes a highlight feature; note that I am already using "jquery ui" on the website referenced in paragraph 2 of this question summary).

Additional notes/considerations end here

Highlighting

With jquery.highlight.js installed on the page ...

change :

$group.find(".filter-count-tr").text("(" + $s.show().length + ")");

to :

$group.find(".filter-count-tr").text("(" + $s.show().unhighlight().highlight(val).length + ")");

However, the accent-insensitivity code below modifies this.

Accent Insensitivity

This seemed almost impossible but I had a breakthrough on finding this which indicates how the hightlight plugin might be modified to offer accent-insensitive highlighting.

To better understand the code, I refactored it into a better plugin (better for me anyway). It now puts no members into the jQuery namespace (previously one) and one member into jQuery.fn (previously two). With the new plugin, setting and unsetting highlights is performed as follows:

$(selector).highlight('set', words, options);
$(selector).highlight('unset', options);

Explanations and further examples are provided with the code (see below).

The 'set' settings include an '.accentInsensitive' option, which operates (I regret) on a limited number of hard-coded (Spanish) accented character groups implemented about as efficiently as I can manage using a private member in the plugin to cache reusable RegExps and replacement strings for later use by the 'set' method. It would be far better to have a generalized "Unicode normalisation" solution but that's something for another day.

The new plugin also afforded the opportunity to split out part of the code as a separate method, .makepattern , with the advantage that RegExp-ready patterns can be used externally, outside the plugin, with provision for them to be reinjected. This feature allows us to use the plugin as a resource for achieving the other aim here - namely accent-insensitive filtering - with absolute certainty that the RegExp patterns used (for highlighting and filtering) are identical.

Here's the plugin code :

/*
 * jQuery highlightIt plugin
 * by Beetroot-Beetroot
 * https://stackoverflow.com/users/1142252/beetroot-beetroot
 *
 * based on Highlight by Bartek Szopka, 2009
 * http://bartaz.github.com/sandbox.js/jquery.highlight.html,
 * based on highlight v3 by Johann Burkard
 * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
 *
 * Most important changes:
 * - Code refactored into jQuery preferred plugin pattern.
 * - Now called with : 
 *    - $(slector).highlight('set', words, options); previously $(slector).highlight(words, options);
 *    - $(slector).highlight('unset', options); previously $(slector).unhighlight(options);
 *    - $().highlight('makePattern', words, options); This new option returns a RegExp-ready pattern that can be used externally and/or re-injected for reuse (see .isPattern option below), thus avoiding remaking the pattern as might otherwise happen.
 *  - 'set' .isPattern option; When true, this new option indicates that the 'words' parameter is a prepared RegExp-ready pattern.
 *  - 'set' .accentInsensitive option; This new option is limited to operating on hard-coded character groups (eg, Spanish accented chars), not Unicode normalized (which would be a better approach but much harder to achieve and probably slower).
 *
 * Usage:
 *   // wrap every occurrance of text 'lorem' in content
 *   // with <span class='highlight'> (default options)
 *   $('#content').highlight('set', 'lorem');
 *
 *   // search for and highlight more terms at once
 *   // so you can save some time on traversing DOM
 *   $('#content').highlight(['set', 'lorem', 'ipsum']);
 *   $('#content').highlight('set', 'lorem ipsum');
 *
 *   // search only for entire word 'lorem'
 *   $('#content').highlight('set', 'lorem', { wordsOnly: true });
 *
 *   // don't ignore case during search of term 'lorem'
 *   $('#content').highlight('set', 'lorem', { caseSensitive: true });
 *
 *   // wrap every occurrance of term 'ipsum' in content
 *   // with <em class='important'>
 *   $('#content').highlight('set', 'ipsum', { element: 'em', className: 'important' });
 *
 *   // remove default highlight
 *   $('#content').highlight('unset');
 *
 *   // remove custom highlight
 *   $('#content').highlight('unset', { element: 'em', className: 'important' });
 *
 *   // get accent-insensitive pattern
 *   $().highlight('makePattern', { element: 'lorem', {'accentInsensitive':true});
 *
 *
 * Copyright (c) 2009 Bartek Szopka
 *
 * Licensed under MIT license.
 *
 */

(function($) {
    // **********************************
    // ***** Start: Private Members *****
    var pluginName = 'highlight';
    var accentedForms = [//Spanish accednted chars
        //Prototype ...
        //['(c|ç)', '[cç]', '[CÇ]', new RegExp('(c|ç)','g'), new RegExp('(C|Ç)','g')],
        ['(a|á)', '[aá]'],
        ['(e|é)', '[eé]'],
        ['(i|í)', '[ií]'],
        ['(n|ñ)', '[nñ]'],
        ['(o|ó)', '[oó]'],
        ['(u|ú|ü)', '[uúü]']
    ];
    //To save a lot of hard-coding and a lot of unnecessary repetition every time the "set" method is called, each row of accentedForms is now converted to the format of the prototype row, thus providing reusable RegExps and corresponding replacement strings.
    //Note that case-sensitivity is established later in the 'set' settings so we prepare separate RegExps for upper and lower case here.
    $.each(accentedForms, function(i, af) {
        af[2] = af[1].toUpperCase();
        af[3] = new RegExp(af[0], 'g');
        af[4] = new RegExp(af[0].toUpperCase(), 'g');
    });
    var h = function(node, re, settings) {
        if (node.nodeType === 3) {//text node
            var match = node.data.match(re);
            if (match) {
                var wordNode = node.splitText(match.index);
                wordNode.splitText(match[0].length);
                $(wordNode).wrap($("<" + settings.element + ">").addClass(settings.className));
                return 1;
           }
        } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
                !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
                !(node.tagName === settings.element.toUpperCase() && node.className === settings.className)) { // skip if already highlighted
            for (var i = 0; i < node.childNodes.length; i++) {
                i += h(node.childNodes[i], re, settings);
            }
        }
        return 0;
    };
    // ***** Fin: Private Members *****
    // ********************************

    // *********************************
    // ***** Start: Public Methods *****
    var methods = {
        //This is a utility method. It returns a string, not jQuery.
        makePattern: function (words, options) {
            var settings = {
                'accentInsensitive': false
            };
            $.extend(settings, options || {});
            if (words.constructor === String) {
                words = [words];
            }
            words = $.grep(words, function(word, i) {
              return word != '';
            });
            words = $.map(words, function(word, i) {
              return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
            });
            if (words.length == 0) { return ''; };
            var pattern = "(" + words.join("|") + ")";
            if (settings.accentInsensitive) {
                $.each(accentedForms, function(i, af) {
                    pattern = pattern.replace(af[3], af[1]).replace(af[4], af[2]);
                });
            }
            return pattern;
        },
        set: function (words, options) {
            var settings = {
                'className': 'highlight',
                'element': 'span',
                'caseSensitive': false,
                'wordsOnly': false,
                'accentInsensitive': false,
                'isPattern': false
            };
            $.extend(settings, options || {});

            var pattern = settings.isPattern ? words : methods.makePattern(words, settings);
            if (pattern === '') { return this; };
            if (settings.wordsOnly) {
                pattern = "\\b" + pattern + "\\b";
            }
            var flag = settings.caseSensitive ? "" : "i";
            var re = new RegExp(pattern, flag);
            return this.each(function () {
                h(this, re, settings);
            });
        },
        unset: function (options) {
            var settings = {
                className: 'highlight',
                element: 'span'
            }, parent;
            $.extend(settings, options || {});
            return this.find(settings.element + "." + settings.className).each(function () {
                parent = this.parentNode;
                parent.replaceChild(this.firstChild, this);
                parent.normalize();
            }).end();
        }
    };
    // ***** Fin: Public Methods *****
    // *******************************

    // *****************************
    // ***** Start: Supervisor *****
    $.fn[pluginName] = function( method ) {
        if ( methods[method] ) {
            return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || !method ) {
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
        }
    };
    // ***** Fin: Supervisor *****
    // ***************************
})( jQuery );

And here's the application code for the language site :

$(function() {
    $(".text-input").on('keyup', function(e) {
        var disallow = [37, 38, 39, 40];//ignore arrow keys
        if($.inArray(e.which, disallow) > -1) {
            return true;
        }
        var $group = $(this).closest(".group"),
            accent_sensitive = false,
            case_sensitive = false,
            val = this.value,
            pattern = $().highlight('makePattern', val, {
                'accentInsensitive': !accent_sensitive,
                'caseSensitive': case_sensitive
            }),
            $trs = $group.find(".myTable tbody tr"),
            $s;
        if(val === '') {
            $s = $trs;
        }
        else {
            $s = $();
            $trs.stop(true,true).each(function(i, tr) {
                $tr = $(tr);
                //if ($tr.text().match(new RegExp(pattern, "i"))) {
                if ($tr.text().match(new RegExp(pattern, case_sensitive ? '' : "i"))) {
                    $s = $s.add(tr);
                }
            });
            $trs.not($s).hide();
        }
        $group.find(".filter-count-tr").text("(" + $s.show().highlight('unset').highlight('set', pattern, {
            'isPattern':true,
            'caseSensitive':case_sensitive
        }).length + ")");
    }).on('focus blur', function() {
        if (this.defaultValue == this.value) this.value = '';
        else if (this.value == '') this.value = this.defaultValue;
    });

    $(".group").each(function() {
        $this = $(this);
        $this.find(".filter-count-tr").text("(" + $this.find("tbody tr").length + ")");
    });
});

All tested, so should work if installed properly.

By the way, I used this page as my source for Spanish accented characters.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM