New feature: BookmarkableUrlConverter #113

Closed
djmj opened this Issue Mar 12, 2015 · 6 comments

Projects

None yet

2 participants

@djmj
djmj commented Mar 12, 2015

In relation to issue: 79.
This converter is useful anytime the public URL must be shown in markup or for meta services such as SEO. You can add it to omnifaces if you want.

Maybe decode feature should be enabled by default and I forgot where in JSF "includeViewParams" was defined as a constant.

/**
 * This Converter is useful in case bookmarkable URL's are needed in the markup. This is often necessary for meta
 * services or search engine optimizations (SEO). <br>
 * <br>
 * It creates a bookmarkable URL for a given view-id and {@link UIParameter} instances within the {@link UIComponent}
 * this Converter is attached to.<br>
 * <br>
 * Further it is possible to provide a full qualified domain name (FQDN) which the relative URL is prefixed with. This
 * can be useful if a canonical page shall point to a different domain or a specific subdomain. <br>
 * <br>
 * Optional decoding in UTF-8 is also possible. <br>
 * <br>
 * <b>Example:</b><br>
 *
 * <pre>
 * <code>
 *  &lt;o:outputFormat value=&quot;#{view.viewId}&quot; converter=&quot;BookmarkableUrlConverter&quot; var=&quot;linkHref&quot;&gt;
 *      &lt;f:attribute name=&quot;decode&quot; value=&quot;true&quot;/&gt;
 *      &lt;o:param name=&quot;foo&quot; value=&quot;#{bean.foo}&quot; converter=&quot;fooConverter&quot;/&gt;
 *      &lt;o:param name=&quot;bar&quot; value=&quot;#{bean.bar}&quot; converter=&quot;#{bean.barConverter}&quot; disable=&quot;#{bean.foo eq null}&quot; /&gt;
 *  &lt;/o:outputFormat&gt;
 *  &lt;link rel=&quot;canonical&quot; href=&quot;#{linkHref}&quot; /&gt;
 * </code>
 * </pre>
 *
 * @see Utils#decodeURL(String)
 * @see Component#getParams(UIComponent)
 * @see FacesLocal#getBookmarkableURL(FacesContext, String, Collection, boolean)
 *
 * @author Marco Janc 2014
 */
@FacesConverter("jsf.converter.BookmarkableUrlConverter")
public class BookmarkableUrlConverter implements Converter
{
    public final static String ATTRIBUTE_DECODE = "decode";
    public final static String ATTRIBUTE_INCLUDE_VIEW_PARAMS = "includeViewParams";
    public final static String ATTRIBUTE_FQDN = "fqdn";

    @Override
    public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
    {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getAsString(final FacesContext context, final UIComponent component, final Object value)
    {
        if (!(value instanceof String))
            return null;

        Collection<ParamHolder> params = null;
        String fqdn = "";
        boolean includeViewParams = false;
        boolean decode = false;
        if (component != null)
        {
            params = Components.getParams(component);

            // or use includeViewParams = (boolean) Utils.coalesce(..., includeViewParams);
            includeViewParams = BooleanUtils.toBoolean((Boolean) context
                .getApplication()
                .getExpressionFactory()
                .coerceToType(component.getAttributes().get(BookmarkableUrlConverter.ATTRIBUTE_INCLUDE_VIEW_PARAMS),
                    Boolean.class));

            decode = BooleanUtils.toBoolean((Boolean) context.getApplication().getExpressionFactory()
                .coerceToType(component.getAttributes().get(BookmarkableUrlConverter.ATTRIBUTE_DECODE), Boolean.class));

            final Object fqdnValue = component.getAttributes().get(BookmarkableUrlConverter.ATTRIBUTE_FQDN);
            if (fqdnValue != null)
                fqdn = fqdnValue.toString();
        }

        String url = FacesLocal.getBookmarkableURL(context, (String) value, params, includeViewParams);

        if (decode)
            url = Utils.decodeURL(url);

        return fqdn + url;
    }
}
@BalusC
Member
BalusC commented Mar 17, 2015

This converter is application-specific and does not solve a general JSF problem.

@BalusC BalusC closed this Mar 17, 2015
@djmj
djmj commented Mar 17, 2015

Can you elaborate a little bit more why it is application specific?

How else shall i create a bookmarkable URL which must be used outside the navigation components such as outputLink, link?

JSF ViewHandler is responsible of generating those URL's. Especially if an URL rewriting solution is used. Within markup i would need to duplicate the rewriting rules to create the final URL.

@BalusC BalusC added the wontfix label Oct 3, 2015
@djmj
djmj commented Mar 25, 2016

I rethought it often but i still don't think its application specific (I already use it in three applications). It solves a general application need that JSF is not capable of providing.

The view itself does not know its rewritten URI's in case a rewrite solution is used. So in some way we need to produce the rewritten url to be displayed in html code. Thats the converter doing.

Example use cases:

Create any SEO meta tags

like canonical or next

<!-- canonical -->
<o:outputFormat value="#{view.viewId}" var="varLinkHrefCanonical"converter="BookmarkableUrlConverter" >
    <o:param name="foo" value="#{cc.attrs.foo}" converter="#{cc.attrs.fooConverter}" />
</o:outputFormat>
<link rel="canonical" href="#{varLinkHrefCanonical}"/>

<!-- next -->
<o:outputFormat value="#{view.viewId}" var="varLinkHrefNext" converter="BookmarkableUrlConverter" >
    <o:param name="foo" value="#{cc.attrs.foo}" converter="#{cc.attrs.fooConverter}" />
    <f:param name="page" value="#{cc.attrs.pageIndex + 1}"/>
</o:outputFormat>
<link rel="next" href="#{varLinkHrefNext}"/>

Anywhere where url is needed in javascript

Like google SiteSearch markup

<!-- google sitesearch markup -->

<o:outputFormat value="#{view.viewId}" var="varSiteSearchBoxUrl" converter="BookmarkableUrlConverter" >
    <o:param name="foo" value="#{cc.attrs.foo}" converter="#{cc.attrs.fooConverter}" />
</o:outputFormat>

<o:outputFormat value="#{view.viewId}" var="varSiteSearchBoxTarget" converter="BookmarkableUrlConverter" >
    <o:param name="foo" value="#{cc.attrs.foo}" converter="#{cc.attrs.fooConverter}" />
    <f:param name="query" value="search_term_string"/>
</o:outputFormat>

<script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "WebSite",
        "url": "#{Servlets:getRequestDomainURL(request)}#{varSiteSearchBoxUrl}",
        "potentialAction": {
            "@type": "SearchAction",
            "target": "#{Servlets:getRequestDomainURL(request)}#{varSiteSearchBoxTarget.replaceFirst("search_term_string", "{search_term_string}")}",
            "query-input": "required name=search_term_string"
        }
    }
