How do I pass an ID from View to the ViewModel as a parameter for GET function? - asp.net-mvc

I'm creating a project using MVC, knockoutJS, Web API, Bootstrap and so forth, the database in use is MSSQL Server 2012. It's all working very well, the controllers have properly created CRUD operations. The data from DB is show in a grid table in the UI, and every row is clickable, and opens up a modal in which the data about that exact element is shown. The problem I'm experiencing is the inability to pass a certain value of the row, in this case an ID, to ViewModel as a parameter for getting a single result in modal. I can do it manually, and put some value in the ViewModel, and the data will show, but I'm unable to send the value from the View.
Here's the code for ViewModel:
var regionsModel = {
regionId: ko.observable(),
companyId: ko.observable(),
name: ko.observable(),
companyName: ko.observable()
};
var regionsListModel = {
regions: ko.observable()
};
function getRegions() {
get(apiUrl + "Regions/GetRegions", {}, function (data) {
regionsListModel.regions(data);
});
}
function getRegion() {
get(apiUrl + "Regions/GetRegion", { aiId: regionsModel.regionId() }, function (data) {
regionsModel.regionId(data.RegionID);
regionsModel.companyName(data.CompanyName);
regionsModel.companyId(data.CompanyID);
regionsModel.name(data.Name);
});
}
function viewRegion() {
$("#ViewRegionModal").modal('show');
//regionsModel.regionId($('#ViewRegion').val());
getRegion();
return false;
}
Here's the code for the View:
<table class="table table-striped table-bordered responsive" id="dtable">
<thead>
<tr>
<th style="width: 20px;">ID</th>
<th>Region Name</th>
<th>Company Name</th>
</tr>
</thead>
<tbody data-bind="foreach: regionsListModel.regions">
<tr id="ViewRegion" data-toggle="modal" data-bind="click: viewRegion, value: RegionID">
<td data-bind="text: RegionID"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: CompanyName"></td>
</tr>
</tbody>
</table>
aiId parameter is for the GetRegion method in Controller.
This is the code for the View in which shows the data for a certain element:
<table class="table table-striped" data-bind="with: regionsModel">
<tbody>
<tr>
<th>Region ID:</th>
<td><span data-bind="text: regionsModel.regionId"></span></td>
</tr>
<tr>
<th>Region Name:</th>
<td><span data-bind="text: regionsModel.name"></span></td>
</tr>
<tr>
<th>Company Name:</th>
<td><span data-bind="text: regionsModel.companyName"></span></td>
</tr>
</tbody>
</table>
Any help would be appreciated!

Knockout adds the current bound object as first argument when it calls the event handler.
The second argument is the event object.
So the only thing you need to do is add a parameter to the viewRegion function.
function viewRegion(region) {
var regionID = region.RegionID;
// get data
return false;
}
I hope it helps.

Related

Kendo UI Grid - issue creating data-id row attributes

