Trauma averted - beans migrated

This commit is contained in:
Tim Holloway 2021-12-28 15:59:40 -05:00
parent 851658a356
commit 4bcf8b911f
9 changed files with 1289 additions and 0 deletions

View File

@ -0,0 +1,208 @@
package com.mousetech.gourmetj;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import com.mousetech.gourmetj.UserSession;
import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.persistence.service.RecipeService;
/**
* Main control panel backing bean.
*
* @author timh
* @since Jun 28, 2012
*/
@Named
@ViewScoped
public class AdminMainBean implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2L;
public AdminMainBean() {
}
/* Logger */
private static final Logger log = LoggerFactory.getLogger(AdminMainBean.class);
/**
* Persistency service for Recipes
*/
@Inject
transient RecipeService recipeService;
public void setRecipeService(RecipeService service) {
log.debug("INJECT RECIPESERVICE===" + service);
this.recipeService = service;
}
// **
@Inject
private UserSession userSession;
/**
* @return the userSession
*/
public UserSession getUserSession() {
return userSession;
}
/**
* @param userSession the userSession to set
*/
public void setUserSession(UserSession userSession) {
this.userSession = userSession;
}
// **
private String searchText;
/**
* @return the searchText
*/
public String getSearchText() {
if (this.searchResults == null) {
// Fake around broken @PostConstruct
this.setSearchText(userSession.getLastSearch());
}
return searchText;
}
/**
* @param searchText the searchText to set
*/
public void setSearchText(String searchText) {
this.searchText = searchText;
}
/**/
transient DataModel<Recipe> searchResults;
/**
* @return the searchResults
*/
public DataModel<Recipe> getSearchResults() {
if (searchResults == null) {
searchResults = new ListDataModel<Recipe>();
init(); // @PostConstruct is broken
}
return searchResults;
}
/**
* @param searchResults the searchResults to set
*/
public void setSearchResults(
DataModel<Recipe> searchResults) {
this.searchResults = searchResults;
}
/**
* Return to last search, if any
*/
@PostConstruct
void init() {
this.setSearchText(userSession.getLastSearch());
// Clean up from any previous operations.
this.userSession.setRecipe(null);
doFind();
}
/**
* Finder
*
* @return Navigation string
*/
public String doFind() {
List<Recipe> recipes =
recipeService.findByTitle(searchText);
getSearchResults().setWrappedData(recipes);
this.userSession.setLastSearch(this.getSearchText());
return null; // Stay on page
}
/**
* Prep to create a new recipe
*
* @return navigation to detailEdit page
*/
public String doNewRecipe() {
// Clear for new recipe
this.userSession.setLastEdit(null);
// Construct a blank recipe to be created.
this.userSession.setRecipe(new Recipe());
return "detailEdit?faces-redirect=true";
}
/**
* Show selected recipe
*
* @return
*/
public String showRecipe() {
long recipeId = getSearchResults().getRowData().getId();
JSFUtils.flashScope().put("recipeID",
Long.valueOf(recipeId));
userSession.setLastEdit(recipeId);
userSession.setRecipe(null); // forces loading of line
// items.
return "recipeDetails?faces-redirect=true";
}
/**
* Get printable preptime. Database version is in seconds.
*
* @return Formatted time. Called from EL on main page.
*/
public String formatPreptime(int timesec) {
StringBuffer sb = new StringBuffer(20);
int preptime = timesec / 60;
if (preptime > 60) {
int hours = preptime / 60;
sb.append(hours);
sb.append(" h. ");
preptime %= 60;
}
if (preptime > 0) {
sb.append(preptime);
sb.append(" min.");
}
return sb.toString();
}
public String formatCategories(Recipe r) {
StringBuffer sb = new StringBuffer(30);
boolean first = true;
for (Category cat : r.getCategories()) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(cat.getCategory());
}
return sb.toString();
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright (C) 2021, Tim Holloway
*
* Date written: Dec 7, 2021
* Author: Tim Holloway <timh@mousetech.com>
*/
package com.mousetech.gourmetj;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import com.mousetech.gourmetj.persistence.service.RecipeService;
/**
* @author timh
* @since Dec 7, 2021
*/
@Named
@ApplicationScoped
public class CuisineBean {
@Inject
private RecipeService recipeService;
private List<String> cuisineList;
/**
* Return list of cuisines currently on file. Create it if
* needed.
*/
public List<String> getCuisineList() {
if (this.cuisineList == null ) {
this.cuisineList = loadCuisineList();
}
return this.cuisineList;
}
private synchronized List<String> loadCuisineList() {
List<String> list = this.recipeService.findCuisines();
return list;
}
public synchronized void registerCuisine(String cuisineString) {
// search in-memory list. Add if needed.
}
}

