I have an object in session for example of a department and this department have children.I got the list of its children, now i want to add this department object in this list.This is very simple at server side but is it possible to do this in thymeleaf.
Yes it is possible to do it in Thymeleaf since it's using Object-Graph Navigation Language for evaluating expression values - content of ${...} block. You can access public methods normally on your model object so calling boolean List#add(E e) method works.
But as others mentioned it's NOT really good idea and you should redesign your application to fill your model in controller. On the other side if you need to make some nasty hack or just playing around see example below. It's using th:text to evaluate adding to list then th:remove to clean tag and leave no trace.
Department Model class:
public class Department {
private String name;
public Department(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Data in model (eg. session)
List<Department> children = new ArrayList<>();
children.add(new Department("Department 1"));
children.add(new Department("Department 2"));
// In model as *department* variable
Department department = new Department("Main Department");
department.setChildren(children);
Thymeleaf template:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<h1>Before</h1>
<ul>
<li th:each="child : ${department.children}" th:text="${child.name}"></li>
</ul>
<!--/* Here comes a nasty trick! */-->
<div th:text="${department.children.add(department)}" th:remove="all"></div>
<h1>Result</h1>
<ul>
<li th:each="child : ${department.children}" th:text="${child.name}"></li>
</ul>
</body>
</html>
Before
Department 1
Department 2
Result
Department 1
Department 2
Main Department
P.S. Spring Expression Language is used with Spring integration but for this example it makes no difference.
This would be like setting a value in a getXXXX method before returning a value. It doesn't fit the patter. Do your data manipulation in the Controller or the Controller to delegate to something else, but not in the view.
Related
My main view uses
#model IEnumerable<Ortund.Models.Reward>
I'm trying to allow the user to do several things on this view without having to navigate away.
Specifically, I want the user to be able to do the following:
View rewards he/she has already claimed (rewards that this user is eligible to redeem)
Claim a new receipt and in so doing, add the reward associated with that receipt to his/her current rewards
Redeem part of the rewards that he/she is eligible for, or all of them at once
I'm using partial views to achieve this as I can set a new model for each partial.
It looks something like this:
/Home/Index
if (Request.Cookies["Ortund"] == null)
{
// render a login form
}
else
{
<p>#String.Format("Welcome, {0}!", Convert.ToString(Request.Cookies["Ortund"]["username"])) <a id="claim-link">Claim New</a> | <a id="redeem-link">Redeem</a></p>
#Html.Partial("_RewardsView")
<!-- Render the new claim and redemption views as well -->
<div class="claim-new">
#Html.Partial("_ClaimsView")
</div>
<div class="redemption">
#Html.Partial("_RedemptionView")
</div>
_RewardsView
#model IEnumerable<Ortund.Models.Reward>
....
_ClaimsView
#model Ortund.Models.Receipt
....
_RedemptionView
#model IEnumerable<Ortund.Models.Reward>
....
I understand that view models are the preferred approach, but as I haven't yet worked out how to correctly use one, I'm going with this approach.
I've done this on another project, but this time I'm getting an error saying that the dictionary time that the view requires is different to the one being supplied (in this specific instance, we're getting confusion between Receipts and Rewards).
I'm not exactly sure what to do about this except to build the forms manually with no associations to the models, but rather to post to the correct controller...
What if you use one model, but build like this:
public class MainModel{
public fakeOneModel fakeModelOneView{get; set;}
public fakeTwomodel fakeModelTwoView{get; set;}
public fakeThreemodel fakeModelThreeView{get; set;}
}
public class fakeOneModel{
public string objectA1 {get; set;}
public string objectA2 {get; set;}
public string objectA3 {get; set;}
}
public class fakeTwoModel{
public string objectB1 {get; set;}
public string objectB2 {get; set;}
public string objectB3 {get; set;}
}
public class fakeThreeModel{
public string objectC1 {get; set;}
public string objectC2 {get; set;}
public string objectC3 {get; set;}
}
Then from your views, you can access all classes from one models like:
#Html.LabelFor(m=>m.fakeModelOneView.objectA1 )
#Html.TextBoxFor(m=>m.fakeModelOneView.objectA1 )
#Html.LabelFor(m=>m.fakeModelTwoView.objectB1 )
#Html.TextBoxFor(m=>m.fakeModelTwoView.objectB1 )
#Html.LabelFor(m=>m.fakeModelThreeView.objectC1 )
#Html.TextBoxFor(m=>m.fakeModelThreeView.objectC1 )
By default, a partial view rendered by calling #Html.Partial("PartialViewName") takes the view model of the parent view.
The model for your main page should include the models that you'll pass on to the partial views:
Model:
public class IndexModel
{
public Ortund.Models.Receipt Receipt { get; set; }
public IEnumerable<Ortund.Models.Reward> ClaimedRewards { get; set; }
public IEnumerable<Ortund.Models.Reward> EligibleRewards { get; set; }
}
View:
When you call the partial view, specify the model you'll pass to it, such as
#model IndexModel
#Html.Partial("_RewardsView", Model.ClaimedRewards)
#Html.Partial("_ClaimsView", Model.Receipt)
#Html.Partial("_RedemptionView", Model.EligibleRewards)
Having multiple forms on the single page is another issue. See
Multiple Forms in same page ASP.net MVC
So, in short: use a viewmodel?
Yes, essentially. There's so many times that view models will simply be required to achieve what you need that you might as well just learn to use them.
However, in this one circumstance, you can hold out just a bit longer as you can also achieve what you're looking for using child actions. Essentially, that would look something like this:
Controller
[ChildActionOnly]
public ActionResult Rewards()
{
// fetch rewards
return PartialView("_Rewards", rewards)
}
View
#Html.Action("Rewards")
If you want to use ajax to update the Views, then you can try MultiPartials. With the added benefit that they will update any elements you define with Ids, this means you can update multiple divs with data and not have to worry about managing complicated view Models.
#Ajax.ActionLink("ActionLink", "ActionLinkClick", new AjaxOptions { OnSuccess = "MultipartialUpdate" })
public ActionResult ActionLinkClick()
{
MultipartialResult result = new MultipartialResult(this);
result.AddView("_Div1", "Div1", new Div1Model("ActionLink clicked"));
result.AddView("_Div2", "Div2", new Div2Model("ActionLink clicked"));
result.AddContent("ActionLink", "LastClickedSpan");
result.AddScript("alert ('ActionLink clicked');");
return result;
}
Then in view
#using(Ajax.BeginForm("FormSubmit", new AjaxOptions { OnSuccess = "MultipartialUpdate" }))
{
<input type="submit" value="Submit">
}
This code snippet is from the above mentioned link, and you can create as many partials as possible without having to create complicated code to manage all of them.
In my _layout.cshtml page, I've got some elements that need to be hidden on some pages. I know the pages on which we won't display some parts. For a single page, I could just do this:
#if (ViewContext.RouteData.Values["action"].ToString() != "LogIn") {
<div> .... <div>
}
But that gets messy and long with multiple pages. Someplace, ideally not in the _Layout page, I could build a list of actions, and if the current action is any of them, set a boolean variable (ShowStuff) to false. Then just do this on _Layout:
#if (ShowStuff== true) {
<div> .... <div>
}
I'm just not sure where would be the best-practice way to examine that list of actions and set the boolean. Can the _Layout page have it's own model and controller like a normal view?
Similarly to MikeSW answer, I'd use an action filter, but I would populate ViewData with a specific ViewModel. When you want to display it simply DisplayFor the value, if it's populated the template is used by whatever type the model is, if it's null nothing is displayed. (examples below from memory, may not be exactly correct.)
public BlahModelAttribute : ActionFilterAttribute
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
BlahModel model = Db.GetModel();
filterContext.Controller.ViewData.Set(model);
}
}
ViewData extensions:
public static ViewDataExtensions
{
private static string GetName<T>()
: where T : class
{
return typeof(T).FullName;
}
public static void Set<T>(this ViewDataDictionary viewData, T value)
: where T : class
{
var name = GetName<T>();
viewData[name] = value;
}
public static T Get<T>(this ViewDataDictionary viewData)
: where T : class
{
var name = GetName<T>();
return viewData[name] as T;
}
}
In your view:
#{var blahModel = ViewData.Get<BlahModel>() }
#Html.DisplayFor(m => blahModel)
If devs would stop looking for the 'best way' for every problem they have, that would be great. No best way here, just opinionated solutions. Here's mine: You can create an action filter [ShowNav] and decorate any controller/action you need. That filter will put a boolean into HttpContext.Items . Create then a HtmlHelper which checks for the boolean. Then in _layout, if (Html.CanShowNavig()) { <nav> } . That's the easiest solution that comes to my mind.
When I display a session value in a master page (<%: Session["companyname"].ToString() %>) the following information is displayed on the page { CompanyName = TestCompany}. How do I get just the value?
Thanks for your help!
If you can show the code where the value is stored in the session, it's more likely that I could help. I would suggest, though, that you might want to reconsider using the value from the session directly in your view. It would be better, in my opinion, to have a base view model that all of your view models derive from that has the CompanyName property, and any other common properties required by your master page, on it. Your master page, then, could be strongly-typed to the base view model and you could use the values from the model. I've used this pattern with good success on a couple of projects. Couple it with a base controller where the common properties are populated for view results in OnActionExecuted(), it can be very effective in both reducing code duplication and the use of magic strings in your views.
Model:
public abstract class CommonViewModel
{
public string CompanyName { get; set; }
public string UserDisplayName { get; set; }
...
}
Controller:
public abstract class BaseController
{
public override void OnActionExecuted( ActionExecutedContext filterContext )
{
if (filterContext.Result is ViewResult)
{
var model = filterContext.ViewData.Model as CommonViewModel;
if (model != null)
{
model.CompanyName = Session["CompanyName"] as string;
model.UserDisplayName = Session["UserDisplayName"] as string;
}
}
}
}
Master Page:
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<Foo.CommonViewModel>" %>
<!-- ... -->
<div>
<%: Model.CompanyName %>
</div>
<!-- ... -->
<div>
<%: Model.UserDisplayName %>
</div>
I have a shared page in my ASP.NET MVC app that can be accessed from several different pages in my app. If I want the user to return to their original page after they have done their business on the shared page, what's the best way to figure out where the user was, and tell the app to go there?
Note that I'm currently doing this for the Cancel button on the shared page by telling onclick to use the page history:
<input type="button" value="Cancel" onclick="if (history.length == 0) { window.location='<%=Url.Action("Index", "Home") %>' } else { history.back() }" />
but this won't work for Save if the shared page updates data displayed on the original page.
try using:
System.Web.HttpContext.Current.Request.UrlReferrer
http://msdn.microsoft.com/en-us/library/system.web.httprequest.urlreferrer.aspx
Two ways I can think of are to store the url of the calling page in a hidden input element or as a argument in the current page's address. Both of these will survive refreshes or several steps of navigation, you just have to make sure that the variables are passed on to the next page.
Issues like this are why I'm more and more considering using a BaseViewModel class to inherit all of my view models from to store information that may be useful to have on any given page. I've been trying to come up with a complete class definition lately, but for these purposes it would probably be nice to have the follwing properties (posted part of this in an answer to a different question, I'll find a link in a bit):
public class BaseModel
{
public string PageTitle { get; set; }
public string PageDescription { get; set; }
public string PageKeywords { get; set; } //maybe use a List<string> or string[] here
public string ReturnPage { get; set; }
//TBD: any other useful HTML page elements
}
Then I could create a view model that inherits from this:
public class RandomViewModel : BaseViewModel
{
RandomViewModel()
{
//set page properties
}
}
Then in a service layer (preferably) or in a controller (may have to do it there, haven't tried this so can't be sure) you would have access to the ReturnPage property when constructing your model (in this example I'll assume you're doing this in the action):
public ActionResult RandomAction()
{
RandomViewModel model = _serviceLayer.GetRandomViewModel();
model.ReturnPage = this.HttpContext.Request.UrlReferrer;
return View(model);
}
Then in the view you would be able to do this:
<input type="button" value="Cancel" onclick="if (history.length == 0) { window.location='<%= Model.ReturnPage %>' } else { history.back() }" />
This is all just kind of brainstorming, but I think it would work. The only big issue I'm not sure of is where you would want to set the referrer value. I would also try and get that onclick event out of the element and set it in the header if you can.
Hope this helps.
There has been many discussion on ASP.NET MVC and Codebehind-files, mostly where it has been pointed out that these Codebehind-files are evil.
So my question is, how do you handle page-specific logic?
What we don't want here is spaghetti-code in the inline-code and we don't want page-specific code scattered throughout helper-classes or on top of the HTML-helper-class.
An example would be:
<% for(int i = 0; i < companyList.Count; i++) { %>
RenderCompanyNameWithRightCapsIfNotEmpty(company, i)
<% } %>
With accompanying codebehind:
private string RenderCompanyNameWithRightCapsIfNotEmpty(string company, index)
{
if (index == 0) {
return string.Format("<div class=\"first\">{0}</div>", company);
}
// Add more conditional code here
// - page specific HTML, like render a certain icon
string divClass = (index % 2 == 0) ? "normal" : "alternate";
return string.Format("<div class=\"{1}\">{0}</div>", company, divClass);
}
This will only be used on one page and is most likely subject to change.
Update: A couple approaches I thought about where these:
1) Inline codebehind on page - with simple methods that returns strings.
<script runat="server">
private string RenderCompanyHtml(string companyName) ...
<script>
2) Putting a method which returns a string in the Controller. But that would be putting View-logic into the Controller.
public class SomeController : Controller
{
[NonAction]
private static string RenderCompanyHtml(string companyName) ...
public ActionResult Index() ...
}
You should put that code in the controlleraction where you prepare the viewdata.
I usually make a region "helper methods" in my controller class with a few [NonAction] methods to keep things clean.
So my (simplified) controller would look like this:
public class SomeController : Controller
{
#region Helper methods
[NonAction]
private static string CompanyNameWithRightCapsIfNotEmpty(string company)
{
if (string.IsNullOrEmpty(company)) {
return company;
}
return UpperCaseSpecificWords(company);
}
#endregion
public ActionResult Companies()
{
var companies = GetCompanies();
var companyNames = companies.Select(c => CompanyNameWithRightCapsIfNotEmpty(c.Name));
ViewData["companyNames"] = companyNames;
return view();
}
}
Helper methods are one good way of handling page specific code, but I think it is a;ways preferable to get your model to show the data you need.
If you're going to go for the helper option, you would be better served by making the operation it perfroms a bit less page specific. If your method RenderCompanyNameWithRightCapsIfNotEmpty has to be so specific it would be better if your model provided it. One way would be to have the model provide a list with the text already formatted, and expose it as a public property (say an IEnumerable of formatted company names).
Use Html Helpers.
Like so create the helper methods in a static class:
public static string Label(this HtmlHelper helper, string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}
.. then use in your view:
<span><% =Html.Label("FinishDateTime.LocalDatetime", "Finish Time:")%><br />
You could create a helper method called maybe RenderCompanyName(string[] companies) that checked for nulls, did the caps manipulation and rendered the html in between - all in the same helper if you like.
Also: controller action methods should be light - ie. only getting the data and returning views. You should delegate things like manipulation of data for presentation to views and Html helpers.
EDIT: Here is a helper that you might be after:
This helper renders an IList<> to html in the form of an unordered list <ul>...</ul>. The useful thing about it is that it gives you control over how the list is rendered thru css AND it allows you to render additional html/content for each item. Take a look - this is the helper:
public static string UnorderedList<TItem>(this HtmlHelper helper,
IList<TItem> items, Func<TItem, string> renderItemHtml,
string ulID, string ulClass, string liClass)
{
StringBuilder sb = new StringBuilder();
// header
if (!ulID.IsNullOrTrimEmpty()) sb.AppendFormat("<ul id='{0}'", helper.Encode(ulID.Trim()));
else sb.AppendFormat("<ul");
if (!ulClass.IsNullOrTrimEmpty()) sb.AppendFormat(" class='{0}'>", helper.Encode(ulClass.Trim()));
else sb.AppendFormat(">");
// items
foreach (TItem i in items)
{
if (!liClass.IsNullOrTrimEmpty())
sb.AppendFormat("<li class='{0}'>{1}</li>", helper.Encode(liClass.Trim()),
renderItemHtml(i));
else
sb.AppendFormat("<li>{0}</li>", renderItemHtml(i));
}
// footer
sb.AppendFormat("</ul>");
return sb.ToString();
}
..using it is easy. here is a simple example to render a list of tags:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<Tag>(Model.Tags.Tags,tag=>
{
return tag.Name;
},null,null,null) %>
</div>
..you can see in my usage example that i have chosen not to specify any css or id attribute and i simply return the name of the Tag item thru the use of the anonymous delegate. Anonymous delegates are way easy to use.. in your case maybe something like this would work:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<string>(ViewData["companies"],company=>
{
if (someCondition) return company.ToUpper();
else return company;
},null,null,null) %>
</div>
.. ViewData["companies"] is an IList<string> for simplicity.