I'm working on a Asp.Net MVC 5 Web Pages project, where one of the partial views generates an Html table. It of course mixes the Razor syntax in with Html.
Now I need to implement this table as a Kendo UI Grid, where the HTML table is used as the DataSource (see this as a reference - http://demos.telerik.com/kendo-ui/grid/from-table).
Great so far - and yes, my new Kendo grid does in fact render nicely.
ex/
Only problem is that the Kendo Grid now seems to be overwriting the <tr> data-id attributes I'm injecting into each row.
So how would I turn this into a Kendo rowTemplate ?
Before implementing the Kendo Grid, here is the Html table as rendered:
<table id="summary-table" class="table table-hover table-extra-condensed">
<tr data-id="80" data-nodes="2008-08-08" data-title="EUREX IRS" data-subtitle="All">
<td class="hidden">80</td>
<td><i class="fa fa-chevron-right"></i> EUREX IRS</td>
<td class="text-right">USD</td>
</tr>
<tr data-id="50" data-nodes="2008-08-08" data-title="EUREX IRS" data-subtitle="IRS-EUR">
<td class="hidden">50</td>
<td><span class="indent">IRS-EUR</span></td>
<td class="text-right">USD</td>
</tr>
</tbody>
</table>
Here is the new table, now rendered as a Kendo grid (Kendo added data-uid but I lost my custom 'data-' row attribs):
<table id="summary-grid" class="table table-hover table-extra-condensed" data-role="grid" role="grid">
<tr data-uid="fcc1ffab-f1e7-4ea4-9b79-7c4149bbbfa3" role="row">
<td style="display:none" role="gridcell">80</td>
<td role="gridcell"><i class="fa fa-chevron-right"></i> EUREX IRS</td>
<td role="gridcell">USD</td>
</tr>
</table>
Here's the partial.cshtml file snippet:
#model IEnumerable<WhatifSummaryViewModel>
#{
string guid = Guid.NewGuid().ToString();
}
<table id="summary-grid" class="table table-hover table-extra-condensed">
<thead>
<tr>
<th data-field="id" hidden class="hidden">Id</th>
<th data-field="product">#Settings.Whatif.SummaryPortfolioName1 - #Settings.Whatif.SummaryPortfolioName2</th>
<th data-field="currency" class="text-right">Currency</th>
<th data-field="margin" class="text-right">#Settings.Whatif.SummaryCurrentExposureName</th>
<th data-field="wi" class="text-right hidden-xs">What-If</th>
<th data-field="impact" class="text-right">Impact</th>
<th data-field="chart" class="text-center hidden-xs">View</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count(); ++i)
{
string title;
if (Model.ElementAt(i).SubTitle.Equals("ALL", StringComparison.OrdinalIgnoreCase))
{
title = "<i class='fa fa-chevron-right'></i> " + Model.ElementAt(i).Title;
}
else
{
title = "<span class='indent'>" + Model.ElementAt(i).SubTitle + "</span>";
}
<tr data-id="#Model.ElementAt(i).PortfolioId" data-nodes="#String.Join(",", Model.ElementAt(i).NodeDates)" data-title="#Model.ElementAt(i).Title" data-subtitle="#Model.ElementAt(i).SubTitle">
<td class="hidden">#Model.ElementAt(i).PortfolioId</td>
<td>#Html.Raw(title)</td>
<td class="text-right">#Model.ElementAt(i).Currency</td>
<td class="text-right">#String.Format("{0:#,0}", Model.ElementAt(i).Utilisation)</td>
<td class="text-right hidden-xs">#(Model.ElementAt(i).WhatIfRun ? String.Format("{0:#,0}", Model.ElementAt(i).WhatifExposure) : "")</td>
<td class="text-right">#(Model.ElementAt(i).WhatIfRun ? String.Format("{0:#,0}", Model.ElementAt(i).ImpactToExposure) : "")</td>
<td class="text-center actions hidden-xs"><i class="fa fa-bar-chart"></i></td>
</tr>
}
</tbody>
</table>
and finally, the Kendo grid which is configured in my onReady javascript function:
(NOTE: the rowTemplate below is commented, and certainly does cause a scripting error if uncommented).
// Kendo UI grid
$("#summary-grid").kendoGrid({
resizable: true,
toolbar: ["pdf","excel"],
columns: [
{ field: "PortfolioId", hidden: true },
{ field: "title"},
{ field: "Currency", title: "Currency" },
{ field: "Utilisation", title: "Margin" },
{ field: "WhatifExposure", title: "What-if"},
{ field: "ImpactToExposure", title: "Impact" },
{ field: "Chart", title: "View" }
]
#*rowTemplate: '<tr data-uid="#= uid #" data-id="#= #Model.ElementAt(i).PortfolioId #" data-title="#= #" >'*#
});
In the end, I'd like to add my custom data- row attribs. I was thinking that the kendo template would work, but I can't figure it out in this Web Pages Razor context.
Help is appreciated...
Bob

How to pass data from one view to other

