In a variant of this question, I want to render a multiline string into an HTML table using Thymeleaf.
That is, how do I convert a string like
Cronut fixie tousled migas.
Whatever neutra offal fanny pack, photo booth kitsch bespoke hammock swag.
Keffiyeh yuccie meditation mustache cornhole paleo.
into
<table class="table">
<tr>
<td>Cronut fixie tousled migas.</td>
</tr>
<tr>
<td>Whatever neutra offal fanny pack, photo booth kitsch bespoke hammock swag.</td>
</tr>
<tr>
<td>Keffiyeh yuccie meditation mustache cornhole paleo.</td>
</tr>
</table>
With the string contained in the model variable model.text, a possible solution (when using the Spring dialect) is this:
<table>
<tr th:each="line : ${#strings.arraySplit(model.text, T(org.apache.commons.lang3.StringUtils).LF)}">
<td th:text="${line}"></td>
</tr>
</table>
The reason for using T(org.apache.commons.lang3.StringUtils).LF instead of just \n is that (as also described in this answer) SpEL escapes the backslash, and then decides to split on 'n'-letters instead of newlines.
Of course, one should also consider the solution of just splitting the string into an array directly in the controller (i.e. using plain Java).
I originally posted this answer on a previous SO question but this one also seems to pop up. I've modified it to output a table instead of just newlines.
It's possible to do this with a custom dialect and attribute processor to handle doing this without a lot of inline SpEl or hacks.
Creating a Custom Attribute Processor
public class NewlineAttrProcessor extends AbstractUnescapedTextChildModifierAttrProcessor
{
public NewlineAttrProcessor()
{
super("nl2br");
}
#Override
protected String getText(Arguments arguments, Element element, String attributeName)
{
final Configuration configuration = arguments.getConfiguration();
final IStandardExpressionParser parser =
StandardExpressions.getExpressionParser(configuration);
final String attributeValue = element.getAttributeValue(attributeName);
final IStandardExpression expression =
parser.parseExpression(configuration, arguments, attributeValue);
final String value = (String)expression.execute(configuration, arguments);
final String[] lines = StringUtils.split(value, "\n");
return "<table><td>" + StringUtils.join(lines, "</td><td>") + "</td></table>";
}
#Override
public int getPrecedence()
{
return 10000;
}
}
You have to extend the AbstractUnescapedTextChildModifierAttrProcessor processor or else you'll get the html entity tags for <table>...</table> and you won't actually get the HTML.
Creating a custom dialect
To implement a custom dialect you need a dialect class like so:
public class MyCustomDialect extends AbstractDialect
{
#Override
public String getPrefix()
{
return "cd";
}
#Override
public Set<IProcessor> getProcessors()
{
final Set<IProcessor> processors = new HashSet<>();
processors.add(new NewlineAttrProcessor());
return processors;
}
}
The getPrefix method return value is what you would use to call any custom processors that you make. For example, Thymeleaf uses th. The custom processor we implemented above this is looking for nl2br so to call it you would use the cd:nl2br attribute instead of th:text.
Registering your new dialect
In your main class, you just need to create a #Bean that will return a new instance of your dialect class.
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
public MyCustomDialect myCustomDialect()
{
return new MyCustomDialect();
}
}
Using your custom processor
Finally, in your template file you would have an HTML tag like this:
<div cd:nl2br="${myObject.myField}">MY MULTILINE FIELD</div>
For a more thorough guide to implementing custom dialects I recommend the Thymeleaf docs:
Extending Thymeleaf in 5 Minutes
Extending Thymeleaf even more in another 5 minutes
Related
This is my view model:
public class TaskViewModel{
public int TaskID{get;set;}
public IEnumerable<TaskExecutor> Executors{get;set;}
}
public class TaskExecutor{
public int ExecutorID{get;set;}
public string LastName{get;set;}
public string FirstName{get;set;}
}
In my view I have done something like this:
<table>
#foreach(var item in Model.Executors)
{
<tr>
<td>item.ExecutorID</td>
<td>#string.Format("{0} {1}",item.FirstName,item.LastName)</td>
</tr>
}
</table>
Now, when loading the view, there won't be any problem, but I might need to edit the table and I want the changes to persist when submitting the form. The only way I can think of is an HtmlHelper extension method that will properly bind an IEnumerable to a table but I have no idea how to do that. I'd be happy to see some code. Or is there any other way to achieve this?
One option could be as follows:
namespace System.Web.Mvc
{
public static class ExecutorsExtensions
{
public static MvcHtmlString Executors(this HtmlHelper helper, List<TaskExecutor> executors)
{
var sb = new StringBuilder();
sb.Append("<table>");
for (var i = 0; i < executors.Count; i++)
{
sb.Append("<tr>");
sb.Append(string.Format("<td><input name=\"Executors[{0}].FirstName\" value=\"{1}\"></td>", i, executors[i].FirstName));
// add other cells here
sb.Append("<tr>");
}
sb.Append("</table>");
return new MvcHtmlString(sb.ToString());
}
}
}
Usage
#Html.Executors(Model.Executors)
Please note you would need to make the Executors a List<TaskExecutor> for the indexing to work properly.
The indexing of the loop and and name variable would keep the model binding happy. You could add further fields where I have commented above.
You could also use Html.TextBox or Html.TextBoxFor to generate the inputs if needed.
Let's say I have the following custom extension to System.Web.Mvc.HtmlHelper<T>:
public static MvcHtmlString WrapInDiv<TModel, TValue>(this HtmlHelper<TModel> html, String text)
{
String htmlRaw = String.Format("<div>{0}</div>", text);
var xDoc = XDocument.Parse(htmlRaw); // For formatting
return new MvcHtmlString(xDoc.ToString());
{
..and I use it in this view snippet:
<span>
#Html.WrapInDiv("sasuage")
</span>
The result would be as follows:
<span>
<div>
Sausage
</div>
</span>
because the XDocument writer obviously don't know that we're already in a section with four characters indentation. Any way I can fix this so that my HTML helpers print perfectly indented multi-line HTML?
Why do you want to keep the indentation proper? Browsers do not care, and it's a lot of work and a very hard problem to resolve, especially when you are doing helpers and do not have the whole context.
The best way, if you really, really need visually formatted outputs, is to do it globally for each page generated with an HTTP Module that intercept answers and maybe one of the .Net port of Html Tidy.
public class HtmlTidyModule: IHttpModule
{
private EventHandler ReleaseRequestStateEvent = null;
public void Dispose()
{
if (ReleaseRequestStateEvent != null)
context.ReleaseRequestState -= ReleaseRequestStateEvent;
}
public void Init(HttpApplication context)
{
ReleaseRequestStateEvent = new EventHandler(event_ReleaseRequestState);
context.ReleaseRequestState += ReleaseRequestStateEvent;
}
void event_ReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
// You need to implement HtmlTidyFilter to handle the response
app.Response.Filter = new HtmlTidyFilter(app.Response.Filter)
}
}
But you must ask yourself, do you really want to do this?? Most integrated HTML viewers/inspectors in browsers does this already (F12 in chrome/FF I believe).
Traditionally, I have built MVC applications using view models with Data Annotations attributes, and I dynamically render the views using editor templates. Everything works great, and it really cuts down on the time it takes me to build new views. My requirements have recently changed. Now, I can't define the view model at design time. The properties that will be rendered on the view are decided at run time based on business rules. Also, the validation rules for those properties may be decided at run time as well. (A field that is not required in my domain model, may be required in my view based on business rules). Also, the set of properties that will be rendered is not known until run time - User A may edit 6 properties from the model, while user B may edit 9 properties.
I am wondering if it is possible to create a model metadata provider that will supply my own metadata from business rules for an untyped view model like a collection of property names and values. Has anyone solved this problem?
I solved a similar problem by creating a more complex model, and using a custom editor template to make the model be rendered to look like a typical editor, but using the dynamic field information:
public class SingleRowFieldAnswerForm
{
/// <summary>
/// The fields answers to display.
/// This is a collection because we ask the MVC to bind parameters to it,
/// and it could cause issues if the underlying objects were being recreated
/// each time it got iterated over.
/// </summary>
public ICollection<IFieldAnswerModel> FieldAnswers { get; set; }
}
public interface IFieldAnswerModel
{
int FieldId { get; set; }
string FieldTitle { get; set; }
bool DisplayAsInput { get; }
bool IsRequired { get; }
bool HideSurroundingHtml { get; }
}
// sample implementation of IFieldAnswerModel
public class TextAreaFieldAnswer : FieldAnswerModelBase<TextAreaDisplayerOptions>
{
public string Answer { get; set; }
}
EditorTemplates/SingleRowFieldAnswerForm.cshtml:
#helper DisplayerOrEditor(IFieldAnswerModel answer)
{
var templateName = "FieldAnswers/" + answer.GetType().Name;
var htmlFieldName = string.Format("Answers[{0}]", answer.FieldId);
if (answer.DisplayAsInput)
{
#Html.EditorFor(m => answer, templateName, htmlFieldName)
// This will display validation messages that apply to the entire answer.
// This typically means that the input got past client-side validation and
// was caught on the server instead.
// Each answer's view must also produce a validation message for
// its individual properties if you want client-side validation to be
// enabled.
#Html.ValidationMessage(htmlFieldName)
}
else
{
#Html.DisplayFor(m => answer, templateName, htmlFieldName)
}
}
<div class="form-section">
<table class="form-table">
<tbody>
#{
foreach (var answer in Model.FieldAnswers)
{
if (answer.HideSurroundingHtml)
{
#DisplayerOrEditor(answer)
}
else
{
var labelClass = answer.IsRequired ? "form-label required" : "form-label";
<tr>
<td class="#labelClass">
#answer.FieldTitle:
</td>
<td class="form-field">
<div>
#DisplayerOrEditor(answer)
</div>
</td>
</tr>
}
}
}
</tbody>
</table>
</div>
So I populate my SingleRowFieldAnswerForm with a series of answer models. Each answer model type has its own editor template, allowing me to customize how different types of dynamic "properties" should be displayed. For example:
// EditorTemplates/FieldAnswers/TextAreaFieldAnswer.cshtml
#model TextAreaFieldAnswer
#{
var htmlAttributes = Html.GetUnobtrusiveValidationAttributes("Answer", ViewData.ModelMetadata);
// add custom classes that you want to apply to your inputs.
htmlAttributes.Add("class", "multi-line input-field");
}
#Html.TextAreaFor(m => m.Answer, Model.Options.Rows, 0, htmlAttributes)
#Html.ValidationMessage("Answer")
The next tricky part is that when you send this information to the server, it doesn't inherently know which type of IFieldAnswerModel to construct, so you can't just bind the SingleRowAnswerForm in your arguments list. Instead, you have to do something like this:
public ActionResult SaveForm(int formId)
{
SingleRowAnswerForm form = GetForm(formId);
foreach (var fieldAnswerModel in form.FieldAnswers.Where(a => a.DisplayAsInput))
{
// Updating this as a dynamic makes sure all the properties are bound regardless
// of the runtime type (since UpdateModel relies on the generic type normally).
this.TryUpdateModel((dynamic) fieldAnswerModel,
string.Format("Answers[{1}]", fieldAnswerModel.FieldId));
}
...
Since you provided MVC with each dynamic "property" value to bind to, it can bind each of the properties on each answer type without any difficulty.
Obviously I've omitted a lot of details, like how to produce the answer models in the first place, but hopefully this puts you on the right track.
You can use The ViewData Property in your ViewModel, View and Controller, it is dynamic, so it can be resolved at runtime.
I'm trying to make a custom template for a basket item list. I need a few different templates, as I have different ways of displaying the item, depending on if it's on the webpage or in a mail. Now my problem is, that when I use the default name it works flawlessly.
#Html.DisplayFor(b => b.Items)
But when I try to add a template name, I get an expection that my templates needs to be of a list type IEnumerable and not BasketItem.
#Html.DisplayFor(i => basket.Items, "CustomerItemBaseList")
Any ideas where my mistake is, or why it's not possible are appreciated. Thanks.
Unfortunately that's a limitation of templated helpers. If you specify a template name for a collection property the template no longer applies automatically for each item of the collection. Possible workaround:
#for (int i = 0; i < Model.Items.Length; i++)
{
#Html.DisplayFor(x => x.Items[i], "CustomerItemBaseList")
}
That's a good idea, Darin. I'm lazy though, so I'd like to take it one step further and make an actual helper that wraps this. I also took out the lambda expression to simplify it for my case, but you can easily add that functionality back in.
public static class DisplayTextListExtension
{
public static MvcHtmlString DisplayForList<TModel>(this HtmlHelper<TModel> html, IEnumerable<string> model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
tempResult.Append(html.DisplayFor(m => item, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
Then the actual usage looks like:
#Html.DisplayForList(Model.Organizations, "infoBtn")
I liked Dan's answer but just adjusted slightly as it can work for any IEnumerable:
using System.Collections;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourProject.Whatever
{
public static class DisplayExtensions
{
public static MvcHtmlString DisplayForIEnumerable<TModel>(this HtmlHelper<TModel> html, IEnumerable model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
var item1 = item;
tempResult.Append(html.DisplayFor(m => item1, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
}
And of course:
#Html.DisplayForIEnumerable(Model.Organizations, "NameOfYourDisplayTemplate")
I suggest another solution useful even more with lists of heterogeneous objects (i.e. BasketItem subclasses), using the additionalViewData parameter of the DisplayFor method, like:
#DisplayFor(b=>b.Items, new { layout="row" })
in this way the helper works fine with IEnumerable<T>, calling for each item (subclass of T) the relative DisplayTemplate, passing it the additionalViewData values in the ViewData dictionary.
The template could so output different code for different layout values.
In the example above the template named View\Shared\DisplayTemplates\BasketItem (or the name of the subclass) should be like this:
#model MyProject.BasketItem // or BasketItem subclass
#{
string layout = ViewData["layout"] as string ?? "default";
switch(layout)
{
case "row":
<div class="row">
...
</div>
break;
// other layouts
...
default: // strongly recommended a default case
<div class="default-view>
...
</div>
break;
}
}
It is strongly recommended to provide always a default code.
I hope this suggestion could help.
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.