Knockout computed column with model - javascript

I have a model with the following property in my MVC C# solution
public class RegistrationRequirementModel
{
public string LoadIntent { get; set; }
public string Francophone { get; set; }
public string Gender { get; set; }
public RegistrationRequirementModel(L09RegistrationRequirement requirement)
{
LoadIntent = requirement.LoadIntent;
Francophone = requirement.Francophone;
Gender = requirement.Gender;
}
}
In my javascript I can call the model and display the data, however when it comes to using some computed function that is where it fails.
Javascript
var registrationRequirementModel = {
frenchData: ko.observable(""),
genderData: ko.observable(""),
loadIntentData: ko.observable(""),
isMissingData: ko.computed(function () {
if (this.frenchData() == "") { return true };
if (this.genderData() == "") { return true };
if (this.loadIntentData() == "") { return true };
return false;
},this),
}
$(document).ready(function () {
ko.applyBindings(registrationRequirementModel, document.getElementById("RegistrationSurveyContent"));
$.ajax({
url: getStudentRegRequirementsUrl,
type: "GET",
contentType: jsonContentType,
dataType: "json",
success: function (data) {
if (!account.handleInvalidSessionResponse(data)) {
registrationRequirementModel.frenchData(data.Francophone);
registrationRequirementModel.genderData(data.Gender);
registrationRequirementModel.loadIntentData(data.LoadIntent);
}
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status != 0)
$('#notificationHost').notificationCenter('addNotification', { message: "Unable to retrieve registration requirement.", type: "error" });
}
});
});
Html
<table style="width:100%">
<tbody>
<tr>
<td data-bind="text: loadIntentData"></td>
<td data-bind="text: frenchData"></td>
<td data-bind="text: genderData"></td>
</tr>
</tbody>
</table>
The purpose is to show html if there is missing data. However when I activate this code, the computed column keep saying the frenchData is not a function. My point would be able to use in my html data-bind="visible: isMissingData". but unfortunately. I can event read from my data.
This is my call to the api
public async Task<JsonResult> GetRegistrationRequirementAsync()
{
string StudentID = CurrentUser.PersonId;
try
{
var requirement = await ServiceClient.L09GetRegistrationRequirementAsync(StudentID);
RegistrationRequirementModel registrationRequirementModel = new RegistrationRequirementModel(requirement);
return Json(registrationRequirementModel, JsonRequestBehavior.AllowGet);
}
catch (Exception e)
{}
}

The frenchData is not a function console error stems from the way that the KnockoutJS ViewModel is set up. In essence, the computed function isMissingData below the normal observables has a new inner scope context of this that does not reflect the same outer scope of the registrationRequirementModel object.
To work around this, you should switch from using an object literal to a constructor function so that you can assign this ViewModel scope to a self/that variable which alleviates scope issues. Then instantiate your newly stored ViewModel via KO Apply Bindings that you will now have access to after AJAX success:
function registrationRequirementModel() {
var self = this;
self.frenchData = ko.observable("");
self.genderData = ko.observable("");
self.loadIntentData = ko.observable("");
self.isMissingData = ko.computed(function() {
if (self.frenchData() == "") {
return true
};
if (self.genderData() == "") {
return true
};
if (self.loadIntentData() == "") {
return true
};
return false;
}, this);
}
$(document).ready(function() {
var vm = new registrationRequirementModel();
ko.applyBindings(vm, document.getElementById("RegistrationSurveyContent"));
// replace with endpoint
var jsonData = {
Francophone: "Francophone",
Gender: "Male",
LoadIntent: "LoadIntent"
};
if (handleInvalidSessionResponse(jsonData)) {
vm.frenchData(jsonData.Francophone);
vm.genderData(jsonData.Gender);
vm.loadIntentData(jsonData.LoadIntent);
}
});
function handleInvalidSessionResponse(data) {
if (typeof data !== "undefined") return true;
return false;
}
Below is a mock JSFiddle of the scenario
http://jsfiddle.net/ajxrw39m/3/

