I'm trying to improve my code overall, although the following code does work, I want to avoid using _this (to me a hacky way of doing it ), and start using either .call/.apply or .bind to set the context of this on the following example.
Here is the code and the link to jsfiddler link.
(function (window, $) {
'use strict';
var ButtonEffect, button;
Function Constructor
ButtonEffect = function (elem) {
this.button = $( elem );
};
//Prototype Chain
ButtonEffect.prototype = {
addEffect : function (ref) {
return $(this.button, ref).addClass('custom-effect-1');
},
btnObserver : function () {
//Don't want to use this approach
var _this = this;
this.button.on({
click : function () {
//Want to call addEffect without using _this/that #hack
_this.addEffect($(this));
}
});
}
};
button = new ButtonEffect('.my-button');
button.btnObserver();
(window, window.jQuery));
Here is another Solution i came up with link
Seems more appropriate to use the built in jQuery methods for passing data to the event handler, that way this still references the element inside the handler
(function (window, $) {
'use strict';
var ButtonEffect, button;
ButtonEffect = function (elem) {
this.button = $( elem );
};
ButtonEffect.prototype = {
addEffect : function (ref) {
return $(this.button, ref).addClass('custom-effect-1');
},
btnObserver : function () {
this.button.on( {
click : function (e) {
e.data.scope.addEffect($(this));
}
}, {scope: this});
}
};
button = new ButtonEffect('.my-button');
button.btnObserver();
}(window, window.jQuery));
FIDDLE
You can change your code like this:
btnObserver : function () {
this.button.on({
click : function (ev) {
this.addEffect($(ev.currentTarget));
}.bind(this)
});
}
ev.currentTarget is usually the same as what this would be if bind is not used. And bind makes it so that the value of this inside your event handler is the same as the scope in which bind executes. I have a fiddle.
Related
I have an Object and I want to bind a function to a button when Object was initialized.
var MyClass = {
Click: function() {
var a = MyClass; // <---------------------- look here
a.Start();
},
Bind: function() {
var a = document.getElementById('MyButton');
a.addEventListener('click', this.Click, false); //<---------- and here
},
Init: function() {
this.Bind();
}
}
So, I'm new at using it and I don't know if object can be declared like this (inside Click() function that should be done after clicking a button):
Is it a bad practise? Which could be the best way in this case when adding an event here?
Edit: fiddle
Firstly you have a syntax error. getElementsById() should be getElementById() - no s. Once you fix that, what you have will work, however note that it's not really a class but an object.
If you did want to create this as a class to maintain scope of the contained methods and variables, and also create new instances, you can do it like this:
var MyClass = function() {
var _this = this;
_this.click = function() {
_this.start();
};
_this.start = function() {
console.log('Start...');
}
_this.bind = function() {
var a = document.getElementById('MyButton');
a.addEventListener('click', this.click, false);
};
_this.init = function() {
_this.bind();
};
return _this;
}
new MyClass().init();
<button id="MyButton">Click me</button>
For event listeners it's easiest and best to use jQuery, for example if you want to have some .js code executed when user clicks on a button, you could use:
https://api.jquery.com/click/
I don't know how new you are to .js, but you should look up to codecademy tutorials on JavaScript and jQuery.
.click() demo:
https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_event_click
If I have the following code:
function Something() {
this.randomElement = $("#element");
}
Something.prototype = {
functionOne: function() {
console.log("hello");
},
functionTwo: function() {
this.randomElement.click(function(e) {
this.functionOne(); //this becomes pointed at randomElement
});
}
}
How can I write this in a clean way where I wouldn't have to use Something.prototype.functionOne() to replace this.functionOne() inside of functionTwo? Since the click event changes the value of this?
Because this is bound to the item that is clicked. You need to use bind
this.randomElement.click( this.functionOne.bind(this) );
or jQuery's proxy.
this.randomElement.click( $.proxy(this.functionOne, this) );
i have written following jquery plugin. what i am trying to do is, when the user click on a link make the relevant div display: block base on the data attribute. But this plugin does not work. i have bn trying to figure this out for last two days. But i failed.
My HTML
<div class="container1">
asd
<div class="window1" data-window="a">
asd
</div>
</div>
<hr>
<div class="container2">
asdf1
asdf2
asdf3
<div class="window2" data-window="b">
asdf1
</div>
<div class="window2" data-window="c">
asdf2
</div>
<div class="window2" data-window="d">
asdf3
</div>
</div>
<script src="jquery-1.11.0.min.js"></script>
<script src="script.js"></script>
<script>
$('.container1').myPlugin({
link: $('.link1'),
container : $('.window1')
});
$('.container2').myPlugin({
link: $('.link2'),
container : $('.window2')
});
</script>
plugin
(function ($, window, document, undefind) {
MyPlugin = {
init : function (options, element) {
$.fn.myPlugin.config = $.extend({}, $.fn.myPlugin.config, options);
var link = $.fn.myPlugin.config.link;
link.on('click', this.secondFunc);
},
secondFunc : function () {
var dataLinkId = $(this).data('link'),
container = $($.fn.myPlugin.config).filter('[data-section="' + dataLinkId + '"]');
container.show();
}
};
$.fn.myPlugin = function(options) {
return this.each(function () {
var rezG = Object.create(MyPlugin);
rezG.init(options, this);
});
};
$.fn.myPlugin.config = {
link: $('.link'),
container : $('.container')
};
})(jQuery, window, document);
CSS
.window1, .window2 {
display: none;
}
DEMO
You need to use var to make sure your variables are all local and not global.
var MyPlugin = {
// ...
};
Also, in the init function, you are doing this:
$.fn.myPlugin.config = $.extend({}, $.fn.myPlugin.config, options);
This is overwriting $.fn.myPlugin.config which is the default options. This means that all elements that call myPlugin() will use the same config. You need to set the config on just the one instance.
this.config = $.extend({}, $.fn.myPlugin.config, options);
Your secondFunc doesn't have a reference to the object (rezG) instance, so it cannot access the config. You need to pass that to secondFunc(). One way is to use a closure to capture the instance.
secondFunc: function (rezG) {
return function(){
var dataLinkId = $(this).data('link'),
container = $(rezG.config.container).filter(function(){
return $(this).data('window') == dataLinkId;
});
container.show();
};
}
Then you bind it like so:
link.on('click', this.secondFunc(this));
Note that in secondFunc, you need to use config.container(not just config which is the object), and also your attribute is data-window, not data-section.
Updated demo: http://jsfiddle.net/K82gg/7/
Your plugin could be as simple as
(function ($, window, document, undefind) {
$.fn.myPlugin = function(options) {
// When $(stuff).myPlugin(...) is called
// this keyword inside of myPlugin function is referencing a set
// of elements plugin was called upon
// e.g. for call like $('.container1').myPlugin();
// this keyword will reference all elements selected by
// $('.container1') not jquery wrapped,
// in general it can be a any number.
return this.each(function pluginImplementation () {
// Here we iterate over the set, and for each element in the set
// we do some pretty standard click
var container = $(this);
// I use 'click.myPlugin' event instead just 'click' ale to later on
// do $(..).off('click.myPlugin') to remove only handlers that were
// attached by plugin (a good practice)
container.on('click.myPlugin', options.linkSelector, function(){
var dataLinkId = $(this).data('link');
container.find('[data-window="' + dataLinkId + '"]').toggle();
})
});
};
})(jQuery, window, document);
See the jsfiddle
However the code above may have a problem luginImplementation () function is created on each iteration and if the body of that function would be something more complicated it would be a mess. That is why it's better to create pluginImplementation () outside.
(function ($, window, document, undefind) {
// Notice that pluginImplementation () now accepts parameters
// They make it possible for pluginImplementation to know which
// elements it's working with
function pluginImplementation (container, options) {
container.on('click.myPlugin', options.linkSelector, function(){
var dataLinkId = $(this).data('link');
container.find('[data-window="' + dataLinkId + '"]').toggle();
})
}
$.fn.myPlugin = function(options) {
return this.each(function () {
pluginImplementation($(this), options);
});
};
})(jQuery, window, document);
The demo
That separation may be not good enough. You may want your plugin to be more OOP and what not. So you can go all OOPy like that:
(function ($, window, document, undefind) {
// For that purpose we create a class
// That describes behavior that our plugin provides
//
function MyPlugin(container, options) {
this.container = container;
this.options = options;
// To the topic of maintainability
// This could be parametrised as an option at plugin instantiation
this.eventName = 'click.myPlugin';
}
MyPlugin.prototype.attachClickHandlers = function() {
var self = this;
// This gets a little messy with all the thises vs selfs and a
// noname function wrapping the handler.
// The point is to preserve this keyword reference
// inside of clickHandler method.
// If I would have just self.clickHandler as a handler there
// this keyword inside of self.clickHandler would reference to
// whatever $(...).on binds handlers to i.e. triggering element.
// I need this keyword inside of self.clickHandler to point to
// "current" instance of MyPlugin, that's why I have wrapping
// function. It just lets me call clickHandler in the right context.
// clickHandler method also needs to know what link is being clicked
// so we pass that in as parameter.
self.container.on(self.eventName,
self.options.linkSelector,
function() {
self.clickHandler($(this));
})
}
MyPlugin.prototype.clickHandler = function(clickedLink) {
var dataLinkId = clickedLink.data('link');
this.container.find('[data-window="' + dataLinkId + '"]').toggle();
}
$.fn.myPlugin = function(options) {
return this.each(function () {
var pluginInstance = new MyPlugin($(this), options);
pluginInstance.attachClickHandlers();
});
};
})(jQuery, window, document);
In this implementation MyPlugin is a class (in javascript sense of the word class) which enables you to tackle each specific point in the way it behaves. and introduce all sorts of OOP features.
The demo
I want to call a function with a namespace based on its name.
Perhaps some background: What I want is, dynamically bind pages via $.mobile.loadPage(inStrUrl, { showLoadMsg: false }); and then, based on the current page, invoke a function within a loaded page. For example: each page has a showFilter function, the Event is attached to a main.html - page which should call the matching function in the current page.
I also tried some solutions, with jquery too, but nothing works for me.
This is my function code:
function namespace() { }
namespace.showFilter = function () {
alert("Test");
}
And want to "invoke" or "call" it via its name.
This is what i tried at least.
$(document).ready(function() {
var fn = window["namespace.showFilter"];
fn();
});
I get error TypeError: fn is not a function
Here is a fiddle http://jsfiddle.net/xBCes/1/
You can call it in the following way:
$(document).ready(function() {
window["namespace"]["showFilter"]();
});
or
$(document).ready(function() {
window["namespace"].showFilter();
});
or
$(document).ready(function() {
window.namespace.showFilter();
});
I found that I had to manually set it to window.
window.namespace = function() { }
window.namespace.showFilter = function () {
alert("Test");
};
$(document).ready(function() {
var fn = window["namespace"]["showFilter"];
fn();
});
http://jsfiddle.net/xBCes/4/
Like this:
$(function() {
window.namespace.showFilter();
});
P.S. I shortened the $(document).ready(...)
function namespace() {}
namespace.showFilter = function () {
alert("Test");
}
$(document).ready(function() {
var fn = namespace.showFilter();
fn();
});
http://jsfiddle.net/xBCes/3/
I have a widget defined like so:
$.widget("ui.mywidget", {
_init: function() {
this.element.bind("keyup", function(event) {
alert(this.options);
alert(this.options.timeout);
});
}
});
And trying to call it like so:
$("input.mywidget").mywidget({timeout: 5});
I also redefined the bind method using the this.element.keyup(function(event) { ... }) style: no difference.
But, in the keyup bind, this.options (and referencing it just as options) both yield undefined. I thought the UI widget framework allowed this type of abstraction; am I doing something wrong?
When inside bind(), this changes to refer to the object that the event is raised on. Try:
$.widget("ui.mywidget", {
_init: function(options) {
var opts = this.options;
this.element.bind("keyup", function(event) {
alert(opts);
alert(opts.timeout);
});
}
});
What #Dave said is right. You can also set "this" to a variable rather than using options as an argument to the init function. Here is how I see it implemented often:
$.widget("ui.mywidget", {
options: {
timeout: 100
},
_init: function() {
var self = this;
self.element.bind("keyup", function(event) {
alert(self.options);
alert(self.options.timeout);
});
}
});
Why stop there? Check out $.proxy and write better code
$.widget("ui.mywidget", {
_create: function() {
//Under this syntax, _omgAlertsRule must be a method of 'this'
this.element.bind("keyup", $.proxy( this, '_omgAlertsRule' ) );
},
_omgAlertsRule: function( event ){
alert(this.options);
alert(this.options.timeout);
}
});