shopcat, ENTER key for ingredients

version2
Tim Holloway 2 years ago
parent 7ba345413d
commit 735bf814cf
  1. 33
      pom.xml
  2. 20
      src/main/java/com/mousetech/gourmetj/IngredientDigester.java
  3. 41
      src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
  4. 18
      src/main/java/com/mousetech/gourmetj/SpringPrimeFacesApplication.java
  5. 2
      src/main/java/com/mousetech/gourmetj/persistence/dao/CategoryRepository.java
  6. 30
      src/main/java/com/mousetech/gourmetj/persistence/dao/ShopcatRepository.java
  7. 7
      src/main/java/com/mousetech/gourmetj/persistence/service/RecipeService.java
  8. 143
      src/main/resources/META-INF/resources/detailEdit.xhtml

@ -82,31 +82,20 @@
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>
</dependency> </dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</dependency>
<dependency> <dependency>
<groupId>commons-fileupload</groupId> <groupId>javax.servlet.jsp</groupId>
<artifactId>commons-fileupload</artifactId> <artifactId>javax.servlet.jsp-api</artifactId>
<version>1.3</version> <version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>org.glassfish</groupId>
<artifactId>commons-io</artifactId> <artifactId>javax.el</artifactId>
<version>2.2</version>
</dependency> </dependency>
<dependency> <dependency>

