HTML5 Local Storage example with PrimeFaces

UI Components for JSF
Post Reply
gadnex
Posts: 32
Joined: 25 Jun 2010, 15:29

23 Jul 2014, 17:51

I recently saw a Web Application that uses the HTML5 Local Storage feature to store incompletely filled HTML forms in the browser to avoid retyping everything if you close your browser or navigate away from the page and back again later.

I decided to build a working version of this functionality using JSF and PrimeFaces. Here is the full code for my working example:

Facelets XHTML with JavaScript

Code: Select all

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>HTML5 Local Storage</title>
        <script>
            function loadLocalStorage() {
                var prefix = 'prefix';
                getLocalStorageValue(prefix, 'firstName');
                getLocalStorageValue(prefix, 'surname');
                getLocalStorageValue(prefix, 'dateOfBirth_input');
                getLocalStorageValue(prefix, 'notes');
            }

            function clearLocalStorage(prefix) {
                localStorage.removeItem(prefix);
//                alert(localStorage.getItem(prefix));
            }

            function getLocalStorageValue(prefix, elementId) {
                if (localStorage.getItem(prefix)) {
                    var element = document.getElementById(elementId);
                    if (element.value === '') {
                        var prefixValue = JSON.parse(localStorage.getItem(prefix));
                        if (prefixValue[elementId]) {
                            element.value = prefixValue[elementId];
                        }
                    }
                }
            }

            function setLocalStorageValue(prefix, elementId) {
                if (!localStorage.getItem(prefix)) {
                    localStorage.setItem(prefix, JSON.stringify({}));
                }
                var element = document.getElementById(elementId);
                var prefixValue = JSON.parse(localStorage.getItem(prefix));
                prefixValue[elementId] = element.value;
//                alert(JSON.stringify(prefixValue));
                localStorage.setItem(prefix, JSON.stringify(prefixValue));
            }
        </script>
    </h:head>
    <h:body>
        <h:form prependId="false">
            <p:panelGrid columns="2">
                <f:facet name="header">
                    <h:outputText value="Registration Form"/>
                </f:facet>
                <p:outputLabel for="firstName"
                               value="First Name:"/>
                <p:inputText id="firstName"
                             widgetVar="firstName"
                             onchange="setLocalStorageValue('prefix', 'firstName')"
                             value="#{registrationBean.firstName}"/>
                <p:outputLabel for="surname"
                               value="Surname:"/>
                <p:inputText id="surname"
                             widgetVar="surname"
                             onchange="setLocalStorageValue('prefix', 'surname')"
                             value="#{registrationBean.surname}"/>
                <p:outputLabel for="dateOfBirth"
                               value="Date of Birth:"/>
                <p:calendar id="dateOfBirth"
                            widgetVar="dateOfBirth"
                            onchange="setLocalStorageValue('prefix', 'dateOfBirth_input')"
                            value="#{registrationBean.dateOfBirth}"/>
                <p:outputLabel for="notes"
                               value="Notes:"/>
                <p:inputTextarea id="notes"
                                 widgetVar="notes"
                                 onchange="setLocalStorageValue('prefix', 'notes')"
                                 value="#{registrationBean.notes}"/>
                <f:facet name="footer">
                    <div align="right">
                        <p:commandButton type="reset"
                                         value="Reset"
                                         onclick="clearLocalStorage('prefix')"/>
                        <p:commandButton value="Register"
                                         onclick="clearLocalStorage('prefix')"/>
                    </div>
                </f:facet>
            </p:panelGrid>
        </h:form>
    </h:body>
</html>
JSF Managed Bean

Code: Select all

package jsf;

import java.util.Date;
import javax.annotation.PostConstruct;
import javax.inject.Named;
import javax.enterprise.context.RequestScoped;
import javax.faces.context.FacesContext;
import org.primefaces.context.RequestContext;

@Named(value = "registrationBean")
@RequestScoped
public class RegistrationBean {

    private String firstName;
    private String surname;
    private Date dateOfBirth;
    private String notes;

    public RegistrationBean() {
//        firstName = "Default";
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    public String getNotes() {
        return notes;
    }

    public void setNotes(String notes) {
        this.notes = notes;
    }

    @PostConstruct
    public void init() {
        if (!FacesContext.getCurrentInstance().isPostback()) {
            RequestContext.getCurrentInstance().execute("loadLocalStorage()");
        }
    }
}
I thought that this might be useful for other PrimeFaces users and perhaps this type of functionality could even be incorporated into a future version of PrimeFaces if it proves to be popular. If incorporated into PrimeFaces the code for the Facelets XHTML could look something like the following and nothing special would be required in the JSF Managed Bean.

Code: Select all

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>HTML5 Local Storage</title>
    </h:head>
    <h:body>
        <h:form prependId="false">
            <p:panelGrid columns="2">
                <f:facet name="header">
                    <h:outputText value="Registration Form"/>
                </f:facet>
                <p:outputLabel for="firstName"
                               value="First Name:"/>
                <p:inputText id="firstName"
                             widgetVar="firstName"
                             localStorage="prefix"
                             value="#{registrationBean.firstName}"/>
                <p:outputLabel for="surname"
                               value="Surname:"/>
                <p:inputText id="surname"
                             widgetVar="surname"
                             localStorage="prefix"
                             value="#{registrationBean.surname}"/>
                <p:outputLabel for="dateOfBirth"
                               value="Date of Birth:"/>
                <p:calendar id="dateOfBirth"
                            widgetVar="dateOfBirth"
                            localStorage="prefix"
                            value="#{registrationBean.dateOfBirth}"/>
                <p:outputLabel for="notes"
                               value="Notes:"/>
                <p:inputTextarea id="notes"
                                 widgetVar="notes"
                                 localStorage="prefix"
                                 value="#{registrationBean.notes}"/>
                <f:facet name="footer">
                    <div align="right">
                        <p:commandButton type="reset"
                                         value="Reset"
                                         localStorage="prefix"/>
                        <p:commandButton value="Register"
                                         localStorage="prefix"/>
                    </div>
                </f:facet>
            </p:panelGrid>
        </h:form>
    </h:body>
</html>
In the code snippet above I basically removed all the JavaScript as this could be included in the standard PrimeFaces JavaScript. The PrimeFaces components would need to be extended to include a localStorage attribute used to specify that localStorage need to be enabled for the components and what the prefix should be. The prefix could even be an EL expression.

I would love some feedback if anyone is interested.
Gadnex
PrimeFaces Version = 5
Server = GlassFish 4

iljkr
Posts: 35
Joined: 30 Dec 2013, 20:52

24 Mar 2020, 15:33

If anyone needs it, I also propose my solution.


The backing bean:

Code: Select all

@Component("pocLocalStorageBean")
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class PocLocalStorageBean {
	
	private static final long serialVersionUID = 1L;
	
	private String[] selectedCities;
	private List<String> cities;
    
    @PostConstruct
    public void initialize() {
    		List<String> cities = new ArrayList<String>();
            cities.add("Miami");
            cities.add("London");
            cities.add("Paris");
            cities.add("Istanbul");
            cities.add("Berlin");
            cities.add("Barcelona");
            cities.add("Rome");
            cities.add("Brasilia");
            cities.add("Amsterdam");
            
            setCities(cities);
            
    }
    
    //GETTER E SETTER HERE    
}
The page xhtml:

Code: Select all

    <h:form id="yuorFormId" cache="false">
    
     <p:remoteCommand name="updateUiAfterLoadChoicesFromLocalStorage" update=":yuorFormId:yourSelectManyCheckboxId">
     </p:remoteCommand>
 	<p:remoteCommand oncomplete="loadCitiesChoicesFromLocalStorage(#{pocLocalStorageBean.cities.size()});" autoRun="true">
     </p:remoteCommand>
    