I Have a table, there i have a details hyperlink on click of which i m showing a new table on same page in other div. The details Hyperlink has 3 input parameters based on which a service method gets triggered and I get data in the data. Now i have this task to show the 2nd grid on a new page. I m not sure how to do it.
Please help.
<table class="table table-bordered table-condensed table-hover ">
<thead>
<tr style="white-space: nowrap;">
<th>Date</th>
<th>Organization No</th>
<th>Contract No</th>
<th>Company Name</th>
<th>Plan No</th>
<th>Status</th>
<th>View Detail</th>
</tr>
</thead>
<tr ng-repeat="mas in vm | startFrom:currentPage*pageSize | limitTo:pageSize" data-ng-class="{active1:$index==selectedRow}" data-ng-click="rowHighilited($index)">
<td>{{mas.startDate | amDateFormat:'YYYY-MM-DD'}} </td>
<td>{{mas.organizationNumber}}</td>
<td>{{mas.contractNumber}} </td>
<td>{{mas.name}}</td>
<td>{{mas.planNumber}} </td>
<td>{{mas.description}} </td>
<td><span>Details</span></td>
</tr>
</table>
//getErrorDetailBySearch(mas.productAccountOid,mas.planNumber,mas.migrationRunID)it calls the function to bring data an binds it to the below div.//
<div ng-show="IsVisible">
<div ng-show="vm1.length > 0 && !loading">
<h2>Error Detail</h2>
<br />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12 table-responsive">
<table class="table table-bordered table-condensed table-hover table-striped">
<thead>
<tr style="white-space: nowrap;">
<th>Contract Number</th>
<th>Plan Number</th>
<th>Business Error Message</th>
<th>System Error Details</th>
</tr>
</thead>
<tr ng-repeat="mas in vm1">
<td>{{mas.contractNumber}} </td>
<td>{{mas.planNumber}} </td>
<td>{{mas.businessErrorMsg }} </td>
<td>{{mas.systemErrorMsg}} </td>
</tr>
</table>
</div>
</div>
I am able to call a new page Called as ErrorDeatils but i dont know how to pass data b/w the pages.
This is controller written for 1st page
$scope.getErrorDetailBySearch = function (productAccountOid, planNr, migrationrunid) {
var path = "/Utilities/Error";
$location.path(path.replace(/\s+/g, ""));
};
var onSuccess = function (response) {
$scope.vm1 = response;
if ($scope.vm1 < 1) {
messageService.noDataFound();
}
}
$scope.getErrorDetailBySearch = function (productAccountOid, planNr,migrationrunid, skipFormValidate) {
if (skipFormValidate || b360FormsService.validateForm($scope.ViewErrorDetail)) {
errorService.globalproductAccountOid = productAccountOid;
errorService.globalplanNr = planNr;
errorService.globalmigrationrunid = migrationrunid;
$scope.loading = true;
repositoryService.getErrorDetailBySearch(productAccountOid, planNr, migrationrunid).$promise.then(onSuccess, onError).finally(function () {
$scope.loading = false;
});
}
}
Hi you can pass data both as query parameters or route params
Query Parameters
Lets say you have path "/errorDetails" and you want to pass "param" with value "10", then you can use $location.path() as follows:
$location.path("/errorDetails").search({param: 10});
and you can access value of "param" on resultant page's controller as:
$location.search().param;
Route Parameters
For using route params you will need to change your route configurations and you path will be like:
$routeProvider
.when('/errorDetails/:param', {
title: 'Error Details',
templateUrl: 'path/to/errorDetails.html'
});
Now you can send param using $location.path as follow:
$location.path("/errorDetails/"+param);
And you can access route parameter on resultant page's controller as follows:
$routeParams.param

two-way binding of a single object in observableArray

My page is as follows:
<button id="add">Add Data</button>
<button id="show">show</button>
<table>
<tr style="vertical-align:top">
<td>
<table border="1">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody data-bind="foreach: students">
<tr>
<td data-bind="text: id"></td>
<td>
<input type="text" data-bind="value: name" />
</td>
<td> Select
</td>
</tr>
</tbody>
</table>
</td>
<td>
<table id="data">
<tbody data-bind="with: selectedData">
<tr>
<td>Id</td>
<td>
<input type="text" data-bind="value: id" />
</td>
</tr>
<tr>
<td>Name</td>
<td>
<input type="text" data-bind="value: name" />
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" value="Close" />
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
The javascript is as follows:
function ViewModel() {
var self = this;
self.students = ko.observableArray([]);
self.showData = function (dt) {
if (window.console) console.log(dt);
self.selectedData(dt);
$('#data').show();
}
this.selectedData = ko.observable();
}
$(function () {
window.appViewModel = new ViewModel();
ko.applyBindings(window.appViewModel);
$('#add').click(function () {
var model = window.appViewModel;
$.each(students, function (idx, student) {
if (window.console) console.log(student);
model.students.push(student);
});
$('table').show();
});
$('table').hide();
$('input').click(function () {
$('#data').hide();
});
$('#show').click(function () {
var s = JSON.stringify(window.appViewModel.students());
alert(s);
});
});
Preview:
In pic, I click on the select corresponding to student with id = 3. The other table shows up with the selected student details. Suppose I enter something in textbox 1, textbox 2 doesn't update, and vice versa.
What to do to make that happen?
Fiddle: http://jsfiddle.net/deostroll/YdrQf/1/
Your inputs aren't updating because the id and name values are not being stored or bound against observables, which are the special object that knockout provides specifically for this purpose. You can easily solve this with your code by adding a new Student type:
function Student(data) {
this.id = ko.observable(data.id);
this.name = ko.observable(data.name);
};
and use it to populate your students array with:
$.each(students, function (idx, student) {
if (window.console) console.log(student);
model.students.push(new Student(student));
});
With those properties now being observables, their changes will propagate to the UI. Here is the fiddle, with these two minor changes.
That being said, I think you have largely missed the point of Knockout. I strongly suggest you go through the Knockout tutorials, if you haven't done so already.
You're use of jQuery to create click functions for your viewmodel really goes against the model that Knockout encourages. Please take a look at this fiddle, which converts your code into 100% Knockout, using viewmodel functions, and drops all the jQuery.

How to display No Data when observable array is empty?

I'm new to Knockout.js and I'm trying to display data from observable array to a table.
The problem I have is it generates two tbody tags. But if I move the empty check logic into the foreach: loop, the No Data does showup at all.
Is there a better way to do this using table? I don't like to use ul or ol in this case.
<table>
<thead>
<tr>
<th>Permit</th>
<th>Region</th>
<th>Landowner</th>
</tr>
</thead>
<tbody data-bind="foreach: requestList">
<tr>
<td><span data-bind="text: permit"></span></td>
<td><span data-bind="text: region"></span></td>
<td><span data-bind="text: landowner"></span></td>
</tr>
</tbody>
<tbody data-bind="if: requestList().length === 0">
<tr>
<td colspan="3">No Data</td>
</tr>
</tbody>
</table>
When doing this we make a lot of use of virtual elements. They are outlined here http://knockoutjs.com/documentation/if-binding.html#note_using_if_without_a_container_element
The rest of your markup is fine, but you could wrap your first tbody in a virtual element like this:
<!-- ko if: requestList().length -->
<tbody data-bind="foreach: requestList">
<tr>
<td><span data-bind="text: permit"></span></td>
<td><span data-bind="text: region"></span></td>
<td><span data-bind="text: landowner"></span></td>
<td><button data-bind="click: $parent.remove">Remove</button></td>
</tr>
</tbody>
<!-- /ko -->
JSFiddle here: http://jsfiddle.net/ZKWMh/
Actually, your html markup is fine. I added the following javascript to your markup
$(document).ready(function() {
var a = [{
permit: "permit1",
region: 'region1',
landowner: 'landowner'},
{
permit: "permit2",
region: 'region2',
landowner: 'landowner2'}];
var vm = {};
vm.requestList = ko.observableArray([]);
ko.applyBindings(vm);
$('#loadData').click(function() {
var a1 = ko.mapping.fromJS(a);
var b1 = a1();
vm.requestList(b1);
});
});​
And it seems to be working as you describe how you want things to work. It is working at http://jsfiddle.net/photo_tom/xmk3P/10/

Knockout JS foreach nested, value updates in all fields

