Binding Asp.Net MVC json result to knockout.js observable Array - asp.net-mvc

I have some hardcoded values in my asp.net mvc controller. The GetPlanList() returns a JsonResult which is supposed to be read in by the viewmodel.js file and assign it to the ko.observableArray() and then data bind it to the table.
The problem I am having is how do i get the results from the mvc controller and assign it to the knockout variables?
MVC Controller:
public JsonResult GetPlanList()
{
List<PlanVm> PlanList = GetPlansListData();
return Json(PlanList, JsonRequestBehavior.AllowGet);
}
public List<PlanVm> GetPlansListData()
{
return new List<PlanVm>()
{
new PlanVm() { PlanName = "706-1", ActiveParticipants = 414, TotalOfAllParticipantShares = 1.22},
new PlanVm() { PlanName = "706-2", ActiveParticipants = 23423, TotalOfAllParticipantShares = 4.00},
new PlanVm() { PlanName = "706-3", ActiveParticipants = 3, TotalOfAllParticipantShares = 564.00},
new PlanVm() { PlanName = "706-4", ActiveParticipants = 123, TotalOfAllParticipantShares = 0.00}
};
}
viewmodel.js file:
function IssuePlansViewModel() {
var self = this;
self.planName = ko.observable("");
self.numberOfParticipants = ko.observable("");
self.totalShares = ko.observable("");
self.result = ko.observableArray();
return self;
}
return IssuePlansViewModel;
HTML:
<table class="table">
<thead>
<tr>
<td>Plan Name</td>
<td class="hide-mobile txt-right">Number of Participants</td>
<td class="txt-right">Total Shares</td>
</tr>
</thead>
<tbody>
<tr>
<td data-bind="text: planName"></td>
<td class="hide-mobile txt-right" data-bind="text: numberOfParticipants"></td>
<td class="txt-right" data-bind="text: totalShares"></td>
</tr>
</tbody>
</table>

Use an ajax method to get the json list from server and bind the results in success callback of ajax method.
But you should have a list of plans in your root ViewModel,
var IssuePlansViewModel = function(data) {
var self = this;
self.planName = ko.observable(data.planName);
self.numberOfParticipants = ko.observable(data.numberOfParticipants);
self.totalShares = ko.observable(data.totalShares);
self.result = ko.observableArray(data.result);
return self;
}
function mainViewModel() {
var self = this;
self.plans = ko.observableArray([]);
$.ajax({
type: 'GET',
url: "GetPlanList",
dataType: "json",
contentType: 'application/json; charset=utf-8',
traditional: true, //// traditional option to true
success: function(result) {
ko.utils.arrayForEach(jsonResult, function(data) {
self.plans.push(new IssuePlansViewModel(data));
});
}
});
}
and bind mainViewModel to your html.
Oh, and in your html you may want to use a foreach loop to list plans,
<tbody data-bind="foreach: plans">
<tr>
<td data-bind="text: planName"></td>
<td class="hide-mobile txt-right" data-bind="text: numberOfParticipants"></td>
<td class="txt-right" data-bind="text: totalShares"></td>
</tr>
</tbody>

Related

Foreach Checked and click event knockout

I would like to add the values of 'Cost' based on checkbox checked event on knock out.
<tbody data-bind="foreach: $root.searchedResults">
<!-- ko if: Id-->
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Cost"></td>
<td><input type="checkbox" data-bind="checked: $root.searchedResults().length > 0, click: $root.Hospital_click"></td>
</tr>
<!-- /ko -->
</tbody>
And the sum of cost should be displayed in
<div data-bind="text: ko.toJSON(SumofItems)"></div>
The click event is not updating my sum based selection. Can someone help?
self.Hospital_click = function () {
//Suggest
}
My View model is:
var SearchModel = function () {
var self = this;
self.searchFromDate = ko.observable('01/01/2014');
self.searchToDate = ko.observable('01/01/2016');
self.searchedResults = ko.observableArray([]);
self.search = function () {
var model = {
BeginDate: self.searchFromDate(),
EndDate: self.searchToDate()
};
$.ajax({
url: '#Url.Action("SearchSpend", "Analysis", new { Area = "Portal" })',
type: 'POST',
contentType: 'application/json; charset=utf-8',
dataType: "json",
data: ko.toJSON(model),
success: function (response) {
self.searchedResults(response);
}
});
};
You could add a new observableArray with the id's of the selected results (selectedResults). The viewmodel could be:
function Result(id,cost) {
this.Id = id;
this.Cost = cost;
}
function viewModel() {
var self = this;
self.searchedResults = ko.observableArray([]);
self.selectedResults = ko.observableArray([])
self.totalCost = ko.computed(function() {
let total = 0;
for(let p = 0; p < self.searchedResults().length; ++p)
{
if (self.selectedResults.indexOf(self.searchedResults()[p].Id)>=0){
total += self.searchedResults()[p].Cost;
}
}
return total;
});
};
totalCost will return the sum of the Cost field for all selected results.
And the view could be something like this:
<table data-bind="foreach: searchedResults">
<tr>
<td data-bind="text: Id"></td>
<td data-bind="text: Cost"></td>
<td><input type="checkbox" value="" data-bind="checkedValue: Id, checked: $parent.selectedResults"></td>
</tr>
</table>
<div data-bind="text: totalCost"></div>
totalCost is a computed value that returns de sum of the Cost for only the selected results.
Here is a fiddle.
In the fiddle, the data comes from the array listOfResults. In your case, this comes from the success function (Ajax request); in this function you also needs to clean the selectedResults.

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>

MVC: HTML table rows in a PartialView are not rendered in HTML after an Ajax call

My form looks like;
#model IEnumerable<SCD.ViewModels.UserLineViewModel>
#{
ViewBag.Title = "List";
}
<h2>List</h2>
<form method="GET" action="#Url.Action("AddNewUser", "DataService")" data-scd-ajax="true" data-scd-target="#userList">
#if (TempData["SuccessMessage"] != null)
{
<p class="successHighlight">#TempData["SuccessMessage"].ToString()</p>
}
#if (TempData["ErrorMessage"] != null)
{
<p class="errorHighlight">#TempData["ErrorMessage"].ToString()</p>
}
<p>
<input type="search" name="searchTerm" id="searchTerm" class="newUser"
data-scd-autocomplete="#Url.Action("AutocompleteUser", "DataService")" style = "width:300px;"/>
<input type="hidden" id="searchId" name="searchId"/>
</p>
<table>
<tr>
<th>Forename</th>
<th>Surname</th>
<th>Created</th>
<th>Administrator?</th>
<th></th>
</tr>
<tbody id="userList">
#Html.Partial("_UserLine")
</tbody>
</table>
</form>
My partial view looks like
#model IEnumerable<UserLineViewModel>
#Html.EditorForModel()
And the editor template so far looks like
#model UserLineViewModel
<tr>
<td>
#Html.DisplayFor(x => x.Employee.Forename)
</td>
<td>
#Html.DisplayFor(x => x.Employee.Surname)
</td>
<td>
#Html.DisplayFor(x => x.UserScd.Created)
</td>
<td>
#Html.EditorFor(x => x.UserScd.AdminFlag)
</td>
<td>
#Html.ActionLink("Remove", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
When the user selects a person from autocomplete, this method on the DataService controller gets called;
public ActionResult AddNewUser(int searchId)
{
var opStatus = this.UserScdRepository.Add(searchId);
if (!opStatus.Status)
{
return this.PartialView("_UserLine", new List<UserLineViewModel>());
}
this.TempData["SuccessMessage"] =
string.Format("You successfully entered the new user {0}",
vw_EmployeesAndJobTitles.GetName(searchId)).MessageTime();
var employeesInScd = this.EmployeeRepository.GetEmployeesInScd();
UserLineViewModel.UserScdRepository = this.UserScdRepository;
var userList = employeesInScd.Select(employee => new UserLineViewModel(employee)).ToList();
return this.PartialView("_UserLine", userList);
}
And the Jquery/Ajax that joins this up looks like;
var ajaxFormSubmit = function () {
var $form = $(this);
var options = {
url: $form.attr("action"),
type: $form.attr("method"),
data: $form.serialize()
};
$.ajax(options).done(function (data) {
var $target = $($form.attr("data-scd-target"));
var $newHtml = $(data);
$target.replaceWith($newHtml);
$newHtml.effect("highlight");
});
return false;
};
var showOtherTrade = function (data) {
$('#searchOtherTrade').val('');
$('#otherTradeList').html(data);
};
var updateAutocompleteForm = function (event, ui) {
var $input = $(this);
if ($input.hasClass("newUser")) {
$('#searchId').val(ui.item.id);
var $form = $input.parents("form:first");
$form.submit();
}
$input.attr('readonly', 'readonly');
$input.css('font-size', '11px');
$input.selectRange(0, 50);
}
};
var createAutocomplete = function () {
var $input = $(this);
var options = {
source: $input.attr("data-scd-autocomplete"),
select: updateAutocompleteForm
};
$input.autocomplete(options);
};
Now the problem I am getting is that when I return the PartialView in the controller, the data is not rendered in the HTML of the Partial View. So how do I fix this?

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.

Child JSON object undefined when its not

I have an array of JSON objects in the page source that all work except a child object (category).
Here is the code in cshtml:
<script type="text/javascript">
var initialData = #Html.Raw(Json.Encode(ViewBag.OfferItems));
</script>
Here is the resulting page source:
<script type="text/javascript">
var initialData = [{"Id":1,"Name":"Item1","ProductVariantLinks":[{"category":{"Id":2,"Name":"Basic Pizza","Products":null},"product":{"Id":1,"Name":"Margherita","Description":null,"ProductVariants":null},"productVariant":{"Id":1,"Name":"10 inch"}},{"category":{"Id":2,"Name":"Basic Pizza","Products":null},"product":{"Id":2,"Name":"Zeno","Description":null,"ProductVariants":null},"productVariant":{"Id":4,"Name":"8 inch"}}]},{"Id":2,"Name":"Item2","ProductVariantLinks":[]}];
</script>
As far as I can tell category is there and contains properties, but it appears as undefined in IE's debugger.
Is there something I'm missing?
P.S. the JSON is valid.
Update
I'm using knockoutjs and category is in inialdata until it does ko.applybindings. I'm not sure why it would do this, code below:
/// <reference path="jquery-1.5.1.js" />
/// <reference path="knockout-2.0.0.js" />
var ProductVariantLink = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.productVariant = ko.observable();
// Whenever the category changes, reset the product selection
self.category.subscribe(function() {
self.product(undefined);
self.productVariant(undefined);
});
};
var OfferItem = function() {
var self = this;
self.Name = new String();
self.ProductVariants = new Array();
};
var SpecialOfferItemModel = function (specialOfferItems) {
var self = this;
self.specialOfferItems = ko.observableArray(ko.utils.arrayMap(specialOfferItems, function (specialOfferItem) {
return { Id: specialOfferItem.Id, Name: specialOfferItem.Name, ProductVariants: ko.observableArray(specialOfferItem.ProductVariantLinks) };
}));
self.addSpecialOfferItem = function () {
self.specialOfferItems.push({
Id: "",
Name: "",
ProductVariants: ko.observableArray()
});
};
self.removeSpecialOfferItem = function (specialOfferItem) {
self.specialOfferItems.remove(specialOfferItem);
};
self.addProductVariant = function (specialOfferItem) {
specialOfferItem.ProductVariants.push(new ProductVariantLink());
};
self.removeProductVariant = function (ProductVariant) {
$.each(self.specialOfferItems(), function () { this.ProductVariants.remove(ProductVariant) })
};
self.save = function () {
var OfferItems = new Array();
$.each(self.specialOfferItems(),
function () {
var item = this;
var offer = new OfferItem();
offer.Name = item.Name;
$.each(item.ProductVariants(),
function () {
offer.ProductVariants.push(this.ProductVariant);
});
OfferItems.push(offer);
});
self.lastSavedJson(JSON.stringify(ko.toJS(self.specialOfferItems()), null, 2));
return false;
};
self.lastSavedJson = ko.observable("");
};
var model = new SpecialOfferItemModel(initialData);
ko.applyBindings(model);
$(function () {
$('#myForm').submit(function () {
model.save();
});
});
<table class="specialOfferItemsEditor">
<tr>
<th>
</th>
<th>
Name
</th>
<th>
ProductVariants
</th>
</tr>
<tbody data-bind="foreach: specialOfferItems">
<tr>
<td>
<div>
Delete</div>
</td>
<td>
<input data-bind="value: Name" />
</td>
<td>
<table>
<tr>
<th>
Category
</th>
<th>
Product
</th>
<th>
ProductVariant
</th>
</tr>
<tbody data-bind="foreach: ProductVariants">
<tr>
<td>
<select data-bind='options: ProductCategories, optionsText: "Name", optionsCaption: "Select...", value: category, uniqueName: true'>
</select>
</td>
<td data-bind="with: category">
<select data-bind='options: Products, optionsText: "Name", optionsCaption: "Select...", value: $parent.product, uniqueName: true' >
</select>
</td>
<td data-bind="with: product">
<select data-bind='options: ProductVariants, optionsText: "Name", optionsCaption: "Select...", value: $parent.ProductVariant, uniqueName: true'
>
</select>
</td>
<td>
<a href='#' data-bind='click: $root.removeProductVariant'>Delete</a>
</td>
</tr>
</tbody>
</table>
<a href='#' data-bind='click: $root.addProductVariant'>Add Product Variant</a>
</td>
</tr>
</tbody>
</table>
The JSON isn't coming in as you're expecting. I assigned the JSON string you provided above and my IE debugger was able to find 'category' without any issues.
Screenshot: https://i.stack.imgur.com/cI60U.jpg
Try to console.log (or alert) JSON.stringify(initialData);

Resources