View File

@ -0,0 +1,332 @@
package com.mousetech.gourmetj;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.mousetech.gourmetj.persistence.model.Ingredient;
/**
* Service for taking an ingredients line, parsing it and
* returning a populated Ingredients object. Understands fraction
* characters in the amounts fields.
*
* @author timh
* @since Dec 1, 2021 TestedBy @see IngredientDigesterTest
*/
public class IngredientDigester {
/**
* @author timh
* @since Dec 5, 2021
*/
public enum IngredientAmountFormat {
IA_DECIMAL, // 5.75
IA_TEXT, // 5 3/4
IA_SYMBOLS, // 5¾
}
final static String amountPatternTxt =
"([0-9¼⅓½⅔¾⅛⅜⅝⅞][\\s\\-\\.]*[/0-9¼⅓½⅔¾⅛⅜⅝⅞]*)";
final static Pattern amountPattern =
Pattern.compile(amountPatternTxt);
final static Pattern fractionPattern =
Pattern.compile("(\\d*)[\\s\\-]*(\\d+)/(\\d+)");
/**
* Digest an ingredient text line and construct an Ingredient
* object
*
* @param inputText Input text line
* @return Ingredient
*/
public static Ingredient digest(String inputText) {
inputText = inputText.trim();
final boolean optional =
inputText.toLowerCase().contains("optional");
Ingredient ing = new Ingredient();
ing.setAmount(0.0);
// Split for amount, unit and rest of string.
String[] pamt = parseFancyNumber(inputText);
if (pamt == null) {
// Can't parse
ing.setAmount(null);
ing.setUnit(null);
ing.setItem(inputText);
} else {
final Double[] amount = digestAmount(pamt[0]);
ing.setAmount(amount[0]);
ing.setRangeamount(amount[1]);
String[] unext = pamt[1].split("\\s", 2);
switch (unext.length) {
case 1:
ing.setUnit(null);
ing.setItem(unext[0]);
break;
case 2:
ing.setUnit(unext[0]);
ing.setItem(unext[1]);
break;
}
}
ing.setPosition(null);
ing.setOptional(optional ? 1 : 0);
// Extract the item type from the item name
// Assume possible prep details ("parsley, chopped")
String istring = ing.getItem();
String iclass = istring.split("\\s*,",2)[0];
ing.setIngkey(iclass);
return ing;
}
/**
* Break down ingredients line into 2 parts. First part is
* numeric text representing amount and optional range,
* second part is text remainder, including units.
*
* @param instring String to parse
* @return 2-dimensional String array with amount and
* remainder or <code>null</code> if instring does
* not parse.
*
*/
static String[] parseFancyNumber(String instring) {
Matcher m = amountPattern.matcher(instring);
if (m.find()) {
// System.out.println("GROUPS=" + m.groupCount());
String rs = m.group(1);
return new String[] { rs.trim(),
instring.substring(m.end()).trim() };
}
return null;
}
// TODO: Remove "optional", if present, including
// brackets/parentheses
/**
* Convert instring into a number with optional fractions.
* Can take decimals, unicode ratio characters, and n/m form
* with embedded spaces and dashes.
*
* @param instring String to analyze
* @return number equivalent pair. Note that thirds are not
* precise in binary floating-point! First element is
* low range amount. Second element is null unless a
* high range was given as well.
*/
public static Double[] digestAmount(String instring) {
String[] amtParts = instring.split("\\s*-\\s*");
Double[] result = new Double[2];
result[0] = parseSingleAmount(amtParts[0]);
if (amtParts.length == 1) {
return result;
}
// dash separates low, high values OR integer and a
// fraction.
result[1] = parseSingleAmount(amtParts[1]);
if (result[1] < result[0]) {
result[0] += result[1];
result[1] = 0.0d;
}
return result;
}
/**
* Parse a single amount. May be integer, decimal number,
* integer + fraction character or [integer] integer /
* integer.
*
* @param instring amount string to parse
* @return parsed value.
* @throws NumberFormatException if bad decimal value given.
*/
static Double parseSingleAmount(String instring) {
double value = 0;
double frac = 0;
if (instring.contains("/")) {
// Break down a/b fraction
frac = digestFraction(instring);
return frac;
}
if (instring.contains(".")) {
frac = Double.valueOf(instring);
return frac;
}
// Look for integer and possibly a symbol;
for (int i = 0; i < instring.length(); i++) {
final Character ch = instring.charAt(i);
switch (ch) {
case '⅛':
frac = 0.125d;
break;
case '⅓':
frac = 1.0 / 3.0;
break;
case '¼':
frac = 0.25d;
break;
case '⅜':
frac = 3.0 * 0.125d;
break;
case '½':
frac = 0.5d;
break;
case '⅝':
frac = 5.0 * 0.125d;
break;
case '⅔':
frac = 2.0 / 3.0;
break;
case '⅞':
frac = 7.0 * 0.125d;
break;
default:
if (Character.isDigit(ch)) {
value = value * 10.0 + (ch - '0');
}
// Ignore spaces, dashes, etc.
break;
}
}
value += frac;
return value;
}
/**
* Pull fraction from tail-end of string and evaluate it.
*
* @param instring String in the form dddxxxddd/ddd, where d
* is digit, x is dash or space (optional)
* @return results of evaluation. May need to throw
* Exception???
*/
static double digestFraction(String instring) {
Matcher m = fractionPattern.matcher(instring);
if (!m.find()) {
return 0.0;
}
String lead = m.group(1);
String snumerator = m.group(2);
String sdenominator = m.group(3);
double numerator;
double denominator;
try {
numerator = Double.parseDouble(snumerator);
denominator = Double.parseDouble(sdenominator);
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return 0.0;
}
if (denominator == 0.0d) {
return 0.0d;
}
double result = numerator / denominator;
if (!lead.isEmpty()) {
result += Double.valueOf(lead); // integral part
}
return result;
}
/**
* Convert floating-point amounts to display-friendly form.
*
* @param format Amount formatting scheme
* @param amt Amount to display
* @param ext amount range high value (can be null)
* @return Displayable String. Zero returns an empty string.
*
*/
public static String displayAmount(
IngredientAmountFormat format, Double amt,
Double ext) {
if (amt == 0.0d) {
return "";
}
String amountRange = displayAmountPart(format, amt);
if (ext != null) {
amountRange += "-" + displayAmountPart(format, ext);
}
return amountRange;
}
/**
* @param format
* @param amt Amount to format
* @return Amount formatted in decimal, text, or text/symbol.
* Values that don't conform to the common fractions
* report as decimal values. Returns empty string for
* 0.
*/
static String displayAmountPart(
IngredientAmountFormat format, Double amt) {
if (amt == 0.0d) {
return "";
}
DecimalFormat df = new DecimalFormat("#.###");
String fnum = df.format(amt);
if (format == IngredientAmountFormat.IA_DECIMAL) {
return fnum;
}
String part[] = fnum.split("\\.");
if (part.length == 1) {
return part[0]; // Integer
}
String p2 = part[1];
String fs = "";
if (format == IngredientAmountFormat.IA_TEXT) {
if (p2.equals("125")) {
fs = "1/8";
} else if (p2.equals("333")) {
fs = "1/3";
} else if (p2.equals("25")) {
fs = "1/4";
} else if (p2.equals("5")) {
fs = "1/2";
} else if (p2.equals("667")) {
fs = "2/3";
} else if (p2.equals("75")) {
fs = "3/4";
} else { // 3, 5, 7/8
return fnum;
}
if (!"0".equals(part[0])) {
fs = ' ' + fs;
}
} else {
if (p2.equals("125")) {
fs = "";
} else if (p2.equals("333")) {
fs = "";
} else if (p2.equals("25")) {
fs = "¼";
} else if (p2.equals("5")) {
fs = "½";
} else if (p2.equals("667")) {
fs = "";
} else if (p2.equals("75")) {
fs = "¾";
} else { // 3, 5, 7/8
return fnum;
}
}
if ("0".equals(part[0])) {
part[0] = "";
}
return part[0] + fs;
}
}

