jQuery - Make dynamically created DIV remove itself on click - javascript

been struggling with this for a while now. Really hope someone can help me out.
Im trying to make divs which are removable when you click onto them.
They have a css-class 'tag-show' which is added and removed (this works), so the selector seems to be fine i guess..?
Why is the $(this).remove() not working?
$(document).ready(function() {
// selectors
var module = $(".divCreate");
var list = module.find(".listTag");
var button = module.find(".divButton");
// the actual issue
button.click(function() {
list.append("<div class='tag'>Tag</div>");
setTimeout(function() {
list.find(".tag").last().addClass("tag-show").on("click", function() {
$(this).removeClass("tag-show");
setTimeout(function() {
$(this).remove();
},190);
});
},40);
})
});

"this" loses it's context in your setTimeout.
add a var containing this and use that instead.
like so
var self = $(this);
setTimeout(function() {
$(self).remove();
},190);
I think that would fix it.

You should use the jQuery .on() function instead of .click(), as it works for dynamically created elements. Then you also don't have to apply it to the element each time it is dynamically added.
Somewhere else in your code within $(document).ready(function() {}); add this event handler:
$('.listTag').on('click', 'div.tag', function() {
$(this).remove();
});
and it should work for any element that is matched by the div.tag selector.

this inside the setTimeout callback is not referring to the div anymore. You could bind the callback to the div or do something like this:
var $this = $(this);
setTimeout(function() {
$this.remove();
},190);

I have made some improvments to your code, adding comments where it was needed. Just check the code below and its comments.
Your problem with remove is that the this in your case is "triggering" the closest parent, which is setTimeout, not clicked element.
For a better understanding just try to call console.log(this); inside timeout function and click function, you will see the difference.
I have made an fiddle which can help you understand better (open developer tools to see the console result)
$(document).ready(function() {
// when you keep DOM elements in variables is better to put $ in the beginning
var $module = $(".divCreate");
// faster than .find()
var $list = $(".listTag", $module);
// can not call 'on' with variable
//var $button = $(".divButton", $module);
// called through document since we need to handle dynamic added elements - check event delegation
$(document).on("click", ".divButton", function() {
$list.append("<div class='tag'>Tag</div>");
setTimeout(function() {
// some improvments
$(".tag", $list).last().addClass("tag-show");
}, 40);
// Just keep this if you have divButton attached to an anchor element
// Useful for preventing default behvaiour - in this case adding "#" to url
return false;
});
// Do not need to create the event inside that event
$(document).on("click", ".tag-show", function() {
// Since we use an element more than once is better to
// add it into a variable to avoid performance issues - js caches it and call the variable
var $el = $(this); // our needed 'this'
$el.removeClass("tag-show");
setTimeout(function() {
// 'this' here returns some properties of window (where setTimeout belongs), we need to call element cached above
//(the 'this' above contains what we need)
$el.remove();
}, 190);
});
});
Note : still do not understand why you need these timeouts but it s up to you, maybe you need them with a bigger interval :D

Not sure what you're trying to accomplish, but perhaps this is a better way to accomplish the same task?
// selectors
var module = $(".divCreate");
var list = module.find(".listTag");
var button = module.find(".divButton");
// the actual issue
button.click(function() {
list.append("<div class='tag'>Tag</div>");
})
$(document).on('click','.tag', function(){
$(this).remove();
});
https://jsfiddle.net/7daffjh8/

Related

How to bind the event in html template?

