views:

133

answers:

5

Hi!

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

The 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,

Is there anyway to inject values from the URL fragments into server side beans?

+1  A: 

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.

Stephen C
Thanks for your idea.I am looking for a way to catch the URL when there is a page reload (GET) and than do getFragment(), parse it correctly to get the values and than set the respective variables.
tchoesang
It's okay that the method exists there, but browsers don't send fragment info to the server. It can be seen by logging HTTP traffic.
pcjuzer
While technically correct, this answer is practically and fundamentally wrong. The fragment set by the webbrowser [ **will not** ](http://en.wikipedia.org/wiki/Fragment_identifier#Processing) arrive at the webserver.
BalusC
Good point. However you *can* send a URL with a fragment ... if you encode it and send it as a query parameter to a different URL. That's how a lot of annotation and bookmarking tools work.
Stephen C
+1  A: 

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;
}
clacke
tchoesang
Added example code. However, I would suggest that you instead write code to parse the params and make the appropriate AJAX request to reconstruct the bookmarked state. Also, my code example is too simple for your needs, it doesn't check for e.g. duplicated params and will therefore run into problems when the user starts clicking around and you start adding things to the fragment again.
clacke
The code example is clumsy. Just `window.location.hash` gives you already the fragment and `window.location.search` the query string. Read the [`window.location`](https://developer.mozilla.org/en/window.location) docs.
BalusC
You read an old version of the example. I already corrected it before you commented.
clacke
Right, this must have been a too-long-opened browser tab :)
BalusC
+1  A: 

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.
pcjuzer
Great reference. Especially the [ajaxpatterns](http://ajaxpatterns.org/Unique_URLs) link.
clacke
+3  A: 

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"&gt;
    <h:head>
        <title>SO question 3475076</title>
        <script>
            window.onload = window.onhashchange = function() {
                var fragment = document.getElementById('fragment');
                fragment.value = window.location.hash;
                fragment.onchange();
            }
        </script>
        <style>.hide { display: none; }</style>
    </h:head>
    <h:body>
        <h:form id="processFragment" prependId="false" 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.

BalusC
Wow, that is simply awesome, thanks for sharing. Now I need to take a look at JSF 2.
clacke
A: 

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);
}   };  
tchoesang