Low-level NavLink implementation
This commit is contained in:
parent
25b76bc6dc
commit
3e30655ea4
|
|
@ -1,4 +1,5 @@
|
|||
<div class='main-nav'>
|
||||
@using Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||
<div class='main-nav'>
|
||||
<div class='navbar navbar-inverse'>
|
||||
<div class='navbar-header'>
|
||||
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
|
||||
|
|
@ -13,14 +14,14 @@
|
|||
<div class='navbar-collapse collapse'>
|
||||
<ul class='nav navbar-nav'>
|
||||
<li>
|
||||
<a href='/'>
|
||||
<c:NavLink href=@("/")>
|
||||
<span class='glyphicon glyphicon-home'></span> Home
|
||||
</a>
|
||||
</c:NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<a href='/counter'>
|
||||
<c:NavLink href=@("/counter")>
|
||||
<span class='glyphicon glyphicon-education'></span> Counter
|
||||
</a>
|
||||
</c:NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,3 +64,22 @@
|
|||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Temporary workaround due to the <blazor-component> wrapper element. The wrapper element
|
||||
will be eliminated in a future update, making this workaround unnecessary.
|
||||
*/
|
||||
.navbar-nav > li a {
|
||||
color: #9d9d9d;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
.navbar-nav > li a:hover {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
.navbar-nav > li a:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||
{
|
||||
// TODO: Move this into Microsoft.AspNetCore.Blazor, and use DI to break the
|
||||
// coupling on Microsoft.AspNetCore.Blazor.Browser.Routing.UriHelper.
|
||||
// That's because you'd use NavLink in non-browser scenarios too (e.g., prerendering).
|
||||
// Can't do this until DI is implemented.
|
||||
|
||||
public class NavLink : IComponent, IDisposable
|
||||
{
|
||||
const string CssClassAttributeName = "class";
|
||||
const string HrefAttributeName = "href";
|
||||
|
||||
private RenderHandle _renderHandle;
|
||||
private bool _isActive;
|
||||
|
||||
private RenderFragment _childContent;
|
||||
private string _cssClass;
|
||||
private string _href;
|
||||
private string _hrefAbsolute;
|
||||
private IDictionary<string, string> _otherAttributes;
|
||||
|
||||
public void Init(RenderHandle renderHandle)
|
||||
{
|
||||
_renderHandle = renderHandle;
|
||||
UriHelper.OnLocationChanged += OnLocationChanged;
|
||||
}
|
||||
|
||||
public void SetParameters(ParameterCollection parameters)
|
||||
{
|
||||
_childContent = null;
|
||||
_href = null;
|
||||
_hrefAbsolute = null;
|
||||
_cssClass = null;
|
||||
_otherAttributes?.Clear();
|
||||
foreach (var kvp in parameters)
|
||||
{
|
||||
switch (kvp.Name)
|
||||
{
|
||||
case RenderTreeBuilder.ChildContent:
|
||||
_childContent = kvp.Value as RenderFragment;
|
||||
break;
|
||||
case CssClassAttributeName:
|
||||
_cssClass = kvp.Value as string;
|
||||
break;
|
||||
case HrefAttributeName:
|
||||
_href = kvp.Value as string;
|
||||
_hrefAbsolute = UriHelper.ToAbsoluteUri(_href).AbsoluteUri;
|
||||
break;
|
||||
default:
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
if (_otherAttributes == null)
|
||||
{
|
||||
_otherAttributes = new Dictionary<string, string>();
|
||||
}
|
||||
_otherAttributes.Add(kvp.Name, kvp.Value.ToString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_isActive = UriHelper.GetAbsoluteUri().Equals(_hrefAbsolute, StringComparison.Ordinal);
|
||||
_renderHandle.Render(Render);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UriHelper.OnLocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
private void OnLocationChanged(object sender, string e)
|
||||
{
|
||||
var shouldBeActiveNow = UriHelper.GetAbsoluteUri().Equals(
|
||||
_hrefAbsolute,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
if (shouldBeActiveNow != _isActive)
|
||||
{
|
||||
_isActive = shouldBeActiveNow;
|
||||
|
||||
if (_renderHandle.IsInitialized)
|
||||
{
|
||||
_renderHandle.Render(Render);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Render(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.OpenElement(0, "a");
|
||||
|
||||
if (!string.IsNullOrEmpty(_href))
|
||||
{
|
||||
builder.AddAttribute(0, HrefAttributeName, _href);
|
||||
}
|
||||
|
||||
var combinedClassValue = CombineWithSpace(_cssClass, _isActive ? "active" : null);
|
||||
if (combinedClassValue != null)
|
||||
{
|
||||
builder.AddAttribute(0, CssClassAttributeName, combinedClassValue);
|
||||
}
|
||||
|
||||
if (_otherAttributes != null)
|
||||
{
|
||||
foreach (var kvp in _otherAttributes)
|
||||
{
|
||||
builder.AddAttribute(0, kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_childContent != null)
|
||||
{
|
||||
builder.AddContent(1, _childContent);
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
private string CombineWithSpace(string str1, string str2)
|
||||
=> str1 == null ? str2
|
||||
: (str2 == null ? str1 : $"{str1} {str2}");
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
{
|
||||
static readonly string _functionPrefix = typeof(UriHelper).FullName;
|
||||
static string _currentAbsoluteUri;
|
||||
static string _baseUriString;
|
||||
static Uri _baseUri;
|
||||
|
||||
/// <summary>
|
||||
/// An event that fires when the navigation location has changed.
|
||||
|
|
@ -38,9 +40,20 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
/// <returns>The URI prefix.</returns>
|
||||
public static string GetBaseUriPrefix()
|
||||
{
|
||||
var baseUri = RegisteredFunction.InvokeUnmarshalled<string>(
|
||||
$"{_functionPrefix}.getBaseURI");
|
||||
return ToBaseUriPrefix(baseUri);
|
||||
EnsureBaseUriPopulated();
|
||||
return _baseUriString;
|
||||
}
|
||||
|
||||
private static void EnsureBaseUriPopulated()
|
||||
{
|
||||
// The <base href> is fixed for the lifetime of the page, so just cache it
|
||||
if (_baseUriString == null)
|
||||
{
|
||||
var baseUri = RegisteredFunction.InvokeUnmarshalled<string>(
|
||||
$"{_functionPrefix}.getBaseURI");
|
||||
_baseUriString = ToBaseUriPrefix(baseUri);
|
||||
_baseUri = new Uri(_baseUriString);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -58,6 +71,17 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
|||
return _currentAbsoluteUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative URI into an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="relativeUri">The relative URI.</param>
|
||||
/// <returns>The absolute URI.</returns>
|
||||
public static Uri ToAbsoluteUri(string relativeUri)
|
||||
{
|
||||
EnsureBaseUriPopulated();
|
||||
return new Uri(_baseUri, relativeUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a base URI prefix (e.g., one previously returned by <see cref="GetBaseUriPrefix"/>),
|
||||
/// converts an absolute URI into one relative to the base URI prefix.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
/// <summary>
|
||||
/// The reserved parameter name used for supplying child content.
|
||||
/// </summary>
|
||||
public static string ChildContent = nameof(ChildContent);
|
||||
public const string ChildContent = nameof(ChildContent);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="RenderTreeBuilder"/>.
|
||||
|
|
|
|||
Loading…
Reference in New Issue