Options de navigation pour SharePoint

Cet article décrit les sites d’options de navigation avec la publication SharePoint activée dans SharePoint. Le choix et la configuration de la navigation ont un impact significatif sur les performances et la scalabilité des sites dans SharePoint. Le modèle de site de publication SharePoint ne doit être utilisé que si nécessaire pour un portail centralisé et la fonctionnalité de publication ne doit être activée que sur des sites spécifiques et uniquement lorsque cela est nécessaire, car elle peut avoir un impact sur les performances en cas d’utilisation incorrecte.

Remarque

Si vous utilisez des options de navigation SharePoint modernes telles que le méga menu, la navigation en cascade ou la navigation hub, cet article ne s’applique pas à votre site. Les architectures de site SharePoint modernes tirent parti d’une hiérarchie de site plus aplati et d’un modèle hub-and-spoke. Cela permet de réaliser de nombreux scénarios qui ne nécessitent PAS l’utilisation de la fonctionnalité de publication SharePoint.

Vue d’ensemble des options de navigation

La configuration du fournisseur de navigation peut avoir un impact significatif sur les performances de l’ensemble du site. Vous devez donc choisir un fournisseur de navigation et une configuration qui s’adapte efficacement aux exigences d’un site SharePoint. Il existe deux fournisseurs de navigation prêtes à l’emploi, ainsi que des implémentations de navigation personnalisées.

La première option, Navigation structurelle, est l’option de navigation recommandée dans SharePoint pour les sites SharePoint classiques, si vous activez la mise en cache de la navigation structurelle pour votre site. Ce fournisseur de navigation affiche les éléments de navigation sous le site actuel, et éventuellement le site actuel et ses frères. Il fournit des fonctionnalités supplémentaires telles que le découpage de sécurité et l’énumération de la structure de site. Si la mise en cache est désactivée, cela aura un impact négatif sur les performances et la scalabilité, et peut être soumis à une limitation.

La deuxième option, Navigation managée (métadonnées), représente les éléments de navigation à l’aide d’un ensemble de termes de métadonnées gérées. Nous vous recommandons de désactiver la suppression de la sécurité, sauf si nécessaire. Le découpage de sécurité est activé en tant que paramètre sécurisé par défaut pour ce fournisseur de navigation ; Toutefois, de nombreux sites ne nécessitent pas la surcharge liée à la réduction de la sécurité, car les éléments de navigation sont souvent cohérents pour tous les utilisateurs du site. Avec la configuration recommandée pour désactiver le découpage de sécurité, ce fournisseur de navigation ne nécessite pas d’énumération de la structure de site et est hautement évolutif avec un impact acceptable sur les performances.

En plus des fournisseurs de navigation prêtes à l’emploi, de nombreux clients ont implémenté avec succès d’autres implémentations de navigation personnalisées. Consultez Recherche script côté client dans cet article.

Avantages et inconvénients des options de navigation SharePoint

Le tableau suivant récapitule les avantages et inconvénients de chaque option.

Navigation structurelle Navigation gérée navigation pilotée par Recherche Fournisseur de navigation personnalisée
Avantages:

Facile à entretenir
Sécurité réduite
Mises à jour automatiques dans les 24 heures lorsque le contenu est modifié
Avantages:

Facile à entretenir
Avantages:

Sécurité réduite
Mises à jour automatiques à mesure que des sites sont ajoutés
Temps de chargement rapide et structure de navigation mise en cache localement
Avantages:

Choix plus large d’options disponibles
Chargement rapide lorsque la mise en cache est utilisée correctement
De nombreuses options fonctionnent bien avec la conception de page réactive
Contre:

Impact sur les performances si la mise en cache est désactivée
Soumis à une limitation
Contre:

Non mis à jour automatiquement pour refléter la structure du site
Impact sur les performances si la réduction de la sécurité est activée ou lorsque la structure de navigation est complexe
Contre:

Pas de possibilité de commander facilement des sites
Nécessite une personnalisation de la page master (compétences techniques requises)
Contre:

Le développement personnalisé est requis
Une source de données externe/un cache stocké est nécessaire, par exemple, Azure

L’option la plus appropriée pour votre site dépend des exigences de votre site et de vos capacités techniques. Si vous souhaitez un fournisseur de navigation facile à configurer qui se met automatiquement à jour lorsque le contenu est modifié, la navigation structurelle avec mise en cache activée est une bonne option.