Please refer to my previous question: How to get element in template html?.
Firstly, I follow the solution in How to get element in template html? to obtain the element.
var template = templateContent.content;
Then I try to bind a click event for searchBtn:
searchBtn.addEventListener('click', function() {
console.log('click'); // does not work
});
This binding fails, so I use the event delegation to handle event:
document.addEventListener('click',function(e){
if(e.target && e.target.id== 'searchBtn'){
console.log('click');
console.log(template.querySelector('#results'));
template.querySelector('#results').innerText = 'Foo';
}
});
The two logs are expected, but the last command to update the innerText does not work. On the other hand, I can update its content outside the click event. Any solution?
The final version of my code:
var link = document.querySelector('link[rel="import"][href="search.html"]');
var templateContent = link.import.querySelector('template');
var template = templateContent.content;
var searchBtn = template.querySelector('#searchBtn');
console.log(searchBtn);
template.querySelector('#results').innerText = 'Bar'; // ok
document.addEventListener('click',function(e){
if(e.target && e.target.id== 'searchBtn'){
console.log('click'); // ok
console.log(template.querySelector('#results')); // ok
template.querySelector('#results').innerText = 'Foo'; // does not work
}
});
Updates
Now I retrieve the results element outside the function:
var results = template.querySelector('#results');
Then inside the function, results.innerText = 'Foo'; works. However, this only updates the results itself, and the UI is not updated correspondingly.
How to update the UI inside the template?
You got it right, it is the binding issue. template is not available inside click function. You need to bind this. There are many ways to do this e.g.
document.addEventListener('click',onClick.bind(this));
onClick = function(e){
if(e.target && e.target.id== 'searchBtn'){
console.log('click'); // ok
console.log(template.querySelector('#results')); // ok
this.template.querySelector('#results').innerText = 'Foo'; // should work
}
}
There are many ways to get the HTML elements
You can get use
var sbtnElement = document.getElementById('searchBtn');
then add eventlistener to it
sbtnElement.addEventlistener('click', function() {
console.log('click'); // does not work
});
Naturally getElement by id will give you one element, and getElementsByClassName will return you an array of elements with the same classname. Same goes for getElementsByTagName.
hi i don't know what are you try to create for. but have you ever try to use template in script ? like this one
<script id="sampleTemplate" type="text/template">
// do the html here
</script>
So in that case you can bind event using that one. just a normal creating of html and binding javascript function.
You can refer to this blog. https://jonsuh.com/blog/javascript-templating-without-a-library/
I hope, i helped you thru this. :)

Appending a jQuery created element to vanilla js?

I need to create a new notification div when an event triggers. Normally, I would do this in jQuery and use something like $("myDiv").append(newDiv), however the item selector to be appended to can't be selected with jQuery. Below is the code to explain more.
function notifMaker() {
var notification = $("<div>").addClass("notification", "id-dark", "overtop");
var notifButton = $("<button>").addClass("delete");
notification.append(notifButton);
notification.text("You've already saved this item. Click the x button to remove it from saved items.");
return notification;
}
and
ondragleave: function (event) {
// console.log("Drag Leave");
var draggableElement = event.relatedTarget;
// console.log(notifMaker());
draggableElement.append(notifMaker());
},
As you can see, the element I need to append to is actually from the event object. And if I try to append the notification from notifMaker into this, I just get [Object object] because jQuery is technically created an object in notifMaker. Is there any way around this? Any other methods I should consider using? Thanks. Here is an image of the object.
Using jQuery you can wrap any DOM element into jQuery object, so that you can use any of library methods on that element. In your case the code would look like this:
ondragleave: function (event) {
var draggableElement = $(event.relatedTarget);
draggableElement.append(notifMaker());
}

What is the preferred pattern for re-binding jQuery-style UI interfaces after AJAX load?

This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here

jQuery / JS: looping function load

