call function for each element in a foreach using jQuery - javascript

I have a view-model which has a data grid like the one shown below
<table>
#foreach (var item in Model)
//for(int i=0;i<Model.Count();i++)
{
using (Html.BeginForm("Edit", "Views", FormMethod.Post))
{
<tr>
<td>
#Html.TextBoxFor(modelItem => item.Description, new { id = "description" })
</td>
<td>
#Html.DisplayFor(modelItem => item.DisplayAt, new { id = "displayat" })
</td>
<td>
#Html.DropDownListFor(m => item.selectedRegion, item.RegionsList, item.Region, new { id = "ddlregion" })
</td>
<td>
#Html.DropDownListFor(modelItem => item.Products, item.ProductsList, item.Products, new { id = "ddlproduct" })
</td>
<td>
#Html.DropDownListFor(modelItem => item.Companies, item.CompaniesList, item.Companies, new { id = "ddlcompany" })
</td>
<td>
#Html.DropDownListFor(modelItem => item.UserName, item.UserNamesList, item.UserName, new { id = "ddlusers" })
</td>
<td>
#Html.CheckBoxFor(modelItem => item.Visible, new { id = "chkVisible" })
</td>
<td>
<input type="submit" value="Edit" id="button" />
</td>
</tr>
}
}
</table>
I have written a jquery function to work on the click of the button with id=button
However, it works only for the first row.
When I click on the buttons from the second row the function is not being called.
How do I write a jquery which will be called when any of the buttons is clicked.
My jQuery code:
$('#button').click(function () {
var product = $("#ddlproduct").find("option:selected").text();
var user = $("#ddlusers").find("option:selected").text();
*does something*
});
This is being called only for the first row.
I'm assuming there is something like a foreach button with id that is clicked.

Once you change your ids to classes so they are not restricted to being unique, you can look up your related elements contextually.
$('.button').click(function () {
//get a reference to the row that contains the button that was clicked
var $contextualRow = $(this).closest('tr');
var product = $contextualRow.find(".ddlproduct").find("option:selected").text();
var user = $contextualRow.find(".ddlusers").find("option:selected").text();
*does something*
});

Give the button a class instead of the ID.
<button class="button">Submit</button>
That will trigger the event each time the button with class button is clicked.
$('.button').click(function () {
*does something*
});
To get the values of your selects, you can go about doing it contextually just like Taplar wrote or you can select the parent of the button and go about its children:
var parent = $(this).parent().parent();
var product = $(".ddlproduct", parent).val();
var user = $(".ddlusers", parent).val();
Dont forget to change those IDs to classes.

Related

How to create reordering functionality for rows in a table using JavaScript

