OO JavaScript circumventing .call - javascript

Take a look at the fiddle here
In the show function the JavaScript call method is used to make this refer to the container variable in my contactForm object. I think, I'm not too sure about the magic that makes this work. Can someone elucidate why this does work, and what a good alternative might be?
JS
$(document).ready(function () {
var contactForm = {
container: $('#contact'),
config: {
effect: 'slideToggle',
speed: 400
},
/*******************/
init: function (config) {
$.extend(this.config, config);
$('<button>').text('Contact me')
.attr('type', 'button')
.insertAfter('#firstArticle')
.on('click', this.show);
//currently only logic on the close button
},
/*******************/
show: function () {
//using variable names to shorten up
var cf = contactForm,
container = cf.container,
config = cf.config;
if (container.is(':hidden')) {
cf.close.call(container);
container[config.effect](config.speed);
}
},
/*******************/
close: function () {
var self = $(this);
if (self.find('span.close').length) {
return;
}
$('<span>').addClass('close')
.text('close')
.prependTo(this)
.on('click', function () {
//self= span
self[contactForm.config.effect](500)
});
}
};
contactForm.init();
});

There's no magic at all; that's just how call works. call lets you call a JavaScript function and manually specify the this value therein, followed by all of the parameters, listed out individually.
So
cf.close.call(container);
calls cf.close with the this value set to container. Hypothetically, this
cf.close.call(container, 1, 'b');
would do the same thing, except also pass in 1 and 'b' as parameters.
Call is very, very similar to apply, with the difference being that apply takes all parameters as an array, rather than being listed out individually. So the (hypothetical) second example would be the same as
cf.close.apply(container, [1, 'b']);
This can be incredibly useful when you want to call another function, set the this value, and wholesale pass all of the current function's arguments along for the ride. Ie
someFunction.apply(thisValue, arguments);

Related

Correct way to override this JavaScript class method

