I have a requirement to create a javascript function that when invoked will save all of the valid components on a JSF 2.0 form. Since the complete form will never be valid as a whole I need to figure out a way to run the lifecycle per component so that if the validation phase is successful the model will be updated and eventually saved.
Ideally, this needs to be a single ajax request as iterating over each component with a separate ajax request would be painfully inefficient.
Has anyone solved the problem before? If not could you give me some pointers on possible implementations?
Edit:
Here's what I have that seems to be working well so far:
#ManagedBean(name = "partialAppSaveBean")
#RequestScoped
public class PartialAppSaveBean implements Serializable {
protected static final Logger LOGGER = LoggerFactory.getLogger(PartialAppSaveBean.class);
private static final long serialVersionUID = 1L;
/**
* Save any valid Application values
*
* #param event
*/
public void saveData(AjaxBehaviorEvent event) {
final FacesContext context = FacesContext.getCurrentInstance();
UIForm form = getParentForm(event.getComponent());
Set<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
form.visitTree(VisitContext.createVisitContext(context, null, hints), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
input.validate(context.getFacesContext());
if (input.isValid() && input.getValue() != null) {
ValueExpression valueExpression = input.getValueExpression("value");
if (valueExpression != null
&& valueExpression.getExpressionString() != null) {
try {
valueExpression.setValue(context.getFacesContext().getELContext(), input.getValue());
} catch (Exception ex) {
LOGGER.error("Expression [ " + valueExpression.getExpressionString() +
"] value could not be set with value [" + input.getValue() + "]", ex);
}
}
}
}
return VisitResult.ACCEPT;
}
});
//Save data here
}
/**
* Returns the parent form for this UIComponent
*
* #param component
* #return form
*/
private static UIForm getParentForm(UIComponent component) {
while (component.getParent() != null) {
if (component.getParent() instanceof UIForm) {
return (UIForm) component.getParent();
} else {
return getParentForm(component.getParent());
}
}
return null;
}
}
Invoked with something like:
<h:commandButton
id="saveData">
<f:ajax listener="#{partialAppSaveBean.saveData}" execute="#form" immediate="true" onevent="onPartialSave" />
</h:commandButton>
You could use UIComponent#visitTree() on the UIForm.
FacesContext context = FacesContext.getCurrentInstance();
UIForm form = getFormSomehow();
Map<String, Object> validValues = new HashMap<String, Object>();
Set<VisitHint> hints = EnumSet.of(VisitHint.SKIP_UNRENDERED);
form.visitTree(VisitContext.createVisitContext(context, null, hints), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent component) {
if (component instanceof UIInput) {
UIInput input = (UIInput) component;
if (input.isValid()) {
validValues.put(input.getClientId(context.getFacesContext()), input.getValue());
}
}
return VisitResult.ACCEPT;
}
});
Related
I'm trying to use primefaces selectManyCheckBox with ajax and converter, but the ajax not fired. If I'm didn't use converter, the ajax can fired. Is there something wrong with my converter?
<div class="form-group">
<p:outputLabel value="Atur Grade Pinjaman" for="gradePinjaman"/>
<p:selectManyCheckbox id="gradePinjaman" value="#{autoInvestController.param.grades}" converter="companyGradeConverter">
<f:selectItems value="#{autoInvestController.grades}" var="grade" itemLabel="#{grade.id}" itemValue="#{grade}"/>
<p:ajax update="selectAll estimation" listener="#{autoInvestController.valueChange}"/>
</p:selectManyCheckbox>
<p:message for="gradePinjaman"/>
</div>
Here is my backing bean code
public void valueChange() {
if (param.getGrades() != null && !param.getGrades().isEmpty()) {
checkAll = param.getGrades().size() == grades.size();
calculateCollectEstimation();
}
}
Here is my converter
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (Strings.isNullOrEmpty(value)) {
return null;
} else {
try {
PlatformService platformService = (PlatformService) CDI.current().select(PlatformService.class).get();
Map<String, Object> param = new HashMap<>();
param.put("id", value);
CompanyGradeResponse companyGrade = platformService.getCompanyGrade(param).get(0);
return companyGrade;
} catch (EndpointException e) {
LOG.error(e.getMessage(), e);
return null;
}
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value != null) {
return ((CompanyGradeResponse) value).getId();
} else {
return null;
}
}
I think my converter work well, but why the ajax won't fired when i check the checkbox?
Thanks
I'm trying to validate the information, user is giving at the registration. One of the fields contains the mailadress, which should be validated by looking in the database to confirm, it doesn't exist yet.
Problem is, that if i type an existing mailadress, it will give back a NonUniqueResultException, but also does store the new user with the duplicate mailadress in the database. Don't understand this, beacuse in the JSF-lifecycle after validation fails, it shouldn't go on to the invoke application phase, right?
Here's my code:
mail field in register formular
<b:inputText id="mail" required="true"
requiredMessage="Bitte geben Sie Ihre E-Mail-Adresse an!"
label="E-Mail" placeholder="name#example.com" value="#{registrierenManagedBean.nutzer.mail}">
<f:validator validatorId="mailValidatorRegistrieren"/>
<b:messages for="mail"/>
</b:inputText>
my custom validator
#FacesValidator("mailValidatorRegistrieren")
public class MailValidatorRegistrieren implements Validator {
#EJB
private DAO dao;
private String mail;
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
#Override
public void validate(FacesContext facesContext, UIComponent uiComponent, Object o) throws ValidatorException {
mail = (String)o;
boolean matchesPattern = EMAIL_PATTERN.matcher(mail).find();
if(!matchesPattern)
{
throw new ValidatorException((new FacesMessage("Invalid mail")));
}
if(mail.isEmpty()) {
return;
} else if(validateNutzer(mail)){
throw new ValidatorException(new FacesMessage("mail alredy used"));
} else{
return;
}
}
private boolean validateNutzer(String mail) {
try {
Nutzer n = dao.findNutzerByMail(mail);
return n.getMail().equals(mail);
} catch (NullPointerException e) {
return false;
}
}
}
and the "findNutzerByMail"-method from my DAO
public Nutzer findNutzerByMail(String mail) {
try {
return em.createNamedQuery("findNutzerByMail", Nutzer.class)
.setParameter("mail", mail)
.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
I am using BeanValidation for Form inputs with AjaxFallbackButton to submit the Form. And FeedbackPanel for showing errors. When I give invalid input the form does not submit but feedbackpanel is not showing.
onError, form.getFeedbackMessages() returns empty array.
Wicket version 6.18.0.
Here is the code:
Form<Address> form = getForm();
add(form);
FeedbackPanel feedbackPanel = new FeedbackPanel("feedbackMessage");
feedbackPanel.setOutputMarkupId(true);
add(feedbackPanel);
public Form<Address> getForm() {
CompoundPropertyModel<Address> model = new CompoundPropertyModel<Address>(address);
final Form<Address> form = new Form<Address>("addressForm", model);
form.add(new Label("fNameLabel", new ResourceModel("fNameLabel")));
form.add(new Label("lNameLabel", new ResourceModel("lNameLabel")));
form.add(new Label("workLabel", new ResourceModel("workLabel")));
form.add(new Label("homeLabel", new ResourceModel("homeLabel")));
form.add(new TextField<String>("firstName").add(new PropertyValidator<String>()));
form.add(new TextField<String>("lastName").add(new PropertyValidator<String>()));
form.add(new TextField<String>("homeLocation").add(new PropertyValidator<String>()));
form.add(new TextField<String>("workLocation").add(new PropertyValidator<String>()));
form.add(new AjaxFallbackButton("submit", form) {
/**
*
*/
private static final long serialVersionUID = 6672729206839722437L;
#Override
protected void onError(final AjaxRequestTarget target, final Form form) {
Page page = target.getPage();
for (Component component : page.visitChildren()) {
String markupId = component.getMarkupId();
if (markupId.contains("feedbackMessage")) {
if (form.hasFeedbackMessage()) {
System.out.println(form.getFeedbackMessages());
}
}
}
}
#Override
protected void onSubmit(AjaxRequestTarget target, Form form) {
if (address.getFirstName() != null) {
AddressGenerator.getInstance().add(address);
modalWindow.closeCurrent(target);
}
}
});
return form;
}
Form is in ModalWindow.
Component#getFeedbackMessages() returns the messages only for this component instance. It doesn't visit the children!
Update your onError() method with:
#Override
protected void onError(final AjaxRequestTarget target, final Form form) {
target.add(feedbackPanel);
}
You can use if (form.hasError()) but since you are in AjaxFallbackButton#onError() it means there is an error.
I want to create UIComponents with an AjaxBehaviour dynamically and add it to a HTMLPanelGrid. The ajax method will be called but there was no value binding before and no rendering after that. Please help me! Thank you so much! Here is some code:
The Ajax function:
public void ajax(AjaxBehaviorEvent event) {
HtmlPanelGrid grid = (HtmlPanelGrid) event.getComponent().getParent();
HashMap<String, Object> params;
try {
ajaxSet(grid);
params = collect();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
SearchComponent search = (SearchComponent) ComponentRegistry.instance()
.getComponent(IComponentConstants.SEARCH);
this.score = Integer.toString(search.getSystemsCount(params));
}
The function for creating a HTMLInputText:
public HtmlInputText inputText(String id, String expression,
UIComponent parent, AjaxBehaviorListener listener) {
HtmlInputText text = new HtmlInputText();
text.setId(id);
if (!expression.isEmpty()) {
text.setValueExpression("value", createValueExpression(expression));
}
AjaxBehavior ajax = (AjaxBehavior) FacesContext.getCurrentInstance()
.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
ajax.setExecute(Arrays.asList(new String[] { "#form" }));
ajax.setRender(Arrays.asList(new String[] { "#all" }));
ajax.addAjaxBehaviorListener(listener);
text.addClientBehavior("valueChange", ajax);
add(text, parent);
return text;
}
The function for creating the fields depending on a list with the desired field informations and the listener:
public void createSearchFields(UIComponent parent) {
String beanname = Utilities.lookupManagedBeanName(this);
GUIHelper helper = GUIHelper.instance();
for (Searchfield searchfield : shown) {
helper.label(searchfield.id, searchfield.displayname, parent);
for (Field f : searchdto.getClass().getDeclaredFields()) {
GUISearchField gsf = f.getAnnotation(GUISearchField.class);
if (gsf == null)
continue;
if (gsf.id().equals(searchfield.id)) {
String expression = "#{" + beanname + ".searchdto."
+ f.getName() + "}";
helper.inputText(searchfield.id, expression, parent, this);
}
}
}
}
#Override
public void processAjaxBehavior(AjaxBehaviorEvent event)
throws AbortProcessingException {
ajax(event);
}
public abstract void ajax(AjaxBehaviorEvent event);
The xhtml part:
<h:form id="searchform" styleClass="ibm-column-form ibm-styled-form">
<h:panelGrid columns="2" binding="#{systemsearchable.searchfields}">
</h:panelGrid>
<h:commandButton value="Search" action="#{systemsearchable.search}">
<f:ajax execute="#form" render="results"></f:ajax>
</h:commandButton>
</h:form>
If you need more info please let me know :)
I am developing a spring mvc application . I just want to do the following ,
When user clicks on a link , I just want to pass some values from that page to the target Controller of that link.
Is AbstractCommandController will be useful for this ?
Is there any way other than using session attributes ?
You can do it in one of the following ways:
1) Submit Form.
2) Send it as parameters in your URL.
3) Create cusom flash scope for your application:
You can read more about it here:http://goo.gl/nQaQh
In spring MVC there is no Flash Bean scope so you can do it as Interceptor:
Here is the simple code how to use
public class FlashScopeInterceptor implements HandlerInterceptor {
public static final String DEFAULT_ATTRIBUTE_NAME = "flashScope";
public static final String DEFAULT_SESSION_ATTRIBUTE_NAME = FlashScopeInterceptor.class.getName();
public static final int DEFAULT_RETENTION_COUNT = 2;
private String sessionAttributeName = DEFAULT_SESSION_ATTRIBUTE_NAME;
private String attributeName = DEFAULT_ATTRIBUTE_NAME;
private int retentionCount = DEFAULT_RETENTION_COUNT;
/**
* Unbinds current flashScope from session. Rolls request's flashScope to
* the next scope. Binds request's flashScope, if not empty, to the session.
*
*/
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (request.getSession( false ) != null)
{
request.getSession().removeAttribute( this.sessionAttributeName );
}
Object requestAttribute = request.getAttribute( this.attributeName );
if (requestAttribute instanceof MultiScopeModelMap)
{
MultiScopeModelMap attributes = (MultiScopeModelMap) requestAttribute;
if (!attributes.isEmpty())
{
attributes.next();
if (!attributes.isEmpty())
{
request.getSession( true ).setAttribute( this.sessionAttributeName, attributes );
}
}
}
}
/**
* merge modelAndView.model['flashScope'] to current flashScope
*/
#Override
public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
if (modelAndView != null)
{
Map<String, Object> modelFlashScopeMap = null;
for (Iterator<Entry<String, Object>> iterator = ((Map<String, Object>) modelAndView.getModel()).entrySet()
.iterator(); iterator.hasNext();)
{
Entry<String, Object> entry = iterator.next();
if (this.attributeName.equals( entry.getKey() ) && entry.getValue() instanceof Map)
{
if (modelFlashScopeMap == null)
{
modelFlashScopeMap = (Map) entry.getValue();
}
else
{
modelFlashScopeMap.putAll( (Map) entry.getValue() );
}
iterator.remove();
}
else if (entry.getKey().startsWith( this.attributeName + "." ))
{
String key = entry.getKey().substring( this.attributeName.length() + 1 );
if (modelFlashScopeMap == null)
{
modelFlashScopeMap = new HashMap<String, Object>();
}
modelFlashScopeMap.put( key, entry.getValue() );
iterator.remove();
}
}
if (modelFlashScopeMap != null)
{
MultiScopeModelMap flashScopeMap;
if (request.getAttribute( this.attributeName ) instanceof MultiScopeModelMap)
{
flashScopeMap = (MultiScopeModelMap) request.getAttribute( this.attributeName );
}
else
{
flashScopeMap = new MultiScopeModelMap( this.retentionCount );
}
flashScopeMap.putAll( modelFlashScopeMap );
request.setAttribute( this.attributeName, flashScopeMap );
}
}
}
/**
* binds session flashScope to current session, if not empty. Otherwise cleans up empty
* flashScope
*/
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession( false );
if (session != null)
{
Object sessionAttribute = session.getAttribute( this.sessionAttributeName );
if (sessionAttribute instanceof MultiScopeModelMap)
{
MultiScopeModelMap flashScope = (MultiScopeModelMap) sessionAttribute;
if (flashScope.isEmpty())
{
session.removeAttribute( this.sessionAttributeName );
}
else
{
request.setAttribute( this.attributeName, flashScope );
}
}
}
return true;
}
}
and then MultiScopeModelMap
public class MultiScopeModelMap extends CompositeMap implements Serializable, MapMutator
{
public MultiScopeModelMap(int num)
{
super();
setMutator( this );
for(int i = 0; i < num; ++i)
{
addComposited( new HashMap() );
}
}
/** Shadows composite map. */
private final LinkedList<Map> maps = new LinkedList<Map>();
#Override
public synchronized void addComposited( Map map ) throws IllegalArgumentException
{
super.addComposited( map );
this.maps.addLast( map );
}
#Override
public synchronized Map removeComposited( Map map )
{
Map removed = super.removeComposited( map );
this.maps.remove( map );
return removed;
}
/**
* Starts a new scope.
* All items added in the session before the previous session are removed.
* All items added in the previous scope are still retrievable and removable.
*/
public void next()
{
removeComposited( this.maps.getFirst() );
addComposited( new HashMap() );
}
public Object put( CompositeMap map, Map[] composited, Object key, Object value )
{
if(composited.length < 1)
{
throw new UnsupportedOperationException("No composites to add elements to");
}
Object result = map.get( key );
if(result != null)
{
map.remove( key );
}
composited[composited.length-1].put( key, value );
return result;
}
public void putAll( CompositeMap map, Map[] composited, Map mapToAdd )
{
for(Entry entry: (Set<Entry>)mapToAdd.entrySet())
{
put(map, composited, entry.getKey(), entry.getValue());
}
}
public void resolveCollision( CompositeMap composite, Map existing, Map added, Collection intersect )
{
existing.keySet().removeAll( intersect );
}
#Override
public String toString()
{
return new HashMap(this).toString();
}
}
Now configure it in xml:
<bean id="flashScopeInterceptor" class="com.vanilla.scopes.FlashScopeInterceptor" />
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list><ref bean="flashScopeInterceptor"/></list>
</property>
</bean>
Usage:
#RequestMapping(value="/login.do", method=RequestMethod.POST)
public ModelAndView login(#Valid User user){
ModelAndView mv = new ModelAndView("redirect:result.html");
if (authService.authenticate(user.getUserName(), user.getPassword()))
mv.addObject("flashScope.message", "Success");
//else
mv.addObject("flashScope.message", "Login Failed");
return mv;
}
#RequestMapping(value ="/result.html", method=RequestMethod.GET)
public ModelAndView result(){
ModelAndView mv = new ModelAndView("login/loginAction");
return mv;
}
In JSP the usage is very simple:
${flashScope.message}