How could i change layout page from viewpage in mvc - c#

I am working on asp.net mvc 3 with razor. I have a layout(master) page in my project. It contains a side panel with 4 links and place for viewpage(#RenderBody). when user clicks on link1 it redirects to viewpage1 and link1 should be selected, and when he clicks link2 it redirects to viewpage2 and link2 should be selected so on. It redirecting very well to required pages but it always selects the link1 only though i clicked link2,link3,link4. How could i select the appropriate link in layout page from the individual viewpage. guide me.

By selected I guess you mean highlight using CSS, don't you? If this is the case I would propose you to write a custom HTML helper to generate those links:
public static IHtmlString MenuItem(
this HtmlHelper htmlHelper,
string text,
string action,
string controller
)
{
var li = new TagBuilder("li");
var routeData = htmlHelper.ViewContext.RouteData;
var currentAction = routeData.GetRequiredString("action");
var currentController = routeData.GetRequiredString("controller");
if (string.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) &&
string.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase))
{
li.AddCssClass("active");
}
li.InnerHtml = htmlHelper.ActionLink(text, action, controller).ToHtmlString();
return new HtmlString(li.ToString());
}
and then inside your layout use the helper:
<ul>
#Html.MenuItem("link 1", "Action1", "Controller1")
#Html.MenuItem("link 2", "Action2", "Controller2")
...
</ul>
and now all that's left is to define the .active rule in your CSS class:
.active {
... something fancy to pop the currently selected link from the others
}

Related

Build Menu Items From Controller

