El function to obtain converter #188

Closed
djmj opened this Issue Dec 27, 2015 · 4 comments

Projects

None yet

2 participants

@djmj
djmj commented Dec 27, 2015

Feature

El-function to obtain a converter by its id.

<comp:entityEdit entity="#{bean.entity}" dao="#{bean.dao}" converter="#{of:createConverter('entityConverter')}" outcome="#{view.viewId}" viewParamName="id"/>

I could use #{facesContext.application.createConverter('entityConverter')} but el does not supports overloading so its not guaranteed to work always.

Use case

I tried to pass a converter by its id to a composite component which is not working since its a String and not a Converter instance.

In the composite this converter is further passed as an attribute of p:commandButton to the bean so the site can be reloaded with the view parameter id of the new persisted entity.

Main

<f:metadata>
    <o:viewParam name="id" value="#{bean.entity}" converter="entityConverter" />
</f:metadata>
<ui:define name="content_main">
    <comp:entityEdit entity="#{bean.entity}" dao="#{bean.dao}" converter="entityConverter" outcome="#{varOutcome}" viewParamName="id"/>
</ui:define>

Composite

<composite:interface>
    <composite:attribute name="outcome" type="java.lang.String" />
    <composite:attribute name="viewParamName" type="java.lang.String" />
    <composite:attribute name="converter" type="javax.faces.convert.Converter" />
</composite:interface>
<composite:implementation>
    <!-- form inputs -->
    <!-- persist entity and reload page with new id -->
    <p:commandButton action="#{cc.attrs.dao.create(cc.attrs.entity)}">
        <f:attribute name="outcome" value="#{cc.attrs.outcome}/>
        <f:attribute name="converter" value="#{cc.attrs.converter}/>
        <f:attribute name="viewParamName" value="#{cc.attrs.viewParamName}/>
    </p:commandButton
</composite:implementation>

Code

Java

/**
 * Shortcut for {@link Application#createConverter(String)} since el does not supports overloaded function
 *
 * @param converterId The converter id for which to create and return a new {@link Converter} instance
 *
 * @return
 */
public static Converter createConverterById(final String converterId)
{
    return FacesContext.getCurrentInstance().getApplication().createConverter(converterId);
}

Taglib definition

<!-- converter -->
<function>
    <description>Shortcut for Application#createConverter(String)</description>
    <function-name>createConverterById</function-name>
    <function-class>
        package.ElUtils
    </function-class>
    <function-signature>
        javax.faces.convert.Converter createConverterById(java.lang.String)
    </function-signature>
</function>

Workaround

Make composite argument more generic Object and check in bean if its a String or Converter instance.

@BalusC
Member
BalusC commented Feb 7, 2016

This is on the edge with "poor practice". I'd rather not provide it from OmniFaces on.

If I understand your use case correctly, you just need to pass entity via <h:inputHidden> with converter on it.

@djmj
djmj commented Feb 8, 2016

I just want to pass a converter that i retrieve by id to a composite component.

Alternatively i could pass the id and check in bean if the attribute is either a String or a Converter instance and create the converter there if necessary.

Use case

In my real use case i need the converter in the create method to redirect to the correct new outcome by creating a bookmarkable url.

Something like create a product and move to next page with the new id. The specific view-parameter id, the outcome and the required converter of the target page is not known in the generic DAO.

create(T entity)
{
    // persist entity at database

    // check if outcome, viewParamName and optional converter attribute is defined at current component
    String outcome = getOutcomeFromAttribute(context, entity);


    if (outcome != null)
            // redirect
}


/**
 * Resolves the outcome attribute ({@link ATTR_OUTCOME}) from the current component if defined.
 * Optional tries to resolve a view-parameter name by {@link ATTR_VIEWPARAMNAME} and its optional
 * converter by attribute {@link ATTR_CONVERTER} to create a bookmarkable url using the given
 * <code>object</code>.
 *
 * @param context
 * @param obj 
 *
 * @return <code>null</code> if no outcome provided
 */
private static String getOutcomeFromAttribute(final FacesContext context, final Object obj)
{
    final UIComponent component = Components.getCurrentComponent();

    if (component == null)
        return null;

    String outcome = (String) component.getAttributes().get(ATTR_OUTCOME);

    if (outcome != null)
    {
        final String viewParamName = (String) component.getAttributes().get(ATTR_VIEWPARAMNAME);

        if (viewParamName == null)
            return outcome;

        final Converter converter = (Converter) component.getAttributes().get(ATTR_CONVERTER);

        outcome = FacesLocal.getBookmarkableURL(context,
            Collections.singleton(new SimpleParam(viewParamName, obj, converter)), false);
    }

    return outcome;
}

But necessary to create the correct redirect url.

Little offtopic:

I think defining the converter instance on a <f:param> is also just a workaround of JSF shortcomings. Converters just need to be defined at target page (DRY principal).
JSF implementation itself must resolve the converter from the markup and expression of the target page. The converter attribute at <f:param> could still be valid to make this resolve process obsolete in certain cases. Maybe i create an issue at jsf spec.

@djmj
djmj commented Feb 12, 2016

I now decided to create the converter in bean code and pass it to composite using generic Object.

<composite:attribute name="converter" type="java.lang.Object" shortDescription="Optional converter, converterId or class to convert the created or updated instance to its String representation for the optional outcome." />

In case the below method is usefull you can add it to omnifaces.

/**
 * Converts the given Object to a {@link Converter} instance by either casting it or creating a new converter in
 * case it represents a converterId or a class.
 *
 * @see Application#createConverter(Class)
 * @see Application#createConverter(String)
 *
 * @param context Current context
 * @param objConverter 
 *
 * @return <code>null</code> in case argument is <code>null</code> or not of the targeted types.
 */
public static Converter convertOrCreateConverter(final FacesContext context, final Object objConverter)
{
    if (objConverter instanceof Converter)
        return (Converter) objConverter;
    // by converterId
    else if (objConverter instanceof String)
        return context.getApplication().createConverter((String) objConverter);
    // by class
    else if (objConverter instanceof Class)
        return context.getApplication().createConverter((Class<?>) objConverter);
    return null;
}
@BalusC BalusC closed this in 573bc27 Jul 16, 2016
@BalusC
Member
BalusC commented Jul 16, 2016

I added a bunch of Faces#createConverter()/createValidator() utility methods.

It's available in today's latest 2.5-SNAPSHOT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment