I am looking for a way to inject values from the fragment (#) of a URL into bean(JSF), in the same way query-parameter values are injected. I am using Ben Alman\'s Bookmarka
The fragment cannot be seen from the server-side, it can only be accessed by client-side scripts. The way it's usually done is that the server-side generates a non-parameterized page, which is then modified by scripts in accordance with the fragment parameters. The scripts could make AJAX requests with query parameters, where the AJAX responses are generated by JSF using beans controlled by the parameters.
If you absolutely want the server-side to have access to the fragment parameters when rendering the page itself, you need to reload the page with the parameters as query parameters instead.
EDIT: To reload the page you could use this code:
if (window.location.hash != '') {
var newsearch = window.location.search;
if(newsearch != '') {
newsearch += '&';
}
newsearch += window.location.hash.match(/#?(.*)/)[1];
window.location.hash = '';
window.location.search = newsearch;
}
You can do this with help of window.onhashchange which fills an input field of a hidden form which submits itself asynchronously when the input field has changed.
Here's a kickoff example of the Facelets page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>SO question 3475076</title>
<script>
window.onload = window.onhashchange = function() {
var fragment = document.getElementById("processFragment:fragment");
fragment.value = window.location.hash;
fragment.onchange();
}
</script>
<style>.hide { display: none; }</style>
</h:head>
<h:body>
<h:form id="processFragment" class="hide">
<h:inputText id="fragment" value="#{bean.fragment}">
<f:ajax event="change" execute="@form" listener="#{bean.processFragment}" render=":showFragment" />
</h:inputText>
</h:form>
<p>Change the fragment in the URL. Either manually or by those links:
<a href="#foo">foo</a>, <a href="#bar">bar</a>, <a href="#baz">baz</a>
</p>
<p>Fragment is currently: <h:outputText id="showFragment" value="#{bean.fragment}" /></p>
</h:body>
</html>
Here's how the appropriate bean look like:
package com.stackoverflow.q3475076;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.AjaxBehaviorEvent;
@ManagedBean
@RequestScoped
public class Bean {
private String fragment;
public void processFragment(AjaxBehaviorEvent event) {
// Do your thing here. This example is just printing to stdout.
System.out.println("Process fragment: " + fragment);
}
public String getFragment() {
return fragment;
}
public void setFragment(String fragment) {
this.fragment = fragment;
}
}
That's all.
Note that the onhashchange
event is relatively new and not supported by the older browsers. In absence of the browser support (undefinied and so on), you'd like to check window.location.hash at intervals using setInterval() instead. The above code example should at least give a good kickoff. It works at at least FF3.6 and IE8.
Thank you very much for your ideas & specially BalusC (much kudos to you). My final solution was a slight mutation and i would like to share with you all.
Since I am using richfaces 3.3.3, it is like using jsf1.2, hence no <f:ajax>
In a conventional situation, you are expected to click on a link with a href pointing to an internal link <a id='internalLink'href='#internalLink'>
. I am using many prepackaged components where not every clickable item is a link <a>
. It can be any html element. It was very useful to use Ben Alman's Bookmarkable jQuery plugin (http://benalman.com/projects/jquery-bbq-plugin/)
Please note below that, onclick I can programmatically set the value of the fragment as key/value and in the following code I replace the old value with a new value jQuery.bbq.pushState(state,2)
(or I could have simply added behind the old value jQuery.bbq.pushState(state)
).
In my case, ws->md->nd
is a hierarchical data where it is enough to have one of them and its parents are calculated on server, so i replace the parent value with child value.
<ui:repeat value="#{workspaceController.modulesForWorkspace}" var="item">
<a4j:commandLink onclick="var state = {}; state['md'] = '#{item.uuid}';
jQuery.bbq.pushState( state, 2 );"
action="#{workspaceController.setSelectedModule}"
reRender="localesList"
oncomplete="selectNavigationElement(this.parentNode>
<f:param name="selectedModule" value="#{item.uuid}"/>
</a4j:commandLink>
</ui:repeat>
In richfaces, this is a cool way to call the setter methods from browser with a parameter. Note that sendURLFragment
is treated as a JS method with 5 parameters and the values are passed to the server (which is very cool).
<h:form id="processFragment" prependId="false" style="display: none">
<h:inputText id="fragment" value="#{workspaceController.selectedWorkspace}">
<a4j:support event="onchange"/>
</h:inputText>
<a4j:jsFunction name="sendURLFragment" reRender="workspaces">
<a4j:actionparam name="ws" assignTo="#{workspaceController.selectedWorkspace}"/>
<a4j:actionparam name="md" assignTo="#{workspaceController.selectedModule}"/>
<a4j:actionparam name="nd" assignTo="#{workspaceController.selectedContentNode}"/>
<a4j:actionparam name="ld" assignTo="#{workspaceController.selectedLOD}"/>
<a4j:actionparam name="ln" assignTo="#{workspaceController.contentNodeTreeLocale}"
converter="localeConverter"/>
</a4j:jsFunction>
</h:form>
Do the thing, when you copy-paste the new url and load the page.
window.onload = function(){
if(window.location.hash != ""){
//Parse the fragment (hash) from a URL, deserializing it into an object
var deparamObj = jQuery.deparam.fragment();
var ws= '', md= '', nd= '', ld= '', ln = '';
jQuery.each(deparamObj, function(key, value) {
switch(key){
case 'ws':
ws = value;
break;
case 'md':
md = value;
break;
case 'nd':
nd = value;
break;
case 'ld':
ld = value;
break;
case 'ln':
ln = value;
break;
default:
break;
}
});
sendURLFragment(ws,md,nd,ld,ln);
} };
Here's the most reliable way to extract a fragment from a syntactically valid URL / URI.
URI uri = new URI(someString);
String fragment = uri.getFragment();
How you inject this into a bean will depend on what server-side framework you are using, and whether you are doing the injection using XML or annotations, or doing it programmaticaly.
Watch this question and especially the answers: JSP Servlet anchor