How do I display hierarical data in ASP.NET? I have a data source that looks like this:
class Tree
{
IEnumerable<Node> Nodes { get; set; }
}
class Node
{
string Description { get; set; }
decimal Amount { get; set; }
IEnumerable<Node> SubNodes { get; set; }
}
I would like to render is as a list of divs, like the one below. I need the div tags to make the animations smooth when expanding and collapsing the branches.
<ul class="foldable">
<li>
<div class="line">
<div class="description">...</div>
<div class="amount">...</div>
</div>
<div class="line">
<div class="description">...</div>
<div class="amount">...</div>
</div>
<ul>
<li>
<div class="line">
<div class="description">...</div>
<div class="amount">...</div>
</div>
</li>
</ul>
</li>
</ul>
I tried building this using ListView elements, but I could not figure out how to call it recursively.
I also looked at the TreeView class, but dropped it because it is rendered using tables.
I gave up on the CSS Friendly Control Adaptors since this seems to be an either or switch for all the controls on the web site.
I tried with a BulledList, but could not figure out how to make it render the divs.
I ended up using four identical nested ListView elements since I know that my lists are never deeper than four levels. But this solution is just so ugly that I'm hoping to find a better one here. =)
You could use a repeater control and then cycle through your list and for each node that has children nest another repeater. It would take some effort because your would need to use it programmatically, but having a prebuilt Repeater control as a server control would make it quite a bit easier.
I like using repeaters because they're VERY flexible when you take some time and work with them. That, and they don't put in extraneous HTML. :)
Related
I'm new to MVC and am a little caught up in how routing works. Currently, my route contains the subject name of a class, but I'd also like it to contain the professor ID.
how can I accomplish this if the subject name and the professor ID are from 2 different models?
So, for example, I'm currently routing the subject name by doing this:
#model List<CourseModel>
<h1>
#foreach (var course in Model)
{
<a asp-controller="Course" asp-action="Index" asp-route-SubjectName="#course.SubjectName"> #course.SubjectName </a>
<br />
}
</h1>
However, as I stated above, the professor ID I'd like to put in the routing is from another model (but I can only call one model in a view) so I feel a bit stuck.
The code I have above is the professor/details view, so I feel like it should be simple to pull data from the professor controller, but I must be mistaken. the variable in the Professor controller is called "ID" but I can't seem to access it in the view when trying to put the value of it in the routing link.
Any suggestions? I'd appreciate any at all
If you want to achieve this you should have a model like this, since you are in a details view of the professor, which teaches n courses.
public class ProfessorDetailsViewModel
{
/*
All the Professor Properties
*/
public int Id {get; set;}
public List<CourseViewModel> Courses {get; set}
}
and then you could use your razor view like this:
#model ProfessorDetailsViewModel
<h1>
#foreach (var course in Model.Courses)
{
<a asp-controller="Course" asp-action="Index" asp-route-SubjectName="#course.SubjectName" asp-route-ProfessorId="#Model.Id"> #course.SubjectName </a>
<br />
}
</h1>
I would like to pass csHtml to a ViewComponent like this:
<vc:some-component title="Title"
body="#Html.Raw("<div>this is a nice div, my name is #Model.Name</div>")">
</vc:some-component>
As you can see, I want to pass HTML that contains references to variables or the model (that's why I'm calling it csHtml and not just Html).
And here is the ViewComponent code:
<div>
<p>#Model.Title</p>
#Model.Content
</div>
Is this even possible? I've been searching and haven't found any examples of how to do this.
Edit: Got it working! Thanks everyone for your help.
Here is some sample code for how to do this:
Calling ViewComponent:
#{
Func<dynamic, object> children =
#<div>
<p style="color: white">aaaa</p>
<p style="color: white">bbbb</p>
<p style="color: white">bbbb</p>
</div>;
}
<vc:some-component body=#children>
</vc:some-component>
View Component content:
#model SomeComponentModel
<div>
#Model.Body("")
</div>
You should be able to add the parameters to the ViewComponent and pass them along towards the ViewComponent view itself as you go.
<vc:some-component title="Title"
body="#Html.Raw("<div>this is a nice div, my name is #Model.Name</div>")">
</vc:some-component>
or something similar to this
#await Component.InvokeAsync("SomeComponent", new { title="Title", body= Html.Raw($"<div>this is a nice div, my name is {Model.Name}</div>") })
Your viewcomponent IViewComponentResult would look something like this, using ViewBag just as example
public async Task<IViewComponentResult> InvokeAsync(string title, string body)
{
//...
ViewBag.Content = body;
ViewBag.Title = title;
//...
return View();
}
And then in the related ViewComponent View
<div>
<p>#ViewBag.Title</p>
#ViewBag.Content
</div>
Creating a more specific ViewModel to pass along would also be an option ofcourse
Yes it is possible, you have to make a workaround but it is achievable,this answer has an example of what you are looking for.
I have a Razor helper function that creates a re-usable HTML panel that saves me writing the same HTML over and over again.
#helper DefaultPanel(string panelTitle) {
<div class="panel">
<div class="panel-logo"><img src="/logo.png"></div>
<div class=panel-inner">
<p class="panel-title">#panelTitle</p>
<div class="panel-content">
/* Can I pass content to be rendered in here here? */
</div>
</div>
</div>
</div>
}
I'm wondering, is it possible to re-use this helper to fill .panel-content with more HTML to allow further flexibility and code reuse - similar to something like below:
#LayoutHelpers.DefaultPanel("Welcome back") {
<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
}
Whilst using .NET MVC I've noticed the Html.BeginForm() does a similar thing when wrapping the code within the #using statement within the Html.BeginForm declaration, like so:
#using (Html.BeginForm("Index", "Login", FormMethod.Post))
{
<div>This content gets rendered within the <form></form> markup.</div>
}
But can this done using #helper methods? If not, is it possible to create a HtmlHelper extension to do a similar thing the way the Html.BeginForm() method does?
You can do a very similar thing using the #section syntax as seen here
This seems like something that would be really useful to be able to do, and odd that there's no easy way to do it on a component level.
There are two ways to achieve the required functionality.
1. #helper
Create #helper which accepts whatever parameters you need plus a function (single object parameter, returns object):
#helper DefaultPanel(string panelTitle, Func<object, object> content)
{
<div class="panel">
<div class="panel-logo">
<img src="/logo.png" />
</div>
<div class="panel-inner">
<p class="panel-title">#panelTitle</p>
<div class="panel-content">
#content(null)
</div>
</div>
</div>
}
Usage:
#DefaultPanel("title",
#<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
)
Your function may also accepts parameters, example here.
2. HtmlHelper extension method
Add the following code anywhere in your project:
namespace System.Web.Mvc
{
public static class HtmlHelperExtensions
{
public static HtmlDefaultPanel DefaultPanel(this HtmlHelper html, string title)
{
html.ViewContext.Writer.Write(
"<div class=\"panel\">" +
"<div class=\"panel-inner\">" +
"<p class=\"panel-title\">" + title + "</p>" +
"<div class=\"panel-content\">"
);
return new HtmlDefaultPanel(html.ViewContext);
}
}
public class HtmlDefaultPanel : IDisposable
{
private readonly ViewContext _viewContext;
public HtmlDefaultPanel(ViewContext viewContext)
{
_viewContext = viewContext;
}
public void Dispose()
{
_viewContext.Writer.Write(
"</div>" +
"</div>" +
"</div>"
);
}
}
}
Usage:
#using (Html.DefaultPanel("title2"))
{
<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
}
The extension method writes directly to the context. The trick is to return a disposable object, which Dispose method will be executed at the end of using block.
I don't know if #helper methods can do this but HtmlHelper extensions certainly can. You've mentioned the Html.BeginForm() example which is probably the most well known - all that does is return an object which implements IDisposable which means that when the Dispose() method is called it just calls the complimentary Html.EndForm() method to add the appropriate closing tags.
It would be very simple to do something similar for your HTML code. You can view the source code to the ASP.NET MVC HtmlHelpers at http://aspnetwebstack.codeplex.com/ - the BeginForm() code can be specifically be viewed here.
I want to create a dynamic website in asp.net using MVC model from Visual Studio 2013.
All the columns will have dynamic content, each one changing it's content on different selections based on user actions or by the controllers.
How can I create those three dynamic columns?
I already have my layout in HTML and CSS but I need to separate those three columns and have the possibility to change their content from my controllers.
Later edit:
In my case I have two menus in the website, one to the left and the other one to the right. Actually they are three with the other one from the top. When I select something from the top, the menu from the left must be showed depending on the top selection.
You can populate each column by using ViewBag or ViewData. So in the Layout, use this data to pupulate this columns, for example:
1) Calling this action will change the content for something that you specify:
public ActionResult MyView()
{
// you can put anything in this ViewBag,
// a string, a list of something and then use it in the view
// Its dinamyc
ViewBag.Column1 = "My content column 1";
ViewBag.Column2 = "My content column 2";
ViewBag.Column3 = new string[] {"Product1", "Product1", "Product1"};
}
2) Consume this data in your layout to generate the dynamic content:
<div id='column1'>
#ViewBag.Column1
</div>
<div id='column2'>
#ViewBag.Column2
</div>
<div id='column3'>
<ul>
#foreach(var s in ViewBag.Column3)
{
<li>
#s
</li>
}
<ul>
You should take a look here for more information:
What is ViewData, ViewBag and TempData?
You should use Sections as well, take a look here:
ASP.NET MVC 3: Layouts and Sections with Razor
I would advise you to make three different Controller Actions. For Example:
public ActionResult Part1()
{
return View();
}
public ActionResult Part2()
{
return View();
}
public ActionResult Part3()
{
return View();
}
And then call them in your Common page.
Example:
<div class="row">
<div class="col-md-4">
#Html.Action("Part1")
</div>
<div class="col-md-4">
#Html.Action("Part2")
</div>
<div class="col-md-4">
#Html.Action("Part3")
</div>
</div>
You can have the selection criteria controls inside the view of the controllers. i.e. if you need a drop down list for the First Block. Place it in the Part1 View. Also to make it async you can have Ajax forms in all three views.
Scenario:
platform: ASP.NET 4.0, MVC 2.0 (doesn't have to be MVC if it's not the right solution)
Create various widgets that inherit the same core markup:
<div class="widget">
<div class="hd-widget">
<!-- dynamically inject code here for each actual widget -->
</div>
<div class="bd-widget">
<!-- dynamically inject code here for each actual widget -->
</div>
<div class="ft-widget">
<!-- dynamically inject code here for each actual widget -->
</div>
</div>
The above "simplified" example should be in some reusable control or masterpage.
The flow is something like this:
Controller -> View -> Partial View (1..n) -> widget markup
User instantiates an action which calls a controller. The controller tries to render a view that contains content + multiple widgets (left rail, right rail, etc). The Widgets all have their own individual partial views but each partial view should inherit or consume a base set of widget markup and have controller areas where to embed functionality.
Functionality in the head, body, and footer can either be plain text, HTML, or additional custom controls.
What is the best recommended approach?
I would be thinking you would have a WidgetModel which contains a set of properties regarding the widget.
Eg.
public class WidgetModel
{
public string ControllerName { get; set; }
public string HeaderAction { get; set; }
public string BodyAction { get; set; }
public string FooterAction { get; set; }
}
And you have a Widget View like
<div class="widget">
<div class="hd-widget">
<%: Html.RenderAction(Model.HeaderAction, Model.ControllerName) %>
</div>
<div class="bd-widget">
<%: Html.RenderAction(Model.BodyAction, Model.ControllerName) %>
</div>
<div class="ft-widget">
<%: Html.RenderAction(Model.FooterAction, Model.ControllerName) %>
</div>
</div>
Of course you could add more to it if you wanted.
I assume that your widgets will be stored in a DB.
I guess you'll need tables for Paths, PageSettings, Widgets, WidgetInstances, MasterPages, MasterPageZones and Zones.
I think that each widget will need to make a call in to RenderAction and this action will be stored in the DB against the widget. The widget header and footer can probably just be retrieved from the DB.
When I did this I spent quite a bit of time creating the dynamic editor for each of the widgets, the state of each widget is serialized in json, the only issue I'm still working on is dynamic validation of the widget editor.
Page Layouts are handled by a custom personalization class, storing the masterpage, path, userId etc.
It's quite an involved solution using JQuery sortable but really quick and SEO friendly.