Add and Remove List Knockout - javascript

Im trying to make a calculator that can add and subtract values. At the same time, I wanted user to be able to add and remove list. My add and remove list button doesn't work. Can someone help me?
here are my javascript code:
var ViewModel = function() {
var self = this;
self.value1 = ko.observable(0),
self.value2 = ko.observable(0),
self.mathOperator = ko.observableArray(['+', '-']);
self.selectedOperator = ko.observableArray(['+']);
self.calculateValues = ko.computed(function(){
var selectedOperator = self.selectedOperator()[0],
value1 = self.value1(),
value2 = self.value2();
switch(selectedOperator[0]){
case '+':
return parseInt(value1) + parseInt(value2);
case '-':
return parseInt(value1) - parseInt(value2);
}
});
self.addValue = funtion() {
self.selectedOperator.push(self.selectedOperator()[0]);
}
self.removeValue = function(val) {
self.selectedOperator.remove(val)
}
}
ko.applyBindings(new ViewModel());
and here are my html code:
<div id="MyCalc">
<table>
<tbody data-bind="foreach: selectedOperator">
<tr>
<td>
<label>Value1</label>
<input type="text" data-bind="value: value1" />
</td>
<td>
<select data-bind="options: mathOperator, selectedOptions: selectedOperator,"></select>
</td>
<td>
<button data-bind="click: removeValue">Remove</button>
</td>
</tr>
<tr>
<td>
<label>Value2</label>
<input type="text" data-bind="value: value2" />
</td>
<td>
<select data-bind="options: mathOperator, selectedOptions: selectedOperator,"></select>
</td>
<td>
<button data-bind="click: removeValue">Remove</button>
</td>
</tr>
<button data-bind="click:addvalue">Add Another Value</button>
</tbody>
</table>
<div id="result">Total:<span data-bind="text: calculateValues" />
</div>
or for easy viewing, this is the jdfiddle link. http://jsfiddle.net/sqkr65td/

Related

Dynamic change of color and text of an element

