Auto-scroll on add ingredient, add Ingredient Group
This commit is contained in:
parent
a06d8e8f79
commit
e859a51772
|
@ -92,10 +92,12 @@ public class JSFUtils {
|
||||||
*/
|
*/
|
||||||
public static Object getFlash(String key) {
|
public static Object getFlash(String key) {
|
||||||
Flash flash = flashScope();
|
Flash flash = flashScope();
|
||||||
if ( flash == null ) {
|
try {
|
||||||
|
return flash.get(key);
|
||||||
|
} catch ( NullPointerException nex ) {
|
||||||
|
// If session is expired, flash.get() will fail!
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return flashScope().get(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void putFlash(String key, Object value) {
|
public static void putFlash(String key, Object value) {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import javax.inject.Named;
|
||||||
import javax.servlet.http.Part;
|
import javax.servlet.http.Part;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.primefaces.PrimeFaces;
|
|
||||||
import org.primefaces.event.FileUploadEvent;
|
import org.primefaces.event.FileUploadEvent;
|
||||||
import org.primefaces.model.UploadedFile;
|
import org.primefaces.model.UploadedFile;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -40,8 +39,6 @@ import com.mousetech.gourmetj.springweb.PictureController;
|
||||||
* Backing bean for display/edit recipe detail
|
* Backing bean for display/edit recipe detail
|
||||||
*
|
*
|
||||||
* @author timh
|
* @author timh
|
||||||
* @since Jun 28, 2012 TODO: Cross-reference ingredients to
|
|
||||||
* keylookup TODO: Cross-reference shopcats
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Named
|
@Named
|
||||||
|
@ -616,7 +613,7 @@ public class RecipeDetailBean implements Serializable {
|
||||||
|
|
||||||
if (recipeService.save(this.getRecipe())) {
|
if (recipeService.save(this.getRecipe())) {
|
||||||
userSession.setRecipe(null);
|
userSession.setRecipe(null);
|
||||||
return "main";
|
return "recipeDetails";
|
||||||
} else {
|
} else {
|
||||||
JSFUtils.addErrorMessage("Save recipe failed");
|
JSFUtils.addErrorMessage("Save recipe failed");
|
||||||
return null;
|
return null;
|
||||||
|
@ -1031,12 +1028,11 @@ public class RecipeDetailBean implements Serializable {
|
||||||
* Add new group to bottom of model as AJAX operation.
|
* Add new group to bottom of model as AJAX operation.
|
||||||
* @return null
|
* @return null
|
||||||
*/
|
*/
|
||||||
public String doAddGroup() {
|
public void doAddGroup() {
|
||||||
IngredientUI iui = new IngredientUI(null);
|
IngredientUI iui = new IngredientUI(null);
|
||||||
iui.setIngGroup(true);
|
iui.setIngGroup(true);
|
||||||
iui.setItem(this.getNewGroupName());
|
iui.setItem(this.getNewGroupName());
|
||||||
List<IngredientUI> ingUIList = this.getWrappedIngredients();
|
List<IngredientUI> ingUIList = this.getWrappedIngredients();
|
||||||
ingUIList.add(iui);
|
ingUIList.add(iui);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.mousetech.gourmetj;
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
|
import javax.faces.application.ViewExpiredException;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ public class SpringPrimeFacesApplication {
|
||||||
|
|
||||||
final String errorPage = "/error/error.html";
|
final String errorPage = "/error/error.html";
|
||||||
final String error404Page = "/error/error404.html";
|
final String error404Page = "/error/error404.html";
|
||||||
|
final String expiredPage = "/error/viewExpired.html";
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(SpringPrimeFacesApplication.class,
|
SpringApplication.run(SpringPrimeFacesApplication.class,
|
||||||
|
@ -59,6 +61,9 @@ public class SpringPrimeFacesApplication {
|
||||||
ErrorPageRegistry registry) {
|
ErrorPageRegistry registry) {
|
||||||
registry.addErrorPages(new ErrorPage(
|
registry.addErrorPages(new ErrorPage(
|
||||||
HttpStatus.NOT_FOUND, error404Page));
|
HttpStatus.NOT_FOUND, error404Page));
|
||||||
|
registry.addErrorPages(new ErrorPage(
|
||||||
|
ViewExpiredException.class,
|
||||||
|
expiredPage));
|
||||||
registry.addErrorPages(new ErrorPage(
|
registry.addErrorPages(new ErrorPage(
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
errorPage));
|
errorPage));
|
||||||
|
|
|
@ -5,6 +5,10 @@ import java.io.Serializable;
|
||||||
import javax.enterprise.context.SessionScoped;
|
import javax.enterprise.context.SessionScoped;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.primefaces.PrimeFaces;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.mousetech.gourmetj.persistence.model.Category;
|
import com.mousetech.gourmetj.persistence.model.Category;
|
||||||
import com.mousetech.gourmetj.persistence.model.Recipe;
|
import com.mousetech.gourmetj.persistence.model.Recipe;
|
||||||
|
|
||||||
|
@ -18,6 +22,12 @@ public class UserSession implements Serializable {
|
||||||
private static final long serialVersionUID =
|
private static final long serialVersionUID =
|
||||||
7449440266704831598L;
|
7449440266704831598L;
|
||||||
|
|
||||||
|
/* Logger */
|
||||||
|
|
||||||
|
private static final Logger log =
|
||||||
|
LoggerFactory.getLogger(UserSession.class);
|
||||||
|
|
||||||
|
|
||||||
private String lastSearch = "";
|
private String lastSearch = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +122,7 @@ public class UserSession implements Serializable {
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String formatTime(Long ltime) {
|
public String formatTime(Long ltime) {
|
||||||
if (ltime == null) {
|
if (ltime == null) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -145,4 +155,28 @@ public class UserSession implements Serializable {
|
||||||
}
|
}
|
||||||
return sb.toString().trim();
|
return sb.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Primefaces handle session timeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session timeout, msec.
|
||||||
|
*/
|
||||||
|
private long sessionTimeoutInterval = 300000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the sessionTimeoutInterval
|
||||||
|
*/
|
||||||
|
public long getSessionTimeoutInterval() {
|
||||||
|
return sessionTimeoutInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sessionIdleListener() {
|
||||||
|
log.warn("Session Idle Listener fired.");
|
||||||
|
PrimeFaces.current()
|
||||||
|
.executeScript("sessionExpiredConfirmation.show()");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logoutAction() {
|
||||||
|
log.warn("Session Idle listener logout");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,30 @@
|
||||||
href="http://www.apache.org/licenses/LICENSE-2.0"
|
href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
>Apache License, Version 2.0</a>.
|
>Apache License, Version 2.0</a>.
|
||||||
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
||||||
|
<h:form id="ftmTimeout">
|
||||||
|
<p:idleMonitor
|
||||||
|
timeout="#{userSession.sessionTimeoutInterval}"
|
||||||
|
onidle="sessionExpiredConfirmation.show()"
|
||||||
|
>
|
||||||
|
<p:ajax event="idle"
|
||||||
|
listener="#{userSession.sessionIdleListener}"
|
||||||
|
/>
|
||||||
|
</p:idleMonitor>
|
||||||
|
<p:confirmDialog closable="false"
|
||||||
|
id="sessionExpiredDlg"
|
||||||
|
message="Your session expired."
|
||||||
|
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
|
||||||
|
severity="alert"
|
||||||
|
widgetVar="sessionExpiredConfirmation"
|
||||||
|
style="z-index: 25000"
|
||||||
|
>
|
||||||
|
<p:commandButton id="confirmRouteDel"
|
||||||
|
value="Ok"
|
||||||
|
oncomplete="sessionExpiredConfirmation.hide()"
|
||||||
|
actionListener="#{userSession.logoutAction}"
|
||||||
|
/>
|
||||||
|
</p:confirmDialog>
|
||||||
|
</h:form>
|
||||||
</h:body>
|
</h:body>
|
||||||
</f:view>
|
</f:view>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<ui:composition template="/WEB-INF/layout/layout.xhtml">
|
<ui:composition template="/WEB-INF/layout/layout.xhtml">
|
||||||
<ui:define name="title">Gourmet Recipe Manager</ui:define>
|
<ui:define name="title">Gourmet Recipe Manager</ui:define>
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
|
<h:outputScript name="js/scrolltable.js"/>
|
||||||
<style>
|
<style>
|
||||||
.ingSel {
|
.ingSel {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
@ -307,6 +308,7 @@
|
||||||
action="#{recipeDetailBean.doAddIngredient}"
|
action="#{recipeDetailBean.doAddIngredient}"
|
||||||
process="@this ctlAddIngTxt"
|
process="@this ctlAddIngTxt"
|
||||||
update="pnlIngredients"
|
update="pnlIngredients"
|
||||||
|
oncomplete="scrollAndFocus('form1:tabGroupClient' , 'ingredientTable', 'ctlAddIngTxt');"
|
||||||
/>
|
/>
|
||||||
<p:outputLabel for="@next"
|
<p:outputLabel for="@next"
|
||||||
value="Add Ingredient: "
|
value="Add Ingredient: "
|
||||||
|
@ -352,12 +354,12 @@
|
||||||
</p:tab>
|
</p:tab>
|
||||||
</p:tabView>
|
</p:tabView>
|
||||||
<p:commandButton id="doSave" value="Save"
|
<p:commandButton id="doSave" value="Save"
|
||||||
icon="pi pi-check"
|
icon="ui-icon-pencil"
|
||||||
disabled="{not recipeDetailBean.dirty}"
|
disabled="{not recipeDetailBean.dirty}"
|
||||||
action="#{recipeDetailBean.doSave}"
|
action="#{recipeDetailBean.doSave}"
|
||||||
/>
|
/>
|
||||||
<p:commandButton id="doCancel" value="Cancel"
|
<p:commandButton id="doCancel" value="Cancel"
|
||||||
immediate="true" action="/main.jsf"
|
immediate="true" action="recipeDetails.jsf"
|
||||||
/>
|
/>
|
||||||
</h:form>
|
</h:form>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
@ -377,7 +379,7 @@
|
||||||
value="OK" style="width: 6em"
|
value="OK" style="width: 6em"
|
||||||
action="#{recipeDetailBean.doAddGroup}"
|
action="#{recipeDetailBean.doAddGroup}"
|
||||||
update="form1:tabGroupClient:ingredientTable"
|
update="form1:tabGroupClient:ingredientTable"
|
||||||
oncomplete="PF('addGroupDlg').hide()"
|
oncomplete="PF('addGroupDlg').hide(); scrollAndFocus('form1:tabGroupClient' , 'ingredientTable', 'ctlAddIngTxt');"
|
||||||
/>
|
/>
|
||||||
<p:commandButton id="agDlgCan"
|
<p:commandButton id="agDlgCan"
|
||||||
value="Cancel" style="width: 6em"
|
value="Cancel" style="width: 6em"
|
||||||
|
|
13
src/main/resources/META-INF/resources/error/viewExpired.html
Normal file
13
src/main/resources/META-INF/resources/error/viewExpired.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ERROR - Page Expired</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Page Expired</h1>
|
||||||
|
<p>The page state could not be restored because it was left
|
||||||
|
idle too long.</p>
|
||||||
|
<p><a href="/main.jsf">Return to Main Page</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
114
src/main/resources/META-INF/resources/js/scrolltable.js
Normal file
114
src/main/resources/META-INF/resources/js/scrolltable.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// scrolltable.js - scroll functions for dataTable
|
||||||
|
|
||||||
|
function scrollAndFocus(rootId, tableId, txtId) {
|
||||||
|
scrollToBottom(rootId + ":" + tableId);
|
||||||
|
$(rootId +":" + txtId).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToBottom(idDataTable) {
|
||||||
|
pTable = digestTable(idDataTable);
|
||||||
|
|
||||||
|
scrollToRow(pTable, 9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
function digestTable(idDataTable) {
|
||||||
|
pTable = {
|
||||||
|
id: idDataTable
|
||||||
|
}
|
||||||
|
pTable.jqId = '#' + idDataTable.replaceAll(':', '\\:');
|
||||||
|
pTable.jqData = pTable.jqId + "_data";
|
||||||
|
pTable.heignt = $(pTable.jqId + " .ui-datatable-scrollable-body").height();
|
||||||
|
//var lichildren = $(pTable.jqId).children("tr");
|
||||||
|
var lichildren = $(pTable.jqId).find("tr");
|
||||||
|
pTable.itemHeight = lichildren.height();
|
||||||
|
pTable.itemCount = lichildren.length;
|
||||||
|
pTable.visibleItemCount = pTable.height / pTable.itemHeight;
|
||||||
|
return pTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function scrollToRow(pTable, selItem) {
|
||||||
|
var maxScrollItem = pTable.itemCount - pTable.visibleItemCount;
|
||||||
|
var scrollTopInPx;
|
||||||
|
if(selItem >= maxScrollItem)
|
||||||
|
{
|
||||||
|
// scroll table to the bottom
|
||||||
|
scrollTopInPx = pTable.itemCount * pTable.itemHeight;
|
||||||
|
}
|
||||||
|
else if(selItem < 2)
|
||||||
|
{
|
||||||
|
// scroll table to the top
|
||||||
|
scrollTopInPx = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// scroll selected item to the 1.2 th position
|
||||||
|
scrollTopInPx = (selItem - 1.2) * pTable.itemHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(pTable.jqId + " .ui-datatable-scrollable-body").animate({scrollTop:scrollTopInPx, duration: 20}, scrollTopInPx);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function scrolls the selected Item of a
|
||||||
|
* <p:dataTable id="idDataTable" selectionMode="single"
|
||||||
|
* into the visible area
|
||||||
|
* <p:dataTable renderes to a scroll-container with the css-class: ui-datatable-scrollable-body (inside the element with the id : "idDataTable")
|
||||||
|
* and to a container holding all items with the id: "idDataTable"_data
|
||||||
|
*
|
||||||
|
* @param idDataTable z.B.: idForm:idDataTable
|
||||||
|
*/
|
||||||
|
function autoScrollPDatatable(idDataTable)
|
||||||
|
{
|
||||||
|
// for selection in JQuery the ids with : must be encoded with \\:
|
||||||
|
var primfcid = idDataTable.replace(':', '\\:');
|
||||||
|
var idDataTbl = '#' + primfcid;
|
||||||
|
var idDataContainer = idDataTbl + "_data";
|
||||||
|
|
||||||
|
var totalHeight = $(idDataTbl + " .ui-datatable-scrollable-body").height();
|
||||||
|
var lichildren = $(idDataContainer).children("tr");
|
||||||
|
var itemHeight = $(idDataContainer).children("tr").height();
|
||||||
|
var anzItems = lichildren.length;
|
||||||
|
var anzVisItems = totalHeight / itemHeight;
|
||||||
|
var selItem = detectDataTableScrollPos(lichildren);
|
||||||
|
if(selItem == -1)
|
||||||
|
{
|
||||||
|
// no selection found...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxScrollItem = anzItems - anzVisItems;
|
||||||
|
var scrollTopInPx;
|
||||||
|
if(selItem >= maxScrollItem)
|
||||||
|
{
|
||||||
|
// scroll table to the bottom
|
||||||
|
scrollTopInPx = maxScrollItem * itemHeight;
|
||||||
|
}
|
||||||
|
else if(selItem < 2)
|
||||||
|
{
|
||||||
|
// scroll table to the top
|
||||||
|
scrollTopInPx = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// scroll selected item to the 1.2 th position
|
||||||
|
scrollTopInPx = (selItem - 1.2) * itemHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(idDataTbl + " .ui-datatable-scrollable-body").animate({scrollTop:scrollTopInPx}, scrollTopInPx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectDataTableScrollPos(liChildren)
|
||||||
|
{
|
||||||
|
for (i=0;i< liChildren.length;++i)
|
||||||
|
{
|
||||||
|
var chd = liChildren[i];
|
||||||
|
var x = chd.getAttribute("aria-selected");
|
||||||
|
if(x === "true")
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
|
@ -25,7 +25,7 @@
|
||||||
<p:commandButton value="Back"
|
<p:commandButton value="Back"
|
||||||
icon="ui-icon-arrowthick-1-w"
|
icon="ui-icon-arrowthick-1-w"
|
||||||
style="margin-left: 2em" immediate="true"
|
style="margin-left: 2em" immediate="true"
|
||||||
action="/main.jsf"
|
action="/recipeDetails.jsf"
|
||||||
/>
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</p:panelGrid>
|
</p:panelGrid>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user