I am using knockout js to acheive a task. My model is like:
var ServiceLevelRates = function(data, availableClasses) {
return {
TaxTypeID: ko.observable(data.Key),
TaxTypeName: ko.observable(data.Name),
ExtendedTaxTypeName: data.Name.replace(/\s+/g, ''),
ApplyAfter: ko.observable(-1),
TaxClasses: ko.observableArray(availableClasses)
};
};
var TaxClass = function(data, availableServices) {
return {
ServiceClassID: data.ServiceClassID,
ServiceClassName: ko.observable(data.ServiceClassName),
TaxServices: ko.observableArray(availableServices)
};
};
var TaxService = function(data) {
return {
ServiceID: ko.observable(data.ServiceID),
ServiceName: ko.observable(data.ServiceName),
ServiceRate: ko.observable(data.ServiceRate > 0 ? data.ServiceRate : "").extend({ numeric: 2 })
};
};
and my html is like:
<tbody data-bind="foreach: ServiceLevelRates">
<tr>
<td style="width:100%;">
<table width="100%">
<tr>
<td style="width:2%;">
<img src="../../Images/del_up.gif" onclick="HideMyChilds(this);" />
</td>
<td data-bind="text: TaxTypeName">
</td>
</tr>
<tr>
<td></td>
<td>
<table width="100%">
<tr>
<td style="width:20%;">
<label id="lblApplyAfter" myId="lblApplyAfter" runat="server">Apply After</label>
</td>
<td></td>
</tr>
<tr>
<td>
<select id="sltApplyAfter" SkinID="drpFields" name="sltApplyAfter" runat="server" myId="sltApplyAfter">
<option value="-1">Charge</option>
</select>
</td>
<td>
<input type="checkbox" />Apply for All Services<input type="text" onkeypress="ValidateDecimalValue(event,this)"; onblur="ApplyForAllServices(this);" data-bind="attr: { 'class': ExtendedTaxTypeName }" /> %
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%">
<tbody data-bind="foreach: TaxClasses">
<tr>
<td style="width:2%;">
<img src="../../Images/del_up.gif" onclick="HideMyChilds(this);" />
</td>
<td style="width:100%;" class="tdRepeaterHeaderBG" data-bind="text: ServiceClassName">
</td>
</tr>
<tr>
<td></td>
<td>
<table width="100%">
<thead>
<tr>
<td style="width:1%;">
<td style="width:24%;" class="tdRepeaterHeaderBG">Service Name</td>
<td style="width:75%;" class="tdRepeaterHeaderBG">Amount</td>
</tr>
</thead>
<tbody data-bind="foreach: TaxServices">
<tr>
<td style="width:1%;">
<td style="width:24%;" data-bind="text: ServiceName"></td>
<td style="width:75%;">
<input type="text" data-bind="value: ServiceRate, attr: { 'class': $parents[1].ExtendedTaxTypeName, 'id': $parents[1].ExtendedTaxTypeName + ServiceID }" />%
</td>
</tr>
<tr>
<td></td>
<td colspan="2">
<div style="font-size: 11px; width:98%;height:5px; border-top: 1px dotted gray;"> </div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
The problem is when I provide ServiceRate for a taxservice in one class, it is updated into text field of same service in all other classes. Any help on it will be great.
Your code have several issues.
First, mostly a cosmetic one. You are using tables for layout. They should only be used when you truly need tabular data. Div's or lists are much better in most cases, and if you need to layout something, you could use css margins.
You are mixing, and mixing up, different object schemes.
One is to return an object literal:
function Foo() {
return {
Property: ko.observable(),
}
}
This schema could, but shouldn't, be called with the new operator.
The other one is prototype-based:
function Foo() {
var self = this;
self.Property = ko.observable();
}
This schema must be called with the new operator.
It is easiest to stick to one schema. With knockout, the latter is easier to use in some cases.
You are not using observables for all properties. It is a little confusing to be using observables for some properties, and not for others. You have to go back to the source-code to confirm for each property.
Your object model does not take into account object reuse. You are passing the same objects to each ServiceLevelRate, so when you are updating one TaxService, the same TaxService in all other TaxClass will also be updated.
One simple solution for this, is to factor out the fields that needs updating into mapping objects.
// This part is constructed once, based on server data.
function TaxService(data) {
var self = this;
self.ServiceID = ko.observable(data.ServiceID);
self.ServiceName = ko.observable(data.ServiceName);
}
// This part is constructed for each TaxClassMapping
function TaxServiceMapping(svc) {
var self = this;
self.TaxService = ko.observable(svc);
self.ServiceRate = ko.observable("");
}
Lastly; To conditionally update the rates based on the check-box, you can bind the it with the checked-binding. In the subscription for the ServiceLevelRate-wide rate, you just check if the check-box was checked, before proceeding to update the other fields.
self.ApplyForAll.subscribe(function (newValue) {
if (self.ApplyForAllCheckBox()) {
ko.utils.arrayForEach(self.Classes(), function (clsMapping) {
ko.utils.arrayForEach(clsMapping.ClassServices(), function (svcMapping) {
svcMapping.ServiceRate(newValue);
});
});
}
});
Here is an updated fiddle:
http://jsfiddle.net/MizardX/V8DTj/
I scaled down the models to the essential parts, to make them easier to work with.
To make the TaxServices show only for certain TaxClasses, you could filter which TaxService-objects you want to include for each TaxClass.
function TaxClassMapping(taxClass, availableServices) {
var self = this;
self.TaxClass = ko.observable(taxClass);
var classID = taxClass.ServiceClassID();
var filtered = ko.utils.arrayFilter(availableServices, function (svc) {
// svc.ServiceClassID is a new property in TaxService
return svc.ServiceClassID() === classID;
});
var mapped = ko.utils.arrayMap(filtered, function (svc) {
return new TaxServiceMapping(svc);
});
self.ClassServices = ko.observableArray(mapped);
}

Resources