How does the # option operator work in AEM HTL templates - syntax

I spen a bit of time trying to read the official documentation on the AEM # operator and the official syntax specification, but I do not understand the logic behind.
Let's take this example presented in the documentation:
${myVar # optOne, optTwo=bar}
How will this expression be evaluated? What is the effect of the assignment optTwo=bar?.
Or this example in the syntax specification:
${myVar # optOne, optTwo=myVar, optThree='string', optFour=[myVar, 'string']}
How each assignment in the list (optTwo=myVar, optThree='string', optFour=[myVar, 'string']) will affect the final value to which the expression will be evaluated to?

In my project we are using this # operator to pass values to the Java backend logic. I have created a sample use-case to demonstrate the usage of the # operator.
I have created a simple info component with a textfield, a numberfield and a checkbox. The corresponding cq:dialog's .content.xml is as below -
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Info"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/tabs"
type="nav"/>
<items jcr:primaryType="nt:unstructured">
<tab
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
class="field-whitespace"
fieldDescription="Enter full name of the user"
fieldLabel="Full Name"
name="./fullName"/>
<age
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/numberfield"
class="field-whitespace"
fieldDescription="Enter the age of the user"
fieldLabel="Age"
name="./age"/>
<married
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/checkbox"
class="field-whitespace"
fieldDescription="Check if the user is married"
name="./married"
text="Married?"
value="true"/>
</items>
</columns>
</items>
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
</tab>
</items>
</content>
</jcr:root>
The corresponding HTL file is as below -
<div data-sly-test="${wcmmode.edit || wcmmode.design}">
Info Component
</div>
<sly
data-sly-use.info="${'org.redquark.aem.learning.core.cq.text.InfoComponent' #
fullName=properties.fullName,
age=properties.age,
married=properties.married
}" />
<sly data-sly-test="${info}">
<h1>${info.details}</h1>
</sly>
Here, you can see that in the tag, we are passing values from the JCR in the variables fullName, age and married.
The java code that will read these values is as below -
package org.redquark.aem.learning.core.cq.text;
import com.adobe.cq.sightly.WCMUsePojo;
public class InfoComponent extends WCMUsePojo {
private String details;
#Override
public void activate() throws Exception {
String fullName = get("fullName", String.class);
Integer age = Integer.parseInt(get("age", String.class));
Boolean isMarried = Boolean.parseBoolean(get("married", String.class));
details = fullName + " is " + age + " years old and is " + (isMarried ? "married" : " not married");
}
/**
* #return the details
*/
public String getDetails() {
return details;
}
}
Or if you are using SlingModels instead of Java Use API then you can access those values in a typical Sling Model way.
package org.redquark.aem.learning.core.models.text;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
#Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class InfoModel {
#Inject
#Optional
#Default(values = "Test")
private String fullName;
#Inject
#Optional
#Default(values = "0")
private String age;
#Inject
#Optional
#Named("married")
#Default(values = "false")
private String isMarried;
// Variable that will be read in the HTL file
private String details;
#PostConstruct
protected void init() {
details = fullName + " is " + Integer.parseInt(age) + " years old and is "
+ (Boolean.parseBoolean(isMarried) ? "married" : " not married");
}
/**
* #return the details
*/
public String getDetails() {
return this.details;
}
}
Of course, then you have to call your InfoModel class in the HTL code as
<sly data-sly-use.info="${'org.redquark.aem.learning.core.models.text.InfoModel' #
fullName=properties.fullName,
age=properties.age,
married=properties.married
}" />
You see, here we are reading the same values which were passed in the HTL code. We can then perform any business logic on them.
I hope this helps. Cheers!

Related

Null pointer for field injection

