Object Literal Pattern: can't access a data attribute when binding events - javascript

Background:
I am trying to dry up my code using the object literal pattern.
The object:
Here is my object:
(function(){
var bookingForm = {
init: function(){
this.cacheDOM();
this.bindEvents();
},
cacheDOM: function(){
this.$nextStep = $('.btn-next-step');
},
bindEvents: function(){
this.$nextStep.on('click', this.nextStep.bind(this));
},
nextStep: function(value){
alert($(value).attr('data'));
},
prevStep: function(){
},
}
bookingForm.init();
})();
And my button which is supposed to trigger the nextStep function
<button class="btn btn-success btn-next-step" data="2">
Next Step
<i class="fa fa-arrow-right"></i>
</button>
So I am trying to access the data attribute of the button, so when I click it I should get an alert of '2'.
I have tried a number of ways... this current code just alerts 'undefined'.
Question
How do I pass my data attribute through to the bound function?

The argument to an event listener is the event, not the element. To get the element, use event.target
nextStep: function(event){
alert($(event.target).attr('data'));
jQuery normally binds this to the target element, but you've overridden that with this.nextStep.bind(this), so this contains the bookingForm object.

I think your problem is in bindEvents. You are binding the same object to function, but the function is hoping to receive an element.
I propose the next change
(function(){
var bookingForm = {
init: function(){
this.cacheDOM();
this.bindEvents();
},
cacheDOM: function(){
this.$nextStep = $('.btn-next-step');
},
bindEvents: function(){
var that = this;
this.$nextStep.on('click', function (e) {
that.nextStep(this)); // here "this" is the element
// or you can do that.nextStep(e.target);
});
},
nextStep: function(value){
alert($(value).attr('data'));
},
prevStep: function(){
},
}
bookingForm.init();
})();

Related

Delete function gets called before button exists

I've had this issue before, essentially I'd like to keep everything my app separate i.e caching the dom, binding events, no html in javascript etc.
I have an issue where in my bindevents method I have a click on the delete button, however the delete button only exists once a to do has been added.
I'm there getting an error:
Uncaught TypeError: Cannot read property 'addEventListener' of null
Because I guess I'm searching the dom for an element that doesn't exist, how do I keep the structure as it is but only search the DOM for the delete button once an item has been added?
JS
(function() {
var toDo = {
data: [],
cacheDom: function() {
this.toDoApp = document.getElementById('to-do-app');
this.toDoTemplate = document.getElementById('to-do-template');
this.addToDo = document.getElementById('add-to-do');
this.addToDoValue = document.getElementById('add-to-do-value');
this.deleteToDo = document.querySelector('.to-do-delete');
},
load: function() {
this.toDoTemplate = Handlebars.compile(this.toDoTemplate.innerHTML);
},
render: function() {
this.toDoApp.innerHTML = this.toDoTemplate(this.data);
},
bindEvents: function() {
this.addToDo.addEventListener("click", this.add.bind(this));
this.deleteToDo.addEventListener("click", this.delete.bind(this));
},
add: function(e) {
var toDoValue = this.addToDoValue.value;
if(toDoValue) {
var toDoObj = {
value: toDoValue,
id: Date.now()
}
this.data.push(toDoObj);
}
this.render();
},
delete: function() {
console.log("delete!");
},
init: function() {
this.cacheDom();
this.bindEvents();
this.load();
this.render();
}
}
toDo.init();
})();
You should be bind()ing your event handler functions to their scope when they're defined, not when they're setup as listeners.
bindEvents: function() {
this.addToDo.addEventListener("click", this.add);
this.deleteToDo.addEventListener("click", this.delete);
},
add: function() {
// ...
}.bind(this),
delete: function() {
// ...
}.bind(this),
At first, you may just try to get Elements after onload:
window.addEventListener("DOMContentLoaded",toDo.init.bind(toDo));
And you could listen at window for all clicks, then filter them:
window.addEventListener("click",function(evt){
var el = evt.target;
do {
if(el.classList.contains("someclass")){
somefunc.call(el);
}
} while ( el = el.parentElement);
});
I would remove the deletetodo caching and event assignment from the init procedure (which is always going to be null anyway) and listen for the event when the button is added instead.

