70

I have this structure:

WebContent
    resources
        components
            top.xhtml

    company
        about_us.xhtml

    index.xhtml

top.xhtml is a component, that is used in index.xthml and about_us.xhtml too.

top.xhtml

<ul>
    <li><a href="index.xhtml">Home</a></li>
    <li><a href="company/about_us.xhtml">About us</a></li>
    ...
</ul>

So my problem is, when the current page is index.xhtml the component generates URLs correctly, but when the current page is about_us.xhtml, it generates wrong URLs. I cannot use relative path because it's going to generate the wrong URL too. I think it is because the component is based on the current path of the *.xhtml page.

The only solution I could found out is:

<ul>
    <li><a href="${pageContext.request.contextPath}/webname/index.xhtml">Home</a></li>
    <li><a href="${pageContext.request.contextPath}/webname/about_us.xhtml">About us</a></li>
    ...
</ul>

But I think is not 'elegant' at all. Any ideas?

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
Valter Silva
  • 15,646
  • 48
  • 123
  • 210

2 Answers2

152

URLs are not resolved based on the file structure in the server side. URLs are resolved based on the real public web addresses of the resources in question. It's namely the webbrowser who has got to invoke them, not the webserver.

There are several ways to soften the pain:

JSF EL offers a shorthand to ${pageContext.request} in flavor of #{request}:

<li><a href="#{request.contextPath}/index.xhtml">Home</a></li>
<li><a href="#{request.contextPath}/about_us.xhtml">About us</a></li>

You can if necessary use <c:set> tag to make it yet shorter. Put it somewhere in the master template, it'll be available to all pages:

<c:set var="root" value="#{request.contextPath}/" />
...
<li><a href="#{root}index.xhtml">Home</a></li>
<li><a href="#{root}about_us.xhtml">About us</a></li>

JSF 2.x offers the <h:link> which can take a view ID relative to the context root in outcome and it will append the context path and FacesServlet mapping automatically:

<li><h:link value="Home" outcome="index" /></li>
<li><h:link value="About us" outcome="about_us" /></li>

HTML offers the <base> tag which makes all relative URLs in the document relative to this base. You could make use of it. Put it in the <h:head>.

<base href="#{request.requestURL.substring(0, request.requestURL.length() - request.requestURI.length())}#{request.contextPath}/" />
...
<li><a href="index.xhtml">Home</a></li>
<li><a href="about_us.xhtml">About us</a></li>

(note: this requires EL 2.2, otherwise you'd better use JSTL fn:substring(), see also this answer)

This should end up in the generated HTML something like as

<base href="http://example.com/webname/" />

Note that the <base> tag has a caveat: it makes all jump anchors in the page like <a href="#top"> relative to it as well! See also Is it recommended to use the <base> html tag? In JSF you could solve it like <a href="#{request.requestURI}#top">top</a> or <h:link value="top" fragment="top" />.

Community
  • 1
  • 1
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • 3
    thank you so much, i'm a big fan of yours =) i hope be a great developer as you are mate, thanks. – Valter Silva Jul 29 '11 at 20:38
  • Nice solution, didn't know that was possible! thanks for sharing! – Robert M. Jul 30 '11 at 16:43
  • Thanks, i have a follow-up question: the correct path for the link would be "/appname/faces/page.xhtml", but with #{request.contextPath} i only get /appname. is there a way to get the /faces/ too? where is that even defined? – styx Apr 25 '12 at 15:28
  • @styx: use `#{request.servletPath}` or use ``. – BalusC Apr 25 '12 at 15:31
  • @BalusC thanks, very helpful as usual :) i needed the first one, since i use the path inside a tag. thanks again! – styx Apr 26 '12 at 09:46
  • @BalusC: I tried your `ui:param` approach & it does work with Mojarra but not with Myfaces 2.1.8. I created an [issue](https://issues.apache.org/jira/browse/MYFACES-3595) thinking it was bug but learnt from there that `` should be direct child of `` or `` to work. See this issue for the discussion on that. – Aklin Aug 23 '12 at 18:51
  • 1
    @balusC yes, the ui:param approach doesn't work with myfaces. – Rajat Gupta Aug 26 '12 at 18:20
  • 1
    @Aklin: I fixed the answer, thank you, I wasn't aware about this being Mojarra specific, I've actually also never seen a Mojarra issue report about this. – BalusC Aug 27 '12 at 12:22
  • `` does not seem to make the `#{root}` work for all the included pages. I works only when `#{root}` is used within the defined page only.. – Aklin Aug 30 '12 at 09:45
  • 1
    @Aklin: Perhaps it's another MyFaces thing again. Try adding `scope="request"`. – BalusC Aug 30 '12 at 10:24
  • Thanks, after adding `scope="request"` now it works! However I was wondering I should use `application` scope for this particular case since the path would remain the same for all requests & all users application wide !? – Aklin Aug 30 '12 at 12:19
  • Notice that the attempt to set "base" as shown above will not work as expected in an proxy-environment (example: apache with mod_proxy, forwarding somedomain/app to somedomain:8080/app; above attempt will set "base" to somedomain:8080/app instead of -somehow- expected somedomain/app. – Markus Schulte Mar 21 '13 at 11:25
  • I am working on a legacy application with JSF1.0. `value="#{facesContext.externalContext.requestContextPath}/` worked for me, while `value="#{request.contextPath}/"` was not working. – vinS Dec 06 '18 at 21:03
1

JSTL 1.2 variation leveraged from BalusC answer

<c:set var="baseURL" value="${pageContext.request.requestURL.substring(0, pageContext.request.requestURL.length() - pageContext.request.requestURI.length())}${pageContext.request.contextPath}/" />

<head>
  <base href="${baseURL}" />
Community
  • 1
  • 1
JET
  • 11
  • 1