I'm missing fundamental things here that I do, I'm sorry for this question, basically I tried to simulate my own question from Autowring for #Sevice field failed , but in very simple form , I tried to somehow raise errors on purpose for building understanding, but when it comes, I just can't handle it.
x-servlet.xml
<beans xmlns= ...... >
<context:component-scan base-package="com" />
<context:annotation-config />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
com.domain
Boss.java
package com.domain;
public class Boss {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Employee:
package com.domain;
import org.springframework.beans.factory.annotation.Autowired;
public class Employee {
#Autowired
private Boss boss;
String nameBoss;
public String getNameBoss() {
nameBoss = this.boss.getName();
return nameBoss;
}
}
com.controller
controller.java :
package com.controller
import com.domain.Boss;
import com.domain.Employee;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class controller {
#RequestMapping("/try")
public String show() {
Boss b = new Boss();
b.setName(" hans");
Employee e = new Employee();
String bossName = e.getNameBoss();
System.out.println(bossName );
return "";
}
}
I was thinking that,
String bossName = e.getNameBoss();
in controller won't be null because Boss is already initialized right before Employee construction, but I'm wrong ..
Are you sure you want to have dependency injection (DI) for domain objects? This is not a typicall usage... For a DI, container have to create instances - so do not use new operator...
You can replace new operator using ApplicationContext.getBean() (iff the bean is of type prototype), but as I said above - even if you know how to create such beans, how should Spring know, which instance of Boss you want to have in Employee?
First thing you need is application context in controller, which should work to add interface org.springframework.context.ApplicationContextAware or you can simply autowire it and then use it "standard" way (ac is ApplicationContext):
Boss b = ac.getBean(Boss.class);
Employee e = ac.getBean(Employee.class, b);
I have Boss and Employee marked with annotations as:
#Component
#Scope("prototype")
public class Boss {
...
}
#Component
#Scope("prototype")
public class Employee {
// #Autowire - wrong
Boss boss;
public Employee(Boss boss) {
this.boss = boss;
}
...
}

Spring Framework auditng fields (CreatedAt, UpdatedAt)

I'm working on a Spring Framework project. For fast development process, I'm using Spring Roo (1.3.2). For several entities, I have requirement to store the creation time and last time it was updated. I've made couple of experiments on Spring Roo tutorial project and this is one of the entities and the way I'm trying to do it.
package com.springsource.roo.pizzashop.domain;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.format.annotation.DateTimeFormat;
#RooJavaBean
#RooToString
#RooJpaActiveRecord
public class Topping {
/**
*/
#NotNull
#Size(min = 2)
private String name;
/**
*/
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date createdAt;
/**
*/
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date updatedAt;
#PrePersist
protected void onCreate() {
createdAt= new Date();
}
#PreUpdate
protected void onUpdate() {
updatedAt = new Date();
}
}
When I create an object (Topping) it's getting the right value for createdAt and leaving "NULL" for updatedAt. Perfect! That's the way it has to be!
But when I update the object (Topping) it's getting the right value for updatedAt and losing the value of createdAt (NULL).
The problem is that when I update the object I am losing the value of createdAt.
Could you tell me why do I get this behavior? Could you help me with a possible solution for it?
However, createdAt and updatedAt fields don't need to appear in the "view" (create.jspx, update.jspx), so I've set its render attributes to false.
These are the forms for creating and updating.
create.jspx
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<form:create id="fc_com_springsource_roo_pizzashop_domain_Topping" modelAttribute="topping" path="/toppings" render="${empty dependencies}" z="w4+E4tQPCNRPSYVWPNOBUuf9zNE=">
<field:input field="name" id="c_com_springsource_roo_pizzashop_domain_Topping_name" min="2" required="true" z="GRdEGRLiZ0QLjBH0pTEOZ252BD8="/>
<field:datetime dateTimePattern="${topping_ts_date_format}" field="ts" id="c_com_springsource_roo_pizzashop_domain_Topping_ts" render="false" z="user-managed"/>
<field:datetime dateTimePattern="${topping_updated_date_format}" field="updated" id="c_com_springsource_roo_pizzashop_domain_Topping_updated" render="false" z="user-managed"/>
</form:create>
<form:dependency dependencies="${dependencies}" id="d_com_springsource_roo_pizzashop_domain_Topping" render="${not empty dependencies}" z="bkqRYdlfs3kDAjK51P0O+7NiahE="/>
</div>
update.jspx
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<form:update id="fu_com_springsource_roo_pizzashop_domain_Topping" modelAttribute="topping" path="/toppings" versionField="Version" z="YGl9ujJNQw182uzoCDgi1FdiafQ=">
<field:input field="name" id="c_com_springsource_roo_pizzashop_domain_Topping_name" min="2" required="true" z="GRdEGRLiZ0QLjBH0pTEOZ252BD8="/>
<field:datetime dateTimePattern="${topping_ts_date_format}" field="ts" id="c_com_springsource_roo_pizzashop_domain_Topping_ts" render="false" z="user-managed"/>
<field:datetime dateTimePattern="${topping_updated_date_format}" field="updated" id="c_com_springsource_roo_pizzashop_domain_Topping_updated" render="false" z="user-managed"/>
</form:update>
</div>
The render="false" on fields tags makes that this fields will not be included on page. So, on POST request, this value is missing and Spring Binding doesn't fill it (take a count that Binding process create new instance of entity, doesn't get it form DB). This makes merge operation to set missing values to null.
For more info about this you can read:
Spring MVC 3.0: How do I bind to a persistent object
Is it possible to update only a subset of attributes on an entity using Spring MVC with JPA?
The easiest workaround is include this value in a hidden input of edit form.
On the other hand, take a look to auditing feature of gvNIX. This feature includes auditing (stores creation/update time stamp and user who done it) and and revision log (using Hibernate Envers).
Good luck!

