Custom attributes added by a custom renderer appears at the wrong HTML elements - html

I had to write a custom renderer for <h:commandLink/> tag to support data-* HTML5 attributes in order to use JSF 2.1 with jQuery mobile framework.
My JSF markup and the output generated by this markup are as follows:
<h:commandLink value="Prev" data-theme="e" data-role="button" data-inline="true" data-mini="true" data-icon="arrow-l"/>
<h:commandLink value="Page 1 of 3" data-theme="e" data-role="button" data-inline="true" data-mini="true"/>
<h:commandLink value="Next" data-theme="e" data-role="button" data-inline="true" data-mini="true" data-icon="arrow-r" data-iconpos="right"/>
It is obvious that my custom renderer properly renders the 2nd and the 3rd <h:commandLink/> tags, but not the 1st one. It seems the data-* attributes belong to the 1st tag is rendered with the immediate parent <div/> tag. This seems a strange (and buggy) behaviour of Mojarra (I use V 2.1.11). Please advice me how to overcome this?
My custom renderer code as follows:
public class MyCommandLinkRenderer extends CommandLinkRenderer {
#Override
public void encodeBegin(FacesContext context, UIComponent component) {
String[] attributes = {"data-theme", "data-role", "data-icon", "data-inline", "data-mini", "data-iconpos"};
ResponseWriter writer = context.getResponseWriter();
try {
for (String attribute : attributes) {
String value = (String) component.getAttributes().get(attribute);
if (value != null) {
writer.writeAttribute(attribute, value, attribute);
System.out.println(attribute + " " + value);
}
}
super.encodeBegin(context, component);
} catch (Exception e) {
}
}
}

You must call super.encodeBegin() before writing the attributes.
Otherwise you're writing the attributes to the previous HTML element, as confirmed by the generated HTML output. The super.encodeBegin() starts the new HTML element.
This is just a bug in your own code, not in Mojarra.
Update: also, overriding the encodeBegin() was not right in first place. You should rather be overriding the LinkRenderer#writeCommonLinkAttributes() method.
#Override
protected void writeCommonLinkAttributes(ResponseWriter writer, UIComponent component) throws IOException {
super.writeCommonLinkAttributes(writer, component);
// ...
}
It does by the way then not matter if you call super before or after your job.

Related

Spring Thymeleaf + Textarea

