"This" in JS Object Literal - Different meaning in event Callback - javascript

How can I refer the the object itself in an event callback defined within an object literal in JS/jQuery please?
I have researched various answers and articles, such as this question: How to access the correct `this` inside a callback? but only found myself more confused.
It makes sense that this should refer to the element that was clicked as we need access to it, but how then do I refer the the object containing the binding function itself?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
</head>
<body>
<button id="test">Click Me</button>
<script>
$( document ).ready( function() {
console.log(MyObj.introspect());
MyObj.bindEvents();
} );
MyObj = {
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(this);
}
}
</script>
</body>
</html>

In your case, you'd use MyObj, just like you did in bindEvents, since it's a singleton:
MyObj = {
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(MyObj);
// ---------^^^^^
}
}
Side note: Your code is falling prey to what I call The Horror of Implicit Globals. Be sure to declare your variables (with var, or ES2015's let or const). In your case, you can make MyObj entirely private since you only need it in your own code, by moving it into the ready callback and declaring it:
$( document ).ready( function() {
var MyObj = { // Note the `var`
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(MyObj);
}
}; // Also added missing ; here
console.log(MyObj.introspect());
MyObj.bindEvents();
});

Related

Set the value of a global variable in a function

I'm currently working on an SAP Fiori app that consumes an OData service.
I created a method in my controller fonct that calculates a variable coming from my OData.
I want to capture this value and put it in a global variable every time the view is refreshed.
I created a global variable like this:
var boo1;
return Controller.extend("com.controller.Detail", {...});
and I passed boo1 as a parameter in my method fonct inside my onInit method but it is undefined.
Here's a snippet of my controller's code:
sap.ui.define([
"com/util/Controller"
], function(Controller) {
"use strict";
var boo1;
return Controller.extend("com.controller.Detail", {
onInit: function() {
this.fonct(boo1);
alert(boo1);
},
fonct: function(ovar) {
var that = this;
var oModel = that.getView().getModel();
oModel.read("/alertSet", {
success: function(data) {
var a = JSON.stringify(data);
var b = a.slice(332,-4);
ovar = b;
}
});
}
});
});
I think that what you want to do is simplier that what you are doing.
To save a global variable, get the Core object and set the variable as a new property of this object:
sap.ui.getCore().myGlobalVar = myCalculatedValue;
Then to use it in other view, get the property directly from the Core:
var mySavedVar = sap.ui.getCore().myGlobalVar
Then use the Router routeMatched event to handle your navigation and refresh the value.
Here a snippet: https://jsbin.com/bewigusopo/edit?html,output
<!DOCTYPE html>
<html><head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' >
<meta charset="UTF-8" >
<title>test</title>
<script id='sap-ui-bootstrap'
src='https://sapui5.hana.ondemand.com/1.38.5/resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-bindingSyntax="complex"></script>
<script id="view1" type="sapui5/xmlview">
<mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc">
<core:ComponentContainer name='my.comp'/>
</mvc:View>
</script>
<script id="home" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"
controllerName="my.controller1">
<Page>
<Input id="input" placeholder="Write a text to save it globally"/>
<Button text="Navigate to other view" press="onNavigate"/>
</Page>
</mvc:View>
</script>
<script id="add" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"
controllerName="my.controller2">
<Page id="page" showNavButton="true" navButtonPress="onBack">
<HBox class="sapUiLargeMarginBegin">
<Label text="The global variable is:" class="sapUiSmallMarginEnd sapUiSmallMarginTop"/>
<Input id="inputResult"/>
</HBox>
</Page>
</mvc:View>
</script>
<script>
// jQuery.sap.declare("my.comp.Component");
sap.ui.define("my/comp/Component", ["sap/ui/core/UIComponent"], function(UIComponent) {
return UIComponent.extend("my.comp.Component", {
metadata : {
name : "GreatComponent",
version : "1.0",
includes : [],
dependencies : {
libs : ["sap.m"]
},
routing: {
config: {
routerClass: "sap.m.routing.Router",
viewType: "XML",
viewPath: "my",
controlId: "app",
transition: "slide",
controlAggregation: "pages"
},
routes: [
{
name: "home",
pattern: "",
target: "home"
},
{
name: "add",
pattern: "add",
target: "add"
}
],
targets: {
home: {
viewName: "Home",
title: "home"
},
add: {
viewName: "Add",
title: "add"
}
}
}
},
init: function() {
sap.ui.core.UIComponent.prototype.init.apply(this, arguments);
var oRouter = this.getRouter();
var oViews = oRouter.getViews();
this.runAsOwner(function() {
var myHome = sap.ui.xmlview({viewContent:jQuery('#home').html()});
oViews.setView("my.Home", myHome);
var myAdd = sap.ui.xmlview({viewContent:jQuery('#add').html()});
oViews.setView("my.Add", myAdd);
});
oRouter.initialize();
},
createContent : function() {
var componentData = this.getComponentData();
return new sap.m.App("app", {
});
}
});
});
sap.ui.define("my/controller1", [
"sap/ui/core/UIComponent"
],function(UIComponent) {
return sap.ui.controller("my.controller1", {
onInit: function() {
this.oRouter = UIComponent.getRouterFor(this.getView());
},
onNavigate: function() {
var sInputText = this.getView().byId("input").getValue();
sap.ui.getCore().myGlobalVar = sInputText;
console.log(sap.ui.getCore().myGlobalVar)
this.oRouter.navTo("add");
}
});
});
sap.ui.define("my/controller2", [
"sap/ui/core/UIComponent"
],function(UIComponent) {
return sap.ui.controller("my.controller2", {
onInit: function() {
this.oRouter = UIComponent.getRouterFor(this.getView());
this.oRouter.getRoute("add").attachPatternMatched(this._onObjectMatched, this);
},
_onObjectMatched: function(){
var sGlobalVariable = sap.ui.getCore().myGlobalVar;
console.log(sGlobalVariable);
this.getView().byId("inputResult").setValue(sGlobalVariable);
},
onBack: function(){
this.oRouter.navTo("home");
}
});
});
sap.ui.require(["my/comp/Component"], function(myComp) {
// instantiate the View
sap.ui.xmlview({viewContent:jQuery('#view1').html()}).placeAt('content');
});
</script>
</head>
<body class='sapUiBody'>
<div id='content'></div>
</body>
</html>
Other possibility is to set a global model, which will simplyfy your binding very much. Just create it and set it into the Core
//To set it
var oGlobalModel = new sap.ui.model.json.JSONModel();
sap.ui.getCore().setModel(oGlobalModel, "myGlobalModelID");
//To get it
var oMyGlobalModel = sap.ui.getCore().getModel("myGlobalModelID");
Please, avoid defining global variables (such as before sap.ui.define) especially if you are working on apps that are going to be placed into the Fiori Launchpad. This approach is considered harmful. This is also repeated in the documentation:
In control and class modules, you should not use global variables at all.(src)
We're using "use strict" purposely to disallow such practices.
Also, defining properties or adding models on sap.ui.getCore() is an anti-pattern for Fiori apps. Not only does this lead to the same flaws as defining global variables but models set on the core are not propagated to the views by default because components are meant to be used modularly. Therefore, defining such data to the corresponding component instead of the core is the way to go for Fiori apps.
==> FLP Best Practices
I created a global variable
var boo1;
return Controller.extend("com.controller.Detail", {...});
What you created though was not a global variable since boo1 was declared inside an anonymous function. Instead, boo1 was stored in a closure which allowed the prototype methods (onInit and fonc) to maintain the lexical environment within which boo1 was made accessible not only by the methods but also by other instances of com.controller.Detail. In terms of Java, we can say that boo1 is a private static variable. But it's not a global variable.
That being said..
I passed boo1 as a parameter in my method fonct inside my onInit method but it is undefined
The closure makes passing boo1 as an argument unnecessary (unless you want a hard copy of boo1 to maintain multiple states of it). The method fonct can access boo1 directly, even in the anonymous callbacks defined in fonct.
Now, you may be asking why boo1 was undefined. There are two reasons:
boo1 was undefined before calling fonct. If boo1 were an object instead of undefined, then ovar would have been a reference to the boo1 object and the change to any ovar property would have took place in the boo1 object. But undefined is not an object. So ovar doesn't know what boo1 is at all. Note: The evaluation strategy of JS is "Call by object-sharing".
alert(boo1) gets fired right after calling fonct. Unless oModel works synchronously (which I strongly recommend not to do so), the browser doesn't wait for the success callback to get fired in order to invoke alert(boo1) later. The alert is fired immediately, and then the success callback where boo1 should be manipulated.
Removing ovar and using boo1 instead updates boo1 properly in the success callback.
fonct: function(/*no ovar*/) {
//...
oModel.read("/alertSet", {
success: function(data) {
//...
boo1 = /*new value*/;
alert(boo1);
}.bind(this)
});
}
You should declare the global variable at global scope (i.e., before sap.ui.define).

