New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: BookmarkableUrlConverter #113

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

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

This comment has been minimized.

Show comment
Hide comment
@BalusC

BalusC Mar 17, 2015

Member

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

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

This comment has been minimized.

Show comment
Hide comment
@djmj

djmj 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.

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

This comment has been minimized.

Show comment
Hide comment
@djmj

djmj 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 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

This comment has been minimized.

Show comment
Hide comment
@BalusC

BalusC May 21, 2016

Member

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.

Member

BalusC commented May 21, 2016

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 closed this in 45c131a May 21, 2016

BalusC added a commit that referenced this issue May 21, 2016

@BalusC

This comment has been minimized.

Show comment
Hide comment
@BalusC

BalusC May 21, 2016

Member

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!

Member

BalusC commented May 21, 2016

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

This comment has been minimized.

Show comment
Hide comment
@djmj

djmj May 21, 2016

Looks great, and thank you to.

djmj commented May 21, 2016

Looks great, and thank you to.

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