Kendo TreeView Search with Highlight

笑着哭i 提交于 2019-12-05 17:38:31

Kendo's tree view widget doesn't like it if you muck around in its HTML, so I suggest modifying the data source instead (this will require the encoded option for all items in the DS).

In the keyup handler, you reset the DS whenever you search to clear highlighting, then instead of replacing the element's HTML directly, you set the model's text property:

$('#search-term').on('keyup', function () {
    var treeView = $("#treeview-sprites").getKendoTreeView();
    treeView.dataSource.data(pristine);

    // ignore if no search term
    if ($.trim($(this).val()) == '') {
        return;
    }

    var term = this.value.toUpperCase();
    var tlen = term.length;

    $('#treeview-sprites span.k-in').each(function (index) {
        var text = $(this).text();
        var html = '';
        var q = 0;
        while ((p = text.toUpperCase().indexOf(term, q)) >= 0) {
            html += text.substring(q, p) + '<span class="highlight">' + text.substr(p, tlen) + '</span>';
            q = p + tlen;
        }

        if (q > 0) {
            html += text.substring(q);

            var dataItem = treeView.dataItem($(this));
            dataItem.set("text", html);

            $(this).parentsUntil('.k-treeview').filter('.k-item').each(

            function (index, element) {
                $('#treeview-sprites').data('kendoTreeView').expand($(this));
                $(this).data('search-term', term);
            });
        }
    });

    $('#treeview-sprites .k-item').each(function () {
        if ($(this).data('search-term') != term) {
            $('#treeview-sprites').data('kendoTreeView').collapse($(this));
        }
    });
});

The tree definition needs the encoded option for this to work:

var pristine = [{
    encoded: false,
    text: "Kendo UI Project",
    expanded: true,
    spriteCssClass: "folder",
    items: [{
        encoded: false,
        text: "about.html",
        spriteCssClass: "html"
    }, {
        encoded: false,
        text: "index.html",
        spriteCssClass: "html"
    }, {
        encoded: false,
        text: "logo.png",
        spriteCssClass: "image"
    }]
}, {
    encoded: false,
    text: "New Web Site",
    expanded: true,
    spriteCssClass: "folder",
    items: [{
        encoded: false,
        text: "mockup.jpg",
        spriteCssClass: "image"
    }, {
        encoded: false,
        text: "Research.pdf",
        spriteCssClass: "pdf"
    }, ]
}, {
    encoded: false,
    text: "Reports",
    expanded: true,
    spriteCssClass: "folder",
    items: [{
        encoded: false,
        text: "February.pdf",
        spriteCssClass: "pdf"
    }, {
        encoded: false,
        text: "March.pdf",
        spriteCssClass: "pdf"
    }, {
        encoded: false,
        text: "April.pdf",
        spriteCssClass: "pdf"
    }]
}];

$("#treeview-sprites").kendoTreeView({
    dataSource: [{
        text: "My Documents",
        expanded: true,
        spriteCssClass: "rootfolder",
        items: pristine
    }]
});

(demo)

Good job guys, just what I neeeded!

Using your code I did a small tweak (actually added just two lines of jquery filtering), so that now when searching for a keyword, the treeview shows only the branches that contain highlighted texts. Easy peasy! :)

Other branches are hidden if they do not contain the higlighted text. Simple as that.

This means we now have a VisualStudio-like treeview search (see the Visual Studio Solution Explorer Search and Filter: http://goo.gl/qr7yVb).

Here's my code and demo on jsfiddle: http://jsfiddle.net/ComboFusion/d0qespaz/2/

HTML:

<input id="treeViewSearchInput"></input>
<ul id="treeview">
    <li data-expanded="true">My Web Site
        <ul>
            <li data-expanded="true">images
                <ul>
                    <li>logo.png</li>
                    <li>body-back.png</li>
                    <li>my-photo.jpg</li>
                </ul>
            </li>
            <li data-expanded="true">resources
                <ul>
                    <li data-expanded="true">pdf
                        <ul>
                            <li>brochure.pdf</li>
                            <li>prices.pdf</li>
                        </ul>
                    </li>
                    <li>zip</li>
                </ul>
            </li>
            <li>about.html</li>
            <li>contacts.html</li>
            <li>index.html</li>
            <li>portfolio.html</li>
        </ul>
    </li>
    <li>Another Root</li>
</ul>

CSS

span.k-in > span.highlight {
    background: #7EA700;
    color: #ffffff;
    border: 1px solid green;
    padding: 1px;
}

JAVASCRIPT

function InitSearch(treeViewId, searchInputId) {

    var tv = $(treeViewId).data('kendoTreeView');

    $(searchInputId).on('keyup', function () {

        $(treeViewId + ' li.k-item').show();

        $('span.k-in > span.highlight').each(function () {
            $(this).parent().text($(this).parent().text());
        });

        // ignore if no search term
        if ($.trim($(this).val()) === '') {
            return;
        }

        var term = this.value.toUpperCase();
        var tlen = term.length;

        $(treeViewId + ' span.k-in').each(function (index) {
            var text = $(this).text();
            var html = '';
            var q = 0;
            var p;

            while ((p = text.toUpperCase().indexOf(term, q)) >= 0) {
                html += text.substring(q, p) + '<span class="highlight">' + text.substr(p, tlen) + '</span>';
                q = p + tlen;
            }

            if (q > 0) {
                html += text.substring(q);
                $(this).html(html);

                $(this).parentsUntil('.k-treeview').filter('.k-item').each(function (index, element) {
                    tv.expand($(this));
                    $(this).data('SearchTerm', term);
                });
            }
        });

        $(treeViewId + ' li.k-item:not(:has(".highlight"))').hide();

        $(treeViewId + ' li.k-item').expand(".k-item");
    });
}

var $tv = $("#treeview").kendoTreeView();

InitSearch("#treeview", "#treeViewSearchInput");

Another tweak from me :)