Remarque

L’application du même principe que les sites SharePoint modernes en simplifiant la structure globale du site à une structure plus plate et non hiérarchique améliore les performances et simplifie le déplacement vers des sites SharePoint modernes. Cela signifie qu’au lieu d’avoir une seule collection de sites avec des centaines de sites (sous-sites web), une meilleure approche consiste à avoir de nombreuses collections de sites avec très peu de sous-sites (sous-sites web).

Analyse des performances de navigation dans SharePoint

L’outil Diagnostics de page pour SharePoint est une extension de navigateur pour les navigateurs Microsoft Edge et Chrome qui analyse à la fois le portail moderne SharePoint et les pages du site de publication classique. Cet outil fonctionne uniquement pour SharePoint et ne peut pas être utilisé sur une page système SharePoint.

L’outil génère un rapport pour chaque page analysée montrant comment la page fonctionne par rapport à un ensemble prédéfini de règles et affiche des informations détaillées lorsque les résultats d’un test sont en dehors de la valeur de base. Les administrateurs et concepteurs SharePoint peuvent utiliser l’outil pour résoudre les problèmes de performances afin de s’assurer que les nouvelles pages sont optimisées avant la publication.

SPRequestDuration en particulier est le temps nécessaire à SharePoint pour traiter la page. Une navigation intensive (comme l’inclusion de pages dans la navigation), des hiérarchies de site complexes et d’autres options de configuration et de topologie peuvent contribuer de manière considérable à des durées plus longues.

Utilisation de la navigation structurelle dans SharePoint

Il s’agit de la navigation prête à l’emploi utilisée par défaut et il s’agit de la solution la plus simple. Il ne nécessite aucune personnalisation et un utilisateur non technique peut également facilement ajouter des éléments, masquer des éléments et gérer la navigation à partir de la page des paramètres. Nous vous recommandons d’activer la mise en cache, sinon il existe un compromis coûteux en matière de performances.

Comment implémenter la mise en cache structurelle de la navigation

SousNavigationd’apparence des>paramètres> du site, vous pouvez vérifier si la navigation structurelle est sélectionnée pour la navigation globale ou la navigation actuelle. La sélection de Afficher les pages aura un impact négatif sur les performances.

Navigation structurelle avec l’option Afficher les sous-sites sélectionnée.

La mise en cache peut être activée ou désactivée au niveau de la collection de sites et au niveau du site, et est activée pour les deux par défaut. Pour activer au niveau de la collection de sites, sous Paramètres> de site Collection de sitesAdministration>Navigation de la collection de sites, case activée la zone Activer la mise en cache.

Activez la mise en cache au niveau de la collection de sites.

Pour activer au niveau du site, sous Navigation paramètres> du site, case activée la zone Activer la mise en cache.

Activez la mise en cache au niveau du site.

Utilisation de la navigation managée et des métadonnées dans SharePoint

La navigation managée est une autre option prête à l’emploi que vous pouvez utiliser pour recréer la plupart des mêmes fonctionnalités que la navigation structurelle. Les métadonnées managées peuvent être configurées pour que le découpage de sécurité soit activé ou désactivé. Lorsqu’elle est configurée avec la suppression de la sécurité désactivée, la navigation managée est assez efficace, car elle charge tous les liens de navigation avec un nombre constant d’appels de serveur. Toutefois, l’activation de la réduction de la sécurité annule certains des avantages en matière de performances de la navigation managée.

Si vous avez besoin d’activer le filtrage de sécurité, nous vous recommandons d’effectuer les opérations suivantes :

  • Mettre à jour tous les liens d’URL conviviaux vers des liens simples
  • Ajouter des nœuds de découpage de sécurité requis en tant qu’URL conviviales
  • Limiter le nombre d’éléments de navigation à un maximum de 100 et à trois niveaux de profondeur maximum

De nombreux sites ne nécessitent pas de découpage de sécurité, car la structure de navigation est souvent cohérente pour tous les utilisateurs du site. Si la suppression de la sécurité est désactivée et qu’un lien est ajouté à la navigation à laquelle tous les utilisateurs n’ont pas accès, le lien s’affiche toujours, mais entraîne un message d’accès refusé. Il n’y a aucun risque d’accès involontaire au contenu.

Comment implémenter la navigation managée et les résultats

Il existe plusieurs articles sur Microsoft Learn sur les détails de la navigation managée. Par exemple, consultez Vue d’ensemble de la navigation managée dans SharePoint Server.

Pour implémenter la navigation managée, vous configurez des termes avec des URL correspondant à la structure de navigation du site. La navigation managée peut même être organisée manuellement pour remplacer la navigation structurelle dans de nombreux cas. Par exemple :

Structure de site SharePoint.)

Utilisation de scripts côté client pilotés par Recherche

Une classe courante d’implémentations de navigation personnalisées adopte les modèles de conception rendus par le client qui stockent un cache local de nœuds de navigation.

Ces fournisseurs de navigation présentent quelques avantages clés :

  • Ils fonctionnent généralement bien avec les conceptions de pages réactives.
  • Ils sont extrêmement évolutifs et performants, car ils peuvent effectuer un rendu sans coût de ressource (et l’actualiser en arrière-plan après un délai d’expiration).
  • Ces fournisseurs de navigation peuvent récupérer des données de navigation à l’aide de différentes stratégies, allant de configurations statiques simples à différents fournisseurs de données dynamiques.

Un exemple de fournisseur de données consiste à utiliser une navigation pilotée par Recherche, ce qui permet d’énumérer les nœuds de navigation et de gérer efficacement le découpage de sécurité.

Il existe d’autres options courantes pour créer des fournisseurs de navigation personnalisée. Consultez Solutions de navigation pour les portails SharePoint pour obtenir des conseils supplémentaires sur la création d’un fournisseur de navigation personnalisée.

À l’aide de la recherche, vous pouvez tirer parti des index qui sont créés en arrière-plan à l’aide de l’analyse continue. Les résultats de la recherche sont extraits de l’index de recherche et les résultats sont délimités par la sécurité. Cela est généralement plus rapide que les fournisseurs de navigation prêtes à l’emploi lorsque le découpage de sécurité est nécessaire. L’utilisation de la recherche pour la navigation structurelle, en particulier si vous avez une structure de site complexe, accélère considérablement le temps de chargement des pages. L’avantage main par rapport à la navigation managée est que vous bénéficiez de la réduction de la sécurité.

Cette approche implique la création d’une page de master personnalisée et le remplacement du code de navigation prête à l’emploi par du code HTML personnalisé. Suivez cette procédure décrite dans l’exemple suivant pour remplacer le code de navigation dans le fichier seattle.html. Dans cet exemple, vous allez ouvrir le seattle.html fichier et remplacer l’élément id="DeltaTopNavigation" entier par du code HTML personnalisé.

Exemple : remplacer le code de navigation prête à l’emploi dans une page master

  1. Accédez à la page Paramètres du site.
  2. Ouvrez la galerie de pages master en cliquant sur Pages maîtres.
  3. À partir de là, vous pouvez naviguer dans la bibliothèque et télécharger le fichier seattle.master.
  4. Modifiez le code à l’aide d’un éditeur de texte et supprimez le bloc de code dans la capture d’écran suivante.
    Supprimez le bloc de code affiché.
  5. Supprimez le code entre les balises et et <\SharePoint:AjaxDelta> remplacez-le <SharePoint:AjaxDelta id="DeltaTopNavigation"> par l’extrait de code suivant :
<div id="loading">
  <!--Replace with path to loading image.-->
  <div style="background-image: url(''); height: 22px; width: 22px; ">
  </div>
</div>
<!-- Main Content-->
<div id="navContainer" style="display:none">
    <div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">
        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
        </a>
        <ul id="menu" data-bind="foreach: $data.children" style="padding-left:20px">
            <li class="static dynamic-children level1">
                <a class="static dynamic-children menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

                 <!-- ko if: children.length > 0-->
                    <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                <!-- ko if: children.length == 0-->
                    <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
                        <span class="menu-item-text" data-bind="text: item.Title">
                        </span>
                    </span>
                <!-- /ko -->
                </a>

                <!-- ko if: children.length > 0-->
                <ul id="menu"  data-bind="foreach: children;" class="dynamic  level2" >
                    <li class="dynamic level2">
                        <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline  ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">

          <!-- ko if: children.length > 0-->
          <span aria-haspopup="true" class="additional-background ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
           <!-- /ko -->
          <!-- ko if: children.length == 0-->
          <span aria-haspopup="true" class="ms-navedit-flyoutArrow dynamic-children">
           <span class="menu-item-text" data-bind="text: item.Title">
           </span>
          </span>
          <!-- /ko -->
                        </a>
          <!-- ko if: children.length > 0-->
         <ul id="menu" data-bind="foreach: children;" class="dynamic level3" >
          <li class="dynamic level3">
           <a class="dynamic menu-item ms-core-listMenu-item ms-displayInline ms-navedit-linkNode" data-bind="attr: { href: item.Url, title: item.Title }">
            <span class="menu-item-text" data-bind="text: item.Title">
            </span>
           </a>
          </li>
         </ul>
           <!-- /ko -->
                    </li>
                </ul>
                <!-- /ko -->
            </li>
        </ul>
    </div>
</div>

6. Remplacez l’URL de la balise d’ancrage de l’image de chargement au début par un lien vers une image de chargement dans votre collection de sites. Après avoir apporté les modifications, renommez le fichier, puis chargez-le dans la galerie de pages master. Cela génère un nouveau . master fichier.
7. Ce code HTML est le balisage de base qui sera rempli par les résultats de recherche retournés à partir du code JavaScript. Vous devez modifier le code pour modifier la valeur de var root = « URL de collection de sites », comme illustré dans l’extrait de code suivant :
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";

8. Les résultats sont affectés au tableau self.nodes et une hiérarchie est générée à partir des objets à l’aide de linq.js affectant la sortie à un tableau self.hierarchy. Ce tableau est l’objet qui est lié au code HTML. Cette opération s’effectue dans la fonction toggleView() en passant l’objet self à la fonction ko.applyBinding().
Le tableau de hiérarchie est alors lié au code HTML suivant :
<div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">

Les gestionnaires d’événements pour mouseenter et mouseexit sont ajoutés à la navigation de niveau supérieur pour gérer les menus déroulants du sous-site, ce qui est effectué dans la addEventsToElements() fonction .

Dans notre exemple de navigation complexe, un nouveau chargement de page sans mise en cache locale indique que le temps passé sur le serveur a été réduit de la navigation structurelle de référence pour obtenir un résultat similaire à l’approche de navigation managée.

À propos du fichier JavaScript...

Remarque

Si vous utilisez un code JavaScript personnalisé, vérifiez que le CDN public est activé et que le fichier se trouve dans un emplacement CDN.

Le fichier JavaScript entier est le suivant :

//Models and Namespaces
var SPOCustom = SPOCustom || {};
SPOCustom.Models = SPOCustom.Models || {}
SPOCustom.Models.NavigationNode = function () {

    this.Url = ko.observable("");
    this.Title = ko.observable("");
    this.Parent = ko.observable("");

};

var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";
var baseUrl = root + "/_api/search/query?querytext=";
var query = baseUrl + "'contentClass=\"STS_Web\"+path:" + root + "'&trimduplicates=false&rowlimit=300";

var baseRequest = {
    url: "",
    type: ""
};


//Parses a local object from JSON search result.
function getNavigationFromDto(dto) {
    var item = new SPOCustom.Models.NavigationNode();
    if (dto != undefined) {

        var webTemplate = getSearchResultsValue(dto.Cells.results, 'WebTemplate');

        if (webTemplate != "APP") {
            item.Title(getSearchResultsValue(dto.Cells.results, 'Title')); //Key = Title
            item.Url(getSearchResultsValue(dto.Cells.results, 'Path')); //Key = Path
            item.Parent(getSearchResultsValue(dto.Cells.results, 'ParentLink')); //Key = ParentLink
        }

    }
    return item;
}

function getSearchResultsValue(results, key) {

    for (i = 0; i < results.length; i++) {
        if (results[i].Key == key) {
            return results[i].Value;
        }
    }
    return null;
}

//Parse a local object from the serialized cache.
function getNavigationFromCache(dto) {
    var item = new SPOCustom.Models.NavigationNode();

    if (dto != undefined) {

        item.Title(dto.Title);
        item.Url(dto.Url);
        item.Parent(dto.Parent);
    }

    return item;
}

/* create a new OData request for JSON response */
function getRequest(endpoint) {
    var request = baseRequest;
    request.type = "GET";
    request.url = endpoint;
    request.headers = { ACCEPT: "application/json;odata=verbose" };
    return request;
};

/* Navigation Module*/
function NavigationViewModel() {
    "use strict";
    var self = this;
    self.nodes = ko.observableArray([]);
    self.hierarchy = ko.observableArray([]);;
    self.loadNavigatioNodes = function () {
        //Check local storage for cached navigation datasource.
        var fromStorage = localStorage["nodesCache"];
        if (false) {
            var cachedNodes = JSON.parse(localStorage["nodesCache"]);

            if (cachedNodes && timeStamp) {
                //Check for cache expiration. Currently set to 3 hrs.
                var now = new Date();
                var diff = now.getTime() - timeStamp;
                if (Math.round(diff / (1000 * 60 * 60)) < 3) {

                    //return from cache.
                    var cacheResults = [];
                    $.each(cachedNodes, function (i, item) {
                        var nodeitem = getNavigationFromCache(item, true);
                        cacheResults.push(nodeitem);
                    });

                    self.buildHierarchy(cacheResults);
                    self.toggleView();
                    addEventsToElements();
                    return;
                }
            }
        }
        //No cache hit, REST call required.
        self.queryRemoteInterface();
    };

    //Executes a REST call and builds the navigation hierarchy.
    self.queryRemoteInterface = function () {
        var oDataRequest = getRequest(query);
        $.ajax(oDataRequest).done(function (data) {
            var results = [];
            $.each(data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results, function (i, item) {

                if (i == 0) {
                    //Add root element.
                    var rootItem = new SPOCustom.Models.NavigationNode();
                    rootItem.Title("Root");
                    rootItem.Url(root);
                    rootItem.Parent(null);
                    results.push(rootItem);
                }
                var navItem = getNavigationFromDto(item);
                results.push(navItem);
            });
            //Add to local cache
            localStorage["nodesCache"] = ko.toJSON(results);

            localStorage["nodesCachedAt"] = new Date().getTime();
            self.nodes(results);
            if (self.nodes().length > 0) {
                var unsortedArray = self.nodes();
                var sortedArray = unsortedArray.sort(self.sortObjectsInArray);

                self.buildHierarchy(sortedArray);
                self.toggleView();
                addEventsToElements();
            }
        }).fail(function () {
            //Handle error here!!
            $("#loading").hide();
            $("#error").show();
        });
    };
    self.toggleView = function () {
        var navContainer = document.getElementById("navContainer");
        ko.applyBindings(self, navContainer);
        $("#loading").hide();
        $("#navContainer").show();

    };
    //Uses linq.js to build the navigation tree.
    self.buildHierarchy = function (enumerable) {
        self.hierarchy(Enumerable.From(enumerable).ByHierarchy(function (d) {
            return d.Parent() == null;
        }, function (parent, child) {
            if (parent.Url() == null || child.Parent() == null)
                return false;
            return parent.Url().toUpperCase() == child.Parent().toUpperCase();
        }).ToArray());

        self.sortChildren(self.hierarchy()[0]);
    };


    self.sortChildren = function (parent) {

        // sjip processing if no children
        if (!parent || !parent.children || parent.children.length === 0) {
            return;
        }

        parent.children = parent.children.sort(self.sortObjectsInArray2);

        for (var i = 0; i < parent.children.length; i++) {
            var elem = parent.children[i];

            if (elem.children && elem.children.length > 0) {
                self.sortChildren(elem);
            }
        }
    };

    // ByHierarchy method breaks the sorting in chrome and firefox
    // we need to resort  as ascending
    self.sortObjectsInArray2 = function (a, b) {
        if (a.item.Title() > b.item.Title())
            return 1;
        if (a.item.Title() < b.item.Title())
            return -1;
        return 0;
    };


    self.sortObjectsInArray = function (a, b) {
        if (a.Title() > b.Title())
            return -1;
        if (a.Title() < b.Title())
            return 1;
        return 0;
    }
}

//Loads the navigation on load and binds the event handlers for mouse interaction.
function InitCustomNav() {
    var viewModel = new NavigationViewModel();
    viewModel.loadNavigatioNodes();
}

function addEventsToElements() {
    //events.
      $("li.level1").mouseover(function () {
          var position = $(this).position();
          $(this).find("ul.level2").css({ width: 100, left: position.left + 10, top: 50 });
      })
   .mouseout(function () {
     $(this).find("ul.level2").css({  left: -99999, top: 0 });
   
    });
   
     $("li.level2").mouseover(function () {
          var position = $(this).position();
          console.log(JSON.stringify(position));
          $(this).find("ul.level3").css({ width: 100, left: position.left + 95, top:  position.top});
      })
   .mouseout(function () {
     $(this).find("ul.level3").css({  left: -99999, top: 0 });
    });
} _spBodyOnLoadFunctionNames.push("InitCustomNav");