When you're defining your viewmodel, this does not point to the newly created object, it points to whatever this is in the context you're creating it (probably window).
var vm = {
computedUsingThis: ko.computed(function() {
return this;
}, this)
}
console.log(
vm.computedUsingThis() === vm, // false
vm.computedUsingThis() === window // true
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
There are many ways to circumvent the issue. You can use a constructor function and the new keyword, or create a factory method for the viewmodel:
const VM = () => {
const a = ko.observable("a");
const b = ko.observable("b");
const ab = ko.pureComputed(
() => a() + b()
);
return { a, b, ab };
}
const vm = VM();
vm.ab.subscribe(console.log);
setTimeout(
() => {
vm.a("A");
},
500
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

Related

Using ENUM with Typescript and Knockout

I am trying to create a basic login screen, as I learn Knockout and TypeScript. I am trying to add a 'PageMode' enum that allows knockout to know what mode we're in and data bind to the pageMode property to display things correctly. However, when the screen loads, 'pageMode' is undefined. How can I get this to work?
I have created 2 models, which will be used by the knockout View Model (Login and Register), and an enum for PageMode.
In design time, pageMode is visiable - but when I run it, it fails as pageMode is undefined.
class LoginModel {
emailAddress: KnockoutObservable<string>;
password: KnockoutObservable<string>;
rememberMe: KnockoutObservable<boolean>;
constructor() {
this.emailAddress = ko.observable("");
this.password = ko.observable("");
this.rememberMe = ko.observable(false);
}
}
class RegisterModel {
emailAddress: KnockoutObservable<string>;
password: KnockoutObservable<string>;
passwordRetry: KnockoutObservable<string>;
constructor() {
this.emailAddress = ko.observable("");
this.password = ko.observable("");
this.passwordRetry = ko.observable("");
}
}
enum PageMode {
LoggingIn,
RecoveringPassword,
Registering
}
class ForgotPassword {
emailAddress: KnockoutObservable<string>;
}
class HomeViewModel {
login: LoginModel;
register: RegisterModel;
pageMode: KnockoutObservable<PageMode>;
isLoginEnabled: KnockoutComputed<boolean>;
constructor() {
this.pageMode(PageMode.LoggingIn);
this.login = new LoginModel();
this.register = new RegisterModel();
this.isLoginEnabled = ko.computed(() => {
return !!this.login.emailAddress() && !!this.login.password();
});
}
ShowRecoverPassword()
{
this.pageMode(PageMode.RecoveringPassword);
}
ShowRegister()
{
this.pageMode(PageMode.Registering);
}
ShowLogin()
{
this.pageMode(PageMode.LoggingIn);
}
}
ko.applyBindings(new HomeViewModel());
On the HTML side, I was hoping to make divs visible via this:
<div id="register-box" data-bind="visible: pageMode() == PageMode.Registering">
However, that might be an issue too, as I don't think the page will know about the enum?
This actually does work. The error 'pageMode' is undefined originates from the fact that pageMode is not instantiated on HomeViewModel
So change
pageMode: KnockoutObservable<PageMode>;
to
pageMode = ko.observable<PageMode>();
In the example below, the typescript is converted to javascript:
var LoginModel = (function () {
function LoginModel() {
this.emailAddress = ko.observable("");
this.password = ko.observable("");
this.rememberMe = ko.observable(false);
}
return LoginModel;
}());
var RegisterModel = (function () {
function RegisterModel() {
this.emailAddress = ko.observable("");
this.password = ko.observable("");
this.passwordRetry = ko.observable("");
}
return RegisterModel;
}());
var PageMode;
(function (PageMode) {
PageMode[PageMode["LoggingIn"] = 0] = "LoggingIn";
PageMode[PageMode["RecoveringPassword"] = 1] = "RecoveringPassword";
PageMode[PageMode["Registering"] = 2] = "Registering";
})(PageMode || (PageMode = {}));
var ForgotPassword = (function () {
function ForgotPassword() {
}
return ForgotPassword;
}());
var HomeViewModel = (function () {
function HomeViewModel() {
var _this = this;
this.pageMode = ko.observable();
this.pageMode(PageMode.LoggingIn);
this.login = new LoginModel();
this.register = new RegisterModel();
this.isLoginEnabled = ko.computed(function () {
return !!_this.login.emailAddress() && !!_this.login.password();
});
}
HomeViewModel.prototype.ShowRecoverPassword = function () {
this.pageMode(PageMode.RecoveringPassword);
};
HomeViewModel.prototype.ShowRegister = function () {
this.pageMode(PageMode.Registering);
};
HomeViewModel.prototype.ShowLogin = function () {
this.pageMode(PageMode.LoggingIn);
};
return HomeViewModel;
}());
ko.applyBindings(new HomeViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div id="register-box" data-bind="visible: pageMode() == PageMode.LoggingIn">Logging in</div>
<div id="register-box" data-bind="visible: pageMode() == PageMode.Registering">Registering</div>
<button data-bind="click: ShowRegister">Show register</button>
A Knockout binding can access properties on the viewmodel, global variables, and properties of the binding context. So a simple solution is to make sure enums are stored globally:
window.PageMode = PageMode;

Get Role parameter as Viewbag from controller and get in js

I have controller where I send role parameter as true or false to js
Controller (relevant code):
public ActionResult GetForEdit()
{
var userRole = User.IsInRole("SuperAdmin");
#ViewBag.Role = userRole;
return Content(result, "application/json");
}
There I get true or false into #Viewbag
Ajax to call controller:
function GetModulLogWasteForEdit() {
debugger;
currentId = 0;
var manifest = $('#manifest').val();
$('#save').removeClass('hidden');
try {
$(function () {
$.ajax({
cache: false,
type: "get",
dataType: "json",
url: "/Log/GetForEdit", // controller
data: { manifest: manifest },
contentType: "application/json; charset=utf-8",
success: onGetModulLogWasteSuccess,
error: function (response) {
ErrorMessage("Error", GetTextError(response));
}
});
});
} catch (e) {
ErrorMessage("Error", e.message);
}
}
and now I populate kendo Grid to show fields, and there is where I want to use my viewbag so:
function onGetModulLogWasteSuccess(response) {
var role = '#ViewBag.Role'; // there I get my role bool
$("#lstInfo").kendoGrid({....
{
field: "", title: "Actions", width: "120px", hidden: role,
template: function (item) {
var id = item.Id;
var dropbox = "<div><div class='btn btn-danger'><a class='light' href='javascript:RemoveLogWaste(" + id + ");' role='button'><i class='fa fa-pencil-square-o'></i> Delete</a></div></div>";
return dropbox;
}
As you can see I use hidden:role parameter, but it always come true, when controller get it to false it hidden field too, Help is very appreciated. Regards
Update:
As Stephen answer, I change my controller to:
public ActionResult GetForEdit(string manifest)
{
string result = string.Empty;
var userRole = User.IsInRole("SuperAdmin");
try
{
result = new JavaScriptSerializer().Serialize(LogWasteModule.GetForEdit(manifest));
}
catch (Exception)
{
throw;
}
return Json(new
{
result,
role = userRole,
JsonRequestBehavior.AllowGet
});
}
GetForedith class:
public static List<LogEdit_Result> GetForEdit(string manifest)
{
var result = new List<LogEdit_Result>();
using (var context = new EF.A())
{
result = context.LogEdit(manifest).ToList();
}
return result;
}
But I get
his request has been blocked because sensitive information could be
disclosed to third party
You can't access the ViewBag from client-side javascript.
It is only available server-side(i.e. in your View/cshtml).
In order to do what you are trying to do, you will need to put the userRole value into the "result" that you are returning and then you will have it client-side, i.e.
public ActionResult GetForEdit()
{
var userRole = User.IsInRole("SuperAdmin");
// ...fill result (not shown)
return Json(new {
result = result,
role = userRole
}, JsonRequestBehavior.AllowGet);
}
And then
function onGetModulLogWasteSuccess(response) {
var role = response.role;
// response.result contains the "result" var from the server action.
$("#lstInfo").kendoGrid({....
}
The reason your role var is always true is because the string '#ViewBag.Role' is really the string '#ViewBag.Role' NOT the contents of the ViewBag(which again is not available client-side), and this string is truthy(evaluates as true when you ask it to be a boolean).
After Question Updated
Change your action to
public ActionResult GetForEdit(string manifest)
{
string result = string.Empty;
var userRole = User.IsInRole("SuperAdmin");
try
{
result = LogWasteModule.GetForEdit(manifest);
}
catch (Exception)
{
throw;
}
return Json(new
{
result = result,
role = userRole,
}, JsonRequestBehavior.AllowGet);
}
because
you shouldn't need to manually serialize your result as Json() will do it
the syntax of your Json(new...) is wrong: you need to use "field = value" syntax, NOT just "value" and the AllowGet needs to be a parameter to Json() NOT a value you add to the object.

Set javascript variable to return value of function

I am trying to set a variable in my controller to the return value of a function. This function is creating a new entry in a table, and then returning its id. When I debug in chrome developer tools, I can see that my function is working correctly and that response.data is in fact a number. However, when I try to set a variable to this function call, the value is being set as undefined.
My AngularJS component:
function saveNewGame($http, gameData) {
var newGameData = {
"InvestigatorGroupUserId": gameData.GroupUserId,
"InvestigatorGroupGameId": gameData.GroupGameId,
"WithTeacher": gameData.WithTeacher
};
$http.post("/APGame.WebHost/play/newGamePlayed", newGameData)
.then(function(response) {
return response.data;
});
}
function controller($http) {
var model = this;
var gameData = model.value;
var gamePlayedId;
model.startGame = function() {
gamePlayedId = saveNewGame($http, gameData);
alert(gamePlayedId);
};
}
module.component("gameApp",
{
templateUrl: "/APGame/GameAngular/game-app.html",
controllerAs: "game",
bindings: {
value: "<"
},
controller: ["$http", controller]
});
This is what my service call is doing:
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "newGamePlayed")]
int NewGamePlayed(GamePlayedData gamePlayedData);
public int NewGamePlayed(GamePlayedData gamePlayedData)
{
var gamePlayedRepo = _gamePlayedRepo ?? new GamePlayedRepository();
var newGame = new GamePlayed()
{
InvestigatorGroupUserId = gamePlayedData.InvestigatorGroupUserId,
InvestigatorGroupGameId = gamePlayedData.InvestigatorGroupGameId,
GameStartTime = DateTime.Now,
IsComplete = false
};
return gamePlayedRepo.Create(newGame);
}
Add a promise resolvement listener to the method invoke like following:
model.startGame = function() {
gamePlayedId = saveNewGame($http, gameData)then(function(response) {
alert(response.data);
}, function(reason) {
alert('Failed: ' + reason);
});
};
Return the http.get promise instead of the data
function saveNewGame($http, gameData) {
var newGameData = {
"InvestigatorGroupUserId": gameData.GroupUserId,
"InvestigatorGroupGameId": gameData.GroupGameId,
"WithTeacher": gameData.WithTeacher
};
return $http.post("/APGame.WebHost/play/newGamePlayed", newGameData);
}
The reason is because your function is not returning any value thus undefined.
$http.post("/APGame.WebHost/play/newGamePlayed", newGameData)
.then(function(response) {
// notice that you are returning this value to the function(response) not your saveNewGame function
return response.data;
});
Due to asynchronous nature of javascript, you should do something like instead. $http.post return a promise object which can be used like following.
return $http.post("/APGame.WebHost/play/newGamePlayed", newGameData);
And in your calling function.
saveNewGame($http, gameData).then(function(response){
gamePlayedId = response.data;
});

Javascript unit testing - stub the data in a success callback

I'm writing a javascript test for my UserRepository.
I want to stub the data in the success callback function of the $http object.
User Repository code:
function UserRepository($http) {
return {
getUsers: function () {
$http({ url: '/GetUsers' }).success(function (data) {
//populate users
});
return users;
}
};
}
My test code:
var httpStub = function() {
return new {
success: function(callback) {
var array = [];
array.push({ forename: 'john', surname: 'smith' });
callback(array);
}
};
};
var userRepository = new UserRepository(httpStub);
userRepository.getUsers();
The error i'm getting is "the object is not function" which i think is happening where my httpstub returns the object literal containing the success function, but I can't figure out how to fix it.
I've fixed the problem. The httpStub was doing
return new{
success : function .....
}
When there should have been no new keyword e.g:
return {
success : function .....
}

Breeze createEntity Type not recognized

I'm trying to make my call to the server with BreezeJS but can't get it to work. It says tblMovie is not recognized. I can't find the problem :S
When I want to add a new movie it says so.
show.js
self.viewAddMovieModal = function () {
self.app.showModal(new self.addmovie()).then(function (result) {
if (result != undefined) {
var movie = dataservice.createMovie({
Title: result[0].title,
Director: result[0].director
});
if (movie.entityAspect.validateEntity()) {
self.movies.push(new movie(result[0].title, result[0].director));
dataservice.saveChanges();
} else {
alert("Error");
}
}
});
};
My dataservice.js layer
/// <reference path="../../Scripts/breeze.debug.js"/>
define(["require"], function (require) {
var Dataservice = (function () {
function Dataservice(service) {
this.serviceName = '';
this._isSaving = false;
this.serviceName = service;
this.Manager = new breeze.EntityManager(this.serviceName);
this.EntityQuery = new breeze.EntityQuery();
}
Dataservice.prototype.getAllMovies = function () {
this.EntityQuery = breeze.EntityQuery.from("AllMovies");
return this.Manager.executeQuery(this.EntityQuery);
};
Dataservice.prototype.createMovie = function (initialValues) {
return this.Manager.createEntity('tblMovies', initialValues); //THis is where it goes wrong :(
};
Dataservice.prototype.saveChanges = function (suppressLogIfNothingToSave) {
if (this.Manager.hasChanges()) {
if (this._isSaving) {
setTimeout(this.saveChanges, 50);
return;
}
this.Manager.saveChanges().then(this.saveSucceeded).fail(this.saveFailed).fin(this.saveFinished);
} else if (!suppressLogIfNothingToSave) {
}
};
Dataservice.prototype.saveSucceeded = function (saveResult) {
this._isSaving = false;
};
Dataservice.prototype.saveFailed = function (error) {
};
Dataservice.prototype.saveFinished = function () {
this._isSaving = false;
};
return Dataservice;
})();
return Dataservice;
})
I do have a model tblMovie
using System;
using System.ComponentModel.DataAnnotations;
namespace DurandalMovieApp.Models
{
public class tblMovie
{
[Key]
public int MovieID { get; set; }
public string Title { get; set; }
public string Director { get; set; }
}
}
Hope someone can help!
I think that the problem is that your entity is: tblMovie, not tblMovies.
Try replacing:
return this.Manager.createEntity('tblMovies', initialValues);
With:
return this.Manager.createEntity('tblMovie', initialValues);

Categories

Resources