I created form with one input text and one textarea. The input text works fine, but textarea isn't even displayed:
<div id="news" th:fragment="admin_panel">
<form method="POST" th:action="#{/addNews}" th:object="${news}" id="myform">
Tytuł:
<input type="text" th:field="*{title}"/>
<input type="submit" value="Wstaw"/>
</form>
<textarea name="news_content" rows="20" cols="80" th:field="${news.content}" form="myform">
...
</textarea>
</div>
When I delete the th:field the textarea is displayed and when I use th:value instead of th:field it's displayed too but doesn't save the written text to news.content (news.title is saved ok).
I don't have any idea... I read thymeleaf references but can't find answer, so please help good people!
You have to use the selected object expression *{content} AND place the textarea tag inside the form tag!
In the end its all about the generated name attribute in the resulting form. The name needs to correspond to the propertyAccessor from the selected root object th:object.
The form is processed by spring (without any thymeleaf interception).
The docs about spring integration are really good: http://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html
They state this:
Values for th:field attributes must be selection expressions (*{...}), which makes sense given the fact that they will be evaluated on the form-backing bean and not on the context variables (or model attributes in Spring MVC jargon).
EDIT:
Thanks to the link to the project, the fix was easy:
Thymeleaf 3.0.0.BETA03 had a bug in the textarea processor, moving to 3.0.0.RELEASE fixed this issue
Additionally I have moved the textarea inside the form element.
My textarea input was working fine when i was saving some data through it(in clean state when i was performing a save to DB) but in case of edit form(where my textarea input was supposed to show the prefilled description from the model property book.description) was blank, the reason for that was the th:value attribute, i changed it to the th:field attribute and it started working as expected.
<textarea class="form-control" id="description" rows="5"
name="description"
placeholder="Description" th:field="${book.description}"
required="required"></textarea>
You don't need the form text field. Linking the textarea with the form by the form id is sufficient.
<textarea rows="8" cols="120" name="lines" form="usrform" th:text="${message}"></textarea>
<form method="POST" enctype="multipart/form-data" th:action="#{/}" id="usrform">
<button type="submit" name="action" value="submitlines">Submit</button>
</form>
and the controller:
#RequestMapping(value="/", method=RequestMethod.POST, params="action=submitlines")
public String handleForm(
#RequestParam("lines") String input,
RedirectAttributes redirectAttributes) {
}
About exceptions :
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/eniupage] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring4.processor.SpringTextareaFieldTagProcessor' (template: "templates/fragments" - line 144, col 60)] with root cause
java.lang.StringIndexOutOfBoundsException: String index out of range: 0
In my form, there are text input and textarea as You see. news.title is saved ok, but news.content don't. When I replace for test these parameters (in the text input I use news.content and in the textarea there is th:field = ${news.title}) it works well too. Maybe should I use another expression instead of th:field?
News.java
package eniupage.domain;
public class News
{
private String title;
private String content;
private Date date;
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content)
{
this.content = content;
}
public Date getDate()
{
return date;
}
public void setDate(Date date)
{
this.date = date;
}
}
HomeController.java
package eniupage.web;
#Controller
#RequestMapping( "/" )
public class HomeController
{
#Autowired
AddNewsService addNewsService;
#RequestMapping( method = GET )
public String home( Model model )
{
model.addAttribute( "newses", addNewsService.getNewses() );
return "home";
}
#RequestMapping( value = "/addNews", method = POST )
public String addNews( News news )
{
addNewsService.addNews( news );
return "redirect:/";
}
}
AdminController.java
#Controller
#RequestMapping( "/admin" )
public class AdminController
{
#RequestMapping( method = GET )
public String admin( Model model )
{
model.addAttribute( new News() );
return "admin";
}
}
There isn't any resulting of HTML form, because it isn't even displayed in div. There are only text input and submit button.
Edited html:
<form action="#" method = "POST" th:action="#{/addNews}" th:object = "${news}" id = "myform">
Tytuł: <input type = "text" th:field = "*{title}" />
<input type = "submit" value = "Add" /></br>
<textarea rows = "20" cols = "80" th:field = "*{content}" form = "myform" >... </textarea>
</form>
I'm using thymeleaf 3.0. Maybe this is the reason?
In reference I read :
The th:field attribute behaves differently depending on whether it is attached to an , or tag (and also depending on the specific type of tag).
But I can't find what is this difference between using th:field in input and textarea.

asp.net conditionally disable a tag helper (textarea)