Data binding in Polymer - function is being removed from bound object

I'm encountering an issue binding an object that contains a function from angular to Polymer 1.0. The function is not being passed through into the target object in the custom element. Here is a simplified code sample:
The custom element has a single property named myprop:
<script>
Polymer({
is: 'my-custom-element',
properties: {
myprop: Object
},
attached: function () {
var x = this.myprop.x; //this is ok
this.myprop.myfunc(); //myfunc is not defined!
}
});
</script>
Here is the HTML:
<div ng-app="myApp">
<div ng-controller="myCtrl">
<my-custom-element myprop="{{myobject}}"></my-custom-element>
</div>
</div>
And here is the angular controller:
<script>
angular.module("myApp", []).controller("myCtrl", function ($scope) {
$scope.myobject= {
x: 4,
myfunc: function() {
//function body
}
}
});
</script>
Why isn't the function available in the custom element?
As documented here: https://github.com/Polymer/polymer/blob/3e96425bf0e0ba49b5f1f2fd2b6008e45a206692/PRIMER.md#attribute-deserialization
... objects passed into polymer elements are being passed through JSON.stringify and then JSON.parse (depending on variable type).
Functions will be completely stripped out by JSON.stringify - just checkout out this sample...
console.log( JSON.stringify({x:123,y:function(){ return 123; }}) );
// outputs: {"x":123}
I believe this is the offending line in source...
https://github.com/Polymer/polymer/blob/3b0d10b4da804703d493da7bd0b5c22fc6f7b173/src/micro/attributes.html#L232
... and comments nearby suggest possibility to change this behavior...
Users may override this method on Polymer element prototypes to provide serialization for custom types
You can't call Angular function like you write this.myprop.myfunc();
I can't explain why this is so, but if you want call Angular function from Polymer you can use this.fire('nameEvent') and in Angular controller or run module add event listener like
document.addEventListener('nameEvent', function() {
//function body
})
I hope that help you. Good luck
I'm not simulating with Angular but I think that {{myobject}} can have a problem. Only with Polymer works fine.
Basically I copied your code in the my-element and created my-element-two where I import the it. The result is "My name" printed in the lifecycle attached.
<link rel="import" href="../polymer/polymer.html">
<dom-module id="my-element">
<script>
Polymer({
is: 'my-element',
properties: {
myprop: Object,
},
attached: function () {
var x = this.myprop.x; //this is ok
this.myprop.myfunc(); //myfunc is not defined!
}
});
</script>
</dom-module>
<dom-module id="my-element-two">
<template>
<my-element myprop="{{myobject}}"></my-element>
</template>
<script>
Polymer({
is: 'my-element-two',
properties: {
myobject: {
type: Object,
value: {
x: 4,
myfunc: function() {
console.log("My name");
}
}
}
},
});
</script>
</dom-module>
<!-- results is print "My name" in the console. -->
<my-element-two></my-element-two>

