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

Este artigo descreve os sites de opções de navegação com a Publicação do SharePoint habilitada no SharePoint Online. A escolha e a configuração da navegação afetam significativamente o desempenho e a escalabilidade dos sites no SharePoint Online. O modelo de site de Publicação do SharePoint só deve ser usado se necessário para um portal centralizado e o recurso de publicação só deve ser habilitado em sites específicos e somente quando absolutamente necessário, pois pode afetar o desempenho quando usado incorretamente.

Observação

Se você estiver usando opções modernas de navegação do SharePoint, como mega menu, navegação em cascata ou navegação de hub, este artigo não se aplicará ao seu site. As arquiteturas de site modernas do SharePoint aproveitam uma hierarquia de site mais nivelada e um modelo hub-and-spoke. Isso permite que muitos cenários sejam obtidos que NÃO exigem o uso do recurso de Publicação do SharePoint.

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

A configuração do provedor de navegação pode afetar significativamente o desempenho de todo o site e deve ser tomada uma consideração cuidadosa para escolher um provedor de navegação e uma configuração que seja dimensionada efetivamente para os requisitos de um site do SharePoint. Há dois provedores de navegação personalizados, bem como implementações de navegação personalizadas.

A primeira opção, navegação estrutural, é a opção de navegação recomendada no SharePoint Online para sites clássicos do SharePoint, se você ativar o cache de navegação estrutural para seu site. Esse provedor de navegação exibe os itens de navegação abaixo do site atual e, opcionalmente, o site atual e seus irmãos. Ele fornece recursos adicionais, como filtragem de segurança e enumeração de estrutura do site. Se o cache estiver desabilitado, isso afetará negativamente o desempenho e a escalabilidade e poderá estar sujeito à limitação.

A segunda opção, navegação Gerenciada (Metadados), representa itens de navegação usando um conjunto de termos de Metadados Gerenciados. Recomendamos que a filtragem de segurança seja desabilitada, a menos que seja necessário. A filtragem de segurança está habilitada como uma configuração segura por padrão para este provedor de navegação; No entanto, muitos sites não exigem a sobrecarga de filtragem de segurança, pois os elementos de navegação geralmente são consistentes para todos os usuários do site. Com a configuração recomendada para desabilitar a filtragem de segurança, esse provedor de navegação não exige a enumeração da estrutura do site e é altamente escalonável com impacto de desempenho aceitável.

Além dos provedores de navegação personalizados, muitos clientes implementaram implementações de navegação personalizada alternativa com êxito. Consulte scripts do lado do cliente controlados por pesquisa neste artigo.

Prós e contras das opções de navegação do SharePoint Online

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

Navegação estrutural Navegação gerenciada Navegação controlada por pesquisa Provedor de navegação personalizada
Profissionais:

Fácil de manter
Segurança cortada
É atualizado automaticamente dentro 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 armazenada localmente em cache
Profissionais:

Escolha mais ampla das opções disponíveis
Carregamento rápido quando o cache é usado corretamente
Muitas opções funcionam bem com o design de página responsivo
Contras:

Afeta o desempenho se o cache estiver desabilitado
Sujeito à limitação
Contras:

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

Não há capacidade de solicitar sites facilmente
Requer personalização da página mestra (habilidades técnicas necessárias)
Contras:

O desenvolvimento personalizado é necessário
A fonte de dados externa/o cache armazenado é necessário, por exemplo, o Azure

A opção mais apropriada para seu site dependerá dos requisitos do site e da capacidade técnica. Se você quiser um provedor de navegação fácil de configurar que seja atualizado automaticamente quando o conteúdo for alterado, a navegação estrutural com o cache habilitado será uma boa opção.

Observação

Aplicar o mesmo princípio que os sites modernos do SharePoint simplificando a estrutura geral do site a uma estrutura lisonjeada e não hierárquica melhora o desempenho e simplifica a migração para sites modernos do SharePoint. Isso significa que, em vez de ter um único conjunto de sites com centenas de sites (subwebs), uma abordagem melhor é ter muitos conjuntos de sites com poucos subsites (subwebs).

Analisando o desempenho de navegação no SharePoint Online

A ferramenta Diagnóstico de Página para SharePoint é uma extensão do navegador para navegadores Microsoft Edge e Chrome que analisa o portal moderno do SharePoint Online e páginas clássicas do site de publicação. Essa ferramenta só funciona para o SharePoint Online e não pode ser usada em uma página do sistema do SharePoint.

