I am trying to bind a user defined callback as Backbone's click event.
var View = Backbone.View.extend({
events: {
'click': 'testClick'
},
tagName: "li",
attributes: {
class: "item"
},
initialize: function() {
this.render();
},
testClick: function(){
},
render: function() {
$("#container").append(this.$el.html("Click Me!!"));
}
});
function Item() {
var _view = View.extend({
testClick: this.testClick.bind(this)
});
this.view = new _view();
}
Item.prototype = {
testClick: function() {
alert("testClick from prototype called!");
}
};
var myItem = new Item();
myItem.testClick = function() {
alert("testClick from instance called!");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
</head>
<body>
<div id="container"></div>
</body>
</html>
Clicking on "Click me", it alerts "testClick from prototype called!"
I am not sure why the alert from the instance is not getting called. What am I doing wrong here? Please help!
Because the following line:
testClick: this.testClick.bind(this)
detaches the testClick function member of the Item instance. You are essentially reusing a function and there is no linkage between the 2 methods.
Consider this example:
var obj = {
foo: function() {
console.log('I was foo!');
}
}
var detachedFoo = obj.foo;
obj.foo = function() {
console.log('The new foo!');
}
obj.foo === detachedFoo // false
obj.foo() // logs 'The new foo!'
deatchedFoo(); // logs 'I was foo!'
If you use the following syntax the alert will show "testClick from instance called!".
testClick: () => {
this.testClick();
}
This is because the above code calls the current .testClick method of the Item instance.
Related
I'm trying to learn to use AngularJS 1.6 events.
I created a component which is registered to the event:
'use strict';
var module = angular.module("Test", []);
module.controller("Controller1", Controller1);
Controller1.$inject = ["$scope"];
function Controller1($scope) {
var self = this;
self.try = function () {
$scope.$broadcast("test:try");
}
}
module.controller("Controller2", Controller2);
module.component("compo2", {
controller: "Controller2",
});
Controller2.$inject = ["$scope"];
function Controller2($scope) {
var self = this;
this.$onInit = function () {
$scope.$on("test:try", self.try);
}
self.try = function (event, data) {
console.log("Logged event!!!")
}
}
and this is my html file:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="./Test/angular.js"></script>
<script src="./compoTest.js"></script>
</head>
<body ng-app="Test" ng-controller="Controller1 as ctrl1">
<compo2> T.E.S.T </compo2>
<button ng-click="ctrl1.try();"> Try! </button>
</body>
</html>
But when the callback "try" is called - I see that the this variable is the window instead of my component's controller.
What am I doing wrong?
This is what happens when object method is called as a callback in JS.
To avoid accidental use of window, use strict mode in the beginning of JS file:
'use strict';
The method should be bound to the context:
this.try = function (event, data) {
this....
}.bind(this);
Or self recipe should be used:
var self = this;
self.try = function (event, data) {
self....
};
I am learning how to create jQuery plugins and have built one using module pattern. It works if I apply it only once, however, if I apply multiple times, all of them get initialized using the last one's settings.
For instance, if I first do $('#div1').myPlugin();, and then later $('#div2').myPlugin({myProperty :'mydiv2Property'});, $('#div1') myProperty is changed from myDefaultProperty to mydiv2Property. The same thing happens when initializing with a different method.
I have a working (well, almost working!) example located at http://jsbin.com/eWePoro/1/, and my full script is listed below.
How do I change this script so each time the plugin is applied, it uses just its own properties and methods? Thank you
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>Testing</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js" type="text/javascript"></script>
<style type="text/css">
</style>
<script type="text/javascript">
(function($){
var defaults = {
myProperty :'myDefaultProperty',
myMethod1 :function () {
console.log('myMethod1',this,this.myProperty);
},
myMethod2 :function () {
console.log('myMethod2',this,this.myProperty);
}
};
var methods = {
init : function (options) {
var settings = $.extend(defaults, options || {});
settings.myMethod1();
return this.each(function () {
$(this).click(function(e) {
settings.myMethod2();
});
});
},
destroy : function () {
//Anything else I should do here?
delete settings;
return this.each(function () {});
}
};
$.fn.myPlugin = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.myPlugin');
}
};
}(jQuery)
);
$(function(){
$('#div1').myPlugin();
$('#div2').myPlugin({
myProperty :'mydiv2Property'
});
$('#div3').myPlugin({
myMethod1 :function () {console.log('myMethod1_new',this,this.myProperty);}
});
$('#div4').myPlugin({
myMethod2 :function () {console.log('myMethod2_new',this,this.myProperty);}
});
});
</script>
</head>
<body>
<div id='div1'>div1</div>
<div id='div2'>div2</div>
<div id='div3'>div3</div>
<div id='div4'>div4</div>
</body>
</html>
The problem is here:
var settings = $.extend(defaults, options || {});
You are actually modifying defaults here with new properties. So next time you run the same code, defaults will be mutated. You should probably do:
var settings = $.extend({}, defaults, options);
This will create a new settings object every time by cloning defaults before extending it.
DEMO: http://jsbin.com/eWePoro/2
The issue is in the model, when recalled by the presenter it does not work like I assumed, in fact it works as if the this keyword refers to an [object HTMLTextAreaElement], not to a Model object.
/** PRESENTER **/
function Presenter() {
var toolbarView;
var outputView;
var sourceCodeModel;
var public = {
setToolbarView: function (view) {
toolbarView = view;
},
setOutputView: function (view) {
outputView = view;
},
setModel: function (_model) {
sourceCodeModel = _model;
},
init: function () {
toolbarView.setNewTableHandler(function () {
outputView.updateSource(sourceCodeModel.string);
});
toolbarView.setNewConstraintHandler(function () {
/*stub*/
alert("new constraint");
});
}
}
return public;
}
/** TOOLBAR VIEW **/
function toolbarView() {
this.setNewTableHandler = function (handler) {
$("#newTable").click(handler);
}
this.setNewConstraintHandler = function (handler) {
$("#newConstraint").click(handler);
}
}
/** OUTPUT VIEW **/
var outputView = {
updateSource: function (newVal) {
$("#sourcetext").val(newVal);
},
draw: function () {
//stub
}
};
/** MODEL **/
var model = new Object(); //o {};
model.source = [];
model.string = function () {
/* ALERT(this) returns [object HTMLTextAreaElement] wtf? */
var stringa = "";
for (var i = 0; i < this.source.length; i++) { //this does not work, since this = HTMLTextAreaElement
stringa += this.source[i] + "\n";
}
return stringa;
}
$(document).ready(function () {
var presenter = new Presenter();
var view1 = new toolbarView();
presenter.setToolbarView(view1);
presenter.setOutputView(outputView);
presenter.setModel(model);
presenter.init();
});
and the HTML is pretty simple:
<!doctype html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="mvp.js"></script>
<meta charset="UTF-8">
<title>Titolo documento</title>
<style type="text/css">
/*unnecessary here*/
</style>
</head>
<body>
<div id="container">
<div id="toolbar">
<button id="newTable">New table</button>
<button id="newConstraint">New Constraint</button>
</div>
<div id="source">
<textarea id="sourcetext"></textarea>
</div>
<button id="update">Update</button>
<div id="output"></div>
</div>
</body>
</html>
What am i doing wrong on the model object?
When you pass a function as a listener the this property will not be available inside the function:
var obj = {
a: function() {
alert(this)
}
};
$('body').click(obj.a);
When body is clicked the function's this property will be document.body.
To prevent this you must bind the function:
$('body').click(obj.a.bind(obj));
Or in older browsers wrap it:
$('body').click(function() {
obj.a();
});
So you must bind the function before pass it:
outputView.updateSource(sourceCodeModel.string.bind(sourceCodeModel));
More info about javascript function's context: http://www.quirksmode.org/js/this.html
this line: var public = { try to do not to use public, that is reserved word.
And a general note, try to bind this to a variable, because this changes context where it is currently.
/** TOOLBAR VIEW **/
function toolbarView() {
var that = this;
that.setNewTableHandler = function (handler) {
$("#newTable").click(handler);
}
that.setNewConstraintHandler = function (handler) {
$("#newConstraint").click(handler);
}
}
This is a bit of a strange one. I have been trying to call a function in a child object from the parent object but it seems to be going out of scope in onbeforeunload function. These function calls work outside of a onbeforeunload, so it only fails when called in onbeforeunload. I can fix it by making the child object a global but I was trying to avoid that. Anyone know why my childobject is out of scope when called in onbeforeunload? Notice I call that same function in windows onload event and it works fine. Here is the code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Broken HTA</title>
<HTA:APPLICATION
ID="Broken HTA"
APPLICATIONNAME="Broken HTA"
BORDERSTYLE = "flat"
CAPTION="Yes"
CONTEXTMENU = "no"
INNERBORDER = "no"
MAXIMIZEBUTTON = "no"
MINIMIZEBUTTON = "yes"
NAVIGABLE = "No"
SCROLL = "auto"
SCROLL FLAT = "Yes"
SELECTION="no"
SYSMENU="yes"
SHOWINTASKBAR="yes"
SINGLEINSTANCE="yes"
VERSION = "1.0"
BORDER="thick"
>
<script language="javascript" type="text/javascript">
var myparent;
function windowLaunch()
{
myparent = new parent();
myparent.getchildvalue();
window.onbeforeunload = myparent.getchildvalue;
window.resizeTo(500, 610);
}
function parent()
{
this.mychild = new childobject("myinput");
this.getchildvalue = function()
{
var tmpval = this.mychild.returnvalue();
};
}
function childobject(thename)
{
this.myprop = thename;
this.returnvalue = function()
{
return (document.getElementById(this.myprop).value);
};
}
</script>
</head>
<body id="thebody" onload="windowLaunch();">
<div id="outerdiv">
<span title="This Input Box">My Input:</span><br />
<input id="myinput" style="width: 290px"/>
</div>
</body>
</html>
this is based on how a function is called. Calling myparent.getchildvalue() is fine, but as soon as you assign myparent.getchildvalue as a handler, it will be called out of context. You can demonstrate this simply:
var obj = { val: 42, fn: function(){ alert(this.val) } };
ref = obj.fn;
alert(obj.fn === ref); // true, so expect the following to do the same thing...
obj.fn(); // 42, so far so good
ref(); // undefined. uh oh...
You can get around this by wrapping:
...
window.onbeforeunload = function(){ myparent.getchildvalue(); };
...
or binding:
...
window.onbeforeunload = myparent.getchildvalue.bind(myparent);
...
When I try to call "Test" function I get an error.
How to fix that? (no jquery!)
Browser:firefox
error:
TypeError: this.Test is not a function
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Untitled Document</title>
<script type="text/javascript">
MyClass = function(){
}
MyClass.prototype = {
Init: function(){
var txt = document.getElementById("text");
if (txt.addEventListener) {
txt.addEventListener("keyup", this.Foo, true)
}
},
Foo: function(){
this.Test();
},
Test: function(){
alert('OK');
}
}
window.onload = function(){
obj = new MyClass;
obj.Init();
}
</script>
</head>
<body>
<textarea id="text" rows="10">
</textarea>
</div>
</body>
It's because you reference this.Foo as the event, what actually happens is that it copies that function out of the object scope, ergo this does not exist. What most people do is use an anonymous function / wrapper around the event.