On site: http://jonirautiainen.net/code_edit/ there on left is a panel which has a filetree.
.load('scandDir.php') loads new <ul><li></li></ul> elements from php-file.
The problem is that because this script is a loop it executes loadNew(); multiple times.
Please check the problem live on page mentioned before. Click on those folders to open them and you will see those files showing up multiple times.
Script needs to be looped because .load is asynchronous. $(".file-tree a").on( won't let me select those new elements created by executing .load.
Is there any other way to do this?
function loadNew()
{
$(".file-tree a").on("click",function ()
{
var path = $(this).attr("title");
var folder = $(this).text();
var name = $(this).attr("name");
var id = $(this).parent().attr("id");
$("#testi").html(path + folder + name + id);
var liHtml = $(this).parent().html();
if(liHtml.match(/<ul /))
{
}else
{
if(name=="dir")
{
$("#hiddenDiv1").load("scanDir.php?path=" + path + "/" + folder, function() {
var hiddenDiv = $("#hiddenDiv1").html();
$("#" + id).append(hiddenDiv);
loadNew();
});
}
}
});
}
loadNew();
You keep piling on new click handlers. I understand that you're loading new elements and they need to have their events handled too, but in the process you're adding more handlers to the existing elements. So after you expand several directories, the older elements will have a couple of handlers on them; and when you click those, all of those handlers will get invoked (which gets you additional elements).
One approach is to take care what you add your click handler to. Only add it to the newly loaded elements.
Another, easier approach is to take advantage of the power of jQuery's on() method. If you write it like this:
$('#fileNavi').on('click', '.file-tree a', function() {
...
});
and only once, the handler will work for current and any future elements that appear in your hierarchy. Then inside the handler just do the AJAX call and add the new data to the document, trusting jQuery to handle the events for you. Get rid of the loadNew function altogether, especially the deviant recursion you have going there.
Your loadNew function is binding a new click handler to $(".file-tree a") every time you call it.
1 click, one load, 3 clicks.. 3 clcik handlers, 3 loads. Delegate the click handler to parent of the list.
$("#fileNavi ").on("click",'.file-tree a',function ()
loadNew();
})
EDIT: will require some reformatting of loadNew , you could pass "this" into load new as an argument so you can get all the variables from the element clicked
It looks to me like every time a .file-tree link is clicked it will be attaching a new click handler to all of the .file-tree elements. You just want to attach to the new elements. So perhaps change to something like this:
var expand = function() {
var path = $(this).attr("title");
var folder = $(this).text();
var name = $(this).attr("name");
var id = $(this).parent().attr("id");
$("#testi").html(path + folder + name + id);
var liHtml = $(this).parent().html();
if(liHtml.match(/<ul /)) {
}
else
{
if(name=="dir")
{
$("#hiddenDiv1").load("scanDir.php?path=" + path + "/" + folder, function() {
var hiddenDiv = $("#hiddenDiv1").html();
$("#" + id).append(hiddenDiv);
});
}
}
};
$('#fileNavi').on('click', '.file-tree a', expand);
EDIT:
on will effect all elements added to the DOM as well as existing elements.
you probably just need to add some parameters to "loadNew()" so it knows exactly what to do and not repeat itself.

Can I run nest a $('element.class').each() function within the change() function for the same $('element.class')?

Specifically, I want to iterate over every element on the page, each time the value of one changes.
So to paraphrase my code I have:
$('select.filterbox').change(function() {
// stuff
$('select.filterbox').each(function() {
// other stuff
});
});
'stuff' all executes just fine, but 'other stuff' doesn't happen
Here's the full code
// On Filterbox Change
$j('select.filterbox').change(function() {
// Show All Rows
$j('#table1 tr').show();
// For Each Filterbox
$j('select.filterbox').each(function() {
var selVal = $j(this).attr('value');
var col = $j(this).closest('th').parent().children().index($j(this).closest('th'));
alert('Column '+val+' : '+selVal);
// If Selected Value Not Empty
if(selVal != "") {
// For Each Row
$j('#table1 tr').each(function() {
var $tds = $j(this).find('td');
var cellVal = $tds.eq(col).text();
cellVal = $j.trim(cellVal);
// If td text != selected
if( cellVal != selVal ) {
// Hide this row
$j(this).hide();
}
});
}
});
});
Answer: yes you can. I've done a lot of stuff like that recently. Indeed, your code should do it. It is likely that you've made some small but significant error in your code that will need to be hunted down and fixed. I've done a lot of stuff stuff like that recently, too.
More helpful answer: do things to figure out where the breakdown is. replace the "$('select.filterbox').each()" call with something obvious - say, calling .hide() on large chunks of your page. Make sure that line of code is even being called. If it is being called, put the "$('select.filterbox').each()" call back, then move the hide() call inside of it. If that works, then you know that it's running at least once. Change the hide() call to a (this).hide(), and see if you can see which one it's identified. Once you've narrowed down where it's failing you, it'll be a lot easier to figure out what you've done wrong.
You can do this, but it's not optimal to attach separate event handlers to a lot of elements. You can take advantage of event bubbling and just attach a single handler to their parent container. For example:
$("#filters").change(function (evt) {
// evt.target will give you actual element that changed
$("#filters select.filterbox").each(function () {
// other stuff
});
});

Categories

Resources