The script below creates a new line of text boxes and a button. However, when the button is clicked, the new field would like to have new button text and a new design.
$(function () {
var newRow = $(".addRows").clone();
$("#addButton").on("click", function () {
let row = newRow.clone().appendTo("#TextBoxesGroup tbody");
$("tr").find("#addButton").css( "border-color", "red" );
});
});
<table id="TextBoxesGroup">
<tr class="addRows">
<td>Start Date:</td>
<td>
<input type="text" name="StartDate[]" class="picker" value="" size="6">
</td>
<td>End Date:</td>
<td>
<input type="text" name="EndDate[]" class="picker" value="" size="6">
</td>
<td>
<input type="button" id="addButton" value="add row" />
</td>
</tr>
</table>
<script type = "text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
For example, the created new button should be with text delete and color red.
Thanks for the help or recommendation
I think using templates might make it easier and cleaner to modify the elements. Here is a quick guide for basic templating with vanillajs https://gomakethings.com/html-templates-with-vanilla-javascript/
This allows you to easily pass in IDs for your inputs.
I am not sure if you are just trying to toggle a second row or add multiple rows. If you simply want to toggle the second row and not add more than that then only use the second part of the js, and remove the first template. Likewise if you want to add multiple you can remove the second part (currently commented out) of the js and the second template.
(function (){
// Interpolate function from https://gomakethings.com/html-templates-with-vanilla-javascript/
//Lets us pass a unique id to the template
function interpolate (str, params) {
let names = Object.keys(params);
let vals = Object.values(params);
return new Function(...names, `return \`${str}\`;`)(...vals);
}
//Using document on click as we are adding new buttons to the DOM and want the event to trigger on them as well
$(document).on('click', '.add-button', function () {
let id = $('.addRows').length + 1; //Use this for our row ID
let newRow = interpolate(row_template.innerHTML, {id}); //Generate our row html from the template
$(this).closest('.addRows').after(newRow); //Add the html to the table
});
//Remove button
$(document).on('click', '.remove-button', function () {
$(this).closest('.addRows').remove();
});
})();
//Use the below INSTEAD of the above for just the single extra toggling row.
/*(function (){
//Add new row from simple template
$(document).on('click', '.add-button', function () {
$("#TextBoxesGroup tbody").append(row_template_single.innerHTML);
});
//Remove the row
$(document).on('click', '.remove-button', function () {
$(this).closest('.addRows').remove();
});
})();*/
/*Style for red remove button*/
.remove-button {
background-color: #f77;
color: white;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<table id="TextBoxesGroup">
<tbody>
<tr class="addRows">
<td>Start Date:</td>
<td>
<input type="text" name="StartDate_1" class="picker" value="" size="6">
</td>
<td>End Date:</td>
<td>
<input type="text" name="EndDate_2" class="picker" value="" size="6">
</td>
<td>
<input type="button" id="addButton_1" class="add-button" value="Add row" />
</td>
</tr>
</tbody>
</table>
<!-- Template allowing to add multiple new rows with unique input names via id passed -->
<template id="row_template">
<tr class="addRows">
<td>Start Date:</td>
<td>
<input type="text" name="StartDate_${id}" class="picker" value="" size="6">
</td>
<td>End Date:</td>
<td>
<input type="text" name="EndDate_${id}" class="picker" value="" size="6">
</td>
<td>
<input type="button" id="addButton_${id}" class="add-button" value="Add row" />
<input type="button" class="remove-button" value="Remove row" />
</td>
</tr>
</template>
<!-- Template for just 'toggling' a second row -->
<!-- <template id="row_template_single">
<tr class="addRows">
<td>Start Date:</td>
<td>
<input type="text" name="StartDate_2" class="picker" value="" size="6">
</td>
<td>End Date:</td>
<td>
<input type="text" name="EndDate_2" class="picker" value="" size="6">
</td>
<td>
<input type="button" class="remove-button" value="Remove row" />
</td>
</tr>
</template> -->
I noticed my previous answer did not properly handle adding items in-between other items, i.e. not at the end of the list.
The following will better handle adding and removing items, while keeping the ids in order. This instead renders the fields based on the data we keep and manage in JavaScript.
(function () {
$(document).ready(function () {
field_data.init()
})
let field_data = {
data: [],
init: function () {
this.cacheDom();
this.bindEvents();
this.data.push(this.getItem());
this.renderData();
},
cacheDom: function () {
this.$render_container = $('#render_container');
this.row_template_html = $('#row_template').html();
},
bindEvents: function () {
$(document).on('click', '.remove-button', this.removeItem);
$(document).on('click', '.add-button', this.addItem);
this.$render_container.on('change', 'input', this.inputChange);
},
//When an item gets added, add new empty item to the data and re-render.
addItem: function () {
let target = parseInt($(this).attr('data-target'));
field_data.data.splice(target+1, 0, field_data.getItem());
field_data.renderData();
},
//When an item gets removed, remove it from the data and re-render.
removeItem: function () {
let target = parseInt($(this).attr('data-target'));
if (field_data.data.length > 1) { //Prevent from removing last item.
field_data.data.splice(target, 1);
field_data.renderData();
}
},
//Get a new/empty item.
getItem: function () {
return {
start_date: '',
end_date: '',
}
},
//Update the data when a value of an input changes
inputChange: function () {
let $this = $(this);
let id = parseInt($this.attr('data-id'));
let target = $this.attr('data-target');
field_data.data[id][target] = $this.val();
},
//Render the data according to the template.
renderData: function () {
let html = '';
for (let i = 0; i < field_data.data.length; i++) {
//Generate our row html from the template
html += field_data.getRowTemplate(
{
id: i,
start_date: field_data.data[i].start_date,
end_date: field_data.data[i].end_date,
}
);
}
field_data.$render_container.html(html);
},
//Gets the html for a single row based on our template
getRowTemplate: function (params) {
let names = Object.keys(params);
let values = Object.values(params);
return new Function(...names, `return \`${field_data.row_template_html}\`;`)(...values);
},
}
})();
.remove-button {
background-color: #f77;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="TextBoxesGroup">
<tbody id="render_container">
</tbody>
</table>
<template id="row_template">
<tr class="addRows">
<td>Start Date:</td>
<td>
<input type="text" name="StartDate_${id}" data-id="${id}" data-target="start_date" class="picker" value="${start_date}" size="6">
</td>
<td>End Date:</td>
<td>
<input type="text" name="EndDate_${id}" data-id="${id}" data-target="end_date" class="picker" value="${end_date}" size="6">
</td>
<td>
<input type="button" class="add-button" data-target="${id}" id="addButton_${id}" value="Add row"/>
<input type="button" class="remove-button" data-target="${id}" value="Remove row"/>
</td>
</tr>
</template>

Clone tr and checkbox event in jQuery

I have to clone my <tr> and I have list of checkbox like code below and when I add new row with list of checkbox and then I click on check box to show value in textbox field_resultsthe value not show only on first textbox not yet clone.
How when I add new tr and then when I click on which list of checkbox in which tr they will show value of my click in the same tr.
$("#add-new").on("click", function () {
$tr = $(this).closest("tr").next().clone(true);
$tr.insertAfter($(this).closest("tr"));
});
$(document).ready(function () {
$checks = $('#mych_box :checkbox');
$checks.on('change', function () {
var string = $checks.filter(":checked").map(function (i, v) {
return this.value;
}).get().join(",");
console.log(string);
$('#field_results').val(string);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<tr>
<td>
<button type="button" id="add-new">Add New</button>
</td>
</tr>
<tr>
<td>
<input type="text" id="field_results" name="field_results[]"/><br>
<div class="multiselect" style="height: 100px;width: auto;" id="mych_box">
<label>
<input type="checkbox" id="virt_software_chb1" name="virt_software[]" value="White"/>White
<input type="checkbox" id="virt_software_chb2" name="virt_software[]" value="Red"/>Red
<input type="checkbox" id="virt_software_chb3" name="virt_software[]" value="Blue"/>Blue
</label>
</div>
</td>
</tr>
As defined above use true in clone to bind default events and use class instead of id to group element
$(".virt_software_chb").on('change', function () {
var string = $(this).closest('td').find('.virt_software_chb').filter(":checked").map(function (i, v) {
return this.value;
}).get().join(",");
$(this).closest('td').find('.field_results').val(string);
});
$("#add-new").on("click", function () {
$tr = $(this).closest("tr").next().clone(true);
$tr.insertAfter($(this).closest("tr"));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td>
<button type="button" id="add-new">Add New</button>
</td>
</tr>
<tr>
<td>
<input type="text" class="field_results" name="field_results[]"/><br>
<div class="multiselect" style="height: 100px;width: auto;" id="mych_box">
<label>
<input type="checkbox" class="virt_software_chb" name="virt_software[]" value="White"/>White
<input type="checkbox" class="virt_software_chb" name="virt_software[]" value="Red"/>Red
<input type="checkbox" class="virt_software_chb" name="virt_software[]" value="White"/>White
</label>
</div>
</td>
</tr>
</table>
withDataAndEvents (default: false)
A Boolean indicating whether event handlers should be copied along with the elements. As of jQuery 1.4, element data will be copied as well. - clone()
Try passing in true and see what you get.
$tr = $(this).closest("tr").next().clone(true);

View data binding in nested dynamic observableArray

I need to put observableArray with dynamic size inside observableArray and bind data to View.
View is render values from newData, but not update it in newData, when i edit it in View.
ko.applyBindings(new (function () {
var self = this;
self.lengthNewData = ko.observable(2);
self.newData = ko.observableArray();
self.AddDataStrings = function () {
let newString = ko.observableArray();
for(let i0 = 0; i0 < self.lengthNewData(); i0++)
newString.push(i0);
self.newData.push(newString);
}
self.SendData = function () {
alert(ko.toJSON(self.newData));
}
})());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<span data-bind="visible: !newData().length">Number of rows: <input data-bind="value: lengthNewData"/></span>
<input type="button" value="Add" data-bind="click: $root.AddDataStrings"/>
<input type="button" value="Send" data-bind="visible: newData().length, click: $root.SendData"/><br>
<table>
<tbody data-bind="foreach: newData">
<tr data-bind="foreach: $data">
<td><input data-bind="value: $data"/></td>
</tr>
</tbody>
</table>
https://jsfiddle.net/tvxyyzkp/6/
Push Add, edit it and push Send. Data was not modified!
Why and What need to change?
There are two immediate mistakes in your code.
You do not push observables into your newString. You should do:
newString.push(ko.observable(i0));
You bind your inputs to $data. $data contains the unwrapped data value, i.e. it's not observable. Bind to $rawData in such a setup.
This works:
ko.applyBindings(new(function() {
var self = this;
self.lengthNewData = ko.observable(2);
self.newData = ko.observableArray();
self.AddDataStrings = function() {
let newString = ko.observableArray();
for (let i0 = 0; i0 < self.lengthNewData(); i0++)
newString.push(ko.observable(i0));
self.newData.push(newString);
}
self.SendData = function() {
alert(ko.toJSON(self.newData));
}
})());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<span data-bind="visible: !newData().length">Number of rows: <input data-bind="value: lengthNewData"/></span>
<input type="button" value="Add" data-bind="click: AddDataStrings" />
<input type="button" value="Send" data-bind="visible: newData().length, click: SendData" /><br>
<table>
<tbody data-bind="foreach: newData">
<tr data-bind="foreach: $data">
<td><input data-bind="value: $rawData" /></td>
</tr>
</tbody>
</table>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

How to append an inline template element with modified (id) content to another element?

Below in the example, I want that each time when the add button is clicked to take the element inside the template div and append it to the landingzone class element. But at the same time I need the NEWID to change for the new element. Of course this is just an example, the table stuff can be a div or anything else.
the form:
<form method="post">
<input type="text" name="title">
<input type="text" name="number">
<table>
<thead>
<tr> <th>Parts</th> </tr>
</thead>
<tbody class="landingzone">
</tbody>
</table>
<input type="submit" value="Save">
<input type="button" name"add" class="add" value="Save">
</form>
the template:
<div class="template" style="display: hidden">
<tr id="NEWID">
<td>
<input type="text" name="part_NEWID">
</td>
</tr>
</div>
What would be the best way to accomplish this?
Here's an example for your need. The javascript will work without changing any html except in place of name"add" should be name="add"
What i have done here is i'm getting the id of the template tr and setting it with increment and also the input field name.
var $landingzone = $('.landingzone');
var $add = $('.add');
var desiredId = 'id';
$add.on('click', function() {
var $template = $('.template').find('tr');
var id = $template.attr('id');
var idArr = id.split('-');
if (!idArr[1]) {
id = desiredId + '-1';
} else {
id = desiredId + '-' + (parseInt(idArr[1]) + 1);
}
$template.attr('id', id);
$template.find('input').attr('name', 'part_'+id);
console.log('input id--->'+id, 'input name--->'+'part_'+id);
$landingzone.append($template.clone());
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form method="post">
<input type="text" name="title">
<input type="text" name="number">
<table>
<thead>
<tr>
<th>Parts</th>
</tr>
</thead>
<tbody class="landingzone">
</tbody>
</table>
<input type="submit" value="Save">
<input type="button" name="add" class="add" value="Add">
</form>
<table class="template" style="display: none">
<tr id="NEWID">
<td>
<input type="text" name="part_NEWID">
</td>
</tr>
</table>
Like #Andrea said in her comment, some more details would be appreciated ...
I think what you are after is:
const $template = $('.template').clone()
$template.attr('id', 'someId')
$template.find('input[name="part_NEWID"]').attr('name', 'part_someId')
$('.landingzone').append($template)
And if you need it in a function:
function appendTemplateToLandingZone (newId) {
const $template = $('.template').clone()
$template.attr('id', newId)
$template.find('input[name="part_NEWID"]').attr('name', 'part_' + newId)
$('.landingzone').append($template)
}
I haven't tested this, so it might need a slight adjustment. If you'll provide a basic jsbin I'll make it work there.

HTML Table - Iterate over row and sum the fields

I have then following table:
<table style="width:100%" id="testTable">
<tr>
<th>length per</th>
<th>width per</th>
<th>length</th>
<th>width</th>
<th>total</th>
</tr>
<tr align='right'>
<td>
<input type="text" name="length-per-input">
</td>
<td>
<input type="text" name="width-per-input">
</td>
<td>
<input type="text" name="length-total-input">
</td>
<td>
<input type="text" name="width-total-input">
</td>
<td>
<input type="text" name="total-output" disabled="disabled">
</td>
</tr>
<tr align='right'>
<td>
<input type="text" name="length-per-input">
</td>
<td>
<input type="text" name="width-per-input">
</td>
<td>
<input type="text" name="length-total-input">
</td>
<td>
<input type="text" name="width-total-input">
</td>
<td>
<input type="text" name="total-output" disabled="disabled">
</td>
</tr>
</table>
<input type=button value='+' onclick="addRow()" />
<input type=button value='Calculate' onclick="Calculate()" />
I also have the javascript which adds the value and puts it in total:
<script>
function Calculate() {
var lengthPerInput = $("input[name='length-per-input']").val();
var widthPerInput = $("input[name='width-per-input']").val();
var lengthTotal = $("input[name='length-total-input']").val();
var widthTotal = $("input[name='width-total-input']").val();
var total = (lengthTotal/lengthPerInput) + (widthTotal/widthPerInput);
$("input[name='total-output']").val(total);
}
</script>
The aim here is to have it iterate over the two rows, then add each one separately.
I know how to get each row by using:
$('#testTable tr').each(function(){
console.log(this);
$(this).find('length-per-input').each(function(){
console.log(this);
})
})
But using the row (accessed via "this") I don't know how to get the correct cells, get their value, then perform the calculate on that row for the total.
Any advice on this please? Thank you!
function Calculate(tr_row) {
var lengthPerInput = tr_row.find("input[name='length-per-input']").val();
var widthPerInput = tr_row.find("input[name='width-per-input']").val();
var lengthTotal = tr_row.find("input[name='length-total-input']").val();
var widthTotal = tr_row.find("input[name='width-total-input']").val();
var total = (lengthTotal/lengthPerInput) + (widthTotal/widthPerInput);
tr_row.find("input[name='total-output']").val(total);
}
For every row you call function to summ the values
To the function you pass the row, then it can collect values on that row
$('#testTable tr').each(function(){
Calculate($(this))
})
You can use each() function to iterate through table and use find() function to find cell values.
function Calculate() {
$('#testTable tr').each(function() {
var lengthPerInput = $(this).find("input[name='length-per-input']").val();
var widthPerInput = $(this).find("input[name='width-per-input']").val();
var lengthTotal = $(this).find("input[name='length-total-input']").val();
var widthTotal = $(this).find("input[name='width-total-input']").val();
var total = (lengthTotal/lengthPerInput) + (widthTotal/widthPerInput);
$(this).find("input[name='total-output']").val(total);
});
}
Working Plunker
How to get a table cell value using jQuery?

Categories