I just want to understand why does Knockout.js does not let me access part of the model data. Is it because I am binding the model to the div containing all the submodels (Form in this example) or I am thinking it wrong?
For example, in this jsfiddle http://jsfiddle.net/zv6hauft/1/
I want to save just fare information but leave out bus information. So even if i just pass "fare_lines" in the example, it shows me all the models in the console.
<!doctype html>
<script src="/javascript/knockout-3.3.0.js"></script>
<form id="transport_form" method="post">
<table class="table table-condensed required" data-bind='visible: bus_lines().length > 0'>
<thead>
<tr>
<th>Bus</th>
</tr>
</thead>
<tbody data-bind='foreach: bus_lines'>
<tr>
<td>
<input name="bus_date" type="text" class="bus_date" value=" " data-bind='value: bus_desc' required/>
</td>
<td><a href='#' id="bus_remove" data-bind='click: $parent.removeBusLine'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="bus_button" data-bind='click: addBusLine'>Add Bus</button>
</div>
<div id="fare_info_table">
<table data-bind='visible: fare_lines().length > 0'>
<thead>
<tr>
<th>Fare</th>
</tr>
</thead>
<tbody class="table required" data-bind='foreach: fare_lines'>
<tr>
<td>
<input id="fare_amnt" data-bind='value: fare_desc' required />
</td>
<td><a href='#' id="fare_remove" data-bind='click:$parent.remove_fare_line'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="fare_button" data-bind='click: add_fare_line'>Add fare</button>
</div>
</div>
</br>
</br>
<div class="control-group">
<div class="controls">
<button type='submit' data-bind="click: save_record">Submit</button>
</div>
</div>
</form>
</html>
<script>
//Bus Model
var Bus_model = function () {
var self = this;
self.bus_desc = ko.observable();
};
var fare_model = function () {
var self = this;
self.fare_desc = ko.observable();
}
var operations_bus_fare = function () {
var self = this;
//Study Schedule Operations
self.bus_lines = ko.observableArray([new Bus_model()]);
self.addBusLine = function () {
self.bus_lines.push(new Bus_model())
};
self.removeBusLine = function (Bus_line) {
self.bus_lines.remove(Bus_line)
};
//Fare operations
self.fare_lines = ko.observableArray([new fare_model()]);
self.add_fare_line = function () {
self.fare_lines.push(new fare_model())
};
self.remove_fare_line = function (fare_line) {
self.fare_lines.remove(fare_line)
};
self.save_record = function (fare_lines) {
var saveData2 = ko.toJSON(fare_lines);
console.log(saveData2);
alert(saveData2);
};
};
ko.applyBindings(new operations_bus_fare(), document.getElementById("transport_form"));
</script>
You can access part of ViewModel my doing like this
ViewModel:
self.save_record = function (data) { // we get entire vm here as param
var saveData2 = ko.toJSON(data.fare_lines); // access required part
console.log(saveData2);
alert(saveData2);
};
Working fiddle here
Related
Knockout observable value not updating on bootstrap Modal. In debug I can see that the value have been successfully added to observable. It just not updating/appearing on the modal DOM.
self.getSdsById = function (val) {
var ID = val.ID;
var operation = event.currentTarget.dataset['operation'];
$.ajax({
url: baseUrl + '/SdsView/getSdsByID',
cache: false,
type: 'GET',
contentType: 'application/json; charset=utf-8',
data: { 'ID': ID },
success: function (data) {
self.CopySdsDetail().RegistrationNo(data.Sds.RegistrationNo);
self.CopySdsDetail().MSDSIssueDate(data.Sds.MSDSIssueDate);
self.CopySdsDetail().Place(data.Sds.Place);
self.CopySdsDetail().Dept(data.Sds.Dept);
self.CopySdsDetail().Div(data.Sds.Div);
if (operation == 'COPY') {
$('#copyModalTitle').text('Copy & Add SDS to other Division');
$('#copyModal').modal();
}
}
}).fail(
function (xhr, textStatus, err) {
swal("Error", err, "error");
});
}
Modal :
<div class="modal fade" id="copyModal">
<div class="table-responsive" data-bind="with: CopySdsDetail">
<table class="table table-bordered table-sm">
<tr>
<td>Division: </td>
<td>Department: </td>
<td>Place: </td>
</tr>
<tr>
<td><input type="text" data-bind="value: Div"/></td>
<td><input type="text" data-bind="value: Dept"/></td>
<td><input type="text" data-bind="value: Place"/></td>
</tr>
<tr>
<td data-bind="text: RegistrationNo" colspan="2"></td>
<td data-bind="text: MSDSIssueDate"></td>
</tr>
</table>
</div>
</div>
Ko observable value in debug:
Any idea why?
in the short time I had to look at this, it generally looks ok, I got a very basic sample working with what you have provided. maybe it will help
var data = {
Dept: "Dicing",
Div: "DCG",
ID: 0,
MSDSIssueDate: "/Date(1602172800000)/",
Place: "Etching Process",
RegistrationNo: "DS-033"
}
function ViewModel() {
var self = this;
self.CopySdsDetail = ko.observable(new ModalViewModel());
self.clearData = function(){
self.CopySdsDetail(new ModalViewModel());
}
self.getData = function() {
self.CopySdsDetail(new ModalViewModel(data));
// self.CopySdsDetail().RegistrationNo(data.RegistrationNo);
// self.CopySdsDetail().MSDSIssueDate(data.MSDSIssueDate);
// self.CopySdsDetail().Place(data.Place);
// self.CopySdsDetail().Dept(data.Dept);
// self.CopySdsDetail().Div(data.Div);
}
}
function ModalViewModel(data){
var self = this;
data = data || {};
self.RegistrationNo = ko.observable(data.RegistrationNo || "");
self.Dept = ko.observable(data.Dept || "");
self.Div = ko.observable(data.Div || "");
self.ID = ko.observable(data.ID || "");
self.MSDSIssueDate = ko.observable(data.MSDSIssueDate || "");
self.Place = ko.observable(data.Place || "");
}
var vm = new ViewModel();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="modal fade" id="copyModal">
<div class="table-responsive" data-bind="with: CopySdsDetail">
<table class="table table-bordered table-sm">
<tr>
<td>Division: </td>
<td>Department: </td>
<td>Place: </td>
</tr>
<tr>
<td><input type="text" data-bind="value: Div" /></td>
<td><input type="text" data-bind="value: Dept" /></td>
<td><input type="text" data-bind="value: Place" /></td>
</tr>
<tr>
<td data-bind="text: RegistrationNo" colspan="2"></td>
<td data-bind="text: MSDSIssueDate"></td>
</tr>
</table>
</div>
</div>
<button data-bind="click: getData">Get Data</button>
<button data-bind="click: clearData">Clear Data</button>
Why is .append() creating 2 identical rows in this code when I click Add New button? I don't see why 2 appends happen. Am I misunderstanding something? This doesn't happen with vanilla javascript but happens with jquery.
I added the table which includes the tbody tag at the end of the table where I would like to append the template string in function onAddProduct(e).
(Note: I removed html since it was an assignment.)
here is the code snippet
$(function() {
var $formEl = $('form');
var $tbodyEl = $('tbody');
var $tableEl = $('table');
function onAddProduct(e) {
e.preventDefault();
var $pName = $('#pname').val();
var $pCat = $('#pcat').val();
var $pPrice = $('#pprice').val();
$tbodyEl.append(`
<tr>
<td>${$pName}</td>
<td>${$pCat}</td>
<td>${$pPrice}</td>
<td><button class="deleteBtn">Delete</button></td>
</tr>
`);
}
function onDeleteRow(e) {
if (!e.target.classList.contains("deleteBtn")) {
return;
}
const btn = e.target;
btn.closest("tr").remove();
}
//formEl.addEventListener("submit", onAddProduct);
$formEl.on({
submit: onAddProduct
});
//tableEl.addEventListener("click", onDeleteRow);
$tableEl.on({
click: onDeleteRow
});
});
Consider the following.
$(function() {
var $formEl = $('form');
var $tbodyEl = $('tbody');
var $tableEl = $('table');
function onAddProduct(e) {
e.preventDefault();
var row = $("<tr>").appendTo($("tbody", $tableEl));
$("<td>").html($('#pname').val()).appendTo(row);
$("<td>").html($('#pcat').val()).appendTo(row);
$("<td>").html($('#pprice').val()).appendTo(row);
$("<td>").html("<button class='deleteBtn'>Delete</button>").appendTo(row);
return;
}
function onDeleteRow(e) {
if (!e.target.classList.contains("deleteBtn")) {
return;
}
if (confirm("Are you sure you want to delete this Product?")) {
$(e.target).closest("tr").remove();
}
}
$formEl.on({
submit: onAddProduct
});
$tableEl.on({
click: onDeleteRow
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>Add Product</h4><br>
<form>
<label for="pname">Product Name:</label>
<input type="text" id="pname" name="pname">
<label for="pcat">Product Category:</label>
<input type="text" id="pcat" name="pcat">
<label for="pprice">Product price:</label>
<input type="text" id="pprice" name="pprice">
<button type="submit" class="addBtn">Add New</button>
</form>
<table>
<thead>
<tr>
<th>Product Name</th>
<th>Product Category</th>
<th>Product Price</th>
<td> </td>
</tr>
</thead>
<tbody>
</tbody>
</table>
I am not able to replicate the issue with this code. Only 1 Row is added.
Your table is missing the <tbody> around the rows you have added, so the browser is adding it in to create a valid table. This results in 2 <tbody> elements, and is why rows are being added twice:
It can be prevented by putting the header and body rows inside the <thead> and <tbody> elements that the browser wants, seen below in the snippet -
$(function() {
var $formEl = $('form');
var $tbodyEl = $('tbody');
function onAddProduct(e) {
e.preventDefault();
var $pName = $('#pname').val();
var $pCat = $('#pcat').val();
var $pPrice = $('#pprice').val();
$tbodyEl.append(`
<tr>
<td>${$pName}</td>
<td>${$pCat}</td>
<td>${$pPrice}</td>
</tr>
`);
}
$formEl.on({
submit: onAddProduct
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="productTable" border="1">
<thead>
<tr>
<th>Product Name</th>
<th>Product Category</th>
<th>Product Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>M&M</td>
<td>Snacks</td>
<td>$1.99</td>
</tr>
<tr>
<td>Table</td>
<td>Furniture</td>
<td>$1.99</td>
</tr>
<tr>
<td>Kale</td>
<td>Vegetables</td>
<td>$2.49</td>
</tr>
</tbody>
</table>
<h4>Add Product</h4><br>
<form>
<label for="pname">Product Name:</label>
<input type="text" id="pname" name="pname">
<label for="pcat">Product Category:</label>
<input type="text" id="pcat" name="pcat">
<label for="pprice">Product price:</label>
<input type="text" id="pprice" name="pprice">
<button type="submit" class="addBtn">Add New</button>
</form>
first of all Im using Knockout js.
So I have a table that I can add and remove rows from it dynamically, my problem is that I want to add a click-to-edit in the table for each row but it doesn't work. once I add a second row Im not enable to edit. Here is my code, you can just copy and past it JSFiddle and it will explain further what Im saying.
Here is my code:
(function () {
var ViewModel = function () {
var self = this;
//Empty Row
self.items = ko.observableArray([]);
self.editing = ko.observable(true);
self.edit = function() { this.editing(true) }
self.addRow = function () {
self.items.push(new Item());
};
self.removeRow = function (data) {
self.items.remove(data);
};
}
var Item = function (fname, lname, address) {
var self = this;
self.firstName = ko.observable(fname);
self.lastName = ko.observable(lname);
self.address = ko.observable(address);
};
vm = new ViewModel()
ko.applyBindings(vm);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<table class="table table-bordered">
<thead class="mbhead">
<tr class="mbrow">
<th>Input</th>
<th>First Name</th>
<th>Last Name</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td>
<select class="form-control common-input-text" data-bind="event: { change: $root.addNewItem }">
<option value="">One</option>
<option value="">Two</option>
<option value="">Three</option>
</select>
</td>
<td>
<b data-bind="uniqueName: true,visible: !($parent.editing()), text: firstName, click: function() { $parent.editing(true) }"></b>
<input data-bind="uniqueName: true, visible: $parent.editing, value: firstName, hasFocus: $parent.editing" />
</td>
<td><span class="input-small" data-bind="value: lastName" /></td>
<td><span class="input-small" data-bind="value: address" /></td>
<td>
<input type="button" value="Remove Row" data-bind="click: $parent.removeRow" class="btn btn-danger" />
</td>
</tr>
</tbody>
</table>
<input type="button" value="Add Row" class="btn btn-primary" data-bind="click: addRow" />
thank you for your help
The problem lies in creating a new row that bounds an observable to hasFocus:
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing" /> <-- the problem cause
On row creation, the previously-focused row loses focus, which causes editing to be set to false.
So the solution would be to just use the observable value (instead of bounding the observable itself):
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing()" /> // <-- call the observable
But better yet is to add an observable into Item view model, called isFocused, and use it instead:
var Item = function (fname, lname, address) {
var self = this;
self.isFocused = ko.observable(true);
// ... other observables ...
};
<input data-bind="uniqueName: true,
visible: isFocused(),
value: firstName,
hasFocus: isFocused" />
I'm trying to get the Knockout foreach binding working in my code. I've done it many times before, but in this particular case I can't seem to get it right. I must be doing something wrong here, but I can't see it. I created a jsfiddle here: https://jsfiddle.net/9Lc144jv/
JavaScript:
var ReportModel = function (parent) {
var self = this;
self.parent = parent;
self.filters = ko.observableArray([]);
self.addFilter = function () {
self.filters.push({ logicOperator: 0, columnName: null, comparisonOperator: 0, value: null });
};
self.removeFilter = function () {
self.filters.remove(this);
};
};
var ViewModel = function () {
var self = this;
self.reportModel = false;
self.init = function () {
self.reportModel = new ReportModel(self);
};
};
var viewModel;
$(document).ready(function () {
viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.init();
});
HTML:
<body>
<table class="table table-bordered">
<thead>
<tr>
<th></th>
<th>Column</th>
<th>Operator</th>
<th>Value</th>
<th></th>
</tr>
</thead>
<tbody>
<!-- ko foreach: reportModel.filters -->
<tr>
<td><input type="text" class="form-control" data_bind="value: logicOperator" /></td>
<td><input type="text" class="form-control" data_bind="value: columnName" /></td>
<td><input type="text" class="form-control" data_bind="value: comparisonOperator" /></td>
<td><input type="text" class="form-control" data_bind="value: value" /></td>
<td>
<button type="button" class="btn btn-danger" data-bind="click: $parent.removeFilter">
Remove
</button>
</td>
</tr>
<!-- /ko -->
</tbody>
<tfoot>
<tr>
<td colspan="5" style="text-align: right;">
<button type="button" class="btn btn-primary" data-bind="click: reportModel.addFilter">
Add
</button>
</td>
</tr>
</tfoot>
</table>
</body>
UPDATE
A few notes:
I was using the wrong attribute: "data_bind" instead of "data-bind". I corrected it now and the updated code is here: https://jsfiddle.net/9Lc144jv/2/
It's still not working even though I made that fix
When I copied and pasted it to a plain .html file and ran it, I could see something interesting in Firebug:
As you can see, running the following script in the command window shows there is nothing in the collection before clicking Add, and then something afterwards:
JSON.stringify(viewModel.reportModel.filters());
So the new object is in the observable array, but it's not binding to the table in the foreach block. But, why? From what I can see, everything looks fine.. but maybe I need a fresh pair of eyes on this. Someone please show me what I am doing wrong here...
You are setting reportModel after the bindings plus I had to add the function call to the buttons() .
slight changes:
var ReportModel = function (parent) {
var self = this;
self.parent = parent;
self.filters = ko.observableArray([]);
self.addFilter = function () {
self.filters.push({ logicOperator: 0, columnName: null, comparisonOperator: 0, value: null });
};
self.removeFilter = function () {
self.filters.remove(this);
};
};
var ViewModel = function () {
var self = this;
self.reportModel = new ReportModel(self);
self.init = function () {
console.info(self);
//self.reportModel = new ReportModel(self);
};
};
var viewModel;
$(document).ready(function () {
viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.init();
});
HTML
<body>
<table class="table table-bordered">
<thead>
<tr>
<th></th>
<th>Column</th>
<th>Operator</th>
<th>Value</th>
<th></th>
</tr>
</thead>
<tbody>
<!-- ko foreach: reportModel.filters -->
<tr>
<td><input type="text" class="form-control" data_bind="value: logicOperator" /></td>
<td><input type="text" class="form-control" data_bind="value: columnName" /></td>
<td><input type="text" class="form-control" data_bind="value: comparisonOperator" /></td>
<td><input type="text" class="form-control" data_bind="value: value" /></td>
<td>
<button type="button" class="btn btn-danger" data-bind="click: $parent.removeFilter()">
Remove
</button>
</td>
</tr>
<!-- /ko -->
</tbody>
<tfoot>
<tr>
<td colspan="5" style="text-align: right;">
<button type="button" class="btn btn-primary" data-bind="click: reportModel.addFilter()">
Add
</button>
</td>
</tr>
</tfoot>
</table>
</body>
https://jsfiddle.net/vw2kngqq/
I need a little help.
I have created a table that gets values from JSON response, but for this example lets create a hardcoded html table like following:
<table id="devtable">
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
</tr>
<tr>
<td>001</td>
<td>Jhon</td>
<td>Single</td>
</tr>
<tr>
<td>002</td>
<td>Mike</td>
<td>Married</td>
</tr>
<tr>
<td>003</td>
<td>Marrie</td>
<td>Complicated</td>
</tr>
</table>
ID : <input type="text" name="ID" data-bind="value: ID" disabled/>
<br>
Name : <input type="text" name="Name" data-bind="value: Name" disabled/>
<br>
Status : <input type="text" name="Status" data-bind="value: Status" disabled/>
<br>
<input type="button" value="Send" disabled/>
Requirement is: when I select a row of table, values of columns goes to the input boxes and enable button as well. As I am trying to learn Knockout.js by doing this exercise. I think I have to make a viewmodel like this:
var rowModel = function (id, name, status) {
this.ID = ko.observable(id);
this.Name = ko.observable(name);
this.Status = ko.observable(status);
}
Link of project is here: http://jsfiddle.net/qWmat/
Here's an example of how you could do it:
http://jsfiddle.net/qWmat/3/
function MyVM(data) {
var self = this;
self.items = ko.observableArray(data.map(function (i) {
return new rowModel(i.id, i.name, i.status);
}));
self.select = function(item) {
self.selected(item);
};
self.selected = ko.observable(self.items()[0]);
}
And you bind your textboxes to the properties in the selected property:
<input type="text" name="ID" data-bind="value: selected().ID" disabled/>
And you bind the click handler in your tr like so:
<tr data-bind="click: $parent.select">
Updated to include enable binding (http://jsfiddle.net/qWmat/8/). Add a property for whether or not to edit:
self.enableEdit = ko.observable(false);
Then update your select function to turn it to true:
self.select = function(item) {
self.selected(item);
self.enableEdit(true);
};
If / when you save or cancel you could the set it back to false if you want.
Update your bindings on the input boxes:
<input type="text" name="Status" data-bind="value: selected().Status, enable: enableEdit" />
I've created a demo for you, but to know how it works, you should investigate knockout documentation.
ViewModel:
<table id="devtable">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="foreach: Items" >
<tr data-bind='click: $parent.setEditItem'>
<td data-bind="text: ID"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: Status"></td>
</tr>
</tbody>
</table>
<!-- ko with: SelectedItem -->
ID :
<input type="text" name="ID" data-bind="value: ID, attr: {disabled: !$parent.IsEditMode()}" />
<br>Name :
<input type="text" name="Name" data-bind="value: Name, attr: {disabled: !$parent.IsEditMode()}"/>
<br>Status :
<input type="text" name="Status" data-bind="value: Status, attr: {disabled: !$parent.IsEditMode()}"/>
<br>
<input type="button" value="Send" data-bind="attr: {disabled: !$parent.IsEditMode()}"/>
<!-- /ko -->
Html:
function ItemModel(id, name, status) {
var self = this;
self.ID = ko.observable(id);
self.Name = ko.observable(name);
self.Status = ko.observable(status);
}
function ViewModel() {
var self = this;
self.Items = ko.observableArray([
new ItemModel('001', 'Jhon', 'Single'),
new ItemModel('002', 'Mike', 'Married'),
new ItemModel('003', 'Marrie', 'Complicated')
]);
self.SelectedItem = ko.observable(new ItemModel());
self.IsEditMode = ko.observable();
self.setEditItem = function(item) {
self.SelectedItem(item);
self.IsEditMode(true);
}
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
Demo