</script>

Show Permalink

for a news or blog post

<!-- show permalink in overlay on click -->
<p:button id="permaLink" icon="fa fa-link" onclick="return false;" title="Permalink"/>
<p:overlayPanel for="permaLink" showCloseIcon="true" dynamic="true">
    <table>
        <tr>
            <td><h:outputLabel for="permaLinkInput" value="Permalink:"/></td>
            <td>
                <h:outputText value="#{view.viewId}" converter="BookmarkableUrlConverter" var="varLinkHrefCanonical">
                    <f:attribute name="#{BookmarkableUrlConverter.ATTRIBUTE_FQDN}" value="#{Servlets:getRequestDomainURL(request)}"/>
                    <o:param name="category" value="#{cc.attrs.entity.category}" converter="#{cc.attrs.categoryConverter}" disable="#{cc.attrs.entity.category eq null}"/>
                    <o:param name="post" value="#{cc.attrs.entity}" converter="#{cc.attrs.postConverter}"/>
                </h:outputText>
            </td>
        </tr>
    </table>
</p:overlayPanel>
@djmj djmj referenced this issue Apr 23, 2016
Closed

Reevaluate #113 #242

@BalusC
Member
BalusC commented May 21, 2016 edited

Reconsidering. An <o:url> makes more sense for those specific use cases. It would be a good JSF alternative to the well known <c:url> which was available in JSP but not anymore in Facelets.

@BalusC BalusC reopened this May 21, 2016
@BalusC BalusC added a commit that closed this issue May 21, 2016
@BalusC BalusC Fix #113: add <o:url> 45c131a
@BalusC BalusC closed this in 45c131a May 21, 2016
@BalusC
Member
BalusC commented May 21, 2016 edited

The <o:url> has been added and is available in today's 2.4-SNAPSHOT.

Some usage examples:

<p>Full URL of current page is: <o:url /></p>
<p>Full URL of current page including view params is: <o:url includeViewParams="true" /></p>
<p>Full URL of current page on a different domain is: <o:url domain="sub.example.com" /></p>
<p>Domain-relative URL of current page is: <o:url domain="/" /></p>
<p>Scheme-relative URL of current page is: <o:url domain="//" /></p>

Thank you for the inspiration!

@djmj
djmj commented May 21, 2016

Looks great, and thank you to.

@BalusC BalusC added a commit that referenced this issue Jun 9, 2016
@BalusC BalusC #113: clarified o:url javadoc 38e45ab
@BalusC BalusC added a commit that referenced this issue Jun 9, 2016
@BalusC BalusC #113: extract ParamValue type when injection target itself is ParamValue
Fix potential NPE on firstValue
58dc46e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment