Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter: Html tags included in results #515

Closed
Surfrat opened this issue Nov 11, 2015 · 10 comments
Closed

Filter: Html tags included in results #515

Surfrat opened this issue Nov 11, 2015 · 10 comments

Comments

@Surfrat
Copy link

Surfrat commented Nov 11, 2015

If you search using the filter usually the first letter will include results that have the same letter in the hidden html tags.

This can be clearly seen in the Filter example

http://wwwendt.de/tech/fancytree/demo/#sample-ext-filter.html

and enter "b" as the search term. The <b> tag of the html snippet is included. This should not be the case.
image

@mar10
Copy link
Owner

mar10 commented Nov 11, 2015

Thanks for reporting this.
I'd say the default filter matcher is not compatible with HTML-formatted titles.
This would require some HTML parsing, and could be solved by passing a custom callback to the filterNode() method.
If you - or someone else - come up with a general example, we can add it to the recipes: https://github.com/mar10/fancytree/wiki/ExtFilter
(or if it appears to be really robust, eventually add it to the core).

If we have a solution there, we might also extend the highlight option to accept a custom callback as well, since simply wrapping this in <mark> tags will result in invalid markup:
<<mark>b</mark>>html</b>

@Surfrat
Copy link
Author

Surfrat commented Nov 12, 2015

I have a solution.

Before:
screenshot - 2015-11-12 12 16 37 pm

After:
screenshot - 2015-11-12 1 59 49 pm

My solution is to use the renderNode event and either strip the <mark> tags or delete the node entirely.

In my controller I generate the node title....

FileViewModel fileViewModel = new FileViewModel
                {
                    Title =
                        blob.DisplayName + "<span><a  id='" + blob.FileName +
                        "' class='action-button-padding no-wrap' href='' name='download' title='Click to download: " +
                        blob.FileName + "' >" +
                        "<span class='glyphicon glyphicon-download-alt gi-1-0x download-touch-target'></span>" +
                        "</a></span>",
                    Key = blob.FileName,
                    Tooltip = blob.DisplayName,
                    ETag = blob.ETag,
                    FileName = blob.FileName,
                    DisplayName = blob.DisplayName,
                    LastModified = blob.LastModified
                };

then the code for the tree.

$("#documentsTree").fancytree({
                extensions: ["glyph", "table", "filter"],
                checkbox: true,
                glyph: glyphs,
                source: {
                    url: getBlobsUrl,
                    type: "POST",
                    headers: security.getSecurityHeaders(),
                    data: {
                        id: vm.id
                    }
                },
                filter: {
                    autoApply: true,
                    counter: false,
                    fuzzy: false,
                    hideExpandedCounter: true,
                    highlight: true,
                    mode: "hide"
                },
                quicksearch: true,
                renderNode: function(event, data) {
                    var titleWithHighlight = data.node.titleWithHighlight;
                    if (titleWithHighlight != null) {
                        var positionOfFirstSpan = titleWithHighlight.indexOf("<span>");
                        if (positionOfFirstSpan > 0) {
                            var titleBeforeSpan = titleWithHighlight.slice(0, positionOfFirstSpan);
                            var titleAfterSpan = titleWithHighlight.slice(positionOfFirstSpan);
                            if (titleBeforeSpan.indexOf("<mark>") > -1) { //Contains
                                titleAfterSpan = titleAfterSpan.replace(/<mark>/g, "");
                                titleAfterSpan = titleAfterSpan.replace(/<\/mark>/g, "");
                                data.node.titleWithHighlight = titleBeforeSpan + titleAfterSpan;
                                data.node.renderTitle();
                            } else {
                                if (titleAfterSpan.indexOf("<mark>") > -1) { //Contains
                                    data.node.remove();
                                    vm.numberOfResultsRemoved = vm.numberOfResultsRemoved + 1;

                                }
                            }
                        }
                    }

                }
            });

I split the highlight title on the first <span> and remove the <mark> tags causing the problem. I also delete nodes that do not have the search string in the title and update the found count.

$("input[name=documentsSearch]").keyup(function(e) {
                var opts = {
                    autoExpand: true,
                    leavesOnly: false
                };
                var match = $(this).val();

                if (e && e.which === $.ui.keyCode.ESCAPE || $.trim(match) === "") {
                    $("button#resetSearch").click();
                    return;
                }
                vm.numberOfResultsRemoved = 0;
                var numberOfResults = tree.filterNodes(match, opts);
                $("button#resetSearch").attr("disabled", false);
                $("span#matches").text("(" + (numberOfResults - vm.numberOfResultsRemoved) + " matches)");
            }).focus();

Note that this is not perfect as there are some orphaned folders. I also have no idea as to how this will impact performance. See this as a starting point.

mar10, many thanks for an awesome control.

@Surfrat
Copy link
Author

Surfrat commented Nov 12, 2015

Not perfect as searching for S , P, A or N on their own still breaks....

@Surfrat Surfrat closed this as completed Nov 12, 2015
@Surfrat Surfrat reopened this Nov 12, 2015
@Surfrat
Copy link
Author

Surfrat commented Nov 14, 2015

Updated the renderNode code. This allows searching for S , P, A or N and fixes js error due to removal of the node. Now just hide it instead.

