2 interlocking questions regarding html helpers - html

first off some background. i have a web site written in MVC5 and VS 2013. i created the project using the MVC template so i do have the bootstrap navbar at the top which is where all my menu links are. each link corresponds to a different view and each view uses (by default) _Layout.cshtml. i want to do 2 things to the links on the navbar - 1. have the current selected (active) item highlited when selected and 2. have them ajax compliant so that only the content of each view is refreshed not the entire page when a link is clicked.
i already have goal 1 done and working but i'm not sure i'm doing it right. after finding a sample on the web i created an html helper extension method in the App_Code directory that looks like this..
public static class MyHelper
{
public static MvcHtmlString HtmlLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName)
{
var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
var builder = new TagBuilder("li")
{
InnerHtml = htmlHelper.ActionLink(linkText, actionName, controllerName).ToHtmlString()
};
if (controllerName == currentController && actionName == currentAction)
builder.AddCssClass("active");
return new MvcHtmlString(builder.ToString());
}
}
i implemented it in _Layout like this..
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>#Html.HtmlLink("Home", "Index", "Home")</li>
<li>#Html.HtmlLink("About Me", "AboutMe", "Home")</li>
<li>#Html.HtmlLink("Samples", "Samples", "Home")</li>
<li>#Html.HtmlLink("Links", "Links", "Home")</li>
<li>#Html.HtmlLink("Contact", "Contact", "Home")</li>
</ul>
#*#Html.Partial("_LoginPartial")*#
</div>
the only question i have with this is how does VS know that HtmlLink is a helper extension? i didn't inherit from anything and both the class and method names were ones i made up. all i had to do was put #Html in front and it knew what it was. just want to understand whats going on. sure it works but i want to know why.
now for the Ajax stuff. i got that working also but i had to change the html helper calls in _Layout to ajax helper calls as so..
<li>#Ajax.ActionLink("Home", "Index", "Home", new AjaxOptions() { UpdateTargetId = "site_content" })</li>
<li>#Ajax.ActionLink("About Me", "AboutMe", "Home", new AjaxOptions() { UpdateTargetId = "site_content" })</li>
<li>#Ajax.ActionLink("Samples", "Samples", "Home", new AjaxOptions() { UpdateTargetId = "site_content" })</li>
<li>#Ajax.ActionLink("Links", "Links", "Home", new AjaxOptions() { UpdateTargetId = "site_content" })</li>
<li>#Ajax.ActionLink("Contact", "Contact", "Home", new AjaxOptions() { UpdateTargetId = "site_content" })</li>
but now i'm in a quandary. since in both cases i had to change the same code in _Layout, i can right now only do one or the other not both.
is there a way to possibly combine the 2 functionalities into one helper method or maybe there's a better way to do either or both? appreciate any help.

surprised nobody came up with this. the first question was answered but the second was not. to reiterate, it was 'how do i get both the active menu selection and Ajax to work at the same time using extensions?' took some doing but i found the answer. i ended up using the Ajax.ActionLink helper in _Layout.cshtml to implement Ajax and the following jquery added to each view to implement the menu selection. very simple and ended up not even needing the extension..
$(document).ready(function () {
$('ul.nav.navbar-nav').find('a[href="' + location.pathname + '"]')
.closest('li').addClass('active');
});
so as it turns out i didn't need the Html helper extension method i created but at least i learned how to do it. anyway, to finish things off, i just changed the default attributes in bootstrap.css to fit my preferences and that was it..
.navbar-inverse .navbar-nav > .active > a,
.navbar-inverse .navbar-nav > .active > a:hover,
.navbar-inverse .navbar-nav > .active > a:focus {
color: #DA9C9C;
background-color: #FFFFFF; /**/
text-shadow: 0px 0px #FFFFFF; /**/
font-weight:bold;
}
both objectives are now met and it works like a champ.

Related

Add active state to 'a' tag under Bootstrap 5 nav-tabs in mvc application when using ActionLink

I have a .NET mvc application, where I use Bootstrap 5.1 to create a nav-tabs. Each 'a' tag is generated via ActionLink.
<ul class="nav nav-tabs">
<li class="nav-item">
#Html.ActionLink("Entity", "Details", "Entity", new { id = Model.Id, tab = Entity.TAB_ENTITY }, new { #class = "nav-link"})
</li>
<li class="nav-item">
#Html.ActionLink("Tasks", "Details", "Entity", new { id = Model.Id, tab = Entity.TAB_TASKS }, new { #class = "nav-link active", #aria_current = "page" })
</li>
</ul>
According to Bootstrap documentation, I should use aria-current attribute on 'a' tag to convey active state between tabs. The problem is I can't hardcode that under ActionLink, like I did with nav-link.
I was trying to make a custom HTML helper, but it didn't went well because of the ActionLink.
Currently the effect is like this
if I hardcode aria-current='Page' and 'nav-link active' in each 'a' tag.
How can I design such a nav-tabs, which will convey active state between tabs? I don't want to use additional js code, since this functionality is already under bootstrap.js
You can do it inside htmlAttributes
#Html.ActionLink("Entity", "Details", "Entity", new { id = Model.Id, tab = Entity.TAB_ENTITY }, new { #class = "nav-link", #aria_current="page" })
Remember: Underscore is used as opposed to hyphens within the HTML Attributes

Use forms on multiple tabs in Net Core 3.1 MVC

In my Net Core 3.1 MVC project I wish to create two tabs on one page, with on each tab a form for editing data. What is the best approach to do this? Preferably with lazy loading, so that form data gets loaded when a tab is active.
I have tried with Pages and PartialViews but this didn't work properly, and now I am wondering if there is a straightforward way of achieving this in Net Core MVC.
My code so far:
basicprofile.cshtml
#page
#model VideoGallery.Presentation.Pages.Account.BasicUserProfile.InputModel
#{
}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="basicprofile-tab" data-toggle="tab" href="#basicprofile" aria-controls="basic" aria-selected="true">Account</a>
</li>
<li class="nav-item">
<a class="nav-link" id="extendedprofile-tab" data-toggle="tab" href="#extendedprofile" aria-controls="extended" aria-selected="false">Profile</a>
</li>
</ul>
<div class="tab-content p-3 border-right border-left">
<div class="tab-pane fade show active" id="basicprofile" role="tabpanel" aria-labelledby="basicprofile-tab">
</div>
<div class="tab-pane fade" id="extendedprofile" role="tabpanel" aria-labelledby="extendedprofile-tab"></div>
</div>
#section scripts{
<script>
var basicprofileLoaded = false;
var extendedprofileLoaded = false;
console.log("active");
$(function () {
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
switch ($(e.target).attr('aria-controls')) {
case "basic":
if (!basicprofileLoaded) {
$('#basicprofile').load("#Url.Page("basicuserprofile", pageHandler:"PartialForm" )");
basicprofileLoaded = true;
}
break;
case "extended":
if (!extendedprofileLoaded) {
console.log("inside switch/ IF extended part");
$('#extendedprofile').load('/account/extendedprofile')
ordersLoaded = true;
}
break;
}
});
});
</script>
}
BasicUserProfile.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using VideoGallery.Presentation.Services;
namespace VideoGallery.Presentation.Pages.Account
{
public class BasicUserProfile : PageModel
{
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
public string LoginName { get; set; }
public string Password { get; set; }
}
// both methods don't even get called, even when the page renders with an empty form.
public async Task<IActionResult> OnGetAsync()
{
Input.Password = "test inpu";
Input.LoginName = "other input";
return Page();
}
public async Task<PartialViewResult> OnGetPartialForm()
{
Input.LoginName = "some name";
return Partial("_BasicProfilePartial", Input);
}
}
}
I have several problems:
My handlers OnGet() and OnGetPartialForm() are not fired. Break points don't get hit.
On loading of BasicUserProfile page, the basic tab should be active, showing the form on this first tab. It has the active class, but the data isn't loaded.
The BasicProfile tab loads the whole Page object, after I click the basic tab (when I am on the extended-tab, for example). It loads the whole page, although none of the backend breakpoints are hit.
What I would like to do:
On loading the initial page, the form data for the first tab (active on loading) should be loaded, with or without data. Then if the user changes to the second tab, that form data should be loaded and displayed under the 2nd tab. Obviously, later, I want to process the data with a post request.
My handlers OnGet() and OnGetPartialForm() are not fired. Break points don't get hit.
That is because you use #model VideoGallery.Presentation.Pages.Account.BasicUserProfile.InputModel , which is not a PageModel.
Change to:
#page
#model VideoGallery.Presentation.Pages.Account.BasicUserProfile
<input asp-for="Input.Password" />
<input asp-for="Input.LoginName" />
$('#basicprofile').load("#Url.Page("basicuserprofile", pageHandler:"PartialForm" )");
Be sure the url is rendered successfully. You can press F12 and click Sources panel to check the js source in browser. In your scenario, it seems to be:$('#basicprofile').load("#Url.Page("/Account/basicuserprofile", pageHandler:"PartialForm" )");.
[BindProperty]
public InputModel Input { get; set; }
You need remember to initialize the InputModel, otherwise it will get error when hit Input.Password = "test inpu";:
[BindProperty]
public InputModel Input { get; set; } = new InputModel();

Add styling to all #Html.ActionLink from css class

I know I am able to add a class to a #Html.Actionlink by using, which acts on a single Actionlink at a time:
#Html.ActionLink("Add",
"UpdateNote",
"Notes",
new { id = 0, type = (int)THOS.Utilities.Enumerations.Enumerations.Note.RelatedApplicationType.Law, appid = ((ObjectModelLibrary.Law)ViewData["currentLaw"]).LawID, baseappid = ((ObjectModelLibrary.Law)ViewData["currentLaw"]).LawID }
new { #class = "btn btn-primary icon-edit"},
null)
However,
Is there a way of defining a class (much like adding a style to all divs in the css file like:
div{
color: red;
}
^ ^
| |
this way will act on *all* divs
instead of going
<div class="myClass"></div>
I can just write:
<div></div>
which will automatically have color:red included
would there be a way of defining a class for an ActionLink without going to each actionlink and typing #Class="myClass"
For Example
for adding styling for all button instances:
input[type="button"]{
background-color:red;
}
Can i do this with something like:
input[type="actionlink"]{
//styles for all actionlinks in project
}
and so all actionlinks can be written as:
#Html.ActionLink("Action","Controller")
and automatically include the styling stated in my css file?
I would do this the first way, but i've already ~100 made without defining a class, and don't fancy copy and pasting:
class="myClass"
ActionLink generates just normal anchors, so you can write:
a{
color:red;
}
But to get just ActionLinks you will need to call them by a class or a container.
Also may be a better way to do it is to create a custom ActionLink and put a default class inside and using this class you can do your selector, like this you will use this new Custom ActionLink and no need to copy paste classes.
public static class LinkExtensions
{
public static MvcHtmlString MyActionLink(
this HtmlHelper htmlHelper,
string linkText,
string action,
string controller
)
{
var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (action == currentAction && controller == currentController)
{
var anchor = new TagBuilder("a");
anchor.Attributes["href"] = "#";
anchor.AddCssClass("currentPageCSS");
anchor.SetInnerText(linkText);
return MvcHtmlString.Create(anchor.ToString());
}
return htmlHelper.ActionLink(linkText, action, controller);
}
}
%= Html.MyActionLink("hello foo", "Index", "Home") %>
<%= Html.MyActionLink("hello bar", "About", "Home") %>
Code copied from https://stackoverflow.com/a/5084672/20126

Retain jquery tabs after redirect

I am having jquery tabs inside the parent menus, I want to retain the opened tabs after the page redirect.
HttpContext.Current.Response.Redirect("~/HRMS/PayrollHome.aspx", false);
You could always store when tabs are opened and closed in the session. Then when the page is visited again you can check if each tab is listed as being open in the session and if it is re-open it.
EDIT
Its difficult to give you entire code snippets that answer your problem completely because I haven't got access to your code base. But it would be something like this.
<ul id="tabs">
<li onclick="$.get("SaveTab.ashx?tab=1");">Tab 1</li>
<li onclick="$.get("SaveTab.ashx?tab=2");">Tab 2</li>
</ul>
Then in the SaveSession handler
<%# WebHandler Language="C#" Class="SaveTab" %>
using System;
using System.Web;
public class SaveTab: IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/plain";
if (HttpContext.Current.Request.QueryString["tab"] != null)
{
HttpContext.Current.Session["OpenTab"] = HttpContext.Current.Request.QueryString["tab"];
}
}
public bool IsReusable {
get {
return false;
}
}
}
Then in the ASPX output some Javascript that selects the tab. Notice that I am outputting the session value.
$(document).ready(function() {
$('#tabs').tabs('select', <%= SESSION["OpenTab"]%>);
});
I hope this is enough for you to get the general idea, I can't obviously tell you exactly what to do just show you a good approach.

Razor output not working in MVC 3 but working in MVC 2

This same code working fine with MVC 2 but not working in MVC 3 Razor. Once page is loaded not loading menu from HTMLHelper called within Razor like below.
Hardcoded menu for testing which is not outputting on the page.
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using myproject.Extensions;
public static class MenuHelper
{
public static string TabbedMenu(this HtmlHelper helper, IEnumerable<MenuTab> tabs)
{
//I have hard coded menu for testing purpose.
return "<div class='menu-image'><img src='/content/Images/common/on-left.gif' alt='' /></div><div class='on'><a class='over' href='/?Length=4'>Home</a></div><div class='menu-image'><img src='/content/Images/common/on-right.gif' alt='' /></div><a href='/Home/About'>About</a><a href='/Home/Contact'>Contact</a>";
}
}
Below is Razor CSHTML code.
#{Html.TabbedMenu
(
new List<MenuTab>
{
MenuTab.Create("Home", "Index", "Home"),
MenuTab.Create("About", "About", "Home"),
MenuTab.Create("Contact", "Contact", "Home")
}
);}
Wrapping code in #{ ... } (like you did) is Razor's equivalent to <% ... %> (without an =).
Therefore, your code calls the function, but doesn't do anything with the result.
You should remove the {} and the ; and simply write #Html.TabbedMenu(...); this is equivalent to <%: Html.TabbedMenu(...) %>.
You'll also need to change the method to return an HtmlString to prevent Razor from escaping the HTML.