When I try to access the values of the newly created elements, I get the function body as the variable values.
In the Fiddle,
Click "Add Name"
Enter First Name as "Kate" in the newly added row.
Click "Show All Names" button.
In the popup, function body gets displayed instead of "Kate". I am able to use the first element using name.FirstName and the rest of the elements using name.FirstName().
Is there any consistent way to get the value of FirstNamein this scenario (across the loop iterations)?
My HTML code is:
<table>
<tbody data-bind="foreach: names">
<tr>
<td>
<label>First Name:</label>
</td>
<td>
<input data-bind="value: FirstName" type="text">
</td>
<td>
<label>Last Name:</label>
</td>
<td>
<input data-bind="value: LastName" type="text">
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addName">Add Name</button>
<button data-bind="click: showAllNames">Show All Names</button>
Javascript Code:
var namesArray = [{
"FirstName": "Tom",
"LastName": "Langdon"
}];
var ViewModel = function () {
var self = this;
self.names = ko.observableArray(namesArray);
self.CreateBlankName = function () {
return {
FirstName: ko.observable(""),
LastName: ko.observable("")
};
};
self.addName = function () {
names.push(CreateBlankName());
};
self.showAllNames = function () {
var namestring = "";
ko.utils.arrayForEach(self.names(), function (name) {
namestring += name.FirstName + "\n";
});
alert(namestring);
};
};
ko.applyBindings(ViewModel);
The fisrtname and lastName fields should be observables because you can changed through the UI.
So you need to turn these into ko.observables as follow :
var namesArray = [{
"FirstName": ko.observable("Tom"),
"LastName": ko.observable("Langdon")
}];
Now items will only have observables. That's why you need to change the showAllNames function :
ko.utils.arrayForEach(self.names(), function (name) {
namestring += name.FirstName() + "\n";
});
See fiddle
You can also automatically convert raw js object into observables object by using ko.mapping.fromJS function
Related
I'm trying to create a functional wages table using html and javascript that calculates the wage for each employee, according to input of each row.
To do this I have generated a different id for the total cell in each row of the table using a loop. The problem is that when i try to access each id in the loop that creates the table, the javascript function uses the last id name of the table created, instead of using the current id from the row that the function is called.
How can i get the correct id using only javascript and html?
This is my html code:
#foreach (var item in Model.CurrentUnpaidWages)
{
<tr>
#{ id = Convert.ToString(Model.Employees[i].PersonID);}
<td>
#Model.Employees[i].FirstName #Model.Employees[i].LastName
</td>
<td id="WagePerHour">
#Html.DisplayFor(modelItem => item.WagePerHour)
#{wagePerHour = Convert.ToDecimal(item.WagePerHour);}
</td>
<td>
#{ id = Convert.ToString(Model.Employees[i].PersonID);}
<div class="form-group">
<div class="col-md-10">
<input oninput="GetWagePerMonth(this)" id=#id type="number"
min="1" />
#Html.ValidationMessageFor(model => model.Wage.HoursPerMonth,
"", new { #class = "text-danger" })
</div>
</div>
</td>
<td><div id="d"#id></div></td>
<td> #Html.DisplayFor(modelItem => item.Bonus)</td>
<td>#Html.DisplayFor(modelItem => item.Paid)</td>
#{i++;}
<td></td>
</tr>
This is my function:
function GetWagePerMonth(idx) {
var h =idx.id;
alert( "d" + idx.id);
var wage = document.getElementById("WagePerHour").innerHTML;
var wagePerHour = #wagePerHour;
var wagePerMonth = wage * wagePerHour;
wagePerMonth = round(wagePerMonth, 2).toFixed(2);
document.getElementById("d" + idx.id).innerHTML = wagePerMonth;
}
Thanks in advance!!
Edit: I've edited my code. See above.
Now I'm getting the correct id each time in an alert, but the function is not changing the text of the div id=#id. The div is staying empty. I would like that for each time I enter an amount into the input of :
then the correct text should be entered into the div id=#id according to the function. For now, nothing is happening.
<input oninput="GetWagePerMonth(this)" id=#id type="number" min="1" />
make <td id=#id"></td > into <div id="d"#id></div >
and your function would access the id as
function GetWagePerMonth(idx) {
alert(idx.id); /* */
var wage = document.getElementById("WagePerHour").innerHTML;
var wagePerHour = #wagePerHour;
var wagePerMonth = wage * wagePerHour;
wagePerMonth = round(wagePerMonth, 2).toFixed(2);
document.getElementById("d"+idx.id).innerHTML = wagePerMonth;
}
USe this instead and see if you get any response.
function GetWagePerMonth(idx) {
alert(idx.id); /* */
document.getElementById("d"+idx.id).innerHTML = "Test Value";
}
Hope this helps you get to a solution that you can improvise
edit:
create a new function
function CallMyFunc(){
for(i=0,i < 10, i++){
GetWagePerMonth(i) ;
}
}
In the HTML part , lets see if you can create a button to call that function
<input type = "button" onClick="javascript:CallMyFunc()"/>
I have some text field for send to popup window ( I want send productPrice and productName) :
<tr>
<form>
<td> ${product.getProductName()}</td>
<input type="hidden" name="productName" value=${product.getProductName()}>
<td>${product.getPrice()}</td>
<input type="hidden" name="productPrice" value=${product.getPrice()}>
<td>
<input type="button" onclick="makeOrder()" value="Make order"/>
</td>
</form>
</tr>
JS:
<script type="text/javascript">
function makeOrder(){
window.open("/user/makeOrder","","height=250,width=400,status=no,location=no,toolbar=no,directories=no,menubar=no");
}
How can I pass a variable ProductName and ProductPrice to popup window "/user/makeOrder" ?
You can pass ProductName and ProductPrice as query string to new location, then use search, .split(), while loop to set properties, values to an object.
function makeOrder(name, price){
window.open("/user/makeOrder?ProductName=" + name + "&ProductPrice=" + price,""
,"height=250,width=400,status=no,location=no,toolbar=no,directories=no,menubar=no");
}
At newly opended window
var params = location.search.split(/\?|=|&/).filter(Boolean);
var obj = {}, i = 0;
while (i < params.length) {
obj[params[i]] = params[++i];
++i;
}
console.log(obj)
I want to build a HTML page that allows the user to construct an object that can then be posted as JSON to an internally hosted service (similar to the Chrome Advanced Rest Client). The user must be able to add and remove properties.
My model is not correct because each property is treated like an object with the properties 'name' and 'value'. I end up with an array of objects instead of an object with properties.
Here is a snippet of the HTML:
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="value: name" /></td>
<td><input data-bind="value: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: ko.toJSON(myFieldList)"></textarea>
Here is the JS:
<script type="text/javascript">
function dynamicProperty(name, value) {
var self = this;
this.name = name;
this.value = value;
}
function fieldModel() {
var self = this;
//start with 2 empty properties
self.myFieldList = ko.observableArray([
new dynamicProperty("", ""),
new dynamicProperty("","")
]);
var noTracker = self.myFieldList.length;
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty(this.name,this.value));
}
}
ko.applyBindings(fieldModel);
</script>
What I get in the textarea is output like this:
[{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}]
What I want is output like this:
{"test name 1":"test value 1","test name 2":"test value 2"}
I fear this is rather trivial but in my defense I am very new to JS and Knockout, so any help you can offer would be hugely appreciated. Thanks.
You may want to do something like this to get it done .
Example : https://jsfiddle.net/9aLvd3uw/79/
HTML
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="textInput: name" /></td>
<td><input data-bind="textInput: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: myFieldList2"></textarea>
JS
function dynamicProperty(name, value) {
var self = this;
self.name = ko.observable(name || '');
self.value = ko.observable(value || '');
}
function fieldModel() {
var self = this;
self.name = ko.observable();
self.value = ko.observable();
self.myFieldList = ko.observableArray([
new dynamicProperty("test_name_1", "test value 1"),
new dynamicProperty("test_name_2","test value 2")
]);
var noTracker = self.myFieldList.length;
self.myFieldList2 = ko.computed(function () {
var string = '{';
ko.utils.arrayForEach(self.myFieldList(), function (item) {
string += item.name() + ': ' + item.value() + ',';
});
string = string.replace(/,\s*$/, "");
string+='}';
return string;
});
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty('',''));
}
}
ko.applyBindings(fieldModel);
What you need is a "reducer".
A simple (naive too) implementation would be this:
function reduce(input, step, init) {
for(var i = 0; i < input.length; i++) {
init = step(init, input[i]);
}
return init;
}
Then you call it like this:
var in = [{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}];
var out = reduce(in, function(result, item) {
result[item.name] = item.value;
return result;
}, {});
console.log(out);
What it does is that it iterates through your array and "accumulates" the result of each step in a single item. Could be the sum of numbers in an array where the "accumulator" would be a number instead of an object.
I advise you don't write your own but instead use lodash, it comes with a _.reduce function that's optimized.
I have three fields
1. Quantity
2. Unit Price
3. Total Cost
I am using knockout JS to calculate total cost in real time. This works fine when adding a new record. However when I edit the record, I want the quantity and unit price to be prepopulated by their value in the database when the page first loads. I have tried the code below which prepopulates the quantity and unit price but the Total Cost result does not update and appears as blank now. Here is the code
<tr>
<td>
<div class="form-group">
<label asp-for="Quantity" class="col-md-2 control-label"></label>
<div class="col-md-10">
<kendo-numerictextbox name="Quantity" min="0" enable="true" data-bind="value: Quant">
</kendo-numerictextbox>
<span asp-validation-for="Quantity" class="text-danger"/>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="form-group">
<label asp-for="UnitPrice" class="col-md-2 control-label"></label>
<div class="col-md-10">
<kendo-numerictextbox name="UnitPrice" min="0" enable="true" data-bind="value: UPrice">
</kendo-numerictextbox>
<span asp-validation-for="UnitPrice" class="text-danger"/>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="form-group">
<label asp-for="TotalCost" class="col-md-2 control-label"></label>
<div class="col-md-10">
<span data-bind="text: TotalCost"> </span>
</div>
</div>
<script>
var someJSON = $.getJSON("/Expenses/EditUSExpense", function(data) {});
var parsed = JSON.parse(someJSON);
// Update view model properties
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function(quantity, unitPrice, Quantity, UnitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function() {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(ViewModel);
</script>
</td>
</tr>
UPDATE
I have modified my code per suggestions below and appear to be cleanly pulling the Quantity and UnitPrice from the database but the computed field TotalCost is still displaying null and when I change the other two parameters the value does not change. Here is my modified code so someone can take a look. I am using the same razor code just changed the javascript.
$.getJSON("/Expenses/EditUSExpense", function (data) {
var parsed = JSON.parse(data);
// Update view model properties
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function (quantity, unitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function () {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(new ViewModel(quantity, unitprice));
});
In your code, you're creating a function that accepts four arguments:
var ViewModel = function(quantity, unitPrice, Quantity, UnitPrice) {
I don't know why four because in the code you posted you're only using the first two.
So if, you're expecting the first two values as arguments you should do this:
var quantity = parsed.Quantity;
var unitprice = parsed.UnitPrice;
var ViewModel = function(quantity, unitPrice) {
this.Quant = ko.observable(quantity);
this.UPrice = ko.observable(unitPrice);
this.TotalCost = ko.pureComputed(function() {
if (isNaN(this.Quant() * this.UPrice())) {
return 0;
}
return this.Quant() * this.UPrice();
}, this);
};
ko.applyBindings(new ViewModel(quantity, unitprice));
This:
var someJSON = $.getJSON("/Expenses/EditUSExpense", function(data) {});
var parsed = JSON.parse(someJSON);
Should be:
$.getJSON("/Expenses/EditUSExpense", function(data) {
var parsed = JSON.parse(data);
// Rest of code...
});
And this:
ko.applyBindings(ViewModel);
Should be:
ko.applyBindings(new ViewModel(quantity, unitprice));
Alright i have working code that removes a selected row(s) via a checkbox being checked. However i am running into the issue of enforcing that only one of the radio buttons can be checked at any given moment. My first approach is to tie a click event to the each radio button and if it gets clicked, it loops through the observable array and marks all "false." Then it simply flips the flag to true for the item that fired the event. I know this isn't the best way but my lack luster knowledge of knockout is forcing me down this path..even though this method doesn't work atm. Can anyone shed light on what i am doing wrong or how to properly wire this up?
The html for the table
<table class="accountGroups information" id="tblAccountGroups">
<tr>
<td width="125px;" style="font-weight: bold;">StandardAccountNo</td>
<td width="125px;" style="font-weight: bold; text-align: center;">Primary</td>
<td style="font-weight: bold;">Effective Date</td>
<td style="font-weight: bold;">End Date</td>
<td style="font-weight: bold;">Remove</td>
</tr>
<!-- ko foreach: NewAccountGroupDetails-->
<tr id="Model.NewAccountGroupDetails[0].AccountGroupName" class="acctgrp-row">
<td>
<div>
<input style="width: 100%;" data-bind="value: StandardAccountNo, attr: {name: 'NewAccountGroupDetails[' + $index() + '].StandardAccountNo'}" />
</div>
</td>
<td>
<div style="text-align:center;">
<input style="width:100%;" type="radio" data-bind="value: IsPrimary, attr: {name: 'NewAccountGroupDetails[' + $index() + '].IsPrimary'}, click: $parent.markIsPrimary" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EffectiveDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EffectiveDate'}" readonly="readonly" />
</div>
</td>
<td>
<div>
<input style="width:125px;" class="datepicker" data-bind="value: EndDate, attr: {name: 'NewAccountGroupDetails[' + $index() + '].EndDate'}" readonly="readonly" />
</div>
</td>
<td>
<div style="text-align:center;">
<input type="checkbox" data-bind="checked: markedForDeletion, attr: {name: 'NewAccountGroupDetails[' + $index() + '].MarkedForDeletion'}" />
</div>
</td>
</tr>
<!-- /ko -->
</table>
The JS below powers the page
////VIEW MODEL FOR KNOCKOUT////
var Detail = function () {
this.StandardAccountNo = ko.observable('');
this.IsPrimary = ko.observable(false);
this.EffectiveDate = ko.observable(formattedDate(new Date()));
this.EndDate = ko.observable(formattedDate(new Date()));
this.markedForDeletion = ko.observable(false);
};
var ViewModel = function () {
var rawList = '#Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model.NewAccountGroupDetails))';
this.NewAccountGroupDetails = ko.observableArray(convertJSONToKoObservableObject($.parseJSON(rawList)));
this.NewAccountGroupDetails.push(new Detail());
this.deleteMarkedItems = function () {
this.NewAccountGroupDetails.remove(function (item) {
return item.markedForDeletion();
});
};
this.markIsPrimary = function () {
for (i = 0; this.NewAccountGroupDetails().length > 0; i++) {
this.NewAccountGroupDetails[i].IsPrimary(false);
}
return item.IsPrimary(true);
};
this.addNew = function () {
this.NewAccountGroupDetails.push(new Detail());
$('.datepicker').each(function (i, obj) {
$(obj).datepicker({ changeYear: true, changeMonth: true });
});
}
};
ko.applyBindings(new ViewModel());
function convertJSONToKoObservableObject(json) {
var ret = [];
$.each(json, function (i, obj) {
var newOBJ = {};
for (prop in obj) {
newOBJ[prop] = ko.observable(obj[prop]);
}
ret.push(newOBJ);
});
return ret;
}
Once i have the page working the way i want it to, i'll look into syntax improvements such as ko mapping library for the array.
In your view model, construct the remove button like this:
viewModel.remove = function (row) {
console.log(row);
viewModel.NewAccountGroupDetails.remove(row);
};
Now, the current context is passed as the first argument to any callback in knockout. Therefore, if you add a button with data-bind="click: $parent.remove", it will call the viewModel.remove function with the row context.
<tr ...>
...
<td>
<button data-bind="click: $parent.remove">Remove</button>
</td>
</tr>
I'd need some extra information, but let me show you an example, and give you a few advices:
First, the advices:
in order to convert your regular object in an object with observable properties an arrays you can use the Knockout Mapping plugin.
you can omit the step of parsing the JSON. You can simply assigng the JSON to a var, like this: var JSON=*your serialized JSON*; (Don't forget the semicolon at the end.
instead of including so many code in the data-bind, like this: NewAccountGroupDetails['+ $index() + '].EndDate, do this calculation on the viewmodel itself, an use a computed named, for example EndDateName
your viewmodel should include a selectedRow observable. When the user selects the row, put the row there, and you can use a computed observable that determines if a row is the selected row or not.
take into account that you can bind events that invoke functions in your code, and this events carry the data associated to the DOM object that originated the event. I.e. if the users clicks a row associated to a account group detail, you'll receive it in the event.
Example for 2:
// Instead of:
var viewModelJson = '[{"name": "Pepe"},{"name":"Juan"}]';
var viewModel = $.parseJSON(viewModelJson);
// Do this directly:
var people = [{"name": "Pepe"},{"name":"Juan"}];
As 4 and 5 are not clear at once, this is a simple sample of what you want to achieve.
<ul data-bind="foreach: people">
<li data-bind="text: name, click: $root.select,
css: {red: $data == $root.selectedPerson()}" >
</li>
</ul>
NOTE that the css class red is applied when the condition true. And the condition is that the value bound to the current row is the same as the value in the selectedPerson observable.
And this is the corresponding JavaScript (remember to reference knockout mapping!!)
var people = [{"name": "Pepe"},{"name":"Juan"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // event receives the current data as 1st param
self.selectedPerson(person);
}
self.delete = function(person) {
// find de index of person and remove 1 item from that index
self.people.splice(self.people.indexOf(person),1);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
You can run the jsfiddle here.
If you change the click binding to invoke $root.delete instead of $root.select, you'll see the person dissapear from the list when clicking it. Of course, you can add an extra element to do so.
NOTE: you can read the docs on click binding on knockout js site.
And a last advice: it's much better to use Web API, or a method returning a JsonResult to recover the data directly from the server, and keep the js on a separate file.
UPDATE
A little bit mode code.
You can add this HTML:
<input type="button" data-bind="click: removeSelected" value="removeSelected"/>
And this method in the view model:
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
If you do so, when clicking the button, if there is a selected item, it will be removed from the list.
UPDATE: Another, more comple example
Here you have a more complete example, in this fiddle, that includes the code below:
CSS:
body {
font-family: Arial;
}
.container {
margin: 10px 0;
border: solid 1px #ABF;
}
.container > div {
padding: 4px;
border: solid 1px #ABF;
position: relative;
}
.selected {
border: solid 1px #00A;
color: #00A;
background-color: #BCF;
}
HTML:
<div data-bind="foreach: people" class="container">
<div data-bind="click: $root.select,
css: {selected: $data == $root.selectedPerson()}" >
<!-- ko text: name --><!-- /ko -->
<input type="button" value="Remove"
style="right:3px;top:2px; position:absolute;"
data-bind="click:$root.delete"/>
</div>
</div>
<div data-bind="visible: selectedPerson()" >
<input type="button" data-bind="click: removeSelected" value="Remove Selected"/>
<input type="button" data-bind="click: unSelect" value="Deselect"/>
</div>
<div data-bind="visible: selectedPerson()" class="container">
<div>
Selected: <!-- ko text: selectedPerson().name --><!-- /ko -->
</div>
</div>
JavaScript:
var people = [{"name": "Pepe"},{"name":"Juan"},{"name":"Luis"},{"name":"Adolfo"}];
var PeopleModel = function(people) {
var self = this;
self.selectedPerson = ko.observable(); // This will hold the selected person
self.people = ko.mapping.fromJS(people); // Note ko.mapping!!
self.select = function(person) { // The event receives the current data as parameter
self.selectedPerson(person);
};
self.delete = function(person) {
// find de index of person and remove (splice) it from the observable array
self.people.splice(self.people.indexOf(person),1);
self.selectedPerson(null);
}
self.removeSelected = function() {
if (self.selectedPerson()) {
self.delete(self.selectedPerson());
}
};
self.unSelect = function() {
self.selectedPerson(null);
}
return self;
};
var peopleModel = new PeopleModel(people);
ko.applyBindings(peopleModel);
Try to temporarily save the selected row when you select it
function AccountGroupViewModel() {
var viewModel = this;
viewModel.selectedRow = null;
// ...
viewModel.selectRow = function (data) {
// ...
viewModel.selectedRow = data;
}
viewModel.remove = function () {
// ...
if (viewModel.selectedRow != null) {
this.NewAccountGroupDetails.remove(viewModel.selectedRow);
}
}
}