My application allows you to track orders in a store database. This database contains a weak entity used for notes which is attached to an orderID. I want to allow users to be able to apply to same 'note' to many orders at the same time, but there are some fields in the notes table that are dependent on the location of the sale. In other words, you should only be allowed to apply the same note if all the sale locations are the same.
Simplified View:
#using (Html.BeginForm("Create", "Note", FormMethod.Get, new { name = "editForm" }))
{
<table id="DataTable">
<tr>
<th>
<input type="button" value="Edit" onclick="checkboxValidation()"/>
</th>
<th>
#Html.DisplayNameFor(model => model.OrderID)
</th>
<th>
#Html.DisplayNameFor(model => model.location)
</th>
</tr>
#foreach (var item in Model)
{
<tr >
<td>
<input type="checkbox" name="ids" value="#item.orderID" />
</td>
<td>
#Html.ActionLink(item.OrderID.ToString(), "Details", "Search", new { orderID = item.orderID.ToString() }, null)
</td>
<td>
#Html.DisplayFor(modelItem => item.location)
</td>
</tr>
}
</table>
checkboxValidation() is a javascript function I wrote to check if at least 1 checkbox is checked. How would I add a check to make sure all of the locations on checked lines are the same? Is this even possible? Thanks
EDIT: I missed a detail. When clicking the edit button, if the check is successful, it submits the form, which brings up the notes editor.
Should be fairly straightforward using JQuery:
// find all the checked rows
var checkedItems = $("#DataTable").find("tr td input[type=checkbox]");
// construct a locations array for all checked items
var locations = [];
checkedItems.each(function(index, element) {
if ($(element).is(":checked")) {
locations.push($(this).closest("td").next("td").next("td").text().trim());
}
});
// confirm each location is the same
var valid = true;
locations.each(function(index, element) {
if (index > 0 && locations[index-1] != element) {
valid = false;
return;
}
});
One additional thing you might want to do, is add some data tags to your tr and td elements, so that you can write more robust selectors that won't break with a minor UI re-arrangement (like tr[data-role=check] and tr[data-role=location], etc).
(Using JQuery closest and and JQuery each.)
Related
I have a table I've built in an app that when I click delete removes all rows following the row that was deleted on submit. That means that the table looks good to the user with just that one row removed, but when it hits the viewmodel on post action, those subsequent rows aren't included.
I've added some pretty complex code that goes all over the place to edit the index values of the rows but that ended up confusing the problem even more with some values replacing others and some other values just being set to 0. I know there has to be a more simple way.
I set this example back to the more simplified version where the delete appears to work well but then doesn't include any subsequent values in the viewModel when it hits the controller.
Here is the HTML Table
<input type="button" value="Add" class="button is-primary" id="btnAdd" />
<table id="tblPart" style="table-layout:fixed; width:100%;">
<thead>
<tr>
<th>
Part Number
</th>
<th>
Quantity
</th>
<th></th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.NumberModel.Count(); i++)
{
<tr >
<td >
#Html.TextBoxFor(modelItem => Model.NumberModel[i].PartNum, new { id = $"part{i}", required = "required", #class = "partTest" })
</td>
<td>
#Html.TextBoxFor(modelItem => Model.NumberModel[i].Quantity, new { type = "number", min = "0", id = $"quantity{i}", required = "required" })
</td>
<td>
<input type='button'
onclick='Remove(this)'
value='Remove' />
</td>
</tr>
}
</tbody>
</table>
Here is the JS
<script>
function Remove(button) {
//Determine the reference of the Row using the Button.
var row = $(button).closest("TR");
var name = $("TD", row).eq(0).html();
//console.log(row + name);
var index = 0;
var table = $("#tblPart")[0];
//Delete the Table row using it's Index.
table.deleteRow(row[0].rowIndex);
}
</script>
Thank you for your assistance.
When you delete the row, all subsequent rows index is wrong, you need to re-index the remaining rows on delete. If you for instance delete row with index 3 and then you have rows 0-2 and rows 4-6, 4-6 will be left out since there is no index 3, to fix this, you need to reindex the id and name attributes on the form fields after delete, also, you should consider using const and let in your function as var should be used for global variables, lastly, I added jquery tag to your post as you are mixing javascript and jquery in your code:
function Remove(button) {
//Determine the reference of the Row using the Button.
const row = $(button).closest("TR");
const name = $("TD", row).eq(0).html(); //this seems unnecessary
let index = 0; //this seems unnecessary
const table = $("#tblPart")[0];
//Delete the Table row using it's Index.
table.deleteRow(row[0].rowIndex);
//re-index
$('#tblPart tbody tr').each(function(i, el) {
//get the form fields
const $partnuminput = $(el).find('input[name*="PartNum"]');
const $quantityinput = $(el).find('input[name*="Quantity"]');
//use the iterator parameter of .each to set the index correctly
$partnuminput.attr("name", $partnuminput.attr("name).replace(/\d+/g, i);
$partnuminput.attr("id", $partnuminput.attr("id").replace(/\d+/g, i);
$quantityinput.attr("name", $partnuminput.attr("name).replace(/\d+/g, i);
$quantityinput.attr("id", $partnuminput.attr("id").replace(/\d+/g, i);
});
}
I came across a problem whose solution has led me to post it here so others may make use of it or improvise better than me.
My Problem: I have a table of results with check boxes to select rows. The requirement was to know if I was selecting(using the checkboxes) the same set of one particular column value, if so I had to do something.
HTML code
Considering the below being my html code for the dynamic table(CFML). The tag has to be inside a loop to dynamically create the table content.
<table class="fixedTable">
<thead class="containerbg">
<tr class="listingheader">
<td>StagingID</td>
<td>BatchID</td>
<td>AuditID</td>
<td>Action</td>
<td>File Name</td>
<td>Card No</td>
<td>Card No</td>
</tr>
</thead>
<tbody>
<tr class="cur_row" >
<td>#staging_id#</td>
<td>#batch_import_job_id#</td>
<td>#audit_Id#</td>
<td>#code#</td>
<td class="file_name">#source_filename#</td>
<td>#card_number#</td>
<td class="tblResolve">
<input type="checkbox" class="checkBoxClass cb" name="resolveErrorsCheck" value="#row_no#">
</td>
</tr>
JS Code
//register the click event
$(document).ready(function() {
$(document).on('click', '.cb',enableDisableActions);
});
function enableDisableActions() {
var values = new Array();
$.each($("input[name='resolveErrorsCheck']:checked").closest('td').siblings('.file_name'),
function (){
values.push($(this).text());
});
const initial = values[0];
const result = values.filter(src => src != initial);
if(result.length){
//no duplicate file_name selected
//do something
}else{
//duplicate file_name selected
//do something
}
}
I have an table created using ng-repeat and there hundreds of rows, up to 600 or 700. Each row includes a checkbox and I have a "Check All" box at the top to check all the boxes in one go. However I'm running into browser performance issues, IE11 (the clients preferred choice) in particular becomes unresponsive. After several minutes all the checkboxes appear checked but you still can't scroll or do anything so it is effectively useless.
I have created a controller array and when the checkAll box is clicked it loops through the model (the one used in ng-repeat) and adds a value to the array. I presume it's this looping through the array that is causing the slow-down but I'm not sure. Pagination has been ruled out, they want all the rows on one page.
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-checked="vm.approvedPayments.indexOf(payment.payId) > - 1"
ng-value="payment.payId" ng-model="payment.approved"
ng-click="vm.handleCheckBoxClick(payment.payId)" />
</td>
</tr>
</tbody>
</table>
Here is the angular function that checks/unchecks all
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = [];
} else {
vm.payments.forEach(function(payment){
vm.approvedPayments.push(payment.payId);
});
}
};
Swapping out the angular vm.tickOrUntickAllCheckBoxes() function for a plain old javascript option makes the checkAll box work almost instantaneously in IE11 however I lose access to the checked payment.payId values. I wonder is there away for angular to get them? Here is the plain javascript checkAll() function:
<script>
function checkAll(x) {
var checkBoxes = document.getElementsByClassName('paymentsApprovalCheckbox');
for (var i = 0; i < checkBoxes.length ; i++) {
checkBoxes[i].checked = (x.checked == true);
}
}
</script>
Then I update the checkAll checkbox like this:
<input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" onclick="checkAll(this)" />
If you check one checkbox individually then the ng-model="payment.approved" in the repeating checkboxes is updated but this does not happen if they are checked with the checkAll function. Is it possible for angular to detect the boxes checked with checkAll()? I guess this is just putting off the same old inevitable slow-down to a slightly later point in the process.
Anyone have any ideas or work-arounds? Thanks!
I would use the ng-model to the best of its abilities. In your controller:
$onInit() {
// If you need this from a REST call to populate, you'll have to
// remember to do that here;
this.model = {
all: true,
items: {}
};
}
In your loop:
<tr>
<th>Table Header</th>
<th>
<input type="checkbox"
id="checkAllCheckBox"
ng-model="vm.model.all"
ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments track by $index">
<td ng-bind="payment.somePaymentValue"></td>
<td>
<input type="checkbox"
class="paymentsApprovalCheckbox"
ng-change="vm.approvedPayments($index)"
ng-model="vm.model.items[$index]" />
</td>
</tr>
Then in your controller:
tickOrUntickAllCheckBoxes() {
const boxes = this.model.items.length;
this.model.all = !this.model.all;
// Several ways to do this, forEach, map, etc.,
this.model.items.forEach((item) => { item.checked = !this.model.all });
}
And for setting it individually:
approvedPayments(idx) {
// Sets all the item's boxes checked, or unchecked;
this.model.items[idx].checked = !this.model.items[idx].checked;
// Possible call to extended model to get payment info;
handleCheckBoxClick(idx);
}
You should be able to put all the payment information into the one approvedPayments() method rather than have two separate methods (move logic out of template and into the controller or a service). I.e., your model could look like:
this.model.items = [
// One 'option' with id, payment etc;
{
id: 73,
paymentId: 73,
somePaymentValue: 210.73,
currencyType: 'GBP',
checked: false
},
{
// Another 'option' etc...
}
]
One issue to note is the incompatibility of ngChecked with ngModel, had to look it up (which is why I haven't used ng-checked in the above).
Thank to everyone for the suggestions. The solution I came up with was to push some of the work back to the server side. Instead of just loading the payments model (in which each payment record contains a lot of info) i am now loading two additional models when the page loads, one of which is a set of key/value pairs where the keys are payId and the values are all false and another one with the same keys and all values are true. Example:
{
"1": false,
"2": false
}
These are used for the checkAll/Uncheck all - just set the vm.approvedIDs variable to the true or false one. Then, the vm.approvedIDs variable is used as the model in the ng-repeat checkbox.
I have to do a bit of extra work on the server side when the user sends the approvedIDs back to the server to get only the key/id of the 'true' entries. Here are the relevant angular controller functions:
$onInit() {
// call http to get 'data' from server
vm.payments = data.payments;
vm.paymentIDsFalse = vm.approvedIDs = data.paymentIDsFalse;
vm.paymentIDsTrue = data.paymentIDsTrue;
};
// tick/untick all boxes
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = vm.paymentIDsFalse;
} else {
vm.approvedPayments = vm.paymentIDsTrue;
}
};
// tick/untick one box
vm.handleCheckBoxClick = function(payId, currentValue){
vm.approvedPayments[payId] = currentValue;
};
vm.submitApprovedIds = function(){
// post vm.approvedPayments to server
};
HTML:
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-value="payment.payId"
ng-model="vm.approvedPayments[payment.payId]"
ng-click="vm.handleCheckBoxClick(payment.payId, vm.approvedPayments[payment.payId])" />
</td>
</tr>
</tbody>
</table>
It looks to me as if there must be a better way than creating these additional models but it is working pretty smoothly for now and I can move on to the next thing!
Iam completely confused at a point and need anyone's help here. Went through various examples but nothing could help.
I have a created dynamic table, added with checkboxes. Now whenever a row is selected its id will be bound to an array and it will be diplayed at the top of table.
What I need is:The code for functionality of select all check box. And whenever all the rows are selected by select all checkbox, its ids has to be displayed.
Below is the code for the table:
<table>
<thead>
<tr>
<th>
<input name="all"
type="checkbox"
ng-click="selectAll()" />
</th>
<th>ID</th>
<th>Date</th>
</tr>
</thead>
<tbody ng-repeat="x in cons">
<tr>
<td>
<input type="checkbox"
name="selectedids[]"
value="{{x.id}}"
ng-checked="idSelection.indexOf(x.id) > -1"
ng-click="toggleSelection(x.id, idSelection)"> </td>
<td>{{x.id}}</td>
<td>{{x.title}}</td>
</tr>
</tbody>
app.js:
$scope.idSelection = [];
$scope.toggleSelection = function toggleSelection(selectionName, listSelection) {
var idx = listSelection.indexOf(selectionName);
// is currently selected
if (idx > -1) {
listSelection.splice(idx, 1);
}
// is newly selected
else {
listSelection.push(selectionName);
}
};
//$scope.selectAll=function(){}
//Need code for this function to work
Here is a demo: http://plnkr.co/edit/m9eQeXRMwzRdfCUi5YpX?p=preview.
Will be grateful, if anyone can guide.
You need a variable to keep track of whether 'All' is currently active or not. If not, we create a new array of all item id's using the array map function, and pass this to idSelection. If allSelected is currently active, we pass an empty array to idSelection
$scope.allSelected = false;
$scope.selectAll = function() {
$scope.allSelected = !$scope.allSelected;
if($scope.allSelected) {
$scope.idSelection = $scope.cons.map(function(item) {
return item.id;
});
} else {
$scope.idSelection = [];
}
}
I'm trying to implement a quick search/filter function to my table using jquery. In essence I want to hide all the rows that don't have the string I'm looking for from a searchbox to be hidden.
I have a dynamically created table and a text field used as the filter for the list.
Table:
<table id="report-table" class="table">
<thead>
<tr>
<th class="">client</th>
<th class="">coach</th>
<th class="">groups</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name">John</td>
<td class="coach">Peter </td>
<td class="groups"> Skiers </td>
</tr>
</tbody>
Ihave a function tied to the change event of the search text box. In this function I essentially want to choose all tr that do not contain the text string in name or coach column and add a class to them. I have tried many things but have not gotten the syntax right, how should it be written?
hideSearch: function(e){
console.log("hideSearch called");
var searchValue = this.$el.find('.search-text').val();
if(!searchValue ){
console.log("hideSearch: empty search param");
this.$el.find('tr').removeClass('hidden');
}
else{
console.log("hideSearch: searched for: " + searchValue);
//$('(#name, #groups):contains:not("'+searchValue+'")').parent().addClass('hidden');
var selection =$('#name, #groups').('*:contains("'+searchValue+'")');
console.log(selection);
//console.log($('#name, #groups').('*:contains("'+searchValue+'")'));
//$('(#name, #groups):contains("'+searchValue+'")').parent().addClass('hidden');
//$('#name, #groups').('*:contains:not("'+searchValue+'")').parent().addClass('hidden');
}
$('#name, #groups').('*:contains("'+searchValue+'")'); would basically try to access the property *:contains("foo") (assuming searchValue is "foo") of the object returned by $('#name, #groups'). I believe I don't have to say that jQuery objects don't have properties with such strange names.
First of all you have to give all the cells a common class instead of an ID. Then you should select all rows and see if either .name or .coach contain the search value. Use .filter to get those for which neither cell matches:
$('#report-table > tbody > tr').filter(function() {
return $(this).children('.name').text().indexOf(searchValue) === -1 &&
$(this).children('.coach').text().indexOf(searchValue) === -1;
}).addClass('hidden');
The filter callback returns true if neither the .name cell nor the .coach cell contain the search value. Those rows for which the callback returns true are kept in the selection and are getting the class hidden added to them.