A ferramenta gera um relatório para cada página analisada mostrando como a página é executada em relação a um conjunto predefinido de regras e exibe informações detalhadas quando os resultados de um teste ficam fora do valor de linha de base. Os administradores e designers do SharePoint Online podem usar a ferramenta para solucionar problemas de desempenho para garantir que novas páginas sejam otimizadas antes da publicação.

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

Usando a navegação estrutural no SharePoint Online

Essa é a navegação completa usada por padrão e é a solução mais simples. Ele não requer nenhuma personalização e um usuário não técnico também pode adicionar itens, ocultar itens e gerenciar facilmente a navegação na página de configurações. É recomendável habilitar o cache, caso contrário, há uma compensação de desempenho cara.

Como implementar o cache de navegação estrutural

Em Navegação de Aparência das > > Configurações do Site, você 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 Mostrar Subsites selecionado.

O cache pode ser habilitado ou desabilitado no nível do conjunto de sites e no nível do site e é habilitado para ambos por padrão. Para habilitar no nível do conjunto de sites, em Configurações do Site > De Navegação do Conjunto de Sites > de Administração do Conjunto de Sites, marque a caixa Habilitar cache.

Habilitar o cache no nível do conjunto de sites.

Para habilitar no nível do site, em Navegação de Configurações do > Site , marque a caixa Habilitar cache.

Habilitar o cache no nível do site.

Usando navegação gerenciada e metadados no SharePoint Online

A navegação gerenciada é outra opção completa que você pode usar para recriar a maioria das mesmas funcionalidades que a navegação estrutural. Os metadados gerenciados podem ser configurados para que a filtragem de segurança esteja habilitada ou desabilitada. Quando configurado com a filtragem de segurança desabilitada, a navegação gerenciada é bastante eficiente, pois carrega todos os links de navegação com um número constante de chamadas de servidor. Habilitar a filtragem de segurança, no entanto, nega algumas das vantagens de desempenho da navegação gerenciada.

Se você precisar habilitar a filtragem de segurança, recomendamos que você:

  • Atualizar todos os links de URL amigáveis para links 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, no máximo, 100 e não mais de 3 níveis de profundidade

Muitos sites não exigem corte de segurança, pois a estrutura de navegação geralmente é consistente para todos os usuários do site. Se a filtragem de segurança estiver desabilitada e um link for adicionado à navegação à qual nem todos os usuários têm acesso, o link ainda será mostrado, mas levará a uma mensagem de acesso negado. Não há risco de acesso inadvertido ao conteúdo.

Como implementar a navegação gerenciada e os resultados

Há vários artigos sobre docs.microsoft.com sobre os detalhes da navegação gerenciada. Por exemplo, consulte Visão geral da navegação gerenciada no SharePoint Server.

Para implementar a navegação gerenciada, você configura termos com URLs correspondentes à estrutura de navegação do site. A navegação gerenciada pode até mesmo ser coletada manualmente para substituir a navegação estrutural em muitos casos. Por exemplo:

Estrutura do site do SharePoint Online.)

Usando scripts do lado do cliente controlados por pesquisa

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

Esses provedores de navegação têm algumas vantagens principais:

  • Eles geralmente funcionam bem com designs de página responsivos.
  • Eles são extremamente escalonáveis e de alto desempenho porque podem renderizar sem custo de recurso (e atualizar em segundo plano após um tempo limite).
  • Esses provedores de navegação podem recuperar dados de navegação usando várias estratégias, desde configurações estáticas simples até vários provedores de dados dinâmicos.

Um exemplo de um provedor de dados é usar uma navegação controlada por pesquisa, que permite flexibilidade para enumerar nós de navegação e manipular a filtragem de segurança com eficiência.

Há outras opções populares para criar provedores de navegação personalizados. Examine as soluções de navegação para portais do SharePoint Online para obter mais diretrizes sobre como criar um provedor de navegação personalizado.

Usando a pesquisa, você pode aproveitar os índices criados em segundo plano usando o rastreamento contínuo. Os resultados da pesquisa são extraídos do índice de pesquisa e os resultados são cortados pela segurança. Isso geralmente é mais rápido do que os provedores de navegação para uso quando a filtragem de segurança é necessária. Usar a pesquisa de navegação estrutural, especialmente se você tiver uma estrutura de site complexa, acelerará consideravelmente o tempo de carregamento de página. A principal vantagem disso em relação à navegação gerenciada é que você se beneficia da filtragem de segurança.

Essa abordagem envolve a criação de uma página mestra personalizada e a substituição do código de navegação inicial pelo HTML personalizado. Siga este procedimento descrito no exemplo a seguir para substituir o código de navegação no arquivo seattle.html. Neste exemplo, você abrirá o arquivo seattle.html e substituirá o elemento inteiro id="DeltaTopNavigation" pelo código HTML personalizado.

Exemplo: substituir o código de navegação inicial em uma página mestra

  1. Navegue até a página Configurações do Site.
  2. Abra a galeria de páginas mestras clicando em Páginas Mestras.
  3. A partir daqui, você pode navegar pela biblioteca e baixar o arquivo seattle.master.
  4. Edite o código usando um editor de texto e exclua o bloco de código na captura de tela a seguir.
    Exclua o bloco de código mostrado.
  5. Remova o código entre as marcas <SharePoint:AjaxDelta id="DeltaTopNavigation"> e <\SharePoint:AjaxDelta> e substitua-o pelo snippet a seguir:
<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 a URL na marca de âncora de imagem de carregamento no início, por um link para uma imagem de carregamento em seu conjunto de sites. Depois de fazer as alterações, renomeie o arquivo e carregue-o na galeria de páginas mestras. Isso gera um novo arquivo .master.
7. Este HTML é a marcação básica que será preenchida pelos resultados da pesquisa retornados do código JavaScript. Você precisará editar o código para alterar o valor de var root = "URL do conjunto de sites", conforme demonstrado no snippet a seguir:
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";

8. Os resultados são atribuídos à matriz self.nodes e uma hierarquia é criada com base nos objetos usando linq.js atribuir a saída a uma matriz self.hierarchy. Essa matriz é o objeto associado ao HTML. Isso é feito na função toggleView() passando o auto-objeto para a função ko.applyBinding().
Isso faz com que a matriz de hierarquia seja associada ao seguinte HTML:
<div data-bind="foreach: hierarchy" class="noindex ms-core-listMenu-horizontalBox">

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

Em nosso exemplo de navegação complexo, uma nova carga de página sem o cache local mostra que o tempo gasto no servidor foi cortado da navegação estrutural do parâmetro de comparação para obter um resultado semelhante à abordagem de navegação gerenciada.

Sobre o arquivo JavaScript...

Observação

Se estiver usando JavaScript personalizado, verifique se a CDN pública está habilitada e se o arquivo está em um local cdn.

Todo o arquivo 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, há uma criação e, em seguida, a viewModel object loadNavigationNodes() função nesse objeto é chamada. Essa função carrega a hierarquia de navegação criada anteriormente armazenada no armazenamento local HTML5 do navegador do cliente ou chama a função queryRemoteInterface().

QueryRemoteInterface() cria uma solicitação usando a função getRequest() com o parâmetro de consulta definido anteriormente no script e retorna dados do servidor. Esses dados são essencialmente uma matriz de todos os sites no conjunto de sites representados como objetos de transferência de dados com várias propriedades.

Esses dados são analisados nos objetos definidos SPO.Models.NavigationNode Knockout.js anteriormente que usam para criar propriedades observáveis para uso pela associação de dados aos valores no HTML que definimos anteriormente.

Em seguida, os objetos são colocados em uma matriz de resultados. Essa matriz é analisada em JSON usando o Knockout e armazenada no armazenamento do navegador local para melhorar o desempenho em cargas de página futuras.

Benefícios desta abordagem

Um dos principais benefícios dessa abordagem é que, usando o armazenamento local HTML5, a navegação é armazenada localmente para o usuário na próxima vez que carregar a página. Obtemos melhorias de desempenho importantes usando a API de pesquisa para navegação estrutural; no entanto, é necessário algum recurso técnico para executar e personalizar essa funcionalidade.

Na implementação de exemplo, os sites são ordenados da mesma forma que a navegação estrutural completa; ordem alfabética. Se você quisesse se desviar dessa ordem, seria mais complicado desenvolver e manter. Além disso, essa abordagem exige que você se desvia das páginas mestras com suporte. Se a página mestra personalizada não for mantida, seu site perderá as atualizações e melhorias que a Microsoft faz 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 usado no código acima e interromperá o código de navegação. Para corrigir isso, adicione o seguinte método ao arquivo 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);
         })
     });
 },

Visão geral da navegação gerenciada no SharePoint Server

Desempenho e cache de navegação estrutural