Spring LDAP 2.0.2 - No Injection (or how to inject) - #Autowired

I am new to Spring LDAP (the version I am using is: 2.0.2) and I have some questions. I am querying Active Directory.
I am going to post my code, and my questions are in my main class - MainLdapTest:
ldap_config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ldap="http://www.springframework.org/schema/ldap"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/ldap
http://www.springframework.org/schema/ldap/spring-ldap.xsd">
<!-- Information about this configuration file can be found:
http://docs.spring.io/spring-ldap/docs/current/reference/#configuration
-->
<ldap:context-source id="contextSource"
url="ldap://<url>:<port>"
base="DC=comp,DC=com"
username="<username>"
password="<secret>" />
<ldap:ldap-template id="ldapTemplate" context-source-ref="contextSource" />
<bean id="LdapUserDAO" class="com.ldap.dao.LdapUserDAOImpl">
<property name="ldapTemplate" ref="ldapTemplate" />
</bean>
</beans>
LdapUser.java
#Entry(objectClasses = {"top", "person", "organizationalPerson", "user"})
public final class LdapUser {
#Id
private Name distinguishedName;
#Attribute(name="cn")
private String cn;
#Attribute(name="description")
private String description;
#Attribute(name="displayName")
private String displayName;
#Attribute(name="sAMAccountName")
private String sAMAccountName;
#Attribute(name="mail")
private String mail;
#Attribute(name="userPrincipalName")
private String userPrincipalName;
#Attribute(name="userAccountControl")
private String userAccountControl;
// ...
// getters and setters
// ...
LdapUserDAO.java
public interface LdapUserDAO
{
public LdapUser create(LdapUser ldapUser);
public void update(LdapUser ldapUser);
public void delete(LdapUser ldapUser);
public LdapUser findByUid(String uid);
public List<LdapUser> findAll();
public List<LdapUser> findByLastName(String lastName);
public List<LdapUser> findBySAMAccountName(String sAMAccountName);
public List<LdapUser> findDisabledUsers();
public List<LdapUser> findDisabledUser(LdapUser ldapUser);
public List<LdapUser> findByObjectClassPerson();
public void setLdapTemplate(LdapTemplate ldapTemplate);
}
LdapUserDAOImpl.java
public class LdapUserDAOImpl implements LdapUserDAO
{
#Autowired
private LdapTemplate localLdapTemplate;
// ...
public List<LdapUser> findBySAMAccountName(String sAMAccountName) {
return localLdapTemplate.find(query().where("sAMAccountName").is(sAMAccountName), LdapUser.class);
}
// ...
MainLdapTest.java
public class MainLdapTest
{
#Autowired
private LdapUserDAO ldapUserDao;
public MainLdapTest() {
/**
* The ldap_config.xml above is in my classpath,
* but how do I specify that the contents of that file must be used to inject ldapTemplate in this class, or any other class?
*
* Below are my tests and their result:
*
* Tests
* 1. This test returns a NullPointerException because ldapTemplate is not set.
* Shouldn't it be injected by the "#Autowired" annotation?
*/
List<LdapUser> queryListError = ldapUserDao.findByObjectClassPerson();
System.out.println("[ERR] Query returns no values[queryListRetValues]: #[" + queryListError.size() + "]");
/**
* 2. As I set the LdapContextSource manually, it returns the data searched in the query.
*
* sAMAccountName is the <username>
*/
LdapContextSource ctxSrc = new LdapContextSource();
ctxSrc.setUrl("<url>");
ctxSrc.setBase("<base>");
ctxSrc.setUserDn("<username>");
ctxSrc.setPassword("<password>");
ctxSrc.afterPropertiesSet();
ldapUserDao = new LdapUserDAOImpl(ldapTemplate);
List<LdapUser> queryListRetValues = ldapUserDao.findBySAMAccountName("<username>");
System.out.println("[OK] Query returns no values[queryListRetValues]: #[" + queryListRetValues.size() + "]");
}
public static void main(String[] args)
{
MainLdapTest t = new MainLdapTest();
}
}
In fact, my code is based on the one in Spring LDAP - ODM [1]. I have used the "#Autowired" annotation and as I understand the LdapTemplate should be injected into my LdapUserDAOImpl.
I know there is a basic error here, I just can't find where.
As I was not sure if I should only post the link, but I have asked the same question in the spring forum [2].
Links:
[1-Spring Ldap Reference]
[2-Spring Forum Question]
Thanks for your attention and help.
Regards!
The issue is that any object that is annotated with #Autowired need to be created by Spring so that Spring can actually inject the object. To get it to work, you should update to be the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:ldap_config.xml")
public class MainLdapTest
#Autowired
private LdapUserDAO ldapUserDao;
#Test
public run() {
/**
* The ldap_config.xml above is in my classpath,
* but how do I specify that the contents of that file must be used to inject ldapTemplate in this class, or any other class?
*
* Below are my tests and their result:
*
* Tests
* 1. This test returns a NullPointerException because ldapTemplate is not set.
* Shouldn't it be injected by the "#Autowired" annotation?
*/
List<LdapUser> queryListError = ldapUserDao.findByObjectClassPerson();
System.out.println("[ERR] Query returns no values[queryListRetValues]: #[" + queryListError.size() + "]");
/**
* 2. As I set the LdapContextSource manually, it returns the data searched in the query.
*
* sAMAccountName is the <username>
*/
LdapContextSource ctxSrc = new LdapContextSource();
ctxSrc.setUrl("<url>");
ctxSrc.setBase("<base>");
ctxSrc.setUserDn("<username>");
ctxSrc.setPassword("<password>");
ctxSrc.afterPropertiesSet();
ldapUserDao = new LdapUserDAOImpl(ldapTemplate);
List<LdapUser> queryListRetValues = ldapUserDao.findBySAMAccountName("<username>");
System.out.println("[OK] Query returns no values[queryListRetValues]: #[" + queryListRetValues.size() + "]");
}
}
This assumes that ldap_config.xml, junit.jar, and spring-test.jar are on your classpath.
This will work because the SpringJunit4ClassRunner will load your Spring configuration and ensure to inject the LdapUserDAO for you.
If you are running in a standard Servlet environment, then you will want to ensure Spring creates your objects there too.
Of course if you do not want to use Spring's dependency injection you can create the LDapUserDAO by injecting its dependencies programmatically.
Sorry about the long time without updates/answer!
The answer is quite simple, in order to "load" the beans in the XML, you need to do:
final ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
And after, get the bean from context:
LdapProvider ldapProvider = (LdapProvider) context.getBean(LdapProvider.class);
Thanks for all the help! And hope it helps someone else.

EclipseLink MOXy: Logical operators in XmlPath annotation

Do logical operators work in XmlPath annotations of EclipseLink MOXy?
I tried and could not make it work (no Exception is thrown and nothing is bound to "elements").
For example, I would like to have in a bindings file something like this:
<java-type name="Content">
<java-attributes>
<xml-element java-attribute="elements" xml-path="/a/b/ | /c/d"
type="ElementType" container-type="java.util.List" />
</java-attributes>
</java-type>
Is there a way to achieve the same result from a modification of the bindings without using the logical or in the xml-path?
I can only think of a workaround where one would use getters and settings in the domain model, bind both /a/b and /c/d to elements and have the setters append elements to the List rather then replacing the list upon each call to setElements(). I'd rather handle it in the bindings file, though.
Does there exist a place in the documentation that specifies which parts of XPath are supported in MOXy?
Here is an example of how you could support this use case.
Mapping Document (bindings.xml)
You could use the xml-elements mapping for this use case. On each of the nested xml-element mappings you would specify a different xml-path.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum17977009">
<java-types>
<java-type name="Content">
<xml-root-element/>
<java-attributes>
<xml-elements java-attribute="elements">
<xml-element xml-path="a/b"/>
<xml-element xml-path="c/d"/>
</xml-elements>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Java Model (Content)
Below is the Java model we will use for this example.
package forum17977009;
import java.util.List;
public class Content {
private List<ElementType> elements;
public List<ElementType> getElements() {
return elements;
}
public void setElements(List<ElementType> elements) {
this.elements = elements;
}
}
jaxb.properties
To specify MOXy as your JAXB provider you include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Input (input.xml)
Below is a sample input document.
<?xml version="1.0" encoding="UTF-8"?>
<content>
<a>
<b/>
<b/>
</a>
<c>
<d/>
<d/>
</c>
</content>
Demo
Below is some demo code you can run to prove that everything works:
package forum17977009;
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(1);
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "forum17977009/bindings.xml");
JAXBContext jc = JAXBContext.newInstance(new Class[] {Content.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum17977009/input.xml");
Content content = (Content) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(content, System.out);
}
}
Output
Since all of the items are of the same type, they will output based on the xml-path of the first xml-element in the xml-elements mapping:
<?xml version="1.0" encoding="UTF-8"?>
<content>
<a>
<b/>
<b/>
<b/>
<b/>
</a>
</content>
UPDATE
Does there exist a place in the documentation that specifies which
parts of XPath are supported in MOXy?
Here are some examples that should help:
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/03/map-to-element-based-on-attribute-value.html
http://blog.bdoughan.com/2010/09/xpath-based-mapping-geocode-example.html
We are going to add some validation on the XPath statements that are entered for mappings. You can track our progress on this using the following link:
http://bugs.eclipse.org/397101