How should I best go about overriding a JavaScript class method when it has been set up as per below. In this snippet, if I want to override the _other method from another JS file, loaded after this one, what is the correct way to go about it?
var review = {};
"use strict";
(function ($) {
review.list = {
_init: function () {
// The code I want to leave intact
},
_other: function () {
// The code I want to override
},
init: function () {
$(document).ready(function () {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
You can just assign to review.list._other. If you want to have access to the previous version, grab that first:
var oldOther = review.list._other;
review.list._other = function() {
// Your new code here, perhaps calling oldOther if you like
console.log("The new other code ran.");
};
Example:
// The original file
var review = {};
"use strict";
(function($) {
review.list = {
_init: function() {
// The code I want to leave intact
},
_other: function() {
// The code I want to override
},
init: function() {
$(document).ready(function() {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
// Your file after it
(function($) {
var oldOther = review.list._other;
review.list._other = function() {
// Your new code here, perhaps calling oldOther if you like
console.log("The new other code ran.");
};
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You're actually quite lucky it was written that way. It could easily have been written such that you couldn't override _other at all...
Slightly off-topic, but you've asked below:
Actually, does this class structure look reasonably sensible to you? Trying to dip toes into more OOP JS
I don't know your design constraints, so take anything that follows with a grain of salt... I should note that there's no "class" there at all (neither in the ES5 and earlier sense nor the ES2015 and later sense), just an object. (Which is fine.) But it looks like _init and _other are meant to be private; they could be genuinely private instead of pseudo-private without any cost — except then you wouldn't be able to override _other! :-) Separately, I would allow the overall controlling code to determine when the initialization happened instead of doing it on ready. (Separately, on a pure style note, I don't hold at all with this two-spaces-indentation nonsense so many of the l33t types seem to be promoting. If your code is so deeply nested that using only two spaces for an indent is necessary, it needs refactoring; four spaces is a good solid clear indent, in my view, without being so big it pushes your code off the right-hand side.)
So something like this if ES5 is required:
(function($) {
var list = {
init: function() {
_init();
_other();
}
};
function _init () {
// Can use `list` here to refer to the object
}
function _other() {
// Can use `list` here to refer to the object
}
review.list = list;
})(jQuery);
...but again, that makes it impossible (well, unreasonable) to override _other.
Or this if ES2015 and above is okay (for code this short, the differences are quite minor):
(function($) {
let list = {
init() {
_init();
_other();
}
};
function _init () {
// Can use `list` here to refer to the object
}
function _other() {
// Can use `list` here to refer to the object
}
review.list = list;
})(jQuery);
Just add your new override below... It will work...
var review = {};
"use strict";
(function($) {
review.list = {
_init: function() {
console.log('I\'m init')
},
_other: function() {
//This original will be overridden
console.log('Original')
},
init: function() {
$(document).ready(function() {
review.list._init();
review.list._other();
});
}
};
review.list.init();
})(jQuery);
review.list._other = function() {
console.log('overridden')
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

JavaScript: How to pass multiple arguments to a callback function inside an object's member?

When working with plugins I sometimes stumble upon one that has something like this:
It will take constructor arguments.
Some of the parameters resolve to functions.
Some of those will take multiple arguements.
like this:
{
color: function (someData, moreData) {
return someData + moreData;
}
}
How do I pass two arguments to that function?
Note: I am unable to change the plugin, so moving the second argument to another constructor argument is not an option.
Sample JavaScript:
(function ($) {
$.fn.greenify = function (options) {
var settings = $.extend({
color: function (someData, moreData) { // <-- THIS TAKES 2 ARGUMENTS!
return someData + moreData;
},
}, options);
return this.css({
color: settings.color,
});
};
}(jQuery));
$("#target1").greenify({
color: "blue"
});
$("#target2").greenify({
color: ["bl", "ue"] // <-- In your answer only this should be changed
});
My targets:
<div id="target1">My Thing</div> // <-- will be blue
<div id="target2">My Thing2</div> // <-- will be black but should be blue
FIDDLE DEMO
UPDATE:
I fell for a major misconception that this code
$("#target1").greenify({
color: "blue"
});
would hand the value "blue" over to function (someData, moreData) as first parameter someData. This is false! What actually happens is that the default callback (function (someData, moreData)) is completely replaced by the string "blue"!
How do I pass two arguments to that function?
You don't. jQuery does. It's a callback function to determine the color property dynamically in the .css() method - see the docs.
So actually, that default function doesn't make any sense as it should return a css color value but returns the old value prefixed by the index of the element - which is definitely not a color.
So just use $("#target2").greenify({color: "black"}).
If you want to pass a function, that is also possible - but it will overwrite, not call that default function. An example:
$("div").greenify({
color: function(index, prevColor) {
return ["blue", "black"][index % 2];
}
});
var settings = $.extend({}, options);
if($.isArray(options.color)) {
settings.color = function () {
return options.color.join('');
};
}
fiddle: http://jsfiddle.net/2QBGm/
See this jsFiddle
I have made some changes to your code and it's now working as you want it.
I used the options object to get the arguments:
color: function () {
return options.color[0] + options.color[1];
}

Is it a good practice to store jquery plugin configuration in data?

I want to create jQuery plugin with config (for example plugin myplugin).
Than call $(elem).myplugin(config); After that I want to call methods from this plugin like $(elem).myplugin().method() with already stored config.
My offer is something like that:
(function($) {
$.fn.myplugin = function(options) {
var $this = $(this);
var getOptions = function() {
return $this.data('myplugin');
};
var initOptions = function(opt) {
$this.data('myplugin', opt);
};
var setOption = function(key, value) {
$this.data('myplugin')[key] = value;
}
var updateBorderWidth = function() {
$this.css('border-width',
getOptions().borderWidth * getOptions().coeficient);
};
var init = function(opt) {
initOptions(opt);
updateBorderWidth();
}
function changeBorder(width) {
setOption('borderWidth', width)
updateBorderWidth();
}
if(options) {
init(options);
}
return {
changeBorder : changeBorder
};
}
})(jQuery);
And usage:
$(function() {
var item1 = $('#test1').myplugin({ coeficient: 1, borderWidth: 1 });
var item1 = $('#test2').myplugin({ coeficient: 2, borderWidth: 1 });
$('#btn').click(updateBorder);
});
function updateBorder() {
$('#test1').myplugin().changeBorder($('#inpt').val());
$('#test2').myplugin().changeBorder($('#inpt').val());
}
Example: http://jsfiddle.net/inser/zQumX/4/
My question: is it a good practice to do that?
May be it's incorrect approach. Can you offer better solution?
Edit:
After searching for threads on jQuery plugin template I found these Boilerplate templates (updated) which are more versatile and extensive designs than what I've offered below. Ultimately what you choose all depends on what your needs are. The Boilerplate templates cover more use cases than my offering, but each has its own benefits and caveats depending on the requirements.
Typically jQuery plugins either return a jQuery object when a value is passed to them as in:
.wrap(html) // returns a jQuery object
or they return a value when no parameter is passed in
.width() // returns a value
.height() // also returns a value
To read your example calling convention:
$('#test1').myplugin().changeBorder($('#inpt').val());
it would appear, to any developer who uses jQuery, as though two separate plugins are being utilized in tandem, first .myplugin() which one would assume will return a jQuery object with some default DOM maniplulation performed on #test1, then followed by .changeBorder($('#inpt').val()) which may also return a jQuery object but in the case of your example the whole line is not assigned to a variable so any return value is not used - again it looks like a DOM manipulation. But your design does not follow the standard calling convention that I've described, so there may be some confusion to anyone looking at your code as to what it actually does if they are not familiar with your plugin.
I have, in the past, considered a similar problem and use case to the one you are describing and I like the idea of having a convenient convention for calling separate functions associated with a plugin. The choice is totally up to you - it is your plugin and you will need to decide based on who will be using it, but the way that I have settled on is to simply pass the name of the function and it's parameters either as a separate .myplugin(name, parameters) or in an object as .myplugin(object).
I typically do it like so:
(function($) {
$.fn.myplugin = function(fn, o) { // both fn and o are [optional]
return this.each(function(){ // each() allows you to keep internal data separate for each DOM object that's being manipulated in case the jQuery object (from the original selector that generated this jQuery) is being referenced for later use
var $this = $(this); // in case $this is referenced in the short cuts
// short cut methods
if(fn==="method1") {
if ($this.data("method1")) // if not initialized method invocation fails
$this.data("method1")() // the () invokes the method passing user options
} else if(fn==="method2") {
if ($this.data("method2"))
$this.data("method2")()
} else if(fn==="method3") {
if ($this.data("method3"))
$this.data("method3")(o) // passing the user options to the method
} else if(fn==="destroy") {
if ($this.data("destroy"))
$this.data("destroy")()
}
// continue with initial configuration
var _data1,
_data2,
_default = { // contains all default parameters for any functions that may be called
param1: "value #1",
param2: "value #2",
},
_options = {
param1: (o===undefined) ? _default.param1 : (o.param1===undefined) ? _default.param1 : o.param1,
param2: (o===undefined) ? _default.param2 : (o.param2===undefined) ? _default.param2 : o.param2,
}
method1 = function(){
// do something that requires no parameters
return;
},
method2 = function(){
// do some other thing that requires no parameters
return;
},
method3 = function(){
// does something with param1
// _options can be reset from the user options parameter - (o) - from within any of these methods as is done above
return;
},
initialize = function(){
// may or may not use data1, data2, param1 and param2
$this
.data("method1", method1)
.data("method2", method2)
.data("method3", method3)
.data("destroy", destroy);
},
destroy = function(){
// be sure to unbind any events that were bound in initialize(), then:
$this
.removeData("method1", method1)
.removeData("method2", method2)
.removeData("method3", method3)
.removeData("destroy", destroy);
}
initialize();
}) // end of each()
} // end of function
})(jQuery);
And the usage:
var $test = $('#test').myplugin(false, {param1: 'first value', param2: 'second value'}); // initializes the object
$test.myplugin('method3', {param1: 'some new value', param2: 'second new value'}); // change some values (method invocation with params)
or you could just say:
$('#test').myplugin(); // assume defaults and initialize the selector
Passing parameters to javascript via data attributes is a great pattern, as it effectively decouples the Javascript code and the server-side code. It also does not have a negative effect on the testability of the Javascript code, which is a side-effect of a lot of other approaches to the problem.
I'd go as far as to say it is the best way for server-side code to communicate with client-side code in a web application.

Changing the state of a toggle in JavaScript/jQuery

Is it possible to change the state of a toggle function? Like:
myDiv.toggle ... function 1 , function 2
I click on the myDiv element, the function 1 executes
I click again, function 2
I click again, function 1
BUT
Change the state
function 1 again
etc.
But I need to be able to change the state from outside the toggle function.
Here is a javascript object that uses closure to track it's state and toggle:
var TOGGLER = function() {
var _state = true;
var _msg = "function1";
var function1 = function() {
_msg = "function1";
}
var function2 = function() {
_msg = "function2";
}
return {
toggle: (function () {
_state = !_state;
if (_state) {
function1();
} else {
function2();
}
return _msg;
})
}
}();
Here is a jsfiddle that shows how to use it to toggle based with the following jquery: http://jsfiddle.net/yjPKH/5/
$(document).ready(function() {
$("#search").click(function() {
var message = TOGGLER.toggle();
$("#state").text(message);
});
});
The toggle function is meant for simple use cases. Changing the state externally is not "simple" anymore.
You cannot easily/safely (it's internal so it may change during minor versions) access the state variable of the toggle function easily as it's stored in the internal dataset of the element.
If you really want to do it, you can try this code though:
$._data(ELEMENT, "lastToggle" + func.guid, 0);
func is the function you passed to .toggle(), so you need to save this function in a variable. Here's a minimal example: http://jsfiddle.net/xqgrP/
However, since inside the function there's a var guid = fn.guid || jQuery.guid++ statement, I somehow think that the devs actually meant to use guid instead of func.guid for the _data key - in that case a minor update is very likely to break things. And after the fix you'd have to iterate over the data set to retrieve the correct key as there is no way to access the guid from outside.

Javascript Class Inheritance

Can anyone tell me why my 'showDiv_boo' is undefined inside the class´s method?
I also can´t access my class´s methods.
Here´s my class 'Blink' class with its properties and methods:
function Blink(div) {
this.div = div
}
Blink.prototype.counter = 0
Blink.prototype.showDiv_boo = true
Blink.prototype.showDiv = function() {
this.div.style.visibility = 'visible'
}
Blink.prototype.hideDiv = function() {
this.div.style.visibility = 'hidden'
}
Blink.prototype.startEngine = function() {
if (this.showDiv_boo) {
this.showDiv()
} else if (!this.showDiv_boo) {
this.hideDiv()
}
this.showDiv_boo = !this.showDiv_boo
this.counter++
}
Blink.prototype.startEffect = function() {
this.idEffect = setInterval(this.startEngine, 1000 / 45)
}
So, if I create:
_blink = new Blink(myDiv);
_blink.startEffect();
You can test... the variable 'showDiv_boo', is undefined inside the method.
Even, if I set the showDiv_boo inside the method to true, it won´t call my class´s methods showDiv or hideDiv.
Anyone?
Thanks :)
The reason why is that startEngine is called from setInterval. The way in which this callback is invoked causes startEngine to have a different value for this than startEffect. You need to save this in order to maintain it in the callback. For example.
Blink.prototype.startEffect = function () {
var self = this;
self.idEffect = setInterval(function () { self.startEngine(); }, 1000 / 45);
};
You need to:
use var self and call the method via self.startEngine()
use an anonymous function to wrap the call in [1] i.e. function(){ self.startEngine(); }
This is because when you just pass this.startEngine or self.startEngine you are just passing the function startEngine without specifying what this is, which in both cases is supplied by the global conext of DOMWindow.
To give an example...
function startEngine() {
...code omitted...
};
Blink.prototype.startEngine = startEngine;
Blink.prototype.start = function() {
setTimeout(startEngine, 0); // obviously wrong, what is this?
setTimeout(Blink.startEngine, 0); // actually the same as line above, although not as obvious
setTimeout(startEngine.bind(this), 0); // works correctly
}
works to add code to the prototype and if used in the anonymous function will work as expected, but if you just use Blink.startEngine as the callback it is exactly the same as using startEngine only the second is more obviously wrong because there's no object it is being called on so you'd expect this to be whatever is supplied by the context.
The other way you could do this without using the anonymous function would be
Blink.startEngine.bind(self)
Which returns a function that will call startEngine with the correct this same as explicitly creating the anonymous function and wrapping the call to self.startEngine()
Heres a link to a fiddle to play around with the differences: http://jsfiddle.net/bonza_labs/MdeTF/
If you do the following, you will find it is defined
var x = new Blink('hello');
x.showDiv_boo
Javascript uses prototypical inheritance. While showDiv_boo may not be explicitly defined within the instance of Blink that you now have, it does exist within the prototype that Blink inherits from. When you try referencing showDiv_boo from within the object, the Javascript engine realizes the object does not own a member by that name and then will check its prototype.
Along with setting a temporal variable to store this, you must call the startEngine() function with that variable:
Blink.prototype.startEffect = function(){
var self = this;
self.idEffect = setInterval(function(){ self.startEngine.call(self); }, 1000/45);
}
Note the .call(self), which basically calls the function with the variable self, so the variable this in startEngine will be the correct one.

Categories