I'm trying to use Razor's validation with Knockout JS.
Using the standard razor approach of
#Html.ValidationMessageFor(m => m.Name)
I can get this working when working with a single item. However, I'm working with inline edits and a collection of items, displayed in a foreach list;
#using (Html.BeginForm())
{
<!-- ko foreach: observables.PageItems-->
<tr>
<td><span data-bind="value: $data.Name"></span></td>
<td><a data-bind="click: $root.update.bind($data)">#Res.Resource.Update</a></td>
<td>#Res.Resource.Delete</td>
</tr>
<!-- /ko -->
}
When, for example, the Update button is clicked, the following method will be checked;
this.validate = function () {
var form = $("form");
form.removeData('validator');
form.removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse(form);
return form.valid();
};
The issue is, whenever the first item in the list is invalid, every item's validation message is shown. If the first items name is blank, each item is given a warning, not just the first.
At the same time, any item after the first being invalid when it's submit is clicked, it will not display any validation message, only if it's the first item.
This is having a single form covering the entire foreach loop.
Is there a way around this?
I was considering having the form within the KO foreach loop, each having a id based on the index, and when the item is submitted checking it that individual form is valid. Does this sound like something that is worth exploring?
Related
Below are two ngFor loops in HTML which are accessing elements when each of two seperate buttons are pressed.
<button (click)='payslips()'>Get Payslips</button>
<div>
<tr *ngFor="let payslip of objPayslips">----------->doesn't work, only works when 2nd button is pressed
<td>{{payslip.MonthYear}}</td>
</tr>
<h3>{{objPayslips | json}}</h3>-------->works fine
</div>
<button (click)='payslipdetails()'>Get Payslip Details</button>
<div>
<tr *ngFor="let index of objPayslipDetails;">
<td *ngIf="index.Addition">{{index.Addition}}</td>
<td *ngIf="index.AmountAddition">{{index.AmountAddition}}</td>
</tr>
<tr>
<td>Total Earnings: </td>
<td>{{payslipAdditionTotal}}</td>
</tr>
</div>
When Get Payslips is pressed then it's Object is generated and seen. But the same objects' values in the ngFor loop doesn't show up. But if I press the second button Get Payslip Details (which has a 2nd ngFor loop) then the values of 1st ngFor loop also show up. The two methods are completely different in the backend as well as the frontend. These methods are only run in a single HTML file. There is no link between these two methods anywhere. Then I don't know where is the problem. If I remove the 2nd button code entirely then the 1st code works fine which means that the ngFor loop shows values as it should. But both of them don't work properly alongside. Please have a look. Thankyou.
Edit:
Component.ts code:
payslipdetails(){
this.payrollService.getpayslipdetails(this.PId);
this.payslipEmployee=this.payrollService.objPayslipDetails[0];
this.objPayslipDetails=this.payrollService.objPayslipDetails;
//below code is to sum all the Earnings and Deductions. So to show their total values.
this.payslipAdditionTotal= this.payrollService.objPayslipDetails.filter((obj:any) => obj.AmountAddition).reduce((accumulator:any, obj:any) => {
return accumulator + obj.AmountAddition;
}, 0);
this.payslipDeductionTotal= this.payrollService.objPayslipDetails.filter((obj:any) => obj.AmountDeduction).reduce((accumulator:any, obj:any) => {
return accumulator + obj.AmountDeduction;
}, 0);
}
payslips(){
this.payrollService.getpayslips();
this.objPayslips=this.payrollService.objPayslips;
}
Service.ts code:
getpayslips(){
const headers = new HttpHeaders().set('Content-Type', 'application/json').set('Authorization','Bearer'+' '+GlobalService.authtoken);
return this.http.post<any>('http://localhost:3000/payroll/getpayslips/',null,{headers}).subscribe(({data}) => {this.objPayslips=data;})
}
getpayslipdetails(PId:number){
const headers = new HttpHeaders().set('Content-Type', 'application/json').set('Authorization','Bearer'+' '+GlobalService.authtoken);
return this.http.post<any>('http://localhost:3000/payroll/getpayslipdetails/',{"payslipId":PId},{headers}).subscribe(({data}) => {this.objPayslipDetails=data;})
}
This is an asychronous issue. You should grasp this concept before trying to use it.
In your service :
return this.http.post(...) // DO NOT put a .subscribe here
In your component TS file :
this.objPayslips = this.payrollService.getpayslips();
In your component HTML file :
<tr *ngFor="let payslip of objPayslips | async">
I'm building a website (e-commerce like) with Django.
At some point I display a list of items and for each item there is a form with submit button Order and Quantity picker.
I've implemented filter function that delete the html code of my items list and rebuild it with the matching items with jquery.
The new forms generated by this function do nothing when the submit button is clicked
Here is a part of the code I use in my function (I use an ajax call to generate a json of matching items and then I display them in the table list) :
$.each(code_json, function(index, value){
var string = "<tr id="+ value.material +"><td>"+ value.manufNo +"</td><form method='POST' action='/add_to_cart/"+value.material+"/"+ value.node+"/{{language}}'><td><input type='submit' class='btn' value='Commander' style='color:blue;'></td><td><input id='qty' type='number' name='qty' class='form-control' value='1'></td></form></tr>";
$("#header").after(string);
});
I know that usually with Django you have to add {% csrf_token %} for each form. However it throw an error page usually when missing and here it doesn't show anything
Edit : I tried to bind an onclick event on the submit button dynamically created. In this I did a $.post in jquery to simulate the submit of the form but nothing happend
$(document).on('click', '.btnStandard', function(event) {
console.log($(this).attr('id'));
$.post('/add_to_cart/'+$(this).attr('id'),
{
qty: $("#qty"+$(this).attr('id')).val()
},function(data,status,xhr){
alert("Data : "+data+", status: "+status+", xhr: "+xhr);
});
It print in console $(this).attr('id') but it doesn't do anything else
Thank you for your help
It doesn't explain why I have this problem but I found a workaround to solve my problem.
Instead of dynamically generate forms, I generate them with template and then I hide them all. Later, I make those I need visible when I need thanks to css.
I have the following view that displays gaming related data from a controller.
When the page initially loads, it hits an Index Controller that just lists all the gaming sessions ever created (100 total).
However, there is an input field, where the user can input a date, and then click a button.
When clicked, this button sends the date & time to another method called GamingSessionsByDate.
The GamingSessionsByDate method then returns new data which only contains Gaming Sessions with a start date of whatever the user entered.
Here is the view:
#model IEnumerable<GamingSessions.Session>
#{
ViewData["Title"] = "GamingSessionsByDate";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Gaming Sessions By Date</h2>
<input type="date" name="gameSession" id="gameSession">
<input type="Submit" id="postToController" name="postToController" Value="Find" />
#section Scripts
{
<script type="text/javascript">
$("#postToController").click(function () {
var url = '#Url.Action("GamingSessionsByDate", "GameSession")';
var inputDate = new Date('2019-01-23T15:30').toISOString();
$.ajax({
type: "POST",
url: url,
data: "startdate=" + inputDate,
success: function (data) {
console.log("data: ", data);
}
});
});
</script>
}
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.GameName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PlayDuration)
</td>
</tr>
}
</tbody>
</table>
Here is the controller that returns the gaming sessions by date:
public IActionResult GamingSessionsByDate(DateTime startdate)
{
var response = GetGameSessionsList(startdate);
var r = response.Results;
return View(r);
}
By the way, I have hard-coded a date time value into the AJAX call above that I know contains 5 gaming sessions.
Please note that I am writing out the data returned from the controller in the AJAX success method.
So when I click the button, nothing happens on the screen, I just see the initially-loaded 100 gaming sessions from the call to the Index controller.
However, behind the scenes, I can see the 5 gaming sessions I need being written to the console via the console.log command in the Ajax call.
I also see the correct data when I step-through the project in Visual Studio.
So it looks like everything is working, but it appears as if the view/page is not getting refreshed.
So, how do I get that data to display on the page?
Thanks!
The XMLHttpRequest object in JavaScript (what actually makes "AJAX" requests) is what's known as a "thin client". Your web browser is a "thick client", it does more than just make requests and receives responses: it actually does stuff automatically such as take HTML, CSS, and JavaScript that's returned and "runs" them, building a DOM and rendering pretty pictures and text to your screen. A thin client, conversely, literally just makes requests and receives responses. That's it. It doesn't do anything on its own. You are responsible, as the developer, for using the responses to actually do something.
In the case here, that means taking the response you receive and manipulating the DOM to replace the list of game sessions with the different game sessions retrieved. How you do that depends on what exactly you're returning as a response from your AJAX call. It could be HTML ready to be inserted or some sort of object like JSON. In the former case, you'd literally just select some parent element in the DOM and then replace its innerHTML with the response you received. In latter case, you'd need to use the JSON data to actually build and insert elements into the DOM.
Returning straight HTML is easier, but it's also less flexible. Returning JSON gives you ultimate freedom, but it's more difficult out of the box to manipulate the DOM to display that data. That's generally the point where you want to employ a client-side framework like Vue, Angular, React, etc. All of these can create templated components. With that, you need only change the underlying data source (i.e. set the data to the JSON that was returned), and the component will react accordingly, manipulating the DOM as necessary to create the view.
I personally like to use Vue, since it has the least friction to get started with an it's almost stupidly simple to use. For example:
<div id="App">
<input type="date" v-model="startDate" />
<button type="button" v-on:click="filterGameSessionsByDate">Find</button>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td>{{ item.GameName }}</td>
<td>{{ item.PlayDuration }}</td>
</tr>
</tbody>
</table>
</div>
Then a bit of JS to wire it up:
(function (options) {
let vm = new Vue({
el: '#App",
data: {
items: options.items,
startDate: null
},
methods: {
filterGameSessionsByDate: function () {
let self = this;
$.ajax({
type: "POST",
url: options.filterByDateUrl,
data: "startdate=" + self.startDate,
success: function (data) {
self.items = data;
}
});
}
}
});
})(
#Html.Raw(Json.Encode(new {
items = Model,
filterByDateUrl = Url.Action("GamingSessionsByDate", "GameSession")
}))
)
That may look a little funky if you're not that used to JS. I'm just using what's called a closure here: defining and calling a function in place. It takes an options param, which is being filled by the parenthesis at the bottom. Inside those, I'm creating an anonymous object that holds info I need, such as the initial items to display and the URL to get filtered results from. Then, that object is encoded into JSON and dumped to the page.
I am wanting to generate a table dynamically using Angular JS, based on what is checked on some checkboxes. The problem is that there are a few fields, we will call them relation/column, that I want to display ALWAYS, and the remaining fields only if their box is checked. The relation is searched for via a search box (relations can have multiple columns), and I want to display only the properties of that relation that are relevant to a user.
So
Update Time [X]
Update Status [ ]
Time Zone [ ]
Would display some html along the lines of
<table>
<tr>
<th> Relation </th>
<th> Column </th>
<th> Update Time </th>
</tr>
<tr ng-repeat= "result in results">
<td> {{result.relation}} </td>
<td> {{result.column}} </td>
<td> {{result.update_time}}</td>
</tr>
If no boxes were checked, only the relation and column fields would be populated. The documentation for Angular JS is taking me all over the place, so would anyone have an idea on how to do this?
edit : controller isn't working quite yet, I still need to filter the search results, but basically it goes
$scope.search = function(){
//validate form input
//create url with the input recieved
$http.get(url).success(function(data){
$scope.results = angular.fromJson(data);
});
}
I use mojolicious backend to grab the data I want. Again, the problem isn't that I can't get any data, or that I can't filter the results based on the relation. I want to be able to search based on relation, and only display the attributes of that relation that I want to, based on what is checked. THAT part, I can't figure out.
edit again : the firewall where I'm at prevents me from writing comments/upvoting. You shall be rewarded for your help when I get home tonight. Thank you thank you!
I think the best way to do this would be using ng-show expressions tied to a variable in the model.
For example.
<input type="checkbox" ng-model="updateTime">
makes a checkbox and ties the result to $scope.updateTime. You can then use this variable later on via the ng-show directive like so...
<th ng-show="updateTime"> Update Time </th>
...
<td ng-show="updateTime"> {{result.update_time}}</td>
this means that these elements will only show when updateTime is set to true (i.e the checkbox is checked.)
You can see an example here, I've only implemented the one field but it should be possible to extend it pretty easily!
http://plnkr.co/edit/W6Ht6dnGw4fBplI83fB1?p=preview
I would suggest using a custom filter with the checkbox scope variables passed in. Something like this:
html
<input type="checkbox" ng-model="checkbox1" />
<input type="checkbox" ng-model="checkbox2" />
<input type="checkbox" ng-model="checkbox3" />
... ng-repeat= "result in results|checkboxFilter:{checkbox1,checkbox2,checkbox3}"
filter.js
.filter('checkboxFilter', function() {
return function (results, checkbox1, checkbox2, checkbox3) {
var filtered_objects = angular.copy(results);
for (var i = 0, len = filtered_objects.length; i < len; i++) {
**modify your filtered_objects based on your checkboxes**
if (checkbox1) ...
if (checkbox2) ...
if (checkbox3) ...
}
return filtered_objects;
}
});
Maybe something like that.
I have this markup.
<form action='xxx.php' method='post'>
<table>
<tr>
<th>Branch Id</th>
<td><input id="branchId" type="text" size="15%" name="branchId"></input></td>
<th>Branch Name</th>
<td colspan="3"><input id="branchName" type="text" size="75%" name="branchName"></input></td>
<td>
<div id="button">
<input type="button" id="btnAdd" value="Add" name="submit"/>
</div>
</td>
</table>
</form>
<!------- Something here--------------->
<table class="divTable" id="exisBranch">
<tr><th id="thBranchId">Branch Id</th>
<th id="thBranchName">Branch Name</th>
<th class="btn" id="tcEdit">Edit</th>
<th class="btn" id="tcDelete">Delete</th>
</tr>
</table>
What basically happens is I populate the second table records retrieved through AJAX. Each row has a 'branchId','branchName' and two buttons of class 'bt'. When I click the edit button, I need the corresponding 'branchId' and 'branchName' values inserted into input elements in the first table, so that I can edit them and later, when I click the "btnAdd", I can save them.
This is the jQuery I have.
function fun(){
var branchId=$(this).closest("tr").find("td").eq(0).html();
$("#btnAdd").click(function () {
if($("#btnAdd").val()=='Save')
{
alert(branchId);
//ajax call
}
});
}
$(document).ready(function(){
$("#exisBranch").on('click','.bt',fun);
$("input[type='button']").click(function(e){
e.preventDefault();
});
});
Everything works fine when I click the 'btnAdd' for the first time. The problem starts with the second and successive clicks on this button.Consider that there are 6 rows in the dynamically populated content, and that 'branchId' of each row is the corresponding row number.
When I first click on 'EDIT' button on the 2nd row, and then the 'btnAdd', an alert correctly pops up showing 2.
Problem is , if I then go on to click 'EDIT' on the 6th row, and then the 'btnAdd' , I get two alerts. The first one shows 2, then 6.I just want 6.
For the third round, it goes like 2,6, and what ever is clicked next. This is making my AJAX fire as many no. of times as the no. of clicks.
This is really infuriating.I just can't seem to figure out why. I am a jQuery novice, so please bear with me if this is something fundamental and I messed it up.Please let me know how to make it fire only once with the latest value, instead of it stacking up on my history of calls?
You shouldn't keep binding your Add button w/ each Edit click--move it out to your document ready.
<script>
var branchId; //keep track of which branch is under edit
function fun(){
branchId = $(this).closest("tr").find("td").eq(0).html();
var branchName = $(this).closest("tr").find("td").eq(1).html();
$('#branchId').val(branchId);
$('#branchName').val(branchName);
}
$(document).ready(function(){
$("#exisBranch").on('click','.btn',fun);
$("#btnAdd").click(function () {
if($("#btnAdd").val()=='Save') {
alert(branchId);
//ajax call
}
});
$("input[type='button']").click(function(e){
e.preventDefault();
});
});
<script>
*you have a number of typos in your stack post.
The problem is that you are attaching a new click handler each time that fun is called. These handlers are not unbound after they fire, so the handlers are building up. You could use jquery's one to ensure that each event fires only once.
Really though, your entire approach is not ideal. Off the top of my head, one possible improvement which wouldn't take too much re-engineering would be to store the IDs in an array, then when add is clicked, process the array and clear it. This would let you only have one handler on add as well as allow for batch ajax calls instead of one per id.