renderNode: function(event, data) {
                    var titleWithHighlight = data.node.titleWithHighlight;
                    if (titleWithHighlight != null) {
                        var positionOfFirstSpan = titleWithHighlight.indexOf("<span>");
                        if (positionOfFirstSpan === -1) {
                            positionOfFirstSpan = titleWithHighlight.indexOf("<<mark>s</mark>pan>");
                        }
                        if (positionOfFirstSpan === -1) {
                            positionOfFirstSpan = titleWithHighlight.indexOf("<s<mark>p</mark>an>");
                        }
                        if (positionOfFirstSpan === -1) {
                            positionOfFirstSpan = titleWithHighlight.indexOf("<sp<mark>a</mark>n>");
                        }
                        if (positionOfFirstSpan === -1) {
                            positionOfFirstSpan = titleWithHighlight.indexOf("<spa<mark>n</mark>>");
                        }
                        if (positionOfFirstSpan > 0) {
                            var titleBeforeSpan = titleWithHighlight.slice(0, positionOfFirstSpan);
                            var titleAfterSpan = titleWithHighlight.slice(positionOfFirstSpan);
                            if (titleBeforeSpan.indexOf("<mark>") > -1) { //Contains
                                titleAfterSpan = titleAfterSpan.replace(/<mark>/g, "");
                                titleAfterSpan = titleAfterSpan.replace(/<\/mark>/g, "");
                                data.node.titleWithHighlight = titleBeforeSpan + titleAfterSpan;
                                data.node.renderTitle();
                            } else {
                                if (titleAfterSpan.indexOf("<mark>") > -1) { //Contains
                                    data.node.tr.classList.add("hide");
                                    vm.numberOfResultsRemoved = vm.numberOfResultsRemoved + 1;
                                }
                            }
                        }
                    }
                }

In my app.css

.hide {
    display: none;
}

@mar10
Copy link
Owner

mar10 commented Nov 15, 2015

cool if it works for you ;-)

For a general solution I'd say we would need an approach that will prevent matching the markup parts.
That means that the matcher function does not simply parse the node.title string, but ignores HTML tags like <span> or </b>. This means that we need a more sophisticated regular expression instead of the current simple substring matching. Or we store a plain node title (i.e. with all tags removed) version, that is used for matching.

This will be even more difficult, if combined with the fuzzy option.

For applying the highlight option, the matched regions must be merged into the original titles (i.e. the titles with tags).

This might be possible, but I don't think it is high priority now.
Will close this issue, until someone provides a general solution.

@mar10 mar10 closed this as completed Nov 15, 2015
@luk156
Copy link

luk156 commented Nov 18, 2015

i use this workaround:

         var strip  = function(html) {
                var tmp = document.createElement("DIV");
                tmp.innerHTML = html;
                return tmp.textContent || tmp.innerText || "";
            }

    // Default to 'match title substring (not case sensitive)'
    if(typeof filter === "string"){
        // console.log("rex", filter.split('').join('\\w*').replace(/\W/, ""))
        if( filterOpts.fuzzy ) {
            // See https://codereview.stackexchange.com/questions/23899/faster-javascript-fuzzy-string-matching-function/23905#23905
            // and http://www.quora.com/How-is-the-fuzzy-search-algorithm-in-Sublime-Text-designed
            // and http://www.dustindiaz.com/autocomplete-fuzzy-matching
            match = filter.split("").reduce(function(a, b) {
                return a + "[^" + b + "]*" + b;
            });
        } else {
            match = _escapeRegex(filter); // make sure a '.' is treated literally
        }



        re = new RegExp(".*" + match + ".*", "i");
        re2 = new RegExp(filter, "gi");
        filter = function(node){
            var res = !!re.test(strip(node.title));
            // node.debug("filter res", res, filterOpts.highlight)
            if( res && filterOpts.highlight ) {
                node.titleWithHighlight = strip(node.title).replace(re2, function(s){
                    return "<mark>" + s + "</mark>";
                });
            // } else {
            //  delete node.titleWithHighlight;
            }
            return res;
        };
    }

@mar10 mar10 added patch and removed waiting labels Nov 19, 2015
@mar10
Copy link
Owner

mar10 commented Nov 19, 2015

That looks promising, thanks!
There is already $.ui.fancytree.unescapeHtml(), and I think we should call it only once.
(also need to test performance impact...)
It might also still lead to unexpected behavior, if <input> or similar markup is contained.
So maybe it should be an opt-in option.

@mar10 mar10 reopened this Nov 19, 2015
@sanjay-git
Copy link

Hello @mar10

I was looking at unescapeHtml() which currently has this code:

unescapeHtml: function(s){
    var e = document.createElement("div");
    e.innerHTML = s;
    return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
},

If we use this in the filter function, the re.test() might not work as expected in scenarios where node title is Hello </br> there and the unescapeHtml of this title would return 'Hello' instead of 'Hello there' and a match wouldn't work when input is something like 'the'. Same issue even when used with titleWithHighlight

Thanks!

@mar10
Copy link
Owner

mar10 commented Dec 6, 2015

Does the strip() function that luk156 proposed work better?

@mkoch42
Copy link

mkoch42 commented Feb 25, 2016

luk156 solution works great to not search the html tags but only the text. Does anyone know how you could alter that code so that if the text is found, the entire node was returned / left intact if part of it matched the filter.

In my example i want to have a select element in the node and with luk156 code it will properly filter but it escapes out all of the html in the node when my goal is to leave the node exactly as it is if the text is found in any option of the select element.
Tree starts like this:
image

Returns like this after filter...
image

But the goal would be for it to filter for that node and leave it like the original way it starts (SS 1) and just hide all nodes that don't match at all!

Any help would be GREATLY appreciated.

Thanks

@mar10 mar10 closed this as completed in 32ad6df Apr 16, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants