Share via


Opções de navegação para o SharePoint

Este artigo descreve sites de opções de navegação com a Publicação do SharePoint ativada no SharePoint. A escolha e a configuração da navegação afetam significativamente o desempenho e a escalabilidade dos sites no SharePoint. O modelo de site de Publicação do SharePoint só deve ser utilizado se necessário para um portal centralizado e a funcionalidade de publicação só deve ser ativada em sites específicos e apenas quando necessário, uma vez que pode afetar o desempenho quando utilizado incorretamente.

Nota

Se estiver a utilizar opções de navegação modernas do SharePoint, como o menu mega, a navegação em cascata ou a navegação no hub, este artigo não se aplica ao seu site. As arquiteturas modernas de sites do SharePoint tiram partido de uma hierarquia de sites mais simplificada e de um modelo hub-and-spoke. Isto permite que sejam alcançados muitos cenários que NÃO requerem a utilização da funcionalidade Publicação do SharePoint.

Descrição geral das opções de navegação

A configuração do fornecedor de navegação pode afetar significativamente o desempenho de todo o site e tem de ter em consideração cuidadosamente a escolha de um fornecedor de navegação e configuração que dimensione eficazmente para os requisitos de um site do SharePoint. Existem dois fornecedores de navegação inicial, bem como implementações de navegação personalizadas.

A primeira opção, Navegação estrutural, é a opção de navegação recomendada no SharePoint para sites sharePoint clássicos, se ativar a colocação em cache de navegação estrutural para o seu site. Este fornecedor de navegação apresenta os itens de navegação abaixo do site atual e, opcionalmente, o site atual e os respetivos irmãos. Fornece capacidades adicionais, como a limitação de segurança e a enumeração da estrutura do site. Se a colocação em cache estiver desativada, isso afetará negativamente o desempenho e a escalabilidade e poderá estar sujeita a limitação.

A segunda opção, navegação Gerida (Metadados), representa itens de navegação através de um conjunto de termos de Metadados Geridos. Recomendamos que a limitação de segurança seja desativada, a menos que seja necessário. A limitação de segurança está ativada como uma definição segura por predefinição para este fornecedor de navegação; no entanto, muitos sites não necessitam da sobrecarga de limitação de segurança, uma vez que os elementos de navegação são frequentemente consistentes para todos os utilizadores do site. Com a configuração recomendada para desativar a limitação de segurança, este fornecedor de navegação não requer a enumeração da estrutura do site e é altamente dimensionável com um impacto de desempenho aceitável.

Além dos fornecedores de navegação inicial, muitos clientes implementaram com êxito implementações de navegação personalizadas alternativas. Veja Pesquisa scripts do lado do cliente orientados por Pesquisa neste artigo.

Opções de navegação prós e contras do SharePoint

A tabela seguinte resume os prós e contras de cada opção.

Navegação estrutural Navegação gerida navegação orientada por Pesquisa Fornecedor de navegação personalizada
Profissionais:

Fácil de manter
Segurança cortada
Atualiza automaticamente no prazo de 24 horas quando o conteúdo é alterado
Profissionais:

Fácil de manter
Profissionais:

Segurança cortada
Atualizações automáticas à medida que os sites são adicionados
Tempo de carregamento rápido e estrutura de navegação em cache local
Profissionais:

Escolha mais ampla de opções disponíveis
Carregamento rápido quando a colocação em cache é utilizada corretamente
Muitas opções funcionam bem com o design de página reativo
Contras:

Afeta o desempenho se a colocação em cache estiver desativada
Sujeito a limitação
Contras:

Não atualizado automaticamente para refletir a estrutura do site
Afeta o desempenho se a limitação de segurança estiver ativada ou quando a estrutura de navegação for complexa
Contras:

Sem capacidade de encomendar sites facilmente
Requer personalização da página mestra (são necessárias competências técnicas)
Contras:

O desenvolvimento personalizado é necessário
É necessária uma origem de dados externa/cache armazenada, por exemplo, o Azure

A opção mais adequada para o seu site depende dos requisitos do site e da sua capacidade técnica. Se quiser um fornecedor de navegação fácil de configurar que atualize automaticamente quando o conteúdo é alterado, a navegação estrutural com a colocação em cache ativada é uma boa opção.

Nota

Aplicar o mesmo princípio que os sites sharePoint modernos ao simplificar a estrutura geral do site para uma estrutura não hierárquica mais simples melhora o desempenho e simplifica a mudança para sites do SharePoint modernos. Isto significa que, em vez de ter uma única coleção de sites com centenas de sites (subwebs), uma abordagem melhor é ter muitas coleções de sites com muito poucos subsites (subwebs).

Analisar o desempenho da navegação no SharePoint

A ferramenta Diagnóstico de Página do SharePoint é uma extensão de browser para browsers Microsoft Edge e Chrome que analisa o portal moderno do SharePoint e as páginas de sites de publicação clássicas. Esta ferramenta só funciona para o SharePoint e não pode ser utilizada numa página de sistema do SharePoint.

A ferramenta gera um relatório para cada página analisada que mostra o desempenho da página num conjunto predefinido de regras e apresenta informações detalhadas quando os resultados de um teste ficam fora do valor de linha de base. Os administradores e designers do SharePoint podem utilizar a ferramenta para resolver problemas de desempenho para garantir que as novas páginas são otimizadas antes da publicação.

SPRequestDuration , em particular, é o tempo que o SharePoint demora a processar a página. A navegação intensa (como incluir páginas na navegação), hierarquias de sites complexas e outras opções de configuração e topologia podem contribuir significativamente para durações mais longas.

Utilizar a navegação estrutural no SharePoint

Esta é a navegação inicial utilizada por predefinição e é a solução mais simples. Não requer qualquer personalização e um utilizador não tecnico também pode facilmente adicionar itens, ocultar itens e gerir a navegação a partir da página de definições. Recomendamos que ative a colocação em cache, caso contrário, existe uma compensação de desempenho dispendiosa.

Como implementar a colocação em cache de navegação estrutural

Em Definições> do SiteProcurar e Sentir>Navegação, pode validar se a navegação estrutural está selecionada para navegação global ou navegação atual. Selecionar Mostrar páginas terá um impacto negativo no desempenho.

Navegação estrutural com a opção Mostrar Subsites selecionada.

A colocação em cache pode ser ativada ou desativada ao nível da coleção de sites e ao nível do site e está ativada para ambos por predefinição. Para ativar ao nível da coleção de sites, em Definições> do SiteAdministração da Coleçãode SitesNavegação da Coleção> de Sites, selecione a caixa Ativar colocação em cache.

Ativar a colocação em cache ao nível da coleção de sites.

Para ativar ao nível do site, emNavegaçãodas Definições> do Site, selecione a caixa Ativar colocação em cache.

Ativar a colocação em cache ao nível do site.

Utilizar navegação gerida e metadados no SharePoint

A navegação gerida é outra opção que pode utilizar para recriar a maioria das mesmas funcionalidades que a navegação estrutural. Os metadados geridos podem ser configurados para ter a limitação de segurança ativada ou desativada. Quando configurado com a limitação de segurança desativada, a navegação gerida é bastante eficiente, uma vez que carrega todas as ligações de navegação com um número constante de chamadas de servidor. No entanto, a ativação da limitação de segurança anula algumas das vantagens de desempenho da navegação gerida.

Se precisar de ativar o corte de segurança, recomendamos que:

  • Atualizar todas as ligações de URL amigáveis para ligações simples
  • Adicionar nós de corte de segurança necessários como URLs amigáveis
  • Limitar o número de itens de navegação a não mais de 100 e não mais de três níveis de profundidade

Muitos sites não necessitam de limitação de segurança, uma vez que a estrutura de navegação é frequentemente consistente para todos os utilizadores do site. Se a limitação de segurança estiver desativada e for adicionada uma ligação à navegação à qual nem todos os utilizadores têm acesso, a ligação continuará a ser apresentada, mas conduzirá a uma mensagem de acesso negado. Não existe qualquer risco de acesso inadvertido ao conteúdo.

Como implementar a navegação gerida e os resultados

Existem vários artigos sobre o Microsoft Learn sobre os detalhes da navegação gerida. Por exemplo, veja Descrição geral da navegação gerida no SharePoint Server.

Para implementar a navegação gerida, configure termos com URLs correspondentes à estrutura de navegação do site. A navegação gerida pode até ser organizada manualmente para substituir a navegação estrutural em muitos casos. Por exemplo:

Estrutura de site do SharePoint.)

Utilizar scripts do lado do cliente orientados por Pesquisa

Uma classe comum de implementações de navegação personalizada abrange padrões de design compostos pelo cliente que armazenam uma cache local de nós de navegação.

Estes fornecedores de navegação têm algumas vantagens principais:

  • Geralmente, funcionam bem com designs de página reativos.
  • São extremamente dimensionáveis e com desempenho porque podem ser compostos sem custos de recursos (e atualizar em segundo plano após um tempo limite).
  • Estes fornecedores de navegação podem obter dados de navegação com várias estratégias, desde configurações estáticas simples a vários fornecedores de dados dinâmicos.

Um exemplo de um fornecedor de dados é utilizar uma navegação orientada por Pesquisa, que permite flexibilidade para enumerar nós de navegação e processar a limitação de segurança de forma eficiente.

Existem outras opções populares para criar Fornecedores de navegação personalizados. Veja Soluções de navegação para portais do SharePoint para obter mais orientações sobre a criação de um Fornecedor de navegação personalizado.

Ao utilizar a pesquisa, pode tirar partido dos índices criados em segundo plano com a pesquisa contínua. Os resultados da pesquisa são retirados do índice de pesquisa e os resultados são cortados pela segurança. Geralmente, isto é mais rápido do que os fornecedores de navegação fora da caixa quando é necessário cortar a segurança. A utilização da pesquisa de navegação estrutural, especialmente se tiver uma estrutura de site complexa, acelerará consideravelmente o tempo de carregamento de páginas. A principal vantagem desta opção em termos de navegação gerida é o facto de beneficiar da limitação de segurança.

Esta abordagem envolve criar uma página mestra personalizada e substituir o código de navegação inicial por HTML personalizado. Siga este procedimento descrito no exemplo seguinte para substituir o código de navegação no ficheiro seattle.html. Neste exemplo, irá abrir o seattle.html ficheiro e substituir todo o elemento id="DeltaTopNavigation" por código HTML personalizado.

Exemplo: Substituir o código de navegação inicial numa página mestra

  1. Navegue para a página Definições do Site.
  2. Abra a galeria de páginas mestras ao clicar em Páginas Mestras.
  3. A partir daqui, pode navegar pela biblioteca e transferir o ficheiro seattle.master.
  4. Edite o código com um editor de texto e elimine o bloco de código na seguinte captura de ecrã.
    Elimine o bloco de código apresentado.
  5. Remova o código entre as <SharePoint:AjaxDelta id="DeltaTopNavigation"> etiquetas e <\SharePoint:AjaxDelta> e substitua-o pelo seguinte fragmento:
<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. Substitua o URL na etiqueta de âncora de imagem de carregamento no início por uma ligação para uma imagem de carregamento na sua coleção de sites. Depois de efetuar as alterações, mude o nome do ficheiro e, em seguida, carregue-o para a galeria de páginas mestras. Isto gera um novo ficheiro .master.
7. Este HTML é o markup básico que será preenchido pelos resultados da pesquisa devolvidos a partir do código JavaScript. Terá de editar o código para alterar o valor de var root = "URL da coleção de sites", conforme demonstrado no fragmento seguinte:
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";

8. Os resultados são atribuídos à matriz self.nodes e uma hierarquia é criada a partir dos objetos com linq.js atribuindo a saída a uma matriz self.hierarchy. Esta matriz é o objeto que está vinculado ao HTML. Isto é feito na função toggleView() ao transmitir o objeto autónomo para a função ko.applyBinding().
Isto faz com que a matriz de hierarquia esteja vinculada ao seguinte HTML:
<div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">

Os processadores de eventos para mouseenter e mouseexit são adicionados à navegação de nível superior para processar os menus pendentes do subsite que é feito na addEventsToElements() função.

No nosso exemplo de navegação complexo, uma nova carga de página sem a colocação em cache local mostra que o tempo gasto no servidor foi reduzido da navegação estrutural de referência para obter um resultado semelhante à abordagem de navegação gerida.

Acerca do ficheiro JavaScript...

Nota

Se utilizar JavaScript personalizado, certifique-se de que a CDN pública está ativada e que o ficheiro está numa localização da CDN.

Todo o ficheiro JavaScript é o seguinte:

//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");

Para resumir o código mostrado acima na jQuery $(document).ready função, é criada uma viewModel object função e, em seguida, a loadNavigationNodes() função nesse objeto é chamada. Esta função carrega a hierarquia de navegação anteriormente criada armazenada no armazenamento local HTML5 do browser do cliente ou chama a função queryRemoteInterface().

QueryRemoteInterface() cria um pedido com a getRequest() função com o parâmetro de consulta definido anteriormente no script e, em seguida, devolve dados do servidor. Estes dados são essencialmente uma matriz de todos os sites na coleção de sites representados como objetos de transferência de dados com várias propriedades.

Em seguida, estes dados são analisados nos objetos definidos SPO.Models.NavigationNode anteriormente, que utilizam Knockout.js para criar propriedades observáveis para utilização por dados que vinculam os valores ao HTML que definimos anteriormente.

Em seguida, os objetos são colocados numa matriz de resultados. Esta matriz é analisada em JSON com Knockout e armazenada no armazenamento do browser local para um desempenho melhorado em futuras cargas de página.

Benefícios desta abordagem

Uma das principais vantagens desta abordagem é que, ao utilizar o armazenamento local HTML5, a navegação é armazenada localmente para o utilizador da próxima vez que carregar a página. Obtemos grandes melhorias de desempenho ao utilizar a API de pesquisa para navegação estrutural; no entanto, é necessária alguma capacidade técnica para executar e personalizar esta funcionalidade.

Na implementação de exemplo, os sites são ordenados da mesma forma que a navegação estrutural inicial; ordem alfabética. Se quisesse desviar-se desta ordem, seria mais complicado desenvolver e manter. Além disso, esta abordagem requer que se desvie das páginas mestras suportadas. Se a página mestra personalizada não for mantida, o seu site perderá as atualizações e as melhorias que a Microsoft efetua nas páginas mestras.

O código acima tem as seguintes dependências:

A versão atual do LinqJS não contém o método ByHierarchy utilizado no código acima e irá quebrar o código de navegação. Para corrigir este problema, adicione o seguinte método ao ficheiro Linq.js antes da linha 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);
         })
     });
 },

Descrição geral da navegação gerida no SharePoint Server

Colocação em cache e desempenho de navegação estrutural