From 875f02046d25eb56dd9c92f3ed4aedf211fff094 Mon Sep 17 00:00:00 2001 From: Robert Goldmann <deadlocker@gmx.de> Date: Sun, 4 Mar 2018 13:09:24 +0100 Subject: [PATCH] #269 - categories can now be added; added validation; --- .../budgetmaster/advices/ColorAdvice.java | 17 +++ .../controller/CategoryController.java | 57 +++++++--- .../budgetmaster/entities/Category.java | 4 + .../budgetmaster/utils/Helpers.java | 34 +++--- .../budgetmaster/utils/Notification.java | 49 +++++++++ .../budgetmaster/utils/NotificationLevel.java | 28 +++++ .../budgetmaster/utils/Strings.java | 1 + .../validators/CategoryValidator.java | 22 ++++ src/main/resources/languages/_de.properties | 1 + src/main/resources/languages/_en.properties | 1 + src/main/resources/templates/newCategory.ftl | 103 ++++++++++-------- 11 files changed, 242 insertions(+), 75 deletions(-) create mode 100644 src/main/java/de/deadlocker8/budgetmaster/advices/ColorAdvice.java create mode 100644 src/main/java/de/deadlocker8/budgetmaster/utils/Notification.java create mode 100644 src/main/java/de/deadlocker8/budgetmaster/utils/NotificationLevel.java create mode 100644 src/main/java/de/deadlocker8/budgetmaster/validators/CategoryValidator.java diff --git a/src/main/java/de/deadlocker8/budgetmaster/advices/ColorAdvice.java b/src/main/java/de/deadlocker8/budgetmaster/advices/ColorAdvice.java new file mode 100644 index 000000000..691982e92 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/advices/ColorAdvice.java @@ -0,0 +1,17 @@ +package de.deadlocker8.budgetmaster.advices; + +import de.deadlocker8.budgetmaster.utils.Helpers; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ModelAttribute; + +import java.util.ArrayList; + +@ControllerAdvice +public class ColorAdvice +{ + @ModelAttribute("categoryColors") + public ArrayList<String> getCategoryColors() + { + return Helpers.getCategoryColorList(); + } +} \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java index fa4ac89a1..f041d21ac 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/CategoryController.java @@ -1,18 +1,26 @@ package de.deadlocker8.budgetmaster.controller; +import de.deadlocker8.budgetmaster.Validators.CategoryValidator; import de.deadlocker8.budgetmaster.entities.Category; import de.deadlocker8.budgetmaster.entities.CategoryType; import de.deadlocker8.budgetmaster.repositories.CategoryRepository; import de.deadlocker8.budgetmaster.utils.Colors; import de.deadlocker8.budgetmaster.utils.Helpers; +import de.deadlocker8.budgetmaster.utils.Notification; +import de.deadlocker8.budgetmaster.utils.NotificationLevel; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import tools.ConvertTo; +import tools.Localization; import java.util.ArrayList; @@ -23,6 +31,7 @@ public class CategoryController extends BaseController @Autowired private CategoryRepository categoryRepository; + @RequestMapping("/categories") public String index(@ModelAttribute("model") ModelMap model) { @@ -39,7 +48,7 @@ public class CategoryController extends BaseController } model.addAttribute("confirm", true); - model.addAttribute("categories", categoryRepository.findAll()); + model.addAttribute("categories", categoryRepository.findAllByOrderByNameAsc()); model.addAttribute("currentCategory", categoryRepository.getOne(ID)); return "categories"; } @@ -65,27 +74,45 @@ public class CategoryController extends BaseController public String newCategory(@ModelAttribute("model") ModelMap model) { //TODO: add color picker for custom colors - //TODO: validate wrong inputs --> empty name --> message on page --> validate before send? - ArrayList<String> categoryColors = Helpers.getCategoryColorList(); //add custom color (defaults to white here because we are adding a new category instead of editing an existing) - categoryColors.add("#FFFFFF"); - model.addAttribute("categoryColors", categoryColors); - - model.addAttribute("activeColor", ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREY)); + model.addAttribute("customColor", "#FFFFFF"); + Category emptyCategory = new Category(null, ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREY), CategoryType.CUSTOM); + model.addAttribute("category", emptyCategory); return "newCategory"; } - @RequestMapping(value = "/categories/post/", method = RequestMethod.POST) - public String post(@ModelAttribute("NewCategory") Category category) + @RequestMapping(value = "/categories/newCategory", method = RequestMethod.POST) + public String post(@ModelAttribute("model") ModelMap model, @ModelAttribute("NewCategory") Category category, BindingResult bindingResult) { - if(category == null - || category.getName() == null - || category.getName().isEmpty() - || category.getColor() == null - || category.getColor().isEmpty()) + System.out.println(category); + CategoryValidator userValidator = new CategoryValidator(); + userValidator.validate(category, bindingResult); + + if(bindingResult.hasErrors()) { - System.out.println("Wrong input"); + ArrayList<Notification> notifications = new ArrayList<>(); + for(ObjectError error : bindingResult.getAllErrors()) + { + notifications.add(new Notification(NotificationLevel.WARNING, null, Localization.getString(error.getCode()))); + } + + model.addAttribute("notifications", notifications); + if(Helpers.getCategoryColorList().contains(category.getColor())) + { + model.addAttribute("customColor", "#FFFFFF"); + } + else + { + model.addAttribute("customColor", category.getColor()); + } + + if(category.getColor() == null) + { + category.setColor(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREY)); + } + model.addAttribute("category", category); + return "newCategory"; } else { diff --git a/src/main/java/de/deadlocker8/budgetmaster/entities/Category.java b/src/main/java/de/deadlocker8/budgetmaster/entities/Category.java index ee68468af..2b8e98b94 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/entities/Category.java +++ b/src/main/java/de/deadlocker8/budgetmaster/entities/Category.java @@ -7,6 +7,8 @@ import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; @Entity public class Category @@ -14,6 +16,8 @@ public class Category @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer ID; + @NotNull + @Size(min = 1) private String name; private String color; private CategoryType type; diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Helpers.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Helpers.java index 006895d8c..e5bea0fe2 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/utils/Helpers.java +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Helpers.java @@ -91,23 +91,23 @@ public class Helpers public static ArrayList<String> getCategoryColorList() { ArrayList<String> categoryColors = new ArrayList<>(); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREY)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_GREY)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_GREY)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_YELLOW)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_YELLOW)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_ORANGE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_RED)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_RED)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_PINK)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_PURPLE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_PURPLE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_BLUE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_SOFT_BLUE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_BLUE)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREEN)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIME_GREEN)); - categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_GREEN)); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREY).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_GREY).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_GREY).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_YELLOW).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_YELLOW).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_ORANGE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_RED).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_RED).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_PINK).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_PURPLE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_PURPLE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_BLUE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_SOFT_BLUE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_BLUE).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIGHT_GREEN).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_LIME_GREEN).toLowerCase()); + categoryColors.add(ConvertTo.toRGBHexWithoutOpacity(Colors.CATEGORIES_DARK_GREEN).toLowerCase()); return categoryColors; } diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Notification.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Notification.java new file mode 100644 index 000000000..181295992 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Notification.java @@ -0,0 +1,49 @@ +package de.deadlocker8.budgetmaster.utils; + +import tools.Localization; + +public class Notification +{ + private NotificationLevel level; + private String messageTitle; + private String messageHeader; + private String messageBody; + + public Notification(NotificationLevel level, String messageHeader, String messageBody) + { + this.level = level; + this.messageTitle = Localization.getString(level.getKey()); + this.messageHeader = messageHeader; + this.messageBody = messageBody; + } + + public NotificationLevel getLevel() + { + return level; + } + + public String getMessageTitle() + { + return messageTitle; + } + + public String getMessageHeader() + { + return messageHeader; + } + + public String getMessageBody() + { + return messageBody; + } + + @Override + public String toString() + { + return "Notification{" + + "level=" + level + + ", messageHeader='" + messageHeader + '\'' + + ", messageBody='" + messageBody + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/NotificationLevel.java b/src/main/java/de/deadlocker8/budgetmaster/utils/NotificationLevel.java new file mode 100644 index 000000000..61bd8ee59 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/NotificationLevel.java @@ -0,0 +1,28 @@ +package de.deadlocker8.budgetmaster.utils; + +public enum NotificationLevel +{ + INFO("title.info"), + WARNING("title.warning"), + ERROR("title.error"); + + private String key; + + NotificationLevel(String key) + { + this.key = key; + } + + public String getKey() + { + return key; + } + + @Override + public String toString() + { + return "NotificationLevel{" + + "key='" + key + '\'' + + '}'; + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java b/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java index 2c62a8e7d..bdad0b0a0 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java +++ b/src/main/java/de/deadlocker8/budgetmaster/utils/Strings.java @@ -183,6 +183,7 @@ public class Strings public static final String WARNING_INTEGER_HEIGHT_IN_PIXELS = "warning.integer.height.in.pixels"; public static final String WARNING_EMPTY_SAVEPATH_CHART = "warning.empty.savepath.chart"; public static final String WARNING_EMPTY_CATEGORY_NAME = "warning.empty.category.name"; + public static final String WARNING_EMPTY_CATEGORY_COLOR = "warning.empty.category.color"; public static final String WARNING_EMPTY_PAYMENT_NAME = "warning.empty.payment.name"; public static final String WARNING_NAME_CHARACTER_LIMIT_REACHED_45 = "warning.name.character.limit.reached.45"; public static final String WARNING_NAME_CHARACTER_LIMIT_REACHED_150 = "warning.name.character.limit.reached.150"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/validators/CategoryValidator.java b/src/main/java/de/deadlocker8/budgetmaster/validators/CategoryValidator.java new file mode 100644 index 000000000..3a9c3db98 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/validators/CategoryValidator.java @@ -0,0 +1,22 @@ +package de.deadlocker8.budgetmaster.Validators; + +import de.deadlocker8.budgetmaster.entities.Category; +import de.deadlocker8.budgetmaster.utils.Strings; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + + +public class CategoryValidator implements Validator +{ + public boolean supports(Class clazz) + { + return Category.class.equals(clazz); + } + + public void validate(Object obj, Errors errors) + { + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", Strings.WARNING_EMPTY_CATEGORY_NAME); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "color", Strings.WARNING_EMPTY_CATEGORY_COLOR); + } +} diff --git a/src/main/resources/languages/_de.properties b/src/main/resources/languages/_de.properties index ec351e3a3..9a2526145 100644 --- a/src/main/resources/languages/_de.properties +++ b/src/main/resources/languages/_de.properties @@ -179,6 +179,7 @@ warning.empty.height.in.pixels=Bitte gib eine H warning.integer.height.in.pixels=Nur ganzahlige Werte sind f�r das Feld H�he erlaubt. warning.empty.savepath.chart=W�hle einen Speicherort f�r das Diagramm aus. warning.empty.category.name=Das Feld f�r den Namen darf nicht leer sein. +warning.empty.category.color=Die Kategoriefarbe darf nicht leer sein. warning.empty.payment.name=Das Feld f�r den Namen darf nicht leer sein. warning.name.character.limit.reached.45=Der Name darf maximal 45 Zeichen lang sein. warning.name.character.limit.reached.150=Der Name darf maximal 150 Zeichen lang sein. diff --git a/src/main/resources/languages/_en.properties b/src/main/resources/languages/_en.properties index 4157b60f9..8788efcc8 100644 --- a/src/main/resources/languages/_en.properties +++ b/src/main/resources/languages/_en.properties @@ -179,6 +179,7 @@ warning.empty.height.in.pixels=Please enter a height in pixels. warning.integer.height.in.pixels=Only integer values are allowed for the height field. warning.empty.savepath.chart=Please select a location where you want to save the chart. warning.empty.category.name=The field for the name can not be empty. +warning.empty.category.color=The category color should not be empty. warning.empty.payment.name=The field for the name can not be empty. warning.name.character.limit.reached.45=The name must not exceed 45 characters in length. warning.name.character.limit.reached.150=The name must not exceed 150 characters in length. diff --git a/src/main/resources/templates/newCategory.ftl b/src/main/resources/templates/newCategory.ftl index 0845c47e6..111cd1dbe 100644 --- a/src/main/resources/templates/newCategory.ftl +++ b/src/main/resources/templates/newCategory.ftl @@ -50,69 +50,86 @@ </div> </div> <div class="container"> - <form name="NewCategory" action="/categories/post/" method="post"> - <div class="row"> - <div class="input-field col s12 m12 l8 offset-l2"> - <input id="category-name" type="text" name="name"> - <label for="category-name">Name</label> + <form name="NewCategory" action="/categories/newCategory" method="post"> + <div class="row"> + <div class="input-field col s12 m12 l8 offset-l2"> + <input id="category-name" type="text" name="name" value="<#if !model["category"].getName??>${model["category"].getName()}</#if>"> + <label for="category-name">Name</label> + </div> </div> - </div> - <input type="hidden" name="color" id="categoryColor" value=""> - <#list model["categoryColors"] as color> - <#if color?counter == 18> - <#--add custom color picker--> - <div class="col s2 m1 no-padding"> - <div class="category-color" style="background-color: ${color}"> - + - </div> - </div> - <#else> + <input type="hidden" name="color" id="categoryColor" value="${model["category"].getColor()}"> + <#list categoryColors as color> <#if color?counter == 1 || color?counter == 7 || color?counter == 13> <div class="row"> <div class="col s2 m1 offset-m3 no-padding"> - <div class="category-color <#if color == model["activeColor"]>category-color-active</#if>" style="background-color: ${color}"></div> + <div class="category-color <#if color == model["category"].getColor()>category-color-active</#if>" style="background-color: ${color}"></div> </div> <#else> <div class="col s2 m1 no-padding"> - <div class="category-color <#if color == model["activeColor"]>category-color-active</#if>" style="background-color: ${color}"></div> + <div class="category-color <#if color == model["category"].getColor()>category-color-active</#if>" style="background-color: ${color}"></div> </div> </#if> - </#if> - <#if color?counter == 6 || color?counter == 12 || color?counter == 18> + <#if color?counter == 6 || color?counter == 12> + </div> + </#if> + </#list> + <#--add custom color picker--> + <div class="col s2 m1 no-padding"> + <div class="category-color <#if model["customColor"] == model["category"].getColor()>category-color-active</#if>" style="background-color: ${model["customColor"]}"> + + + </div> </div> - </#if> - </#list> - <br> - <div class="row hide-on-small-only"> - <div class="col m6 l4 offset-l2 right-align"> - <a href="/categories" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">clear</i>Abbrechen</a> </div> - <div class="col m6 l4 left-align"> - <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="action"> - <i class="material-icons left">save</i>Speichern - </button> - </div> - </div> - <div class="hide-on-med-and-up"> - <div class="row center-align"> - <div class="col s12"> + <br> + <div class="row hide-on-small-only"> + <div class="col m6 l4 offset-l2 right-align"> <a href="/categories" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">clear</i>Abbrechen</a> </div> - </div> - <div class="row center-align"> - <div class="col s12"> - <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="buttonSave"> + + <div class="col m6 l4 left-align"> + <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="action"> <i class="material-icons left">save</i>Speichern </button> </div> </div> - </div> - </form> + <div class="hide-on-med-and-up"> + <div class="row center-align"> + <div class="col s12"> + <a href="/categories" class="waves-effect waves-light btn budgetmaster-blue"><i class="material-icons left">clear</i>Abbrechen</a> + </div> + </div> + <div class="row center-align"> + <div class="col s12"> + <button class="btn waves-effect waves-light budgetmaster-blue" type="submit" name="buttonSave"> + <i class="material-icons left">save</i>Speichern + </button> + </div> + </div> + </div> + </form> + </div> </div> - </div> - </main> + </main> + + <!-- notificatiom modal --> + <#if model["notifications"]??> + <#if model["notifications"]?size == 1> + <div id="notificationModal" class="modal"> + <div class="modal-content"> + <h4>${model["notifications"][0].messageTitle}</h4> + <#if model["notifications"][0].messageHeader??><h5>${model["notifications"][0].messageHeader}</h5></#if> + <p>${model["notifications"][0].messageBody}</p> + </div> + <div class="modal-footer"> + <a class="modal-action modal-close waves-effect waves-green btn-flat">OK</a> + </div> + </div> + <#else> + + </#if> + </#if> <!-- Scripts--> <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> -- GitLab