binding html events to an instance of a controller class _within_ the constructor

Sorry, probably bit of a noob JS question regarding binding handlers to instances.
I am creating a controller instance with some data that will subsequently be used to process incoming events (the actual use case is composing complex d3 handlers with varying ajax urls and into which I compose the function(s) doing the actual tree update).
RequireJS and jquery are involved, but I suspect my issue has more to do with my specific binding code. I guess I could forego the use of 'this' since I have only one controller per page which can be a global. But this feels like it should be doable, if only I knew how to.
This is how I bind the controller to its target, from within the constructor (doing it outside the constructor seems to work):
function BtnMgr(msg, tgt_id) {
this.msg = msg;
this.tgt_id = tgt_id;
var selector = "#" + tgt_id;
$(selector).on("click", this.handleClick);
}
What is going wrong?
When I click on the button, 'this', in the handleClick refers to the html button, not to the controller instance.
If I call the controller instance method directly, 'this' is correct.
I've tried call or creating a wrapper function, as suggested in
How can I bind an event handler to an instance in JQuery?
$(selector).click(function(e) { BtnMgr.prototype.handleClick.call(this, e); });
My button click keeps seeing 'this' as the button, not the controller:
output
global var controller:BtnMgr.I am a button
this:[object HTMLButtonElement],type:
e:[object Object],type:Object
BtnMgr.handleClick:this.msg:undefined
Simplified version:
HTML
page4.html
<!DOCTYPE html>
<html>
<head>
<title>Page 4</title>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.15/require.min.js"></script>
<script type="text/javascript">
requirejs.config({
baseUrl: '.',
paths: {
"jquery": "//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min"
}
});
var controller;
require(["main4"], function(BtnMgr) {
controller = new BtnMgr("I am a button", "btn_click");
//this simulated call works - 'this' refers to the BtnMgr instance
controller.handleClick("dummy_btn");
});
</script>
</head>
<body>
<button id="btn_click">click me!</button>
</body>
</html>
RequireJS
main4.js
define(["jquery"], function($) {
function BtnMgr(msg, tgt_id) {
this.msg = msg;
this.tgt_id = tgt_id;
var selector = "#" + tgt_id;
$(selector).on("click", this.handleClick);
}
BtnMgr.prototype.toString = function(){
return "BtnMgr." + this.msg;
};
BtnMgr.prototype.handleClick = function(e) {
//I want 'this' to refer to the BtnMgr instance
//and e to the html element that got clicked...
console.log("global var controller:" + controller);
console.log("this:" + this + ",type:" + this.constructor.name);
console.log("e:" + e + ",type:" + e.constructor.name);
console.log("BtnMgr.handleClick:this.msg:" + this.msg);
};
//define is returning the constructor method for the object
return BtnMgr;
});
You could achieve (nearly) what you want with :
$(selector).on("click", this.handleClick.bind(this));
this will be the instance of BtnMgr and e.target will, as always, be the button.
However, that would fly in the face of convention and confuse anyone trying to understand your code, including yourself in 6 months time. In a click handler, this should always refer to the clicked element, as is natural.
If you really must have a reference from the handler back to the instance of BtnMgr that attached the click, then I might opt for "e-augmentation" like this :
function BtnMgr(msg, tgt_id) {
var that = this;
this.msg = msg;
this.tgt_id = tgt_id;
var selector = "#" + tgt_id;
$(selector).on("click", function(e) {
e.clickAttacher = that;
that.handleClick(e);
});
}
BtnMgr.prototype.toString = function(){
return "BtnMgr." + this.msg;
};
BtnMgr.prototype.handleClick = function(e) {
console.log("click attacher was instance of : " + e.clickAttacher.constructor.name); // BtnMgr
console.log("button id: " + e.target.id); // xxx
console.log("msg: " + e.clickAttacher.msg); // Hello World!
};
var b = new BtnMgr('Hello World!', 'xxx');
DEMO
Having done that, you have to ask whether it's really worthwhile defining handleClick in that way. Sure, if it's a monster function then yes, define it with BtnMgr.prototype...., but if it's only small, then define it in the constructor itself and take direct advantage of that being in the scope chain (as does the augmenter function above).
Try this when you bind your onClick:
function BtnMgr(msg, tgt_id) {
this.msg = msg;
this.tgt_id = tgt_id;
var selector = "#" + tgt_id;
$(selector).on("click", $.proxy(this.handleClick, this));
}
That would make sure that the 'this' variable in your callback is your class and not the clickevent.
You can read more about jQuery Proxy here: http://api.jquery.com/jquery.proxy/

Why do I get this JavaScript ReferenceError

What am doing wrong. I try to make object but when i try to initialize i get this error in console: I try to put all in document.ready and whitout that but dont work. In both case i have some error. Am new sorry for dumb question
ReferenceError: Circle is not defined
var obj = new Circle;
JS
$(function(){
var Circle = {
init: function() {
console.log("Circle initialized");
}
};
});
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="javascript/circle.js"></script>
<script>
$(document).ready(function(){
var obj = new Circle;
obj.init();
})
</script>
</head>
<body>
<div id="test" >TODO write content</div>
</body>
</html>
NEW UPDATE
$(function(){
window.Circle = {
init: function() {
console.log("Circle initialized");
}
};
window.Circle.init();
});
....
<head>
<script>
window.Circle().init();
</script>
</head>
You've defined your "Circle" function inside another function — the anonymous function you pass in as a a "ready" handler. Therefore, that symbol ("Circle") is private to that function, and not visible to the other code.
You can make it global like this:
window.Circle = {
// ...
};
You could also add it to the jQuery namespace (may or may not be appropriate; depends on what you're doing), or you could develop your own namespace for your application code. Or, finally, you could consider combining your jQuery "ready" code so that the "Circle" object and the code that uses it all appears in the same handler.
edit — another possibility is to move your "Circle" declaration completely out of the "ready" handler. If all you do is initialize that object, and your property values don't require any work that requires the DOM or other not-yet-available resources, you can just get rid of the $(function() { ... }) wrapper.
1) you are assigning Circle in a function context, not as a global. You can only use it there unless you expose it to global.
2) you are calling Circle as a constructor, but Circle is not a function.
This solves both issues:
var Circle = function () {};
Circle.prototype.init = function () {
console.log('Circle initialized.');
};
var obj = new Circle();
obj.init();

Objects's global variable

The problem is with object's variable:
this.timer
it's not "global", so when I click the stop button the value of the variable is wrong.
If I declare a global variable MyObject (loke var mytimer;) and use it instead this.timer, it works.
This is my code:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title></title>
<script type="text/javascript" language="JavaScript">
var MyObject = {
init: function(){
this.timer = 0;
document.getElementById("btn1").onclick = function(){
MyObject.RunIt();
};
document.getElementById("btn2").onclick = function(){
clearInterval(this.timer);
};
},
RunIt: function(){
var x=0;
this.timer = setInterval(function(){
x++;
document.getElementById("spn").innerHTML=x;
}, 1000);
}
};
</script>
<style type="text/css">
</style>
</head>
<body onload="MyObject.init();">
<input type="button" id="btn1" value="Run"/>
<input type="button" id="btn2" value="Stop"/>
<span id="spn"></span>
</body>
</html>
The problem is this: when you set "onclick" to a function call like that, there's no object reference in the call. The browser calls your function to do the "clearInterval", but "this" is not pointing to your object - in fact, it's pointing at the button element itself.
Here's one way to work around the problem:
var self = this;
document.getElementById('btn2').onclick = function() {
clearInterval(self.timer);
};
I know that question-askers on Stackoverflow get annoyed sometimes when people urge them to investigate jQuery or some other modern Javascript framework, but it's simply a better way to do things.
This is a common problem in writing javascript code; the Scope.
in an .onclick method on an element, the scope (this) is the element itself not the class you are in (MyObject).
i use this/that method; like below:
init: function(){
this.timer = 0;
document.getElementById("btn1").onclick = function(){
MyObject.RunIt();
};
var that = this;
document.getElementById("btn2").onclick = function(){
/**
Here i use 'that' instead of 'this';
because 'this' is the button element
*/
clearInterval(that.timer);
};
},
You can access an object through this only if the object was created by new.
The this in your code refers to the window object. In the event handlers it refers to the respective HTML element.
Read a detailled explanation.
Your MyObject declaration is an object, but lets say that it is not an object instance. There is a difference in JS.
Object instance example:
function MyClass() {
this.timer = 0;
this.RunIt = function() {
var x=0;
this.timer = setInterval(function(){
x++;
document.getElementById("spn").innerHTML=x;
}, 1000);
};
var me = this; // alias to current "this"
document.getElementById("btn1").onclick = function(){
// "this" refers to btn1, use me
me.RunIt();
};
document.getElementById("btn2").onclick = function(){
// "this" refers to btn2, use me
clearInterval(me.timer);
};
}
var MyObject = new MyClass();
Note, that there are many different ways to construct objects in JavaScript.
EDIT: it contains another bug: the event handler functions will be executed as members of the HTML element. So this in the handlers refers to the HTML element, not to your object.
EDIT: fixed the code
EDIT: Bad day, don't listen to me ;-)

Categories