I am creating HTML Menus from controller. Menus are stored in database and I make html tag as below :
foreach (UIMenuModel item in list)
{
if (item.Controller != "Home")
{
string line = string.Format(#"<li><a asp-area=""{0}"" asp-controller=""{1}"" id=""{1}""
asp-action = ""{2}"" style = ""font-size:16px;;"" > {3} </a></li>", item.Area, item.Controller, item.Action, item.LinkText);
sb.Append(line);
}
}
which gives me below HTML :
<li><a asp-area="" asp-controller="CrossApproval" id="CrossApproval" asp-action="Index" style="font-size:16px;;"> Cross Approval </a></li>
Other Menu Item, Which is written in HTML itself, gives below HTML in browser.
<li><a id="CrossRequest" style="font-size:16px" href="/CrossRequest">Cross Request</a></li>
On UI, it looks perfect. However, I am not able to click and navigate to desired controller and action methods. Can someone please help me to identify while this anchor tag is not allowing me to navigate.
Use RazorLightEngine to convert plain string as rendered Razor string:
string content = "Hello #Model.Name. Welcome to #Model.Title repository";
var model = new
{
Name = "John Doe",
Title = "RazorLight"
};
var engine = new RazorLightEngine();
string result = engine.ParseString(content, model);
And then add it any place in razor view like encoded string
<div>
#Html.Raw(result)
</div>
As posted in Question, HTML with href was working fine. So, I decided to mimic the same behaviour from the controller and changed my code as below :
string line = string.Format(#"<li><a asp-area=""{0}"" id=""{1}"" href=""/{1}/{2}""
style=""font-size:16px"">{3}</a></li>", item.Area, item.Controller, item.Action, item.LinkText);
This generated a link which I can click and navigate.

Making menu item active based on url and query string

I have some menu item in my application which are as below :
Statistics
Tax
Skills
Now when user click on any menu item I want to make it active like when user click on statistic I would like to make it active and this is how currently I am doing it and it is working fine.
_Layout.cshtml:
<div class="Menu #Html.IsSelected(actions: "Index", controllers: "Statistics", class: "active")" >Stats</div>
<div class="Menu #Html.IsSelected(actions: "Index", controllers: "Tax", class: "active")">Tax</div>
<div class="Menu #Html.IsSelected(actions: "Index", controllers: "Skills", class: "active")">Skills</div>
public static string IsSelected(this HtmlHelper html, string controllers = "", string actions = "", string cssClass = null)
{
ViewContext viewContext = html.ViewContext;
bool isChildAction = viewContext.Controller.ControllerContext.IsChildAction;
if (isChildAction)
viewContext = html.ViewContext.ParentActionViewContext;
RouteValueDictionary routeValues = viewContext.RouteData.Values;
string currentAction = routeValues["action"].ToString();
string currentController = routeValues["controller"].ToString();
if (String.IsNullOrEmpty(actions))
actions = currentAction;
if (String.IsNullOrEmpty(controllers))
controllers = currentController;
//for passing multiple comma seperated Action.
//For Eg:class="#Html.IsSelected(actions: "List,Detail", controllers: "Default")"
string[] acceptedActions = actions.Trim().Split(',').Distinct().Select(t=>t.ToLower().ToString()).ToArray();
string[] acceptedControllers = controllers.Trim().Split(',').Distinct().Select(t=>t.ToLower().ToString()).ToArray();
return acceptedActions.Contains(currentAction.ToLower()) && acceptedControllers.Contains(currentController.ToLower()) ?
cssClass : String.Empty;
}
But now I have 1 common page accessible from both statistics and tax page :
Rank
Url when user goes to this page from Statistics :
http://localhost:1231/Rank/Index?type=stats&Id=21
Url when user goes to this page from Statistics :
http://localhost:1231/Rank/Index?type=Tax&Id=48
So I would like to make Statistics menu active when user goes on Rank page from Statistics page and make Tax menu item active when user goes on rank page from Tax page.
You're making this way too complicated. Try this instead:
#{ string url = null; }
#{ url = Url.Action("Index", "Statistics"); }
<div class="Menu #(url == Request.Url.AbsolutePath ? "active" : null)">
Stats
</div>
#{ url = Url.Action("Index", "Tax"); }
<div class="Menu #(url == Request.Url.AbsolutePath ? "active" : null)">
Tax
</div>
#{ url = Url.Action("Index", "Skills"); }
<div class="Menu #(url == Request.Url.AbsolutePath ? "active" : null)">
Skills
</div>
In other words, all you need is a simple ternary to compare the URL of the link with the current URL. Request.Url.AbsolutePath is used so that query strings are ignored. Setting a url variable just prevents having to call Url.Action multiple times, keeping things DRY.
Its simple, just like a trick
Create a css of Active and set whatever color you want to show active.
On selection of any of them, remove class from all, get what item is clicked. add class named Active to only that element.

Update a shared view from different controllers

I have a partial view which is being rendered in a shared layout called _Layout.cshtml.
_Layout.cshtml
<h1>#Html.Action("Home", "TestView");</h1>
Controller:
[ChildActionOnly]
public ActionResult TestView()
{
var model = new TestViewModel();
model.Title = "Default Title";
return PartialView("/Views/Shared/_TestView.cshtml", model);
}
_TestView.cshtml
#model TestMVC.Models.TestViewModel
#Model.Title
So far everything works fine, but I want to know if it is possible to update the model from a different action/controller.
Example in: localhost:8888/home/index, I want to display the default title which is "Default Title", but in certain actions/controller ex localhost:8888/home/contactus
I want to display a different title ex. "Contact us form title".
Is this possible?
just add an optional parameter:
[ChildActionOnly]
public ActionResult TestView(string title = "Default Title")
{
var model = new TestViewModel{ Title = title };
return PartialView("/Views/Shared/_TestView.cshtml", model);
}
then call it
<h1>#Html.Action("Home", "TestView", new { title = "Custom Title" })</h1>
or
<h1>#Html.Action("Home", "TestView")</h1> //use default title
Since your partial view is between <h1> tags, does this mean it will only function as a placeholder for text?
Either way, I get the feeling there are better solution than using partial pages.
You could simply use ViewBag to set a chosen field, e.g. ViewBag.CustomHeader = "Hello";.
Then you can remove the partial view and reference that field:
<h1>#ViewBag.CustomHeader</h1>
Keep in mind that you are then responsible for setting that value each time, or having the _Layout page provide a default value in case you didn't set it.
<h1>#(ViewBag.CustomHeader ?? "No title was set!")</h1>
A second option is possible by using Razor's sections to set the header. This would allow even HTML to be entered if you so choose.
Change your <h1> (including the tags) to:
#RenderSection("CustomHeader", required : false);
//you can set required to true if you need it.
Then, on any View you render, you can add a section by that name, and it will be added on the _Layout page instead of the contained View.
#section CustomHeader {
<h1>Hello, again! I'm another page!</h1>
<!-- You can add anything you want here -->
}
More info on sections can be found here.

Telerik MVC Menu "MenuItem.Action()" renders wrong url

I'm using Telerik MVC Menu to render my mainmenu.
Following code is the line where a certain menu item gets built up:
item.Add().Text("Address").ImageUrl("~/Content/Images/Icons/house.png").Action("index", "basicdata", new {basicdatatype=BasicDataType.ADDRESS});
I expect the url to become: localhost/basicdata/address
But it actually renders: localhost/basicdata?basicdatatype=address
I'd like to get that enum in my Controller as so:
public ActionResult Index(BasicDataType basicDataType)
{
//Code here
}
But it doesn't work because the URL isn't in the right format. Can someone help out?
EDIT:
Even the following renders the wrong url:
item.Add().Text("Test").Action<BasicDataController>(o => o.Index(BasicDataType.PROJECT));
As you are using areas, you have to specify that area when linking to it from outside:
#{ Html.Telerik().Menu()
.Name("Menu")
.Items(menu =>
{
menu.Add()
.Text("Address")
.Action("index",
"basicdata",
new { basicdatatype = BasicDataType.ADDRESS,
area = "basicdata" });
}).Render();
}

