Knockout.js How should I add computed value to observaleArray? - javascript

I need to add to observableArray computed values, my current code is look like this:
self.prices = ko.observableArray();
.....
var pax = $("select#ticketsnumber option:selected").val();
var irt;
if ($("input[name='isrt']").is(":checked")) {
irt = 1;
} else {
irt = 0;
}
$.each(self.prices(), function (price) {
price.FinalPrice = ko.computed(function() {
return prices.Price * irt * parseInt(pax);
});
});
But I do not have any idea how should I call binding of this computed value (currently this way - <span name="totalprice" data-bind="text: ko.utils.unwrapObservable($data.FinalPrice)">) as well as this just seemed like computed value has not been added - binding result show 0.
Data model:
public class PriceItem
{
...
public string Price { get; set; }
...
public int FinalPrice { get; set; }
}
This is the way how I retrieve data to self.prices:
self.getprices = function () {
var destinationtypefrom = $("select#optionsfrom option:selected").attr("name");
var destinationtypeto = $("select#optionsto option:selected").attr("name");
var fromcode = $("select#optionsfrom").val();
var tocode = $("select#optionsto").val();
var json = { desttypefrom: destinationtypefrom, desttypeto: destinationtypeto, codefrom: fromcode, codeto: tocode, 'DepartureDate': $("#departure").val(), 'ReturnDate': $("#return").val() };
$.ajax({
url: "/Home/GetFlights",
data: json,
type: "post",
cache: false,
success: function (result) {
if (result.Error == null) {
self.prices(result);
ko.mapping.fromJS(result, {}, self.prices);
} else {
$("#modalerror").on("show.bs.modal", function () {
var modal = $(this);
modal.find("#errormsg").text(result.Error);
});
}
},
error: function (xhr) {
var err = eval("(" + xhr.responseText + ")");
alert(err.Message);
}
});
}

The main problem is that your $.each is wrong by assuming the first argument to be the actual element in the prices array. In fact, the first argument is the index, the second argument is the actual price you want to augment.
Also, you seem to have a typo in the computed function calculation, it's price.Price instead of prices.Price.
Try this:
$.each(self.prices(), function (index, price) {
price.FinalPrice = ko.computed(function() {
return price.Price * irt * parseInt(pax);
});
});
And in your HTML, something like:
<div data-bind="foreach: prices">
<span name="totalprice" data-bind="text: FinalPrice"></span>
</div>
See Fiddle

Related

Cannot get DOT NET CORE MVC to return multiple LINQ results to a view

I am able to pass one value from the controller method, see first cal query to return just the calorie value from the SQL LINQ query. However, I cannot get two colums, see my code controller code and javascript in the view below (I cannot work out what is causing zero data to be populated
I can see var mytext in javascript code return : {"foodCal":101,"foodVarient":"Vegan"}
public JsonResult GetCalories(string Productdata)
{
//THis is the orginal code which returns one value, CALORIES
//var calquery = (from c in _context.Foods
// where c.FoodName == Productdata
// select c.FoodCal).FirstOrDefault();
var calquery = (from c in _context.Foods
where c.FoodName == Productdata
select new
{ c.FoodCal, c.FoodVarient }
).FirstOrDefault();
if (calquery != null)
{
return Json(calquery);
}
else
{
calquery = null;
return Json(calquery);
}
}
<script src="/lib/jquery/dist/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function ()
{
document.getElementById("FoodType").onchange = function ()
{
$.ajax({
type: 'POST',
dataType: 'json',
url: '#Url.Action("GetCalories")',
data: { Productdata: document.getElementById("FoodType").value },
success: function (data)
{
var mytext = JSON.stringify(data);
alert(mytext);
var obj = JSON.parse(json);
document.getElementById("FoodCalories").value = obj.foodCal;
document.getElementById("FoodHealth").value = obj.foodVarient;
}
});
}
});
</script>
I figured out the problem
var object =JSON.parse(mytext), not json
problem solved.
Hope other people benefit from this code.

KnockoutJS - Observable Array of Observable objects with Data from SQL server

I am working on a simple proof of concept for a web app
I would like to know how to achieve the above please.
I have a class of items I am retrieving in a API from SQL server. The simple structure of the Class is
public partial class ReqsTest
{
public string ID { get; set; }
public string Requisition { get; set; }
public Nullable<System.DateTime> DateReqnRaised { get; set; }
public Nullable<decimal> ReqnValue { get; set; }
public Nullable<decimal> ApprovedValue { get; set; }
public decimal Line { get; set; }
public long INDX { get; set; }
public string ReqStatus { get; set; }
public string ReqBackground { get; set; }
}
I am populating a Knockout Observable Array with the data return from the server
My View Model code is
var self = this;
self.reqs = ko.observableArray();
self.error = ko.observable();
var reqsUri = '/api/ReqsTests/';
function ajaxHelper(uri, method, data) {
self.error(''); // Clear error message
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
function getAllReqs() {
ajaxHelper(reqsUri, 'GET').done(function (data) {
self.reqs(data);
});
}
The problem is that of course I now know the underlying object properties in the array are not observable as in this question here
I am trying to understand how to use this code here to bridge the gap but I do fully understand the calls
I believe I will need this sort of function to create the object with the Observable properties for updating later, such as something like this
function Item(ID, Requistion,DateReqnRaised,ReqnValue,ApprovedValue,Line,INDX,ReqStatus,ReqBackground) {
//Not editable properties
this.ID = ID;
this.Requistion = Requistion;//Not editable
this.DateReqnRaised = DateReqnRaised;//Not editable
this.ReqnValue = ReqnValue; //Not editable
this.Line = Line;
this.INDX = INDX;
//editable later properties
this.ApprovedValue = ko.observable(ApprovedValue);
this.ReqStatus = ko.observable(ReqStatus);
this.ReqBackground = ko.observable(ReqBackground);
}
But that maybe not quite right yet and I believe I need to change the code here to but I am not certain how to call the item function with it. It feels like I need to loop through each return in data to call the function item to add it to the observable array but I am not certain yet.
function getAllReqs() {
ajaxHelper(reqsUri, 'GET').done(function (data) {
self.reqs(data);
});
}
Can any one help please
****UPDATED CODE****
Index.cshtml code
<div class="page-header">
<h1>Chamberlin Requistions</h1>
</div>
<div class="row">
<div class="col-xs-4">
<div class="panel panel-default" >
<div class="panel-heading">
<h2 class="panel-title">Requistions</h2>
</div>
<div class="panel-body panel-info ">
<ul class="list-unstyled" data-bind="foreach: Reqs">
<li>
<div >
<strong>
<span data-bind="text: reqs().Requisition"></span>
: <span data-bind="text: reqs().Line"></span>
</strong>
</div>
</li>
</ul>
</div>
</div>
<div class="alert alert-danger" data-bind="visible: error"><p data-bind="text: error"></p></div>
</div>
</div>
As requested the update code for the View model
function ReqsTest(rt) {
rt = rt || {};
var self = this;
self.id = ko.observable(rt.ID || 0);
self.requisition = ko.observable(rt.Requisition || "");
self.dateReqnRaised = ko.observable(rt.DateReqnRaised || null);
self.reqnValue = ko.observable(rt.ReqnValue || null);
self.approvedValue = ko.observable(rt.ApprovedValue || null);
self.line = ko.observable(rt.Line || 0.00);
self.indx = ko.observable(rt.INDX || 0);
self.reqStatus = ko.observable(rt.ReqStatus || "");
self.reqBackground = ko.observable(rt.ReqBackground || ""); }
function ReqsViewModel (){
var self = this;
self.Reqs = ko.observableArray([]);
self.error = ko.observable();
var reqsUri = '/api/ReqsTests/';
function ajaxHelper(uri, method, data) {
self.error(''); // Clear error message
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
function getAllReqs() {
ajaxHelper(reqsUri, 'GET').done(function (data) {
// Build the ReqsTest objects
var reqs = ko.utils.arrayMap(data, function (rt) {
return new ReqsTest(rt);
});
self.Reqs(reqs);
});
}
// Load the reqs - Take this out if you don't want it
getAllReqs(); }
//Details
self.detail = ko.observable();
self.getReqDetail = function (item) {
ajaxHelper(reqsUri + item.INDX, 'GET').done(function (data) {
self.detail(data);
});
}
ko.applyBindings(new ReqsViewModel());
Thank you
First make a matching JavaScript function for your ReqsTest class.
function ReqsTest(rt) {
rt = rt || {};
var self = this;
self.id = ko.observable(rt.ID || 0);
self.requisition = ko.observable(rt.Requisition || "");
self.dateReqnRaised = ko.observable(rt.DateReqnRaised || null);
self.reqnValue = ko.observable(rt.ReqnValue || null);
self.approvedValue = ko.observable(rt.ApprovedValue || null);
self.line = ko.observable(rt.Line || 0.00);
self.indx = ko.observable(rt.INDX || 0);
self.reqStatus = ko.observable(rt.ReqStatus || "");
self.reqBackground = ko.observable(rt.ReqBackground || "");
}
Then make a viewmodel to bind to the page.
function ReqsViewModel {
var self = this;
self.reqs = ko.observableArray([]);
self.error = ko.observable();
var reqsUri = '/api/ReqsTests/';
function ajaxHelper(uri, method, data) {
self.error(''); // Clear error message
return $.ajax({
type: method,
url: uri,
dataType: 'json',
contentType: 'application/json',
data: data ? JSON.stringify(data) : null
}).fail(function (jqXHR, textStatus, errorThrown) {
self.error(errorThrown);
});
}
function getAllReqs() {
ajaxHelper(reqsUri, 'GET').done(function (data) {
// Build the ReqsTest objects
var reqs = ko.utils.arrayMap(data, function(rt) {
return new ReqsTest(rt);
});
self.reqs(reqs);
});
}
// Load the reqs - Take this out if you don't want it
getAllReqs();
}
And bind the viewmodel to the page...
ko.applyBindings(new ReqsViewModel());
You now have an observable array of objects with observable properties.
I hand typed this code in so there may be a few syntax errors

Custom attr() method cannot be given a different name

I have the following perfectly working code (written by someone else). The problem is that if I simply rename the attr method, I get an error. For example, I rename the method to attrx and get this error:
TypeError: arg.attrx is not a function
Here is the working code:
function Action(name) {
this.attr = function(n) {
if (n=="name") {
return "action";
}
},
this.val = function() {
return name;
};
}
Action.prototype.toString = function() {
return "&" + this.attr("name") + "=" + this.val();
}
When a user triggers an event, the following function is called:
function serializeElements() {
var result = "";
for(var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
result += (arg.attr("name") + "=" + arg.val() + "&");
}
return result;
}
Here is the identical code above but it has the attr method renamed to attrx:
function Action(name) {
this.attrx = function(n) {
if (n=="name") {
return "action";
}
},
this.val = function() {
return name;
};
}
Action.prototype.toString = function() {
return "&" + this.attrx("name") + "=" + this.val();
}
function serializeElements() {
var result = "";
for(var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
result += (arg.attrx("name") + "=" + arg.val() + "&");
}
return result;
}
I cannot figure out the reason that the code does not work (see error at top of message) after I rename the method to attrx or anything else for that matter.
Note: The web page does include jQuery, but I don't think that is what causes the problem.
Here is the code used to call serializeElements:
function addStatesListener() {
$("#states").on("change", function(e) {
var form = $(this.form);
var url = form.attr("action");
var type = form.attr("method");
// pass 1) jQuery 'country' and 'state' objects and 2) a query string fragment, e.g.: '&action=change_state'
var data = serializeElements($("#countries"), $("#states"), new Action("change_state"));
e.preventDefault();
$.ajax({
url: url,
type: type,
data: data,
dataType: "html", // The type of data that you're expecting back from the server
success: function(result) {
$("#cities").html(result); // list of all cities, e.g.: <option value="Albany"</option>
}
});
});
}
The proper answer to your question is want already catch #dinesh, you are passing 3 arguments to your function, and only the third is an Action with the .attrx method you changed.
Considering you are working on jquery objects, and if you want to clean your code, you could use .serialize() method instead of calling the couple .attr() and .val().
.serialize() is the jquery method to serialize form objects.
So you can change your code as follow:
function Action(name) {
this.serialize=function() {
return 'name='+encodeURI(name);
}
}
And then your function serializeElements:
function serializeElements() {
var args = Array.prototype.slice.call(arguments);
return args.reduce(function(a, b){
if (a) return a.serialize() + '&' + b.serialize();
if (b) return b.serialize();
});
}
Then you can call it so:
var data = serializeElements($("#countries,#states"), new Action("change_state"));
As you see, you could put form elements in a comma separated list on jquery selector.
That's it.

Send single item of list from view to controller via AJAX

I have a view composed of a list of users:
#model List<LabelPrinting.Models.UserModel>
and I put these into a JavaScript object:
users = function () { return #Html.Raw(Json.Encode(Model)) }();
I then load a jQuery accordion with the values for each. I then choose to print avery labels for a particular user in the list and set a value. I'm trying to send only that particular user to the controller and am getting a null values for of the user properties:
function PrintAveryLabel(but) {
var id = but.id.substring(9);
var $rad = $(but).closest('tr').siblings().find('.radLabelOther');
if (($rad).is(':checked')) {
var $txtr = $rad.closest('tr').siblings().find('.classRows');
var $txtc = $rad.closest('tr').siblings().find('.classCols');
if ($txtr.val() === "0" || $txtc.val() === "0") {
$("#lblError").text("You have have not selected the rows and columns for the labels.");
$("#MessageDialog").dialog({ title: "Select Rows/Columns" });
$("#MessageDialog").dialog("open");
return false;
}
}
var data = findUser(id);
$.ajax({
type: 'POST',
data: { pat: data },
url: '#Url.Action("PrintUserLabels")'
});
}
The findUser function simply picks the entry in the list that matches the ID.
function findUser(id) {
var data;
for (i = 0; i < cnt; i++) {
if (users[i].UserId === parseInt(id)) {
data = users[i];
break;
}
}
return data;
}
My controller action:
[HttpPost]
public ActionResult PrintUserLabels(UserModel pat)
{
string retval = "";
if (pat.PrintLabel)
{
return RedirectToAction("Label", new { user = pat });
}
else
{
retval = "ERROR - you must make a selection";
Exception e = new Exception(retval);
HandleErrorInfo info = new HandleErrorInfo(e, "DYMO", "PrintPatientLabels");
return RedirectToAction("Error", info);
}
}
The label action is tried and true but I get a null user model. What am I doing wrong?

JavaScript Revealing Module Pattern Variable Scope

I have an issue where I can't get to a variable inside a function:
EDIT
I forgot to add that I am setting this workerPage.grid = $("#grid").data("kendoGrid"); on the jQuery $(function(){});
I can't use claimsGird variable inside the save function, I have to referenec it by workerPage.grid. Not the other variables like viewModel work fine. Here is the snippet:
save = function () {
saif.kendoGridUtils.addModifiedDataItems(
viewModel.CompanionClaims.Updated,
viewModel.CompanionClaims.Added,
$("#grid").data("kendoGrid").dataSource.data()
);
$.ajax({
url: $("#contentForm").attr("action"),
data: JSON.stringify(viewModel),
type: "POST",
contentType: "application/json"
}).success(function (data) {
//syncs viewModel with changes in model
$.extend(viewModel, kendo.observable(data));
//rebinds the grid data source
claimsGrid.dataSource.data(viewModel.CompanionClaims.Rows);
Here is the full script:
var workerPage = (function () {
var viewModel = kendo.observable(#Html.Raw(Json.Encode(Model))),
claimsGrid = null,
deleteFirm = function (firmModel) {
firmModel.Name = "";
firmModel.AttorneyName = "";
firmModel.Address.Line1 = "";
firmModel.Address.Line2 = "";
firmModel.Address.City = "";
firmModel.Address.State = "OR";
firmModel.Address.ZipCode = "";
firmModel.Address.PlusFourCode = "";
firmModel.PhoneNumber = "";
firmModel.FaxNumber = "";
firmModel.ContactName = "";
},
bind = function () {
kendo.bind($("#main-content"), viewModel);
},
save = function () {
saif.kendoGridUtils.addModifiedDataItems(
viewModel.CompanionClaims.Updated,
viewModel.CompanionClaims.Added,
$("#grid").data("kendoGrid").dataSource.data()
);
$.ajax({
url: $("#contentForm").attr("action"),
data: JSON.stringify(viewModel),
type: "POST",
contentType: "application/json"
}).success(function (data) {
//syncs viewModel with changes in model
$.extend(viewModel, kendo.observable(data));
//rebinds the grid data source
claimsGrid.dataSource.data(viewModel.CompanionClaims.Rows);
//rebinds view elements to view model so changes are visible
//kendo.bind($("#main-content"), viewModel);
bind();
// Errors and Warnings
var results = messageUtils.parseMessages(
viewModel.Messages.Errors,
viewModel.Messages.Informationals,
viewModel.Messages.Warnings
);
var errs = $("#errors").html(results.errorMessages);
$("#informationals").html(results.informationalMessages);
$("#warnings").html(results.warningMessages);
$.each(saif.kendoGridUtils.processErrors(viewModel.CompanionClaims.Rows), function (i, message) {
errs.html(errs.html() + message + "<br>");
});
// End Errors and Warnings
});
},
deleteRow = function () {
var row = claimsGrid.select(),
rowDataItem = claimsGrid.dataItem(row),
rowIndex = $(row).index(),
addedItemIndex = $.inArray(rowDataItem, viewModel.CompanionClaims.Added);
//add to Deleted if not new
if (addedItemIndex == -1 && $.inArray(rowDataItem, viewModel.CompanionClaims.Rows) != -1) {
viewModel.CompanionClaims.Deleted.push(rowDataItem);
}
//remove from Added if exists
if (addedItemIndex != -1) {
viewModel.CompanionClaims.Added.splice(addedItemIndex, 1);
}
claimsGrid.removeRow(row);
//select the next row, eg. if you delete row 2, select the row that took that rows poisition after it was deleted.
claimsGrid.select(claimsGrid.tbody.find(">tr:eq(" + rowIndex + ")"));
};
return {
bind: bind,
deleteFirm: deleteFirm,
deleteRow: deleteRow,
grid: claimsGrid,
save: save,
viewModel: viewModel
};
}());
The issue is that claimsGrid is never set to anything other than null. And setting workerPage.grid won't change the value of claimsGrid -- it's not a pointer, just a copy.
You'll instead have to use a getter/setter. With newer browsers/engines, that can be done with get and set:
// ...
return {
// ...
get grid() {
return claimsGrid;
},
set grid(grid) {
claimsGrid = grid;
},
// ...
};
You can also define grid as a function:
// ...
function getOrSetGrid(grid) {
if (typeof newGrid === 'undefined') {
return claimsGrid;
} else {
claimsGrid = grid;
}
}
return {
// ...,
grid: getOrSetGrid,
// ...
};
// ...
// rather than: workerPage.grid = ...;
workerPage.grid(...);
Or split it into getGrid and setGrid functions.
Scope in javascript works differently than other languages like Java or C#. In your case claimsGrid is not in scope for the save function. Does this help? http://coding.smashingmagazine.com/2009/08/01/what-you-need-to-know-about-javascript-scope/

Categories

Resources