I have a function which creates a tooltip for specific objects. Currently, I am running a tooltip function after ajax insertions to create and append the new tooltip objects. I am curious if there is a way to use .on() to auto-run the tooltip function on insertion, rather than manually running it.
For instance:
$('[title]').on('inserted', function(){
tooltip(this);
});
I did some reading and it looks like custom triggers might be the way to go, but I'd love if it something like this existed :)
Here's the pseudo code as per request.
$(document).ready(function() {
$('body').on('added','*',function() {
console.log($(this),'has been added');
});
$('body').append('<div>This is the first div</div>');
});
(function($) {
fncs = {
append:$.fn.append,
appendTo:$.fn.appendTo
// etc.
}
// we're assigning the original functions in this
// object to be executed (applied) later
$.fn.append = function() {
fncs.append.apply(this,arguments);
$(this).children().last().trigger('added');
return $(this);
}
$.fn.appendTo = function() {
fncs.appendTo.apply(this,arguments);
return $(this);
// no need to trigger because this function calls the one
// above for some reason, and it's taking care of the
// triggering the right element(s I think)
}
})(jQuery);
This is not the response you're looking for, but I would not attach tooltips directly on elements. Instead I would use a class for the ones I want the tooltip to show on mouseover and use the .on() event handler in the following way:
$('body').on('mouseover','.tooltip',function() {
// show tooltip
console.log($(this).data('tooltip'));
return false;
}).on('mouseout','.tooltip',function() {
// hide tooltip
return false;
});
So that whatever you add to the body (not necessarily as a direct child) will trigger this event handler.
I would probably just create an additional function to assign the tooltip data to each element along with the class.
$.fn.extend({
tooltip:function(text) {
text = text || '';
return $(this).each(function() {
$(this).data('tooltip',text).addClass('tooltip');
});
}
});
$('#someID').tooltip("Click me!");
$('button').tooltip("I'm a button");
Related
Edit: I think I got the solution! I want to try and fix this myself before I ask for further help = )
First script inhibits the second one from functioning as the click event from the first one overides the second one. Because the second one does not function it is impossible to open the drop down menu to select a list item to trigger the first scripts click.
What I tried was replacing all return false statements with event.stopPropagation(). Didnt work however. Tried re-ordering my scripts but that failed as well. I was thinking of making my second script target another parent div but that didnt work either.I also tried event.stopImmediatePropagation() and .bind methods.
Any idea?
First script that makes the drop down function. Contains click event.
function DropDown(el) {
this.f = el;
this.placeholder = this.f.children('span');
this.opts = this.f.find('ul.dropdown > li');
this.val = '';
this.index = -1;
this.initEvents();
}
DropDown.prototype = {
initEvents : function() {
var obj = this;
obj.f.on('click', function(event){
$(this).toggleClass('active');
return false;
});
obj.opts.on('click',function(){
var opt = $(this);
obj.val = opt.text();
obj.index = opt.index();
obj.placeholder.text(obj.val);
});
},
getValue : function() {
return this.val;
},
getIndex : function() {
return this.index;
}
}
$(function() {
var f = new DropDown( $('#f') );
$(document).click(function() {
// all dropdowns
$('.filter-buttons').removeClass('active');
});
});
Second script that does the filtering, also contains click event:
jQuery(document).ready(function(e) {
var t = $(".filter-container");
t.imagesLoaded(function() {
t.isotope({
itemSelector: "figure",
filter: "*",
resizable: false,
animationEngine: "jquery"
})
});
$(".filter-buttons a").click(function(evt) {
var n = $(this).parents(".filter-buttons");
n.find(".selected").removeClass("selected");
$(this).addClass("selected");
var r = $(this).attr("data-filter");
t.isotope({
filter: r
});
evt.preventDefault();
});
$(window).resize(function() {
var n = $(window).width();
t.isotope("reLayout")
}).trigger("resize")
});
html structure
<div id="f" class="filter-buttons" tabindex="1">
<span>Choose Genre</span>
<ul class="dropdown">
<li>All</li>
<li>Electronic</li>
<li>Popular</a></li>
</ul>
</div>
This doesn't really solve your problem but I was bored while drinking my coffee and felt like helping you write your dropdown plugin a little nicer
My comments below are inline with code. For uninterrupted code, see DropDown complete paste.
We start with your standard jQuery wrapper (function($){ ... })(jQuery)
(function($) {
// dropdown constructor
function DropDown($elem) {
First we'll make some private vars to store information. By using this.foo = ... we expose things (probably) unnecessarily. If you need access to these vars, you can always create functions to read them. This is much better encapsulation imo.
// private vars
var $placeholder = $elem.children("span");
var $opts = $elem.find("ul.dropdown > li")
var value = "";
var index = -1;
Now we'll define our event listeners and functions those event listeners might depend on. What's nice here is that these functions don't have to access everything via this.* or as you were writing obj.f.* etc.
// private functions
function onParentClick(event) {
$elem.toggleClass("active");
event.preventDefault();
}
function onChildClick(event) {
setValue($(this));
event.preventDefault();
}
function setValue($opt) {
value = $opt.text();
index = $opt.index();
$placeholder.text(value);
}
Here's some property descriptors to read the index and value
// properties for reading .index and .value
Object.defineProperty(this, "value", {
get: function() { return value; }
});
Object.defineProperty(this, "index", {
get: function() { return index; }
});
Lastly, let's track each instance of DropDown in an array so that the user doesn't have to define a special listener to deactivate each
// track each instance of
DropDown._instances.push(this);
}
This is the array we'll use to track instances
// store all instances in array
DropDown._instances = [];
This event listener deactivate each "registered" instance of DropDown
// deactivate all
DropDown.deactiveAll = function deactiveAll(event) {
$.each(DropDown._instances, function(idx, $elem) {
$elem.removeClass("active");
});
}
Here's the document listener defined right in the plugin! The user no longer has to set this up
// listener to deactiveAll dropdowns
$(document).click(DropDown.deactiveAll);
Might as well make it a jQuery plugin since everything in our DropDown constructor relies upon jQuery. This let's the user do var x = $("foo").dropdown();
// jQuery plugin
$.fn.dropdown = function dropdown() {
return new DropDown($(this));
};
Close the wrapper
})(jQuery);
Now here's how you use it
$(function() {
var x = $('#f').dropdown();
// get the value
f.value;
// get the index
f.index;
});
Anyway, yeah I know this doesn't really help you with your click listeners, but I hope this is still useful information to you. Off to the Post Office now!
I think you're going to need to simplify this to figure out what's going on. There's actually not enough information to see what elements the events are being attached to here.
For argument's sake, open the console and try the following:
$(document).on('click', function() { console.log('first'); return false; });
$(document).on('click', function() { console.log('second'); return false; });
Then click in the page. You'll see that both events are triggered. It might well be that your code is actually attaching the events to different elements (you don't say anywhere). If that's the case then you need to understand how event bubbling works in the DOM.
When you trigger an event, say a click on an element, that event will fire on that element, and then on it's parent, then grandparent etc all the way to the root node at the top.
You can change this behaviour by calling functions in the event itself. evt.stopPropagation tells the event to not bubble up to the ancestor nodes. evt.preventDefault tells the browser not to carry out the default behaviour for a node (eg, moving to the page specified in the href for an A tag).
In jQuery, return false from an event handler is a shortcut for, evt.preventDefault and evt.stopPropagation. So that will stop the event dead in its tracks.
I imagine you have something like:
<div event_two_on_here>
<a event_one_on_here>
</div>
If the thing that handles event_one_on_here calls stopPropagation then event_two_on_here will never even know it has happened. Calling stopPropagation explicitly, or implicitly (return false) will kill the event before it travels to the parent node/event handler.
UPDATE: In your case the issue is that the handler on .filter-buttons a is stopping the propagation (so #f doesn't get to run its handler).
$(".filter-buttons a").click(function(evt) {
// your code here...
// Don't do this - it stops the event from bubbling up to the #f div
// return false;
// instead, you'll probably just want to prevent the browser default
// behaviour so it doesn't jump to the top of the page ('url/#')
evt.preventDefault();
});
A variety of elements on my page have the content editable tag.
When they are clicked I do this:
$('[contenteditable]').on('click', this.edit);
p.edit = function(e) {
console.log(e.currentTarget);
e.currentTarget.on('keydown', function() {
alert("keydown...");
});
};
I get the current target ok, but when I try to add keydown to it, I get the err:
Uncaught TypeError: undefined is not a function
It's a native DOM element, you'll have to wrap it in jQuery
$(e.currentTarget).on('keydown', function() {
alert("keydown...");
});
e.currentTarget should equal this inside the event handler, which is more commonly used ?
It's a little hard to tell how this works, but I think I would do something like
$('[contenteditable]').on({
click : function() {
$(this).data('clicked', true);
},
keydown: function() {
if ($(this).data('clicked'))
alert("keydown...");
}
});
Demo
First issue is you are trying to use jQuery methods on a DOM element. Second issue is I do not think you want to bind what is clicked on, but the content editable element itself.
It also seems weird to be adding the event on click instead of a global listener. But this is the basic idea
$(this) //current content editable element
.off("keydown.cust") //remove any events that may have been added before
.on('keydown.cust', function(e) { //add new event listener [namespaced]
console.log("keydown"); //log it was pressed
});
Edited: I had a fail in code. It works fine now.
Getting your code, I improved to this one:
$(function(){
$('[contenteditable]').on('click', function(){
p.edit($(this));
});
});
var p = {
edit: function($e) {
console.log($e);
$e.on('keydown', function() {
console.log($(this));
alert("keydown...");
});
}
}
You can check it at jsFiddle
You need to wrap the e.currentTarget(which is a native DOM element) in jQuery since "on" event is a jQuery event:
$(e.currentTarget).on('keydown', function() {
alert("keydown...");
});
EDIT:
$('[contenteditable]').on('click', p.edit);
p.edit = function(e) {
$(e.currentTarget).on('keydown', function() {
alert("keydown...");
});
};
You're defining p.edit AFTER $('[contenteditable]').on('click', p.edit); resulting in an error since p.edit doesn't exist when declaring the on.
In case you don't know, you are defining p.edit as a function expression, meaning that you have to define it BEFORE calling it.
I am trying to implement a script to set different class name on a specific element…
Let's suppose the dom looks like this:
<body class='pre-existing-class-name'>
If I make
smartToogle('body', 'new-class');
// the dom should look like this
// <body class='pre-existing-class-name new-class'>
smartToogle('body', 'new-class-2');
// the dom should look like this
// <body class='pre-existing-class-name new-class-2'>
I did the following code but it does not work:
var smartToogle = function (element, newClassName) {
var oldClassName;
var $element = $(element);
$element.addClass(newClassName);
if (oldClassName !== newClassName) {
$element.removeClass(oldClassName);
}
oldClassName = newClassName;
};
Requirements:
1) I am using query
2) I would like to pass just one class name, the new one.
Solution:
The following code works but I do not like it because it uses global variable.
Any hint to fix it?
function myToggle(newClassName) {
if (window.oldClassName) {
$('body').toggleClass(window.oldClassName);
}
window.oldClassName = newClassName;
$('body').toggleClass(newClassName);
}
You can use data attribute for the element, that is accessible using
$(element).data(attrib_name)
Just a small change is required in your method
function myToggle(newClassName) {
if (window.oldClassName) {
$('body').toggleClass(window.oldClassName);
}
window.oldClassName = newClassName;
$('body').toggleClass(newClassName);
}
can be replaced with
function myToggle(element, newClassName) {
if ($(element).data('oldClassName')) {
$(element).toggleClass($(element).data('oldClassName'));
}
$(element).data('oldClassName', newClassName)
$(element).toggleClass(newClassName);
}
Hope this solves it for you.
Update:
There is one thing you need to understand.
If you want two different behaviors you don't need 2 different classes for the change in behavior.
One is enough, because you can change the behavior based on weither the class is on or off.
Let's say I want my element to have a red hover event in one way.
And want it to have a blue hover event the other way with CSS.
Then this is the way to go:
$('#toggle').click(function(){
$('.normal').each(function(){
$(this).toggleClass('active');
});
});
JSFiddle Demo
Here we use a button to toggle all the divs and change their CSS behavior, looks easy now right?
However if you need to toggle Javascript/jQuery events as well this won't do. In that case you will need to use 3 other methods to manage this; .on(), .off(), and .hasClass().
$('#toggle').click(function(){
$('.normal').each(function(){
if($(this).hasClass('active')){
$(this).off('click');
} else {
$(this).on('click', function(){
alert('You are clicking on an active div.');
});
}
$(this).toggleClass('active');
});
});
JSFiddle Demo 2
As you can see we have added an if statement. If the element has the .active class we turn .off() the .click(). And if there isn't an active class we turn the .click() .on(). Under the if statement we always toggle the .active class. So this doesn't have to be placed inside the if statement.
I hope this clears everything up for you, good luck!
Old Answer:
It is better to use .toggleClass() here.
Use a first class on the element for the default properties and a second like .active for example for the interaction.
Also, using a .on('click', function(){}) bind will make you able to add interaction that will be bound instantly once the element is toggled.
Here's a fiddle: http://jsfiddle.net/NCwmF/2/
I little jQuery plugin for that. Removes the current smart class (if any) and adds the new smart class. If called without parameter className the current smart class gets only removed.
$.fn.smartToggle = function (className) {
var dataId = 'smartToggle';
return this.each(function () {
var $el = $(this);
$el
.removeClass($el.data(dataId) || '')
.addClass(className)
.data(dataId, className);
});
};
​use it like every other jQuery method:
$('body').smartToggle('myClass');
NEW, SIMPLER ANSWER
Works similar to before, with 2 additions: 1.) works if there is no class initially and 2.) works if other functions change the elements class in between calls. I also changed the function name so it doesn't interfere with jQuerys native toggleClass.
$.fn.fancyToggleClass = function(new_class) {
return this.each(function() {
// get the last class this function added (if exists) or false (if not)
var $this = $(this),
toggled_class = $this.data('toggled-class') || false;
// if we dont have an original class, then set it based on current class
if (toggled_class) {
$this.removeClass(toggled_class);
}
// add new class and store as data,
// which we check for next time function is called
$this.addClass(new_class).data('toggled-class', new_class);
// alert the class, just as a check to make sure everything worked!
// remove this for production, or switch to console.log
alert('element class: ' + $this.attr('class'));
});
}
updated fiddle: http://jsfiddle.net/facultymatt/xSvFC/3/
OLD ANSWER
I would suggest storing the original class in the elements data attribute. Then, your function can check if this data is set, and if so clear the elements class adding the original class from the elements data and also the new class you passed in the function.
If data is not set, the function will store the current class as data the first time it runs.
Check out this fiddle for a working example with comments: http://jsfiddle.net/facultymatt/xSvFC/
here is the code. It's a jquery function so it can be called on any element (and is chainable too!)
$.fn.toggleClass = function(new_class) {
return this.each(function() {
// cache selector for this
$this = $(this);
// get original class (if exists) or false (if not)
var original_class = $this.data('original-class') || false;
// if we dont have an original class, then set it based on current class
if (!original_class) {
original_class = $this.attr('class');
$this.data('original-class', original_class);
// we do have an original class, so we know user is now trying to add class
// here we clear the class, add the original class, and add the new class
} else {
// assign the original class, and new class,
// and a space to keep the classes from becoming one
$this.attr('class', original_class + ' ' + new_class);
}
// alert the class, just as a check to make sure everything worked!
// remove this for production, or switch to console.log
alert('element class: ' + $this.attr('class'));
});
}
Hope this helps!
To avoid a global variable you can use data attribute as #ankur writes. Here is a working solution for your problem:
function myToggle(element, newClassName) {
if (!$(element).data('baseclassname')) {
$(element).data('baseclassname', $(element).attr('class'));
}
$(element)
.attr('class', $(element).data('baseclassname'))
.addClass(newClassName);
}
Does this do your job?
var smartToogle = function (element, preExistingClassName, newClassName) {
$(element)[0].className = preExistingClassName + ' ' + newClassName;
};
Just use hasClass. But you'll have to tell the function what both classes are:
function smartToggle(element, class1, class2) {
var $element = $(element);
if ($element.hasClass(class1)) {
$element.removeClass(class1);
$element.addClass(class2);
}
else {
$element.removeClass(class2);
$element.addClass(class1);
}
}
$(function(){
var smartToggle = function (element, newClassName) {
var elementClasses = element.attr('class');
element.addClass(newClassName);
// check if there is more than one class on the element
if(elementClasses .indexOf(' ') >= 0){
var oldClassNames = elementClasses.split(" ");
if (oldClassNames[oldClassNames.length - 1] !== newClassName) {
element.removeClass(oldClassNames[oldClassNames.length - 1]);
}
}
};
smartToggle($('.test'), 'newclass');
smartToggle($('.test'), 'newclass2');
});
Demo - http://jsfiddle.net/Q9A8N/ (look at the console to see what it is doing on each pass)
That should do what you want but as #T.J. Crowder said it is rather fragile and assumes that the class you want to remove is the last one on the element.
As an answer to your question, I would go with ankur's answer
As a follow-up to Sem's answer, regarding the handling of jQuery events :
you can use the on function to handle any jquery event from a parent node, based on a live filter :
function myToggle(element, newClassName) {
if ($(element).data('oldClassName')) {
$(element).toggleClass($(element).data('oldClassName'));
}
$(element).data('oldClassName', newClassName);
$(element).toggleClass(newClassName);
}
//event delegation : 'on' is called on the $('.divContainer') node, but we handle
//clicks on '.divItm' items, depending on their current class
$('.divContainer')
.on('click', '.divItm.plain', function(){ myToggle( this, 'red' ); })
.on('click', '.divItm.red', function(){ myToggle( this, 'blue' ); })
.on('click', '.divItm.blue', function(){ myToggle( this, 'plain' ); });
//initialize each item with the 'plain' class
myToggle( $('.divItm'), 'plain' );
Here is the jsFiddle.
You will note that the function called each time you click on an item depends on its "live" class, and that you don't need to manually enable/disable click handlers each time an item changes class.
You can learn more details from the documentation page.
var smartToogle = function (element, newClass) {
var $element = $(element),
currentClass = $element.data('toggle-class');
if (currentClass != newClass) $element.data('toggle-class',newClass).removeClass(currentClass || '');
$element.toggleClass(newClass);
};
or the other variant:
$.fn.smartToogle = function (newClass) {
currentClass = this.data('toggle-class');
if (currentClass != newClass) this.data('toggle-class',newClass).removeClass(currentClass || '');
this.toggleClass(newClass);
};
In this implementation you'll have to keep the a reference to this instance of fancytoggle.
var fancytoggle = function(el, oldClass){
// create a function scope so we'll have a reference to oldClass
return function(newClass) {
// toggle the old class and the new class
$(el).toggleClass(oldClass+ ' ' + newClass);
// update the new class to be the old class
oldClass = newClass;
};
};
for your example the code would look something like.
var bodytoggle = fancytoggle('body', 'pre-existing-class-name');
bodytoggle('new-class');
// 'new-class' replaces 'pre-existing-class-name'
bodytoggle('new-class-2');
// 'new-class-2' replaces 'new-class'
to see it in action refer to http://jsfiddle.net/aaf2L/6/
JavaScript's late binding is great. But how do I early bind when I want to?
I am using jQuery to add links with event handlers in a loop to a div. The variable 'aTag ' changes in the loop. When I click the links later, all links alert the same message, which is the last value of 'aTag'. How do I bind a different alert message to all links?
All links should alert with the value that 'aTag' had when the event handler was added, not when it was clicked.
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
nextTag.click(function() { alert(aTag); });
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
You can pass data to the bind method:
nextTag.bind('click', {aTag: aTag}, function(event) {
alert(event.data.aTag);
});
This will make a copy of aTag, so each event handler will have different values for it. Your use case is precisely the reason this parameter to bind exists.
Full code:
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
nextTag.bind('click', {aTag: aTag}, function(event) {
alert(event.data.aTag);
});
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
You can also make a wrapper function that takes the text to alert as a parameter, and returns the event handler
function makeAlertHandler(txt) {
return function() { alert(txt); }
}
and replace
nextTag.click(function() { alert(aTag); });
with
nextTag.click(makeAlertHandler(aTag));
You need to keep a copy of this variable, like this:
for (aTag in tagList) {
if (tagList.hasOwnProperty(aTag)) {
nextTag = $('');
nextTag.text(aTag);
var laTag = aTag;
nextTag.click(function() { alert(laTag); });
$('#mydiv').append(nextTag);
$('#mydiv').append(' ');
}
}
The aTag variable is changing each time you loop, at the end of the loop it's left as the last item in the loop. However, each of the functions you created point at this same variable. Instead, you want a variable per, so make a local copy like I have above.
You can also shorten this down a lot with chaining, but I feel it clouds the point in this case, since the issue is scoping and references.
I'm new to the whole JavaScript and jQuery coding but I'm currently doing this is my HTML:
<a id="tog_table0"
href="javascript:toggle_table('#tog_table0', '#hideable_table0');">show</a>
And then I have some slightly ponderous code to tweak the element:
function toggle_table(button_id, table_id) {
// Find the elements we need
var table = $(table_id);
var button = $(button_id);
// Toggle the table
table.slideToggle("slow", function () {
if ($(this).is(":hidden"))
{
button.text("show");
} else {
button.text("hide");
}
});
}
I'm mainly wondering if there is a neater way to reference the source element rather than having to pass two IDs down to my function?
Use 'this' inside the event. Typically in jQuery this refers to the element that invoked the handler.
Also try and avoid inline script event handlers in tags. it is better to hook those events up in document ready.
NB The code below assumes the element invoking the handler (the link) is inside the table so it can traverse to it using closest. This may not be the case and you may need to use one of the other traversing options depending on your markup.
$(function(){
$('#tog_table0').click( toggle_table )
});
function toggle_table() {
//this refers to the element clicked
var $el = $(this);
// get the table - assuming the element is inside the table
var $table = $el.closest('table');
// Toggle the table
$table.slideToggle("slow", function () {
$el.is(":hidden") ? $el.text("show") : $el.text("hide");
}
}
You can do this:
show
and change your javascript to this:
$('a.tableHider').click(function() {
var table = $(this.name); // this refers to the link which was clicked
var button = $(this);
table.slideToggle("slow", function() {
if ($(this).is(':hidden')) { // this refers to the element being animated
button.html('show');
}
else {
button.html('hide');
}
});
return false;
});
edit: changed script to use the name attribute and added a return false to the click handler.
I'm sure this doesn't answer your question, but there's a nifty plugin for expanding table rows, might be useful to check it out:
http://www.jankoatwarpspeed.com/post/2009/07/20/Expand-table-rows-with-jQuery-jExpand-plugin.aspx