@ -65,11 +65,11 @@ public class IngredientDigester {
switch (unext.length) { switch (unext.length) {
case 1: case 1:
ing.setUnit(null); ing.setUnit(null);
ing.setItem(unext[0]); ing.setItem(nonoise(unext[0]));
break; break;
case 2: case 2:
ing.setUnit(unext[0]); ing.setUnit(unext[0]);
ing.setItem(unext[1]); ing.setItem(nonoise(unext[1]));
break; break;
} }
} }
@ -84,6 +84,22 @@ public class IngredientDigester {
return ing; return ing;
} }
/**
* Remove "noise" from ingredient title. For best results,
* should be adaptable for incoming language.
*
* @param string Item string with noise
* @return Item string with noise removed
*/
private static String nonoise(String string) {
String xstring = string;
if ( xstring.startsWith("of ")) {
xstring = xstring.substring(3);
}
// Todo: remote "optional" from string, if present.
return xstring;
}
/** /**
* Break down ingredients line into 2 parts. First part is * Break down ingredients line into 2 parts. First part is
* numeric text representing amount and optional range, * numeric text representing amount and optional range,

@ -51,6 +51,9 @@ public class RecipeDetailBean implements Serializable {
private static final Logger log = private static final Logger log =
LoggerFactory.getLogger(RecipeDetailBean.class); LoggerFactory.getLogger(RecipeDetailBean.class);
// Split lines at 2 or more spaces OR at line terminators.
private static final String RE_INGSPLIT = "\\s\\s+|\\r?+\\n";
/** /**
* Default Constructor. * Default Constructor.
*/ */
@ -359,10 +362,15 @@ public class RecipeDetailBean implements Serializable {
// ===== // =====
/** /**
* Handle entry of a single ingredient line into the input * Handle entry of ingredient line(s) into text control on
* form. * the input form.
*
* Note: In the original Tobago port of this app, the input
* was an inputText. In PrimeFaces, this did not preserve
* line separation characters, so an inputTextArea was
* used instead.
* *
* @param event Unused??? * @param event Unused
*/ */
public void ajaxAddIngredient(AjaxBehaviorEvent event) { public void ajaxAddIngredient(AjaxBehaviorEvent event) {
doAddIngredient(); doAddIngredient();
@ -442,7 +450,7 @@ public class RecipeDetailBean implements Serializable {
} }
// Otherwise, try for split. // Otherwise, try for split.
String[] lineArray = ingredientTextLines.split(" "); String[] lineArray = ingredientTextLines.split(RE_INGSPLIT);
for (String line : lineArray) { for (String line : lineArray) {
if (line.isBlank()) { if (line.isBlank()) {
continue; // actually should discard any above continue; // actually should discard any above
@ -758,32 +766,21 @@ public class RecipeDetailBean implements Serializable {
this.cuisineList = cuisineList; this.cuisineList = cuisineList;
} }
// *** //***
private String shopcatPartial; // Shopcat for IngredientUI
/**
* @return the shopcatPartial
*/
public String getShopcatPartial() {
return shopcatPartial;
}
/**
* @param shopcatPartial the shopcatPartial to set
*/
public void setShopcatPartial(String shopcatPartial) {
this.shopcatPartial = shopcatPartial;
}
private List<String> shopcatList; private List<String> shopcatList;
public List<String> getShopcatList() { public List<String> shopcatList(String query) {
if (shopcatList == null) { if (shopcatList == null) {
shopcatList = recipeService.findShoppingCategories(); shopcatList = recipeService.findShoppingCategories();
} }
return shopcatList; return shopcatList;
} }
public void ajaxShopcat(AjaxBehaviorEvent event) {
log.warn("SHOPCAT ");
}
// *** // ***
public String editDescription() { public String editDescription() {

@ -27,7 +27,7 @@ public class SpringPrimeFacesApplication {
public void onStartup(ServletContext servletContext) public void onStartup(ServletContext servletContext)
throws ServletException { throws ServletException {
servletContext.setInitParameter( servletContext.setInitParameter(
"primefaces.THEME", "bluesky"); "primefaces.THEME", "afternoon");
servletContext.setInitParameter( servletContext.setInitParameter(
"javax.faces.FACELETS_SKIP_COMMENTS", "javax.faces.FACELETS_SKIP_COMMENTS",
"true"); "true");
@ -39,20 +39,4 @@ public class SpringPrimeFacesApplication {
} }
}; };
} }
// @Bean
// public FilterRegistrationBean FileUploadFilter() {
// FilterRegistrationBean registration = new FilterRegistrationBean();
// registration.setFilter(new org.primefaces.webapp.filter.FileUploadFilter());
// registration.setName("PrimeFaces FileUpload Filter");
// return registration;
// }
// @Bean
// public FilterRegistrationBean hiddenHttpMethodFilterDisabled(
// HiddenHttpMethodFilter filter) {
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(filter);
// filterRegistrationBean.setEnabled(false);
// return filterRegistrationBean;
// }
} }

@ -9,7 +9,7 @@ import org.springframework.stereotype.Repository;
import com.mousetech.gourmetj.persistence.model.Category; import com.mousetech.gourmetj.persistence.model.Category;
/** /**
* JpaRepositort for Categories, which relate ManyToOne to Recipe. * JpaRepository for Categories, which relate ManyToOne to Recipe.
* Service method is @see CategoryService * Service method is @see CategoryService
* *
* @author timh * @author timh

@ -0,0 +1,30 @@
package com.mousetech.gourmetj.persistence.dao;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import com.mousetech.gourmetj.persistence.model.Shopcat;
/**
* JpaRepository for Shoppind Categories, which relate ManyToOne to Ingredient.
* Service method is @see RecipeService
*
* @author timh
* @since Dec 28, 2021
*/
@Repository
public interface ShopcatRepository
extends JpaRepository<Shopcat, Long> {
final static String SQL_FIND_CATEGORIES =
"SELECT DISTINCT shopcategory from shopcats"
+ " where shopcategory is not null and shopcategory <> ''"
+ " ORDER BY shopcategory ASC";
@Query(value = SQL_FIND_CATEGORIES, nativeQuery = true)
public List<String> findDistinctCategoryNative();
}

@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.mousetech.gourmetj.RecipeDetailBean; import com.mousetech.gourmetj.RecipeDetailBean;
import com.mousetech.gourmetj.persistence.dao.CategoryRepository; import com.mousetech.gourmetj.persistence.dao.CategoryRepository;
import com.mousetech.gourmetj.persistence.dao.RecipeRepository; import com.mousetech.gourmetj.persistence.dao.RecipeRepository;
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository;
import com.mousetech.gourmetj.persistence.model.Category; import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Ingredient; import com.mousetech.gourmetj.persistence.model.Ingredient;
import com.mousetech.gourmetj.persistence.model.Recipe; import com.mousetech.gourmetj.persistence.model.Recipe;
@ -75,9 +76,11 @@ public class RecipeService implements Serializable {
} }
@Inject
ShopcatRepository shopcatRepository;
public List<String> findShoppingCategories() { public List<String> findShoppingCategories() {
// TODO Auto-generated method stub return shopcatRepository.findDistinctCategoryNative();
return null;
} }
@Inject @Inject

@ -8,7 +8,7 @@
> >
<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">
<style> <style>
.ingSel { .ingSel {
width: 3em; width: 3em;
text-align: center; text-align: center;
@ -40,6 +40,7 @@
/> />
<p:inputText id="rtitle" size="45" <p:inputText id="rtitle" size="45"
required="true" focus="true" required="true" focus="true"
placeholder="A recipe title is required."
value="#{recipeDetailBean.recipe.title}" value="#{recipeDetailBean.recipe.title}"
> >
<f:ajax execute="rtitle" <f:ajax execute="rtitle"
@ -99,8 +100,7 @@
value="Description" value="Description"
/> />
<p:inputTextarea id="description" <p:inputTextarea id="description"
rows="10" cols="30" rows="10" cols="45"
escape="false"
value="#{recipeDetailBean.recipe.description}" value="#{recipeDetailBean.recipe.description}"
/> />
<p:panel id="picPanel"> <p:panel id="picPanel">
@ -131,25 +131,28 @@
<p:tab id="ingredientsTab" <p:tab id="ingredientsTab"
title="Ingredients" title="Ingredients"
> >
<p:panel header="Ingredients"> <p:panel id="pnlIngredients">
<f:facet name="header">Ingredients</f:facet>
<!-- NOTE: disabled doesn't work from AJAX render. Swap images --> <!-- NOTE: disabled doesn't work from AJAX render. Swap images -->
<h:panelGroup id="ingButtons"> <h:panelGroup id="ingButtons">
<p:button value="Up" id="ctlUp"> <p:commandButton value="Up"
<!-- <f:ajax id="ctlUp"
listener="recipeDetailBean.ajaxMoveUp" >
execute="ingredientTable" <f:ajax
render="ingredientTable" listener="#{recipeDetailBean.ajaxMoveUp}"
/> --> execute="ingredientTable"
</p:button> render="ingredientTable"
<p:button value="Down" />
</p:commandButton>
<p:commandButton value="Down"
id="ctlDown" id="ctlDown"
> >
<!-- <f:ajax <f:ajax
listener="recipeDetailBean.ajaxMoveDown" listener="#{recipeDetailBean.ajaxMoveDown}"
execute="ingredientTable" execute="ingredientTable"
render="ingredientTable" render="ingredientTable"
/> --> />
</p:button> </p:commandButton>
<p:button value="Add Group" <p:button value="Add Group"
disabled="true" disabled="true"
/> />
@ -162,17 +165,16 @@
disabled="true" disabled="true"
/> />
<p:button value="Paste" /> <p:button value="Paste" />
<p:button value="Delete" <p:commandButton value="Delete"
id="ctlDelete" id="ctlDelete"
immediate="true"
disabled="not #{recipeDetailBean.selectionActive}" disabled="not #{recipeDetailBean.selectionActive}"
> >
<!-- <f:ajax <f:ajax
listener="recipeDetailBean.ajaxDeleteItems" listener="#{recipeDetailBean.ajaxDeleteItems}"
immediate="true" immediate="true"
render="ingredientTable" render="ingredientTable"
/> --> />
</p:button> </p:commandButton>
</h:panelGroup> </h:panelGroup>
<h:panelGrid columns="1" <h:panelGrid columns="1"
id="ingredientsDiv" id="ingredientsDiv"
@ -197,9 +199,12 @@
/> --> /> -->
</p:selectBooleanCheckbox> </p:selectBooleanCheckbox>
</p:column> </p:column>
<p:column label="Amt" <p:column
style="width: 4em" style="width: 4.4em"
> >
<f:facet name="header">
Amt.
</f:facet>
<p:inputText id="ingAmt" <p:inputText id="ingAmt"
size="5" size="5"
value="#{item.displayAmount}" value="#{item.displayAmount}"
@ -208,9 +213,10 @@
> >
</p:inputText> </p:inputText>
</p:column> </p:column>
<p:column label="Units" <p:column style="width: 7em">
style="width: 7em" <f:facet name="header">
> Units
</f:facet>
<p:inputText id="ingUnit" <p:inputText id="ingUnit"
value="#{item.unit}" value="#{item.unit}"
size="10" size="10"
@ -218,11 +224,12 @@
> >
</p:inputText> </p:inputText>
</p:column> </p:column>
<p:column label="Item" <p:column style="width: 26em">
style="width: 22em" <f:facet name="header">
> Item
</f:facet>
<p:inputText id="ingItem" <p:inputText id="ingItem"
size="35" size="42"
value="#{item.item}" value="#{item.item}"
> >
</p:inputText> </p:inputText>
@ -231,51 +238,69 @@
align="center" align="center"
styleClass="ingSel" styleClass="ingSel"
> >
<f:facet name="header">
Opt.
</f:facet>
<p:selectBooleanCheckbox <p:selectBooleanCheckbox
id="ingOpt" id="ingOpt"
value="#{item.optionalCB}" value="#{item.optionalCB}"
rendered="#{not item.ingGroup}" rendered="#{not item.ingGroup}"
/> />
</p:column> </p:column>
<p:column label="Key"> <p:column style="width: 13em">
<f:facet name="header">
Ing. Key
</f:facet>
<p:inputText id="ingKey" <p:inputText id="ingKey"
value="#{item.ingkey}" value="#{item.ingkey}"
size="20" size="20"
rendered="#{not item.ingGroup}" rendered="#{not item.ingGroup}"
/> />
</p:column> </p:column>
<p:column <p:column>
label="Shopping Category" <f:facet name="header">
> Shop. Cat.
<p:inputText id="shopCat" </f:facet>
<p:autoComplete
id="shopCat"
value="#{item.shopCat}" value="#{item.shopCat}"
rendered="#{not item.ingGroup}" rendered="#{not item.ingGroup}"
tip="Note that changing Shopping category for an ingredient key changes it for all users of that key." editable="true"
forceSelection="false"
autoSelection="false"
dropdown="true"
cache="true"
title="Note that changing Shopping category for an ingredient key changes it for all users of that key."
completeMethod="#{recipeDetailBean.shopcatList}"
> >
<p:autoComplete <p:ajax event="blur"
minimumCharacters="1" listener="#{recipeDetailBean.ajaxShopcat}"
completeMethod="#{recipeDetailBean.shopcatPartial}" />
> </p:autoComplete>
</p:autoComplete>
</p:inputText>
</p:column> </p:column>
</p:dataTable> </p:dataTable>
</h:panelGrid> </h:panelGrid>
<h:panelGroup id="addIng"> <h:panelGroup id="pnlIng">
<p:inputText <p:remoteCommand name="ingButton"
label="Add Ingredient: " action="#{recipeDetailBean.doAddIngredient}"
id="ctlAddIng" focus="true" process="@this ctlAddIngTxt"
update="pnlIngredients"
/>
<p:outputLabel for="@next"
value="Add Ingredient: "
/>
<p:inputTextarea id="ctlAddIngTxt"
focus="true" rows="1"
cols="65"
value="#{recipeDetailBean.ingredientText}" value="#{recipeDetailBean.ingredientText}"
tip="You can paste in multiple ingredients here!" title="You can paste in multiple ingredients here!"
onkeydown="if (event.keyCode === 13) { ingButton(); this.focus(); return false; }"
/> />
<p:button value="+ Add" <p:commandButton id="ctlAddIng"
defaultCommand="true" value="+ Add"
onclick="ingButton(); return false;"
> >
<!-- <f:ajax execute="ctlAddIng" </p:commandButton>
render="ingredientTable ctlAddIng"
listener="recipeDetailBean.ajaxAddIngredient"
/> -->
</p:button>
</h:panelGroup> </h:panelGroup>
</p:panel> </p:panel>
</p:tab> </p:tab>

Loading…
Cancel
Save