I think that I currently write pretty good javascript, but am trying to move to a more Object Oriented approach. I am just starting with this so forgive my noob-ness. I was moving some of my functions over to objects and ran into this issue. Previously, I had an accordion function that worked like this:
jQuery(document).ready(function ($){
var accordionTrigger = $('.accordion-title');
function toggleAccordion() {
// Set a variable for the accordion content
var accordionContent = $('.accordion-container .accordion-content p');
// Slide up any open content
accordionContent.slideUp();
// Remove any active classes
accordionTrigger.removeClass("active");
// If the sibling content is hidden
if(!$(this).siblings().is(":visible")) {
// slide it down
$(this).siblings().slideDown();
// add a class to the title so we can style the active state and change the svg
$(this).addClass("active");
}
}
accordionTrigger.on("click", toggleAccordion);
});
I have moved this over to an Object in my new set-up like this:
Accordion = {
accordionContent: '.accordion-container .accordion-content p',
accordionTrigger: '.accordion-title',
init: function() {
jQuery(this.accordionTrigger).click(this.toggleAccordion.bind(this));
},
toggleAccordion: function() {
// Slide up any open content
jQuery(this.accordionContent).slideUp();
// Remove any active classes
jQuery(this.accordionTrigger).removeClass("active");
// If the sibling content is hidden
if(!jQuery(this.accordionTrigger).siblings().is(":visible")) {
// slide it down
jQuery(this.accordionTrigger).siblings().slideDown();
// add a class to the title so we can style the active state and change the svg
jQuery(this.accordionTrigger).addClass("active");
}
}
}
jQuery(document).ready(function ($){
Accordion.init();
});
The issue that I'm running into is with the way that 'this' works in Object Oriented Javascript. In the original setup, I was able to use 'this' to reference the accordion content that was clicked. I do not have access to that with the Object Oriented method. Can someone please help me out with?
You can use event.target to refer to the element that triggered the event, or event.currentTarget to refer to the element the handler was bound to, which is equivalent to using this.
toggleAccordion: function(event) {
// Slide up any open content
jQuery(this.accordionContent).slideUp();
// Remove any active classes
jQuery(this.accordionTrigger).removeClass("active");
// If the sibling content is hidden
if(!jQuery(event.currentTarget).siblings().is(":visible")) {
// slide it down
jQuery(event.currentTarget).siblings().slideDown();
// add a class to the title so we can style the active state and change the svg
jQuery(event.currentTarget).addClass("active");
}
}
Learn more about event handling with jQuery.
The problem is not how this works on OOP JavaScript because this is always referencing the caller object.
You can learn more about this with You Don't Know JS: this & Object Prototypes.
You can indeed use event.target and event.currentTarget as #Felix says, but eventually you will get into trouble if you don't understand how to use this properly or at least understand how it works.
An alternative approach to #FelixKling solution, removing .bind() , using event.data to have access to both jQuery(this) and Accordion object within accordionTrigger handler
var Accordion = {
accordionContent: '.accordion-container .accordion-content p',
accordionTrigger: 'div',
init: function() {
// set `event.data` to `this`:`Accordion` at first parameter to `.click()`
jQuery(this.accordionTrigger).click(this, this.toggleAccordion);
},
toggleAccordion: function(e) {
// Slide up any open content
// jQuery(e.data.accordionContent).slideUp();
// Remove any active classes
// jQuery(e.data.accordionTrigger).removeClass("active");
// If the sibling content is hidden
// `this`:`div`
if (jQuery(this).is(e.data.accordionTrigger)) {
console.log(this, e.data, $(e.data.accordionTrigger))
}
}
}
Accordion.init()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div>
click
</div>
Within toggleAccordion(), you can still use $(this) to get the accordion title element that has been clicked on. Hopefully then you can get the appropriate accordion content.
First bind it like how PHPglue mentioned:
jQuery(this.accordionTrigger).click(this.toggleAccordion);
Then you can do $(this) to get the accordionTitle:
toggleAccordion: function() {
var $accordionTitle = $(this);
}
Basically when you bind it like that and the click is triggered, the scope changes and "this" is no longer "Accordion".
If you need to access accordionContent and accordionTrigger, you can either pass it into the function toggleAccordion or use Accordion.accordionContent and Accordion.accordionTrigger.
Additional source: http://api.jquery.com/click/#click-eventData-handler
Related
I am trying to get a script running that adds some events to a class.
I want these events only to be added to one specific DIV among this class. I am using pep.js:
$( ".drag" ).pep({
start: function() {
$(".drag").addClass('color');
$('.drag').next(".text").fadeIn("slow");
$(".text-video").fadeOut("fast");
},
stop: function() {
$('.drag').removeClass('color');
$('.drag').next(".text").fadeOut("slow");
}
});
This works, but it triggers all .drag items at onceā¦ And I want only the one I am dragging to have added all the events.
I tried to write it with:
$(".drag", this).addClass('color');
or:
$(this).addClass('color');
or:
$(this.element).addClass('color');
But it all didn't work.
Does someone have an idea?
UPDATE:
I made a JSFiddle that hopefully explains my problem.
https://jsfiddle.net/ke6d5r1h/
As you see, the .color class for example is not only added to the DIV that is dragged, but also to the other. This is what I want to change.
Using console.log(arguments) in the event handlers, I was able to determine that the handlers are passed two arguments: the event object, and an object containing context. The context has a property $el, which is a jQuery object for the event target.
start: function(e,a) {
a.$el.addClass('color');
a.$el.next(".text").fadeIn("slow");
$(".text-video").fadeOut("fast");
},
https://jsfiddle.net/85tffqhL/
e.target would also give you a reference to the element.
So I'm making a small quiz app with object oriented JS using Object.create cloning method. I have an ol, and a function called showVals() that populates it with lis. That seems to be working fine. What I'm having trouble with is: my li click function to give the attr of ".selected' class seems to work intitially, but after I click to proceed and qn2.showVals() is called it is no longer giving the lis a class of selected when clicked.
The data for qn2 is there. Everything looks normal, except for the click function no longer working (giving the lis the class).
$(document).ready(function(){
qn1.showVals();
qn1.setAns(1); // calling question1 answer for now
$('li').click(function(){
$('li').removeAttr("class");
$(this).attr({"class": "selected"});
});
$('.proceed').click(function(e){
e.preventDefault();
if ($('.selected').html() == qn1.ctAns) {
if (confirm("You are correct")){
qn2.showVals();
qn2.setAns(3);
};
};
});
});
var qn1 = {
title:"The Mouth of Sauron",
qn: "How did 'The mouth of Sauron' meet his demise?",
img: "images/mouth-sauron.gif",
ans: ["Shot in the back", "Beheaded", "Drowned in a swamp", "Sacrificed"],
setAns: function(x) {
this.ctAns = this.ans[x]; //setting correct answer
},
showVals: function(){
$('#slide-title').text(this.title);
$('.question-box > p').text(this.qn);
$('#obj-img').attr("src", this.img);
$('ol').html('<li>'+this.ans[0]+'</li>'+'<li>'+this.ans[1]+'</li>'+
'<li>'+this.ans[2]+'</li>'+'<li>'+this.ans[3]+'</li>')
}
}
var qn2 = Object.create(qn1);
qn2.title = "Golemn";
qn2.qn = "What this dude's name?";
qn2.ans= ["Golemn", "Gimli", "Goober", "Poop"]
qn2.img = "images/golemn.gif";
This is likely because your li elements are dynamically added.
You should try using jQuery on(), which allows you to bind an event handler to the parent element which must already exists in your DOM, and then you can specify the child/descendant selector that will call the event handler. This child element may still be non-existent at the time you do the event binding. In such a case, you call on() like:
$('ol').on('click', 'li', function () {...});
where ol already exists.
Alternatively, you could always bind your click handler to your dynamically generated li elements after you have added them to your DOM. Although I think that is more processor-time consuming as I assume you have to do this for all quiz questions you ask your user.
I understand you can use .on() to attach a single click event to an element and then specify which child elements receive the click. So, for example:
$(this.el).on("click", "span", function () {
alert("Bloop!");
});
I need to be a bit more specific and target selectors with a particular attribute, like this:
$(this.el).on("click", "span[data-placeholder]", function () {
alert("Bloop!");
});
That doesn't seem to work, though. As soon as I add the attribute it stops working. No errors, just doesn't seem to find the elements.
Is that the expected behavior? Is there a way around it?
CLARITY
$(this.el) is just a div that contains a number of elements, some of which are <span data-placeholder="First Name"></span> tags. There could be dozens of those <span> tags and I didn't want that many event listeners, so I thought I'd use .on() to add the click to the parent container.
Here's JSFiddle showing your example working, with both existing <span>s and with newly created ones.
Just to be clear, this will work with your event delegation:
var span = $('<span>Test</span>');
span.attr('data-placeholder', 'test'); // declare as an attribute
$(this.el).append(span);
span.click();
This will not:
var span = $('<span>Test</span>');
span.data('placeholder', 'test'); // declare with .data()
$(this.el).append(span);
span.click();
jQuery's .data() method will read properties from data attributes if declared, but does not store them as attributes on the element when adding data.
Here's another JSFiddle.
try
$("span[data-placeholder]", this.el).on("click", function () {
alert("Bloop!");
});
You can choose to filter your spans
$('span', this.el).filter(function() {
return $(this).hasAttr('data-placeholder');
}).on('click', function() {
//This is for all the spans having data-placeholder
//...
});
Or if the placeholder is set via data api:
$(this.el).filter(function() {
return $(this).data('placeholder') != 'undefined';
}).on('click', function() {
//This is for all the spans having data-placeholder
//...
});
This functions above select those elements specifically, if event delegation on the OP is needed, then you can do the following:
$('span', this.el).on('click', 'span', function() {
if($(this).data('placeholder') != 'undefined') {
alert('bloop');
}
});
add a id to your span and pin point it using # tag
I'm working in a card game system that the player can select the card by clicking on it and then select the place to put it on. My problem is that when the player click on the target place, nothing happens.
This is my try: http://jsfiddle.net/5qMHz/
And this is my code:
function target() {
$(".target").on("click", function() {
$("#"+x).appendTo(this);
console.log(x);
});
};
What's wrong?
Try binding with document, since you change the class during document ready and there was no element with the class target initially
$(document).on("click",".target", function() {
$("#" + x).appendTo(this);
console.log(x);
}
WORKING FIDDLE
Firstly, your practice of putting function references in to jQuery objects is rather odd. The problem however is that because the .target class is applied after DOM load you need to use a delegate selector. Try this:
var $card
$(".card").on("click", function () {
$card = $(this);
if ($(".myslot").length) {
if ($(".myslot").is(':empty')) {
$(".myslot:empty").addClass("target");
} else {
alert('No empty slots');
}
}
});
$('.field').on('click', ".target", function () {
$card.appendTo(this);
$card = $();
});
Example fiddle
At the moment you are trying to bind the event handler, the elements don't have a class target yet. From the documentation:
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().
(Technically the elements exist, but they are not (yet) addressable by the class target)
You have three options to solve this:
Add the class to your HTML markup.
Bind the handler after you added the class to the elements.
Use event delegation.
The first two don't really fit to your use case, since your are adding the class target in response to an other event and the number of elements with the class target changes over time. This is a good use case for event delegation though:
$('.field').on('click', '.target', function() {
// ...
});
I am having some trouble removing an element created by jQuery in the same function. Here is what is happening:
I have created a jQuery function (toggleClick) that opens up a side panel and then creates an overlay element that is applied to the rest of the page (excluding the panel).
What is supposed to happen is after the user clicks on the overlay it will close the panel and also get rid of the overlay element. Am I doing something wrong here?
jQuery Code:
jQuery(document).ready(function(){
/* DEFINE TOGGLECLICK */
$.fn.toggleClick=function(){
var functions=arguments
return this.click(function(){
var iteration=$(this).data('iteration')||0
functions[iteration].apply(this,arguments)
iteration= (iteration+1) %functions.length
jQuery(this).data('iteration',iteration)
})
}
/* DEFINE APANEL ID */
var aPanel = "#aPanel";
/* DEFINE APANEL FUNCTIONALITY */
jQuery('#apanel-fire,#aPanelOverlay').toggleClick(function() {
jQuery("body").addClass("active");
jQuery(aPanel).addClass("active");
jQuery("<div id='aPanelOverlay'></div>").appendTo(".page");
}, function() {
jQuery("body").removeClass("active");
jQuery(aPanel).removeClass("active");
jQuery("#aPanelOverlay").remove();
});
});
The /* DEFINE APANEL FUNCTIONALITY */ is where I am having the issue. Everything else above is defining the toggleClick function and the aPanel variable ID.
You can see a live version of the issue I am having below by clicking on the Menu button located in the top left of the screen.
Live Version:
http://iamaaron.com/alpha/
Thank you so much!
First of all when you call jQuery('#apanel-fire,#aPanelOverlay').toggleClick the overlay does not exist, as it is attached later. So it has no click handler and does not react to any click.
Second (which does not really matter anymore ;) return this.click(function(){ would attach a click handler to each element. jQuery(this).data('iteration',iteration) would store the iteration on #apanel-fire on the first click. When you would now click on the overlay, its iteration would still be 0. So the first function would be called again.
Update: a very simple solution
function showOverlay() {
jQuery("body").addClass("active");
jQuery(aPanel).addClass("active");
jQuery("<div id='aPanelOverlay'></div>").appendTo(".page").click(hideOverlay);
}
function hideOverlay() {
jQuery("body").removeClass("active");
jQuery(aPanel).removeClass("active");
jQuery("#aPanelOverlay").remove();
}
jQuery('#apanel-fire').click(showOverlay);