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.
Related
<div data-bind="with: SimpleListModel">
<form data-bind="submit: addItem" >
New item:
<input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' />
<button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button>
<p>Your items:</p>
<select multiple="multiple" width="50" data-bind="options: items"> </select>
</form>
</div>
<div data-bind="with: SimpleListModel2">
<div data-bind="foreach: baselist">
<div>
<span data-bind="text: basename"></span>
<div data-bind="foreach: subItems">
<span data-bind="text: subitemname"></span>
Del
</div>
</div>
<button data-bind="click:$parent.addChild">Add</button>
</div>
</div>
this is the viewmodel
var SimpleListModel = function(items) {
this.items = ko.observableArray(items);
this.itemToAdd = ko.observable("");
this.addItem = function() {
if (this.itemToAdd() != "") {
this.items.push(this.itemToAdd()); // Adds the item. Writing to the "items" observableArray causes any associated UI to update.
this.itemToAdd(""); // Clears the text box, because it's bound to the "itemToAdd" observable
}
}.bind(this); // Ensure that "this" is always this view model
};
var initialData = [
{ basename: "Danny", subItems: [
{ subitemname: "Mobile"},
{ subitemname: "Home"}]
},
{ basename: "Sensei", subItems: [
{ subitemname: "Mobile"},
{ subitemname: "Home"}]
}];
var SimpleListModel2 = function(baselist) {
var self= this;
self.baselist= ko.observableArray(baselist);
self.addChild = function(list) {
alert(list.basename);
}.bind(this);
self.removecard = function (data) {
//tried
data.baselist.subItems.remove(data);
data.subItems.remove(data);
$.each(self.baselist(), function() { this.subItems.remove(data) })
};
};
var masterVM = (function () {
var self = this;
self.SimpleListModel= new SimpleListModel(["Alpha", "Beta", "Gamma"]);
self.SimpleListModel2= new SimpleListModel2(initialData);
})();
ko.applyBindings(masterVM);
This is a small code snippet i constructed of my project. Can someone make remove card work? last two increments of my questions are of the same type. but this question is the highest i reach.
removecard doesn't work now in this scenario at least for me.
Use $parents[index] to get to specific parent. http://knockoutjs.com/documentation/binding-context.html.
$parents[0] --> parent
$parents[1] --> grand parent
etc
var initialData = [
{ basename: "Danny", subItems: [
{ subitemname: "Mobile"},
{ subitemname: "Home"}]
},
{ basename: "Sensei", subItems: [
{ subitemname: "Mobile"},
{ subitemname: "Home"}]
}];
var SimpleListModel2 = function(baselist) {
var self= this;
self.baselist= ko.observableArray(baselist);
self.addChild = function(list) {
alert(list.basename);
}.bind(this);
self.removecard = function (data) {
//tried
console.log(data);
};
};
var masterVM = (function () {
var self = this;
self.SimpleListModel2= new SimpleListModel2(initialData);
})();
ko.applyBindings(masterVM);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with: SimpleListModel2">
<div data-bind="foreach: baselist">
<div>
<span data-bind="text: basename"></span>
<div data-bind="foreach: subItems">
<span data-bind="text: subitemname"></span>
Del
</div>
</div>
<button data-bind="click:$parent.addChild">Add</button>
</div>
</div>
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/
While following example 3 in this Knockout-example: http://knockoutjs.com/documentation/options-binding.html
I have a lists of sport-objects I select via a dropdown. Each sport-object has a list of categories which I iterate over based on which sport is selected. This currently works.
Now, I need to get the name of the sport currently selected so I can use it in CRUD-operations. In the example below I try to store it in a hidden field which does not work as intended, leaving the value-attribute in the field empty.
So, how can I get the name (or any other observable in the model) from the selected value in the dropdown?
The code below shows the models and the html I use:
<select id="categorySelector" class="form-control" data-bind="options: Sports, optionsText: 'Name', value: SelectedSport"></select>
<table data-bind="foreach: SelectedSport">
<tbody data-bind="foreach: Categories">
<tr data-bind="attr: { 'data-name': Name }">
<td data-bind="text: Name"></td>
</tr>
</tbody>
</table>
<input type="hidden" data-bind="value: $root.SelectedSport.Name" />
<span data-bind="text: SelectedSport.Name"></span>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.1.0.js"></script>
<script>
var CategoryModel = function (name) {
var self = this;
self.Name = ko.observable(name);
};
var SportModel = function (name) {
var self = this;
self.Name = ko.observable(name);
};
var ViewModel = function () {
var self = this;
self.Sports = ko.observableArray();
self.SelectedSport = ko.observable();
self.GetSports = function () {
$.getJSON('data.json', function (data) {
$.each(data, function (sportIndex, sportValue) {
if (sportValue.hasOwnProperty('name')) {
var newSport = new SportModel(sportValue.name);
console.log(sportValue.name);
$.each(sportValue.categories, function (categoryIndex, categoryValue) {
if (categoryValue.hasOwnProperty('name')) {
newSport.Categories.push(new CategoryModel(categoryValue.name));
console.log('--' + categoryValue.name);
}
});
self.Sports.push(newSport);
}
});
})
.fail(function () {
console.log("Failed to load and/or parse bikeData.json");
});
};
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.GetSports();
</script>
The Json file:
[
{
"name": "Archery",
"categories": [
{ "name": "Världsmästerskap" },
{ "name": "Vintermästerskap" }
]
},
{
"name": "Bike",
"categories": [
{ "name": "Världsmästerskap" },
{ "name": "Europamästerskap" }
]
}
]
I found the answer: you need to check if there really is a value selected and if there isn't, insert a default, like so:
<input type="hidden" data-bind="value: SelectedSport() ? SelectedSport().Name : 'unknown'" />
The example on the Knockout-site showed this.
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/
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.