What I did was change the highlight code in order to preserve anything else that might exist in the node html (such as sprite span for example).

I also implemented it as a TypeScript class wrapper around the TreeView.

If you don't want TypeScript stuff just copy the code out and it should work fine :)

export class SearchableTreeView {
    TreeView: kendo.ui.TreeView;
    emphasisClass: string;

    constructor(treeView: kendo.ui.TreeView) {
        this.TreeView = treeView;
        this.emphasisClass = "bg-warning";
    }

    search(term: string): void {
        var treeElement: JQuery = this.TreeView.element;
        var tv: kendo.ui.TreeView = this.TreeView;
        var emphClass = this.emphasisClass;

        this.resetHighlights();

        // ignore if no search term
        if ($.trim(term) === '') { return; }

        var term = term.toUpperCase();
        var tlen = term.length;

        $('span.k-in', treeElement).each(function (index) {
            // find all corresponding nodes
            var node = $(this);
            var htmlContent = node.html();
            var text = node.text();

            var searchPosition = text.toUpperCase().indexOf(term);
            if (searchPosition === -1) {
                // continue
                return true;
            }

            var generatedHtml = '<span class="highlight-container">' + text.substr(0, searchPosition) + '<span class="' + emphClass + '">' + text.substr(searchPosition, tlen) + '</span>' + text.substr(searchPosition + tlen) + '</span>';

            htmlContent = htmlContent.replace(text, generatedHtml);
            node.html(htmlContent);

            node.parentsUntil('.k-treeview').filter('.k-item').each(
                function (index, element) {
                    tv.expand($(this));
                    $(this).data('search-term', term);
                }
            );

        });

        $('.k-item', treeElement).each(function () {
            if ($(this).data('search-term') != term) {
                tv.collapse($(this));
            }
        });
    }

    resetHighlights(): void {
        this.TreeView.element.find("span.k-in:has('." + this.emphasisClass + "')")
            .each(function () {
                var node = $(this);
                var text = node.text();
                $(".highlight-container", node).remove();
                node.append(text);
            });
    }
}
   $("#textBox").on("input", function () {

    var query = this.value.toLowerCase();
    var dataSource = $("#Treeview").data("kendoTreeView").dataSource;

    filter(dataSource, query);
   });


  function filter(dataSource, query) {
        var uidData = [];
        var data = dataSource instanceof kendo.data.DataSource && dataSource.data();
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            var text = item.text.toLowerCase();
            var isChecked = item.checked;
            var itemVisible =
                query === true 
                || query === "" 
                || text.indexOf(query) >= 0; 

            uidData.push({ UID: item.uid, Visible: itemVisible });

        }

        if (query != "") {
            $.each(uidData, function (index, datavalue) {
                if (datavalue.Visible) {
                    $("li[data-uid='" + datavalue.UID + "']").addClass("highlight");
                }
                else {
                    $("li[data-uid='" + datavalue.UID + "']").removeClass("highlight");
                }

            });
        }
        else {
            $.each(uidData, function (index, datavalue) {
                $("li[data-uid='" + datavalue.UID + "']").removeClass("highlight");

            });
        }
    }

CSS :

.highlight {
    background:#0fa1ba;
    color:white;
}

For Angular 2+ you need to create a pipe for this feature.

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { NGXLogger } from 'ngx-logger';

@Pipe({
    name: 'highlight'
})

export class TypeaheadHighlight implements PipeTransform {
    constructor(private readonly _sanitizer: DomSanitizer, private readonly logger: NGXLogger) { }
    transform(matchItem: any, query: any): string {
        let matchedItem: any;
        if (matchItem) {
            matchedItem = matchItem.toString();
        }
        if (this.containsHtml(matchedItem)) {
            this.logger.warn('Unsafe use of typeahead please use ngSanitize');
        }
        matchedItem = query ? ('' + matchedItem).replace(new RegExp(this.escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchedItem; // Replaces the capture string with a the same string inside of a "strong" tag

        if (!this._sanitizer) {
            matchedItem = this._sanitizer.bypassSecurityTrustHtml(matchedItem);
        }

        return matchedItem;
    }

    escapeRegexp = (queryToEscape) => queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');

    containsHtml = (matchItem) => /<.*>/g.test(matchItem);

} 

Use of this pipe in html template....

            <input name="searchTerm" type="text" [(ngModel)]="searchTerm" (keyup)='onkeyup(searchTerm)'
             />
        </div>

        <div style="height: 70vh;overflow: auto">
            <kendo-treeview style="margin-top: 50px" id="availableColumns" [nodes]="availableColumns"
                textField="displayName" kendoTreeViewExpandable kendoTreeViewFlatDataBinding idField="id"
                parentIdField="parentId">
                <ng-template kendoTreeViewNodeTemplate let-dataItem>
                    <span [tooltip]="dataItem.columnDescription"
                        [innerHtml]="dataItem.displayName | highlight:searchTerm "></span>
                </ng-template>
            </kendo-treeview>
        </div> 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!