Scoping of 'this' in TypeScript - javascript

I have a very simple class, but already run into pain with the definition of ‘this’ in Typescript:
Typescript
/// <reference path='jquery.d.ts' />
/// <reference path='bootstrap.d.ts' />
module Problem {
export class Index {
detailsUrl: string;
constructor() {
$('.problem-detail-button').click((e) => {
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: this.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
Javascript
var Problem;
(function (Problem) {
var Index = (function () {
function Index() {
var _this = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(_this).closest('tr');
var problemId = $row.data('problem-id');
$.ajax({
url: _this.detailsUrl,
data: {
id: problemId
},
type: 'POST',
success: function (result) {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
}
});
});
}
return Index;
})();
Problem.Index = Index;
})(Problem || (Problem = {}));
Now the problem is that the line
var $row = $(this).closest('tr'); //this must be that of the callback
and this line
this.detailsUrl, //this must be the instance of the class
conflict in the meaning of 'this'
How do you handle the mixture of the 'this'?

module Problem {
export class Index {
detailsUrl: string;
constructor() {
var that = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: that.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
Explicitly declare that = this so you have a reference for that.detailsUrl, then
don't use a fat arrow for the click handler, so you get the correct this scope for the callback.

You need to fallback to the standard way of javascript. i.e store the variable as :
var self = this;
Then you can use function instead of ()=> and use this to access variable in callback and self to access the instance of the class.
Here is the complete code sample:
module Problem {
export class Index {
detailsUrl: string;
constructor() {
var self = this;
$('.problem-detail-button').click(function(e){
e.preventDefault();
var $row = $(this).closest('tr'); //this must be that of the callback
var problemId: number = $row.data('problem-id');
$.ajax({
url: self.detailsUrl, //this must be the instance of the class
data: { id: problemId },
type: 'POST',
success: (result) => {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
},
})
});
}
}
}
// Creating
var foo:any = {};
foo.x = 3;
foo.y='123';
var jsonString = JSON.stringify(foo);
alert(jsonString);
// Reading
interface Bar{
x:number;
y?:string;
}
var baz:Bar = JSON.parse(jsonString);
alert(baz.y);
And your generated javascript:
var Problem;
(function (Problem) {
var Index = (function () {
function Index() {
var self = this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var $row = $(this).closest('tr');
var problemId = $row.data('problem-id');
$.ajax({
url: self.detailsUrl,
data: {
id: problemId
},
type: 'POST',
success: function (result) {
$('#details-modal-placeholder').html(result);
$('#details-modal-placeholder modal').modal('show');
}
});
});
}
return Index;
})();
Problem.Index = Index;
})(Problem || (Problem = {}));
var foo = {
};
foo.x = 3;
foo.y = '123';
var jsonString = JSON.stringify(foo);
alert(jsonString);
var baz = JSON.parse(jsonString);
alert(baz.y);

If you're only supporting browsers that have .addEventListener, I'd suggest using that to associate your data with your elements.
Instead of implementing your code, I'll just give a simple example.
function MyClass(el) {
this.el = el;
this.foo = "bar";
el.addEventListener("click", this, false);
}
MyClass.prototype.handleEvent = function(event) {
this[event.type] && this[event.type](event);
};
MyClass.prototype.click = function(event) {
// Here you have access to the data object
console.log(this.foo); // "bar"
// ...and therefore the element that you stored
console.log(this.el.nodeName); // "DIV"
// ...or you could use `event.currentElement` to get the bound element
};
So this technique gives you an organized coupling between elements and data.
Even if you need to support old IE, you can shim it using .attachEvent().
So then to use it, you just pass the element to the constructor when setting up the data.
new MyClass(document.body);
If all the logic is in your handler(s), you don't even need to keep a reference to the object you created, since the handlers automatically get it via this.

I normally bind this to a variable as soon as I have it in the scope I want.
However the this you are after could be found like this:
constructor() {
var class_this=this;
$('.problem-detail-button').click(function (e) {
e.preventDefault();
var callback_this=e.target;

Late to the thread, but I have something different to suggestion.
Instead of:
var $row = $(this).closest('tr'); //this must be that of the callback
Consider using:
var $row = $(e.currentTarget).closest('tr');
As in this example, anywhere you might want to use this in a jQuery callback, you have access to a function parameter you can use instead. I would suggest that using these parameters instead of this is cleaner (where "cleaner" is defined as more expressive and less likely to be turned into a bug during future maintenance).

module Problem {
export class Index {
constructor() {
$('.classname').on('click',$.proxy(this.yourfunction,this));
}
private yourfunction(event){
console.log(this);//now this is not dom element but Index
}
}
}
check about jquery.proxy().
just remind you there is another way.

Related

JavaScript says that a programmed function which is called by another is not defined

In an Ionic-app i have a problem with one of my JavaScript functions which is called by another one.
I always get an error, that the function getSqlSelect is not defined.
But the functions are all defined in the services.js script.
Here's a short version of the code from services.js:
.factory('LocalDatabase', function () {
var arrResult = "";
return {
select: function (table, filterAttributesArr, success) {
var sql = getSqlSelect(table, filterAttributesArr);
var db = window.sqlitePlugin.openDatabase({ name: 'p16.sqlite', location: 0 });
},
getSqlSelect: function (tablename, filterAttributesArr) {
return "";
}
};
})
That's because you don't have a getSqlSelect variable in scope in that location. If select is called as obj.select(...) where obj is that object returned by the callback to factory, then within it you can use this.getSqlSelect(...) to refer to it. E.g.
var sql = this.getSqlSelect(table, filterAttributesArr);
// ^^^^^
If select isn't called with the right this, that won't work. If you don't need to expose it, just define it within your callback:
.factory('LocalDatabase', function () {
var arrResult = "";
function getSqlSelect(tablename, filterAttributesArr) {
return "";
}
return {
select: function (table, filterAttributesArr, success) {
var sql = getSqlSelect(table, filterAttributesArr);
var db = window.sqlitePlugin.openDatabase({ name: 'p16.sqlite', location: 0 });
}
};
})
If select isn't called with the right this and you do need to expose getSqlSelect, you can do that too:
.factory('LocalDatabase', function () {
var arrResult = "";
function getSqlSelect(tablename, filterAttributesArr) {
return "";
}
return {
select: function (table, filterAttributesArr, success) {
var sql = getSqlSelect(table, filterAttributesArr);
var db = window.sqlitePlugin.openDatabase({ name: 'p16.sqlite', location: 0 });
},
getSqlSelect: getSqlSelect
};
})

Access Properties from Knockout Object

Here is the scenario:
I currently have an object that looks like the following:
After running modal.PersonInfo I get the object returned starting at line 3. See image above.
Question: Within my HTML, how can I call FirstNameand LastName
Error States:
Uncaught ReferenceError: Unable to process binding "text: function (){return PersonInfo().FirstName}" Message PersonInfo is not defined
JavaScript:
function Person() {
var modal = this;
modal.PersonInfo = ko.observable('');
modal.setData = function (id) {
$.ajax({
type: "GET",
url: '/Person/UserInformation?id=' + id,
contentType: "application/json; charset=utf-8",
success: function (data) {
modal.setPersonInfo(data);
$('#person-modal').modal('show');
}
});
modal.setPersonInfo = function (data) {
modal.PersonInfo = data;
}
}
};
HTML
My thought was that I could do the following:
<p data-bind="text: PersonInfo().FirstName"></p>
<p data-bind="text: PersonInfo().LastName"></p>
This line replaces the observable, instead of assigning the value:
modal.PersonInfo = data;
Try this instead:
modal.PersonInfo(data)
There's also a misplaced closing brace: model.setPersonInfo was inside model.setData
See working demo.
You would need to just use some kind of mapping, for instance ko.mapping. Make sure you bind your view model, start your function in some way and you should be ok.
var DemoPage = (function () {
function DemoPage() {
var _this = this;
_this.PersonInfo = ko.observable({});
_this.setData = function (id) {
// some ajax that returns data
var _data = {
FirstName: 'Frankie',
LastName: 'Jones'
};
_this.setPersonInfo(_data);
//$('#person-modal').modal('show');
};
_this.setPersonInfo = function (data) {
ko.mapping.fromJS(data, {}, _this.PersonInfo());
};
this.setData();
}
return DemoPage;
})();
var demoApp = new DemoPage();
ko.applyBindings(demoApp);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<p data-bind="text: PersonInfo().FirstName"></p>
<p data-bind="text: PersonInfo().LastName"></p>

JavaScript uncaught type error illegal invocation

var Rules = Rules || (function () {
saverule = function () {
var level = document.getElementById("level-selection");
var metrics = document.getElementById("metric-selection");
var operator = document.getElementById("operator-selection");
var value = document.getElementById("value123");
var saveAction = $("#hidden-save").val();
$.post(saveAction, { level_id: level, product_id: metrics, opp: operator, value: value }, function () {
},
'json');
};
wireLinkActions = function () {
$("a.save-ok").on("click", function(event) {
saverule();
return false;
});
};
return {
Initialize: function () {
wireLinkActions();
}
}
})();
$(document).ready(Rules.Initialize);
illegal invocation error it wont even cal the the save rule function while debugging also
Make sure your return the values and not the DOM element itself.
For example change this:
var level = document.getElementById("level-selection");
into this:
var level = document.getElementById("level-selection").value;
or, simply use jQuery such as this:
var level = $("#level-selection").val();

Javascript Scope and this.Variable

So I have some javascript with the following (pseudo) structure. How do I set the this.last_updated variable of the parent function from the showUpdates function, without specifically referencing the name assignment (my_main_function).
var my_main_function = new main()
function main() {
this.last_updated;
function showUpdates(data){
//set this.last_updated=
// do Stuff
}
this.updateMain(){
$.ajax({
url:"/my_url/"
type:"POST",
datatype:"json",
data: {'last_updated':this.last_updated },
success : function(data) { showUpdates(data)},
error : function(xhr,errmsg,err) {
alert(xhr.status + ": " + xhr.responseText); },
});
}
}
Updated the code base one the comments:
There are two way of creating objects.
If you need to create the object multiple time you will do it like this:
var YourDefintinon = function() {
};
YourDefintinon.prototype.foo = function() {
};
obj1 = new YourDefintinon();
obj2 = new YourDefintinon();
obj1.foo();
If you only need it once in your code you can just do it like that:
var obj = {
};
obj.foo = function() {
};
foo();
So your would need the main only once your code would look like this:
Using Function.prototype.bind (and its polyfill for older browsers) to bind the showUpdates to the obj.
var main = {
last_updated : null
};
function showUpdates(data){
this.last_updated = data.update_time;
}
main.updateMain = function () {
//<< bind showUpdates to `this` and save the bound function in the local variabel showUpdates
var showUpdates = showUpdates.bind(this);
$.ajax({
url:"/my_url/"
type:"POST",
datatype:"json",
data: {'last_updated':last_updated },
success : showUpdates, //<< uses the showUpdates variable not the function
error : function(xhr,errmsg,err) {
alert(xhr.status + ": " + xhr.responseText);
},
});
};
As you don't want to make showUpdates accessible to others you could wrap the whole block into a function that is immediatly called:
var main = (function() {
var main = {
last_updated : null
};
function showUpdates(data){
this.last_updated = data.update_time;
}
main.updateMain = function () {
var showUpdates = showUpdates.bind(this);
$.ajax({
url:"/my_url/"
type:"POST",
datatype:"json",
data: {'last_updated':last_updated },
success : showUpdates,
error : function(xhr,errmsg,err) {
alert(xhr.status + ": " + xhr.responseText);
},
});
};
return main;
}());

How to create a object/function with defined setting when create new?

I wish to do something like this:
function Student (id, class) {
var id = id
var class = class
this.get = function (subject) {
$.ajax({
url: 'myurl',
data: { id: id, class: class, subject: subject },
success: function (r) { return r }
})
}
this.set = function (subject, mark) {
$.ajax({
url: 'myurl',
method: 'post',
data: { id: id, class: class, subject: subject, mark: mark },
success: function (r) { return r }
})
}
}
my question is how can I modify my function so that I can create new student as below
var s1 = new Student (22, 4) // to set predefined id & class
but, I want the set and get as below (like jquery set & get)
s1("math") // to get
s1("history", 70) // to set
**
so i think the answer is not possible to work as an object to store attribute id & class and call like a function without function name. thanks for your answer guys.
**
You can check how many arguments the caller has provided. Or check for undefined values.
function test(a, b) {
// both ifs check b was not provided
if (typeof b === "undefined") {
}
if (arguments.length == 1) {
}
}
Your current functions probably won't work because you are returning from a callback. AJAX is (in most cases) asynchronous. So in your case, you have to add another argument for providing a callback.
this.get = function (subject, callback) {
$.ajax({
url: 'myurl',
data: { id: id, class: class, subject: subject },
success: function (r) { callback(r); }
})
}
FYI, class is a reserved keyword by the ECMAScript specification.
function sample(x,y){
id=x;
subjectClass =y;
if(arguments.length == 1){
//getter
}
else{
//setter
}
}
call getter
sample("maths")
call setter
sample("history",70);
Note :
class is a reserved keyword, so please remove it and you can use some other variable name
but, I want the set and get as below
That would mean s1 would be a function, not a Student instance. So your constructor would need to return that.
function student(id, klass) {
// no need to declare variables here that are parameters already
return function(subject, mark) {
var data = {id: id, class: klass, subject: subject},
opt = {url: 'myurl', data: data};
if (arguments.length > 1) { // something was given to `mark`
data.mark = mark;
opt.method = "post";
}
return $.ajax(opt);
};
}
Btw, since you cannot return the response from an ajax call, the function will return the jqXHR promise:
var s1 = student(22, 4); // `new` is unnecessary now
s1("math").then(function(r) { console.log("got maths result:", r); });
s1("history", 70).then(function(r) { console.log("successfully set marks"); });

Categories