Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation Overhaul #68

Merged
merged 1 commit into from
Feb 20, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/Bootstrap/App_Start/ExampleLayoutsRouteConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Web.Mvc;
using System.Web.Routing;
using BootstrapMvcSample.Controllers;
using NavigationRouteFilterExamples;
using NavigationRoutes;

namespace BootstrapMvcSample
Expand All @@ -13,11 +14,21 @@ public class ExampleLayoutsRouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapNavigationRoute<HomeController>("Automatic Scaffolding", c => c.Index(), "", true);
// this enables menu suppression for routes with a FilterToken of "admin" set
NavigationRouteFilters.Filters.Add(new AdministrationRouteFilter());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those who want to get the role based navigation menu working properly should pay special attention to this code line.


routes.MapNavigationRoute<HomeController>("Automatic Scaffolding", c => c.Index(), "",
new NavigationRouteOptions {HasBreakAfter = true});

// this route will only show if users are in the role specified in the AdministrationRouteFilter
// by default, when you run the site, you will not see this. Explore the AdministrationRouteFilter
// class for more information.
routes.MapNavigationRoute<HomeController>("Administration Menu", c => c.Admin(), "",
new NavigationRouteOptions { HasBreakAfter = true, FilterToken = "admin"});

routes.MapNavigationRoute<ExampleLayoutsController>("Example Layouts", c => c.Starter())
.AddChildRoute<ExampleLayoutsController>("Marketing", c => c.Marketing())
.AddChildRoute<ExampleLayoutsController>("Fluid", c => c.Fluid(), "", true)
.AddChildRoute<ExampleLayoutsController>("Fluid", c => c.Fluid(), new NavigationRouteOptions{ HasBreakAfter = true})
.AddChildRoute<ExampleLayoutsController>("Sign In", c => c.SignIn())
;
}
Expand Down
6 changes: 6 additions & 0 deletions src/Bootstrap/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,11 @@ public ActionResult Details(int id)
return View(model);
}


internal ActionResult Admin()
{
// used for demonstrationg route filters
throw new NotImplementedException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NavigationRoutes;

namespace NavigationRouteFilterExamples
{
public class AdministrationRouteFilter :INavigationRouteFilter
{
// an excercise for the reader would be to load the role name
// from your config file so this isn't compiled in, or add a constructor
// that accepts a role name to use to make this a more generic filter
private string AdministrationRole = "admin";

public bool ShouldRemove(System.Web.Routing.Route navigationRoutes)
{
if (navigationRoutes.DataTokens.HasFilterToken())
{
var filterToken = navigationRoutes.DataTokens.FilterToken();
var result = !HttpContext.Current.User.IsInRole(AdministrationRole) && filterToken == AdministrationRole;
return result;
}

return false;

}
}
}
4 changes: 1 addition & 3 deletions src/Bootstrap/Views/Shared/_BootstrapLayout.basic.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
</a>
<a class="brand" href="#" title="change in _bootstrapLayout.basic.cshtml">Application Name</a>
<div class="nav-collapse collapse">
<ul class="nav">
@Html.Navigation()
</ul>
@Html.Navigation()
</div>
</div>
</div>
Expand Down
28 changes: 24 additions & 4 deletions src/NavigationRoutes/NavigationRoutes/NamedRoute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ namespace NavigationRoutes
{
public class NamedRoute : Route
{
string _name;
string _displayName;
List<NamedRoute> _childRoutes = new List<NamedRoute>();
string _name = string.Empty;
string _displayName = string.Empty;
string _navigationRoute = string.Empty;

List<NamedRoute> _childRoutes = new List<NamedRoute>();

public NamedRoute(string name, string url, IRouteHandler routeHandler)
: base(url, routeHandler)
Expand Down Expand Up @@ -55,8 +57,26 @@ public string DisplayName
get { return _displayName ?? _name; }
set { _displayName = value; }
}

public List<NamedRoute> Children { get { return _childRoutes; } }
public bool IsChild { get; set; }
public bool ShouldBreakAfter { get; set; }

/// <summary>
/// The ID to be rendered for the UL containing the navigation items. Ignored on child routes.
/// If set, each distinct ID will be rendered as a seperate UL.
/// </summary>
public string NavigationGroup
{
get { return _navigationRoute; }
set { _navigationRoute = value.Replace(" ", "-"); }
}

/// <summary>
/// Optional settings to control the rendering behavior of the navigation route in the menu.
/// </summary>
public NavigationRouteOptions Options { get; set; }



}
}
83 changes: 61 additions & 22 deletions src/NavigationRoutes/NavigationRoutes/NavigationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

