I'm using cloudscribe navigation to build the sidebar and associated breadcrumb menu
I couldn't find it in the help documentation, so posting here
we have some route parameters in our routing templates - I'm unsure how to preserve those in breadcrumbs, navigation parameter preservedRouterParameters I understand that only works with Query string params
our route is something like this:
{wholesaleCustomerCode}/{area:exists}/{baseItemId:guid}/{controller=Home}/{action=Index}/{id:guid?}
and would translate to something like :
http://127.0.0.1:5100/TOM/Service/09185d87-5e3f-4217-a0b9-02f766efc714/Home/Detail
now when I'm on the /Detail view - it's nested quite deep - but the previous parent in breadcrumb loses baseItemId value - any suggestions how I can preserve route parameters in the breadcrumb hierarchy ?
I have attempted the following, just to test if it would pick up preserve route parameters, but didn't work
<Children>
<NavNode
key="d902daaa-99ec-488a-85de-c03641fb547d"
area="Service"
controller="Home"
action="Detail"
text="Service"
iconCssClass=""
componentVisibility="breadcrumbs"
viewRoles="Administrator"
preservedRouteParameters="baseItemId"
>
<Children>
<NavNode
key="3047c57d-0f8c-4875-aff9-9c1f91909e41"
area="Service"
controller="RecurringCredit"
action="Edit"
text="Recurring Credit"
iconCssClass=""
componentVisibility="breadcrumbs"
viewRoles="Administrator"
preservedRouteParameters="baseItemId"
>
</NavNode></Children></NavNode>
It is true that preservedRouteParameters only works for query string parameters.
One possible solution is to use code from within the controller action for your detail view to update the url for the parent breadcrumbs as needed. Relevant documentation here:https://www.cloudscribe.com/docs/adjusting-menu-items-per-request
var crumbAdjuster = new NavigationNodeAdjuster(Request.HttpContext);
crumbAdjuster.KeyToAdjust = "d902daaa-99ec-488a-85de-c03641fb547d";
crumbAdjuster.AdjustedUrl = // set the url here as you want it to be;
crumbAdjuster.AddToContext();
that will update the url for the given menu node for the life of the current request
Here's how I solved the issue
Consider you have the following default BootstrapBreadcrumbs view from cloudscribe.Web.Navigation/Views:
#using cloudscribe.Web.Navigation
#model NavigationViewModel
#if (Model.CurrentNode != null && (Model.ParentChain.Count > 1 || (Model.TailCrumbs != null && Model.TailCrumbs.Count > 0)))
{
<ul class="breadcrumb">
#foreach (var node in Model.ParentChain)
{
if (!Model.ShouldAllowView(node)) { continue; }
if (node.EqualsNode(Model.CurrentNode))
{
if (Model.TailCrumbs != null)
{
<li>#Model.AdjustText(node)<span class="divider"></span></li>
}
else
{
<li class="active">#Model.AdjustText(node)</li>
}
}
else
{
<li>#Model.AdjustText(node)<span class="divider"></span></li>
}
}
#if (Model.TailCrumbs != null)
{
foreach (var n in Model.TailCrumbs)
{
<li class="active">#n.Text</li>
}
}
</ul>
}
instead of using Model.AdjustUrl bypass it in favour of using asp-route-* helper
#using cloudscribe.Web.Navigation
#model NavigationViewModel
#{
var id = ViewContext.RouteData.Values["id"];
var baseItemId = ViewContext.RouteData.Values["baseItemId"];
var orderId = ViewContext.HttpContext.Request.Query["orderId"];
}
#if (Model.CurrentNode != null && (Model.ParentChain.Count > 1 || (Model.TailCrumbs != null && Model.TailCrumbs.Count > 0)))
{
<ul class="breadcrumb">
#foreach (var node in Model.ParentChain)
{
if (!Model.ShouldAllowView(node)) { continue; }
if (node.EqualsNode(Model.CurrentNode))
{
if (Model.TailCrumbs != null)
{
<li><a asp-area="#node.Value.Area" asp-controller="#node.Value.Controller" asp-action="#node.Value.Action" asp-route-baseItemId="#baseItemId" asp-route-orderId="#orderId" asp-route-id="#id">
#Model.AdjustText(node)
</a>
<span class="divider"></span>
</li>
}
else
{
<li class="active">#Model.AdjustText(node)</li>
}
}
else
{
<li><a asp-area="#node.Value.Area" asp-controller="#node.Value.Controller" asp-action="#node.Value.Action" asp-route-baseItemId="#baseItemId" asp-route-orderId="#orderId" asp-route-id="#id">#Model.AdjustText(node)</a><span class="divider"></span></li>
}
}
#if (Model.TailCrumbs != null)
{
foreach (var n in Model.TailCrumbs)
{
<li class="active">#n.Text</li>
}
}
</ul>
}
this implementation has some drawbacks and may not work for everyone - for example, all the previous breadcrumbs hierarchy now have baseItemId in the route -
e.g.
dashboard / all notes / note / detail
here , the all notes will also have the baseItemId added to it, although it doesn't need it -
but it works for us as it's an administrative portal (no SEO requirements) and the way our route templates are mapped
Related
Blazor's NavLink component detects whether the link refers to the current page, and sets the active class.
It is customary to also set the aria-current="page" attribute, when it is part of a menu.
Can the component do that somehow? Or could I wrap it in a custom component that does this?
I can't find an extension point that easily allows for this: docs, source.
Extending the existing NavLink
public class NavLinkExtended : NavLink
{
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "a");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
if(CssClass.Contains("active"))
{
builder.AddAttribute(3, "aria-current", "page");
builder.AddContent(4, ChildContent);
builder.CloseElement();
}
else
{
builder.AddContent(3, ChildContent);
builder.CloseElement();
}
}
}
Here is my solution.
AriaNavLink.razor:
<NavLink class=#CssClass #attributes=Attributes>
#ChildContent
</NavLink>
#inherits NavLink
#code {
private const string _defaultActiveClass = "active";
private Dictionary<string, object>? _attributes;
private IReadOnlyDictionary<string, object> Attributes
{
get
{
if (_attributes != null) return _attributes;
_attributes = AdditionalAttributes != null
? new Dictionary<string, object>(AdditionalAttributes)
: new Dictionary<string, object>();
// the trick to inferring "active" indirectly:
var isActive = CssClass?.Split(' ', StringSplitOptions.TrimEntries).Contains(ActiveClass ?? _defaultActiveClass) ?? false;
if (isActive)
_attributes.Add("aria-current", "page");
return _attributes;
}
}
}
Some page using bootstrap:
<li class="nav-item">
<AriaNavLink href="/foo" class="nav-link" foo="bar">
<i class="some-icon"></i>
Foo
</AriaNavLink>
</li>
Pardon me for not being exact, but... I would approach this with something like...
aria-current="#(route == nav route) ? "page" : "" ")
and then figure out how to detect the route of the current page and the configured nav route for the thing you're modifying.
Then, if you have a LOT of these nav things and you don't want to duplicate the logic all over the place.... write a method that does the above and add that to your code like...
The accepted answer is a good solution for now. When v8 is released (probably November 2023), the functionality will be built-in. The merged PR is here.
I am using the MVC Widget Template to read a list of Dynamic Content, but need to be able to query the list. Because it has pagination, it only goes through the first 20 or so. How do I turn this off?
I Tried using this but didnt work:
<telerik:RadListView ID="KBList" ItemPlaceholderID="ItemsContainer" runat="server"
EnableEmbeddedSkins="False" EnableEmbeddedBaseStylesheet="False"
AllowPaging="False">
My Code Here:
#model Telerik.Sitefinity.Frontend.DynamicContent.Mvc.Models.DynamicContentListViewModel
#using Telerik.Sitefinity.Frontend.DynamicContent.WidgetTemplates.Fields.Helpers;
#using Telerik.Sitefinity;
#using Telerik.Sitefinity.Data.ContentLinks;
#using Telerik.Sitefinity.Frontend.Mvc.Helpers;
#using Telerik.Sitefinity.Frontend.Mvc.Models;
#using Telerik.Sitefinity.Web.DataResolving;
#using Telerik.Sitefinity.Model.ContentLinks;
#using Telerik.Sitefinity.Modules.Pages;
#Html.Script(ScriptRef.JQuery, "top", false)
<div class="#Model.CssClass">
<ul>
#foreach (var item in Model.Items)
{
var navigateUrl = HyperLinkHelpers.GetDetailPageUrl(item, ViewBag.DetailsPageId, ViewBag.OpenInSamePage, Model.UrlKeyPrefix);
<li #Html.InlineEditingAttributes(Model.ProviderName, Model.ContentType.FullName, (Guid)item.Fields.Id)>
<h3>
<a #Html.InlineEditingFieldAttributes("Title", "ShortText") href="#navigateUrl">
#item.Fields.Title
</a>
</h3>
</li>
}
</ul>
#if (Model.ShowPager)
{
#Html.Action("Index", "ContentPager", new { currentPage = Model.CurrentPage,
totalPagesCount = Model.TotalPagesCount.Value,
redirectUrlTemplate = ViewBag.RedirectPageUrlTemplate })
}
</div>
You need to edit the widget in page edit mode and there find the List Settings tab and once there you select the No Limit and Paging option.
This way, all items will be sent to the view.
Try writing your own MVC widget. In the Index method of your controller try fetching all content using resolveType of the dynamic module and (if needed convertinto required model) and pass it to your view file.
public ActionResult Index()
{
DynamicModuleManager dynamicModuleManager = DynamicModuleManager.GetManager(String.Empty,"SomeName");
Type dynArticleType = TypeResolutionService.ResolveType(resolveType);
var FilteredCollection = dynamicModuleManager.GetDataItems(dynArticleType)
.Where(x => x.Status == ContentLifecycleStatus.Live && x.Status != ContentLifecycleStatus.Deleted
&& x.Status != ContentLifecycleStatus.Temp && x.Status != ContentLifecycleStatus.Master)
.OrderByDescending(x => x.GetValue<System.DateTime>("PublicationDate"));
var fullList = FilteredCollection.ToList();
//Convert into customModel.
return View("Default", yourCustomModel);
}
Might be a asked and answered...sorry but, I've been searching for a while.
RAZOR VIEW
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader")" data_languageID="#item.LanguageID" data_someval="hello" data_somevalb="world">
<div class="flag #item.LanguageFlag">
</div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
How do I access the data attributes in my controller?
CONTROLLER
public ActionResult ChangeLanguage()
{
var x = ControllerContext; //// ??? get the collection of [data-xx] from where?
return RedirectToAction("Buttons", "Designer");
}
data-* attributes are client side values and are not sent in the request.
In order to send those values, add them as route values
#foreach (var item in Model.LanguageList)
{
<li>
<a href="#Url.Action("ChangeLanguage", "UserHeader", new { languageID=item.LanguageID, someval="hello", somevalb="world" })">
<div class="flag #item.LanguageFlag"></div>
<div class="flag-title"> #item.LanguageName</div>
</a>
</li>
}
and include parameters in your GET method for the values
public ActionResult ChangeLanguage(int LanguageID, string someval, string somevalb)
Alternatively you could handle this using javascript/jquery (the ChangeLanguage() method also needs to be modified as shown above)
$('a').click(function() {
// get the url
var url = $(this).attr('href');
// get the data attributes
var languageID = $(this).data('languageID');
var someVal= $(this).data('someval');
var someValB= $(this).data('somevalb');
location.href = url + '?languageID=' + languageID + '&someVal =' + someVal + '&someValB=' + someValB;
return false; // cancel the default redirect
});
I'm trying to make a menu with asp .net mvc 4.
Calling menu
#{Html.RenderAction("LeftMenu", "Navigation",
new { currentPoint = ViewData["CurrentCategory"] });}
Navigation controller:
public ViewResult LeftMenu(string currentPoint) {
List<NavLink> navLinks = new List<NavLink>();
navLinks.Add(new CategoryLink(null)
{
IsSelected = (currentPoint == null)
});
List<string> categories = new List<string>{
"Bicycles",
"Details"
};
foreach (var category in categories)
navLinks.Add(new CategoryLink(category){
IsSelected = (category == currentPoint)
});
return View(navLinks);
}
Partial View
#model IEnumerable<MvcWebShopApp.Controllers.NavigationController.NavLink>
<ul class="nav navbar-nav">
#foreach (var link in Model)
{
<li class = "#(link.IsSelected ? "active": "")">
<a href="#Url.RouteUrl(link.RouteValues)")>#link.Text</a>
</li>
}
</ul>
But when I run my project I got Exception:
Insufficient stack to continue executing the program safely. This can
happen from having too many functions on the call stack or function on
the stack using too much stack space.
Please help.
You're actually rendering a full view, not a partial view.
That includes the layout, which renders that view again, creating a stack overflow.
You need to return PartialView().
I am very new to MVC
I need some help to over come the issue of passing parameter to a controller on form submit
what i have got is the following controller and the view
public ActionResult Index(string method ="None")
{
if (Request.HttpMethod == "POST")
{
switch (method)
{
case "Add10":
_bag.GetBag = Get100Products().Take(10).ToList<Product>();
break;
case "Clear":
_bag = null;
_bag.GetBag = null;
_bag = new Models.Bag();
break;
case "Add":
if ((Request.Form["Id"] != null) && (Request.Form["Id"] != ""))
{
if (_bag.GetBag.Count < 100)
{
var p = GetProduct(Request.Form["Id"]);
int qnt = Convert.ToInt16(Request.Form["qnt"]);
if (p.ItemNumber != null)
{
p.Quantity = qnt;
p.Index++;
_bag.Item = p;
}
}
}
break;
}
}
return View(_bag.GetBag);
}
and the view part of the view
<div style="vertical-align:middle">
#using (Html.BeginForm("", "Home", new { method = "Add10" }, FormMethod.Post))
{
<!-- form goes here -->
<input type="submit" value="Add 10 Items to bag" />
}
#using (Html.BeginForm("GetDiscount", "Home", FormMethod.Post))
{
<div>
<!-- form goes here -->
<input type="submit" value="Get Discount" />
With MAX time in seconds <input type="text" name="time" maxlength="2" value="2" />
</div>
}
#using (Html.BeginForm("", "Home", new { method = "Clear" }, FormMethod.Post))
{
<input type="submit" value="Empty the bag" />
}
</div>
so i am expecting when the use clicked button Add 10 Items to bag to pass the method value "Add10" to the index controller and when clicked Empty the bag to pass "Clear" the method value in index controller
but it always shows as "None"
what have I done wrong?
</form>
First of all, you have to add [HttpPost] to your controller in order to accept POST requests:
[HttpPost]
public ActionResult Index(string method ="None")
{
You should differentiate GET and POST actions.
You can do like this:
// [HttpGet] by default
public ActionResult Index(Bag bag = null)
{
// "bag" is by default null, it only has a value when called from IndexPOST action.
return View(bag);
}
[HttpPost]
public ActionResult Index(string method)
{
// Your logic as specified in your question
return Index(_bag.GetBag);
}
EDIT:
Your code is wrong, for example you will get a NullReferenceException because your try to call a property on a null object (_bag):
_bag = null;
_bag.GetBag = null; // NullReferenceException: _bag is null!
Also your code would be cleaner and more easier to maintain if we split this Action into several actions and follow the technology philosophy.
Do you consider refactoring this piece of code into smaller and more understandable chunks?