Using ENUM with Typescript and Knockout - javascript

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;

Related

Knockout computed column with model

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>

Making a ES6 class out of Angular 1.5+ component and getting function callbacks to work

var app = angular.module('testApp', []);
class Component {
constructor(app, name, template, as, bindings) {
this.bindings = bindings;
this.config = {}
this.config.template = template;
this.config.controllerAs = as;
// pre-create properties
this.config.controller = this.controller;
this.config['bindings'] = this.bindings;
app.component(name, this.config);
console.log("Inside Component ctor()");
}
addBindings(name, bindingType) {
this.bindings[name] = bindingType;
}
controller() {
}
}
class App extends Component {
constructor(app) {
var bindings = {
name: "<"
};
super(app, "app", "Hello", "vm", bindings);
}
controller() {
this.$onInit = () => this.Init(); // DOESN'T WORK
/*
var self = this;
self.$onInit = function () { self.Init(); }; // DOESN'T WORK
*/
/*
this.$onInit = function () { // WORKS
console.log("This works but I don't like it!");
};
*/
}
Init() {
console.log("Init");
}
onNameSelected(user) {
this.selectedUser = user;
}
}
var myApp = new App(app);
<div ng-app="testApp">
<app></app>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.js"></script>
I'm trying to "classify" angular 1.5's .component(). I can get most of it figured out but when I try to assign a class method for $onInit it doesn't work. I've tried assigning to it and using arrow notation to call back to the class method but neither work. It does work if I assign an anonymous function directly but I don't want to do that. I want those functions to point to class methods because I find it cleaner.
So ultimately I want my App classes Init() method to get called for $onInit(). Is it possible?

Prototypes in Javascript to Typescript Syntax

Does somebody know how do I write this Javascript code into Typescript? Especially the prototype inside of the class causes me problems...
var Module = (function () {
function Module(name) {
this.name = name;
}
Module.prototype.toString = function () {
return this.name;
};
return Module;
})();
var Student = (function () {
function Student(name, studentNumber) {
this.bookedModules = [];
this.name = name;
this.studentNumber = studentNumber;
}
Student.prototype.bookModule = function (bookedModule) {
this.bookedModules.push(bookedModule);
};
Student.prototype.bookedModuleNames = function () {
return this.bookedModules.map(function (module) {
return module.toString();
});
};
return Student;
})();
In typescript you use classes, the compiler will do the prototype work for you.
You code is equivalent to:
class Module {
public name: string;
constructor(name: string) {
this.name = name;
}
toString(): string {
return this.name;
}
}
class Student {
public name: string;
public studentNumber: number;
public bookedModules: Module[];
constructor(name: string, studentNumber: number) {
this.name = name;
this.bookedModules = [];
this.studentNumber = studentNumber;
}
bookModule(book: Module): void {
this.bookedModules.push(book);
}
bookedModuleNames(): string[] {
return this.bookedModules.map(book => book.name);
}
}
(code in playground)
Which compiles into:
var Module = (function () {
function Module(name) {
this.name = name;
}
Module.prototype.toString = function () {
return this.name;
};
return Module;
}());
var Student = (function () {
function Student(name, studentNumber) {
this.name = name;
this.bookedModules = [];
this.studentNumber = studentNumber;
}
Student.prototype.bookModule = function (book) {
this.bookedModules.push(book);
};
Student.prototype.bookedModuleNames = function () {
return this.bookedModules.map(function (book) { return book.name; });
};
return Student;
}());
Use classes - typescript will generate this code for you:
class Module {
constructor(public name) {
}
toString() {
return this.name;
}
}
class Student {
bookedModules: Module[];
constructor(public name, public studentNumber) {
this.bookedModules = [];
}
bookModule(bookedModule: Module) {
this.bookedModules.push(bookedModule);
}
//...
}

Typescript promise bind angular 1.5

I am having a problem that my promise isn't being bound to the correct this.
I have read many articles on this subject and I think I understand the problem, but the solution isn't clear.
Suggestions
Here is the code:
// AngleCouch.ts`enter code here`
namespace AngleCouchDb {
//Note this is not a ng Service
export class AngleCouch {
...
public getAllUsers(): ng.IPromise<any> {
let dbUrl: string = this.urlPrefix + "/_users/_all_docs? include_docs=true";
let status = new CouchStatus();
console.log("in getAllUsers");
return this.http.get(dbUrl);
}
...
}
}
// UserManagementController.ts
module app {
class UserManagementController {
static $inject = [
'$mdSidenav', '$mdToast', '$mdDialog',
'$mdMedia', '$mdBottomSheet', '$state'];
...
public fetchUsers = () => {
let aUser = AngleCouchDb.ActiveUser.getInstance();
if (aUser.loginStatus `enter code here`!== Shows.StaticData.LoggedIn) {
return;
}
console.log("userManagementController: ");
console.log(this.$state);
this.vm.couch = new AngleCouchDb.AngleCouch();
this.vm.version = {};
this.vm.docTypeList = [];
this.vm.couch.urlPrefix = Shows.StaticData.server;
this.vm.user = new AngleCouchDb.UserCred();
this.vm.couch = new AngleCouchDb.AngleCouch();
this.vm.couch.getAllUsers().then(this.getAllUsersCB, (response: any) => {
console.log(response);`enter code here`
});
}
public getAllUsersCB = (response) => {
this.vm.gridObj = this.vm.initGridOpt();
this.vm.gridObj.data = response.data.rows;
}
...
angular.module("app").
controller("app.userManagementController", UserManagementController );
}

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