I'm playing around with jQuery plugin development, and I'd like to chain methods. I read in the jQuery tutorial (https://learn.jquery.com/plugins/basic-plugin-creation/) that you can chain methods by adding return this; to the end of the method, and that works for the first method (test 1). How can I do that for the second method (test 2), which uses console.log? Can all methods be chained?
// test 1
$.fn.greenify = function () {
this.css('color', 'green');
return this;
};
// test 2
$.fn.console = function () {
this.on('click', function () {
console.log('hello world');
});
};
$('a').greenify().console();
The second method should return the jQuery instance. The fact that the event handler uses console.log function has nothing to do with the returned value of that method. As on returns the jQuery object you can code:
$.fn.console = function () {
return this.on('click', function () {
console.log('hello world');
});
};
Now the console method is chainable!
Related
This question already has answers here:
The value of "this" within the handler using addEventListener
(10 answers)
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
Original Modal
I want to use a universal app object. To this empty object I will add functions as needed. The issue is that some functions will need to all others within the app object.
So, my question is: how do I construct a large object without having to define all functions inside the object at the time of creation? I would like to split up the chunks of code to not have one astronomical long .js file.
There is a simple example of my original code:
var app = {
tow: function () {
return true;
},
one: function () {
return this.tow();
}
};
// app.one() => returns true
Updated Modal
Here is something I found interesting. I was playing around with the prototype modal and discovered something strange. When I use this model I can add functions that can call other added functions. But, when I create an event listener it is unable to run the code. Can anyone explain why this is?
Modified code with unexpected result:
function modal () {}
modal.prototype.one = function () {
return this.two();
};
modal.prototype.two = function () {
return "cool";
};
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", this.one);
}
var app = new modal();
app.init();
// app.one() => returns true
// event listener => returns "TypeError: this.two is not a function"
JSBIN: https://jsbin.com/vureruziza/edit?js,console,output
this.one called as you done refers to addEventListener function, not to your object. This will solve the issue
modal.prototype.init = function () {
var self = this;
document.getElementById('go')
.addEventListener("click", function(){
self.one()
});
}
bind the click function with this cause the function will need the this context, not the window context. Then call your this.one function in de click handler.
function modal () {}
modal.prototype.one = function () {
return this.two();
};
modal.prototype.two = function () {
return "cool";
};
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", function(e){
console.log(this.one())
}.bind(this));
/*
The following wil also be called but your return value
of your this.one function won't be captured. But your code will run.
.addEventListener("click", this.one.bind(this));
Try replacing it with the above and put a debugger statement in this.one
and see that the code will actualy be ran, just not captured to output.
*/
}
var app = new modal();
app.init();
// app.one() => returns true
// event listener => returns "TypeError: this.two is not a function"
<div id="go">go</div>
Use ES6 fat arrow function. Update modal.prototype.init as below -
modal.prototype.init = function () {
document.getElementById('go')
.addEventListener("click", () => this.one());
}
Edit - If you wanted to debug the issue, you could just console.log the this value in function one like so -
modal.prototype.one = function () {
console.log(this);
return this.two();
};
You will most likely see the window object. You will certainly not see the modal object.
I have a javascript function deleteSomething(), within which the load() function in jQuery is called.
function deleteSomething(a, b){
deleteIt(a, b, function(success){
if(success){
$("#table").load(...);
}
})
}
Now I want to test deleteSomething() with Jasmine to see if load() is being called. But got Error: Expected a spy, but got Function
describe("deleteSomething", function() {
beforeEach(function () {
...
});
it("should call load() when deleteIt returns true", function() {
spyOn($('#table'), 'load'));
deleteIt.and.callFake(function(a, b, callback){
callback(true);
});
deleteSomething(a, b);
expect($('#table').load).toHaveBeenCalled();
});
});
I'm new to Jasmine, how should I do this?
You need to spy on jQuery prototype, it is available here: $.fn.
So your code should look something like this:
describe("deleteSomething", function() {
it("Should call load", function() {
//$.fn is where the load function is defined
// $("...") returns a new jQuery.fn instance that has load function
// from jQuery.fn.prototype
spyOn($.fn, 'load');
$("#anything").load();
expect($.fn.load).toHaveBeenCalled();
});
});
Some more information about the difference of object own members and prototype members (inherited) can be found here.
I am not asking what is the appropriate syntax for chaining, I know it could be something like:
$('myDiv').removeClass('off').addClass('on');
As far as I know chaining is one of the advantages against other famous frameworks. Can someone explain to me how chaining works here?
If you have an object with certain methods, if each method returns an object with methods, you can simply call a method from the object returned.
var obj = { // every method returns obj---------v
first: function() { alert('first'); return obj; },
second: function() { alert('second'); return obj; },
third: function() { alert('third'); return obj; }
}
obj.first().second().third();
DEMO: http://jsfiddle.net/5kkCh/
All that it is doing is returning a reference to this when the method finishes. Take this simple object for example:
var sampleObj = function()
{
};
sampleObj.prototype.Foo = function()
{
return this;
};
You could chain these calls all day because you return a reference to this:
var obj = new sampleObj();
obj.Foo().Foo().Foo().Foo() // and so on
jQuery simply performs an operation, then returns this.
Basically the first function call $('myDiv') returns a jQuery object, then each subsequent call returns the same one.
Loosely,
var $ = function(selector) {
return new jQuery(selector);
};
jQuery.prototype.removeClass = function(className) {
// magic
return this;
}
return $this;
each jQuery function returns an instance of the jQuery class, which can then have methods called on it. you could break it down, and this code would have the same effect.
jQuery_obj = $('myDiv');
jQuery_obj = jQuery_obj.removeClass('off');
jQuery_obj = jQuery_obj.addClass('on');
The point is that a function must evaluate to the "parent" function. So e.g.
foo().bar().test();
has to evaluate to:
foo().test();
so that you can call another function on foo(). To do this, you can return this:
function foo() {
// empty, nothing interesting here
}
foo.prototype.bar = function() {
return this;
}
foo.prototype.test = function() {
return this;
}
Then,
var something = new foo();
something.bar() === something; // true
And because of this:
something.bar().test() === something.test(); // true
So because something.bar() evaluates to something, you can immediately call the second function in one go.
In chaining parent function/method returns an object which is then used by the child function/method, and things go on such a way. In short the jQuery or $ returns itself (an object) which allows the chaining.
It is the same mechanism below
var obj=$('input'); //returns jQuery object
var obj1=obj.val('a'); //returns jQuery object
var obj2=obj1.fadeOut();//returns jQuery object
It looks like this if it is done with chaining
$('input').val('a').fadeOut();
Here is an example of conditional callback chaining, like is used on the $.ajax jQuery function.
// conditional callback function example
myFunction = function () {
// define event callback prototypes without function parameter
var callback_f = new Object;
callback_f.callback1 = function () { return callback_f; };
callback_f.callback2 = function () { return callback_f; };
if ([condition]){
// redefine the callback with function parameter
// so it will run the user code passed in
callback_f.ReturnPlayer = function (f) { f(); return callback_f; };
}else{
callback_f.NewPlayer = function (f) { f(); return callback_f; };
}
return callback_f;
}
One of the way to chaining, check demo .
test("element").f1().f2().f3()
Chaining is used to connect multiple events and functions in a selector.
To run multiple jQuery commands, one after the other, on the same element(s). Generally chaining uses the jQuery built in functions that makes compilation a bit faster.
It makes your code short and easy to manage and it gives better performance,
Eaxample
Without Chaining
$(document).ready(function(){
$('#dvContent').addClass('dummy');
$('#dvContent').css('color', 'red');
$('#dvContent').fadeIn('slow');
});
With Chaining
$(document).ready(function(){
$('#dvContent').addClass('dummy')
.css('color', 'red')
.fadeIn('slow');
});
Note: The chain starts from left to right. So left most will be called first and so on.
Chaining allows us to run multiple jQuery methods (on the same element) within a single statement.
The following example chains together the css(), slideUp(), and slideDown() methods. The "p1" element first changes to red, then it slides up, and then it slides down :
$("#p1").css("color", "red").slideUp(2000).slideDown(2000);
Problem
I just started learning javascript. I am trying to reproduce a working piece of code in a more modular way. It helps me keep things clean, and understand it better.
I am sure that there are better efficient or concise ways to achieve what the code does so please ladies/gents don't mention it-you can save your breath on that. The point here is to learn things I do not understand yet by playing with the code.
What does the code do
It creates an alternative method to the method toggle that has been deprecated
that can then be used in the following fashion $('#foo h2').mytoggle(plus,minus);
Below is the original piece of code:
$.fn.clicktoggle = function(a, b) {
return this.each(function() {
var clicked = false;
$(this).click(function() {
if (clicked) {
clicked = false;
return b.apply(this, arguments);
}
clicked = true;
return a.apply(this, arguments);
});
});
};
Below is my version of the previous code:
function call_a_or_b (a,b) {
var clicked = false;
function alternate (a,b) {
if (clicked) {
clicked = false;
return a.apply(this, arguments);
}
else {
clicked = true;
return b.apply(this, arguments);
}
} // end function alternate
return $(this).each(function () {$(this).click(alternate(a,b))});
} //end function call_a_or_b
$.fn.clicktoggle = function(a,b) { call_a_or_b(a,b); };
Questions
Why the original version uses
return this.each
instead of return $(this).each?
Note: I cannot use this on my version otherwise it returns an error: Uncaught TypeError: Object [object global] has no method 'each'
Isn't each a jQuery method?
To my understanding when using this, you can call DOM methods on it, but not jQuery methods. And vice versa.
How come my version is not working? What am I missing?
Note: I have no errors, so it's harder to debug.
Inside a plugin object this refers to the jQuery wrapper object on which the plugin was initiated, not the dom object as in the case of other methods
Since this is the jQuery wrapper object .each() is available
There are multiple problems in your implementation
When you are calling call_a_or_b you are not passing the execution context to the method, so this inside your method refers to the window object
As a rule of thumb, to enable chaining in jQuery you need to return the jQuery wrapper, which you are not doing
There was closure and invokation related issues with the alternate method
Try
(function ($) {
function call_a_or_b(a, b) {
//for each matched element the clicked variable should be a closure one, so needed to rearrage it a bit
function alternate(el) {
var clicked = true;
$(this).click(function () {
if (clicked) {
clicked = false;
return a.apply(this, arguments);
} else {
clicked = true;
return b.apply(this, arguments);
}
})
} // end function alternate
return $(this).each(alternate);
} //end function call_a_or_b
$.fn.clicktoggle = function (a, b) {
//call the method with the current execution context and return the value returned fromit
return call_a_or_b.apply(this, arguments);
};
})(jQuery);
Demo: Fiddle
When you assign a function to $.fn it gets executed in the context of jQuery , so this is a jQuery object. Your function gets executed most likely in the context of the window. If you change the last line to this , it should work exactly the same:
$.fn.clicktoggle = call_a_or_b(a,b);
I need for a function to be executable only after an object is defined, I'm currently working in a fascade pattern and one method is dependent on another method. in this case 'addNewLayer' fails because 'setFullMap' hasn't finished executing. is there a solution? I'm using jquery and vanilla js so most any solution would be helpful at this point:
var jen = (function(){
function setFullMap(mapID){
jen.map = new Map(mapID);
}
function setLayer(opt){
//execute code here after jen.map is defined
}
return{
samp: function(id, opt){
setFullMap(id);
addNewLayer(opt);
}
};
})();
Thanks
solution:
var jen = (function(){
function setFullMap(mapID, callback) {
jen.map = new Map(mapID);
if(jen.map){
callback();
}
}
return {
samp: function(id, opt){
setFullMap(id, function(){
addNewLayer(opt);
}.bind(this));
}
};
})();
You will have to pass a callback function to setFullMap, and execute it once the function has completed (at the very end, before the closing }).
var jen = (function(){
function setFullMap(mapID, callback){
jen.map = new Map(mapID);
callback();
}
function setLayer(opt){
//execute code here after jen.map is defined
}
return{
samp: function(id, opt){
setFullMap(id, function() {
addNewLayer(opt);
}.bind(this));
}
};
})();
Do not forget using .bind(this) - it is very important in order to keep the original this in your callback function.
Edit:
Actually that would not work work if the Map constructor is a-synchronous. If you do not have access to the constructor and/or you cannot pass it a callback, then presumably the only (and sad) option would be to use a setTimeout or (easier) setInterval, continuously checking at defined intervals if the operation has been completed, and then fire the callback.
You could use a callback parameter:
function setFullmap(mapId,callback) {
jen.map = new Map(mapId);
callback();
}
....
samp: function(id, opt){
setFullMap(id,function() {
addNewLayer(opt);
});
}
When u dont have a way to manipulate the Map Object then u need to use a loop:
var loop=self.setInterval(function(){
if(jen.map) {
//execute code here after jen.map is defined
console.log(typeof jen.map);
window.clearInterval(loop);
}
},50);
Check jsfiddle:
http://jsfiddle.net/9yv5t/1/
I have checked the docs and it seems that there are various events you could listen to.
For example:
var m = new Map(...);
m.on('load', function () {
//execute code when the first layer is ready
});
var l = new Layer(...);
l.on('load', function () {
//execute code when the layer has been initialized
});
It's also carefully stated for the Layer.load event:
fires after layer properties for the layer are successfully populated.
This event must be successful before the layer can be added to the
map.