View File

@ -0,0 +1,375 @@
package com.mousetech.gourmetj;
import com.mousetech.gourmetj.persistence.model.Ingredient;
import com.mousetech.gourmetj.persistence.model.IngredientIF;
import com.mousetech.gourmetj.persistence.model.Recipe;
import com.mousetech.gourmetj.IngredientDigester;
import com.mousetech.gourmetj.IngredientDigester.IngredientAmountFormat;
/**
* JSF-friendly decorator for @see Ingredient. Formats amount
* with fractions and supports checkboxes. Primary use
* is as a JSF TableModel wrapped content in @see RecipeDetailBean.
*
* TestedBy @see IngredientUITest
*
* @author timh
* @since Dec 2, 2021
*/
public class IngredientUI implements IngredientIF {
private Ingredient ingredient;
/**
* Constructor.
* @param ingredient
*/
public IngredientUI(Ingredient ingredient) {
this.ingredient = ingredient;
}
public Ingredient getIngredient() {
return this.ingredient;
}
private boolean ingGroup;
/**
* @param ingGroup the ingGroup to set
*/
public void setIngGroup(boolean ingGroup) {
this.ingGroup = ingGroup;
}
/**
* Ingredient groups are rendered visually as synthetic
* rows. Actual group IDs are part of line-item ingredients,
* so when building the model, we create a group row when
* the line item inggroup value changes.
*
* @return <code>true</code> for an Ingredient Group
* header row.
*/
public boolean isIngGroup() {
return this.ingGroup;
}
/**
* Row selection checkbox (UI only)
*/
private boolean selected;
/**
* @return the selected status
*/
public boolean isSelected() {
return selected;
}
/**
* @param selected the selected to set
*/
public void setSelected(boolean selected) {
this.selected = selected;
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getAmount()
*/
public Double getAmount() {
return ingredient.getAmount();
}
/**
* @param amount
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setAmount(java.lang.Double)
*/
public void setAmount(Double amount) {
ingredient.setAmount(amount);
}
/**
* Get amount display-friendly
* @see #getAmount
*/
public String getDisplayAmount() {
// TODO
Double amt = ingredient.getAmount();
if ( amt == null ) {
return "";
}
String amt1 = IngredientDigester.displayAmount(
IngredientAmountFormat.IA_SYMBOLS, amt, ingredient.getRangeamount());
return amt1;
}
/**
* Set amount display-friendly
* @see #setAmount
*/
public void setDisplayAmount(String amount) {
if ( amount.isBlank() ) {
ingredient.setAmount(null);
}
IngredientDigester digester = new IngredientDigester();
Double[] amt = digester.digestAmount(amount);
ingredient.setAmount(amt[0]);
ingredient.setRangeamount(amt[1]);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getDeleted()
*/
public Integer getDeleted() {
return ingredient.getDeleted();
}
/**
* @param deleted
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setDeleted(java.lang.Integer)
*/
public void setDeleted(Integer deleted) {
ingredient.setDeleted(deleted);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getId()
*/
public Long getId() {
return ingredient.getId();
}
/**
* @param id
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setId(long)
*/
public void setId(Long id) {
ingredient.setId(id);
}
/**
* @return
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return ingredient.hashCode();
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getInggroup()
*/
public String getInggroup() {
return ingredient.getInggroup();
}
/**
* @param inggroup
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setInggroup(java.lang.String)
*/
public void setInggroup(String inggroup) {
ingredient.setInggroup(inggroup);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getIngkey()
*/
public String getIngkey() {
return ingredient.getIngkey();
}
/**
* @param ingkey
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setIngkey(java.lang.String)
*/
public void setIngkey(String ingkey) {
ingredient.setIngkey(ingkey);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getItem()
*/
public String getItem() {
return ingredient.getItem();
}
/**
* @param item
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setItem(java.lang.String)
*/
public void setItem(String item) {
ingredient.setItem(item);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getOptional()
*/
public Integer getOptional() {
return ingredient.getOptional();
}
/**
* @param optional
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setOptional(java.lang.Integer)
*/
public void setOptional(Integer optional) {
ingredient.setOptional(optional);
}
/**
* Get optional value in boolean Checkbox friendly form
* @return
*/
public boolean getOptionalCB() {
return ingredient.getOptional() != 0;
}
public void setOptionalCB(boolean value) {
ingredient.setOptional(value ? 1 : 0);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getPosition()
*/
public Integer getPosition() {
return ingredient.getPosition();
}
/**
* @param position
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setPosition(java.lang.Integer)
*/
public void setPosition(Integer position) {
ingredient.setPosition(position);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getRangeamount()
*/
public Double getRangeamount() {
return ingredient.getRangeamount();
}
/**
* @param rangeamount
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setRangeamount(java.lang.Double)
*/
public void setRangeamount(Double rangeamount) {
ingredient.setRangeamount(rangeamount);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getRecipe()
*/
public Recipe getRecipe() {
return ingredient.getRecipe();
}
/**
* @param recipe
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setRecipe(com.mousetech.gourmetj.persistence.model.Recipe)
*/
public void setRecipe(Recipe recipe) {
ingredient.setRecipe(recipe);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getRefid()
*/
public Long getRefid() {
return ingredient.getRefid();
}
/**
* @param refid
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setRefid(java.lang.Long)
*/
public void setRefid(Long refid) {
ingredient.setRefid(refid);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getShopoptional()
*/
public Integer getShopoptional() {
return ingredient.getShopoptional();
}
/**
* @param shopoptional
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setShopoptional(java.lang.Integer)
*/
public void setShopoptional(Integer shopoptional) {
ingredient.setShopoptional(shopoptional);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getShopoptional()
*/
public boolean getShopoptionalCB() {
return ingredient.getShopoptional() != 0;
}
/**
* @param shopoptional
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setShopoptional(java.lang.Integer)
*/
public void setShopoptionalCB(boolean shopoptional) {
ingredient.setShopoptional(shopoptional? 1:0);
}
/**
* @return
* @see com.mousetech.gourmetj.persistence.model.Ingredient#getUnit()
*/
public String getUnit() {
return ingredient.getUnit();
}
/**
* @param unit
* @see com.mousetech.gourmetj.persistence.model.Ingredient#setUnit(java.lang.String)
*/
public void setUnit(String unit) {
ingredient.setUnit(unit);
}
// This goes to the shopCats table via ManyToOne at save time.
private String shopCat;
/**
* @return the shopCat
*/
public String getShopCat() {
return shopCat;
}
/**
* @param shopCat the shopCat to set
*/
public void setShopCat(String shopCat) {
this.shopCat = shopCat;
}
/**
* @return
* @see java.lang.Object#toString()
*/
public String toString() {
return ingredient.toString();
}
}

View File

@ -0,0 +1,86 @@
package com.mousetech.gourmetj;
import java.io.InputStream;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JSFUtils {
/* Logger */
private static final Logger log =
LoggerFactory.getLogger(JSFUtils.class);
private static ExternalContext getExternalContext() {
FacesContext facesContext =
FacesContext.getCurrentInstance();
ExternalContext externalContext =
facesContext.getExternalContext();
return externalContext;
}
/**
* Obtain resource as Stream. The caller must close this
* stream after use.
*
* @param pathName Path of the resource. For example,
* "/WEB-INF/classes/xys.properties".
* @return InputStream or <code>null</code>, if no such
* resource exists.
*/
public static InputStream getResourceStream(
String pathName) {
InputStream response = getExternalContext()
.getResourceAsStream(pathName);
return response;
}
// ===
/**
* Post an info-level message to the FacesContext where the
* &lt;h:messages&gt; tag can display it.
*
* @param string Message text
*/
public static void addInfoMessage(String string) {
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_INFO, string, string);
FacesContext.getCurrentInstance().addMessage(null,
message);
}
/**
* Post an error-level message to the FacesContext where the
* &lt;h:messages&gt; tag can display it.
*
* @param string Message text
*/
public static void addErrorMessage(String string) {
FacesMessage message = new FacesMessage(
FacesMessage.SEVERITY_ERROR, string, string);
FacesContext.getCurrentInstance().addMessage(null,
message);
}
public static void addErrorMessage(String string,
Exception e) {
if (e instanceof NullPointerException) {
addErrorMessage(
"Internal logic error (NullPointerException)");
} else {
addErrorMessage(string);
}
log.error(string, e);
}
public static Flash flashScope() {
return (FacesContext.getCurrentInstance()
.getExternalContext().getFlash());
}
}

View File

@ -0,0 +1,97 @@
package com.mousetech.gourmetj;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import com.mousetech.gourmetj.persistence.model.Recipe;
@Named
@SessionScoped
public class UserSession implements Serializable {
/**
* Serial version for session save/restore
*/
private static final long serialVersionUID =
7449440266704831598L;
private String lastSearch = "";
/**
* @return the lastSearch
*/
public String getLastSearch() {
return lastSearch;
}
/**
* @param lastSearch the lastSearch to set
*/
public void setLastSearch(String lastSearch) {
this.lastSearch = lastSearch;
}
private Long lastEdit;
/**
* @return the lastEdit
*/
public Long getLastEdit() {
return lastEdit;
}
/**
* @param lastEdit the lastEdit to set
*/
public void setLastEdit(Long lastEdit) {
this.lastEdit = lastEdit;
}
//***
/**
* Tab index to select when presenting editDetails.
* First tab is 0.
*/
private int detailTab;
/**
* @return the detailTab
*/
public int getDetailTab() {
return detailTab;
}
/**
* @param detailTab the detailTab to set
*/
public void setDetailTab(int detailTab) {
this.detailTab = detailTab;
}
//***
private Recipe recipe;
/**
* Recipe is set by the mainpage bean to a blank recipe
* before dispatching to the detailEdit page (new recipe).
* It is also set by the detail view page so that the
* detail view can be edited.
*
* In addition to detail editing, it's also used by the
* @see PictureController.
*
* @return Recipe selected.
*/
public Recipe getRecipe() {
return recipe;
}
/**
* @param recipe the recipe to set
*/
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
}

View File

@ -0,0 +1,47 @@
package com.mousetech.gourmetj.persistence.service;
import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.springframework.transaction.annotation.Transactional;
import com.mousetech.gourmetj.persistence.dao.CategoryRepository;
import com.mousetech.gourmetj.persistence.dao.RecipeRepository;
import com.mousetech.gourmetj.persistence.model.Category;
import com.mousetech.gourmetj.persistence.model.Recipe;
@Named
@ApplicationScoped
@Transactional
public class RecipeService implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private RecipeRepository recipeRepository;
public List<Recipe> findAll() {
return recipeRepository.findAll();
}
public List<String> findCuisines() {
// TODO Auto-generated method stub
return null;
}
public List<Recipe> findByTitle(String searchText) {
// TODO Auto-generated method stub
return null;
}
// /**
// *
// * @return All Category names that are not null/blank, sorted.
// */
// public List<String> findCategoryNames() {
// return categoryRepository.findDistinctCategoryNative();
// }
}

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition template="/WEB-INF/layout/layout.xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:tc="http://myfaces.apache.org/tobago/component"
>
<ui:define name="title">Gourmet Recipe Manager</ui:define>
<ui:define name="content">
<tc:messages />
<tc:form id="form1">
<tc:section label="Find a Recipe" level="2">
<tc:gridLayout id="gfinder"
columns="60em auto auto"
>
<tc:in id="searchFor" focus="true"
placeholder="Recipe title (todo) cuisine, etc."
value="#{adminMainBean.searchText}"
/>
<tc:button id="find" label="Find"
defaultCommand="true"
action="#{adminMainBean.doFind}"
>
<tc:style minWidth="58px" height="40px" />
</tc:button>
<tc:button label="New Recipe"
action="#{adminMainBean.doNewRecipe}"
>
<tc:style minWidth="58px" height="40px" />
</tc:button>
</tc:gridLayout>
</tc:section>
</tc:form>
<tc:sheet id="table1" rows="30"
value="#{adminMainBean.searchResults}" var="row"
>
<tc:column label="Icon">
<tc:image width="64" height="64"
value="/gourmetj/img/thumb/#{row.id}"
/>
</tc:column>
<tc:column label="Recipe">
<tc:link action="#{adminMainBean.showRecipe}"
label="#{row.title}"
/>
</tc:column>
<tc:column label="Category">
<tc:out
value="#{adminMainBean.formatCategories(row)}"
/>
</tc:column>
<tc:column label="Cuisine">
<tc:out value="#{row.cuisine}" />
</tc:column>
<tc:column label="Rating">
<tc:stars value="#{row.rating}" readonly="true" />
</tc:column>
<tc:column label="Source">
<tc:out value="#{row.source}" />
</tc:column>
<tc:column label="Prep Time">
<tc:out
value="#{adminMainBean.formatPreptime(row.preptime)}"
/>
</tc:column>
</tc:sheet>
</ui:define>
</ui:composition>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui:composition xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:tc="http://myfaces.apache.org/tobago/component"
>
<f:view>
<tc:page id="page">
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
<link rel="icon" type="image/vnd.microsoft.icon"
href="#{pageContext.contextPath}/favicon.ico"
/>
<tc:style file="css/style.css" />
<h1>
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
</h1>
<ui:insert name="content">
<ui:include src="content.xhtml" />
</ui:insert>
<tc:footer fixed="true">
© 2021 Tim Holloway, Licensed under the <a
href="http://www.apache.org/licenses/LICENSE-2.0"
>Apache License, Version 2.0</a>.
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
</tc:footer>
</tc:page>
</f:view>
</ui:composition>