I want to enable or disable a textarea depending on a condition that evalueates from the model, and I am using the textarea tag helper.
In other words, something like this:
<textarea asp-for="Doc" #(Model.MustDisable ? "disabled" : "")></textarea>
But I got the following design-time error: The tag helper 'textarea' must not have C# in element's attribute declaration area.
Then I tried:
<textarea asp-for="Doc" disabled='#(Model.MustDisable ? "disabled" : "")'></textarea>
which did not show any design time error but it renders like this:
Model.MustDisable==true renders disabled='disabled' AND Model.MustDisable==false renders disabled.
So the text area will always be disabled.
Then I tried (removing the 's):
textarea asp-for="Doc" disabled=#(Model.MustDisable ? "disabled" : "")></textarea>
which did not show any design time error but it renders the same as the previous one.
How can I implement this the right way?
It is actually very simple, the disable attribute is already working as you want - you can pass in a boolean value:
<textarea asp-for="Doc" disabled="#Model.MustDisable"></textarea>
if false the disabled attribute is not rendered:
<textarea></textarea>
if true the disabled attribute is set to "disabled":
<textarea disabled="disabled"></textarea>
I was facing the same issue with select tag helper, i tried few things and it worked.
Try this-
<textarea asp-for="Doc" disabled="#(Model.MustDisable ? "disabled" : null)"></textarea>
The textarea tag helper does not have direct support to conditionally render a disabled text area. But you can always extend the TextAreaTagHelper and add this feature.
So create a new class which inherits from the TextAreaTagHelper class.
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("asp-is-disabled")]
public bool IsDisabled { set; get; }
public MyCustomTextArea(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsDisabled)
{
output.Attributes["disabled"] = "disabled";
}
base.Process(context, output);
}
}
In your _ViewImports.cshtml file, using the #addTagHelper directive, specify the assembly where the above class is defined so that our new tag helper is available in other razor views.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Now in your views, you can use it like
#model YourSomeViewModel
<textarea asp-for="Doc" asp-is-disabled="Model.MustDisable"></textarea>
where SomeViewModel has a Doc and MustDisable property.
public class YourSomeViewModel
{
public string Doc { set;get; }
public bool MustDisable { set;get; }
}
I am posting this separately since I don't have enough reputation to add a comment to Shyju's answer.
If you inherit from one of the default tag helpers and then register both the default tag helpers and your custom tag helper in _ViewImports.cshtml, then both tag helpers will be executed for the specified tags.
For the following:
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
...
With the following _ViewImports.cshtml:
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Both MyCustomTextArea and TextAreaTagHelper will be executed for each textarea tag.
I did not notice any problems with the output generated for textareas, but I have run into problems inheriting from other default tag helpers. The solution is to remove the default tag helper in _ViewImports.cshtml.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
#removeTagHelper "Microsoft.AspNet.Mvc.TagHelpers.TextAreaTagHelper, Microsoft.AspNet.Mvc.TagHelpers"

Getting a file from html input field with vaadin

I have to use a HTML snippet to get an image from Android/iOS devices with
<input type="file" accept="image/*" capture="camera" />
which is displayed in a Label:
Label label = new Label("<input type=\"file\" accept=\"image/*\" capture=\"camera\" />");
label.setContentMode(Label.CONTENT_XHTML);
Because I'm using Vaadin on Liferay I'm not sure how to obtain the Image since there is no POST submit
How am I able to get this image?
You need to write custom Vaadin component which extends FormPanel. Thats not hard as it may sounds, just let Vaadin Widget creator generate classes for you and then replace auto-generated GWT component with the following code
public class CameraCaptureWidget extends FormPanel {
public CameraCaptureWidget() {
setEncoding(FormPanel.ENCODING_MULTIPART);
setMethod(FormPanel.METHOD_POST);
FileUpload f = new FileUpload();
this.add(f);
f.getElement().setAttribute("accept", "image/*");
f.getElement().setAttribute("capture", "camera");
f.addChangeHandler(new ChangeHandler(){
#Override
public void onChange(ChangeEvent event)
{
submit();
}
});
}
}
You can also add button instead of adding ChangeHandler on FileUpload itself.
After you are done with that you need to handle submit() event in the Connector. Example:
public CameraCaptureConnector() {
getWidget().addSubmitHandler(new SubmitHandler()
{
#Override
public void onSubmit(SubmitEvent event)
{
Window.alert("Hello world");
}
});
}
You may also need to remove some unnecessary auto-generated methods.
This solution generates this HTML code in DOM:
<input capture="camera" accept="image/*" class="gwt-FileUpload" type="file">
Should you encounter trouble, please make sure you have read https://vaadin.com/wiki/-/wiki/Main/Creating+a+simple+component

JSF 2.2 HTML5 Friendly Markup for number fields