    <div class="ui-g ui-fluid">
    
      <div class="ui-g-12 ui-md-12">
      
      		<div class="card">

		     		<p:selectManyCheckbox id="yourSelectManyCheckboxId" value="#{pocLocalStorageBean.selectedCities}" layout="responsive" columns="3">
						<f:selectItems value="#{pocLocalStorageBean.cities}" var="city" itemLabel="#{city}" itemValue="#{city}"/>
		    			<p:ajax oncomplete="setCitiesChoicesToLocalStorage(#{pocLocalStorageBean.cities.size()})"/>
		    		</p:selectManyCheckbox>
        
        		</div>
        
        	 </div>
        </div>
        
    </h:form>
And the javascript functions:

Code: Select all

function findPreviousChoose(arrayChoicesFromLocalStorage,valueToFind){
	if(arrayChoicesFromLocalStorage != null && arrayChoicesFromLocalStorage.length > 0){
		var arrayLength = arrayChoicesFromLocalStorage.length;
		for (var j = 0; j < arrayLength; j++) {
		    var iteratedChoose = arrayChoicesFromLocalStorage[j];
		    if(iteratedChoose!=null){
		    	if(iteratedChoose.value == valueToFind){
			    	return iteratedChoose;
			    }
		    }
		}
	}
	return null;
}

function parseSafeJSON(str){
	try {
		if(str!=null){
			var obj = JSON.parse(str);
			return obj;
		}
	} catch (ex) {
	  return null;
	}
	return null;
}

function loadCitiesChoicesFromLocalStorage(citiesNumber){
	var arrayChoicesFromLocalStorageStringed = localStorage.getItem('CITIES_LOCAL_STORE_KEY');
	if(arrayChoicesFromLocalStorageStringed!=null){
		
		var arrayChoicesFromLocalStorage = parseSafeJSON(arrayChoicesFromLocalStorageStringed);
		var elementId = 'yuorFormId:yourSelectManyCheckboxId';
	    var element = document.getElementById(elementId);
	    
	    var i;
	    for (i = 0; i < citiesNumber; i++) {
	    	var elementIterated = document.getElementById(elementId+':'+i);
	    	var valueIterated = elementIterated.value;
	    	var previousChoose = findPreviousChoose(arrayChoicesFromLocalStorage,valueIterated);
	    	if(previousChoose != null) {
	    		elementIterated.defaultChecked = previousChoose.defaultChecked;
	    	}
	    }
	    
	    //update the needed components:
	    updateUiAfterLoadChoicesFromLocalStorage();
		
	}

}

function setCitiesChoicesToLocalStorage(citiesNumber) {
	
	var elementId = 'yuorFormId:yourSelectManyCheckboxId';
    var element = document.getElementById(elementId);
    
    var i;

    var arrayChoices = new Array();
    for (i = 0; i < citiesNumber; i++) {
    	var elementIterated = document.getElementById(elementId+':'+i);
    	var valueIterated = elementIterated.value;
    	var defaultCheckedIterated = elementIterated.checked;
    	var objIterated = { "value":valueIterated, "defaultChecked":defaultCheckedIterated};
    	arrayChoices.push(objIterated);
    }
    
    var storageValuesArray= JSON.stringify(arrayChoices);
    localStorage.setItem('CITIES_LOCAL_STORE_KEY', storageValuesArray);
    
}
javax.faces-2.3 | primefaces-7 | tomcat 8.5

Melloware
Posts: 3717
Joined: 22 Apr 2013, 15:48

25 Mar 2020, 13:41

Nice. Thanks for posting!
PrimeFaces Developer | PrimeFaces Extensions Developer
GitHub Profile: https://github.com/melloware
PrimeFaces Elite 13.0.0 / PF Extensions 13.0.0
PrimeReact 9.6.1

Post Reply

Return to “PrimeFaces”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 33 guests