f:ajax and inputs inside ui:repeat - why aren't getters called?

I'm having trouble understanding why my getters aren't being called when I expect them inside a ui:repeat. I'm using Glassfish 3.1.1 / Mojarra 2.1.3.
The code below will render a table like
Alice [empty input] [empty output] [link: update value] [link: cancel]
Bob [empty input] [empty output] [link: update value] [link: cancel]
If I click "update value" on the Alice row, then "update value" on the "Bob" row, I end up with this:
Alice [Alice] Alice
Bob [Alice] Bob
I don't understand why the output for the "Bob" row is picking up "Alice" instead. It's like the getter isn't being called during the render-response phase, and instead the old value from the managed bean is stuck to the UIInput in the update-model-values phase.
What's weird is that if I hit "update value" on the Alice row, then "cancel", then "update value" on the Bob row, I get the expected result.
Also, if I add "#form" to the render=... on the "update value" link, I will see the right values (although they will be duplicated on each row). I don't like this as much, primarily because I don't want to update the whole table to process a single row.
What could be causing this? What am I missing about the JSF lifecycle?
Also - the same pattern works just fine outside of a ui:repeat. In that case, the h:inputText seems to always refresh with the right value from the managed bean, calling the getter in the "render response" phase as I expect.
This was originally using PrimeFaces p:commandLink but I get exactly the same behavior with standard JSF h:commandLink and f:ajax.
Also I'm aware of PrimeFaces row editor and that would possibly be a better solution to the general overall problem - I still want to understand why this doesn't work though.
Thanks!
The relevant XHTML is as follows
<h:form id="testForm">
<table style="width:400px; ">
<ui:repeat value="#{testBean.customers}" var="customer" varStatus="status">
<tr>
<td><h:outputText id="output" value="#{customer.name}"/></td>
<td><h:inputText id="input" value="#{testBean.customerEdit.name}"/></td>
<td><h:outputText id="otherOutput" value="#{testBean.customerEdit.name}"/></td>
<td>
<h:commandLink actionListener="#{testBean.edit(status.index)}">
<f:ajax execute="#this" render="input otherOutput"/>
Update value
</h:commandLink>
<h:commandLink actionListener="#{testBean.cancel}">
<f:ajax execute="#this" render="input otherOutput"/>
Cancel
</h:commandLink>
</td>
</tr>
</ui:repeat>
</table>
</h:form>
The "testBean" managed bean is view-scoped:
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ViewScoped
#ManagedBean
public class TestBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public static class Customer {
private String name;
public Customer(String name) {
this.name = name;
}
public String getName() {
System.out.println("returning name: " + name);
return name;
}
public void setName(String name) {
this.name = name;
}
}
private List<Customer> customers;
private Customer customerEdit = new Customer(null);
#PostConstruct
public void init() {
customers = Arrays.asList(new Customer("Alice"),
new Customer("Bob"), new Customer("Carol"), new Customer("David"), new Customer("Eve"));
}
public Customer getCustomerEdit() {
return customerEdit;
}
public void setCustomerEdit(Customer customerEdit) {
this.customerEdit = customerEdit;
}
public void edit(int index) {
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
public void save(int index) {
System.out.println("Called save with index: " + index + " new name = " + customerEdit.getName());
customers.set(index, customerEdit);
customerEdit = null;
}
public void cancel() {
System.out.println("Called cancel");
customerEdit = null;
}
public List<Customer> getCustomers() {
return customers;
}
public void setCustomers(List<Customer> customers) {
this.customers = customers;
}
}
Your problem lies in this row:
<h:commandLink actionListener="#{testBean.edit(status.index)}">
You can't send arguments to actionlisteners this way, that's not how it works. You need to change that row to something like:
<h:commandLink actionListener="#{testBean.edit}" customerIndex="#{status.index}>
And then change the edit method to something like this.
public void edit(ActionEvent ae) {
int index = ae.getComponent().getAttributes().get("customerIndex");
System.out.println("Called edit with index: " + index);
customerEdit = new Customer(customers.get(index).getName());
}
Also I'm not sure how your "save" method relates to anything else, but that's probably just because you skipped some irrelevant code.
EDIT: You can send arguments this way if it's a javascript method, but not to managed beans or anything else inside the #{} tags.

Resources