I want to create a layout like the User page on Stack Overflow where there is a view (the parent view) at the top of the page and then content in tabs, each with it's own view (child views).
When I hover over each of the tabs on the User page in SO it looks like they are pointed at the user controller and are being sent the tab name in the query string to render the appropriate tab content.
I believe I can achieve this using a layout with a section defined in the parent view. The section would be the child view, but I don't know how I would tell the section which view or partial view to show.
I have not been able to find anything useful on the web. Can someone tell me how to do this or at least point me in the right direction?
Thanks in advance!
Edit: Thanks to #Mystere's help I was able to come up with the solution below in case anyone else is trying to do the same thing.
HTH
Final Solution:
Controller Actions
public ActionResult Details(int id, string tab = null)
{
ViewBag.Jobid = id;
ViewBag.Tab = tab ?? "Services";
var viewModel = getJobRecordDetails(id);
return View(viewModel);
}
public ActionResult JobInfo(int id, string tab)
{
ViewBag.Jobid = id;
ViewBag.Tab = tab;
if (tab == "Services")
{
var viewModel = getServices(id);
return View("Services", viewModel);
}
if (tab == "Equipment")
{
var viewModel = getEquipment(id);
return View("Equipment", viewModel);
}
if (tab == "Personnel")
{
var viewModel = getPersonnel(id);
return View("Personnel", viewModel);
}
return View("Error");
}
Parent View
#model MyApplication.Models.JobViewModel
#{
ViewBag.Title = "Details";
}
<h2>Job Details</h2>
...
#* Child View Action *#
#Html.Action("JobInfo", new { id = ViewBag.Jobid, tab = ViewBag.Tab })
Child View
#model MyApplication.Models.ServicesViewModel[]
#{
ViewBag.Title = "Services";
Layout = null;
}
#* Submenu Navigation *#
#{
Html.RenderPartial("SubMenu");
}
<h2>Services</h2>
Services here...
Subnavigation Partial View
<div id="submenucontainer">
<ul id="submenu">
<li class="#Html.ActiveTab("Job","JobInfo","Services")">Services </li>
<li class="#Html.ActiveTab("Job","JobInfo","Equipment")">Equipment</li>
<li class="#Html.ActiveTab("Job","JobInfo","Personnel")">Personnel</li>
</ul>
ActiveTab Helper
public static string ActiveTab(this HtmlHelper helper, string controller, string action, string tab)
{
var classValue = "";
var currentController =
helper.ViewContext.Controller.ValueProvider.GetValue("controller").RawValue.ToString();
var currentAction =
helper.ViewContext.Controller.ValueProvider.GetValue("action").RawValue.ToString();
var currentTab = helper.ViewContext.Controller.ValueProvider.GetValue("tab").RawValue.ToString();
if (currentController == controller && currentAction == action && currentTab == tab)
classValue = "selected";
return classValue;
}
It is unlikely they are using a section for that. sections are used primarily in layout pages (the equivelent of master pages).
More than likely, they just have multiple views, and they pass whichever view is appropriate to the View() method. They might use partial views, or MVC templates to render the tab areas, so that common code is factored out.
Edit:
As requested, code sample:
In action method:
public ActionResult Dashboard(string tab) {
if (tab == "summary")
ViewBag.Tab = "~/Views/Dashboard/Summary.cshtml";
if (tab == "activity")
ViewBag.Tab = "~/Views/Dashboard/Activity.cshtml";
return View()
}
in Dashboard.cshmtl
... your parent view
#Html.Partial(ViewBag.Tab)
... your footer
It's not rocket science. There are so many ways to do this it doesn't take much thought to come up with one of them.
Related
I'm trying to render different partial views from the _Layout file depending on what function I'm in, controller-wise.
The partial view is in the right column of the website which is located in the _Layout like so:
<aside id="right">
#Html.Partial("RightPartial")
</aside>
What I want to do is render a different partial view depending on where I am.
If I'm in the Index view I might want to view news and in the About view I might want to view phone numbers or something.
Appreciate any help :)
#{
string currentAction = ViewContext.RouteData.GetRequiredString("action");
string currentController = ViewContext.RouteData.GetRequiredString("controller");
}
Now based on the values of those variables decide which partial to render. To avoid polluting the Layout I would write a custom HTML helper:
<aside id="right">
#Html.RightPartial()
</aside>
which might look like this:
public static class HtmlExtensions
{
public static IHtmlString RightPartial(this HtmlHelper html)
{
var routeData = html.ViewContext.RouteData;
string currentAction = routeData.GetRequiredString("action");
if (currentAction == "Index")
{
return html.Partial("IndexPartialView");
}
else if (currentAction == "About")
{
return html.Partial("AboutPartialView");
}
return html.Partial("SomeDefaultPartialView");
}
}
I have one controller which will pass data to view Index.cshtml
public ActionResult Index()
{
var empId = #User.Identity.Name;
var empDetails = empRepository.GetEmployeeDetails(empId);
var emp = new UserViewModel {EmployeeDetail = empDetails};
return View(emp);
}
Layout of view Index.cshtml is Partialview _Layout.cshtml
#model HRM.Areas.EmployeeDetails.Models.UserViewModel
#{
ViewBag.Title = "Index";
Layout = "~/Areas/EmployeeDetails/Views/Shared/_Layout.cshtml";
}
<div>Hello</div>
I want to access the data which I passed from controller action to Index.cshtml in _Layout.cshtml.
Is there any way to do this ?
Your question is not clear on how you want to access the values so i'm making an assumption here:
If you want to include data from a view in a rendering from your layout page you can use the RenderSection method.
Layout Page:
#RenderSection("ViewSection", false)
View Page:
#section ViewSection {
<div>
<label>Emp Name:</label>
#model.EmpDetails.Name
</div>
}
When your Layout is rendered it'll look for a section in your view that matches and render it. Passing a false as the 2nd param tell the Layout that the section is not required.
For more info on this: http://weblogs.asp.net/scottgu/archive/2010/12/30/asp-net-mvc-3-layouts-and-sections-with-razor.aspx
Your question is not quite clear to me:/
My assumption ->
If you change your code to look like this:
public ActionResult Index()
{
var empId = #User.Identity.Name;
var empDetails = empRepository.GetEmployeeDetails(empId);
var emp = new UserViewModel { EmployeeDetail = empDetails };
ViewBag.UserViewModel = emp;
return View(emp);
}
on the Layout page you can access your data:
#if (ViewBag.UserViewModel != null)
{
//do sth with ViewBag.UserViewModel here
//for example:
#Html.Partial("_UserView",ViewBag.UserViewModel)
}
If you explain in details what you want to achive it will be easier for us to give you the right answer.
You can access the data in the model like this:
#Model.EmployeeDetail ...
I've got a view that displays a list of items. Rather than display items in a grid, I'd like to display 4 items per page, each with an image and multiple properties, displayed in a unique layout.
So, I'd like a foreach to iterate through the items, and each item to get displayed in a div. I could put all the code in the loop, but I'd like to have a custom html helper extension to do this.
I came up with this,
public static MvcHtmlString DisplayViewerFor(this HtmlHelper helper, TestModel model, bool rightAligned = true) {
if (model == null) {
model = new TestModel();
}
var outterDiv = new TagBuilder("div");
outterDiv.AddCssClass(rightAligned ? "item-display-right" : "item-display");
var image = new TagBuilder("image");
image.Attributes.Add("src", "Item/GetImage/" + model.ItemName);
image.Attributes.Add("height", "150");
var editorLabel = new TagBuilder("div");
editorLabel.AddCssClass("editor-label");
//LOOKING TO ADD CODE LIKE THIS HERE
var labelContent= html.LabelFor({my model property here})
editorLabel.InnerHtml += labelContent;
//END OF ADD
return new MvcHtmlString(outterDiv.ToString(TagRenderMode.EndTag));
}
In my method above, I need to display a few more values, and I would like to use the Html.LabelFor and Html.DisplayFor helpers, but the methods aren't available and I'm not sure what to pass to them if they were.
I'm not sure if this is possible or not, but I thought I would ask.
EDIT
I'm trying to use the html.LabelFor. See my code where I have updated it above, adding to it these two lines.
var labelContent= html.LabelFor({my model property here})
editorLabel.InnerHtml += labelContent;
You can see the code above.
EDIT 2
Here is the planned use for this Helper with dummied down view.
#model TestItemDisplayList
#{
ViewBag.Title = "Items";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#foreach(var item in #model.Items){
#Html.DisplayViewerFor(item)
}
You can use the Html.DisplayFor method to render a DisplayTemplate. One of the over loads for the method is to specify a template to use. You can modify your page code to read:
Page:
#model TestItemDisplayList
#{ ViewBag.Title = "Items"; Layout = "~/Views/Shared/_Layout.cshtml"; }
#Html.DisplayFor(model => Model,"TestItemDisplayList")
Display Template for TestItemDisplayList
#model TestItemDisplayList
#* You could limit this loop to the first 4 items *#
#foreach(var item in model.Items){ #Html.DisplayFor(item => item) }
Display Template for TestModel
#model TestModel
<div class="item-display">
<img src="#Url.Action("GetImage", "Image", new { id = Model.ItemName})" height="150"/>
<div class="editor-label">#Html.LabelFor(model => model.PropertyHere)</div>
</div>
I assume that your URL for the image used the default route of {controller}/{action}/{id} so I used the Url.Action and specified your ID.
You could also get away witout using a DisplayTemplate for "TestItemDisplayList" and moving that code in to your page but I wasn't clear if you wanted to add logic in that to limit the number of pages.
I am creating a breadcrumb partial view which takes in a collection of title/URL. The collection will be generated in action methods and would have to be available on the breadcrumb partial view.
I tried couple of ways to get it done and this is one among such: http://goo.gl/rMFlp
But some how i could not get it working. All i get is an "Object reference not set to an instance of an object." Can you guys help me out?
{Updare}
Here is the code:
I created a Model class as follows
public class ShopModel
{
public Dictionary<string,string> Breadcrumb { get; set; }
}
Action Method
public ActionResult Index()
{
var breadcrumbCollection = new Dictionary<string,string>();
breadcrumbCollection.Add("/home","Home");
breadcrumbCollection.Add("/shop","Shop");
var model = new ShopModel() { Breadcrumb = breadcrumbCollection};
return View(model);
}
Model binding the view - Index
#Model NexCart.Model.Model.Custom.ShopModel
Finally here is the code on partial view:
<div>
#{
foreach (var item in #Model.Breadcrumb)
{
#item.Key
}
}
You haven't shown any code, so your question is impossible to answer. This being said here's how you could proceed. As always in an ASP.NET MVC application you start by defining a view model:
public class Breadcrumb
{
public string Title { get; set; }
public string Url { get; set; }
}
then you could write a controller action which will populate a collection of breadcrumbs and pass them to a partial view:
public class BreadcrumbController: Controller
{
public ActionResult Index()
{
// TODO: pull the breadcrumbs from somewhere instead of hardcoding them
var model = new[]
{
new Breadcrumb { Title = "Google", Url = "http://www.google.com/" },
new Breadcrumb { Title = "Yahoo", Url = "http://www.yahoo.com/" },
new Breadcrumb { Title = "Bing", Url = "http://www.bing.com/" },
};
return PartialView(model);
}
}
then you could have a corresponding partial view which will render this model (~/Views/Breadcrumb/Index.cshtml):
#model IEnumerable<Breadcrumb>
<ul>
#Html.DisplayForModel()
</ul>
and the corresponding display template (~/Views/Breadcrumb/DisplayTemplates/Breadcrumb.cshtml):
#model Breadcrumb
<li>
#Model.Title
</li>
Now all that's left is to include this child action somewhere using the Html.Action helper. For example you could do this in the _Layout if this breadcrumb is repeated on each page:
#Html.Action("Index", "Breadcrumb")
But obviously it could also be done in any view.
I am becoming more familiar with MVC 3 and the RAZOR view engine. I have a question regarding layouts and shared controls on pages.
Let’s say I have a header section defined in my main layout. In that header is a dropdown I need to populate with project names. This dropdown will serve as a context for the entire site and is present on all pages. As an example, if the user selects “Project A” from the drop down, all of the views for the site will be based on “Project A”. Since this dropdown control is rather static and is used by the entire site, where is the best place to put the code to pull all the projects to display in the dropdown? In a Partial View? In a HTML helper? Another thought is, if a user selects a new value, they would be taken to a dashboard or similar page for that newly selected project. I am trying to figure out how to reuse this control on every page in the site without having to keep wiring it up in every possible controller.
You could use a child action along with the Html.Action helper. So you start by defining a view model:
public class ProjectViewModel
{
[DisplayName("Project name")]
public string ProjectId { get; set; }
public IEnumerable<SelectListItem> ProjectNames { get; set; }
}
then a controller:
public class ProjectsController: Controller
{
private readonly IProjectsRepository _repository;
public ProjectsController(IProjectsRepository repository)
{
_repository = repository;
}
public ActionResult Index(string projectId)
{
var projects = _repository.GetProjects();
var model = new ProjectViewModel
{
ProjectId = projectId,
ProjectNames = projects.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Name
})
};
return PartialView(model);
}
}
then the corresponding view (~/views/projects/index.cshtml):
#model ProjectViewModel
#Html.LabelFor(x => x.ProjectId)
#Html.DropDownListFor(
x => x.ProjectId,
Model.ProjectNames,
new {
id = "projects",
data_url = Url.Action("SomeAction", "SomeController")
}
)
Now all that's left is to render this widget inside the _Layout.cshtml:
#Html.Action("Index", "Products", new { projectid = Request["projectId"] })
And now we could put some javascript so that when the user decides to change the selection he is redirected to some other action:
$(function() {
$('#projects').change(function() {
var url = $(this).data('url');
var projectId = encodeURIComponent($(this).val());
window.location.href = url + '?projectid=' + projectId;
});
});
Another possibility is to put the dropdown inside an HTML form:
#model ProjectViewModel
#using (Html.BeginForm("SomeAction", "SomeController", FormMethod.Get))
{
#Html.LabelFor(x => x.ProjectId)
#Html.DropDownListFor(
x => x.ProjectId,
Model.ProjectNames,
new {
id = "projects",
}
)
}
so that inside the javascript we don't have to worry about building urls when the selection changes and simply trigger the containing form submission:
$(function() {
$('#projects').change(function() {
$(this).closest('form').submit();
});
});
We just did a similiar thing on a project.
First, you can't really put it in a section because you have to put that section on every view, you could put it in a partial but you would still have to call it from every view.
Second, you can't really put it in the Layout page because the layout page isn't passed any kind of model. So I created an html helper and referenced that in the layout page. There are lots of tutorials on creating html helpers so I won't put the code here. But essentially in your html helper you can make a database call to get all of your projects. Then you can create a select list using string builder in the html helper and return that to the layout page. We then used jquery to add an on change event to the select list. When the select list changed it loaded a new page. So for example, in your select list the value of each item could be the project id, then on change it redirects them to a page like /Projects/View?id=234 where 234 is your project id.
So things to research. 1. Creating HTML Helpers 2. JQUERY change event.
That should get you in the right direction. Let me know if you need any other help and I can post some code.