I am trying to add rows and columns to a knockout grid on the client side after .NET MVC has displayed the html grid
I could find a way to dynamically add rows but am not able to get the grid to add columns and rows. Does anyone know how to add both rows and columns dynamically with knockout and then have the data in the grid (including the new row and column headers) to be available for saving to the server?
<form action='/controller/save'>
<p>There are <span data-bind='text: series().length'> </span> series set</p>
<table data-bind='visible: series().length > 0'>
<thead>
<tr>
<th>---</th>
<!-- ko foreach: cols -->
<th data-bind='text:title'></th>
<!-- /ko -->
<th><button> Add New Column</button></th>
</tr>
</thead>
<tbody data-bind='foreach: series'>
<tr>
<td data-bind='text:name'></td>
<!-- ko foreach: cols -->
<td><input class='required' /></td>
<!-- /ko -->
<td><a href='#' data-bind='click: $root.removeSeries'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: addNewSeries'>Add New Series</button>
<button data-bind='enable: series().length > 0' type='submit'>Save</button>
and the js
var ItemModel = function (series) {
var self = this;
self.series = ko.observableArray(series);
self.addNewSeries = function () {
self.series.push({
name: "",
value: ""
});
};
self.removeSeries = function (series) {
self.series.remove(series);
};
self.save = function (form) {
ko.utils.postJson($("form")[0], self.items);
};
};
var viewModel = new ItemModel([
{ name: "2012/13", value: "122345" },
{ name: "2013/14", value: "564543" }
]);
ko.applyBindings(viewModel);
var ColumnModel = function (cols) {
var self = this;
self.cols = ko.observableArray(cols);
var Col = function (title) {
var self = this;
self.title = ko.observable(title);
self.addNewCol = function () {
self.cols.push(new Col('new'));
};
};
};
var columnModel = new ColumnModel([
{ title: "col1" },
{ title: "col2" }
]);
ko.applyBindings(columnModel);
I have created a fiddle to show
you made a lot of mistakes there. you need to use the chrome\explorer\firefox tools to debug your stuff. i got it yo work though
<form action='/controller/save'>
<p>There are <span data-bind='text: series().length'> </span> series set</p>
<div data-bind='visible: series().length > 0'>
<table>
<thead>
<tr data-bind="foreach: cols">
<th data-bind='text:$data.title'></th>
</tr>
<button data-bind="click:addNewCol"> Add New Column</button>
</thead>
<tbody data-bind='foreach: series'>
<tr>
<td data-bind='text:name'></td>
<!-- ko foreach :$root.cols -->
<!-- ko if: $index() != 0 -->
<td><input class='required' /></td>
<!-- /ko -->
<!--/ko-->
<td><a href='#' data-bind='click: $root.removeSeries'>Delete</a></td>
</tr>
</tbody>
</table>
</div>
<button data-bind='click: addNewSeries'>Add New Series</button>
<input type="text" data-bind="value: title"/>
<button data-bind='enable: series().length > 0' type='submit'>Save</button>
</form>
$(document).ready(function(){
var ItemModel = function (series) {
var self = this;
self.series = ko.observableArray(series);
self.addNewSeries = function () {
self.series.push({
name: self.title(),
value: ""
});
};
self.title=ko.observable();
self.removeSeries = function () {
//do something with series
self.series.remove(this);
};
self.save = function (form) {
ko.utils.postJson($("form")[0], self.items);
};
self.cols = ko.observableArray([{ title: "col1" },
{ title: "col2" }]);
function Col (title) {
this.title = ko.observable(title);
};
self.addNewCol = function () {
self.cols.push(new Col('new'));
};
};
var viewModel = new ItemModel([
{ name: "2012/13", value: "122345" },
{ name: "2013/14", value: "564543" }
]);
ko.applyBindings(viewModel);
/* var ColumnModel = function (cols) {
};
var columnModel = new ColumnModel([
{ title: "col1" },
{ title: "col2" }
]);
ko.applyBindings(columnModel);
*/
// Activate jQuery Validation
// $("form").validate({ submitHandler: viewModel.save });
});
http://jsfiddle.net/dCfYP/23/
Related
Error :
"Uncaught Error: Unable to process binding "foreach: function() {return phones }"
Message: Invalid object that looks like an observable; possibly from another Knockout instance"
html :
<h3>Contacts</h3>
<div >
<table >
<thead>
<tr>
<th>first name</th>
<th>last name</th>
<th>number</th>
</tr>
</thead>
<tbody data-bind="foreach: contacts">
<tr>
<td>
<input data-bind="value :firstName" /><br />
delete contact
</td>
<td>
<input data-bind="value : lastName" />
</td>
<td>
<table>
<tbody data-bind="foreach : phones">
<tr>
<td><select data-bind="options : contactType , value : contactTypeValue"></select></td>
<td><input data-bind="value : number" /></td>
<td>delete number</td>
</tr>
</tbody>
</table>
add number
</td>
</tr>
</tbody>
</table>
<br /><br />
<button data-bind="click : addContact"> add contact</button>
</div>
javascript :
var contactsModel = function ()
{
var self = this;
this.contacts = ko.observableArray([]);
this.addContact = function ()
{
this.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray()
});
};
this.deleteContact = function (contact)
{
this.contacts.remove(contact);
};
this.addNumber = function (contact)
{
contact.phones.push({
type: "",
number: ""
});
};
this.deleteNumber= function (phone)
{
$.each(this.contacts(), function () { this.phones.remove(phone) })
};
}
ko.applyBindings(new contactsModel());
I am new to Knockout JS so can someone please give solution to above error.
I will note 2 things.
First of all, you declare the observableArray without an empty array in it, this might look like a simple case, yet knockout has an issue regarding array declarations, at least it had, up to some version.
So change to this:
this.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray([]) // here
});
Also there is a reason that you define self=this;, yet you never really use it.
Re-write your code like so:
var contactsModel = function ()
{
var self = this;
self.contacts = ko.observableArray([]);
self.addContact = function ()
{
self.contacts.push({
firstName: "",
lastName: "",
phones: ko.observableArray([])
});
};
self.deleteContact = function (contact)
{
this.contacts.remove(contact);
};
self.addNumber = function (contact)
{
contact.phones.push({
type: "",
number: ""
});
};
self.deleteNumber= function (phone)
{
$.each(self.contacts(), function () { self.phones.remove(phone) })
};
}
I wanted to get values from form fields and save them as an object into an observableArray. And show them in the table. So, every time i hit 'add' button the table should be updated but its not working.
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="$data.id"></td>
<td data-bind="$data.name"></td>
<td data-bind="$data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
this is my html. 'gradeList' is also an observableArray but its working and i'm getting nice dropdown menu. On last 'p' element, text gets updated with every 'add' button click with [Object object] text but table never gets updated.
var newModel = function () {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function () {
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
};
self.addMark = function () {
// console.log("button clicked");
self.mark({ "id": self.selectedGrade().Id, "name": self.selectedGrade().Name, "mark": self.komark() });
console.log(self.mark());
self.allMarks.push(self.mark());
console.log(self.allMarks());
};
self.loadAllGrades();
}
this is my javasript. The value of 'mark' and 'allMarks' gets updated in console but TABLE never gets updated.
<td data-bind="$data.id"></td> doesn't do anything, you haven't specified a binding. You probably wanted:
<td data-bind="text: $data.id"></td>
<!-- ----------^^^^^^ -->
...and similar for name, mark.
Working example:
var newModel = function() {
var self = this;
self.komark = ko.observable();
self.mark = ko.observable();
self.selectedGrade = ko.observable();
self.gradeList = ko.observableArray([]);
self.allMarks = ko.observableArray([]);
self.loadAllGrades = function() {
/*
$.ajax({
type: "GET",
dataType: "text",
url: "studenthandler.ashx",
data: { "action": "getAllGrades", "id": 0 },
success: function (res) {
self.gradeList(JSON.parse(res));
},
error: function () {
alert("Failed to load.\nHit Refresh.");
}
});
*/
self.gradeList.push(
{Id: 1, Name: "Grade1"},
{Id: 2, Name: "Grade2"},
{Id: 3, Name: "Grade3"}
);
};
self.addMark = function() {
// console.log("button clicked");
self.mark({
"id": self.selectedGrade().Id,
"name": self.selectedGrade().Name,
"mark": self.komark()
});
//console.log(self.mark());
self.allMarks.push(self.mark());
//console.log(self.allMarks());
};
self.loadAllGrades();
}
ko.applyBindings(new newModel(), document.body);
<select data-bind="options: gradeList, optionsText: 'Name', value: selectedGrade"></select>
<input type="text" data-bind="value: komark" />
<button data-bind="click: addMark">Add</button>
<table>
<thead>
<tr>
<th>SN</th>
<th>Name</th>
<th>Mark</th>
</tr>
</thead>
<tbody data-bind="foreach: allMarks">
<tr>
<td data-bind="text: $data.id"></td>
<td data-bind="text: $data.name"></td>
<td data-bind="text: $data.mark"></td>
</tr>
</tbody>
</table>
<p data-bind="text: allMarks"></p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
Side note: $data.id is a long way to write id. :-)
Side note 2: The [object Object] you're seeing for allMarks is because you're applying the text binding to an array of objects, so you get the default toString behavior of the object. You probably want a foreach for allMarks as well.
Im new to knockout and loving it so far. I've been trying to build a row editing grid using a simple table with edit links. So far it seemed to be going great but been stuck on this issue where im getting the errors when tying to save and update or canel:
Uncaught TypeError: this.description is not a function
and
Uncaught TypeError: this.editdescription is not a function
Been staring at the code for several hours now can't seem to wrap my head around this one. I am able to replicate the issue in this JSFIDDLE:
http://jsfiddle.net/N2zNk/49/
Would anyone know what is cloging in my code?
Here is my HTML:
<table>
<tr>
<th>ID</th>
<th>Description</th>
<th>Department</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<tbody data-bind="template: { name: 'rowTmpl', foreach: products }"></tbody>
</table>
<script id="rowTmpl" type="text/html">
<tr>
<td data-bind="text: ID"></td>
<td data-bind="text: Description"></td>
<td data-bind="text: Composante"></td>
<td>
Edit
</td>
<td>
Delete
</td>
</tr>
</script>
<!-- popup -->
<div id="dialogEditProduct" style="width: 400px; max-width: 100%;" data-role="popup" data-theme="c" data-overlay-theme="a" data-dismissible="false" data-bind="with: selectedProduct">
<div data-role="header" data-position="inline">
<h1 ></h1>
</div>
<div data-role="content" data-theme="c">
<p>
<label>Description:</label>
<input data-bind="value: editDescription" />
</p>
<p>
<label>Composante:</label>
<input data-bind="value: editComposante" />
</p>
<button data-role="button" data-bind="click: function() { Incidents.pvm.acceptEdit(); }">Save</button>
<button data-role="button" data-bind="click: function() { Incidents.pvm.cancelEdit() }">Cancel</button>
</div>
</div>
Here is my code:
function Item(ID, Description, Composante) {
var self = this;
this.ID = ID;
this.Description = ko.observable(Description);
this.Composante = ko.observable(Composante);
this.editDescription = ko.observable(Description);
this.editComposante = ko.observable(Composante);
this.accept = function () {
this.description(this.editdescription);
this.price(this.editPrice);
return true;
}.bind(this);
//reset to originals on cancel
this.cancel = function () {
this.editdescription(this.description);
this.editComposante(this.Composante);
}.bind(this);
}
Incidents = {
pvm: {},
productStore: {
products: [],
init: function (data) {
this.products = $.map(data, function (product) {
return new Item(product.ID, product.Description, product.Composante);
});
}
},
init: function () {
/*emuluate pulling orders from DB*/
/*this will come from server or local web storage*/
var dataFromServer = [{
ID: "123",
Description: "The server x is unavailable",
Composante: "CCD"
}, {
ID: "124",
Description: "The router located downtown is down",
Composante: "CCDD"
}, {
ID: "125",
Description: "Fiber optic cable downtown is flapping",
Composante: "MIG"
}, {
ID: "126",
Description: "Network unvailable at the beaver site",
Composante: "MIC"
}];
this.productStore.init(dataFromServer);
$(function () {
Incidents.pvm = new Incidents.productViewModel(Incidents.productStore.products);
ko.applyBindings(Incidents.pvm);
$("#productList").listview('refresh');
});
},
productViewModel: function (data) {
var self = this;
var productsArray = [];
if (data && data.length > 0) {
productsArray = data;
}
this.products = ko.observableArray(productsArray);
this.selectedProduct = ko.observable();
this.editProduct = function (productToEdit) {
self.selectedProduct(productToEdit);
// Incidents.pvm.selectedProduct(productToEdit);
};
this.acceptEdit = function () {
var selected = Incidents.pvm.selectedProduct();
if (selected.accept()) {
Incidents.pvm.selectedProduct("");
$('#dialogEditProduct').popup('close');
}
};
this.cancelEdit = function () {
Incidents.pvm.selectedProduct().cancel();
Incidents.pvm.selectedProduct("");
$('#dialogEditProduct').popup('close');
};
}
};
ko.bindingHandlers.jqButton = {
init: function (element) {
$(element).button();
},
update: function (element, valueAccessor) {
var currentValue = valueAccessor();
$(element).button("option", "disabled", currentValue.enable === false);
}
};
ko.bindingHandlers.jqmListView = {
init: function (element) {
$(element).listview();
},
update: function (element, valueAccessor) {
$(element).listview('refresh');
}
};
ko.bindingHandlers.openProductDialog = {
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$.mobile.changePage("#dialogEditProduct", {
role: 'dialog'
});
$("#dialogEditProduct").open();
// $("#dialogEditProduct").trigger('create');
}
}
};
$.extend({
isNumber: function (obj) {
return !isNaN(parseFloat(obj)) && isFinite(obj);
}
});
Incidents.init();
Javascript is case sensitive. You have mixed up description and Description. Also, editDescription and editdescription.
I've been struggling to make an interactive form, in which a viewmodel has a collection of items. I want to dynamically add/remove items from that collection.
I've found it difficult to find examples that go to this depth and most of them usually stay on a more straight forward implementation, however I've come across This post which pretty much explains what i'm doing with this brilliant jsfiddle, in which a json is pulled using the knockout mapping pluggin and then mapped.
var company;
function PersonViewModel(data) {
var personMapping = {
'ignore': ['twitter', 'webpage'],
'copy': ['age'],
'lastName': {
'create': function (options) {
return ko.observable(options.data.toUpperCase());
}
}
};
ko.mapping.fromJS(data, personMapping, this);
this.fullName = ko.computed(function () {
return this.firstName() + ' ' + this.lastName();
}, this);
}
function CompanyViewModel(data) {
var companyMapping = {
'ignore': ['address', 'website'],
'name': {
'create': function (options) {
return ko.observable(options.data.toUpperCase());
}
},
'employees': {
key: function (data) {
return ko.utils.unwrapObservable(data.personId);
},
create: function (options) {
return new PersonViewModel(options.data);
}
}
};
ko.mapping.fromJS(data, companyMapping, this);
}
What i don't know how to achieve is how and where exactly to add the 'addEmployee' and 'removeEmployee' functions? and how to bind them to a button?.
Thank you in advance!
The logical place to add these would be to your CompanyViewModel. For example, something like this:
function CompanyViewModel(data) {
var self = this;
var companyMapping = {
// ...as before
};
self.addEmployee = function () {
// as an example, we are just adding a static new employee
self.employees.push(new PersonViewModel({
lastName: "new",
firstName: "employee",
age: 10
}));
}
// important, with how we are binding the function, we expect the
// argument, e, to be the employee to remove
self.removeEmployee = function (e) {
self.employees.remove(e);
}
ko.mapping.fromJS(data, companyMapping, this);
}
Add to bind, you can do something like this:
<div id="company">
<h1 data-bind="text: name"></h1>
<h2>Employees</h2>
<input type="button" value="add" data-bind="click: addEmployee" />
<table>
<thead>
<tr>
<th>Full name</th>
<th>Last name</th>
<th>First name</th>
<th>Age</th>
</tr>
</thead>
<tbody data-bind="foreach: employees">
<tr>
<td data-bind="text: fullName"></td>
<td data-bind="text: lastName"></td>
<td data-bind="text: firstName"></td>
<td data-bind="text: age"></td>
<td>
<input type="button" value="x" data-bind="click: $parent.removeEmployee" />
</td>
</tr>
</tbody>
</table>
</div>
Which will add an add button as well as a remove x button to each employee which calls the removeEmployee function on the parent CompanyViewModel passing in the current employee.
Here's an updated fiddle
You would add those functions to your CompanyViewModel.
CompanyViewModel
...
this.addEmployee = function () {
this.employees.push(new PersonViewModel({
firstName: 'New',
lastName: 'Employee',
age: 777,
}));
};
this.removeEmployee = function () {
this.employees.pop();
};
HTML
....
<div data-bind="click: addEmployee">Add Employee</div>
<div data-bind="click: removeEmployee">Remove Employee</div>
...
http://jsfiddle.net/HBKYP/198/
I am making Add or Invite option on my site , I need to fetch first all the users of the site and show them either in typeahead or something like select picker option and as soon as the user is selected the user should be added to the invite list. That is something like trello invite to the board or organisation.
See here trello: https://trello.com/
At the very basic step I am trying to use knockout live tutorial example list and collections example. (http://learn.knockoutjs.com/#/?tutorial=collections)
and Here is my code
HTML
<h2>Your seat reservations (<span data-bind="text: seats().length"></span>)</h2>
<button class="btn btn-deffault" data-bind="click: addSeat, enable: seats().length < 5">Reserve another seat</button>
<table>
<thead><tr>
<th>Passenger name</th><th>Meal</th><th>Surcharge</th><th></th>
</tr></thead>
<!-- Todo: Generate table body -->
<tbody data-bind="foreach: seats">
<tr>
<td><input data-bind="value: name" /></td>
<td><select class="selectpicker" data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'" data-live-search="true"></select></td>
<td><input data-bind="value: formattedPrice"></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<h3 data-bind="visible: totalSurcharge() > 0">
Total surcharge: $<span data-bind="text: totalSurcharge().toFixed(2)"></span>
</h3>
and here is knockout code
//custom binding for selectpicker
ko.bindingHandlers.selectPicker = {
init: function (element, valueAccessor, allBindingsAccessor) {
if ($(element).is('select')) {
if (ko.isObservable(valueAccessor())) {
ko.bindingHandlers.value.init(element, valueAccessor, allBindingsAccessor);
}
$(element).selectpicker();
}
},
update: function (element, valueAccessor, allBindingsAccessor) {
if ($(element).is('select')) {
var selectPickerOptions = allBindingsAccessor().selectPickerOptions;
if (typeof selectPickerOptions !== 'undefined' && selectPickerOptions !== null) {
var options = selectPickerOptions.options,
optionsText = selectPickerOptions.optionsText,
optionsValue = selectPickerOptions.optionsValue,
optionsCaption = selectPickerOptions.optionsCaption;
if (ko.utils.unwrapObservable(options).length > 0) {
ko.bindingHandlers.options.update(element, options, ko.observable({ optionsText: optionsText, optionsValue: optionsValue, optionsCaption: optionsCaption }));
}
}
if (ko.isObservable(valueAccessor())) {
ko.bindingHandlers.value.update(element, valueAccessor);
}
$(element).selectpicker('refresh');
}
}
};
function AllUsers(data){
var self = this;
self.name = name;
self.meal = ko.observable(initialMeal);
self.formattedPrice = ko.computed(function() {
var price = self.meal().price;
return price ? "$" + price.toFixed(2) : "None";
});
self.formatPrice = ko.observable(self.formattedPrice());
self.about_me = ko.observable(data.about_me);
self.email = ko.observable(data.email);
self.uname = ko.observable(data.uname);
self.uuid = ko.observable(data.uuid);
self.everyuser = [
{aboutMe: self.about_me(), email: self.email(), name: self.uname, id: self.uuid()}
];
}
function PostViewModel() {
var self = this;
self.allusers= ko.observableArray([]);
self.gert = [
{ mealName: "Standard (sandwich)", price: 0 },
{ mealName: "Premium (lobster)", price: 34.95 },
{ mealName: "Ultimate (whole zebra)", price: 290 }
];
$.getJSON('/users', function (json) {
var t = $.map(json.users, function(item) {
console.log("Something",item);
return new AllUsers(item);
});
self.allusers(t);
});
}
ko.applyBindings(new PostViewModel());
But the code is not working and not showing the options while using class selectpicker in HTML code, and if selectpicker is not used then the simple dropdown comes that should not come.
<td><select class="selectpicker" data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'" data-live-search="true"></select></td>
anyone knowing about Knockout.js please help
It appears that you are using the name of the custom binding as your CSS class for the <select>. In order to apply the binding to the correctly you'll need to do this within the data-bind attribute.
<select data-bind="selectpicker: meal"></select>
Knockout's documentation on this is also very helpful.