Pour résumer le code indiqué ci-dessus dans la jQuery $(document).ready fonction, un viewModel object est créé, puis la loadNavigationNodes() fonction sur cet objet est appelée. Cette fonction charge la hiérarchie de navigation précédemment générée stockée dans le stockage local HTML5 du navigateur client ou elle appelle la fonction queryRemoteInterface().

QueryRemoteInterface() génère une requête à l’aide de la getRequest() fonction avec le paramètre de requête défini précédemment dans le script, puis retourne des données à partir du serveur. Ces données sont essentiellement un tableau de tous les sites de la collection de sites représentés sous forme d’objets de transfert de données avec différentes propriétés.

Ces données sont ensuite analysées dans les objets précédemment définis SPO.Models.NavigationNode qui utilisent Knockout.js pour créer des propriétés observables à utiliser en liant les valeurs dans le code HTML que nous avons défini précédemment.

Les objets sont ensuite placés dans un tableau de résultats. Ce tableau est analysé au format JSON à l’aide de Knockout et stocké dans le stockage du navigateur local pour améliorer les performances lors des chargements de pages futurs.

Avantages de cette approche

L’un des principaux avantages de cette approche est qu’en utilisant le stockage local HTML5, la navigation est stockée localement pour l’utilisateur la prochaine fois qu’il charge la page. Nous obtenons des améliorations majeures des performances de l’utilisation de l’API de recherche pour la navigation structurelle ; Toutefois, il faut une certaine capacité technique pour exécuter et personnaliser cette fonctionnalité.

Dans l’exemple d’implémentation, les sites sont classés de la même façon que la navigation structurelle prête à l’emploi ; ordre alphabétique. Si vous vouliez vous écarter de cet ordre, il serait plus compliqué de développer et de maintenir. En outre, cette approche vous oblige à vous écarter des pages master prises en charge. Si la page de master personnalisée n’est pas conservée, votre site manquera les mises à jour et les améliorations apportées par Microsoft aux pages master.

Le code ci-dessus a les dépendances suivantes :

La version actuelle de LinqJS ne contient pas la méthode ByHierarchy utilisée dans le code ci-dessus et interrompt le code de navigation. Pour résoudre ce problème, ajoutez la méthode suivante au fichier Linq.js avant la ligne Flatten: function ().

ByHierarchy: function(firstLevel, connectBy, orderBy, ascending, parent) {
     ascending = ascending == undefined ? true : ascending;
     var orderMethod = ascending == true ? 'OrderBy' : 'OrderByDescending';
     var source = this;
     firstLevel = Utils.CreateLambda(firstLevel);
     connectBy = Utils.CreateLambda(connectBy);
     orderBy = Utils.CreateLambda(orderBy);

     //Initiate or increase level
     var level = parent === undefined ? 1 : parent.level + 1;

    return new Enumerable(function() {
         var enumerator;
         var index = 0;

        var createLevel = function() {
                 var obj = {
                     item: enumerator.Current(),
                     level : level
                 };
                 obj.children = Enumerable.From(source).ByHierarchy(firstLevel, connectBy, orderBy, ascending, obj);
                 if (orderBy !== undefined) {
                     obj.children = obj.children[orderMethod](function(d) {
                         return orderBy(d.item); //unwrap the actual item for sort to work
                     });
                 }
                 obj.children = obj.children.ToArray();
                 Enumerable.From(obj.children).ForEach(function(child) {
                     child.getParent = function() {
                         return obj;
                     };
                 });
                 return obj;
             };

        return new IEnumerator(

        function() {
             enumerator = source.GetEnumerator();
         }, function() {
             while (enumerator.MoveNext()) {
                 var returnArr;
                 if (!parent) {
                     if (firstLevel(enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }

                } else {
                     if (connectBy(parent.item, enumerator.Current(), index++)) {
                         return this.Yield(createLevel());
                     }
                 }
             }
             return false;
         }, function() {
             Utils.Dispose(enumerator);
         })
     });
 },

Vue d’ensemble de la navigation gérée dans SharePoint Server

Mise en cache et performances de la navigation structurelle