I migrating a Vaadin 8 project to Vaadin 14 and i try to show HTML in a grid column.
I figured out, that i have to use a TemplateRenderer, but how can i use it?
Here is the code from Vaadin 8:
grid.addColumn(e -> {
return ((Data) e).getValues()[index];
}).setCaption(myCaption).setRenderer(new HtmlRenderer());
In Vaadin 14 i did this:
gird.addColumn(e -> {
return TemplateRenderer.<Data>of((String) e.getValues()[index])
}).setHeader(myCaption);
e.getValues()[index] includes HTML, for example: <FONT SIZE = 4 COLOR = BLACK> ⚫</FONT>
In Vaadin 14 it always returns com.vaadin.flow.data.renderer.
Before we get to how to use a TemplateRenderer with Grid, I first need to point out that what you're trying to do is potentially dangerous because of the way it can lead to XSS vulnerabilities if the HTML strings that you want to show may be supplied by application users.
Using the Html component is indeed one potential solution to this problem, but it causes some overhead because there will be one component instance in memory for each row in the grid. There's also the same problem with potentially causing XSS vulnerabilities.
The first thing to notice with TemplateRenderer is that the renderer needs to be supplied directly as a parameter to addColumn. Wrapping it in a lambda will instead use that lambda as a value provider, which means that the toString() value of the renderer instance will be used with the default plain text renderer.
All rows should use the same renderer instance, configured with the same template string. The trick is that you can pass the data to show as a per-row property that the template will render for you. The last piece of the puzzle is that the template syntax tries to protect you against accidental XSS vulnerabilities, so you need to use a slightly contrived syntax to actually make it render the data as HTML.
Putting everything together, and also using JSoup to remove any dangerous stuff from your HTML strings, the working solution looks like this:
grid.addColumn(TemplateRenderer
.<Data> of("<div inner-h-t-m-l='[[item.html]]'></div>")
.withProperty("html", e -> {
String unsafeHtml = e.getValues()[index];
String safeHtml = Jsoup.clean(unsafeHtml, Whitelist.basic());
return safeHtml;
})).setHeader(myCaption);
I found a solution.
Instead of using the TemplateRenderer I used a ComponentRenderer.
The migration documentation recomented to use a TempleteRenderer or an ComponentRenderer instead of the htmlRenderer.
https://vaadin.com/docs/v14/flow/migration/8-migration-example.html#step-4-product-grid
Here is the code that worked for me:
grid.addColumn(new ComponentRenderer<>(e -> {
String value = (String) e.getValues()[index];
return new Html(value);
})).setHeader(String.valueOf(col + 1));
Comparing your attempts with TemplateRenderer and the documentation, I would assume it will have to look like this:
grid.addColumn(e ->
TemplateRenderer.<Data>of("[[item.customValue]]")
.withProperty("customValue", (String) e.getValues()[index])
).setHeader(myCaption);
Related
In my web application I'm trying to prevent users from inserting JavaScript in the freeText parameter when they're running a search.
To do this, I've written code in the header Velocity file to check whether the query string contains a parameter called freeText, and if so, use the replace method to replace the characters within the parameter value. However, when you load the page, it still displays the original query string - I'm unsure on how to replace the original query string with my new one which has the replaced characters.
This is my code:
#set($freeTextParameter = "$request.getParameter('freeText')")
freeTextParameter: $freeTextParameter
#if($freeTextParameter)
##Do the replacement:
#set($replacedQueryString = "$freeTextParameter.replace('confirm','replaced')")
replacedQueryString after doing the replace: $replacedQueryString
The query string now: $request.getQueryString()
The freeText parameter now: $request.getParameter('freeText')
#end
In the code above, the replacedQueryString variable has changed as expected (ie the replacement has been carried out as expected), but the $request.getQueryString() and $request.getParameter('freeText') are still the same as before, as if the replacement had never happened.
Seeing as there is a request.getParameter method which works fine for getting the parameters, I assumed there would be a request.setParameter method to do the same thing in reverse, but there isn't.
The Java String is an immutable object, which means that the replace() method will return an altered string, without changing the original one.
Since the parameters map given by the HttpServletRequest object cannot be modified, this approach doesn't work well if your templates rely on $request.getParameter('freeText').
Instead, if you rely on VelocityTools, then you can rather rely on $params.freeText in your templates. Then, you can tune your WEB-INF/tools.xml file to make this parameters map alterable:
<?xml version="1.0">
<tools>
<toolbox scope="request">
<tool key="params" readOnly="false"/>
...
</toolbox>
...
</tools>
(Version 2.0+ of the tools is required).
Then, in your header, you can do:
#set($params.freeText = params.freeText.replace('confirm','replaced'))
I managed to fix the issue myself - it turned out that there was another file (which gets called on every page) in which the $!request.getParameter('freeText')" variable is used. I have updated that file so that it uses the new $!replacedQueryString variable (ie the one with the JavaScript stripped out) instead of the existing "$!request.getParameter('freeText')" variable. This now prevents the JavaScript from being executed on every page.
So, this is the final working code in the header Velocity file:
#set($freeTextParameter = "$!m.request.httpRequest.getParameter('freeText')")
#if($freeTextParameter)
#set($replacedQueryString = "$freeTextParameter.replace('confirm','').replace('<','').replace('>','').replace('(','').replace(')','').replace(';','').replace('/','').replace('\"','').replace('&','').replace('+','').replace('script','').replace('prompt','').replace('*','').replace('.','')")
#end
I should have asked this question way earlier but now I am really tired of dodging around this problem:
I have a normal datatable like
<p:dataTable id="dt1" var="tVar" value="#{mrBean.queriedElements}" filteredValue="#{mrBean.filteredElements}" ...
Now in addition to the primefaces filters, I made my own panelGrid in which you can apply filters to the data base which work before any PF action.
The following lists exist: queriedElements which holds all the data that is returned after my personal filter applied and filteredElements which is needed for primefaces datatable filtering. In addition, I am not exactly sure whether I need an element list that represents all the data from the database. If no personal filter is applied, queriedElements = allElements.
The datatable displays a lot of information on the objects contained and you can change these objects via a dialog. I want the following:
When saved, update all changes made to the selectedElement
When cancelled, revert all changes in the datatable (I dont use a temporary object that is edited but the very object from the list)
When closing the dialog, remember all filters and paginator position
What is the best practise to do so and how can I avoid redundant code for queriedElements and filteredElements (in case I must iterate through it to change it explicitly in addition to database merges)? I found the first attribute for pagination, but I'm not really sure how to use it properly combined with my other requirements. My main problem is that the datatable almost never displays the right values if I don't refetch from database.
PF 4.0
I don't know why PF 4.0 doesn't do this by itself, but something that worked for me can be found at http://www.brainhemorage.com/?p=258
Although I had to replaced the lines
ValueExpression filterBy = column.getValueExpression("filterBy");
String filterField = null;
if (filterBy != null)
filterField = table.resolveStaticField(filterBy);
String filterId = column.getContainerClientId(context) + separator + "filter";
String filterValue = params.containsKey(filterId) && !table.isReset() ? params.get(filterId) :
table.getFilters().get(filterField); // <-- and here, was ""
String filterStyleClass = column.getFilterStyleClass();
by
String filterId = column.getContainerClientId(context) + separator + "filter";
String filterValue = params.containsKey(filterId) && !table.isReset() ? params.get(filterId) :
table.getFilters().get(column.getFilterBy());
because getValueExpression always returned null.
Although this doesn't answer my question about the BP, this will surely help others with the filteredValue problem.
Was wondering why the constraints created for a form are not included in the input tag directly when created through the form helper?
Explanation (using Play 2.1):
Model:
public class Account {
#MaxLength(5)
private String id = "";
...
...
view:
#form(action = routes.Application.addAccount()) {
#inputText(accountForm("id"), '_label -> "Enter your id:")
}
renders automatically in html as:
Enter your id:
Maximum length: 5
Should it not render like this (actually constraining the form text field):
Enter your id:
Maximum length: 5
How can I get code that will automatically include constraints such as these in the form? It's just that I do not really think it is a good idea to have a maxlength defined in the form model and a separate one defined in the view.
Thanks
If I've understood you correctly, it sounds like you're looking to implement one of these features:
Highlight an input text field that is overlength before form submission
Clipping text in an input field so that it does not go overlength
Play's HTML templating engine doesn't natively provide this kind of client-side instant form validation. This functionality needs to be implemented via JavaScript, and JavaScript generation is not really a concern for Play.
If you want to progressively enhance your form and provide client-side validation, you'll have to write the JavaScript yourself. Of course there are libraries that you can use to help you with this task. For example, if you are already using jQuery you can use its validation plugin.
As you've mentioned in your question, it would be better to have a maximum length limit declared in one place only, rather than duplicated in your client-side JavaScript code and your server-side Java code. As a suggestion, you could keep the limit declared in Java code, but introduce a new action in your controller tier that returns a JSON response containing this limit. This action could then be called via AJAX when loading your form page.
EDIT
Didn't know about the maxlength attribute, thanks Saad. If you feed in your maximum length limit as an input parameter to your template, you can populate an input element's maxlength attribute as follows:
#(accountForm: Form[Account], maxLength: Int)
...
#form(action = routes.Application.addAccount()) {
...
#inputText(
field = accountForm("id"),
args = '_label -> "Enter your id:", 'maxlength -> maxLength
)
...
}
...
There may be a more elegant way to pass maxLength into your HTML template (e.g use the HTTP context map, or have it as a public field on your Account form object). The above code snippet just demonstrates how to correctly generate the input text field once you can access it in the template.
I want to add an ImageCell in a CellTable. my code is the following:
Column<Message, String> myColumn = new Column<Message, String>(new ImageCell()) {
#Override
public String getValue(Message details) {
Image image = new Image (ClientResources.of().image1());
return image.getUrl();
}
};
It shows the following warnning when I run it:
[WARN] [adminportal] - Template with variable in URL attribute context: The template code generator cannot guarantee HTML-safety of the template -- please inspect manually or use SafeUri to specify arguments in a URL attribute context
My question is that how I can return the url as string value safely. I saw an example about using imagecell before and the return value is string. I cannot find it now. Could anyone tell me how to fix this.
Note: I just want to know how to fix this if I want to use imagecell in celltable and return value is string. I know how to use imageresourcecell to achieve the same goal. Also, I know I can change the type of getValue() from string to safeHtml to achieve the goal. But i am really wondering how to achieve this by using imagecell and string type of getValue() because I saw an example about this before and I tried it successfully. Just cannot remember what I did wrong here.
There's no way to fix this (remove the warning): using a String as part of a template is inherently unsafe, and GWT warns you about it. If you're absolutely certain of the safety of your values, then you can safely ignore the warnings, but they'll still be emitted.
The only way to not have those warnings is to use a SafeUri, i.e. use the SafeImagecell. It's then up to you to guarantee the safety of your URL, depending on the method you use to construct the SafeUri (have a look at UriUtils)
Wicket has a flexible internationalisation system that supports parameterising UI messages in many ways. There are examples e.g. in StringResourceModel javadocs, such as this:
WeatherStation ws = new WeatherStation();
add(new Label("weatherMessage", new StringResourceModel(
"weather.${currentStatus}", this, new Model<String>(ws)));
But I want something really simple, and couldn't find a good example of that.
Consider this kind of UI message in a .properties file:
msg=Value is {0}
Specifically, I wouldn't want to create a model object (with getters for the values to be replaced; like WeatherStation in the above example) only for this purpose. That's just overkill if I already have the values in local variables, and there is otherwise no need for such object.
Here's a stupid "brute force" way to replace the {0} with the right value:
String value = ... // contains the dynamic value to use
add(new Label("message", getString("msg").replaceAll("\\{0\\}", value)));
Is there a clean, more Wicket-y way to do this (that isn't awfully much longer than the above)?
Take a look at Example 4 in the StringResourceModel javadoc - you can pass a null model and explicit parameters:
add(new Label("message",
new StringResourceModel(
"msg", this, null, value)));
msg=Value is {0}
I think the most consistent WICKETY way could be accomplished by improving Jonik's answer with MessageFormat:
.properties:
msg=Saving record {0} with value {1}
.java:
add(new Label("label", MessageFormat.format(getString("msg"),obj1,obj2)));
//or
info(MessageFormat.format(getString("msg"),obj1,obj2));
Why I like it:
Clean, simple solution
Uses plain Java and nothing else
You can replace as many values as you want
Work with labels, info(), validation, etc.
It's not completely wickety but it is consistent with wicket so you may reuse these properties with StringResourceModel.
Notes:
if you want to use Models you simply need to create a simple model that override toString function of the model like this:
abstract class MyModel extends AbstractReadOnlyModel{
#Override
public String toString()
{
if(getObject()==null)return "";
return getObject().toString();
}
}
and pass it as MessageFormat argument.
I don't know why Wicket does not support Model in feedback message. but if it was supported there was no reason to use these solutions and you could use StringResourceModel everywhere.
There's a way, which although still involves creating a model, doesn't requires a bean with a getter.
given this message in a properties file:
msg=${} persons
Here's how to replace the placeholder with a value, be it a local variable, a field or a literal:
add(new Label("label", new StringResourceModel("msg", new Model<Serializable>(5))));
When faced with something like described in the question, I would now use:
.properties:
msg=Saving record %s with value %d
Java:
add(new Label("label", String.format(getString("msg"), record, value)));
Why I like it:
Clean, simple solution
Uses plain Java and nothing else
You can replace as many values as you want (unlike with the ${} trick). Edit: well, if you actually need to support many languages where the replaced values might be in different order, String.format() is no good. Instead, using MessageFormat is a similar approach that properly supports this.
Disclaimer: this is "too obvious", but it's simpler than the other solutions (and definitely nicer than my original replaceAll() hack). I originally sought for a "Wicket-y" way, while this kinda bypasses Wicket—then again, who cares? :-)
In case you have a Model in your Component which holds an object with values you want to access from your placeholders as substitutions, you can write:
new StringResourceModel("salutation.text", getModel());
Let's imagine getModel()'s return type is IModel<User> and User contains fields like firstName and lastName. In this case you can easily access firstName and lastName fields inside your property string:
salutation.text=Hej ${firstName} ${lastName}, have a nice day!
Further information you can find here: https://ci.apache.org/projects/wicket/apidocs/8.x/org/apache/wicket/model/StringResourceModel.html#StringResourceModel-java.lang.String-org.apache.wicket.model.IModel-
Creating a Model for your Label really is The Wicket Way. That said, you can make it easy on yourself with the occasional utility function. Here's one I use:
/**
* Creates a resource-based label with fixed arguments that will never change. Arguments are wrapped inside of a
* ConvertingModel to provide for automatic conversion and translation, if applicable.
*
* #param The component id
* #param resourceKey The StringResourceModel resource key to use
* #param component The component from which the resourceKey should be resolved
* #param args The values to use for StringResourceModel property substitutions ({0}, {1}, ...).
* #return the new static label
*/
public static Label staticResourceLabel(String id, String resourceKey, Component component, Serializable... args) {
#SuppressWarnings("unchecked")
ConvertingModel<Serializable>[] models = new ConvertingModel[args.length];
for ( int i = 0; i < args.length; i++ ) {
models[i] = new ConvertingModel<Serializable>( new Model<Serializable>( args[i] ), component );
}
return new CustomLabel( id, new StringResourceModel( resourceKey, component, null, models ) );
}
Details I'm glossing over here are:
I've created my own ConvertingModel which will automatically convert objects to their String representation based on the IConverters available to the given component
I've created my own CustomLabel that applies custom label text post-processing (as detailed in this answer)
With a custom IConverter for, say, a Temperature object, you could have something like:
Properties key:
temperature=The current temperature is ${0}.
Page.java code:
// Simpler version of method where wicket:id and resourceKey are the same
add( staticResourceLabel( "temperature", new Temperature(5, CELSIUS) ) );
Page.html:
<span wicket:id='temperature'>The current temperature is 5 degrees Celsius.</span>
The downside to this approach is that you no longer have direct access to the Label class, you can't subclass it to override isVisible() or things like that. But for my purposes it works 99% of the time.