Compare commits
3 Commits
8bcd8cb9a1
...
68462514db
Author | SHA1 | Date | |
---|---|---|---|
68462514db | |||
61942f547a | |||
9cf0f89e76 |
12
README.md
12
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Gourmet Recipe Manager - Spring Boot
|
# Gourmet Recipe Manager - Spring Boot - Version 2
|
||||||
|
|
||||||
This is a port of Thomas Hinkle (thinkle) Gourmet Recipe Manager.
|
This is a port of Thomas Hinkle (thinkle) Gourmet Recipe Manager.
|
||||||
|
|
||||||
|
@ -86,3 +86,13 @@ employed when you run this app on your local desktop.
|
||||||
|
|
||||||
A lot of recipe websites publish images in webp form. Support
|
A lot of recipe websites publish images in webp form. Support
|
||||||
for webp has now been added.
|
for webp has now been added.
|
||||||
|
|
||||||
|
### Better session management
|
||||||
|
|
||||||
|
JSF tends to depend on session-scope context. Sessions, however
|
||||||
|
time out and this has been an annoyance when a recipe is being
|
||||||
|
displayed. To minimize this, better timeout mechanisms have been
|
||||||
|
installed and the recipe browser keeps last-search and search-type
|
||||||
|
values in long-lived cookies on the client. The server will read
|
||||||
|
and cache them, but if the server times out, it will automatically
|
||||||
|
re-read the cookies on the next request.
|
|
@ -22,3 +22,5 @@ spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
|
||||||
# My special properties
|
# My special properties
|
||||||
gourmet.password.file=${user.home}/.gourmetpw
|
gourmet.password.file=${user.home}/.gourmetpw
|
||||||
|
|
||||||
|
# This will override aplication.yml
|
||||||
|
#server.servlet.context-parameters.primefaces.THEME=le-frog
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -78,6 +78,12 @@
|
||||||
<artifactId>all-themes</artifactId>
|
<artifactId>all-themes</artifactId>
|
||||||
<version>1.0.10</version>
|
<version>1.0.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<!-- Primefaces theme won't work without this! -->
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<!-- <dependency>
|
<!-- <dependency>
|
||||||
<groupId>javax.enterprise</groupId>
|
<groupId>javax.enterprise</groupId>
|
||||||
<artifactId>cdi-api</artifactId>
|
<artifactId>cdi-api</artifactId>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.mousetech.gourmetj;
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import jakarta.faces.event.AjaxBehaviorEvent;
|
import jakarta.faces.event.AjaxBehaviorEvent;
|
||||||
|
@ -14,7 +15,9 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.mousetech.gourmetj.persistence.model.Recipe;
|
import com.mousetech.gourmetj.persistence.model.Recipe;
|
||||||
import com.mousetech.gourmetj.persistence.service.RecipeService;
|
import com.mousetech.gourmetj.persistence.service.RecipeService;
|
||||||
|
@ -44,8 +47,11 @@ public class AdminMainBean implements Serializable {
|
||||||
private static final Logger log =
|
private static final Logger log =
|
||||||
LoggerFactory.getLogger(AdminMainBean.class);
|
LoggerFactory.getLogger(AdminMainBean.class);
|
||||||
|
|
||||||
|
/** Cookie delimiter */
|
||||||
|
private static final String CKDLM = ",";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistency service for Recipes
|
* Persistency service for Recipes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -56,6 +62,24 @@ public class AdminMainBean implements Serializable {
|
||||||
this.recipeService = service;
|
this.recipeService = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
@Inject
|
||||||
|
private CookieBean cookieBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the cookieBean
|
||||||
|
*/
|
||||||
|
public CookieBean getCookieBean() {
|
||||||
|
return cookieBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cookieBean the cookieBean to set
|
||||||
|
*/
|
||||||
|
public void setCookieBean(CookieBean cookieBean) {
|
||||||
|
this.cookieBean = cookieBean;
|
||||||
|
}
|
||||||
|
|
||||||
// **
|
// **
|
||||||
@Inject
|
@Inject
|
||||||
private UserSession userSession;
|
private UserSession userSession;
|
||||||
|
@ -81,10 +105,10 @@ public class AdminMainBean implements Serializable {
|
||||||
* @return the searchText
|
* @return the searchText
|
||||||
*/
|
*/
|
||||||
public String getSearchText() {
|
public String getSearchText() {
|
||||||
if (this.searchResults == null) {
|
// if (this.searchResults == null) {
|
||||||
// Fake around broken @PostConstruct
|
// this.setSearchText(cookieBean.getSearchText());
|
||||||
this.setSearchText(userSession.getLastSearch());
|
// }
|
||||||
}
|
this.searchText = cookieBean.getSearchText();
|
||||||
return searchText;
|
return searchText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +117,7 @@ public class AdminMainBean implements Serializable {
|
||||||
*/
|
*/
|
||||||
public void setSearchText(String searchText) {
|
public void setSearchText(String searchText) {
|
||||||
this.searchText = searchText;
|
this.searchText = searchText;
|
||||||
userSession.setLastSearch(searchText);
|
cookieBean.setSearchText(searchText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> suggestionList = null;
|
private List<String> suggestionList = null;
|
||||||
|
@ -104,7 +128,7 @@ public class AdminMainBean implements Serializable {
|
||||||
|
|
||||||
public List<String> searchSuggestionList(String query) {
|
public List<String> searchSuggestionList(String query) {
|
||||||
if (suggestionList == null) {
|
if (suggestionList == null) {
|
||||||
switch (this.userSession.getSearchType()) {
|
switch (searchtypeEnum()) {
|
||||||
case rst_BY_CATEGORY:
|
case rst_BY_CATEGORY:
|
||||||
suggestionList = recipeService.findCategories();
|
suggestionList = recipeService.findCategories();
|
||||||
break;
|
break;
|
||||||
|
@ -118,6 +142,16 @@ public class AdminMainBean implements Serializable {
|
||||||
return suggestionList;
|
return suggestionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RecipeSearchType searchtypeEnum() {
|
||||||
|
int stn = cookieBean.getSearchType();
|
||||||
|
return searchtypeEnum(stn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecipeSearchType searchtypeEnum(int stn) {
|
||||||
|
RecipeSearchType st = RecipeSearchType.values()[stn];
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
/**/
|
/**/
|
||||||
transient DataModel<Recipe> searchResults;
|
transient DataModel<Recipe> searchResults;
|
||||||
|
|
||||||
|
@ -127,7 +161,7 @@ public class AdminMainBean implements Serializable {
|
||||||
public DataModel<Recipe> getSearchResults() {
|
public DataModel<Recipe> getSearchResults() {
|
||||||
if (searchResults == null) {
|
if (searchResults == null) {
|
||||||
searchResults = new ListDataModel<Recipe>();
|
searchResults = new ListDataModel<Recipe>();
|
||||||
init(); // @PostConstruct is broken
|
init(); // @PostConstruct is broken TODO: fixed??
|
||||||
}
|
}
|
||||||
return searchResults;
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
@ -147,7 +181,7 @@ public class AdminMainBean implements Serializable {
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
void init() {
|
void init() {
|
||||||
log.debug("Initializing AdminMainBean " + this);
|
log.debug("Initializing AdminMainBean " + this);
|
||||||
this.setSearchText(userSession.getLastSearch());
|
this.setSearchText(cookieBean.getSearchText());
|
||||||
// Clean up from any previous operations.
|
// Clean up from any previous operations.
|
||||||
this.userSession.setRecipe(null);
|
this.userSession.setRecipe(null);
|
||||||
doFind();
|
doFind();
|
||||||
|
@ -177,9 +211,20 @@ public class AdminMainBean implements Serializable {
|
||||||
*/
|
*/
|
||||||
public String doFind() {
|
public String doFind() {
|
||||||
List<Recipe> recipes = null;
|
List<Recipe> recipes = null;
|
||||||
|
if ( searchText == null ) {
|
||||||
|
setSearchText("");
|
||||||
|
}
|
||||||
searchText = searchText.trim();
|
searchText = searchText.trim();
|
||||||
|
|
||||||
switch (this.getUserSession().getSearchType()) {
|
// Persist current settings
|
||||||
|
try {
|
||||||
|
cookieBean.saveCookies();
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
// Something is really wrong if we can't create UTF-8!
|
||||||
|
log.error("Unable to save cookies!", e);
|
||||||
|
}
|
||||||
|
RecipeSearchType st = searchtypeEnum();
|
||||||
|
switch (st) {
|
||||||
case rst_BY_NAME:
|
case rst_BY_NAME:
|
||||||
recipes = recipeService.findByTitle(searchText);
|
recipes = recipeService.findByTitle(searchText);
|
||||||
break;
|
break;
|
||||||
|
@ -197,12 +242,11 @@ public class AdminMainBean implements Serializable {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("Invalid recipe search type: "
|
log.error("Invalid recipe search type: "
|
||||||
+ this.getUserSession().getSearchType());
|
+ st);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSearchResults().setWrappedData(recipes);
|
getSearchResults().setWrappedData(recipes);
|
||||||
this.userSession.setLastSearch(this.getSearchText());
|
|
||||||
return null; // Stay on page
|
return null; // Stay on page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,27 +287,4 @@ public class AdminMainBean implements Serializable {
|
||||||
// items.
|
// items.
|
||||||
return "recipeDetails?faces-redirect=true";
|
return "recipeDetails?faces-redirect=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get printable preptime. Database version is in seconds.
|
|
||||||
*
|
|
||||||
* @deprecated User {@link UserSession#formatTime(Long)}
|
|
||||||
*
|
|
||||||
* @return Formatted time. Called from EL on main page.
|
|
||||||
*/
|
|
||||||
public String formatPreptime(int timesec) {
|
|
||||||
StringBuffer sb = new StringBuffer(20);
|
|
||||||
int preptime = timesec / 60;
|
|
||||||
if (preptime > 60) {
|
|
||||||
int hours = preptime / 60;
|
|
||||||
sb.append(hours);
|
|
||||||
sb.append(" h. ");
|
|
||||||
preptime %= 60;
|
|
||||||
}
|
|
||||||
if (preptime > 0) {
|
|
||||||
sb.append(preptime);
|
|
||||||
sb.append(" min.");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
51
src/main/java/com/mousetech/gourmetj/AppBean.java
Normal file
51
src/main/java/com/mousetech/gourmetj/AppBean.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.faces.model.SelectItem;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appplication-scope data (mostly constants)
|
||||||
|
*
|
||||||
|
* @author timh
|
||||||
|
* @since Feb 1, 2024
|
||||||
|
*/
|
||||||
|
@Named
|
||||||
|
@ApplicationScoped
|
||||||
|
public class AppBean {
|
||||||
|
|
||||||
|
public AppBean() {
|
||||||
|
// TODO Auto-generated constructor stub
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelectItem> searchTypeList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the searchTypeList
|
||||||
|
* @see RecipeSearchType
|
||||||
|
* Used by main.xhtml
|
||||||
|
*/
|
||||||
|
public List<SelectItem> getSearchTypeList() {
|
||||||
|
if (searchTypeList == null) {
|
||||||
|
searchTypeList = loadSearchTypeList();
|
||||||
|
}
|
||||||
|
return searchTypeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SelectItem> loadSearchTypeList() {
|
||||||
|
List<SelectItem> list = new ArrayList<SelectItem>(5);
|
||||||
|
list.add(new SelectItem(RecipeSearchType.rst_BY_NAME.ordinal(),
|
||||||
|
"Title"));
|
||||||
|
list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY.ordinal(),
|
||||||
|
"Category"));
|
||||||
|
list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE.ordinal(),
|
||||||
|
"Cuisine"));
|
||||||
|
list.add(
|
||||||
|
new SelectItem(RecipeSearchType.rst_BY_INGREDIENT.ordinal(),
|
||||||
|
"Ingredient"));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
127
src/main/java/com/mousetech/gourmetj/CookieBean.java
Normal file
127
src/main/java/com/mousetech/gourmetj/CookieBean.java
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2024, Tim Holloway
|
||||||
|
*
|
||||||
|
* Manages app data persisted client-side in cookies.
|
||||||
|
*
|
||||||
|
* Date written: Jan 31, 2024
|
||||||
|
* Author: Tim Holloway <timh@mousetech.com>
|
||||||
|
*/
|
||||||
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import jakarta.faces.view.ViewScoped;
|
||||||
|
import jakarta.inject.Named;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caching object for cookie data persistence.
|
||||||
|
*
|
||||||
|
* @author timh
|
||||||
|
* @since Jan 31, 2024
|
||||||
|
*/
|
||||||
|
@Named
|
||||||
|
@ViewScoped
|
||||||
|
public class CookieBean {
|
||||||
|
|
||||||
|
private static final String KEY_DISPLAY_ROWS = "displayRows";
|
||||||
|
|
||||||
|
private static final String KEY_SEARCH_TYPE = "searchType";
|
||||||
|
|
||||||
|
private static final String KEY_SEARCH_FOR = "searchFor";
|
||||||
|
|
||||||
|
/* Logger */
|
||||||
|
|
||||||
|
private static final Logger log =
|
||||||
|
LoggerFactory.getLogger(CookieBean.class);
|
||||||
|
|
||||||
|
private Map<String, String> cookieMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
public CookieBean() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
this.cookieMap = JSFUtils.getCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist us to client cookie storage
|
||||||
|
*
|
||||||
|
* @throws UnsupportedEncodingException (which should never
|
||||||
|
* happen)
|
||||||
|
*/
|
||||||
|
public void saveCookies()
|
||||||
|
throws UnsupportedEncodingException {
|
||||||
|
final Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put("maxAge", 31536000);
|
||||||
|
properties.put("path", "/");
|
||||||
|
properties.put("SameSite", "Strict");
|
||||||
|
|
||||||
|
for (Entry<String, String> e : cookieMap.entrySet()) {
|
||||||
|
JSFUtils.outputCookie(e.getKey(), e.getValue(),
|
||||||
|
properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Cookie value by name
|
||||||
|
*
|
||||||
|
* @param name Name of the cookie
|
||||||
|
* @return Value stored in the cookie
|
||||||
|
*/
|
||||||
|
public String getCookieValue(String name) {
|
||||||
|
return cookieMap.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCookieValue(String name, String value) {
|
||||||
|
cookieMap.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************************
|
||||||
|
// App-specific properties
|
||||||
|
// ************************
|
||||||
|
|
||||||
|
public String getSearchText() {
|
||||||
|
return cookieMap.get(KEY_SEARCH_FOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchText(String value) {
|
||||||
|
cookieMap.put(KEY_SEARCH_FOR, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
public Integer getSearchType() {
|
||||||
|
if (!cookieMap.containsKey(KEY_SEARCH_TYPE)) {
|
||||||
|
cookieMap.put(KEY_SEARCH_TYPE, "0");
|
||||||
|
}
|
||||||
|
String st = cookieMap.get(KEY_SEARCH_TYPE);
|
||||||
|
return Integer.valueOf(String.valueOf(st));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchType(Integer value) {
|
||||||
|
cookieMap.put(KEY_SEARCH_TYPE, String.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
public Integer getDisplayListSize() {
|
||||||
|
if (!cookieMap.containsKey(KEY_DISPLAY_ROWS)) {
|
||||||
|
cookieMap.put(KEY_DISPLAY_ROWS, "30");
|
||||||
|
}
|
||||||
|
String st = cookieMap.get(KEY_DISPLAY_ROWS);
|
||||||
|
return Integer.valueOf(String.valueOf(st));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayListSize(Integer value) {
|
||||||
|
cookieMap.put(KEY_DISPLAY_ROWS, String.valueOf(value));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
package com.mousetech.gourmetj;
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.faces.application.FacesMessage;
|
import jakarta.faces.application.FacesMessage;
|
||||||
import jakarta.faces.context.ExternalContext;
|
import jakarta.faces.context.ExternalContext;
|
||||||
import jakarta.faces.context.FacesContext;
|
import jakarta.faces.context.FacesContext;
|
||||||
import jakarta.faces.context.Flash;
|
import jakarta.faces.context.Flash;
|
||||||
|
import jakarta.servlet.http.Cookie;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -55,6 +61,14 @@ public class JSFUtils {
|
||||||
message);
|
message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void addWarningMessage(String msgString) {
|
||||||
|
FacesMessage message = new FacesMessage(
|
||||||
|
FacesMessage.SEVERITY_WARN, "WARNING", msgString);
|
||||||
|
FacesContext.getCurrentInstance().addMessage(null,
|
||||||
|
message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post an error-level message to the FacesContext where the
|
* Post an error-level message to the FacesContext where the
|
||||||
* <h:messages> tag can display it.
|
* <h:messages> tag can display it.
|
||||||
|
@ -103,4 +117,36 @@ public class JSFUtils {
|
||||||
public static void putFlash(String key, Object value) {
|
public static void putFlash(String key, Object value) {
|
||||||
flashScope().put(key, value);
|
flashScope().put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//***********
|
||||||
|
//* COOKIE!!!
|
||||||
|
//***********
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cookie values.
|
||||||
|
*/
|
||||||
|
public static Map<String, String> getCookies(){
|
||||||
|
Map<String, Object> m0 = getExternalContext().getRequestCookieMap();
|
||||||
|
Map<String, String>m1 = new HashMap<String, String>();
|
||||||
|
m1 = m0.entrySet()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
e -> e.getKey(),
|
||||||
|
e -> ((Cookie)e.getValue()).getValue()));
|
||||||
|
return m1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a cookie value in Response.
|
||||||
|
* @param name Cookie name
|
||||||
|
* @param value Cookie value
|
||||||
|
* @param properties Cookie property Map (timeout, <i>etc.</i>)
|
||||||
|
* @throws UnsupportedEncodingException
|
||||||
|
*/
|
||||||
|
public static void outputCookie(String name,
|
||||||
|
String value, Map<String, Object> properties) throws UnsupportedEncodingException {
|
||||||
|
getExternalContext().addResponseCookie(name,
|
||||||
|
URLEncoder.encode(value, "UTF-8"),
|
||||||
|
properties);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,32 +129,6 @@ public class UserSession implements Serializable {
|
||||||
this.searchType = searchType;
|
this.searchType = searchType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SelectItem> searchTypeList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the searchTypeList
|
|
||||||
*/
|
|
||||||
public List<SelectItem> getSearchTypeList() {
|
|
||||||
if (searchTypeList == null) {
|
|
||||||
searchTypeList = loadSearchTypeList();
|
|
||||||
}
|
|
||||||
return searchTypeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SelectItem> loadSearchTypeList() {
|
|
||||||
List<SelectItem> list = new ArrayList<SelectItem>(5);
|
|
||||||
list.add(new SelectItem(RecipeSearchType.rst_BY_NAME,
|
|
||||||
"Title"));
|
|
||||||
list.add(new SelectItem(RecipeSearchType.rst_BY_CATEGORY,
|
|
||||||
"Category"));
|
|
||||||
list.add(new SelectItem(RecipeSearchType.rst_BY_CUISINE,
|
|
||||||
"Cuisine"));
|
|
||||||
list.add(
|
|
||||||
new SelectItem(RecipeSearchType.rst_BY_INGREDIENT,
|
|
||||||
"Ingredient"));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====
|
// ====
|
||||||
|
|
||||||
public String formatCategories(Recipe r) {
|
public String formatCategories(Recipe r) {
|
||||||
|
@ -190,16 +164,17 @@ public class UserSession implements Serializable {
|
||||||
private List<Recipe> shoppingList = new ArrayList<Recipe>();
|
private List<Recipe> shoppingList = new ArrayList<Recipe>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the sessionTimeoutInterval
|
* @return the sessionTimeoutInterval, msec
|
||||||
*/
|
*/
|
||||||
public long getSessionTimeoutInterval() {
|
public long getSessionTimeoutInterval() {
|
||||||
return sessionTimeoutInterval;
|
return 5000L; //sessionTimeoutInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sessionIdleListener() {
|
public void sessionIdleListener() {
|
||||||
log.warn("Session Idle Listener fired.");
|
log.warn("Session Idle Listener fired.");
|
||||||
PrimeFaces.current()
|
JSFUtils.addWarningMessage("Timeout approaching. Save your work!");
|
||||||
.executeScript("sessionExpiredConfirmation.show()");
|
// PrimeFaces.current()
|
||||||
|
// .executeScript("sessionExpiredConfirmation.show()");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String logoutAction() {
|
public String logoutAction() {
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
>
|
>
|
||||||
<head></head>
|
<h:head></h:head>
|
||||||
<body>
|
<h:body>
|
||||||
<ui:composition>
|
<ui:composition>
|
||||||
<f:view>
|
|
||||||
<h:head>
|
<h:head>
|
||||||
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
|
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
|
||||||
<link rel="icon" type="image/vnd.microsoft.icon"
|
<link rel="icon" type="image/vnd.microsoft.icon"
|
||||||
|
@ -29,50 +28,14 @@
|
||||||
</ui:insert>
|
</ui:insert>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
(C) 2021 Tim Holloway, Licensed under the <a
|
(C) 2021, 2024 Tim Holloway, Licensed under the <a
|
||||||
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.
|
<p>Based on Gourmet Recipe Manager by T.
|
||||||
Hinkle</p>
|
Hinkle</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<h:form id="frmTimeout">
|
|
||||||
<p:idleMonitor
|
|
||||||
timeout="#{userSession.sessionTimeoutInterval}"
|
|
||||||
onidle="PF('dlgSessionExpired').show()"
|
|
||||||
>
|
|
||||||
<p:ajax event="idle"
|
|
||||||
listener="#{userSession.sessionIdleListener}"
|
|
||||||
/>
|
|
||||||
</p:idleMonitor>
|
|
||||||
<p:confirmDialog closable="false"
|
|
||||||
id="sessionExpiredDlg"
|
|
||||||
message="Your session has expired."
|
|
||||||
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
|
|
||||||
severity="alert"
|
|
||||||
widgetVar="dlgSessionExpired"
|
|
||||||
style="z-index: 25000"
|
|
||||||
>
|
|
||||||
<p:commandButton id="cmdExpiredOK"
|
|
||||||
value="OK" action="/main.jsf"
|
|
||||||
oncomplete="PF('dlgSessionExpired').hide()"
|
|
||||||
/>
|
|
||||||
</p:confirmDialog>
|
|
||||||
</h:form>
|
|
||||||
<!-- -->
|
|
||||||
<h:form id="frmOpErr">
|
|
||||||
<p:confirmDialog
|
|
||||||
message="Session may have expired."
|
|
||||||
header="Error"
|
|
||||||
severity="alert" widgetVar="opError"
|
|
||||||
>
|
|
||||||
<p:commandButton value="OK"
|
|
||||||
oncomplete="PF('opError').hide()"
|
|
||||||
/>
|
|
||||||
</p:confirmDialog>
|
|
||||||
</h:form>
|
|
||||||
</h:body>
|
</h:body>
|
||||||
</f:view>
|
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
</body>
|
</h:body>
|
||||||
</html>
|
</html>
|
|
@ -395,6 +395,19 @@
|
||||||
/>
|
/>
|
||||||
</h:form>
|
</h:form>
|
||||||
</p:panel>
|
</p:panel>
|
||||||
|
<!-- -->
|
||||||
|
<p:growl id="growl" showDetail="true" />
|
||||||
|
<h:form id="frmTimeout">
|
||||||
|
<p:idleMonitor
|
||||||
|
timeout="#{userSession.sessionTimeoutInterval}"
|
||||||
|
>
|
||||||
|
<p:ajax id="ajaxIdle" event="idle"
|
||||||
|
listener="#{userSession.sessionIdleListener}"
|
||||||
|
update="growl"
|
||||||
|
/>
|
||||||
|
</p:idleMonitor>
|
||||||
|
</h:form>
|
||||||
|
<!-- -->
|
||||||
<p:dialog id="addGroupDlg" widgetVar="addGroupDlg">
|
<p:dialog id="addGroupDlg" widgetVar="addGroupDlg">
|
||||||
<h:form id="frmAddGroup">
|
<h:form id="frmAddGroup">
|
||||||
<p:panelGrid columns="1">
|
<p:panelGrid columns="1">
|
||||||
|
|
|
@ -28,10 +28,10 @@
|
||||||
/>
|
/>
|
||||||
<p:outputLabel for="@next" value="Search for " />
|
<p:outputLabel for="@next" value="Search for " />
|
||||||
<p:selectOneMenu id="ctlSearchType"
|
<p:selectOneMenu id="ctlSearchType"
|
||||||
value="#{userSession.searchType}"
|
value="#{cookieBean.searchType}"
|
||||||
>
|
>
|
||||||
<f:selectItems
|
<f:selectItems
|
||||||
value="#{userSession.searchTypeList}"
|
value="#{appBean.searchTypeList}"
|
||||||
/>
|
/>
|
||||||
<p:ajax
|
<p:ajax
|
||||||
listener="#{adminMainBean.resetSuggestions}"
|
listener="#{adminMainBean.resetSuggestions}"
|
||||||
|
@ -45,12 +45,12 @@
|
||||||
<p:commandButton value="New Recipe"
|
<p:commandButton value="New Recipe"
|
||||||
action="#{adminMainBean.doNewRecipe}"
|
action="#{adminMainBean.doNewRecipe}"
|
||||||
/>
|
/>
|
||||||
<p:commandButton value="More..."
|
<p:commandButton value="Shopping..."
|
||||||
action="#{adminMainBean.doMore}"
|
action="#{adminMainBean.doMore}"
|
||||||
/>
|
/>
|
||||||
<h:outputText id="slistSSize"
|
<h:outputText id="slistSSize"
|
||||||
style="margin-left: 2em"
|
style="margin-left: 2em"
|
||||||
value="#{userSession.shoppingList.size()}"
|
value="#{cookieBean.displayListSize}"
|
||||||
/>
|
/>
|
||||||
<h:outputLabel for="slistSize"
|
<h:outputLabel for="slistSize"
|
||||||
value=" Recipes in Shopping List"
|
value=" Recipes in Shopping List"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
xmlns:p="http://primefaces.org/ui"
|
xmlns:p="http://primefaces.org/ui"
|
||||||
xmlns:c="http://xmlns.jcp.org/jstl"
|
xmlns:c="http://xmlns.jcp.org/jstl"
|
||||||
>
|
>
|
||||||
<!-- Tabbed page for the Mainpage "More..." button -->
|
<!-- Tabbed page for the Mainpage "Shopping..." button -->
|
||||||
<ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define>
|
<ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define>
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
<style>
|
<style>
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
This is the list of recipe items you've selected to shop for.
|
||||||
<h:messages />
|
<h:messages />
|
||||||
<p:tabView id="tabGroupClient" orientation="left"
|
<p:tabView id="tabGroupClient" orientation="left"
|
||||||
dynamic="true"
|
dynamic="true"
|
||||||
|
@ -95,13 +96,11 @@
|
||||||
value="Ingredients"
|
value="Ingredients"
|
||||||
/>
|
/>
|
||||||
</f:facet>
|
</f:facet>
|
||||||
<p:headerRow>
|
|
||||||
<p:column colspan="4">
|
<p:column colspan="4">
|
||||||
<h:outputText
|
<h:outputText
|
||||||
value="#{item.shopCat}"
|
value="#{item.shopCat}"
|
||||||
/>
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
</p:headerRow>
|
|
||||||
<p:column label="Amt"
|
<p:column label="Amt"
|
||||||
style="width: 3em; text-align: right"
|
style="width: 3em; text-align: right"
|
||||||
>
|
>
|
||||||
|
@ -150,6 +149,7 @@
|
||||||
</p:tab>
|
</p:tab>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<p:tab id="tabPantry" title="Pantry">
|
<p:tab id="tabPantry" title="Pantry">
|
||||||
|
<h:outputText value="Stuff already in the pantry." />
|
||||||
<h:outputText value="For future implementation" />
|
<h:outputText value="For future implementation" />
|
||||||
</p:tab>
|
</p:tab>
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
|
|
@ -26,6 +26,11 @@ server:
|
||||||
servlet:
|
servlet:
|
||||||
session:
|
session:
|
||||||
timeout: '30m'
|
timeout: '30m'
|
||||||
|
# Theme here pverrides jinfaces theme
|
||||||
|
# context-parameters:
|
||||||
|
# primefaces:
|
||||||
|
# THEME: vela
|
||||||
|
|
||||||
|
|
||||||
gourmet:
|
gourmet:
|
||||||
password:
|
password:
|
||||||
|
@ -33,4 +38,4 @@ gourmet:
|
||||||
|
|
||||||
joinfaces:
|
joinfaces:
|
||||||
primefaces:
|
primefaces:
|
||||||
theme: saga
|
theme: casablanca
|
||||||
|
|
Loading…
Reference in New Issue
Block a user