Editable shopping category

version2
Tim Holloway 2 years ago
parent e4da9a6a71
commit ce0f354f9a
  1. 5
      pom.xml
  2. 106
      src/main/java/com/mousetech/gourmetj/EditShopcatBean.java
  3. 4
      src/main/java/com/mousetech/gourmetj/IngredientUI.java
  4. 8
      src/main/java/com/mousetech/gourmetj/JSFUtils.java
  5. 75
      src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
  6. 31
      src/main/java/com/mousetech/gourmetj/SpringPrimeFacesApplication.java
  7. 9
      src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java
  8. 14
      src/main/resources/META-INF/resources/WEB-INF/layout/layout.xhtml
  9. 65
      src/main/resources/META-INF/resources/detailEdit.xhtml
  10. 61
      src/main/resources/META-INF/resources/editShopcat.xhtml
  11. 9
      src/main/resources/META-INF/resources/error/error.html

@ -88,6 +88,11 @@
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>

@ -0,0 +1,106 @@
package com.mousetech.gourmetj;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.primefaces.event.SelectEvent;
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository;
import com.mousetech.gourmetj.persistence.model.Shopcat;
@Named
@ViewScoped
public class EditShopcatBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String ingkey;
private String shopcatName;
private String shopcatSuggestion;
private List<String> shopcatSuggestionList;
@Inject
ShopcatRepository shopcatRepository;
/**
* Default Constructor.
*/
public EditShopcatBean() {
}
@PostConstruct
public void init() {
this.shopcatSuggestionList = this.shopcatRepository
.findDistinctCategoryNative();
// // Get ingkey from Flash scope
// ingkey = (String) JSFUtils.getFlash("ingkey");
// Shopcat scat = this.shopcatRepository
// .findShopcatByIngkey(ingkey);
// if (scat != null) {
// this.setShopcatName(scat.getShopcategory());
// }
}
/**
* @return the shopcatName
*/
public String getShopcatName() {
return shopcatName;
}
/**
* @param shopcatName the shopcatName to set
*/
public void setShopcatName(String shopcatName) {
this.shopcatName = shopcatName;
}
/**
* @return the shopcatSuggestion
*/
public String getShopcatSuggestion() {
return shopcatSuggestion;
}
/**
* @param shopcatSuggestion the shopcatSuggestion to set
*/
public void setShopcatSuggestion(String shopcatSuggestion) {
this.shopcatSuggestion = shopcatSuggestion;
}
/**
* @return the ingkey
*/
public String getIngkey() {
return ingkey;
}
public void setIngkey(String ingkey) {
this.ingkey = ingkey;
}
/**
* @return the shopcatSuggestionList
*/
public List<String> getShopcatSuggestionList() {
return shopcatSuggestionList;
}
public void ajaxShopcatSuggest(AjaxBehaviorEvent event) {
this.shopcatName = this.shopcatSuggestion;
}
}

