Opções de navegação para SharePoint
Este artigo descreve sites de opções de navegação com a Publicação do SharePoint habilitada no SharePoint. A escolha e a configuração da navegação afetam significativamente o desempenho e a escalabilidade dos sites no SharePoint. O modelo do 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 necessário, pois pode afetar o desempenho quando usado incorretamente.
Observação
Se você estiver usando opções de navegação modernas do SharePoint, como menu mega, navegação em cascata ou navegação de hub, este artigo não se aplica ao seu site. As arquiteturas de site do SharePoint modernas aproveitam uma hierarquia de site mais achatada e um modelo hub-and-spoke. Isso permite que muitos cenários sejam alcançados 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 fora da caixa, 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 clássicos do SharePoint, se você ativar o cache de navegação estrutural para seu site. Este 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 corte de segurança e enumeração da estrutura do site. Se o cache estiver desabilitado, isso afetará negativamente o desempenho e a escalabilidade e poderá estar sujeito a 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 o corte de segurança seja desabilitado, a menos que seja necessário. O corte de segurança está habilitado como uma configuração segura por padrão para este provedor de navegação; no entanto, muitos sites não exigem a sobrecarga do corte 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 o corte de segurança, esse provedor de navegação não requer enumeração da estrutura do site e é altamente escalonável com impacto de desempenho aceitável.
Além dos provedores de navegação fora da caixa, muitos clientes implementaram com êxito implementações alternativas de navegação personalizada. Consulte scripts do lado do cliente controlados por Pesquisa neste artigo.
Opções de navegação prós e contras do SharePoint
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 aparada Atualiza automaticamente dentro de 24 horas quando o conteúdo é alterado |
Profissionais: Fácil de manter |
Profissionais: Segurança aparada 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 o cache é usado corretamente Muitas opções funcionam bem com design de página responsivo |
Contras: Afeta o desempenho se o cache estiver desabilitado Sujeito a limitação |
Contras: Não é atualizado automaticamente para refletir a estrutura do site Afeta o desempenho se o corte de segurança estiver habilitado ou quando a estrutura de navegação for complexa |
Contras: Nenhuma capacidade de solicitar sites facilmente Requer personalização da página master (habilidades técnicas necessárias) |
Contras: O desenvolvimento personalizado é necessário A fonte de dados externo/cache armazenado é necessário, por exemplo, o Azure |
A opção mais apropriada para seu site depende dos requisitos do site e da sua capacidade técnica. Se você quiser um provedor de navegação fácil de configurar que atualiza automaticamente quando o conteúdo é alterado, então a navegação estrutural com o cache habilitado é uma boa opção.
Observação
Aplicar o mesmo princípio que sites modernos do SharePoint simplificando a estrutura geral do site a uma estrutura não hierárquica mais plana melhora o desempenho e simplifica a migração para sites modernos do SharePoint. O que isso 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 pouquíssimos subsites (subwebs).
Analisando o desempenho de navegação no SharePoint
A ferramenta Diagnóstico de Página para SharePoint é uma extensão de navegador para navegadores Microsoft Edge e Chrome que analisa tanto o portal moderno do SharePoint quanto as páginas clássicas do site de publicação. Essa ferramenta funciona apenas para o SharePoint 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 estão fora do valor da linha de base. Administradores e designers do SharePoint 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 necessário para o SharePoint processar a página. A navegação pesada (como incluir páginas na navegação), hierarquias de site complexas e outras opções de configuração e topologia podem contribuir dramaticamente para durações mais longas.
Usando a navegação estrutural no SharePoint
Essa é a navegação fora da caixa 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 facilmente itens, ocultar itens e gerenciar a navegação na página de configurações. Recomendamos habilitar o cache, caso contrário, há uma compensação de desempenho cara.
Como implementar o cache de navegação estrutural
Em Configurações> do SiteProcure e Sinta>Navegação, você pode validar se a navegação estrutural estiver selecionada para navegação global ou navegação atual. Selecionar páginas mostrar terá impacto negativo no desempenho.
O cache pode ser habilitado ou desabilitado no nível da coleção do site e no nível do site e está habilitado para ambos por padrão. Para habilitar no nível da coleção do site, em Configurações> do SiteCollection>Site Collection Navegação de Coleta de Sites, marcar a caixa para Habilitar cache.
Para habilitar no nível do site, em Navegação de Configurações> do Site, marcar a caixa para Habilitar o cache.
Usando navegação gerenciada e metadados no SharePoint
A navegação gerenciada é outra opção fora da caixa que você pode usar para recriar a maior parte da mesma funcionalidade que a navegação estrutural. Metadados gerenciados podem ser configurados para ter o corte de segurança habilitado ou desabilitado. Quando configurada com o corte de segurança desabilitado, a navegação gerenciada é bastante eficiente, pois carrega todos os links de navegação com um número constante de chamadas de servidor. Habilitar o corte de segurança, no entanto, nega algumas das vantagens de desempenho da navegação gerenciada.
Se você precisar habilitar o corte 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 não mais de 100 e não mais de três 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 o corte de segurança estiver desabilitado e um link for adicionado à navegação à qual nem todos os usuários têm acesso, o link ainda será exibido, mas levará a uma mensagem de acesso negada. Não há risco de acesso inadvertido ao conteúdo.
Como implementar a navegação gerenciada e os resultados
Há vários artigos no Microsoft Learn sobre os detalhes da navegação gerenciada. Por exemplo, confira Visão geral da navegação gerenciada no SharePoint Server.
Para implementar a navegação gerenciada, você configurou termos com URLs correspondentes à estrutura de navegação do site. A navegação gerenciada pode até mesmo ser curada manualmente para substituir a navegação estrutural em muitos casos. Por exemplo:
)
Usando scripts do lado do cliente controlados por Pesquisa
Uma classe comum de implementações de navegação personalizadas 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 das principais vantagens:
- Eles geralmente funcionam bem com designs de página responsivos.
- Eles são extremamente escalonáveis e performantes 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 provedor de dados é usar uma navegação controlada por Pesquisa, que permite flexibilidade para enumerar nós de navegação e lidar com o corte de segurança com eficiência.
Há outras opções populares para criar provedores de navegação personalizados. Examine soluções de navegação para portais do SharePoint 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 rastreamento contínuo. Os resultados da pesquisa são retirados do índice de pesquisa e os resultados são cortados pela segurança. Isso geralmente é mais rápido do que provedores de navegação fora da caixa quando o corte de segurança é necessário. Usar a pesquisa para navegação estrutural, especialmente se você tiver uma estrutura de site complexa, acelerará consideravelmente o tempo de carregamento da página. A vantagem main disso sobre a navegação gerenciada é que você se beneficia do corte de segurança.
Essa abordagem envolve a criação de uma página de master personalizada e a substituição do código de navegação fora da caixa por 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 seattle.html
arquivo e substituirá todo o elemento id="DeltaTopNavigation"
pelo código HTML personalizado.
Exemplo: substitua o código de navegação fora da caixa em uma página master
- Navegue até a página Configurações do Site.
- Abra a galeria de páginas master clicando em Páginas Mestras.
- A partir daqui, você pode navegar pela biblioteca e baixar o arquivo
seattle.master
. - Edite o código usando um editor de texto e exclua o bloco de código na captura de tela a seguir.
- Remova o código entre as
<SharePoint:AjaxDelta id="DeltaTopNavigation">
marcas e<\SharePoint:AjaxDelta>
e substitua-o pelo seguinte snippet:
<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 sua coleção de sites. Depois de fazer as alterações, renomeie o arquivo e carregue-o na galeria de páginas master. Isso gera um novo . master arquivo.
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 da raiz do var = "URL de coleção de sites", conforme demonstrado no seguinte snippet:
var root = "https://spperformance.sharepoint.com/sites/NavigationBySearch";
8. Os resultados são atribuídos à matriz self.nós e uma hierarquia é criada a partir dos objetos usando linq.js atribuindo a saída a uma auto-hierarquia de matriz. Essa matriz é o objeto associado ao HTML. Isso é feito na função toggleView() passando o objeto auto para a função ko.applyBinding().
Em seguida, 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 lidar com os menus suspensos do subsite que são feitos 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 reduzido da navegação estrutural de referência 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 o arquivo está em um local de 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 função, há um viewModel object
criado e, em jQuery $(document).ready
seguida, a loadNavigationNodes()
função nesse objeto é chamada. Essa função carrega a hierarquia de navegação criada anteriormente armazenada no armazenamento local HTML5 do navegador cliente ou chama a função queryRemoteInterface()
.
QueryRemoteInterface()
cria uma solicitação usando a getRequest()
função 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 da coleção de sites representados como objetos de transferência de dados com várias propriedades.
Esses dados são então analisados nos objetos definidos SPO.Models.NavigationNode
anteriormente que usam Knockout.js
para criar propriedades observáveis para uso por dados que associam os valores ao 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 grandes melhorias de desempenho 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 fora da caixa; 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 desvie das páginas de master com suporte. Se a página de master personalizada não for mantida, seu site perderá atualizações e melhorias que a Microsoft faz nas páginas master.
O código acima tem as seguintes dependências:
- Jquery- https://jquery.com/
- KnockoutJS - https://knockoutjs.com/
- Linq.js -
https://linqjs.codeplex.com/
ou github.com/neuecc/linq.js
A versão atual do LinqJS não contém o método ByHierarchy usado no código acima e quebrará 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);
})
});
},