namespace NavigationRoutes
{
public class Defaults
{
public const string FILTER_TOKEN_KEY = "FilterToken";
}

public class CompositeMvcHtmlString : IHtmlString
{
readonly IEnumerable<IHtmlString> _strings;
Expand All @@ -26,7 +31,7 @@ public string ToHtmlString()
}
}

public static class NavigationRoutes
public static class NavigationRouteFilters
{
public static List<INavigationRouteFilter> Filters=new List<INavigationRouteFilter>();
}
Expand All @@ -36,7 +41,9 @@ public static class NavigationViewExtensions
public static IHtmlString Navigation(this HtmlHelper helper)
{
return new CompositeMvcHtmlString(
GetRoutesForCurrentRequest(RouteTable.Routes,NavigationRoutes.Filters).Select(namedRoute => helper.NavigationListItemRouteLink(namedRoute)));
GetRoutesForCurrentRequest(RouteTable.Routes,NavigationRouteFilters.Filters)
.GroupBy(route => route.NavigationGroup)
.Select(routeGroup => helper.NavigationListItemRouteLink(routeGroup.Select(g=>g))));
}

public static IEnumerable<NamedRoute> GetRoutesForCurrentRequest(RouteCollection routes,IEnumerable<INavigationRouteFilter> routeFilters)
Expand All @@ -59,34 +66,52 @@ public static IEnumerable<NamedRoute> GetRoutesForCurrentRequest(RouteCollection
return navigationRoutes;
}

public static MvcHtmlString NavigationListItemRouteLink(this HtmlHelper html, NamedRoute route)
public static MvcHtmlString NavigationListItemRouteLink(this HtmlHelper html, IEnumerable<NamedRoute> routes)
{
var li = new TagBuilder("li");
li.InnerHtml = html.RouteLink(route.DisplayName, route.Name).ToString();

if (CurrentRouteMatchesName(html, route.Name))
{
li.AddCssClass("active");
}

if (route.Children.Any())
var ul = new TagBuilder("ul");
ul.AddCssClass("nav");

var namedRoutes = routes as IList<NamedRoute> ?? routes.ToList();
if (namedRoutes.Any(r=>r.Options.IsRightAligned))
{
BuildChildMenu(html, route, li);
ul.AddCssClass("pull-right");
}

// collect our html
var tagBuilders = new List<TagBuilder>();
tagBuilders.Add(li);
if (route.ShouldBreakAfter)

foreach (var route in namedRoutes)
{
var breakLi = new TagBuilder("li");
breakLi.AddCssClass("divider-vertical");
tagBuilders.Add(breakLi);
var li = new TagBuilder("li");
li.InnerHtml = html.RouteLink(route.DisplayName, route.Name).ToString();
if (route.Options.IsRightAligned)
{
li.AddCssClass("pull-right");
}

if (CurrentRouteMatchesName(html, route.Name))
{
li.AddCssClass("active");
}

if (route.Children.Any())
{
BuildChildMenu(html, route, li);
}

tagBuilders.Add(li);
if (route.Options.HasBreakAfter)
{
var breakLi = new TagBuilder("li");
breakLi.AddCssClass("divider-vertical");
tagBuilders.Add(breakLi);
}

}
var tags = new StringBuilder();
tagBuilders.ForEach(b => tags.Append(b.ToString(TagRenderMode.Normal)));
ul.InnerHtml = tags.ToString();

return MvcHtmlString.Create(tags.ToString());
return MvcHtmlString.Create(ul.ToString(TagRenderMode.Normal));
}

private static void BuildChildMenu(HtmlHelper html, NamedRoute route, TagBuilder li)
Expand All @@ -106,7 +131,7 @@ private static void BuildChildMenu(HtmlHelper html, NamedRoute route, TagBuilder
ul.InnerHtml += childLi.ToString();

// support for drop down list breaks
if (child.ShouldBreakAfter)
if (child.Options.HasBreakAfter)
{
var breakLi = new TagBuilder("li");
breakLi.AddCssClass("divider");
Expand Down Expand Up @@ -134,5 +159,19 @@ static bool CurrentRouteMatchesName(HtmlHelper html, string routeName)
}
}