I'm learning JSF with HTML5 friendly markup. I want to receive a number in a text field:
<form jsf:id="form_item">
<label for="nombre">Nombre:</label><input type="text" jsf:id="nombre" value="#{backend.item.nombre}"/>
<label for="edad">Edad:</label> <input type="text" jsf:id="edad" value="#{backend.item.edad}"/>
<input type="button" jsf:action="#{backend.addItem}" value="Agregar"/>;
</form>
Backend bean:
#Named(value = "backend")
#ViewScoped
public class Backend implements Serializable{
private Item item;
private List<Item> items;
/**
* Creates a new instance of Backend
*/
public Backend() {
this.items = new ArrayList<>();
this.item = new Item();
}
public void addItem(){
this.getItems().add(item);
}
//Setters and getters
Item bean:
public class Item {
private String nombre;
private Integer edad;
public Item(){
this.nombre="";
this.edad=0;
}
//Setters and getters
This code results in java.lang.Integer cannot be cast to java.lang.String.
Replacing the input text by h:inputText works:
<h:inputText id="edad" value="#{backend.item.edad}"/>
Do I have to give up HTML5 friendly markup in this case?
The app is running in GF4 Full profile.
I finally was able to make this work using
<input type="text" jsf:id="edad" jsf:value="#{backend.item.edad}"/>
By adding jsf: before the value attribute, everything worked as expected. The JEE7 tutorial says in 8.9.1 Using Pass-through Elements that "specify at least one of its attributes using the http://xmlns.jcp.org/jsf namespace", with an example showing the value property without the namespace. But it seems this only works if the bean property is a String.
Any time you want to make your attribute be "JSF-ish" you need to prefix it with the JSF namespace. I'm glad you found the answer anyway!
Ed

GWT uibinder autocorrect off

im using GWT uibinder method and my html contains a textbox like
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:idmw="urn:import:com.testing.wid.impl">
<g:HTMLPanel>
<table align="center" valign="center" height="25%">
<tr><td><g:TextBox ui:field='searchS' /></td></tr>
</table>
</g:HTMLPanel>
How can i TURN OFF autocorrect and autocapitalize for this Textbox??
i tried
<g:TextBox ui:field='searchS' autocapitalize="off" autocorrect="off"/>
but i get
[ERROR] Class TextBox has no appropriate setAutocorrect()
method Element <g:TextBox autocapitalize='off' autocorrect='off' ui:field='searchS'>
Any other way i can do this???
Thanks
As already pointed by #Boris Brudnoy there is no built-in way to do it with TextBox. Takin futher his suggestion it will be nice to extract this into new custom component (to simplify reuse and support):
Add new package (for example com.app.shared.customcontrol)
Add new CustomTextBox:
public class CustomTextBox extends TextBox {
public void setAutocomplete(String value){
this.getElement().setAttribute("autocomplete", value);
}
public void setAutocapitalize(String value){
this.getElement().setAttribute("autocapitalize", value);
}
}
Declare new namespace using UI binder and use your component:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:c="urn:import:com.app.shared.customcontrol">
<g:HTMLPanel ...>
<c:CustomTextBox ui:field="..." autocomplete="off" autocapitalize="off" />
</g:HTMLPanel>
</ui:UiBinder>
As alternative way if you want apply these settings system wide you can do it via constructor:
public class CustomTextBox extends TextBox {
public CustomTextBox() {
this.getElement().setAttribute("autocomplete", "off");
this.getElement().setAttribute("autocapitalize", "off");
}
....
}
What you've tried will not work since GWT doesn't translate UiBinder attributes directly into HTML element properties. Instead, as your error message hints, it looks up widget setter methods of the form set[UiBinder_attribute]. Since there is neither setAutocorrect nor setAutocapitalize method in the TextBox class, the errors you're getting are expected.
What you could do is drop to the element level and write something like this, e.g. in your widget's constructor:
public MyWidget() {
initWidget(uiBinder.createAndBindUi(this));
searchS.getElement().setProperty("autocapitalize", "off");
searchS.getElement().setProperty("autocorrect", "off");
}