Converted detail view with basic layout
This commit is contained in:
parent
11f77cf995
commit
50a07f0f6d
|
@ -3,7 +3,7 @@ package com.mousetech.gourmetj;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.faces.event.AjaxBehaviorEvent;
|
||||||
import javax.faces.model.DataModel;
|
import javax.faces.model.DataModel;
|
||||||
import javax.faces.model.ListDataModel;
|
import javax.faces.model.ListDataModel;
|
||||||
import javax.faces.view.ViewScoped;
|
import javax.faces.view.ViewScoped;
|
||||||
|
@ -129,6 +129,15 @@ public class AdminMainBean implements Serializable {
|
||||||
doFind();
|
doFind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove images from recipe
|
||||||
|
* @param event Notused
|
||||||
|
*/
|
||||||
|
public void ajaxUpdateList(AjaxBehaviorEvent event) {
|
||||||
|
this.doFind();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finder
|
* Finder
|
||||||
*
|
*
|
||||||
|
|
898
src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
Normal file
898
src/main/java/com/mousetech/gourmetj/RecipeDetailBean.java
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package com.mousetech.gourmetj.persistence.dao;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
@ -25,6 +26,10 @@ public interface RecipeRepository
|
||||||
|
|
||||||
List<Recipe> findByTitleContaining(String searchText);
|
List<Recipe> findByTitleContaining(String searchText);
|
||||||
|
|
||||||
|
@EntityGraph(value="Recipe.findWorkingSet")
|
||||||
|
public Recipe findDetailsById(Long recipeId);
|
||||||
|
|
||||||
|
|
||||||
// final static String SQL_FIND_CATEGORIES =
|
// final static String SQL_FIND_CATEGORIES =
|
||||||
// "SELECT DISTINCT category from categories"
|
// "SELECT DISTINCT category from categories"
|
||||||
// + " where category is not null and category <> ''"
|
// + " where category is not null and category <> ''"
|
||||||
|
|
|
@ -2,7 +2,9 @@ package com.mousetech.gourmetj.persistence.model;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.persistence.*;
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
@ -15,6 +17,10 @@ import javax.persistence.*;
|
||||||
@NamedQueries({
|
@NamedQueries({
|
||||||
@NamedQuery(name = "Recipe.findAll", query = "SELECT r FROM Recipe r"),
|
@NamedQuery(name = "Recipe.findAll", query = "SELECT r FROM Recipe r"),
|
||||||
@NamedQuery(name = "Recipe.findByTitle", query = "SELECT r FROM Recipe r WHERE r.title LIKE concat('%', :searchText,'%')") })
|
@NamedQuery(name = "Recipe.findByTitle", query = "SELECT r FROM Recipe r WHERE r.title LIKE concat('%', :searchText,'%')") })
|
||||||
|
@NamedEntityGraph(name = "Recipe.findWorkingSet", attributeNodes = {
|
||||||
|
@NamedAttributeNode(value = "categories"),
|
||||||
|
@NamedAttributeNode(value = "ingredientHash", subgraph = "subgraph.shopcat") }, subgraphs = {
|
||||||
|
@NamedSubgraph(name = "subgraph.shopcat", attributeNodes = @NamedAttributeNode(value = "shopCat")) })
|
||||||
public class Recipe implements Serializable {
|
public class Recipe implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -40,7 +46,8 @@ public class Recipe implements Serializable {
|
||||||
|
|
||||||
// Actual Ingredient_hash field is VARCHAR(32) - UUID???
|
// Actual Ingredient_hash field is VARCHAR(32) - UUID???
|
||||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<Ingredient> ingredientHash = new ArrayList<Ingredient>();
|
private List<Ingredient> ingredientHash =
|
||||||
|
new ArrayList<Ingredient>();
|
||||||
|
|
||||||
@Column(name = "instructions")
|
@Column(name = "instructions")
|
||||||
private String instructions;
|
private String instructions;
|
||||||
|
@ -82,13 +89,13 @@ public class Recipe implements Serializable {
|
||||||
private Double yields;
|
private Double yields;
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.EAGER, mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
private List<Category> categories =
|
private Set<Category> categories =
|
||||||
new ArrayList<Category>();
|
new HashSet<Category>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param categories the categories to set
|
* @param categories the categories to set
|
||||||
*/
|
*/
|
||||||
public void setCategories(List<Category> categories) {
|
public void setCategories(Set<Category> categories) {
|
||||||
this.categories = categories;
|
this.categories = categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +268,7 @@ public class Recipe implements Serializable {
|
||||||
return "Recipe #" + this.getId() + " " + this.getTitle();
|
return "Recipe #" + this.getId() + " " + this.getTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Category> getCategories() {
|
public Set<Category> getCategories() {
|
||||||
return this.categories;
|
return this.categories;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,20 @@ import java.util.Optional;
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javax.persistence.NamedEntityGraph;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
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.model.Category;
|
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.Recipe;
|
||||||
|
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
||||||
|
|
||||||
@Named
|
@Named
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
|
@ -22,6 +29,9 @@ public class RecipeService implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private static final Logger log =
|
||||||
|
LoggerFactory.getLogger(RecipeService.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private RecipeRepository recipeRepository;
|
private RecipeRepository recipeRepository;
|
||||||
|
|
||||||
|
@ -29,11 +39,6 @@ public class RecipeService implements Serializable {
|
||||||
return recipeRepository.findAll();
|
return recipeRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> findCuisines() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Recipe> findByTitle(String searchText) {
|
public List<Recipe> findByTitle(String searchText) {
|
||||||
return recipeRepository.findByTitleContaining(searchText);
|
return recipeRepository.findByTitleContaining(searchText);
|
||||||
}
|
}
|
||||||
|
@ -42,6 +47,36 @@ public class RecipeService implements Serializable {
|
||||||
return recipeRepository.findById(recipeId).orElse(null);
|
return recipeRepository.findById(recipeId).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Recipe findDetails(Long recipeId) {
|
||||||
|
return recipeRepository.findDetailsById(recipeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> findCuisines() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void save(Recipe recipe) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Recipe recipe) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> findShoppingCategories() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> findCategories() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// *
|
// *
|
||||||
// * @return All Category names that are not null/blank, sorted.
|
// * @return All Category names that are not null/blank, sorted.
|
||||||
|
|
|
@ -3,23 +3,23 @@
|
||||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||||
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"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
<ui:define name="title">Gourmet Recipe Manager</ui:define>
|
<ui:define name="title">Gourmet Recipe Manager</ui:define>
|
||||||
<ui:define name="content">
|
<ui:define name="content">
|
||||||
|
|
||||||
<h:form id="form1">
|
<h:form id="form1">
|
||||||
<p:panelGrid label="Find a Recipe" columns="1">
|
<p:panelGrid label="Find a Recipe" columns="1">
|
||||||
<p:panelGrid columns="3">
|
<p:panelGrid columns="3">
|
||||||
<p:focus for="@next"/>
|
|
||||||
<p:inputText id="searchFor"
|
<p:inputText id="searchFor"
|
||||||
placeholder="Recipe title (todo) cuisine, etc."
|
placeholder="Recipe title (todo) cuisine, etc."
|
||||||
styleClass="redBox"
|
styleClass="redBox" accesskey="f"
|
||||||
accesskey="f"
|
|
||||||
value="#{adminMainBean.searchText}"
|
value="#{adminMainBean.searchText}"
|
||||||
/>
|
>
|
||||||
|
<f:ajax event="change" execute="@this"
|
||||||
|
render="@form"
|
||||||
|
listener="#{adminMainBean.ajaxUpdateList}"
|
||||||
|
/>
|
||||||
|
C </p:inputText>
|
||||||
<p:button id="find" value="Find"
|
<p:button id="find" value="Find"
|
||||||
defaultCommand="true"
|
defaultCommand="true"
|
||||||
action="#{adminMainBean.doFind}"
|
action="#{adminMainBean.doFind}"
|
||||||
|
@ -32,38 +32,47 @@
|
||||||
</p:panelGrid>
|
</p:panelGrid>
|
||||||
</p:panelGrid>
|
</p:panelGrid>
|
||||||
</h:form>
|
</h:form>
|
||||||
<p:dataTable id="table1" rows="30"
|
<h:form id="form2">
|
||||||
value="#{adminMainBean.searchResults}" var="row"
|
<p:dataTable id="table1" rows="30"
|
||||||
>
|
value="#{adminMainBean.searchResults}" var="row"
|
||||||
<p:column headerText="Icon">
|
paginator="true"
|
||||||
<img height="40"
|
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||||
src="/img/thumb/#{row.id}"
|
currentPageReportTemplate="{startRecord}-{endRecord} of {totalRecords} records"
|
||||||
/>
|
rowsPerPageTemplate="5,10,15,20,30"
|
||||||
</p:column>
|
>
|
||||||
<p:column headerText="Recipe" style="width: 600px">
|
<p:column headerText="Icon">
|
||||||
<h:commandLink action="#{adminMainBean.showRecipe}"
|
<img height="40" src="/img/thumb/#{row.id}" />
|
||||||
value="#{row.title}"
|
</p:column>
|
||||||
/>
|
<p:column headerText="Recipe"
|
||||||
</p:column>
|
style="width: 600px"
|
||||||
<p:column headerText="Category">
|
>
|
||||||
<h:outputText
|
<h:commandLink
|
||||||
value="#{adminMainBean.formatCategories(row)}"
|
action="#{adminMainBean.showRecipe}"
|
||||||
/>
|
value="#{row.title}"
|
||||||
</p:column>
|
/>
|
||||||
<p:column headerText="Cuisine">
|
</p:column>
|
||||||
<h:outputText value="#{row.cuisine}" />
|
<p:column headerText="Category">
|
||||||
</p:column>
|
<h:outputText
|
||||||
<p:column headerText="Rating">
|
value="#{adminMainBean.formatCategories(row)}"
|
||||||
<p:rating value="#{row.rating}" readonly="true" />
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Source">
|
<p:column headerText="Cuisine">
|
||||||
<h:outputText value="#{row.source}" />
|
<h:outputText value="#{row.cuisine}" />
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Prep Time">
|
<p:column headerText="Rating">
|
||||||
<h:outputText
|
<p:rating value="#{row.rating}"
|
||||||
value="#{adminMainBean.formatPreptime(row.preptime)}"
|
readonly="true"
|
||||||
/>
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
|
<p:column headerText="Source">
|
||||||
|
<h:outputText value="#{row.source}" />
|
||||||
|
</p:column>
|
||||||
|
<p:column headerText="Prep Time">
|
||||||
|
<h:outputText
|
||||||
|
value="#{adminMainBean.formatPreptime(row.preptime)}"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
</p:dataTable>
|
</p:dataTable>
|
||||||
|
</h:form>
|
||||||
</ui:define>
|
</ui:define>
|
||||||
</ui:composition>
|
</ui:composition>
|
||||||
|
|
161
src/main/resources/META-INF/resources/recipeDetails.xhtml
Normal file
161
src/main/resources/META-INF/resources/recipeDetails.xhtml
Normal file
|
@ -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>
|
87
src/main/resources/META-INF/resources/recipePrint.xhtml
Normal file
87
src/main/resources/META-INF/resources/recipePrint.xhtml
Normal file
|
@ -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
Block a user