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 navbar-inverse'>
|
||||||
<div class='navbar-header'>
|
<div class='navbar-header'>
|
||||||
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
|
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
|
||||||
|
|
@ -13,14 +14,14 @@
|
||||||
<div class='navbar-collapse collapse'>
|
<div class='navbar-collapse collapse'>
|
||||||
<ul class='nav navbar-nav'>
|
<ul class='nav navbar-nav'>
|
||||||
<li>
|
<li>
|
||||||
<a href='/'>
|
<c:NavLink href=@("/")>
|
||||||
<span class='glyphicon glyphicon-home'></span> Home
|
<span class='glyphicon glyphicon-home'></span> Home
|
||||||
</a>
|
</c:NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='/counter'>
|
<c:NavLink href=@("/counter")>
|
||||||
<span class='glyphicon glyphicon-education'></span> Counter
|
<span class='glyphicon glyphicon-education'></span> Counter
|
||||||
</a>
|
</c:NavLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,22 @@
|
||||||
text-overflow: ellipsis;
|
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 readonly string _functionPrefix = typeof(UriHelper).FullName;
|
||||||
static string _currentAbsoluteUri;
|
static string _currentAbsoluteUri;
|
||||||
|
static string _baseUriString;
|
||||||
|
static Uri _baseUri;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An event that fires when the navigation location has changed.
|
/// 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>
|
/// <returns>The URI prefix.</returns>
|
||||||
public static string GetBaseUriPrefix()
|
public static string GetBaseUriPrefix()
|
||||||
{
|
{
|
||||||
var baseUri = RegisteredFunction.InvokeUnmarshalled<string>(
|
EnsureBaseUriPopulated();
|
||||||
$"{_functionPrefix}.getBaseURI");
|
return _baseUriString;
|
||||||
return ToBaseUriPrefix(baseUri);
|
}
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
|
|
@ -58,6 +71,17 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Routing
|
||||||
return _currentAbsoluteUri;
|
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>
|
/// <summary>
|
||||||
/// Given a base URI prefix (e.g., one previously returned by <see cref="GetBaseUriPrefix"/>),
|
/// 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.
|
/// converts an absolute URI into one relative to the base URI prefix.
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The reserved parameter name used for supplying child content.
|
/// The reserved parameter name used for supplying child content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ChildContent = nameof(ChildContent);
|
public const string ChildContent = nameof(ChildContent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an instance of <see cref="RenderTreeBuilder"/>.
|
/// Constructs an instance of <see cref="RenderTreeBuilder"/>.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue