Data binding for 20 <inputs> but only one common onchange-method - .net-core

I have a calculation sheet where the user can enter several positions (basic price, multiple options) that should be added up to a total. I am struggling in binding the events to add up all the positions to a final total. The problem is, that the #bind="" does not allow me to call one onchange events for every input, hence I would have to implement one event handler per position (circa 20 in total).
<input type="number" #bind="calc.BasicPrice" class="form-control" />
<input type="number" #bind="calc.Option" class="form-control" />
<input type="number" #bind="calc.Total" class="form-control" readonly/>
#code
{
private CalculationModel calc = new CalculationModel();
}
What I have tried so far is to bind a UpdateBottomLine() method to the input onchange events like that:
<input type="number" value="#calc.BasicPrice"
#onchange="(e) => CalculateBottomLines(calc.BasicPrice.GetType(), e)"
class="form-control" />
#code {
private void CalculateBottomLines(Type field, ChangeEventArgs input)
{
input.Value = input.Value.ToString().Replace(".", ",");
// Update the input variable
calc.BasicPrice = Convert.ToDecimal( input.Value.ToString() ); // <-- The name of the receiving variable has to be determined dynamically, depending on the field that was changed
// Update the total
calc.Total = calc.BasicPrice + calc.Option;
}
}
Now unfortunately, while this changes my total when i change the basic price, this does not yet work dynamically but is only implemented for the basic price. I would need a solution where I can pass a reference to the field that was changed to the CalculateBottomLines() method.

Here I am using the property setter to recalculate the total and #bind:event="oninput" to trigger on input.
<input type="number" #bind="calc.BasicPrice" #bind:event="oninput" class="form-control" />
<input type="number" #bind="calc.Option" #bind:event="oninput" class="form-control" />
<input type="number" #bind="calc.Total" class="form-control" readonly />
#code {
SomeModel calc = new SomeModel();
public class SomeModel
{
public double BasicPrice
{
get => basicPrice;
set { basicPrice = value; UpdateTotal(); }
}
public double Option
{
get => option;
set { option = value; UpdateTotal(); }
}
public double Total { get; set; }
internal void UpdateTotal()
{
Total = BasicPrice + Option;
}
private double basicPrice;
private double option;
}
}

Related

Saving record in a proper way

I have a problem with saving records to DB with Spring-Mvc and Thymeleaf.
When I click "Update" button on record, to enter the update form (included beneath), all values are in place correctly, but, when I want to subbmit, an error occur. There is no any stacktrace in console, only error in web page, that I am not able to solve.
This is my code:
Controller:
#GetMapping("/{maltId}")
public ModelAndView showMalt(#PathVariable("maltId") Long maltId) {
ModelAndView mav = new ModelAndView("malt/malt-show");
mav.addObject(maltService.findById(maltId));
return mav;
}
#GetMapping("/{maltId}/edit")
public String initUpdateMaltForm(#PathVariable("maltId") Long maltId, Model model) {
model.addAttribute("malt", maltService.findById(maltId));
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
}
#PostMapping("/{maltId}/edit")
public String processUpdateMaltForm(#Valid Malt malt, BindingResult result, #PathVariable("maltId") Long maltId) {
if (result.hasErrors()) {
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
} else {
malt.setId(maltId);
Malt savedMalt = maltService.save(malt);
return "redirect:/malt/" + savedMalt.getId();
}
}
Model:
#Column(name="malt_name")
private String maltName;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
#Column(name="malt_filling")
private int maltFilling;
#Column(name="malt_ebc")
private int maltEbc;
#Column(name="malt_usage")
private String maltUsage;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="country_id")
private Country country;
#ManyToMany(mappedBy="malts")
private Set<Batch> batches;
This is the view:
<body>
<form th:object="${malt}" th:action="#{/malt/}" method="post">
<input type="hidden" th:field="*{id}" />
<label>Malt name:</label>
<input type="text" class="form-control" th:field="*{maltName}" />
<label>Producer:</label>
<input type="text" class="form-control"
th:field="*{producer.producerName}" />
<label>Country:</label>
<select class="form-control" th:field="*{country.id}">
<option value="0">Select country</option>
<option th:each="country : ${countries}"
th:value="${country?.id}"
th:text="${country?.countryName}">
</option>
</select>
<label>Malt filling:</label>
<input type="text" class="form-control"
th:field="*{maltFilling}" />
<label>Malt usage:</label>
<input type="text" class="form-control"
th:field="*{maltUsage}" />
<label>Malt EBC:</label>
<input type="number" class="form-control"
th:field="*{maltEbc}" />
<button class="submit-button" type="submit">Submit</button>
</form>
</body>
When I hit Submit button, I get this error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed May 15 22:46:22 CEST 2019
There was an unexpected error (type=Not Found, status=404).
No message available
I have tried couple of different approaches, but nothing helps, and since there is no stacktrace in console, I have no idea what is wrong here.
Link to repo: https://github.com/fangirsan/maruszka-new
No stack trace 404 normally indicates that there is no mapping. Since you have, potentially, provided only a part of your Controller I assume that the causation for this is the code in your view right here:
<form th:object="${malt}" th:action="#{/malt/}" method="post">
The action takes to ("/malt/"), however, your controller has not got mapping for this?!
I expect that this should fix it:
<form th:object="${malt}" th:action="#{${'/' + malt.id + '/edit'}}" method="post">
Update
Had a look at your controller and you have the following annotations on your class
#Controller
#RequestMapping("/malt")
public class MaltController{..
#RequestMapping("/malt") will now make your path to save ../malt/{id}/edit'. The code below now should work:
<form th:object="${malt}" th:action="#{${'/malt/' + malt.id + '/edit'}}" method="post">
On using "#{${...}}"
#{} is a link variable, contents within this tag will be appended to the applications root context, e.g., at Stack Overflow #{'/posts'} would result with https://stackoverflow.com/posts
The ${} is a variable expression which will return a String or the object's .toString() value.
If we want to pass a variable within #{} link variable we must include the ${} variable within it, thus resulting in :
#{${'/hope/this/helps' + yourVariable}}

How to use input radio button with thymeleaf and Spring MVC

I would like to get a destination address from a input radio button list. The DestinationAddress class is the following:
public class DestinationAddress {
private Integer destinationAddressId;
private String name;
private Location location;
private User owner;
public DestinationAddress(String name, Location location, User owner) {
this.name = name;
this.location = location;
this.owner = owner;
}
public DestinationAddress() {
}
// getter and setter
}
The controller who handles the get and post is the following:
#PreAuthorize("hasRole('USER')")
#GetMapping(value = "/select-address")
public String selectAddress(Principal principal, Model model) {
List<DestinationAddress> addresses = destinationAddressService.getAllByUsername(principal.getName());
model.addAttribute("destinationAddresses", addresses);
model.addAttribute("destinationAddress", new DestinationAddress());
return "purchase/select-address";
}
#PreAuthorize("hasRole('USER')")
#PostMapping(value = "/select-address")
public String selectAddress(#ModelAttribute DestinationAddress destinationAddress, Principal principal) {
Purchase purchase = purchaseService.addPurchase(principal.getName(), destinationAddress);
return "redirect:/purchases/pay/" + purchase.getPurchaseId();
}
And the html page is the following:
<form th:object="${destinationAddress}" method="post">
<fieldset>
<legend>Your addresses</legend>
<ul>
<li th:each="destinationAddress : ${destinationAddresses}">
<input type="radio" th:field="*{destinationAddressId}" th:value="${destinationAddress.destinationAddressId}" />
<label th:for="${#ids.prev('destinationAddress.destinationAddressId')}" th:text="${destinationAddress}"></label>
</li>
</ul>
</fieldset>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
The error message is the following:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'destinationAddressId' available as request attribute
I don't know what's the problem here. I don't know which type will the form return to the controller. So I don't know which variable pass to the model and which one to get from the post controller method. Integer or DestinationAddress? I cannot find anything googling it, just small pieces of code without any explanations. Any suggestions?
I found a solution to my problem. I changed the html page, now it looks like this:
<form th:object="${address}" method="post">
<fieldset>
<legend>Your addresses</legend>
<ul>
<li th:each="destinationAddress : ${destinationAddresses}">
<input type="radio" th:field="${address.destinationAddressId}" th:value="${destinationAddress.destinationAddressId}" />
<label th:for="${destinationAddress.destinationAddressId}" th:text="${destinationAddress}"></label>
</li>
</ul>
</fieldset>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
I changed the name of the object inside the model because it was the same as the name of the temp destinationAddress of the loop. I also replaced '{#ids.prev(' because it was giving me an error:
Cannot obtain previous ID count for ID ...
Now it works fine.

Handling map-like request parameters in Spring MVC

Say I have a form for a list of questions where I need a 0/1 answer. I could easily model a static list with radios, something like
<input type="radio" name="question1" value="0">
<input type="radio" name="question1" value="1">
<br>
<input type="radio" name="question2" value="0">
<input type="radio" name="question2" value="1">
#RequestMapping("/answer")
public String answer(Integer question1, Integer question2) {
But I have a dynamic list of questions instead, where each question has a numeric ID. I therefore tried to model it like the following (the HTML is dynamically created with an iteration on the question list):
<input type="radio" name="question[42]" value="0">
<input type="radio" name="question[42]" value="1">
<br>
<input type="radio" name="question[51]" value="0">
<input type="radio" name="question[51]" value="1">
where 42 and 51 are the question id.
I was expecting to capture all values in a Map parameter of my Spring controller, like so:
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<Integer, Integer> question) {
It didn't work (the method isn't called).
I also tried with string ids:
<input type="radio" name="question['42']" value="0">
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<String, Integer> question) {
Same as before.
It only works if I use a map of string/string, but in this case I get all request parameters in the map, which I will then need to parse:
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<String, String> question) {
--> question.keys: "question[42]", "question[51]"
So what is the proper way of handling dynamic radios, or more generally map-like request parameters?
I don't know why, but it works if I put the map inside a bean:
public class QuestionForm {
private HashMap<Long, String> question;
public HashMap<Long, String> getQuestion() {
return question;
}
public void setQuestion(HashMap<Long, String> question) {
this.question = question;
}
}
#RequestMapping("/answer")
public String answer(QuestionForm questionForm) {

How dynamicly add new partial form and how get data from them in ASP MVC?

I have project on ASP MVC 5. I have a model "Article". This model have HashSet and ICollection of Author. Author - second model:
public partial class Article
{
public Article()
{
Authors = new HashSet<Author>();
}
[DisplayName("Авторы")]
public virtual ICollection<Author> Authors { get; set; }
I need to add page of creating Article, on which you can increase the number of authors(using AJAX), and each author to register the fields. I decided to use partial view of Author's model, without "Create" button(Create button used only view of creating Article). I need in unlimited adding new partial views, and after fill them - get all data from them. How make it? I newbie in MVC, and can't imagine how it will works.
http://i.stack.imgur.com/0RHD0.png - an illustration of how it should look
Is there a need to use partials? wouldnt it be easier to write a small script that would instead clone the first author enclosing element and just change the names of the elements involved to create a new author?
<div id="enclosingDiv" data-count="x">
<div class="someClass" data-index='x1' >
Author1 name Aurthor1 Textboxname="CollectionList[Index].Property"...
</div>
Now when creating a new Authouther, you can just create:
<script>
function createNewAuthor()
{
//clone first author
var count = $('encolsingDiv').attr('data-count');
//var count = $('encolsingDiv').children().length;
var author = $('enclosingDiv').first().clone();
//change name and id,etc using data-count
author.find('*[name$='value'])attr('name','ListCollection[count + 1]");
author.find('*[name$='value'])attr('id',....);
author.attr('data-index',count +1)
$('enclosingDiv').append(author);
$('enclosingDiv').attr('data-count',count + 1 to it);//makes life easier
}
function deleteAuthor(authourIndex)
{
//assumes that an author can be removed
$('div[data-index="'+authorIndex+'"]").remove();
$('enclosingDiv').children.each(function()
{
//if delete functionality exists, change the names of the element indices using the count variable
change all indices of element properties concerned
$(this).find('*[name$='value']).attr('name','ListCollection['+count+'].sumproperty");
count++;
});
}
</script>
So you can use that for create and delete methods, you don't need partials for that.
The code might need some work as what I show is the concept
It is not that hard. Your partial views will be posted as a collection.
Suppose that your partial view has 2 values, FirstName and LastName. It should be something like this:
#{
Guid index = Guid.NewGuid();
}
<input type="hidden" name="People.index" value="#index" />
<input type="text" name="People[#index].FirstName" value="" />
<input type="text" name="People[#index].LastName" value="" />
The final output would be:
<input type="hidden" name="People.index" value="B756DAD8-5D5D-449E-A4B4-E61F75C1562C" />
<input type="text" name="People[B756DAD8-5D5D-449E-A4B4-E61F75C1562C].FirstName" value="" />
<input type="text" name="People[B756DAD8-5D5D-449E-A4B4-E61F75C1562C].LastName" value="" />
<input type="hidden" name="People.index" value="B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32" />
<input type="text" name="People[B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32].FirstName" value="" />
<input type="text" name="People[B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32].LastName" value="" />
Your model must have a collection People object.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Article
{
//other properties...
public ICollection<Person> People { get; set; }
}
Your Controller:
public ActionResult YourAction (Article model)
{
//...
}
Untested code, but it should work fine.

knockout.js boolean data-binding issues with radio buttons

I'm currently doing the following to compensate for boolean's not mapping well to radio buttons. I am stuck binding 1 and 0 to the value (instead of true and false) because of how the fields are read out of the observables. The value of Pref1/Pref2 come as true/false boolean values from the server. The key here is I want to not only data-bind the checked value of the radio button to match the true/false in the object, but I also want the boolean value of true/false to be written back into the GraduationClass object. My compensation code is not only ugly, but not scalable.
<input type="radio" value="1" name="radioGroup" data-bind="checked: Pref1" />Yes
<input type="radio" value="0" name="radioGroup" data-bind="checked: Pref2" />No
Save
function SiteSettingsViewModel() {
var self = this;
this.saveGraduationClass = function(graduationClass) {
// hack until i get a custom radio button binding
if (graduationClass.Pref1() == 1) {
graduationClass.Pref1(true);
} else {
graduationClass.Pref1(false);
}
if (graduationClass.Pref2() == 1) {
graduationClass.Pref2(true);
} else {
graduationClass.Pref2(false);
}
// ...ajax call to save graduationClass to the server
}
function GraduationClass(data) {
var self = this;
ko.mapping.fromJS(data, {}, this);
}
Here is example from knockoutJs website, that demonstrate how to use radio buttons with
"checked" attribute:
<p>Send me spam: <input type="checkbox" data-bind="checked: wantsSpam" /></p>
<div data-bind="visible: wantsSpam">
Preferred flavor of spam:
<div><input type="radio" name="flavorGroup" value="cherry" data-bind="checked: spamFlavor" /> Cherry</div>
<div><input type="radio" name="flavorGroup" value="almond" data-bind="checked: spamFlavor" /> Almond</div>
<div><input type="radio" name="flavorGroup" value="msg" data-bind="checked: spamFlavor" /> Monosodium Glutamate</div>
</div>
<script type="text/javascript">
var viewModel = {
wantsSpam: ko.observable(true),
spamFlavor: ko.observable("almond") // Initially selects only the Almond radio button
};
// ... then later ...
viewModel.spamFlavor("msg"); // Now only Monosodium Glutamate is checked
</script>
But I dont understand why you use two objects - "Pref1" and "Pref2" fro one radiobutton group "radioGroup"? In this case you just could use one object as in an example used "spamFlavor".
So, please, describe more ditaily what you want to bind: one radiobuttons group by one selected value, or something else.
Also you could use computed observables to calculate different values, please see example.

Resources