I've created some kind of JavaScript object with the such design pattern:
var MyObject = (function(){
var element;
var config = {
// Defaults...
};
function initialize(id, options){
element = $(id);
config = $.extend(config, options);
}
function method(){
// Some code...
}
element.on('click', function(){
// Some actions when clicked on element...
});
return {
initialize: initialize,
// Other public methods...
};
})();
And that's how object is initialized:
MyObject.initialize('#someId');
Script works great, however an error occurs, when I try to add some events for element.
As I realized, an anonymous function (function(){ ... })(); is executed immediatelly, but initialization of variable elementcomes after.
So, how can I implement an event handling exactly for this JavaScript object pattern?
Thanks!
You should be calling on inside the initialize function. Since the outer function is executing immediately (as you realized yourself), element is undefined. So you should only be calling on on it, after the variable has been defined:
function initialize(id, options){
element = $(id);
element.on('click', function() {
...
});
config = $.extend(config, options);
}
Stick it in the initialize method.
function initialize(id, options){
element = $(id);
config = $.extend(config, options);
element.on('click', function(){
// Some actions when clicked on element...
});
}
Related
On the simple example below and on JSFiddle here - https://jsfiddle.net/jasondavis/dnLzytju/ you can see the issue I have.
I can see why it could happen but I am not sure how to fix it while keeping the same JS structure.
The issue is when I define a JavaScript objects prototype functions and I have a 2nd level nested object which has a function and in that function I call a function on the parent/root level it fails.
This function from the code below this.nestedObject.nested_object_function() tries to call the function this.normal_function() however it fails and says:
Uncaught TypeError: this.normal_function is not a function
at Object.nested_object_function (VM2493:79)
I assume the reason is that this is referencing this.nestedObject instead of the parent object.
If that is the case, then how can I call that function like I am trying to do from the nested object function and call a parent function?
I have also tried calling JsLibTest.normal_function() as a test from the this.nestedObject.nested_object_function() function but I get the same error.
var JsLibTest = (function (document) {
"use strict";
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
};
/**
* JsLibTest prototype functions
*/
JsLibTest.prototype = {
init: function() {
// as expected this function runs fine
this.normal_function();
// nested level objects functions run fune from parent level object function
this.nestedObject.nested_object_function();
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
// calling a function on the parent object fails here when called from this nested object function
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
},
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function(){
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Your assessment is correct. this will be set to the nested object instead of the parent object and that's why it says the function is undefined.
What you need is a way of referencing the parent. Objects don't normally carry any information needed to reference an object which references them. This makes sense when you consider the fact that many objects can reference the same object internally.
You can either store a reference to the parent object and reference that in the nested function:
var nested = {
g() {
this.parent.f();
}
};
var parent = {
f() {
console.log('called');
}
};
nested.parent = parent;
nested.g();
or you can use Function.prototype.call (or something similar) to set the correct context.
var obj = {
f() {
console.log('called');
},
g() {
this.nested.nested_f.call(this);
},
nested: {
nested_f() {
this.f();
}
}
};
obj.g();
Putting the last solution in to the context of your problem:
var JsLibTest = (function(document) {
"use strict";
var JsLibTest = function() {
this.init();
};
JsLibTest.prototype = {
init: function() {
this.normal_function();
// NOTICE: Using .call here to set the context
this.nestedObject.nested_object_function.call(this);
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
}
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function() {
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You are correct that scope doesn't have access to the parent. Easy solution would be that you pass parent to the nested object like:
this.nestedObject.nested_object_function(this);
then in your nested function call parent as:
nested_object_function: function(self) {
self.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
since you pass this (parent) as self you can then call it from nested one.
At first, the Object must be unique for Each, having a prototype:
this.nestedObject=Object.create(this.nestedObject);
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
this.nestedObject.parent=this;
};
Now you can use this.parent inside of your inner function...
this.parent.normal_function();
If you want this to be the parent, bind:
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
for(i in this.nestedObject){
var el=this.nestedObject[i];
if(typeof el==="function"){
this.nestedObject[i]=el.bind(this);
}
}
};
To make it easier, may use sth like that ( a helper function):
getfunc:function(...a){
a.reduce((obj,key)=>obj[key],this).bind(this);
}
Use like this:
JsLibTestInstance("nestedObject","nestedobject_function")();
Yea, you are right that the this value in your JSLibTest.prototype.nestedObject function is pointing to nestedObject and not JSLibTest.
If you want to maintain the same call signature, you can declare nestedObject as an IIFE:
nestedObject: (function() {
var that = this;
return {
nested_object_function: function() {
console.log(that);
// this.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
}
}())
https://jsfiddle.net/dnLzytju/1/
Note: You probably do not want to declare your prototype that way is it effectively deletes all the native prototype methods of the object.
To author your code in a similar way, consider using Object.assign to help you out.
var foo = Object.assign({}, Function.prototype, {
bar() {
console.log("Hello!")
}
});
foo.bar();
I have two different functions who’s values are self-invoking. In each function I have an “init” method. If I use document.ready to fire “init()” inside both functions – they both fire only the last “init”, not their own (so it gets called twice in my example on jsFiddle).
var LightBox = (function(){
var me = this;
$(document).ready(function(){ me.init(); });
me.init = function(){
console.log('Lightbox Init');
}
})();
var CustomAnchors = (function(){
var me = this;
$(document).ready(function(){ me.init(); });
me.init = function(){
console.log('CustomAnchors Init');
}
})();
This results of this code logs "CustomAnchors Init" twice, but I expect it to log "Lightbox Init" followed by "CustomAnchors Init"
Why is this? And is there a way to avoid this?
You don't really need to do any of that, you should be able to just send in the function directly to document.ready. You don't need to wrap it inside that anonymous function at all:
var LightBox = (function() {
var init = function(){
console.log('Lightbox Init');
}
$(document).ready(init);
})();
var CustomAnchors = (function(){
var init = function(){
console.log('CustomAnchors Init');
}
$(document).ready(init);
})();
An explanation of what you were trying to do:
As others have already said, since Lightbox is just a straight up function (even though it's an IIFE), referring to this inside it will just refer to the global object. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
What you're trying to do is refer to this as a custom object, but for that to work you have to instantiate an object. One method to do this is using the new operator, as in new Lightbox() (if Lightbox is a function) or new function() if you want to just create one instance from an anonymous function.
With your specific code it would be
var LightBox = new function() {
var me = this; // this will now refer to the instantly instantiated LightBox object
$(document).ready(function(){ me.init(); });
this.init = function(){ // You could use me.init here but I think it makes for harder-to-understand code
console.log('Lightbox Init');
}
};
var CustomAnchors = new function(){
var me = this; // and same here, for the CustomAnchors object
$(document).ready(function(){ me.init(); });
this.init = function(){
console.log('CustomAnchors Init');
}
};
But again, you don't really need to wrap it. I'm just explaining here what you were trying to do.
This seems to work:
var LightBox = (function(){
$(document).ready(function(){ init(); });
var init = function(){
console.log('Lightbox Init');
};
})();
var CustomAnchors = (function(){
$(document).ready(function(){ init(); });
var init = function(){
console.log('CustomAnchors Init');
};
})();
The value of this in your example was not the function object you expected.
Try returning functions from both LightBox , CustomAnchors ; utilizing .ready() outside of IIFE with variables LightBox , CustomAnchors as functions within array to be called by .ready()
var LightBox = (function(){
return function(){
console.log('Lightbox Init');
}
})();
var CustomAnchors = (function(){
return function(){
console.log('CustomAnchors Init');
}
})();
$(document).ready([LightBox, CustomAnchors])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 8 years ago.
As in, sometimes when I look at code by other people, they will go var self = this; or in jquery for example, go var $self = $(this);
is there a particular reason for doing so?
It preserves the value of this for use in functions defined inside the current function.
// Contrived example
var myObject = {
func: function () {
var self = this;
setTimeout(bar, 1000);
function bar () {
alert(this); // `window`
alert(self); // `myObject`
}
}
};
myObject.func();
By holding a reference to this in some context, you have the ability to access it in other contexts such as within member functions or forEach loops.
Consider the following example:
function ViewModel() {
var self = this;
self.linksArray = ["link1", "link2", "link3"];
self.linksArray.forEach(function(link) {
// this refers to the DOM window
// and self refers to the parent context (ViewModel)
});
};
As others have mentioned, you could set a variable to $(this) if you wish to use it in another function.
On practical example would be when doing an ajax call tied to an event on the page. Using JQuery:
<script>
$(document).on("click", ".mySelector", function () {
// Where we are in the click event, $(this) refers to whatever
// element has a class of mySelector that was clicked
var self = $(this);
theDiv.html('');
$.ajax({
cache: false,
type: "GET",
url: "/SomeAjaxMethod",
data: { },
success: function (data) {
// Trying to access $(this) here will return undefined, as
// we are technically in the callback method
// Where our event is based on a class, there is likely more
// than one element on the page with the class, so it would be
// difficult to get the exact element again without some other code
self.html(data);
},
error: function (xhr, ajaxOptions, thrownError) {
alert("Ajax failed.")
}
}); // end ajax call
}); // end on mySelector class click
</script>
or:
<script>
$(document).ready(function () {
$('.foo').click(function () {
var self = $(this); // Whatever element that was clicked with foo class
$('.bar').each(function () {
var bar = $(this); // Current iteration of bar element in the loop
var baz = self; // self is still the initial value, but $(this) is not
}); // end bar loop
}); // end foo click
}); // end doc ready
</script>
The particular example (not using JQuery) is the function closure. Referencing this in a function closure refers to the function object, not the context in which the closure was defined. Your example is one way to deal with the closure problem:
var that = this;
function(){
that.something = 1;
}();
Another way to deal with this is with the apply method on the function:
function(arg){
this.something = 1;
}.apply(this, argumentArray);
The first argument in apply is the "this argument" that "this" will refer too.
One purpose of that would be to make this accessible to inner functions. for example:
function clickHandler(){
console.log(this); // this is body
var $self = this;
function inner(){
console.log(this); // this is window
console.log($self); // this is body
}
inner();
}
$("body").click(clickHandler);
Run it in console to get a sense.
At the moment I came this far.
function Class() {
var privateMethod = function () {
return 'private'
}
this.publicMethod = function () {
return 'public'
}
var _constructor = function () {
$(document).on('click', _onClick)
}
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(this.publicMethod())
}
_constructor()
}
$(document).ready(init)
function init() {
new Class()
}
The problem is that, in the click event, I am unable to call publicMethod.
I am able to call the private method.
How can I achieve this?
The problem is that, in your handler you've lost your context (this no longer means your instance of Class, it instead means the object that triggered your event. You need to create a closure scoped version of this to hold onto that context.
var self = this;
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(self.publicMethod())
}
You have a scope issue, this in the onclick is pointing to a different object than what you expect. In your case it is the document
var that = this;
var _onClick = function () {
// My error is `this`, focus now on the click event, but I need the object itself
console.log(privateMethod())
console.log(that.publicMethod())
}
Running Example
My goal is to be able to call functions that are inside my JQuery plugin.
What is the correct syntax?
For example, this does not work:
Click Me
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script>
(function($) {
$.fn.foo = function(options) {
do_stuff = function(){
console.log("hello world!"); // works
do_other_stuff = function(){
alert("who are you?");
}
} // function
} // function
})(jQuery);
$("body").foo();
$("#click_me").click(function(){
$.fn.foo.do_stuff.do_other_stuff(); // doesn't work
});
</script>
when you assign functions to variables without the var keyword they either overwrite the local variable of that name or the are added to the global namespace. (so your do_stuff is a global function, which is not what you want)
one way to do what you want is to explicitly give where you want your function to reside.
(function($) {
$.fn.foo = function(options) {
// whatever $().foo() should do
};
$.fn.foo.do_stuff = function() {
console.log("hello world!");
};
$.fn.foo.do_stuff.do_other_stuff = function(){
alert("who are you?");
};
})(jQuery);
Edit:
This works because all functions in javascript are objects, which means you can assign values to any arbitrary property.
If you want to access the variables of other functions you can move the definitions inside of the other ones like:
$.fn.foo.do_stuff = function() {
console.log("hello world!");
$.fn.foo.do_stuff.do_other_stuff = function(){
alert("who are you?");
};
};
but this will mean the function is only defined once you run the other function, and that each time you run the function it will overwrite the last definition.
Possibly a more ideal solution would be to have each function return an object containing the nested function like so:
(function($) {
$.fn.foo = function(options) {
// whatever $().foo() should do
var do_stuff = function(do_stuff_args) {
console.log("hello world!");
// do stuff with options and do_stuff_args
var do_other_stuff = function(other_args) {
alert("who are you?");
// here you have access to options, do_stuff_args, and other_args
};
return {
do_other_stuff: do_other_stuff
};
};
return {
do_stuff: do_stuff
}
};
})(jQuery);
and call it using
foo().do_stuff(some_options).do_other_stuff(other_options);
or
var a = foo(stuff).do_stuff(some_options);
a.do_other_stuff(foo);
a.do_other_stuff(bar);