I'm currently refactoring a project that is using Python widgets along with JavaScript. It currently uses a table with a reorder feature that could use some major improvements. When using the "reorderRowDown" button, it works correctly the current row moves down and the previous and next row adjust accordingly.
However, on the "reorderRowUp" button the current row simply alternates back and forth between the current and previous row. (I hope I'm explaining this well, my apologies) It's very clunky moving the current row up the table.
I would like to achieve the functionality similar to "reorderRowDown" where when clicking "reorderRowUp" the current row moves up and the previous and next row adjust accordingly. In summary, I would like to know how to implement reordering of the rows in the table either up or down the correct way. Any help would be greatly appreciated.
(Here are gifs posted below to better demonstrate the scenarios I'm referencing)
reorderRowDown Example:
https://media.giphy.com/media/8WHiGw57pPTK9Zdibk/giphy.gif
reorderRowUp Example:
https://media.giphy.com/media/Wp7x9GtYDX29cFLT6I/giphy.gif
Here's my code (please let me know if you require more)
PathContext.js
'use strict';
module.exports = () => {
window.reorderRowUp = function(ruleSetIdPriority) {
let ruleSetId;
ruleSetId = ruleSetIdPriority.split('-priority')[0];
const row = document.getElementById(ruleSetId);
const table = row.parentNode;
const prevRow = row.previousElementSibling;
table.insertBefore(row, prevRow);
};
window.reorderRowDown = function(ruleSetIdPriority) {
let ruleSetId;
ruleSetId = ruleSetIdPriority.split('-priority')[0];
const row = document.getElementById(ruleSetId);
const table = row.parentNode;
const nextRow = row.nextElementSibling;
table.insertBefore(nextRow, row);
};
};
reorder_row_widget.html
<button class="reorder-btn" type="button" onclick=reorderRowUp("{{widget.name}}")>Up</button>
<button class="reorder-btn" type="button" onclick=reorderRowDown("{{widget.name}}")>Down</button>
<input id="{{ widget.name }}" type="hidden" name="{{ widget.name }}" value="{{ widget.value }}"></input>
Here's the html of the actual table row from the console in my browser
<table>
<tbody>
<tr class="form-row row1 has_original dynamic-rule_set" id="rule_set-0">
<td class="original">
<p>
Rule object (84)
</p>
<input type="hidden" name="rule_set-0-id" value="84" id="id_rule_set-0-id">
<input type="hidden" name="rule_set-0-path_context" value="6" id="id_rule_set-0-path_context">
</td>
<td class="field-priority">
<button class="reorder-btn" type="button" onclick="reorderRowUp("rule_set-0-priority")">Up</button>
<button class="reorder-btn" type="button" onclick="reorderRowDown("rule_set-0-priority")">Down</button>
<input id="rule_set-0-priority" type="hidden" name="rule_set-0-priority" value="-301">
</td>
<td class="field-pattern">
<input type="text" name="rule_set-0-pattern" value="^/$" id="id_rule_set-0-pattern">
</td>
<td class="field-value">
<input class="tgl" id="rule_set-0-value" name="rule_set-0-value" type="checkbox" checked="">
<label class="tgl-btn" for="rule_set-0-value"></label>
</td>
<td class="field-experience">
<select name="rule_set-0-experience" id="id_rule_set-0-experience">
<option value="">---------</option>
<option value="modal" selected="">Modal</option>
<option value="sticky_cta">Sticky CTA</option>
</select>
</td>
<td class="delete"><input type="checkbox" name="rule_set-0-DELETE" id="id_rule_set-0-DELETE"></td>
</tr>
</tbody>
</table>
admin.py (python code if needed)
class ReorderRowWidget(forms.Widget):
template_name = 'admin/reorder_row_widget.html'
def get_context(self, name, value, attrs=None):
return {'widget': {
'name': name,
'value': value,
}}
def render(self, name, value, attrs=None, renderer=None):
context = self.get_context(name, value, attrs)
template = loader.get_template(self.template_name).render(context)
return mark_safe(template)
Here is the implementation I used to resolve my issue and create a better UI. I refactored the PathContext.js file.
function replaceReorderFunction() {
const reorderRowUp = window.reorderRowUp || function() {};
const reorderRowDown = window.reorderRowDown || function() {};
// when page gets rendered, django creates a hidden row with a special ruleSetId with id __prefix__
// once 'add new row' is clicked, a real ruleSetId is given to the row
// need to replace the reorder function of that row so that it uses the proper ruleSetId so the row can be reordered properly
// should only need to happen once, on the first reordering after the row is added
// therefore I assume that the row in question is always at the bottom of the table
const tableWrapper = document.getElementById('rule_set-group');
const tbody = tableWrapper.querySelector('tbody');
const rowToUpdate = tbody.lastElementChild.previousElementSibling.previousElementSibling;
const priorityField = rowToUpdate.getElementsByClassName('field-priority')[0];
const buttons = priorityField.getElementsByTagName('button');
buttons[0].onclick = () => {reorderRowUp(rowToUpdate.id);};
buttons[1].onclick = () => {reorderRowDown(rowToUpdate.id);};
return rowToUpdate.id;
}
window.reorderRowUp = function(ruleSetIdPriority) {
let ruleSetId;
// it's a new row, ruleSetId is not correct
if (ruleSetIdPriority.match(/__prefix__/)) {
// get the proper ruleSetId and replace existing onclick functions
ruleSetId = replaceReorderFunction();
} else {
ruleSetId = ruleSetIdPriority.split('-priority')[0];
}
const row = document.getElementById(ruleSetId);
const table = row.parentNode;
const prevRow = row.previousElementSibling;
if (!prevRow) {
return;
}
table.insertBefore(row, prevRow);
// swap priority values
const prevPriorityValue = getPriorityValueFromRow(prevRow);
const curPriorityValue = getPriorityValueFromRow(row);
setPriorityValueOfRow(row, prevPriorityValue);
setPriorityValueOfRow(prevRow, curPriorityValue);
};
window.reorderRowDown = function(ruleSetIdPriority) {
let ruleSetId;
// it's a new row, ruleSetId is not correct
if (ruleSetIdPriority.match(/__prefix__/)) {
ruleSetId = replaceReorderFunction();
} else {
ruleSetId = ruleSetIdPriority.split('-priority')[0];
}
const row = document.getElementById(ruleSetId);
const table = row.parentNode;
const nextRow = row.nextElementSibling;
if (!nextRow || nextRow.className === 'add-row' || nextRow.id.includes('empty')) {
return;
}
table.insertBefore(nextRow, row);
// swap priority values
const nextPriorityValue = getPriorityValueFromRow(nextRow);
const curPriorityValue = getPriorityValueFromRow(row);
setPriorityValueOfRow(row, nextPriorityValue);
setPriorityValueOfRow(nextRow, curPriorityValue);
};

Get the id of object in a tablerow - javascript

Hey i'm trying to get the id of the specific object so that i can send it to the backend for now. I'm having a hard time get the individual object and not the whole list at once.
If anything looks funky it's because I'm pretty new at this and it's been alot of trial and error
my javascript looks like this:
//waits for the html doc to be ready before atempting to run any js.
$(document).ready( () =>{
// jquery getting our json order data from API
$.get("http://localhost:8888/orderslist", (data) => {
// loops through our orderlist api
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
$clone.find('.customer_name').text(item.customer_name);
$clone.find('.date').text(item.date);
$clone.find('.time').text(item.time);
$clone.find('.pickup').text(item.pickup);
$clone.find('.comments').text(item.comments);
$clone.find('.total').text(item.total + ' Kr.');
let foo = function(){
//gets id from object and sends it to backend using get method
};
// accept and cancel buttons
$clone.find('.order_status').html(
`<button id = "acceptOrder" type="button" onclick="${foo()}">Accept</button>` +
`<button id = "cancelOrder" type="button" onclick="${foo()})">Cancel</button>`
);
// loops through orders product name
let productsName = item.products.map(prod => `${prod.name}`);
$clone.find('.products').html(productsName.join('<br />'));
// loops through orders product price
let productsPrice = item.products.map(prod => `${prod.price} Kr.`);
$clone.find('.price').html(productsPrice.join('<br />'));
return $clone;
});
//appends to our frontpage html
$("#frontpage_new_ordertable tbody").append(rows);
});
});
This is the json data i get from my route.
{
id: "3JBBdJdBUP7QyDvCnmsF",
date: "30/04-2020",
time: "13:40:41",
total: 40,
products: [
{
name: "Caffe Latte",
price: 40
}
]
}
My html looks like this:
<!-- this is the table of new orders -->
<table id="frontpage_new_ordertable">
<tbody>
<tr>
<th>Customer</th>
<th>Date</th>
<th>Ordered at</th>
<th>Wished pickup time</th>
<th>Order</th>
<th>Comments</th>
<th>Price</th>
<th>Total</th>
<th>Order Status</th>
</tr>
</tbody>
<tfoot>
<tr>
<td class="customer_name"></td>
<td class="date"></td>
<td class="time"></td>
<td class="pickup"></td>
<td class="products"></td>
<td class="comments"></td>
<td class="price"></td>
<td class="total"></td>
<td class="order_status"></td>
</tr>
</tfoot>
</table>
After edit it looks like this. I'm not seeing the accept button
// loops through our orderlist api
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
$clone.data("id", item.id);
$clone.find('.date').text(item.date);
$clone.find('.time').text(item.time);
$clone.find('.pickup').text(item.pickup);
$clone.find('.comments').text(item.comments);
$clone.find('.total').text(item.total + ' Kr.');
$(function() {$(document).on("click", ".acceptOrder", foo);
function foo() {
var btn = $(this);
var row = btn.closest("tr");
var id = row.data("id");
var name = row.find(".customer_name").text();
};
$clone.find('.order_status').html(
`<button type="button" class='acceptOrder">Accept</button>`
);
});
The relevant parts of the code are:
$(function() {
$.get("http://localhost:8888/orderslist", (data) => {
// loops through our orderlist api
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
let foo = function(){
//gets id from object and sends it to backend using get method
};
$clone.find('.order_status').html(
`<button id="acceptOrder" type="button" onclick="${foo()}">Accept</button>`
);
return $clone;
});
//appends to our frontpage html
$("#frontpage_new_ordertable tbody").append(rows);
});
});
First step is to remove the duplicate id: and onclick=
$(function() {
$(document).on("click", ".acceptOrder", foo);
function foo() {
}
...
$clone.find('.order_status').html(
`<button type="button" class='acceptOrder">Accept</button>`
);
...
});
Now, clicking the Accept button will call 'foo' as an event, with this as the button. You can get the original JSON ID by either putting this on the button as a data-id or on the parent tr:
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
$clone.data("id", item.id);
then, in foo, you can get this as:
function foo() {
var btn = $(this);
var row = btn.closest("tr");
var id = row.data("id");
var name = row.find(".customer_name").text();
...
}
the alternative is to add to the button in the same way - I tend to use it on the tr as you'll probably need to get the tr anyway and it means it's available from any element (eg another button).
`<button type="button" data-id="${item.id}" ...
To include some more context:
$(document).ready(() =>{
// add event listener here
$(document).on("click", ".acceptOrder", foo);
// add the event handler at the top-level inside doc.ready
function foo() {
var btn = $(this);
var row = btn.closest("tr");
var id = row.data("id");
var name = row.find(".customer_name").text();
...
}
// original code
$.get("http://localhost:8888/orderslist", (data) => {
// loops through our orderlist api
let rows = data.map(item => {
let $clone = $('#frontpage_new_ordertable tfoot tr').clone();
// add ID to the row as a data-id
$clone.data("id", item.id);
// original code
$clone.find('.customer_name').text(item.customer_name);
...etc
// remove let foo =
// let foo = function(){
// update accept/cancel buttons
// accept and cancel buttons
$clone.find('.order_status').html(
`<button class="acceptOrder" type="button">Accept</button>` +
`<button class="cancelOrder" type="button">Cancel</button>`
);

Run OnChange event for each row in table - MVC

MVC 5 - Razor, Javascript
Novice user!!
I would like to be able to run a simple onchange event when a checkbox is altered in the table, but the onchange only works for the first row.
I have read that this is because the script only runs on the first row and does not run for the subsequent dynamically populated data.
I understand this but don't know how to fix it... I assume that I need to create an individual ID for each checkbox in the each row - I don't know how to do this.
Also, when the checkboxes have different IDs I don't know how to refer to them to add the onchange.
Hope this makes sense. Thanks in advance.
#model IEnumerable<core_point.Models.RegisterEmployee_Step6Model>
#{
ViewBag.Title = "Index";
}
<h2>Select the roles that you have been employed to do, by clicking the check-box...</h2>
<table class="table">
#foreach (var item in Model)
{
<tr class="h3">
<td class="vert-align">
<img src="#item.IconLocation" height="80" width="80" />
</td>
<td class="vert-align">
#Html.CheckBoxFor(modelItem => item.Selected, new { #id = "CheckBoxSelected" })
</td>
<td class="vert-align">
#Html.DisplayFor(modelItem => item.RoleDescription)
</td>
<td class="vert-align">
#Html.TextBoxFor(modelItem => item.Role, new { hidden = "hidden"} )
</td>
</tr>
}
</table>
<div>
Roles selected:<input type="number" id="noSelected" value="0"/>
</div>
<script>
var fld_CheckBoxSelected = document.getElementById("CheckBoxSelected");
var fld_noSelected = document.getElementById("noSelected");
fld_CheckBoxSelected.onchange = function () {
if (fld_CheckBoxSelected == true) {
fld_noSelected = fld_noSelected.value + 1;
}
else
{
fld_noSelected = fld_noSelected.value - 1;
}
}
</script>
replace id with class in CheckBoxFor:
<td class="vert-align">
#Html.CheckBoxFor(modelItem => item.Selected, new {#class= "CheckBoxSelected"})
</td>
In Javascript code use fallowing code:
(function($) {
var onChanged = function(){
/*Do smth that you needed*/
};
$(function() {
$(".checkbox").on("change", onChanged)
});
}(jQuery));
Because all your input boxes will have the same Id value "CheckBoxSelected". That is invalid markup.Id values should be unique.
You may consider removing the id from the markup and use a css class.
#Html.CheckBoxFor(modelItem => item.IsSelected, new { #class = "mycheckBox" })
and using jQuery library, listen to the change event on the input element with this css class.
$(function(){
$("input.mycheckBox").change(function(){
var isSelected = this.checked;
alert("Checkbox changed");
if (isSelected ) {
$("#noSelected").val( parseInt($("#noSelected").val())+ 1));
}
else
{
$("#noSelected").val( parseInt($("#noSelected").val()) - 1));
}
});
});
Assuming the current value of input with id noSelected is a numeric value(else parseInt will fail).

MVC razor display element that can be edited by javascript

I'm working with a table that I would like to have display text but also have that text updated by javascript.
I can create a readonly textbox and have javascript edit that, but I would rather just have some text on the screen. Here is a sample that has a DisplayFor which displays text and a readonly textbox which shows the same text.
#for (var i = 0; i < Model.ProjectSubmissions.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model.ProjectSubmissions[i].ChapterNumber)
</td>
<td>
#Html.TextBox("recordsToSort[" + i + "].ChapterNumber", Model.ProjectSubmissions[i].ChapterNumber, new { #id = "recordsToSort[" + i + "].ChapterNumber", #class = "SortOrder", #readonly = "readonly" })
</td>
</tr>
}
My javacript can edit the textbox like this:
$(".sortable-table tr").each(function (index, element) {
var hiddenInput = $(element).find(".SortOrder").first();
hiddenInput.val(index);
});
Can I write javascript that will update the DisplayFor? Or should I use a different element than DisplayFor that javascript can update?
UPDATE:
I'd like to end up with something like this (I would like to keep the hidden .SortOrder element.):
#for (var i = 0; i < Model.ProjectSubmissions.Count; i++)
{
<tr>
<td>
<span id="#Html.Id("recordstosort[" + i + "].ChapterNumber")" class="SortOrderDisplay">#Model.ProjectSubmissions[i].ChapterNumber</span>
#Html.Hidden("recordsToSort[" + i + "].ChapterNumber", Model.ProjectSubmissions[i].ChapterNumber, new { #id = "recordsToSort[" + i + "].ChapterNumber", #class = "SortOrder" })
</td>
</tr>
}
And javascript like this:
$(".sortable-table tr").each(function (index, element) {
var hiddenInput = $(element).find(".SortOrder").first();
hiddenInput.val(index);
var displayInput = $(element).find(".SortOrderDisplay").first();
if (displayInput !== 'undefined') {
displayInput.text = index;
}
});
But this isn't working.
You can use jQuery text instead of val.
#for (var i = 0; i < Model.ProjectSubmissions.Count; i++)
{
<tr>
<td class="SortOrder">
#Html.DisplayFor(modelItem => Model.ProjectSubmissions[i].ChapterNumber)
</td>
</tr>
}
Then the javascript would be
$(".sortable-table tr").each(function (index, element) {
var hiddenInput = $(element).find(".SortOrder").first();
hiddenInput.text(index);
});
Javascript can be used to update any element you like. The easiest way would probably be to create a span with an id property and then use document.getElementById.innerText to set the text.
You can easily replace the #Html.DisplayFor with a <span id="#Html.IdFor(m => m.ProjectSubmissions[i].ChapterNumber)"></span>.
This only an example, and there are several ways to go about changing text client-side.

show/hide elements dynamically using knockout

I have a table which has four columns namely Code, Name, Quantity and Price. Out of these, I want to change the content/element of Quantity column dynamically. Normally, it should show the element with quantity displayed in it and when user click on element, I want to show the element so user can edit the quantity. I'm trying to implement as per "Example 2" on this knockout documentation link.
Following is my code :
Page Viewmodel
function OrderVM (vm) {
var self = this;
self.OrderNo= ko.observable(vm.OrderNo());
.....
.....
self.OrderedProducts = ko.observableArray([]);
for (i = 0; i < vm.OrderedProducts().length; i++) {
var p = new ProductVM(vm.OrderedProducts()[i]);
self.OrderedProducts.push(p);
}
.....
}
function ProductVM(vm) {
var self = this;
self.Code = ko.observable(vm.Code());
self.Name = ko.observable(vm.Name());
self.Quantity = ko.observable(vm.Quantity());
self.Price = ko.observable(vm.Price());
self.IsEditing = ko.observable(false);
this.edit = function () {
self.IsEditing(true);
}
}
In my Razor view I have following code :
<tbody data-bind="foreach:OrderedProducts">
<tr>
<td class="lalign"><span data-bind="text:Code"/></td>
<td class="lalign"><span data-bind="text:Name" /></td>
<td class="ralign" style="padding:1px!important;">
<span data-bind="visible: !IsEditing(), text: Quantity, click: edit"
style="width:100%;float:left;text-align:right;padding-right:10px;" />
<input data-bind="value: Quantity,visible:IsEditing,hasfocus:IsEditing"
style="width:100%;text-align:right;padding-right:10px;" />
</td>
<td class="ralign rightbordernone" style="padding-right:20px!important;"><span data-bind="text:Price"/></td>
</tr>
With above code, when I click on span element in my Quantity column of table, "edit" function is called and "IsEditing" value is set to true but I don't see input element visible in my cell. After clicking on span element, If I look at the html using "Inspect Element", I can still see the element only and not but on screen in my view, I see neither span nor input element.
This is very simple logic and executes as expected however final result on view is not as expected. Can anyone help me to detect what's wrong with above code?
The problem is tricky. It lies in the fact that span is not a self-closing element. This will work:
<td>
<span data-bind="visible: !IsEditing(), text: Quantity, click: edit"></span>
<input data-bind="value: Quantity, visible: IsEditing, hasfocus: IsEditing" />
</td>
Here's a full demo:
function ProductVM(vm) {
var self = this;
self.Code = ko.observable(vm.Code());
self.Name = ko.observable(vm.Name());
self.Quantity = ko.observable(vm.Quantity());
self.Price = ko.observable(vm.Price());
self.IsEditing = ko.observable(false);
this.edit = function () {
self.IsEditing(true);
}
}
ko.applyBindings({ OrderedProducts: [new ProductVM({
Code: function() { return 1; },
Name: function() { return "Apples"; },
Quantity: function() { return 10; },
Price: function() { return 12.50; }
})]})
span { padding: 5px; background: pink; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
(Click "10" e.g. the Quantity for demo.)
<table>
<tbody data-bind="foreach: OrderedProducts">
<tr>
<td><span data-bind="text:Code"></span></td>
<td><span data-bind="text:Name"></span></td>
<td>
<span data-bind="visible: !IsEditing(), text: Quantity, click: edit"></span>
<input data-bind="value: Quantity, visible: IsEditing, hasfocus: IsEditing" />
</td>
<td><span data-bind="text:Price"></span></td>
</tr>
</tbody>
</table>
Remember, the same holds for div elements, don't use them in a self-closing manner or Knockout will fool you when you least expect it.

Categories

Resources