Is this a 'hack', and if so is there a better way to determine which ActionResult to return?

I have an A - Z directory 'widget' that I have on every page. If the user is on the home page and they click something in the directory, I want to load up the directory page with the corresponding result loaded. But if the user is on the directory page and they click something, I want to asynchronously load the result without doing a page refresh.
The directory widget has links that point to the DirectoryResult action method on the GroupController, which would normally return a PartialView if they're on the directory page. But if they're not on the directory page, I redirect to the main Directory action method which returns a View and loads the entire page.
This is the code in question:
public ActionResult DirectoryResult(string search)
{
if (Request.IsAjaxRequest())
{
var groups = _groupService.GetGroupsBySearchExpression(search);
var premiumGroups = _groupService.FilterPremiumGroups(groups);
return PartialView(new FundDirectoryViewModel
{
Groups = groups,
PremiumGroups = premiumGroups
});
}
else
{
TempData[UIMessageDataKeys.FundDirectorySearch] = search;
return RedirectToAction("Directory", "Group");
}
}
I showed this to one of the guys in the office and his immediate response was "that's a hack!". I don't know whether to agree with him or not though, because I don't know any better way to do it.
For reference, this is the definition of the widget that exists on every page:
<div id="DirectoryList" class="directory-list">
<span>Fund Directory</span>
<% var letters = new [] { "A", "B", "C", "D", "E", "F", "G", "H", "I", ... }; %>
<% var current = (Model.Search.IsNotNullOrEmpty()) ? Model.Search : "A"; %>
<% foreach (var letter in letters) { %>
<span>
// use HtmlHelper extension to generate links as our system needs them
<%= Html.RouteActionLink("funddirectory", "DirectoryResult"
, letter
, (letter.ToLower() == current) ? new { #class = "active" } : new { #class = "" })%>
</span>
<%} %>
</div>
Is there a better way for me to determine whether I should return a PartialView or a View depending on the page the request comes from?
While your view can definitely be improved to avoid all this spaghetti code (using editor/display templates and HTML helpers and avoid hard-coding an alphabet in a view :-)), the action method seems fine to me. Using Request.IsAjaxRequest to determine whether the action has been requested with AJAX and return a partial view or if not redirect is perfectly fine.
What could be considered as a hack is the usage of TempData instead of using a query string because if the user presses F5 on the redirected page he will loose the context, but if this is the behavior you expect then it's ok.
While I am not familiar with the context I would be interested in the arguments that the guys at your office used to support their reaction of this being a hack.
While Darin is 100% correct and your code is not a hack I usually prefer making two Actions with different names and signatures. This is especially easy if you use an AjaxOnly action filter such as:
http://helios.ca/2009/05/27/aspnet-mvc-action-filter-ajax-only-attribute/
public ActionResult DirectoryResult(string search)
{
var groups = _groupService.GetGroupsBySearchExpression(search);
var premiumGroups = _groupService.FilterPremiumGroups(groups);
return PartialView(new FundDirectoryViewModel
{
Groups = groups,
PremiumGroups = premiumGroups
});
}
//optional [AjaxOnly]
public ActionResult DirectoryAjaxResult( string search )
{
TempData[UIMessageDataKeys.FundDirectorySearch] = search;
return RedirectToAction("Directory", "Group");
}

Categories

Resources