How bind a event and use apply/call to change the scope

+function ($) {
'use strict';
var popup = {
init: function(element) {
this._active = 'products__popup--active';
this._product = $('.products__popup');
this._element = $('[data-popup-to]');
this._TIME = 500;
popup.attachEvt();
},
attachEvt: function() {
var that = this;
that._element.bind('click', popup.handlerEvt.call(that));
},
handlerEvt: function() {
console.log(this);
console.log('test');
}
};
$(window).on('load', function() {
popup.init();
});
}(jQuery);
I have this script, and is not working yet, I cant show you a working example because it is not ready, I'm organizing the code first.
And there is a problem with the attachEvt function, inside it I want to call another function of my object, this function will bind a click in the that._element, but I want pass to the handlerEvt the scope of this (the clicked element) and the that (the object), but this is not working:
that._element.bind('click', popup.handlerEvt.call(that));
I'm just passing the that scope and when the script loads, the element will be clicked without click, I want avoid this.. this is possible?
UPDATE:
Resuming:
I want be able to use the scope of the object (that) and the scope of the clicked element (this) inside the handlerEvt function, but without make the event click when the script loads.. :B
Try utilizing .bind() , with this set to that._element , that passed as parameter to handlerEvent . Note order of parameters at handlerEvent: obj: that first , evt event object second
+function ($) {
'use strict';
var popup = {
init: function(element) {
this._active = 'products__popup--active';
this._product = $('.products__popup');
this._element = $('[data-popup-to]');
this._TIME = 500;
popup.attachEvt();
},
attachEvt: function() {
var that = this;
that._element.bind('click', popup.handlerEvt.bind(that._element, that));
},
handlerEvt: function(obj, evt) {
console.log(evt, obj, this);
console.log('test');
}
};
$(window).on('load', function() {
popup.init();
});
}(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div data-popup-to="true">click</div>

Bind event relative to the class that created it?

Let's say I have a class laid out like so:
function slider() {
this.init = function(options, title, content) {
var self = this;
$('body').append('<button type="button">' + title + '</button>')
},
this.create = function(title, content, options) {
var self = this;
self.init(options, title, content);
},
this.closeSlider = function(elem) {
var self = this;
self.assignPositions();
},
this.assignPositions = function() {
alert('assign positions called from button?');
}
}
To create the 'slider', I use this:
var slider = new slider();
Then I call the create function:
slider.create('title', 'content');
My question is, how can I bind the closeSlider function to the button, but it's only linked to the instance that created it? If that makes sense?
Basically, I'll have many buttons with the 'closeSlider' function, and I don't want them all to fire at once, I only want it linked to the instance that created it.
This is also a VERY trimmed down version of my class, just trying to figure this little problem out :)
Cheers
Use bind on the callback function when setting the event callback
this.init = function(options, title, content) {
var btn = $('<button type="button">' + title + '</button>');
btn.click(this.closeSlider.bind(this));
$('body').append(btn)
},
This will make it so when the closeSlider function is called it retains the context of the slider instance that made it. But note this will no longer be the context of the html element that triggered the event. So you would need to get the target from event.target
There might be another way of doing this without losing the context of the html element i will have to look and re-edit.
Edit
Using event.target
this.init = function(options, title, content) {
var btn = $('<button type="button">' + title + '</button>');
btn.click(this.closeSlider.bind(this));
$('body').append(btn)
},
this.closeSlider:function(event){
//`this` will refer to slider instance
//and event.target will be the button dom object
var element = event.target;
});
Passing the button object as an argument in bind
this.init = function(options, title, content) {
var btn = $('<button type="button">' + title + '</button>');
btn.click(this.closeSlider.bind(this,btn));
$('body').append(btn)
},
this.closeSlider:function(btn,event){
//`this` will refer to slider instance
//btn will refer to the jQuery wrapped button dom object
//event.target will still refer to the button dom object
});
Instead of simply appending a string to the body, create a live element with document.createElement and attach the onclick event before releasing it into the wild.
Here's an example:
function slider() {
this.init = function(options, title, content) {
var myButton = document.createElement("button");
myButton.setAttribute("type", "button");
myButton.innerHTML = title;
myButton.onclick = this.closeSlider;
$('body').append(myButton);
},
//Other object definitions
}
First of all, you should add methods onto the prototype rather than on each instance (saves memory).
Secondly, you can use jQuery's .proxy() to create an anonymous function that will "hardwire" this to a particular value when it calls your method.
// empty constructor
function slider()
{
}
// define prototype
$.extend(slider.prototype, {
init: function(options, title, content) {
$('<button>', { text: title })
.on('click', $.proxy(this, 'closeSlider'))
.appendTo('body');
},
create: function(title, content, options) {
this.init(options, title, content);
},
closeSlider: function(event) {
// event.target is the HTML element
this.assignPositions(event.target);
},
assignPositions: function(elem) {
alert('assign positions called from button?');
}
});

Cloning $(this) in a javascript object?

I'm learning how to use objects to help organize my code and give it some structure but I've run into a problem. I don't understand how to set the $(this) from inside of one function to the $(this) of another function.
I'm researching call and apply but I can't seem to grasp how it works in this scenario.
cloneCard and clickCard is where I'm having the problem. I want to pass the $(this) that is referenced when I click the card to the cloneCard function.
Here is my code so far (updated to reflect the answer):
var Modal = {
init: function(config) {
this.config = config;
this.clickCard();
this.removeModal();
this.clickOutside();
this.createClose();
},
clickCard: function() {
$this = this;
this.config.boardOutput.on('click', '.card', function(event) {
$this.showOverlay();
$this.cloneCard.call($(this));
$this.createClose();
});
},
cloneCard: function() {
this.clone()
.replaceWith($('<div/>').html(this.html()))
.removeClass('card')
.addClass('modal')
.css("margin-top", $(window).scrollTop())
.prependTo('body');
},
showOverlay: function() {
this.config.overlay.show();
},
removeModal: function() {
$('.modal').remove();
$('.overlay').hide();
},
clickOutside: function() {
this.config.overlay.on('click', this.removeModal);
},
createClose: function() {
$('<span class="close">X</span>')
.prependTo('.modal')
.on('click', this.removeModal);
}
};
Modal.init({
boardOutput: $('#board-output'),
overlay: $('.overlay')
});
For what you need, calling self.cloneCard.call($(this)); instead of self.cloneCard($(this));
should work. What you're doing is, calling cloneCard passing it the element in which the the clickCard event occured.
If this doesn't work, i think we'll need more information to sovle your problem.

Is there a way to pass context to bind in jQuery?

I'm inside a javascript object (vr roxx :) ), but every time I do an event bind with jQuery I have to include the main object instance's context through the data parameter in order to work with it. Isn't there an easy/neat way to do this in jQuery?
var oink =
{
pig: null,
options:
{
showPigMom: 0
},
init: function(pigObj)
{
//Show the pigmom
$(this.doc).bind('keyup click', {o: this}, function(e)
{
var o = e.data.o;
if (o.options.showpath)
o.doWhatever();
});
...
I use the $.proxy() function
init: function(pigObj)
{
//Show the pigmom
$(this.doc).bind('keyup click', $.proxy(function(e) {
if (this.options.showpath)
this.doWhatever();
$(e.currentTarget).text(); // use this to access the clicked element
}, this));
}
init: function() {
var self = this;
$(this.doc).bind('keyup click', function() {
if (self.options.showpath) self.doWhatever();
});
}
init: function() {
$(this.doc).bind('keyup click', function() {
if (this.options.showpath) this.doWhatever();
$(e.currentTarget).text(); // use this to access the clicked element
}.bind(this))
}

Categories