@ -100,7 +100,6 @@ public class IngredientUI implements IngredientIF {
* @see #getAmount
*/
public String getDisplayAmount() {
// TODO
Double amt = ingredient.getAmount();
if (amt == null) {
return "";
@ -120,8 +119,7 @@ public class IngredientUI implements IngredientIF {
if (amount.isBlank()) {
ingredient.setAmount(null);
}
IngredientDigester digester = new IngredientDigester();
Double[] amt = digester.digestAmount(amount);
Double[] amt = IngredientDigester.digestAmount(amount);
ingredient.setAmount(amt[0]);
ingredient.setRangeamount(amt[1]);
}

@ -83,4 +83,12 @@ public class JSFUtils {
return (FacesContext.getCurrentInstance()
.getExternalContext().getFlash());
}
public static Object getFlash(String key) {
return flashScope().get(key);
}
public static void putFlash(String key, Object value) {
flashScope().put(key, value);
}
}

@ -2,8 +2,11 @@ package com.mousetech.gourmetj;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -17,6 +20,8 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.Part;
import org.apache.commons.lang3.StringUtils;
import org.primefaces.PrimeFaces;
import org.primefaces.event.FileUploadEvent;
import org.primefaces.model.UploadedFile;
import org.slf4j.Logger;
@ -30,7 +35,6 @@ import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.persistence.model.Shopcat;
import com.mousetech.gourmetj.persistence.service.RecipeService;
import com.mousetech.gourmetj.springweb.PictureController;
import com.mousetech.gourmetj.IngredientDigester;
/**
* Backing bean for display/edit recipe detail
@ -218,7 +222,6 @@ public class RecipeDetailBean implements Serializable {
private void init() {
this.recipe = userSession.getRecipe();
log.info("Using recipe: " + this.recipe);
/**
* For "create new, this recipe is a blank constructed
@ -438,6 +441,64 @@ public class RecipeDetailBean implements Serializable {
}
}
// =====
@Inject
EditShopcatBean editShopcatBean;
public void ajaxEditShopcat(IngredientUI item) {
// Map<String, Object> options = new HashMap<>();
// options.put("resizable", false);
// TODO: Reject if ingkey is empty/null
editShopcatBean.setIngkey(item.getIngkey());
editShopcatBean.setShopcatName(item.getShopCat());
// if ( ! StringUtils.isBlank(key) ) {
// PrimeFaces.current().dialog().openDynamic("editShopcat");
// }
}
public void doUpdateShopcat() {
final String key = editShopcatBean.getIngkey();
if ( StringUtils.isBlank(key)) {
return; // Do not set category if no ingKey
}
final String catname = editShopcatBean.getShopcatName();
Shopcat sc = this.recipeService
.findShopcatForIngredientKey(catname);
if (sc == null) {
sc = new Shopcat();
sc.setIngkey(key);
sc.setShopcategory(catname);
} else {
if (catname.equals(sc.getShopcategory())) {
// No change. Do nothing.
return;
}
}
if ( StringUtils.isBlank(catname)) {
this.recipeService.deleteShopcat(sc);
} else {
this.recipeService.saveShopcat(sc);
}
updateDisplayedShopcats(key, sc);
}
/**
* When Shopcat name changes, update the Ingredients.
* In detailEdit, an AJAX "render" will then update
* the display. In recipeDetails, nothing actually shows.
*/
private void updateDisplayedShopcats(String key, Shopcat sc) {
List<IngredientUI> ingList = this.getWrappedIngredients();
for (IngredientUI ingUI: ingList ) {
if ( key.equals(ingUI.getIngkey())) {
ingUI.setShopCat(sc);
}
}
}
/**
* Bulk add for ingredients. Looks for long input and if
* found, tries to split it up and add as multiple
@ -623,7 +684,6 @@ public class RecipeDetailBean implements Serializable {
}
}
/**
* Update shopcat for Ingredient.
*
@ -638,10 +698,12 @@ public class RecipeDetailBean implements Serializable {
ing.setShopCat(null);
return true;
}
Shopcat scat = recipeService.findShopcatForIngredientKey(ingKey);
Shopcat scat = recipeService
.findShopcatForIngredientKey(ingKey);
if (scat == null) {
JSFUtils.addErrorMessage(
"No Shopping Category is defined for Ingredient Key "+ ingKey);
"No Shopping Category is defined for Ingredient Key "
+ ingKey);
return false;
}
@ -808,7 +870,8 @@ public class RecipeDetailBean implements Serializable {
public void ajaxUpdateShopcat(IngredientUI item) {
log.warn("SHOPCAT2 ");
updateShopcat(item);;
updateShopcat(item);
;
}
// ***

@ -1,23 +1,28 @@
package com.mousetech.gourmetj;
import javax.inject.Qualifier;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.http.HttpStatus;
@SpringBootApplication
@EntityScan(value = {"com.mousetech.gourmetj.persistence.model"})
@EntityScan(value = {
"com.mousetech.gourmetj.persistence.model" })
public class SpringPrimeFacesApplication {
final String errorPage = "/error/error.html";
public static void main(String[] args) {
SpringApplication.run(SpringPrimeFacesApplication.class, args);
SpringApplication.run(SpringPrimeFacesApplication.class,
args);
}
@Bean
@ -39,4 +44,20 @@ public class SpringPrimeFacesApplication {
}
};
}
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return new ErrorPageRegistrar() {
@Override
public void registerErrorPages(
ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(
HttpStatus.NOT_FOUND, errorPage));
registry.addErrorPages(new ErrorPage(
HttpStatus.INTERNAL_SERVER_ERROR,
errorPage));
}
};
}
}

@ -89,6 +89,15 @@ public class RecipeService implements Serializable {
return shopcatRepository.findShopcatByIngkey(ingkey);
}
public void saveShopcat(Shopcat sc) {
shopcatRepository.save(sc);
}
public void deleteShopcat(Shopcat sc) {
shopcatRepository.delete(sc);
}
/**
* CategoryService as a sub-function of RecipeService
*/

@ -1,16 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns:f="http://xmlns.jcp.org/jsf/core"
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:tc="http://myfaces.apache.org/tobago/component"
xmlns:p="http://primefaces.org/ui"
>
<head></head>
<body>
<ui:composition>
<f:view>
<h:head>
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
<link rel="icon" type="image/vnd.microsoft.icon"
href="#{pageContext.contextPath}/favicon.ico"
/>
<h:outputStylesheet name="css/style.css" />
</h:head>
<h:body>
@ -28,3 +32,5 @@
</h:body>
</f:view>
</ui:composition>
</body>
</html>

@ -1,11 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition template="/WEB-INF/layout/layout.xhtml"
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jstl"
>
<head></head>
<body>
<ui:composition template="/WEB-INF/layout/layout.xhtml">
<ui:define name="title">Gourmet Recipe Manager</ui:define>
<ui:define name="content">
<style>
@ -29,17 +33,21 @@
header="#{recipeDetailBean.recipe.title}"
>
<h:form id="form1" enctype="multipart/form-data">
<p:tabView id="tabGroupClient" orientation="left"
<p:tabView id="tabGroupClient"
orientation="left"
activeIndex="#{userSession.detailTab}"
>
<p:tab id="overviewTab" title="Description">
<p:tab id="overviewTab"
title="Description"
>
<p:panelGrid columns="2">
<f:facet name="header">Description</f:facet>
<p:outputLabel for="@next"
value="Title"
/>
<p:inputText id="rtitle" size="45"
required="true" focus="true"
<p:inputText id="rtitle"
size="45" required="true"
focus="true"
placeholder="A recipe title is required."
value="#{recipeDetailBean.recipe.title}"
>
@ -58,7 +66,8 @@
<p:commandButton
value="&lt;- Suggest"
>
<f:ajax execute="rcategory bxCat"
<f:ajax
execute="rcategory bxCat"
listener="#{recipeDetailBean.ajaxSuggestCategory}"
render="rcategory"
/>
@ -87,7 +96,8 @@
<p:outputLabel for="@next"
value="Source"
/>
<p:inputText id="rsource" size="45"
<p:inputText id="rsource"
size="45"
value="#{recipeDetailBean.recipe.source}"
/>
<p:outputLabel for="@next"
@ -114,7 +124,8 @@
global="true" mode="advanced"
multiple="false"
update=":messages picPanel"
auto="true" sizeLimit="1000000"
auto="true"
sizeLimit="1000000"
allowTypes="/(\.|\/)(gif|jpe?g|png)$/"
/>
<p:commandButton id="ctlDelImg"
@ -166,7 +177,8 @@
disabled="true"
/>
<p:button value="Paste" />
<p:commandButton value="Delete"
<p:commandButton
value="Delete"
id="ctlDelete"
disabled="not #{recipeDetailBean.selectionActive}"
>
@ -191,12 +203,15 @@
id="selected"
value="#{item.selected}"
>
<f:ajax immediate="true"
<f:ajax
immediate="true"
render="pnlIngredients"
/>
</p:selectBooleanCheckbox>
</p:column>
<p:column style="width: 3.6em">
<p:column
style="width: 3.6em"
>
<f:facet name="header">
Amt.
</f:facet>
@ -251,7 +266,8 @@
size="20"
rendered="#{not item.ingGroup}"
>
<f:ajax event="change"
<f:ajax
event="change"
listener="#{recipeDetailBean.ajaxUpdateShopcat(item)}"
render="shopCat"
/>
@ -261,14 +277,24 @@
<f:facet name="header">
Shop. Cat.
</f:facet>
<h:outputText id="shopCat"
<h:outputText
id="shopCat"
value="#{item.shopCat}"
rendered="#{not item.ingGroup}"
/>
<p:commandButton
id="eShopcat"
value="E"
action="#{recipeDetailBean.ajaxEditShopcat(item)}"
update="editShopcatDlg"
oncomplete="PF('editShopcatDlg').show()"
>
</p:commandButton>
</p:column>
</p:dataTable>
<h:panelGroup id="pnlIng">
<p:remoteCommand name="ingButton"
<p:remoteCommand
name="ingButton"
action="#{recipeDetailBean.doAddIngredient}"
process="@this ctlAddIngTxt"
update="pnlIngredients"
@ -284,7 +310,8 @@
title="You can paste in multiple ingredients here!"
onkeydown="if (event.keyCode === 13) { ingButton(); this.focus(); return false; }"
/>
<p:commandButton id="ctlAddIng"
<p:commandButton
id="ctlAddIng"
value="+ Add"
onclick="ingButton(); return false;"
>
@ -299,7 +326,8 @@
<div id="insection">
<p:textEditor
id="ctlInstructions"
height="320" escape="false"
height="320"
escape="false"
value="#{recipeDetailBean.recipe.instructions}"
/>
</div>
@ -323,5 +351,10 @@
/>
</h:form>
</p:panel>
<p:dialog id="editShopcatDlg" widgetVar="editShopcatDlg">
<ui:include src="editShopcat.xhtml" />
</p:dialog>
</ui:define>
</ui:composition>
</body>
</html>

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
>
<h:head>
<title>Shopping Category</title>
<style type="text/css">
html {
font-size: 14px;
}
</style>
</h:head>
<h:body>
<ui:component>
<h:form id="frmShopcat">
<p:panelGrid columns="1">
<div>Ingredient key:
#{editShopcatBean.ingkey}
</div>
<p:outputLabel for="@next"
value="Category Name" />
<p:inputText id="ctlShopcat"
value="#{editShopcatBean.shopcatName}"
>
</p:inputText>
<h:outputText
value="suggestion:" />
<p:selectOneMenu
id="ctlShopcatMenu"
value="#{editShopcatBean.shopcatSuggestion}"
>
<f:selectItems
value="#{editShopcatBean.shopcatSuggestionList}" />
<p:ajax event="change"
listener="#{editShopcatBean.ajaxShopcatSuggest}"
update="ctlShopcat" />
</p:selectOneMenu>
<p:panelGrid columns="2"
style="width: 100%"
>
<p:commandButton
id="scDlgOK" value="OK"
style="width: 6em"
action="#{recipeDetailBean.doUpdateShopcat}"
update="form1:tabGroupClient:ingredientTable"
oncomplete="PF('editShopcatDlg').hide()" />
<p:commandButton
id="scDlgCan" value="Cancel"
style="width: 6em"
onclick="PF('editShopcatDlg').hide()" />
</p:panelGrid>
</p:panelGrid>
</h:form>
</ui:component>
</h:body>
</html>

@ -0,0 +1,9 @@
<html>
<head>
<title>ERROR</title>
</head>
<body>
<h1>Uh-oh!</h1>
An error happened.
</body>
</html>
Loading…
Cancel
Save