parent
11f77cf995
commit
50a07f0f6d
8 changed files with 1261 additions and 50 deletions
@ -0,0 +1,898 @@ |
|||||||
|
package com.mousetech.gourmetj; |
||||||
|
|
||||||
|
import java.io.Serializable; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
|
||||||
|
import javax.faces.event.AjaxBehaviorEvent; |
||||||
|
import javax.faces.model.DataModel; |
||||||
|
import javax.faces.model.ListDataModel; |
||||||
|
import javax.faces.view.ViewScoped; |
||||||
|
import javax.inject.Inject; |
||||||
|
import javax.inject.Named; |
||||||
|
import javax.servlet.http.Part; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.mousetech.gourmetj.IngredientUI; |
||||||
|
import com.mousetech.gourmetj.UserSession; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Category; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Ingredient; |
||||||
|
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 |
||||||
|
* |
||||||
|
* @author timh |
||||||
|
* @since Jun 28, 2012 TODO: Cross-reference ingredients to |
||||||
|
* keylookup TODO: Cross-reference shopcats |
||||||
|
*/ |
||||||
|
|
||||||
|
@Named |
||||||
|
@ViewScoped |
||||||
|
public class RecipeDetailBean implements Serializable { |
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L; |
||||||
|
|
||||||
|
/* Logger */ |
||||||
|
|
||||||
|
private static final Logger log = |
||||||
|
LoggerFactory.getLogger(RecipeDetailBean.class); |
||||||
|
|
||||||
|
/** |
||||||
|
* Default Constructor. |
||||||
|
*/ |
||||||
|
public RecipeDetailBean() { |
||||||
|
log.warn("Constructing RecipeDetail " + this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Persistency service for Recipes |
||||||
|
*/ |
||||||
|
|
||||||
|
@Inject |
||||||
|
transient RecipeService recipeService; |
||||||
|
|
||||||
|
public void setRecipeService(RecipeService service) { |
||||||
|
log.debug("INJECT RECIPESERVICE===" + service); |
||||||
|
this.recipeService = service; |
||||||
|
} |
||||||
|
|
||||||
|
private String instructions = null; |
||||||
|
|
||||||
|
@Inject |
||||||
|
UserSession userSession; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the userSession |
||||||
|
*/ |
||||||
|
public UserSession getUserSession() { |
||||||
|
return userSession; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param userSession the userSession to set |
||||||
|
*/ |
||||||
|
public void setUserSession(UserSession userSession) { |
||||||
|
this.userSession = userSession; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param instructions the instructions to set |
||||||
|
* @see #getInstructions() |
||||||
|
*/ |
||||||
|
public void setInstructions(String instructions) { |
||||||
|
this.instructions = instructions; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ingredients the ingredients to set |
||||||
|
*/ |
||||||
|
public void setIngredients( |
||||||
|
DataModel<IngredientUI> ingredients) { |
||||||
|
this.ingredients = ingredients; |
||||||
|
} |
||||||
|
|
||||||
|
// **
|
||||||
|
private String ingredientText = ""; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the ingredientText |
||||||
|
*/ |
||||||
|
public String getIngredientText() { |
||||||
|
return ingredientText; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ingredientText the ingredientText to set |
||||||
|
*/ |
||||||
|
public void setIngredientText(String ingredientText) { |
||||||
|
this.ingredientText = ingredientText; |
||||||
|
} |
||||||
|
|
||||||
|
// ==================================
|
||||||
|
|
||||||
|
private Recipe recipe = new Recipe(); |
||||||
|
|
||||||
|
public Recipe getRecipe() { |
||||||
|
if (this.recipe.getId() == null) { |
||||||
|
Long recipeId = userSession.getLastEdit(); |
||||||
|
if (recipeId != null) { |
||||||
|
loadRecipe(recipeId); |
||||||
|
} |
||||||
|
} |
||||||
|
return this.recipe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param recipe the recipe to set Required by JSF 2.2, but |
||||||
|
* DON'T USE! |
||||||
|
*/ |
||||||
|
public void setRecipe(Recipe recipe) { |
||||||
|
this.recipe = recipe; |
||||||
|
} |
||||||
|
|
||||||
|
public String getTitle() { |
||||||
|
return "ATITLE"; |
||||||
|
} |
||||||
|
|
||||||
|
/**/ |
||||||
|
transient DataModel<IngredientUI> ingredients; |
||||||
|
|
||||||
|
/**/ |
||||||
|
private String category = ""; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the category as a comma-separated list. |
||||||
|
*/ |
||||||
|
public String getCategory() { |
||||||
|
return category; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param category the category (list) to set |
||||||
|
* @see #getCategory() |
||||||
|
*/ |
||||||
|
public void setCategory(String category) { |
||||||
|
this.category = category; |
||||||
|
this.setDirty(); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean dirty; |
||||||
|
|
||||||
|
private String modifications; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the dirty |
||||||
|
*/ |
||||||
|
public boolean isDirty() { |
||||||
|
return dirty; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param dirty the dirty to set |
||||||
|
*/ |
||||||
|
public void setDirty(boolean dirty) { |
||||||
|
this.dirty = dirty; |
||||||
|
} |
||||||
|
|
||||||
|
public void setDirty() { |
||||||
|
setDirty(true); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the ingredients |
||||||
|
*/ |
||||||
|
public DataModel<IngredientUI> getIngredients() { |
||||||
|
if (ingredients == null) { |
||||||
|
ingredients = new ListDataModel<>(); |
||||||
|
ingredients |
||||||
|
.setWrappedData(new ArrayList<IngredientUI>(1)); |
||||||
|
} |
||||||
|
return ingredients; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* After construction and injection, we obtain the recipe ID |
||||||
|
* passed to us, if any and load the recipe. It's also stored |
||||||
|
* in @see UserSession for the detail editor and |
||||||
|
* |
||||||
|
* @see PictureController. |
||||||
|
*/ |
||||||
|
@PostConstruct |
||||||
|
private void init() { |
||||||
|
this.recipe = userSession.getRecipe(); |
||||||
|
|
||||||
|
/** |
||||||
|
* For "create new, this recipe is a blank constructed |
||||||
|
* and passed from main page. For Detail display, it's |
||||||
|
* null and ID come in via Flash scope. For Detail Edit, |
||||||
|
* it's passed from detail display or "create new". |
||||||
|
*/ |
||||||
|
if (this.recipe == null) { |
||||||
|
Long rid = |
||||||
|
(Long) JSFUtils.flashScope().get("recipeID"); |
||||||
|
if (rid != null) { |
||||||
|
this.recipe = loadRecipe(rid); |
||||||
|
} else { |
||||||
|
// alternative (and probably dead) version of "new
|
||||||
|
// recipe".
|
||||||
|
this.recipe = new Recipe(); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
userSession.setRecipe(this.recipe); |
||||||
|
|
||||||
|
getIngredients().setWrappedData( |
||||||
|
buildIngredientFacade(recipe.getIngredientHash())); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Load recipe from database. @see RecipeDetailBean#init() |
||||||
|
* |
||||||
|
* @param recipeId ID of recipe to load |
||||||
|
* @return loaded recipe or null, if not found. |
||||||
|
*/ |
||||||
|
private Recipe loadRecipe(Long recipeId) { |
||||||
|
Recipe recipe = recipeService.findDetails(recipeId); |
||||||
|
if ( recipe == null ) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
Set<Category> cList = recipe.getCategories(); |
||||||
|
StringBuffer sb = new StringBuffer(35); |
||||||
|
boolean first = true; |
||||||
|
for (Category cat : cList) { |
||||||
|
if (first) { |
||||||
|
first = false; |
||||||
|
} else { |
||||||
|
sb.append(", "); |
||||||
|
} |
||||||
|
sb.append(cat.getCategory()); |
||||||
|
} |
||||||
|
this.category = sb.toString(); |
||||||
|
return recipe; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Wrap a list of Ingredients in a decorative facade for |
||||||
|
* better displayability. |
||||||
|
* |
||||||
|
* Ingredient group is actually part of Ingredient. Add dummy |
||||||
|
* "ingredients" to make them separate lines. |
||||||
|
* |
||||||
|
* @param ingredients |
||||||
|
* @return Wrapped list |
||||||
|
*/ |
||||||
|
private List<IngredientUI> buildIngredientFacade( |
||||||
|
List<Ingredient> ingredients) { |
||||||
|
final List<IngredientUI> list = |
||||||
|
new ArrayList<>(ingredients.size()); |
||||||
|
String currentIngGroup = null; |
||||||
|
|
||||||
|
for (Ingredient ing : ingredients) { |
||||||
|
String ingGroup = ing.getInggroup(); |
||||||
|
if (ingGroup != null && ingGroup.isBlank()) { |
||||||
|
ingGroup = null; |
||||||
|
} |
||||||
|
if (!Objects.equals(ingGroup, currentIngGroup)) { |
||||||
|
Ingredient dummy = new Ingredient(); |
||||||
|
IngredientUI groupIng = new IngredientUI(dummy); |
||||||
|
groupIng.setItem(ingGroup); |
||||||
|
groupIng.setIngGroup(true); |
||||||
|
list.add(groupIng); |
||||||
|
currentIngGroup = ingGroup; |
||||||
|
} |
||||||
|
|
||||||
|
IngredientUI ingUi = new IngredientUI(ing); |
||||||
|
list.add(ingUi); |
||||||
|
// Shopcat is an eager fetch on Ingredient
|
||||||
|
Shopcat shopCat = ing.getShopCat(); |
||||||
|
if (shopCat != null) { |
||||||
|
ingUi.setShopCat(shopCat.getShopcategory()); |
||||||
|
} |
||||||
|
} |
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the instructions (cached) |
||||||
|
*/ |
||||||
|
public String getInstructions() { |
||||||
|
if (instructions == null) { |
||||||
|
instructions = formatInstructions( |
||||||
|
getRecipe().getInstructions()); |
||||||
|
} |
||||||
|
return instructions; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the instructions (cached) |
||||||
|
*/ |
||||||
|
public String getModifications() { |
||||||
|
if (this.modifications == null) { |
||||||
|
this.modifications = formatInstructions( |
||||||
|
getRecipe().getModifications()); |
||||||
|
} |
||||||
|
return this.modifications; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert instruction plain-text to HTML form |
||||||
|
* |
||||||
|
* @param instructions |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private String formatInstructions(String instructions) { |
||||||
|
if (instructions == null) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
String s = instructions.replace("\n\n", "<p/>"); |
||||||
|
s = s.replace("\n", "<br/>"); |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Action for "Add Ingredient" button |
||||||
|
* |
||||||
|
* @return null - stay on page. The Ingredient list table |
||||||
|
* will update. |
||||||
|
*/ |
||||||
|
public String doAddIngredient() { |
||||||
|
this.addIngredientList(this.getIngredientText()); |
||||||
|
setIngredientText(""); // clear for next entry
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
// =====
|
||||||
|
/** |
||||||
|
* Handle entry of a single ingredient line into the input |
||||||
|
* form. |
||||||
|
* |
||||||
|
* @param event Unused??? |
||||||
|
*/ |
||||||
|
public void ajaxAddIngredient(AjaxBehaviorEvent event) { |
||||||
|
doAddIngredient(); |
||||||
|
} |
||||||
|
|
||||||
|
public void ajaxMoveUp(AjaxBehaviorEvent event) { |
||||||
|
if (!isMoveUpAble()) { |
||||||
|
JSFUtils.addErrorMessage("Cannot move up."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final List<IngredientUI> rows = getWrappedIngredients(); |
||||||
|
final int ingSize = rows.size(); |
||||||
|
for (int i = 1; i < ingSize; i++) { |
||||||
|
IngredientUI r = rows.get(i); |
||||||
|
if (r.isSelected()) { |
||||||
|
// swap with preceding row.
|
||||||
|
rows.remove(i); |
||||||
|
rows.add(i - 1, r); |
||||||
|
} |
||||||
|
} |
||||||
|
this.setDirty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Move selected rows down. |
||||||
|
* |
||||||
|
* @param eventUnused |
||||||
|
*/ |
||||||
|
public void ajaxMoveDown(AjaxBehaviorEvent event) { |
||||||
|
if (!isMoveDownAble()) { |
||||||
|
JSFUtils.addErrorMessage("Cannot move down."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final List<IngredientUI> rows = getWrappedIngredients(); |
||||||
|
final int ingSize = rows.size() - 1; |
||||||
|
for (int i = ingSize; i > 0; i--) { |
||||||
|
IngredientUI r = rows.get(i - 1); |
||||||
|
if (r.isSelected()) { |
||||||
|
// swap with following row.
|
||||||
|
rows.remove(i - 1); |
||||||
|
rows.add(i, r); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void ajaxDeleteItems(AjaxBehaviorEvent event) { |
||||||
|
final List<IngredientUI> rows = getWrappedIngredients(); |
||||||
|
List<IngredientUI> selectedRows = |
||||||
|
new ArrayList<IngredientUI>(); |
||||||
|
for (IngredientUI row : rows) { |
||||||
|
if (row.isSelected()) { |
||||||
|
this.dirty = true; |
||||||
|
// Delete row from list.
|
||||||
|
selectedRows.add(row); |
||||||
|
} |
||||||
|
} |
||||||
|
// 2nd stage to avoid ConcurrentModificationException
|
||||||
|
for (IngredientUI row : selectedRows) { |
||||||
|
rows.remove(row); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Bulk add for ingredients. Looks for long input and if |
||||||
|
* found, tries to split it up and add as multiple |
||||||
|
* ingredients. |
||||||
|
* |
||||||
|
* @param ingredientText2 |
||||||
|
* @see #addIngredient(String) |
||||||
|
*/ |
||||||
|
private void addIngredientList(String ingredientTextLines) { |
||||||
|
if (ingredientTextLines.length() < 40) { |
||||||
|
addIngredient(ingredientTextLines); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Otherwise, try for split.
|
||||||
|
String[] lineArray = ingredientTextLines.split(" "); |
||||||
|
for (String line : lineArray) { |
||||||
|
if (line.isBlank()) { |
||||||
|
continue; // actually should discard any above
|
||||||
|
// this
|
||||||
|
} |
||||||
|
if (line.toLowerCase().contains("ingredients")) { |
||||||
|
continue; // actually should discard any above
|
||||||
|
// this
|
||||||
|
} |
||||||
|
addIngredient(line); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add ingredient text line to recipe |
||||||
|
* |
||||||
|
* @param ingredientText |
||||||
|
* @see #doAddIngredient(), bulk loader |
||||||
|
* @see #addIngredientList(String) |
||||||
|
*/ |
||||||
|
public void addIngredient(String ingredientText) { |
||||||
|
log.info("Ingredient line: \"" + ingredientText + "\""); |
||||||
|
Ingredient ing = |
||||||
|
IngredientDigester.digest(ingredientText); |
||||||
|
// get ing list size, set ing position, append
|
||||||
|
List<IngredientUI> ingredients = getWrappedIngredients(); |
||||||
|
int lsize = ingredients.size(); |
||||||
|
ing.setPosition(lsize + 1); |
||||||
|
ingredients.add(new IngredientUI(ing)); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isSelectionActive() { |
||||||
|
List<IngredientUI> rows = getWrappedIngredients(); |
||||||
|
for (IngredientUI row : rows) { |
||||||
|
if (row.isSelected()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public void setSelectionActive(boolean value) { |
||||||
|
// This is required by JBoss JSF, but the property is
|
||||||
|
// read-only.
|
||||||
|
} |
||||||
|
|
||||||
|
public void setMoveUpAble() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void setMoveDownAble() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMoveUpAble() { |
||||||
|
if (isSelectionActive()) { |
||||||
|
List<IngredientUI> rows = getWrappedIngredients(); |
||||||
|
return !rows.get(0).isSelected(); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isMoveDownAble() { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// ===
|
||||||
|
|
||||||
|
/** |
||||||
|
* Convenience method to get ingredientUI list without |
||||||
|
* constant whining about unchecked casting. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private List<IngredientUI> getWrappedIngredients() { |
||||||
|
return (List<IngredientUI>) this.getIngredients() |
||||||
|
.getWrappedData(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Save the recipe. |
||||||
|
* |
||||||
|
* @return Main page if OK, re-display with error if cannot |
||||||
|
* save ingredients. |
||||||
|
*/ |
||||||
|
public String doSave() { |
||||||
|
if (!saveIngredients()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
updateRecipeCategories(recipe, category); |
||||||
|
// Rebuild ingredients list with groups applied
|
||||||
|
updateRecipeGroups(getWrappedIngredients()); |
||||||
|
recipeService.save(recipe); |
||||||
|
userSession.setRecipe(null); |
||||||
|
setDirty(false); |
||||||
|
return "main"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Delete the recipe. This will cause ingredients to be |
||||||
|
* deleted by cascade. |
||||||
|
* |
||||||
|
* @return Main page |
||||||
|
*/ |
||||||
|
public String doDelete() { |
||||||
|
recipeService.delete(this.recipe); |
||||||
|
this.userSession.setLastEdit(null); // Don't point to me!
|
||||||
|
return "main"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Apply ingredient group IDs (optional) to individual |
||||||
|
* ingredients. |
||||||
|
* |
||||||
|
* @param wrappedIngredients The wrapped ingredient facade. |
||||||
|
*/ |
||||||
|
private void updateRecipeGroups( |
||||||
|
List<IngredientUI> wrappedIngredients) { |
||||||
|
String ingGroup = null; |
||||||
|
for (IngredientUI ingUI : wrappedIngredients) { |
||||||
|
if (ingUI.isIngGroup()) { |
||||||
|
ingGroup = ingUI.getItem(); |
||||||
|
} else { |
||||||
|
ingUI.getIngredient().setInggroup(ingGroup); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update ingredients and shopcat from UI model |
||||||
|
* |
||||||
|
* @return false if a category has no ingredient key to link |
||||||
|
* it. |
||||||
|
*/ |
||||||
|
private boolean saveIngredients() { |
||||||
|
List<IngredientUI> saveIng = getWrappedIngredients(); |
||||||
|
List<Ingredient> iList = recipe.getIngredientHash(); |
||||||
|
iList.clear(); |
||||||
|
for (IngredientUI iui : saveIng) { |
||||||
|
Ingredient ing = iui.getIngredient(); |
||||||
|
ing.setRecipe(recipe); |
||||||
|
String ingKey = iui.getIngkey(); |
||||||
|
String shopCatName = iui.getShopCat(); |
||||||
|
Shopcat scat = ing.getShopCat(); |
||||||
|
if (scat == null) { |
||||||
|
if ((ingKey != null) && !ingKey.isBlank()) { |
||||||
|
scat = new Shopcat(); |
||||||
|
scat.setIngkey(ingKey); |
||||||
|
scat.setShopcategory(shopCatName); |
||||||
|
ing.setShopCat(scat); |
||||||
|
} else { |
||||||
|
JSFUtils.addErrorMessage( |
||||||
|
"Shopping Category requires an Ingredient Key"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if ((ingKey == null) || ingKey.isBlank()) { |
||||||
|
ing.setShopCat(null); |
||||||
|
} else { |
||||||
|
ing.getShopCat() |
||||||
|
.setShopcategory(shopCatName); |
||||||
|
} |
||||||
|
} |
||||||
|
iList.add(ing); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parse out the comma-separated category text control and |
||||||
|
* post the results as children of the recipe |
||||||
|
* |
||||||
|
* @param recipe2 |
||||||
|
* @param category2 |
||||||
|
*/ |
||||||
|
private void updateRecipeCategories(Recipe recipe2, |
||||||
|
String category2) { |
||||||
|
final Set<Category> oldList = recipe2.getCategories(); |
||||||
|
List<Category> newList = new ArrayList<Category>(); |
||||||
|
String[] cats = this.category.split(","); |
||||||
|
for (String s : cats) { |
||||||
|
s = s.trim(); |
||||||
|
if (!s.isEmpty()) { |
||||||
|
Category newCat = new Category(); |
||||||
|
newCat.setRecipe(recipe2); |
||||||
|
newCat.setCategory(s); |
||||||
|
newList.add(newCat); |
||||||
|
} |
||||||
|
} |
||||||
|
for (Category cat : newList) { |
||||||
|
// For existing Categories, use existing ID.
|
||||||
|
Category ocat = searchCategory(oldList, cat); |
||||||
|
if (ocat != null) { |
||||||
|
cat.setId(ocat.getId()); |
||||||
|
} |
||||||
|
} |
||||||
|
recipe.setCategories(new HashSet(newList)); |
||||||
|
} |
||||||
|
|
||||||
|
private Category searchCategory(Set<Category> oldList, |
||||||
|
Category cat) { |
||||||
|
String catName = cat.getCategory(); |
||||||
|
for (Category c : oldList) { |
||||||
|
if (catName.equals(c.getCategory())) { |
||||||
|
return c; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Partial input sent via AJAX when suggestion box is keyed. |
||||||
|
* called each time a character is entered or removed, |
||||||
|
* subject (I presume) to the minimum character limit. |
||||||
|
* |
||||||
|
* Use this to assemble the suggestion list. |
||||||
|
*/ |
||||||
|
private String cuisinePartial; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the cuisinePartial |
||||||
|
*/ |
||||||
|
public String getCuisinePartial() { |
||||||
|
return cuisinePartial; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param cuisinePartial the cuisinePartial to set |
||||||
|
*/ |
||||||
|
public void setCuisinePartial(String cuisinePartial) { |
||||||
|
this.cuisinePartial = cuisinePartial; |
||||||
|
this.cuisineList = null; // trigger construction of new
|
||||||
|
// list.
|
||||||
|
} |
||||||
|
|
||||||
|
private List<String> masterCuisineList = null; |
||||||
|
private List<String> cuisineList = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* Load the Cuisine list, which is assembled by scanning |
||||||
|
* cuisine fields from all recipes in the database at the |
||||||
|
* time we are called. |
||||||
|
*/ |
||||||
|
private List<String> loadCuisineList() { |
||||||
|
List<String> cuisines = recipeService.findCuisines(); |
||||||
|
return cuisines; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the master cuisineList |
||||||
|
*/ |
||||||
|
public List<String> getMasterCuisineList() { |
||||||
|
if (this.masterCuisineList == null) { |
||||||
|
this.masterCuisineList = loadCuisineList(); |
||||||
|
} |
||||||
|
return this.masterCuisineList; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the cuisineList built by matching the master list |
||||||
|
* agaist the partial cuisine names. |
||||||
|
*/ |
||||||
|
public List<String> getCuisineList() { |
||||||
|
if (this.cuisineList == null) { |
||||||
|
this.cuisineList = buildCuisineList(); |
||||||
|
} |
||||||
|
|
||||||
|
return this.cuisineList; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Using the cuisinePartial, build a subset of the master |
||||||
|
* cuisine list. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private List<String> buildCuisineList() { |
||||||
|
List<String> list = new ArrayList<String>(); |
||||||
|
String partial = this.cuisinePartial; |
||||||
|
// Handle cases where we aren't set up, or the partial
|
||||||
|
// input is blank.
|
||||||
|
if (partial == null) { |
||||||
|
return list; |
||||||
|
} |
||||||
|
partial = partial.trim(); |
||||||
|
if (partial.isEmpty()) { |
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
List<String> masterList = this.getMasterCuisineList(); |
||||||
|
for (String s : masterList) { |
||||||
|
if (s.contains(this.cuisinePartial)) { |
||||||
|
list.add(s); |
||||||
|
} |
||||||
|
} |
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param cuisineList the cuisineList to set |
||||||
|
*/ |
||||||
|
public void setCuisineList(List<String> cuisineList) { |
||||||
|
this.cuisineList = cuisineList; |
||||||
|
} |
||||||
|
|
||||||
|
// ***
|
||||||
|
private String shopcatPartial; |
||||||
|
|
||||||
|
/** |
||||||
|
* @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; |
||||||
|
|
||||||
|
public List<String> getShopcatList() { |
||||||
|
if (shopcatList == null) { |
||||||
|
shopcatList = recipeService.findShoppingCategories(); |
||||||
|
} |
||||||
|
return shopcatList; |
||||||
|
} |
||||||
|
|
||||||
|
// ***
|
||||||
|
|
||||||
|
public String editDescription() { |
||||||
|
this.setDetailTab(0); |
||||||
|
return "detailEdit"; |
||||||
|
} |
||||||
|
|
||||||
|
public String editIngredients() { |
||||||
|
this.setDetailTab(1); |
||||||
|
return "detailEdit"; |
||||||
|
} |
||||||
|
|
||||||
|
public String editInstructions() { |
||||||
|
this.setDetailTab(2); |
||||||
|
return "detailEdit"; |
||||||
|
} |
||||||
|
|
||||||
|
public String editNotes() { |
||||||
|
this.setDetailTab(3); |
||||||
|
return "detailEdit"; |
||||||
|
} |
||||||
|
|
||||||
|
private void setDetailTab(int i) { |
||||||
|
this.userSession.setDetailTab(i); |
||||||
|
} |
||||||
|
|
||||||
|
// ***
|
||||||
|
// *** Category suggestions
|
||||||
|
private String catToAdd = ""; |
||||||
|
private List<String> suggestCategory = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the catToAdd |
||||||
|
*/ |
||||||
|
public String getCatToAdd() { |
||||||
|
return catToAdd; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param catToAdd the catToAdd to set |
||||||
|
*/ |
||||||
|
public void setCatToAdd(String catToAdd) { |
||||||
|
this.catToAdd = catToAdd; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the suggestCategory |
||||||
|
*/ |
||||||
|
public List<String> getSuggestCategory() { |
||||||
|
if (suggestCategory == null) { |
||||||
|
suggestCategory = loadCategories(); |
||||||
|
} |
||||||
|
return suggestCategory; |
||||||
|
} |
||||||
|
|
||||||
|
private List<String> loadCategories() { |
||||||
|
List<String> catList = |
||||||
|
this.recipeService.findCategories(); |
||||||
|
return catList; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param suggestCategory the suggestCategory to set |
||||||
|
*/ |
||||||
|
public void setSuggestCategory( |
||||||
|
List<String> suggestCategory) { |
||||||
|
this.suggestCategory = suggestCategory; |
||||||
|
} |
||||||
|
|
||||||
|
public void ajaxSuggestCategory(AjaxBehaviorEvent event) { |
||||||
|
if (!this.category.isBlank()) { |
||||||
|
this.category += ", "; |
||||||
|
} |
||||||
|
this.category += catToAdd; |
||||||
|
catToAdd = ""; |
||||||
|
} |
||||||
|
|
||||||
|
// ***
|
||||||
|
|
||||||
|
Part imageFile = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the imageFile set by the image upload control |
||||||
|
*/ |
||||||
|
public Part getImageFile() { |
||||||
|
return imageFile; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param imageFile the imageFile to set |
||||||
|
*/ |
||||||
|
public void setImageFile(Part imageFile) { |
||||||
|
this.imageFile = imageFile; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Load/replace images. Computes thumbnail. |
||||||
|
* @param event Notused |
||||||
|
*/ |
||||||
|
public void ajaxUploadImage(AjaxBehaviorEvent event) { |
||||||
|
// String fileType = imageFile.getContentType();
|
||||||
|
PictureController.importImage(recipe, imageFile); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove images from recipe |
||||||
|
* @param event Notused |
||||||
|
*/ |
||||||
|
public void ajaxDeleteImage(AjaxBehaviorEvent event) { |
||||||
|
this.recipe.setImage(null); |
||||||
|
this.recipe.setThumb(null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return marker for image. Unlike normal JSF, I don't |
||||||
|
* care if it gets multiple times and returns different values. |
||||||
|
* |
||||||
|
* @return "random" string |
||||||
|
*/ |
||||||
|
public String getCurrentTime() { |
||||||
|
long now = new java.util.Date().getTime(); |
||||||
|
return String.valueOf(now); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,161 @@ |
|||||||
|
<?xml version="1.0"?> |
||||||
|
<ui:composition template="/WEB-INF/layout/layout.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" |
||||||
|
> |
||||||
|
<ui:define name="title">Gourmet Recipe Manager</ui:define> |
||||||
|
<ui:define name="content"> |
||||||
|
<h:messages /> |
||||||
|
<h:form id="form1"> |
||||||
|
<p:panelGrid label="#{recipeDetailBean.recipe.title}" |
||||||
|
columns="1" |
||||||
|
> |
||||||
|
<p:panelGrid > |
||||||
|
<f:facet name="header"> |
||||||
|
<h:outputText |
||||||
|
value="#{recipeDetailBean.recipe.title}" |
||||||
|
/> |
||||||
|
</f:facet> |
||||||
|
<p:row> |
||||||
|
<p:column id="leftCol" style="width: 80%"> |
||||||
|
|
||||||
|
<img id="bigpix" |
||||||
|
src="/img/picture/#{recipeDetailBean.recipe.id}" |
||||||
|
/> |
||||||
|
<p:panelGrid columns="2"> |
||||||
|
<p:button value="<- Back" |
||||||
|
action="main" |
||||||
|
/> |
||||||
|
<p:button value="Print" |
||||||
|
action="recipePrint" |
||||||
|
/> |
||||||
|
<p:outputLabel for="@next" |
||||||
|
value="Categories:" |
||||||
|
/> |
||||||
|
<h:outputText |
||||||
|
label="Category: " |
||||||
|
value="#{adminMainBean.formatCategories(recipeDetailBean.recipe)}" |
||||||
|
/> |
||||||
|
<p:outputLabel for="@next" |
||||||
|
value="Cuisine:" |
||||||
|
/> |
||||||
|
<h:outputText |
||||||
|
label="Cuisine: " |
||||||
|
value="#{recipeDetailBean.recipe.cuisine}" |
||||||
|
/> |
||||||
|
<p:outputLabel for="@next" |
||||||
|
value="Prep Time:" |
||||||
|
/> |
||||||
|
<h:outputText |
||||||
|
label="Prep Time: " |
||||||
|
value="#{recipeDetailBean.recipe.preptime}" |
||||||
|
/> |
||||||
|
<p:outputLabel for="@next" |
||||||
|
value="Cook Time:" |
||||||
|
/> |
||||||
|
<h:outputText |
||||||
|
label="Cook Time: " |
||||||
|
value="#{recipeDetailBean.recipe.cooktime}" |
||||||
|
/> |
||||||
|
</p:panelGrid> |
||||||
|
<h:commandLink |
||||||
|
value="Edit Details" |
||||||
|
action="#{recipeDetailBean.editDescription}" |
||||||
|
> |
||||||
|
<c:set target="#{flash}" |
||||||
|
property="recipeID" |
||||||
|
value="#{recipeDetailBean.recipe.id}" |
||||||
|
/> |
||||||
|
</h:commandLink> |
||||||
|
<!-- --> |
||||||
|
<p:panelGrid label="Instructions" |
||||||
|
columns="1" |
||||||
|
> |
||||||
|
<h:outputText id="instructions" |
||||||
|
escape="false" |
||||||
|
value="#{recipeDetailBean.instructions}" |
||||||
|
/> |
||||||
|
<h:commandLink |
||||||
|
value="Edit Instructions" |
||||||
|
action="#{recipeDetailBean.editInstructions}" |
||||||
|
/> |
||||||
|
</p:panelGrid> |
||||||
|
<p:panelGrid label="Notes" |
||||||
|
columns="1" |
||||||
|
> |
||||||
|
<h:outputText escape="false" |
||||||
|
value="#{recipeDetailBean.modifications}" |
||||||
|
/> |
||||||
|
<h:commandLink value="Edit Notes" |
||||||
|
action="#{recipeDetailBean.editNotes}" |
||||||
|
/> |
||||||
|
</p:panelGrid> |
||||||
|
<p:button id="ctlDelete" |
||||||
|
value="Delete Recipe" |
||||||
|
action="#{recipeDetailBean.doDelete}" |
||||||
|
immediate="true" |
||||||
|
> |
||||||
|
<f:facet name="confirmation"> |
||||||
|
<h:outputText |
||||||
|
value="Delete this recipe. Are you sure?" |
||||||
|
/> |
||||||
|
</f:facet> |
||||||
|
</p:button> |
||||||
|
</p:column> |
||||||
|
<!-- ====== Ingredients ============================ --> |
||||||
|
<p:column id="ingredientsc" |
||||||
|
style="width: 20%" |
||||||
|
> |
||||||
|
<h:commandLink |
||||||
|
value="Edit Ingredients" |
||||||
|
style="vertical-align: top" |
||||||
|
action="#{recipeDetailBean.editIngredients}" |
||||||
|
/> |
||||||
|
<p:dataTable id="ingredients" |
||||||
|
showDirectLinksArrows="true" |
||||||
|
value="#{recipeDetailBean.ingredients}" |
||||||
|
var="ingredient" |
||||||
|
> |
||||||
|
<p:column label="Amt" |
||||||
|
style="width: 4em; text-align: right" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{ingredient.displayAmount}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column label="Units" |
||||||
|
style="width: 8em" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{ingredient.unit}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column label="Item" |
||||||
|
style="width: 20em" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{ingredient.item}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column label="Optional" |
||||||
|
style="width: 2em" |
||||||
|
> |
||||||
|
<p:selectBooleanCheckbox |
||||||
|
readonly="true" |
||||||
|
value="#{ingredient.optionalCB}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:dataTable> |
||||||
|
<h:outputText |
||||||
|
value="Recipe ID: #{recipeDetailBean.recipe.id}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:row> |
||||||
|
</p:panelGrid> |
||||||
|
</p:panelGrid> |
||||||
|
</h:form> |
||||||
|
</ui:define> |
||||||
|
</ui:composition> |
@ -0,0 +1,87 @@ |
|||||||
|
<?xml version="1.0"?> |
||||||
|
<ui:composition template="/WEB-INF/layout/layout.xhtml" |
||||||
|
xmlns:c="http://java.sun.com/jsp/jstl/core" |
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core" |
||||||
|
xmlns:ui="http://java.sun.com/jsf/facelets" |
||||||
|
xmlns:tc="http://myfaces.apache.org/tobago/component" |
||||||
|
> |
||||||
|
<!-- Print Recipe --> |
||||||
|
<ui:define name="title">Gourmet Recipe Manager</ui:define> |
||||||
|
<ui:define name="content"> |
||||||
|
<tc:messages /> |
||||||
|
<tc:section level="2" |
||||||
|
label="#{recipeDetailBean.recipe.title}" |
||||||
|
> |
||||||
|
<tc:flexLayout columns="auto 6em auto"> |
||||||
|
<tc:image id="bigpix" |
||||||
|
value="/gourmetj/img/picture/#{recipeDetailBean.recipe.id}" |
||||||
|
/> |
||||||
|
<tc:button label="<- Back" action="main"> |
||||||
|
<tc:style width="5em" height="2em"/> |
||||||
|
</tc:button> |
||||||
|
<tc:gridLayout columns="15em 15em"> |
||||||
|
<tc:out label="Category: " |
||||||
|
value="#{adminMainBean.formatCategories(recipeDetailBean.recipe)}" |
||||||
|
/> |
||||||
|
<tc:out label="Cuisine: " |
||||||
|
value="#{recipeDetailBean.recipe.cuisine}" |
||||||
|
/> |
||||||
|
<tc:out label="Prep Time: " |
||||||
|
value="#{recipeDetailBean.recipe.preptime}" |
||||||
|
/> |
||||||
|
<tc:out label="Cook Time: " |
||||||
|
value="#{recipeDetailBean.recipe.preptime}" |
||||||
|
/> |
||||||
|
</tc:gridLayout> |
||||||
|
</tc:flexLayout> |
||||||
|
<!-- --> |
||||||
|
<tc:section id="ingredientsc" label="Ingredients"> |
||||||
|
<tc:sheet id="ingredients" |
||||||
|
showDirectLinksArrows="true" |
||||||
|
value="#{recipeDetailBean.ingredients}" |
||||||
|
var="ingredient" |
||||||
|
> |
||||||
|
<tc:row> |
||||||
|
<tc:style |
||||||
|
customClass="#{item.ingGroup ? 'stGroup' : ''}" |
||||||
|
/> |
||||||
|
</tc:row> |
||||||
|
<tc:column label="Amt" align="right"> |
||||||
|
<tc:out |
||||||
|
value="#{ingredient.displayAmount}" |
||||||
|
/> |
||||||
|
</tc:column> |
||||||
|
<tc:column label="Units"> |
||||||
|
<tc:out value="#{ingredient.unit}" /> |
||||||
|
</tc:column> |
||||||
|
<tc:column label="Item"> |
||||||
|
<tc:out value="#{ingredient.item}" /> |
||||||
|
</tc:column> |
||||||
|
<tc:column label="Optional" align="center"> |
||||||
|
<tc:selectBooleanCheckbox readonly="true" |
||||||
|
value="#{ingredient.optional}" |
||||||
|
/> |
||||||
|
</tc:column> |
||||||
|
</tc:sheet> |
||||||
|
<tc:out |
||||||
|
value="Recipe ID: #{recipeDetailBean.recipe.id}" |
||||||
|
/> |
||||||
|
</tc:section> |
||||||
|
<tc:segmentLayout medium="7seg 5seg"> |
||||||
|
<tc:panel id="summ"> |
||||||
|
<!-- --> |
||||||
|
<tc:box label="Instructions"> |
||||||
|
<tc:out id="instructions" escape="false" |
||||||
|
value="#{recipeDetailBean.instructions}" |
||||||
|
/> |
||||||
|
</tc:box> |
||||||
|
<tc:box label="Notes"> |
||||||
|
<tc:out escape="false" |
||||||
|
value="#{recipeDetailBean.modifications}" |
||||||
|
/> |
||||||
|
</tc:box> |
||||||
|
</tc:panel> |
||||||
|
</tc:segmentLayout> |
||||||
|
</tc:section> |
||||||
|
</ui:define> |
||||||
|
</ui:composition> |
Loading…
Reference in new issue