Update razor collection via knockout - asp.net-mvc

I have this UI
And my goal is to update table after clicking Update button based on Id and Description parameters
I have following code
Html
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Products)
{
<tr>
<td>#item.Id</td>
<td>#item.Description</td>
</tr>
}
</tbody>
</table>
<label>Id</label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description</label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>
JavaScript
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = json.products;
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = null;
self.productDescription = null;
// Update function.
self.update = function () {
// Get products collection index.
index = self.products.findIndex(function (product) {
return product.Id == self.productId
});
// Throws -1, index was not found !
alert(index);
// Assign new value.
// self.products[index].description(self.productDescription);
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I need help with two things
Update knockout viewmodel (self.products collection based on Id and Description inputs in viewModel.Update function)
Update table (I don't know how to bind the rows, when using razor foreach)
EDIT:
When I change Razor loop with Knockout foreach
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
it does not work either :/ I tried some changes with observables
<script type='text/javascript'>
// Plain javascript alternative to jQuery.ready.
(function () {
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observable(json.products);
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
self.update = function () {
self.products()[self.productId()-1].description = self.productDescription()
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
})();
</script>
I do not get an error, but the value is not changed in the html table.

Your knockout code looks mostly correct but your UI isn't updating because the values that you're changing aren't observable. If you want the changes to the items to reflect in the table you need to make each field that will update an observable instead of having a single observable property for the entire json object.
The following code will only update if the entire object reference changes. IE if you fill self.products with a new object. It will not update if you change a property on the existing object.
// Product array from server.
self.products = ko.observable(json.products);
You'll need to change your code to individually apply observable properties to your json object when it comes from the server.
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
And then you'll have to update the value using the function style getter/setter.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
// Convert server model to json object.
var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};
function viewModel()
{
var index;
// Store default this to self, to be able use self in subfunctions.
var self = this;
// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
self.products.push({
id: json.products[i].id,
description: ko.observable(json.products[i].description)
});
}
// Automatic refreshed parameter in UI.
self.categoryName = ko.observable(json.categoryName);
// Update form parameters.
self.productId = ko.observable();
self.productDescription = ko.observable();
// Update function.
// Update function.
self.update = function () {
self.products()[Number(self.productId())-1].description(self.productDescription());
};
}
// Apply viewModel to UI
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>Description</th>
</tr>
</thead>
<tbody data-bind="foreach: products">
<tr>
<td data-bind="text: id"></td>
<td data-bind="text: description"></td>
</tr>
</tbody>
</table>
<br/>
<label>Id: </label>
<input data-bind="value: productId" type="text" class="form-control" />
<br />
<label>Description: </label>
<input data-bind="value: productDescription" type="text" class="form-control" />
<br />
<button class="btn btn-primary" data-bind="click: update">Update</button>

Related

Connecting MVC Model View with associated Knockout.js ViewModel

Background: I'm pretty new to MVC & Knockout.js but I am trying to get up to speed on these technologies. I am using MVC 5 with EF6 and Knockout.JS 3.2.
I have a Detail view that pulls a "VoteAnswer" object using MVC based on the ID passed in the URL:
For example I can go to the url MyDomain/VoteAnswers/Details/1 and it will pull the information from my database correctly (It pulls a VoteAnswer with the ID of 1) and display in my Details view. However I am trying to hook-up my Knockout.js "VoteAnswer" ViewModel to function the same way and am having trouble.
Here is my Details View: (Note the #Html.DisplayFor(model => model.VoteAnswerId) etc works and displays the data from my Database.
#model AM_SPA_TestSite.Models.VoteAnswer
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: id"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: isActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: displayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: id" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: displayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: isActive" /></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td>#Html.DisplayFor(model => model.VoteAnswerId)</td>
</tr>
<tr>
<td>Display Text</td>
<td>#Html.DisplayFor(model => model.DisplayText)</td>
</tr>
<tr>
<td>IsActive</td>
<td>#Html.DisplayFor(model => model.IsActive)</td>
</tr>
</table>
</div>
Here is my Knockout.Js ViewModel
// VoteAnswer ViewModel
var VoteAnswerVM = {
id: ko.observable(),
displayText: ko.observable(),
isActive: ko.observable(),
SaveVoteAnswer: function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
};
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
ko.applyBindings(VoteAnswerVM);
});
I know what I am missing is initializing the ViewModel with the ID of 1, but I was thinking the MVC model already has the data and the knockout.js SHOULD map to that data without manually initializing by sending a request to the database again. What am I missing? thanks.
EDIT: Added solution below. I'm not sure I am settled on this approach but here it is. Updated the controller to ONLY return a view and not query the DB. (otherwise I would have two database calls for the same data.
// GET: VoteAnswers/Details/5
public ViewResult Details(int? id)
{
return View();
}
Added an API Controller that does query the DB.
// GET: api/VoteAnswers/5
[ResponseType(typeof(VoteAnswer))]
public async Task<IHttpActionResult> GetVoteAnswer(int id)
{
VoteAnswer voteAnswer = await db.VoteAnswers.FindAsync(id);
if (voteAnswer == null)
{
return NotFound();
}
return Ok(voteAnswer);
}
In my View (.cshtml file) I reference my knockout.js ModelView, View is Below:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<script src="~/KnockoutViewModels/VoteAnswers.js"></script>
</head>
<body>
<div>
<h4>VoteAnswer</h4>
<hr />
<table>
<tr>
<td>Id</td>
<td data-bind="text: VoteAnswerId"></td>
</tr>
<tr>
<td>Display Text</td>
<td data-bind="text: IsActive"></td>
</tr>
<tr>
<td>IsActive</td>
<td data-bind="text: DisplayText"></td>
</tr>
</table>
<table>
<tr>
<td>Id</td>
<td><input type="text" data-bind="value: VoteAnswerId" /></td>
</tr>
<tr>
<td>Display Text</td>
<td><input type="text" data-bind="value: DisplayText" /></td>
</tr>
<tr>
<td>IsActive</td>
<td><input type="text" data-bind="value: IsActive" /></td>
</tr>
</table>
</div>
<div id="error"></div>
</body>
</html>
Updated My ViewModel script to access the Database based on the URL ID.
// VoteAnswer ViewModel
var VoteAnswer = function () {
var self = this;
self.VoteAnswerId = ko.observable();
self.DisplayText = ko.observable();
self.IsActive = ko.observable();
self.SaveVoteAnswer = function () {
$.ajax({
url: '/VoteAnswers/Create',
type: 'post',
dataType: 'json',
data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
},
error: function (err) {
if (err.responseText == "Creation Failed")
{ window.location.href = '/VoteAnswers/Index/'; }
else {
alert("Status:" + err.responseText);
window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function () {
window.location.href = '/VoteAnswers/Index/';
}
});
}
self.load = function (id) {
if (id != 0) {
$.ajax({
url: '/api/VoteAnswers/' + id,
type: 'get',
data: ko.toJSON(this),
contentType: 'application/json',
success: function(data) {
self.VoteAnswerId = ko.observable(data.voteAnswerId);
self.DisplayText = ko.observable(data.displayText);
self.IsActive = ko.observable(data.isActive);
ko.applyBindings(self);
},
error: function(err) {
if (err.responseText == "Creation Failed") {
window.location.href = '/VoteAnswers/Index/';
} else {
$("#error").text("Status:" + err.responseText);
//window.location.href = '/VoteAnswers/Index/';;
}
},
complete: function() {
//window.location.href = '/VoteAnswers/Index/';
}
});
} else {
window.location.href = '/VoteAnswers/Index/';
}
}
};
function GetURLParameter() {
var sPageUrl = window.location.href;
var indexOfLastSlash = sPageUrl.lastIndexOf("/");
if (indexOfLastSlash > 0 && sPageUrl.length - 1 != indexOfLastSlash)
return sPageUrl.substring(indexOfLastSlash + 1);
else
return 0;
}
//Go
$(document).ready(function () {
//initialize and create new VoteAnswerVM by URL value here?
var viewModel = new VoteAnswer();
viewModel.load(GetURLParameter());
});
I hope I understood you correctly, if my answer is all wrong for your question let me know where I went wrong.
First thing to realize is, if you bind a KO observable to an input field, knockout will not look at the initial value of that input field and store it in the observable. It will do the opposite: it will look at the current value in the observable and store it in the input field's value. In your case, the observable are initialized without a value, which in JavaScript means the value undefined. So if you bind your observables to the fields you've filled with Razor/MVC viewmodel, you'll immediatly overwrite those values with the empty values stored in your observables.
There IS a way to fill your Knockout model with your data through Razor, but it involves inline JavaScript and is a bad practise for a number of reasons (I will elaborate on this on request).
The best way to do this is to separate your views from your data: Don't inject the MVC viewmodel into the view, but make a separate endpoint that returns JSON and return the data there (this endpoint will receive the ID parameter instead of the view). The JSON endpoint is called from JavaScript and can be used to fill your model with the correct values.
Upsides: separation of concerns, possibility to enable view caching for a more responsive frontend, no need to use razor syntax, or even worse, combine it with inline JS. All your binding of data to the UI will happen through Knockout. I learned this myself because we also started out using razor, but on the long run this solution wasn't feasible for a big project. We never regretted the switch to always getting the data from separate JSON endpoints.
If you are unsure on how to do this I can write some pseudocode to illustrate the idea.
These may be the possible solutions
1.) data_bind is wrongly used
<td><input type="text" data-bind="value: isActive" /></td> // which is wrong
<td><input type="text" data_bind="value: isActive" /></td> //data_bind is wrongly used
2.)
If still problem exists you may try this syntax
#Html.DisplayFor(model => model.IsActive, new { data_bind = "value:IsActive" });
If you find still something missing please provide some detail info .

Can not save data 2nd time knockout mvc

I am new in knockout and asp.net mvc both.
I am trying to Insert update delete data in database with knockout. My knockout model is
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.CityId = ko.observable();
self.CityName = ko.observable();
self.selectedCity = ko.observable();
// self.City = ko.observable();
selectCity = function (item) {
self.selectedCity(item);
}
//load
loadCitys = function () {
$.getJSON("/Admin/GetCitys", {}, function (result) {
var mappedCitys = ko.utils.arrayMap(result.Data, function (item) {
return new City(item);
});
self.Citys([]);
self.Citys.push.apply(self.Citys, mappedCitys);
});
}
//edit
EditCity = function (item) {
//what need to do here
// is it possible to fill the hidden fild and the text box ??
}
//save
SaveCity = function (item) {
City = new City(item);
$.ajax({
type: "POST",
url: "/Admin/SaveCity",
data: ko.toJSON({ City: City }),
contentType: "application/json",
success: function (result) {
if (result.Edit) {
City.CityId = result.Success;
City.CityName = item.CityName;
self.Citys.push(City);
toastr.success('City Information Save Successfully', 'Success');
}
else if (result.Edit == false) {
toastr.success('City Information Update Successfully', 'Success');
}
else {
toastr.error('There is an error please try again later', 'Errror');
}
}
});
}
//delete
DeleteCity = function (City) {
$.ajax("/Admin/DeleteCity", {
data: ko.toJSON({ CityId: City.CityId }),
type: "POST", contentType: "application/json",
success: function (result) {
if (result.Success) {
self.Citys.remove(City);
toastr.success('City Remove Successfully', 'Success');
}
else {
alert("Error..");
}
}
});
}
}
(function () {
ko.applyBindings(new CityViewModel, document.getElementById("Citys"));
loadCitys();
});
And my Html codes are
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: $root.Citys">
<tr data-bind="click: selectCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city name…">
<button type="submit" data-bind="click: SaveCity" class="btn">Submit</button>
</fieldset>
With this codes I can get data form database display them successfully in my view file,
I delete the data from database, and I also can Insert data to database but here is a problem I can save data only 1st time when I change the textbox value (without page refresh) and try to save city information then it say (in Firebug on my javascript code):
TypeError: City is not a constructor
City = new City(item);
My question is what have I done wrong in this codes, and I am trying to fill the textbox and the hidden field when edit button click, how can I do this?
Thanks in advance.
There are a number of faults with your javascript, including:
The methods on your viewmodel, such as SaveCity, DeleteCity, EditCity are all being defined without the 'this/self' prefixes, therefore they are being added to the global namespace.
In your SaveCity method, your are assigning a new instance of the City class to a variable called 'City', therefore destroying the City class. It will work the first time, but any other attempts to create an instance of a City will yield an exception.
Anyway, this should be a working version of your script and HTML without the ajax stuff. You will need to adapt that yourself. I have also created a working JsFiddle here..
function City(data) {
this.CityId = ko.observable(data.CityId);
this.CityName = ko.observable(data.CityName);
}
function CityViewModel() {
var self = this;
self.Citys = ko.observableArray([]);
self.SelectedCity = ko.observable();
self.EditingCity = ko.observable(new City({CityId: null, CityName: ''}));
self.EditCity = function(city){
self.EditingCity(new City(ko.toJSON(city)));
};
//load
self.loadCitys = function () {
self.Citys().push(new City({CityId: '1245', CityName: 'Los Angeles'}));
self.Citys().push(new City({CityId: '45678', CityName: 'San Diego'}));
};
//save
self.SaveCity = function () {
var city = self.EditingCity();
if(city.CityId()){
var cityIndex;
for(var i = 0; i < self.Citys().length; i++) {
if(self.Citys()[i].CityId() === city.CityId()) {
cityIndex = i;
break;
}
}
self.Citys[cityIndex] = city;
}
else{
city.CityId(Math.floor((Math.random()*1000000)+1));
self.Citys.push(city);
}
self.EditingCity(new City({CityId: null, CityName: ''}));
}
//delete
self.DeleteCity = function (city) {
self.Citys.remove(city);
};
}
var viewModel = new CityViewModel();
viewModel.loadCitys();
ko.applyBindings(viewModel, document.getElementById("Citys"));
HTML
<table class="table table-striped">
<thead>
<tr>
<th>City Id</th>
<th>City Name</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: Citys">
<tr data-bind="click: $root.SelectedCity">
<td><span data-bind="text:CityId"></span></td>
<td><span data-bind="text:CityName"></span></td>
<td><button data-bind="click: $root.EditCity" class="btn btn-primary">Edit</button></td>
<td><button data-bind="click: $root.DeleteCity" class="btn btn-danger">Delete</button></td>
</tr>
</tbody>
</table>
<fieldset data-bind='with:EditingCity'>
<legend>Add new / Edit City</legend>
<label>City name</label>
<input type="hidden" data-bind="value: CityId" />
<input type="text" data-bind="value: CityName" placeholder="Type city name" />
<button type="submit" data-bind="click: $root.SaveCity" class="btn">Submit</button>
</fieldset>

Knockout Mapping Observable Array MVC

Hi I have looked around online for hours trying to solve this but I couldn't find a solution.
I am building the solution using ASP.NET MVC and Knockout. The controller returns the options for the user to select from to the view which converts it into JSON and uses the mapping plugin to map it to a knockout View Model. It populates correctly because when I do this: ko.toJSON($data) to test it, the correct data is returned but the view won't update the list when an item is added to it.
My guess is that the properties aren't observables but I'm not sure how to fix that.
This is an example of the JSON Returned:
[{"Key":1,"Value":"company"}]
This is my javascript:
function ProjectWorkedOn() {
var self = this;
self.client = ko.observable();
self.job = ko.observable();
self.project = ko.observable();
self.workType = ko.observable();
}
function createTimesheetViewModel() {
var self = this;
//list of options
self.UserClients = ko.observableArray();
self.UserProjects = ko.observableArray();
self.UserJobs = ko.observableArray();
self.UserWorkTypes = ko.observableArray();
//keep track of selected options
self.selectedClient = ko.observable();
self.selectedProject = ko.observable();
self.selectedJob = ko.observable();
self.selectedWorkType = ko.observable();
//list to add choices in
self.ListProjectsWorkedOn = ko.observableArray();
self.addProjectWorkedOn = function () {
var project = new ProjectWorkedOn();
project.client = self.selectedClient;
project.job = self.selectedJob;
project.project = self.selectedProject;
project.workType = self.selectedWorkType;
self.ListProjectsWorkedOn.push(project)
}
self.removeProjectWorkedOn = function (projectWorkedOn) {
self.ListProjectsWorkedOn.remove(projectWorkedOn)
}
}
$(function () {
var CreateTimesheetViewModel = new createTimesheetViewModel();
CreateTimesheetViewModel.UserClients = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserClients))');
CreateTimesheetViewModel.UserProjects = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserProjects))');
CreateTimesheetViewModel.UserJobs = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserJobs))');
CreateTimesheetViewModel.UserWorkTypes = ko.mapping.fromJSON('#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.UserWorkTypes))');
ko.applyBindings(CreateTimesheetViewModel, document.getElementById("CreateTimesheet"));
});
This is the part of the view that adds items to the list:
<table>
<tr>
<td>
Projects Worked On:
</td>
<td>
Client: <select data-bind="options: UserClients, optionsText: 'Value', value: selectedClient"></select>
</td>
<td>
Project: <select data-bind="options: UserProjects, optionsText: 'Value', value: selectedProject"></select>
</td>
<td>
Job: <select data-bind="options: UserJobs, optionsText: 'Value', value: selectedJob"></select>
</td>
<td>
Service: <select data-bind="options: UserWorkTypes, optionsText: 'Value', value: selectedWorkType"></select>
</td>
<td>
<button data-bind="click: addProjectWorkedOn">Add Project</button>
</td>
</tr>
</table>
and this is what displays them:
<table>
<tr>
<td>Client</td>
<td>Project</td>
<td>Job</td>
<td>Service</td>
<td></td>
</tr>
<tbody data-bind="foreach: ListProjectsWorkedOn()">
<tr>
<td data-bind="text: client.Value"></td>
<td data-bind="text: project.Value"></td>
<td data-bind="text: job.Value"></td>
<td data-bind="text: workType.Value"></td>
<td><button data-bind="click: $parent.removeProjectWorkedOn">Remove</button></td>
</tr>
</tbody>
</table>
Thanks in advance!
EDIT:
I worked it out
to add project set the attributes like this: project.clientValue(self.selectedClient().Value);
then to reference them in view call it as a function: clientValue()
credit goes to Chris Patt for explaining that observables should be called as functions when you want to get their value
Because you're setting the ProjectWorkedOn observables and getting the selected values incorrectly. To set an observable you pass the value as a parameter as you would for a function and to get the value of an observable, you call it like a function. (They are in fact functions.):
self.addProjectWorkedOn = function () {
var project = new ProjectWorkedOn();
project.client(self.selectedClient());
project.job(self.selectedJob());
project.project(self.selectedProject());
project.workType(self.selectedWorkType());
self.ListProjectsWorkedOn.push(project)
}
EDIT
I was thinking you literally wanted the selected values, not the actual whole JSON object. What you're attempting simply isn't possible. The value of the select has to be an integer or string -- it can't be a full object. Usually you would handle something like this by first simply returning a value/text list to populate the selects, where the value would be something like the id. Then, upon selection, you would issue an AJAX request to retrieve the object with that id.

KnockoutJS: ko.mapping.fromJS problems

My JS:
<script type="text/javascript">
function DictionaryEntry() {
var self = this;
self.Simplified = ko.observable("");
self.Traditional = ko.observable("");
self.Phonetic = ko.observable("");
self.Definition = ko.observable("");
}
function DictionaryModel() {
var self = this;
self.entries = ko.observableArray([]);
}
var viewModel = new DictionaryModel();
viewModel.update = function () {
var self = this;
var f = $("#fSearch");
$.ajax({
url: f.attr('action'),
type: f.attr('method'),
data: f.serialize(),
success: function (result) {
self.entries = ko.mapping.fromJS(result, viewModel);
}
});
return false;
}
ko.applyBindings(viewModel);
</script>
Table html:
<table class="table table-striped">
<thead>
<tr>
<th>#T("Simplified")</th>
<th>#T("Traditional")</th>
<th>#T("Phonetic")</th>
<th>#T("Definition")</th>
</tr>
</thead>
<tbody data-bind="foreach: entries">
<tr>
<td data-bind="text: Simplified"></td>
<td data-bind="text: Traditional"></td>
<td data-bind="text: Phonetic"></td>
<td data-bind="text: Definition"></td>
</tr>
</tbody>
</table>
A button which triggers the update.. searches the dictionary and returns results to replace what is currently in the table:
<input type="submit" value="Search" class="btn" data-bind="click: update" />
in my action method, this is what is returned:
return Json(new
{
// here list is a List<T> with the 4 properties to display in UI
entries = list,
IndexOfPage = indexOfPage,
SizeOfPage = sizeOfPage,
TotalRecords = totalRecords,
Pages = (int)Math.Ceiling((double)totalRecords / sizeOfPage)
});
Problem I am having is that it seems to be stuck in an infinite loop for some reason. I put a breakpoint in the action and I can see it goes there over-and-over again... continuously..
What am I doing wrong? Completely new to knockout (and not exactly super at JS either, so please don't give a vague answer)
There is a callback that is called update in knockout.
What's happening is when you call the mapping, it's triggering that callback ( which exists as a function on the viewModel ). This is causing your update function to be called, thus causing an infinite loop.
http://knockoutjs.com/documentation/plugins-mapping.html
look for the section about Customizing object updating using “update”
If result has an entries propery that is your list, you should just have to 'ko.mapping.fromJS(result, viewModel)'. Setting entries to the result gives you an entries property with it's own entries property.

create div dynamically on click of a hyperlink in asp.net mvc

1.I want to dynamically generate div containing textbox with unique id on click of button
<input id="<%:rid %>" type="button" value="reply"/>
2.I also want to use jquery ajax mathod to carry the textbox data to ashx file .
Can anyone help me
code
var lineItemCount = 0;
$(document).ready(function () {
$(".commentbox input[type='button']").click(function () {
var id = $(this).attr("id");
alert(id);
var cid = id.substring(5);
var containerid = "container" + cid;
alert(containerid);
//Increase the lineitemcount
lineItemCount++;
//Add a new lineitem to the container, pass the lineItemCount to makesure
getLineItem()
// can generate a unique lineItem with unique Textbox ids
$(containerid).append(getLineItem(lineItemCount));
});
});
//Create a new DIV with Textboxes
function getLineItem(number) {
var div = document.createElement('div');
//Give the div a unique id
div.setAttribute('id', 'lineitem_' + number);
//pass unique values to the getTextbox() function
var t1 = getTextbox('txt_' + number + '_1');
div.appendChild(t1);
return div;
}
//Create a textbox, make sure the id passed to this function is unique...
function getTextbox(id) {
var textbox = document.createElement('input');
textbox.setAttribute('id', id);
textbox.setAttribute('name', id);
return textbox;
}
iteration through model in aspx page
<%var i=1;%>
<%foreach (var commentitem in item.commentsModelList)
{
<table border="0" class="commentbox">
<tr>
<%var rid = "reply" + i;%>
<div id="<%:containerid %>">
<td> <input id="<%:rid %>" type="button" value="reply"/>
</div>
</td>
</tr>
</table>
<% i++;}%>
I changed your markup little bit to get the corresponding id of items on my click events
HTML
<table border="0" class="commentbox">
<tr>
<td>Some Item text
</td>
</tr>
<tr>
<td>
<div id="container-1" ></div>
<input type="button" class='btnReply' id="reply-1" value="Reply" />
</td>
</tr>
</table>
And the Script
$(function(){
$(".commentbox .btnReply").click(function(){
$(this).hide();
var id=$(this).attr("id").split("-")[1]
var strDiv="<input type='text' class='txtCmnt' id='txtReply-"+id+"' /> <input type='button' class='btnSave' value='Save' id='btnSave-"+id+"' /> ";
$("#container-"+id).html(strDiv);
});
$(".commentbox").on("click",".btnSave",function(){
var itemId=$(this).attr("id").split("-")[1]
var txt=$(this).parent().find(".txtCmnt").val();
$.post("/echo/json/", {reply: txt, id: itemId},function(data){
alert(data);
//do whatever with the response
})
});
});
Here is the jsfiddle example : http://jsfiddle.net/UGMkq/30/
You need to change the post target url to your relevant page which handles the ajax response.
EDIT : As per the comment about handing Multiple Divs
As long as you have the container div ids unique, it will work, I just changed the markup to include more than one item.
<table border="0" class="commentbox">
<tr>
<td>Some Item text<br/>
<div id="container-1" ></div>
<input type="button" class='btnReply' id="reply-1" value="Reply" />
</td>
</tr>
<tr>
<td>Some Another Content here <br/>
<div id="container-2" ></div>
<input type="button" class='btnReply' id="reply-2" value="Reply" />
</td>
</tr>
</table>
Here is the sample :http://jsfiddle.net/UGMkq/44/
For the above output to be rendered, you probably want to write your razor syntax like this
<table border="0" class="commentbox">
#foreach (var commentitem in item.commentsModelList)
{
<tr>
<td>Some Another Content here<br/>
<div id="container-#(commentitem.Id)" ></div>
<input type="button" class='btnReply' id="reply-#(commentitem.Id)" value="Reply" />
</td>
</tr>
}
</table>
Instead of creating a new table for each item, I created a new row in existing table.

Resources