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 Bookmarkable jQuery plugin (http://benalman.com/projects/jquery-bbq-plugin/) to create the URL fragments. I was hoping that Custom regex patterns from prettyFaces could be a way to solve my problem but until now I have been unsuccessful.
(http://ocpsoft.com/docs/prettyfaces/snapshot/en-US/html_single/#config.pathparams.regext)
I would like to define here my situation and if any one has an idea, i would love to try them out.
I am using
RichFaces: 3.3.3, Spring: 3.0.2.RELEASE, Hibernate: 3.5.3-Final, JSF: 2.0.2-FCS, PrettyFaces: 3.0.1The web application generates, following kind of URL where parameters are listed after a hash(#). The idea is to have an ajax based Bookmarkable URL. So every time I click on an element that changes the state of the system, the value is sent to the server via ajax and the URL after the hash is rewritten. There can be 1 to 3 parameters after the hash, the number of parameters are optional.
My goal is, when the user bookmarks the URL (with hash) and than revisits the saved page, the page should inject the correct values into the system and visualize the page in the previous state (like query-parameter).
Below, I have a regular expression that would catch all the parameters after the hash.
//URL:
http://localhost:8080/nymphaea/workspace/#node=b48dd073-145c-4eb6-9ae0-e1d8ba90303c&lod=75e63fcd-f94a-49f5-b0a7-69f34d4e63d7&ln=en
//Regular Expression:
\#(\w*\=(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}))|\&(\w*\=(\w{8}-\w{4}-\w{4}-\w{4}-\w{12}))|\&(\w*\=\w{2})
I know there are websites that some how send the URL fragment into there server side logic,
- http://maps.yahoo.com/#mvt=m&lat=36.952736&lon=-95.84758&zoom=11&tt=starbucks&tp=1&ioride=us
- http://www.cbc.ca/video/#/Shows/Death_Comes_to_Town/ID=1365210427
Is there anyway to inject values from the URL fragments into server side beans?
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.
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
- Fragment cannot be seen on server-side.
- You can extract fragment on client-side, convert it and send to the server via Ajax.
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;
}
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);
} };
精彩评论