public static class RouteValueDictionaryExtensions
{


public static string FilterToken(this RouteValueDictionary routeValues)
{
return (string)routeValues[Defaults.FILTER_TOKEN_KEY];
}

public static bool HasFilterToken(this RouteValueDictionary routeValues)
{
return routeValues.ContainsKey(Defaults.FILTER_TOKEN_KEY);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,30 @@ public static class NavigationRouteConfigurationExtensions
}


public static NavigationRouteBuilder MapNavigationRoute<T>(this RouteCollection routes, string displayName, Expression<Func<T, ActionResult>> action, string areaName="", bool breakAfter = false) where T : IController
public static NavigationRouteBuilder MapNavigationRoute<T>(this RouteCollection routes, string displayName, Expression<Func<T, ActionResult>> action, string navigationGroup = "", NavigationRouteOptions options = null) where T : IController
{
var newRoute = new NamedRoute("", "", new MvcRouteHandler());
newRoute.ToDefaultAction(action,areaName);
newRoute.DisplayName = displayName;
newRoute.ShouldBreakAfter = breakAfter;
newRoute.NavigationGroup = navigationGroup;
newRoute.Options = options ?? new NavigationRouteOptions();
newRoute.ToDefaultAction(action, newRoute.Options);
routes.Add(newRoute.Name, newRoute);
return new NavigationRouteBuilder(routes, newRoute);
}

public static NavigationRouteBuilder AddChildRoute<T>(this NavigationRouteBuilder builder, string DisplayText, Expression<Func<T, ActionResult>> action, string areaName="", bool breakAfter = false) where T : IController
public static NavigationRouteBuilder AddChildRoute<T>(this NavigationRouteBuilder builder, string DisplayText, Expression<Func<T, ActionResult>> action, NavigationRouteOptions options = null) where T : IController
{
var childRoute = new NamedRoute("", "", new MvcRouteHandler());
childRoute.ToDefaultAction<T>(action,areaName);
childRoute.DisplayName = DisplayText;
childRoute.IsChild = true;
childRoute.ShouldBreakAfter = breakAfter;
childRoute.Options = options ?? new NavigationRouteOptions();
childRoute.ToDefaultAction<T>(action, childRoute.Options);
builder._parent.Children.Add(childRoute);
builder._routes.Add(childRoute.Name,childRoute);
return builder;
}

public static NamedRoute ToDefaultAction<T>(this NamedRoute route, Expression<Func<T, ActionResult>> action,string areaName) where T : IController
public static NamedRoute ToDefaultAction<T>(this NamedRoute route, Expression<Func<T, ActionResult>> action, NavigationRouteOptions options) where T : IController
{
var body = action.Body as MethodCallExpression;

Expand Down Expand Up @@ -108,6 +109,8 @@ public static class NavigationRouteConfigurationExtensions
route.Defaults.Add("controller", controllerName);
route.Defaults.Add("action", actionName);

var areaName = options.AreaName;

route.Url= CreateUrl(actionName,controllerName,areaName);
//TODO: Add area to route name
if(areaName=="")
Expand All @@ -118,10 +121,15 @@ public static class NavigationRouteConfigurationExtensions
if(route.DataTokens == null)
route.DataTokens = new RouteValueDictionary();
route.DataTokens.Add("Namespaces", new string[] {typeof (T).Namespace});

if (!string.IsNullOrEmpty(areaName))
{
route.DataTokens.Add("area", areaName.ToLower());
}
if (!string.IsNullOrEmpty(options.FilterToken))
{
route.DataTokens.Add(Defaults.FILTER_TOKEN_KEY, options.FilterToken.ToLower());
}

return route;
}
Expand Down
53 changes: 53 additions & 0 deletions src/NavigationRoutes/NavigationRoutes/NavigationRouteOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace NavigationRoutes
{
public class NavigationRouteOptions
{
public NavigationRouteOptions()
{
this.AreaName = string.Empty;
this.HasBreakAfter = false;
this.IsRightAligned = false;
this.FilterToken = string.Empty;
this.ElementId = string.Empty;
}

/// <summary>
/// You can specify an area to properly render your route, if required
/// </summary>
public string AreaName { get; set; }

/// <summary>
/// This will be the ID rendered in the DOM, if specified
/// </summary>
public string ElementId { get; set; }

/// <summary>
/// This will insert a break after the menu item rendered for the route. If this is a top-level
/// item, the break will be in the nav bar. For child items, the break appears in the dropdown list.
/// </summary>
public bool HasBreakAfter { get; set; }

/// <summary>
/// Setting this property on any item will cause the UL containing the entire group to be
/// pulled to right.
/// </summary>
public bool IsRightAligned { get; set; }

/// <summary>
/// A string value to store additional information about the route for the purposes of
/// filtering. The value is stored in the DataTokens collection and can be accessed in
/// an implementation of INavigationRouteFilter.
/// </summary>
public string FilterToken { get; set; }

// todo: figure out other relevant options
// ? public bool HasBreakBefore { get; set; }
// ? public string Icon { get; set; }

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<Compile Include="NavigationExtensions.cs" />
<Compile Include="NavigationRouteConfigurationExtensions.cs" />
<Compile Include="NavigationRouteFilter.cs" />
<Compile Include="NavigationRouteOptions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Loading