I have the template like this
<template>
<div>
<div id="hiddenElement">
<MyElement v-for='...' #click="...">
</MyElement>
</div>
<div id="appendElementsHere" />
</div<
</template>
The user can append the element into the list, so I have some function like this:
someFunc(){
const hidden = document.querySelector('#hiddenElement')
const target = document.querySelector('#appendElementsHere')
target.innerHtml += hidden.outerHtml
}
The element is cloned can append to the #appendElementsHere successfully,
but the click function is not working. I think that maybe the click function in the vue element, not the html. How can I clone the element as vue-element, not html only? Or any idea to create vue element in the script (method) and then append to the dom ??
What you're doing is technically correct from a javascript perspective, however the click function doesn't work because you're not doing any data bindings after you "clone" the elements. I say "clone", because what you're doing is just passing a bunch of strings containing HTML. With that said, what you will have to do next is to add the event listeners to the cloned elements manually as well.
However, you could try to do it in a more Vue way, so instead of having a hidden component waiting to be cloned, you could create the Vue instance of the component you want (MyElement in your case) with code passing all the props/data you want and then append it to the element where you need it.
Here's an example with how to do it with buttons. If click on "Click to insert" you'll see how a CustomButton component gets appended to the right of the button.
Thanks,
Related
What's the best way to re-initialize javascript without a page refresh
I'm currently trying to append an MDBootstrap <select> tag, which is not as simple as adding a child element. Instead I'm removing the element and reconstructing it with the updated data via AJAX request.
At the moment, the only possibility I see is just executing the code again after the element is recreated.
Apologies if this isn't clear enough.
What I'm attempting to try, which works, however it's not very clean:
$("#function-btn").click(function(){
$.get("api/endpoint/getprofiles", function(){}).done(function(data){
$(".select-wrapper.mdb-select.md-form").remove()
$("#charcontainer").html(data);
$('.mdb-select').materialSelect();
})
// Reinitialize other JQuery functions around the '.mdb-select' element (alot)
})
Consider the following html
<div id='wrapper'>
<div id='container'>
<span>Content</span>
</div>
</div>
If you're deleting and replacing #container you will not want to hook your selector on #container but rather your jQuery should hook onto the parent (#wrapper) first and then drill down.
Therefore it will look something like this.
$('#wrapper>#container').on('click',function(){
//do the thing
});
That way you're technically not hooking onto the element that's removed from the DOM but rather the parent (#wrapper) element even though the selector has the child.
I created a couple of reusable components with a slot within it. So I can manage the content, style, or whatever it is anytime I call it, in other components. I wonder, can I passing an event handler to
those components but inside the template tag?
ReusableComponent
<a :href="hrefProps"> // I want the handler goes here
<slot></slot> // it will render plain text, without html tags
</a>
Main Component
<reusable-component>
<template #click="sayHelloWorld">Hello World!</template> // didn't work
</reusable-component>
How can I make that to work? Should I wrap them into at least 1 tag, like
<template><a #click="sayHelloWorld"></a></template> // sure it will working
Template tags don’t create a DOM element, so pi can’t add a listener to them, or add a class or anything else.
They are just a semantic tool to wrap multiple children in a loop.
Add the listener to the real parent element i-e href tag
I am still new to Angular and I'm struggling to get the DOM Element of an Angular Click Listener.
What I have is the following component HTML:
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top" (click)="itemClicked($event, !!menuItem.submenu)">
<!-- Additional divs inside... -->
</div>
</div>
I would like to toggle a class of "menuItem__top" when it is clicked. My approach was to use a click event listener but I can't get the source Element to apply the class on.
itemClicked(event, hasSubmenu){
console.log(this) //is the component
let target = event.target || event.srcElement || event.currentTarget;
if(hasSubmenu){
console.log(target);
}
}
It could be done by getting the target of the $event but this way I would need to check the target and move with closest(".menuItem__top") up to the correct source element.
Is there an easy way in Angular to pass the source element of the click listener to the click function or a way to get it inside the click function?
In vanilla JS it would be as easy as using "this" inside the click function, but in Angular this is bind to the component. (In this case, it would be ok to loose the binding to the component if this is the only way.)
I thought about two ways:
Assigning a dynamic reference containing some string and the itemId, passing the itemId and retrieving the reference object based on the itemId in the listener.
Using a #HostListener to listen on every "menuItem__top" click and toggling the class every time.
What do you think is the best way? I feel like I am missing something simple here.
Go the other way around. People are used to jQuery and the way it works (selecting elements already present in the DOM, then adding them a class). So in Angular, they try to do the same thing and grab the element that was clicked, to apply it a class. Angular (and React and others) work the other way around. They construct the DOM from data. If you want to modify an element, start with modifying the data it was generated from.
This <div class="menuItem__top"> that you click on is constructed from let menuItem. All you have to do is add a property, say "isClicked" to menuItem.
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top"
[class.isClicked]="menuItem.isClicked"
(click)="menuItem.isClicked = true">
<!-- Additional divs inside... -->
</div>
</div>
I want to have a button which toggles element in the document.
I don't want to use class display: none if/else statement. Instead, I want to appendChild if it doesn't exist and if it exists, then I want to removeChild.
There is an idea of what I want to achieve, but I have some problem there. The element is shown, but on next click, it is not removed, instead, I get multiple copies of it. (I think so). Please, no jQuery. Vanilla JavaScript only. Don't know is it important, but my-element is HTML <template>.
<body>
<button id="my-button">Toggle</button>
<template id="my-element">
<div>
Some content
</div>
</template>
<script>
let element = document.getElementById('my-element');
let content = element.content;
function toggle () {
if (document.body.contains(content)) {
document.body.removeChild(content);
} else {
document.body.appendChild(content);
}
}
let button = document.getElementById('my-button');
button.addEventListener('click', toggle, false);
</script>
</body>
You should understand that template exists outside of the loaded DOM, so no matter where you physically locate it in the code really won't make any difference later. Also, understand that when you want to access content of a template, you use .content, but after that content is injected into the DOM, it's not template.content anymore, it's part of the DOM.
So, you can't search the document for template .content because, after it gets inserted, it won't be template content in your document, it will be actual DOM content. You'll need some way of identifying it and a class is the simplest way.
Also, the documentation on templates says that you bring template content into the document with document.importNode, which you aren't using.
Next, always remember that .removeChild does what its name implies, it removes child elements. document.body.removeChild() can therefore only remove children of the body element, so you need to remember this. Your code may be OK for finding the imported node as a child of body, but that may not always be the case depending on where you inserted it. The code below dynamically locates the imported content's parent node and will always remove it, regardless of where it winds up being located in the DOM.
Lastly, and this is very important, although you have indicated that you don't want to hide/show the element and would rather append it and remove it, doing so is very expensive in terms of performance. Every time you add or remove an element from the DOM, the entire DOM has to be rebuilt and the node(s) you remove don't necessarily get removed from memory even though they are not present in the DOM anymore. So, do this at your own risk. It's actually much better (from a performance standpoint) to simply hide/show content.
let element = document.getElementById('my-element');
function toggle () {
// Attempt to reference the element in the document, not the template content
var imported = document.querySelector(".imported");
// Check for the element, not the template content
if (document.body.contains(imported)) {
// Element exists, call removeChild on its parent
imported.parentNode.removeChild(imported);
} else {
// Use .importNode to bring template content in:
document.body.appendChild(document.importNode(element.content, true));
}
}
document.getElementById('my-button').addEventListener('click', toggle);
<button id="my-button">Toggle</button>
<template id="my-element">
<div class="imported">Some content</div>
</template>
I'm trying to understand why loading HTML into a div block renders its class statement effectively non-existent to a click event.
My HTML code looks like this:
<div id="load-to"></div>
<div id="load-from">
<div class="load-from-css"> Hello!</div>
</div>
<button>load it!</button>
My JS code looks like this:
$('button').click(function(){
var html = $('#load-from').html();
$('#load-to').html(html);
});
$('.load-from-css').click(function(){
alert('clicked');
});
When I click the button the HTML from the lower div block is loaded into the upper div block, and then the HTML looks like this:
<div id="load-to">
<div class="load-from-css"> Hello!</div>
</div>
<div id="load-from">
<div class="load-from-css"> Hello!</div>
</div>
My question is, why does the second click event (defined in my jQuery code) only work on the original lower "Hello!" div block but not on the loaded upper one, when both have the same class definition?
Other answers have already covered the core reason for your problem (that copying the HTML of an element and placing it elsewhere will create a brand new DOM element and does not copy any events that were bound to the original element... keeping in mind that when you add an event listener, it will only bind to any elements that exist at the time that you do so)
However, I wanted to add some other options for accomplishing what you want to do.
jQuery has a few different techniques that make this sort of thing easy:
.clone() will essentially do the same thing as you are doing now*, it will copy the HTML content and create a new DOM element. However, if you pass true (ie: .clone(true)), it will clone it with all data and events intact.
* note that to truly get the same result as using .html(), you need to do .children().clone(), otherwise you'll get both the inner and outer div.. this may or may not be necessary depending on the use case
ex: https://jsfiddle.net/Lx0973gc/1/
Additionally, if you were in this same situation but did not want to make a clone, and simply wanted to move an element from one place to another, there is another method called .detach() which will remove the element from the DOM, but keep all data and events, allowing you to re-insert it later, in the same state.
ex: https://jsfiddle.net/Lx0973gc/2/ (not the best example because you won't see it move anywhere, but it's doing it!)
As another alternative, you can use delegated event binding, which actually binds the event to a different element (a parent) which you know won't change, but still allows you to target a child element within it:
$('body').on({
'click': function() {
alert('clicked');
}
}, '.load-from-css');
ex: https://jsfiddle.net/Lx0973gc/4/
The $('.load-from-css') finds all elements currently existing and .click(...) attaches a listener to all these elements. This is executed once.
Then you copy the raw html which does not transfer any listeners. The DOM has nodes onto which the listeners are attached but when you copy the plain HTML you essentially create new nodes based on the html.
Because you are copying just the HTML. The js file is loaded at the beginning, when there is just one instance of a div with the "load-from-css" class. You should execute again the code adding the listener after you copy the html. Somethinglike:
$('button').click(function(){
var html = $('#load-from').html();
$('#load-to').html(html);
$('.load-from-css').click(function(){
alert('clicked');
});
});
#load-to inner HTML is initially empty. so added click listener only for #load-from .load-from-css. Dynamically bind element will not attach the click listener.
jQuery new version have the feature to attach the event for dynamic elements also. Try this
$('button').click(function(){
var html = $('#load-from').html();
$('#load-to').html(html);
});
$(document).on('click', '.load-from-css', function(){
alert('clicked');
});
Also we can use like this
$( document ).delegate( "load-from-css", "click", function() {
alert( "Clicked!" ); // jQuery 1.4.3+
});
Simply because the page did not refresh. You loaded a content to another content without loading the page, and the browser wont recognized any event added to the loaded element.
What you should do is load your javascript tag with the load along with the content.
Your code should be like this:
<div id="load-to">
<div class="load-from-css"> Hello!</div>
</div>
<div id="load-from">
<div class="load-from-css"> Hello!</div>
<script>$('button').click(function(){
var html = $('#load-from').html();
$('#load-to').html(html);
});
$('.load-from-css').click(function(){
alert('clicked');
});</script>
</div>