I'm working on a project that returns GIFs from the GIPHY API, and whenever a search is executed, I'm capturing each search history item as its own button that a user can click on and see the results of the search as opposed to retyping the search again. I successfully added the buttons to the HTML with the proper classes, however, when I tried to write a simple on click event such as $('.history-btn').on('click', function() {
console.log('hello');
});
nothing appears. What is causing this? Here is my whole set of code for context:
$(document).ready(function () {
var searches = [];
function addSearch() {
$(".prev-searches").empty();
for (var i = 0; i < searches.length; i++) {
var history = $('<button>');
history.addClass("btn btn-primary history-btn");
history.attr("data-name", searches[i]);
history.text(searches[i]);
$(".prev-searches").append(history);
}
}
$('.history-btn').on('click', function() {
console.log('hello');
});
function returnSearch() {
var gifSearch = $(".search-input").val().trim();
var queryURL = 'https://api.giphy.com/v1/gifs/search?api_key=XXXXX-HIDDEN-XXXXX&q=' + gifSearch + '&limit=15&offset=0&rating=PG&lang=en';
$.ajax({
url: queryURL,
method: "GET"
}).then(function(response){
console.log(queryURL);
console.log(response);
console.log(response.data.length);
for (var i =0; i < response.data.length; i++) {
arrImg = response.data[i].images.fixed_width.url;
var newContent = '';
newContent = '<img src="' + arrImg + '">';
$('.gif-area').html(newContent);
console.log(newContent);
}
});
}
//When search is executed
$('.search-btn').on('click', function() {
event.preventDefault();
var search = $('.search-input').val().trim();
searches.push(search);
addSearch();
returnSearch();
});
function renderGIFs () {
for (var i = 0; i < response.data.length; i++) {
var newGifs = '';
newGifs += '<img src="' + response.data[i].bitly_gif_url + '"';
$(".gif-area").html(newGifs);
}
}
});
You need event delegation:
$('.prev-searches').on('click', '.history-btn', function() {
console.log('hello');
});
Resource
Understanding Event Delegation
Is the class 'history-btn' added dynamically from your addSearch function?
Then please use an element which is above in heirarchy of history-btn and bind the event to that element.
For example I bind the event to the div element,
<div id='historybtncontainer'>
<button class='history-btn'>Button</button>
</div>
and in script you can do
$('historybtncontainer').on('click', 'history-btn', function(){
console.log("hello");
});
document allows you to get a new tree of elements after dynamically adding the history-btn class to the <button>
$(document).on('click', '.history-btn', function() {
console.log('hello');
});
Related
Is there a way to get all javascript associated with an html element by class name returned in an array? Any suggestion as to how one would achieve doing this? Are there any node packages that would allow me to do something like this?
For example:
HTML
<div class="click_me">Click Me</div>
JS
$('.click_me').on('click', function() { alert ('hi') });
I would want something like (psuedo-code either on the client or server side):
function meta() {
let js = [];
js = getAllJavascriptByClassName('click_me');
console.log(js[0]);
}
Output of meta()
$('.click_me').on('click', function() { alert ('hi') });
This will pull out all event handlers of all elements of given class.
But these handlers must be attached using jquery.
function getAllEventHandlersByClassName(className) {
var elements = $('.' + className);
var results = [];
for (var i = 0; i < elements.length; i++) {
var eventHandlers = $._data(elements[i], "events");
for (var j in eventHandlers) {
var handlers = [];
var event = j;
eventHandlers[event].forEach(function(handlerObj) {
handlers.push(handlerObj.handler.toString());
});
var result = {};
result[event] = handlers;
results.push(result);
}
}
return results;
}
// demo
$('.target').on('click',function(event){
alert('firstClick handler')
});
$('.target').on('click',function(event){
alert('secondClick handler')
});
$('.target').on('mousedown',function(event){
alert('firstClick handler')
});
console.log(getAllEventHandlersByClassName('target'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='target'> </div>
You can use getEventListeners() which is part of the chrome devtools but for employing client side, there's an possible-duplicate question that partially answers this: How to find event listeners on a DOM node when debugging or from the JavaScript code? which basically shows (in the second voted answer) that depending on how the events are set (javascript attribute, eventListener, jquery, other lib) there are different ways to retrieve the functions.
The Visual Event 2 program mentioned in the first question seems to be more of a library doing what the second answer is suggesting so maybe this will solve your problem.
If you are interested only in jQuery solution I may suggest you (I assume there is only one event per type, but you need to cycle on all instances):
function getAllJavascriptByClassName(className) {
var elem = $('.' + className);
var result = [];
$('.' + className).each(function(index, element) {
var resultObjs = jQuery._data(element, "events");
var partialResult = [];
var x = Object.keys(resultObjs).forEach(function(currentValue, index, array) {
partialResult.push(resultObjs[currentValue][0].handler.toString());
});
result.push(partialResult);
});
return result;
}
function meta() {
let js = [];
js = getAllJavascriptByClassName('click_me');
console.log(JSON.stringify(js, null, 4));
}
$(function () {
$('.click_me').on('click', function (e) {
alert('Click event: hi')
});
$('.click_me:last').on('keypress', function (e) {
alert('Keypress event: hi')
});
meta();
});
<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
<div class="click_me">Click Me</div>
<div class="click_me">Click Me</div>
I would personally override addEventListener at the right places (meaning at the very top) with some safe guards.
UNfortunately jquery event handlers appear to be quite hard to read...
var element = document.getElementById("zou");
element.addEventListener("click", function(e) {
console.log("clicked from addevent");
});
element.addEventListener("mouseup", function(e) {
console.log("mouseup from addevent");
});
$(element).on("mousedown", function(e) {
console.log("mousedown from $")
});
console.log(element.getListeners());
<script>
window.eventStorage = {};
(function() {
var old = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function(a, b, c) {
if (!window.eventStorage[this]) {
window.eventStorage[this] = [];
}
var val = {
"event": a,
"callback": b
};
var alreadyRegistered = false;
var arr = window.eventStorage[this];
for (var i = 0; i < arr.length; ++i) {
if (arr.event == a && arr.callback == b) {
alreadyRegistered = true;
break;
}
}
if (!alreadyRegistered) {
arr.push(val);
}
old.call(this, a, b, c);
}
HTMLElement.prototype.getListeners = function() {
return window.eventStorage[this] || {};
}
}());
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="zou">click on me</div>
I am building a smartphone app using localStorage. Everything works well until I make an action. Here's the JS:
var ls = localStorage,
$input = $('#add-movie'),
$content = $('.content'),
$tools = $('.tools');
var movy = {
// returns the next id
// prev id is stored in ls
nextId: function() {
var i = ls.getItem('index') || 0;
i++;
ls.setItem('index', i);
return i;
},
getRelease: function(name, cb) {
cb('12/4'); // only temporary
},
// adds a new movie to the localStorage
new: function(name) {
var data = {
name: name
};
movy.getRelease(name, function(results) {
data.release = results;
});
ls.setItem(movy.nextId(), JSON.stringify(data));
},
// show all films
renderAll: function() {
$tools.hide();
var html = '';
for (var i = 1; i < ls.length; i++) {
var item = JSON.parse(ls.getItem(i));
if (!item) { }
else
html += '<tr data-index="' + i + '"><td class="name">' + item.name + '</td><td class="date">' + item.release + '</td></tr>';
}
$content.html(html);
},
remove: function(key) {
ls.removeItem(key);
for (var i = key + 1; i <= ls.length; i++) {
var item = ls.getItem(i);
ls.setItem(i - 1, item);
}
// decriment i
var index = ls.getItem('index');
index--;
ls.setItem('index', index);
}
}
$(function() {
movy.renderAll();
$('form').submit(function(e) {
e.preventDefault();
movy.new($input.val());
console.log($input.val());
movy.renderAll();
console.log('rendered');
});
$('.content tr').click(function() {
$(this).toggleClass('checked');
$tools.toggle();
$tools.find('#trash').attr('data-index', $(this).attr('data-index'));
});
$('#trash').click(function() {
var i = $(this).attr('data-index');
console.log(i);
movy.remove(i);
movy.renderAll();
// now nothing works until page is refreshed
});
});
Now, at the very first time when I refresh the page, it responds to clicks, shows the toolbar when needed and everything is great. However, after I click on trash, and it successfully deletes that item and re-renders all the elements, suddenly jQuery stops listening for clicks, and the whole thing becomes not responsive. That is, until I refresh the page.
Thanks!
Making my comment that solved the problem into an answer:
If you are rebuilding all the DOM elements (e.g. making new DOM elements), then your event handlers were bound to the old DOM elements and there are no event handlers on the new DOM elements.
You have to either use delegated event handling (attaching event handlers to static parent objects) or assign new event handlers to the newly create DOM elements. See this answer for how to do delegated event handling.
I was using the following code without Backbone.js and it was working - preventing the ghost images from appearing when trying to drag the image:
$(document).ready(function() {
$('img').attr('draggable', false);
document.getElementsByTagName('img').draggable = false;
});
Now I'm learning backbone.js and trying to implement it in the Views, this is how it looks:
function noDrag () {
$(that.el).find('img').attr('draggable', false);
document.getElementsByTagName('img').draggable = false;
}
noDrag();
It doesn't work.
I know that the key to making this work is getting the part enter code heredocument.getElementsByTagName('img').draggable = false; to work. What's wrong with my code?
Here goes the full code:
window.dolnyPanelView = Backbone.View.extend({
tagName : 'div',
className : 'dolnyPanel-menu-view',
initialize : function() {
var that = this;
// tu wybierz template z templates/main.tpl
this.template = _.template($("#dolnyPanel-view").html());
return this;
},
events : {
},
render : function() {
var that = this;
$(this.el).html(this.template());
$(this.el).find('#panel-view').carousel({
interval: 3000
});
var BandCount;
$.post('api/getBandsCount.php', function(data) {
BandCount=data;
});
var items = getItems(BandCount);
$(this.el).find('.carousel-inner').html($(items));
$(this.el).find('.item').first().addClass('active');
function getItems(BandCount) {
// console.log(BandCount);
var allItems = '';
for (var i = 1; i <= BandCount; i++) {
var items = '';
for (var j = 0; j < 6; j++) {
if (i <= BandCount) {
items += getImageItem(i);
i++;
}
}
allItems += '<div class="item"><div class="row">' + items + '</div></div>';
}
return allItems;
}
function getImageItem(id) {
var item = '<div class="col-md-2 col-sm-3 col-xs-6 artist-col biography-artist-col"><a href="#x" bandId="'+id+'">';
var src = 'LEKSYKON';
$.post('api/getAwatar.php', {id: id}, function(data) {
src = src + data.path;
}, "json");
item += '<img src="' + src + '" alt="Image" class="img-responsive artist"></a></div>';
return item;
}
function noDrag () {
$(that.el).find('img').attr('draggable', false);
document.getElementsByTagName('img').draggable = false;
}
noDrag();
return this;
}
});
UPDATE: thank you for all the answers, it turned out that it's not working because the whole view doesn't work. The thread could be closed now not to mistake anybody.
document.getElementsByTagName returns a NodeList of DOM elements, so you'd need to apply the attribute change to every element rather than to the collection itself.
As you're using jQuery already, you don't need to use document.getElementsByTagName - you can create another jQuery selection.
In fact, that's exactly what you are doing in your first, working example - document.getElementsByTagName is not doing anything there.
You can use jQuery's prop method to reliably change a toggleable attribute for all elements in a selection.
$('img').prop('draggable', false);
See this question for an discussion of prop vs attr.
Try this
//this will get the first img element
var element = document.getElementsByTagName('img')[0];
//setting value of attribute
element.setAttribute("draggable", false);
This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 8 years ago.
I've just read some threads about using on() to attach events to dynamically created html elements. I came up with this example (FIDDLE) to try to bind the click event to elements div.clicktitle returned via AJAX. The div elements have data attributes from which the click event should get the values and then display them. But It doesn't seem to be working.
What is working for me is to define the click event function and put it in the Ajax success callback. (FIDDLE) Is that the only way to make the click event to work with AJAX returned elements? Can anyone tell me why .on() isn't attaching the event to div.clicktitle?
HTML before Ajax
<div class="main"></div>
<div class="second"></div>
After Ajax it should be
<div class="main"><div data-source="Hello" class="clicktitle"><h6>Title1</h6></div></div>
<div class="second"><div data-source="Hello" class="clicktitle"><h6>Title2</h6></div></div>
JS Code:
$.ajax({
url: "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss%20where%20url%3D%22https%3A%2F%2Fnews.google.com%2F%3Foutput%3Drss%22&format=json&diagnostics=true&callback=",
success: function (data) {
var length = 0;
var items = data.query.results.item;
items = items.map(function(item){
return '<div data-source="Hello" class="clicktitle"><h6 style="background:beige;">'+item.title+'</h6></div>';
});
var half = items.length/2;
for(var i = 0; i < items.length; i++)
if(i < half)
$('.main').append(items[i]);
else
$('.second').append(items[i]);
length++;
},
error: function () {}
});
$('.clicktitle').on("click",function(){
var datasource = $(this).data('source');
$(this).text(datasource);
});
This should do it for you:
$('div').on("click",'.clicktitle',function(){
var datasource = $(this).data('source');
$(this).text(datasource);
});
the problem is that you try to add the listener directly to the generated element.
You have to add it to the parent element and use the filter in the on function like this :
$.ajax({
url: "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss%20where%20url%3D%22https%3A%2F%2Fnews.google.com%2F%3Foutput%3Drss%22&format=json&diagnostics=true&callback=",
success: function (data) {
var length = 0;
var items = data.query.results.item;
items = items.map(function(item){
return '<div data-source="Hello" class="clicktitle"><h6 style="background:beige;">'+item.title+'</h6></div>';
});
var half = items.length/2;
for(var i = 0; i < items.length; i++)
if(i < half)
$('.main').append(items[i]);
else
$('.second').append(items[i]);
length++;
},
error: function () {}
});
$('.main, .second').on("click",".clicktitle",function(){
var datasource = $(this).data('source');
$(this).text(datasource);
});
JSFIDDLE
hope this cut help you
Your binding won't work because your element doesn't exist yet, if you take #Alen's suggestion, that should do it for you, but you'll evaluate the handler EVERYTIME a click is made anywhere.
What you could do is attach the handler to the element before you write it to the DOM.
// Reusable event bheaviour
function clickBinding = function( event ){
var $elem = $(event.target),
datasource = $(this).data('source');
$elem.text(datasource);
}
// format item
function formatItem (item){
//setup elems
var h6 = document.createElement('h6'),
clickTitle = document.createElement('div');
// config h6
h6.style.background = 'beige';
h6.innerHTML = item.title;
clickTitle.appendChild(h6);
// config clickTitle
clickTitle.className = 'clicktitle';
clicktitle.setAttribute('data-source', 'Hello');
// Add event listeners
if (clicktitle.addEventListener) {
clicktitle.addEventListener ('click', clickBinding, false);
} else if (clicktitle.attachEvent) {
clicktitle.attachEvent ('onclick', clickBinding);
}
// return elem
return clicktitle;
}
$.ajax({
url: "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss%20where%20url%3D%22https%3A%2F%2Fnews.google.com%2F%3Foutput%3Drss%22&format=json&diagnostics=true&callback=",
success: function (data) {
var length = 0;
var items = data.query.results.item;
items = items.map(function(item){
return formatItem(item);
});
var half = items.length/2;
for(var i = 0; i < items.length; i++)
if(i < half)
$('.main').append(items[i]);
else
$('.second').append(items[i]);
length++;
},
error: function () {}
});
I am currently coding an instant chatbox using jquery which will show the latest chat on top (refreshes when user send data via post request)
and push the oldest chat downward and remove it.
The problem is that if more than one latest chat is retrieved(for example, 2), two new div will be prepended but only one oldest div is removed instead of two...I tried timeout but it didnt work either..
Below are the code snippets I believe which got problem in it.
function showData(currentchatstyle, data, final){
var newchatstyle;
if (currentchatstyle == "chatone") {
newchatstyle = "chattwo";
}
else {
newchatstyle = "chatone";
}
$('div[class^="chat"]:first').before('<div class="' + newchatstyle + '" style="display:none;">' + data + ' </div>');
$('div[class^="chat"]:first').slideDown(500,"swing", function(){
$('div[class^="chat"]').last().fadeOut(500, function() {
$(this).remove();
});
});
return newchatstyle;
}
$('input[name="content"]').keyup(function(key) {
if (key.which==13) {
var author = $('input[name="author"]').val();
var content = $('input[name="content"]').val();
var lastnum = $('postn:first').text();
var chatstyle = $('div[class^="chat"]:first').attr("class");
$.post(
"chatajax.php",
{ "author": author, "content": content, "lastnum": lastnum },
function(data) {
var msg = data.split("|~|");
for (var i = 0; i < msg.length; i++) {
chatstyle = showData(chatstyle, msg[i], true);
}
}
);
}
});
Help will be very much appreciated.
The problem is that you do select also currently-fading-out divs with $('div[class^="chat"]').last(), as you don't remove them immediately but in the animation callback. You for example might immediately remove the chat class so it won't be selected in the next call to showData.
Also, you should only use one class "chat" for a similar divs and for a zebra-style give them independent classes.
var chatstyle = "one";
function showData(data, final){
chatstyle = chatstyle=="one" ? "two" : "one";
var newDiv = $('<div class="chat '+chatstyle+'" style="display:none;">'+data+'</div>');
$('div.chat:first').before(newDiv);
newDiv.slideDown(500, "swing", function(){
$('div.chat:last').removeClass('chat').fadeOut(500, function() {
// ^^^^^^^^^^^^^^^^^^^^
$(this).remove();
});
});
}
function post(data) {
return $.post(
"chatajax.php",
data,
function(data) {
var msg = data.split("|~|");
for (var i = 0; i < msg.length; i++)
showData(msg[i], true); // what's "final"?
}
);
}
$('input[name="content"]').keyup(function(key) {
if (key.which==13)
post({
"author": $('input[name="author"]').val(),
"content": $('input[name="content"]').val(),
"lastnum": $('postn:first').text() // I'm sure this should not be extracted from the DOM
});
});