Angular: Search Tree with highlight match

http://angularjs.org/

by mnk

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
<script type="text/ng-template"  id="tree_item_renderer.html">
    <span ng-click="displayChildren(data)" data-text="data.name" highlight="searchFilter"></span>
    <ul>
        <li ng-repeat="data in data.nodes | filter: {display: true}" ng-include="'tree_item_renderer.html'"></li>
    </ul>
</script>

<ul ng-app="Application" ng-controller="TreeController">
    <input type="search" placeholder="Search categories ..." ng-model="searchFilter" ng-change="update()"></input>
    <li ng-repeat="data in tree | filter: {display: true}" ng-include="'tree_item_renderer.html'"></li>
</ul>

CSS

body {
    margin: 15px;
}
input {
    margin-bottom: 10px;
}
ul {
    font: 14px/18px Arial;
    list-style: circle;
}
li {
    list-style: none;
    color: #777;
    margin-left: 5px;
    cursor: pointer;
}
.highlight {
    font-weight: bold;
    color: black;
}

JavaScript

angular.module("myApp", [])
.controller("TreeController", ['$scope', function($scope) {
    
    $scope.searchFilter = '';
    
    $scope.displayChildren = function(node) {
        if (node.nodes) {
            for (var i = 0; i < node.nodes.length; i++) {
                node.nodes[i].display = !node.nodes[i].display;
            }
        }        
    };
    
    function generateGenres(prefix, genreCount, subgenreCount) {
        var nodes = [genreCount];
        for (var i = 0; i < genreCount; i++) {
            nodes[i] = {
                name: prefix + '-' + i,
                nodes: generateSubGenres(prefix + '-' + i, subgenreCount)
            };
        }
        return nodes;
    }
    
    function generateSubGenres(prefix, subgenreCount) {
        var nodes = [subgenreCount];
        for (var i = 0; i < subgenreCount; i++) {
            nodes[i] = {
                name: prefix + '-' + i
            };
        }
        return nodes;
    }
    
    $scope.tree = [{
        name: "Music",
        nodes: generateGenres('Music', 10, 25)
    }, {
        name: "Sports",
        nodes: generateGenres('Sports', 10, 25)
    }, {
        name: "Arts",
        nodes: generateGenres('Arts', 10, 25)
    }, {
        name: "Family",
        nodes: generateGenres('Family', 10, 25)
    }];
    
    $scope.update = function() {
        if ($scope.searchFilter === undefined || $scope.searchFilter.length === 0) {
            showOnlyRoots();
        } else {
            showMatches($scope.searchFilter);
        }
    };
    
    function showMatches(filter) {
        for (var i = 0; i < $scope.tree.length; i++) {
            updateSubtree(new RegExp(filter, 'i'), $scope.tree[i]);
        }
    }
    
    function showOnlyRoots() {
        var tree = $scope.tree;
        for (var i = 0; i < tree.length; i++) {
            tree[i].display = true;
            if (tree[i].nodes) {
                for (var j = 0; j < tree[i].nodes.length; j++) {
                   ...