I have a component and its inner html is dynamically changing. I do some operation on the child DOM elements of this component, so I have to repeat the operations each time the inner html of this component is changed.
For example in the component:
myElements = this.el.nativeElement.querySelectorAll('.myClass');
// do something with that DOM elements
And in the view:
<div *ngFor="let item of myItems">
<div class="myClass">...</div>
</div>
I have to call
myElements = this.el.nativeElement.querySelectorAll('.myClass');
Each time myItems is updated and new DOM elements loaded.
Can I bind the innerHTML to ngOnChange() or is there other ways to achieve this?
You could try ngDoCheck:
https://angular.io/docs/ts/latest/api/core/index/DoCheck-class.html
You would manually check the old value and the new value of the innerHtml, then execute your custom code.
Potential example:
public ngDoCheck(){
if(this.oldInnerHtml != this.newInnerHtml){
//take custom action
}
}
Related
I'm reactively loading elements using Svelte's {#each} functionality, like so:
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button> (*)
</div>
{/each}
(*) a component which forwards its on:click
What I want to do is when someFunction is called by clicking the created item, reference the actual DOM element. I know you can reference the specific array item by passing the index to the function, but that gives me the array item, not a reference to the unique DOM element. How would one go about doing this?
Things I tried so far:
on:click={() => someFunction(this)}: returns undefined
on:click={(el) => someFunction(el)}: returns undefined
on:click={(e) => someFunction(e)}: followed by using e.target, which does return the button that is clicked, but would need .parentElement to get to the div, which doesn't seem like a very Svelte way.
on:click={someFunction}: combined with bind:this={anItem}, which of course only returns the last created element in the {#each} block.
If you just want the parent div, a quick and dirty solution would be to just use an Array.
A more elegant solution would be to wrap <Button> within a parent component and get the reference from there, through the context API for instance or a slotted prop.
<script>
let itemsElements = [];
const someFunction = (i) => console.log(itemsElements[i]); // <- this is your div
</script>
{#each $items as item, i}
<div bind:this={itemsElements[i]}>
<Button on:click={() => someFunction(i)}>{item.text}</Button>
</div>
{/each}
the button in question is a modal which I want to have an open/close functionality. So, upon clicking itself I want it to toggle the class "open". However, using the class directive this results in all buttons/modals generated by {#each} to simultaneously toggle the class.
That just means you are using the wrong property. If you have multiple objects you need to store the state per item and then use that for the class directive.
E.g.
{#each $items as item}
<div>
<Button on:click={someFunction}>{item.text}</Button>
<div class="modal" class:open={item.show}>...</div>
</div>
{/each}
Alternatively you can store the state in a separate list or a dictionary. You can also extract the content of the {#each} to a component which then can have a local state variable for the open state.
I am trying to get an array of DOM-Elements in Vue.js. If I had the following HTML structure:
<select onchange="toggleDisability(this);" class="mySelect" id="mySelect1">
</select>
<select onchange="toggleDisability(this);" class="mySelect" id="mySelect2">
</select>
I could get all elements with the mySelect class with normal JS like:
var arraySelects = document.getElementsByClassName('mySelect');
Now I am trying to get the same thing with Vue $refs, but I am always getting the last element. it looks like:
<select id="selection-x" ref="Axis" #change="log($event)"></select>
<select id="selection-y" ref="Axis" #change="log($event)"></select>
and
log(selectElement){
var arraySelects = this.$refs['Axis'];
}
Of course there are also options ,so that #change event gets emitted, but it doesn't do what I want it to. I want to get an array of the elements with the same ref just like it works in the example above for normal JS, where you are getting an array of select elements whose class attribute equals to mySelect.
P.S. I know ref should be unique, but how could it be then used for this particular usecase?
No. It is not possible with ref and $refs. If you wish to do DOM manipulation then, use vue-directive or directly access DOM from the root element of the component like:
Vue.extend({
mounted() {
// Do what you want with your children.
const children = this.$el.querySelectorAll('.mySelect');
}
})
For me the best way to do this was to set a ref on the parent element (thanks joaner in original comment), but then I also needed to run my code in the "updated" hook so my data was loaded before trying to access the dom (I also have a v-if on the same element I want to reference children):
template:
<ul v-if="dataLoaded" ref="eventlist">
<li class="eventItem"></li>
<li class="eventItem"></li>
<li class="eventItem"></li>
</ul>
javascript:
updated() {
let eventItems = this.$refs.eventlist.children
console.log(eventItems)
}
I have an element in local storage with multiple elements, for simplicity, I will make the element:
<div id="outer">
<ul id="inner">
<li id="item">
</li>
</ul>
</div>
The element is saved as a string and I want to manipulate the contents.
Like such:
let local_storage_element = localStorage.getItem("val")
$(local_storage_element+':last-child').append("<p>something</p>")
No matter what selector I add after local_storage_element it will always append the value to the string not to the selected element(:last-child in this case)
does anyone know how to append to a specific element within the string??
Although you have written jquery in the title there is a javascript tag added also so I thought why not provide an answer that justifies your needs and helps you accomplish the task in the same way you want.
The
DocumentFragment interface represents a minimal document object that has no parent. It
is used as a lightweight version of Document that stores a segment of
a document structure comprised of nodes just like a standard document.
The key difference is that because the document fragment isn't part of
the active document tree structure, changes made to the fragment don't
affect the document, cause reflow, or incur any performance impact
that can occur when changes are made.
So how to do it as the DocumentFragment still appends node with it and not string, we will create a temp element and add the HTML from the localStorage using innerHtml and then append the firstChild of that temp node i.e our actual string which is now treated as a node, to the document fragment and then search and appends HTML to it, we will use our temp element to add HTML every time see below.
I will append a new child div to the element #outer in the string given above in the post here is the working FIDDLE as SO does not support localStorage you can see it working there open the console to view the resulting HTML with the new child added and below is the code
$(document).ready(function () {
if (localStorage.getItem('html') === null) {
localStorage.setItem('html', '<div id="outer"><ul id="inner"><li id="item"></i></ul></div>');
}
var frag = document.createDocumentFragment();
var temp = document.createElement('div');
temp.innerHTML = localStorage.getItem('html');
frag.appendChild(temp.firstChild);
temp.innerHTML = '<div class="new-child"></div>'
frag.querySelector("#outer").appendChild(temp.firstChild);
console.log(frag.querySelector("#outer"));
localStorage.removeItem('html');
});
You can't use string as selector. If you want transform string to html then you should put it in some element as innerHTML. So try create some hidden div and insert your string as HTML to it. Something like this
var your_string = '<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>';
document.querySelector('.hidden').innerHTML = your_string;
document.querySelector('ul li:last-child').innerHTML = 'your content';
document.querySelector('.result').appendChild(document.querySelector('ul'));
Example
The problem may arise when you get '<div id="outer">' from localStorage to use it as a selector since it only accepts "#outer" to be a selector. If you want to add an element to be the last child of parent's element, you could use after() instead of append().
$(document).ready(() => {
if ($("#charl").children().length === 0)
{
// if using after with no element inside ul then it will be inserted after il
$("#charl").html("<li>foo</li>")
}
else {
$("#charl li").after("<li>bar</li>")
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="charl">
<li>Foo</li>
</ul>
I want to create several elements using JSON data with a template, by creating a clone of the template and appending them to the specific div.
However, when using the cloned template variable, the clone can't be used inside the function which handles the JSON data, even when this clone is above the function (which should make it global for everything the same level?).
HTML
<template class="content">
<h2>Hello World</h2>
<h3>Hello World</h3>
</template>
<div class="box">
</div>
JS
let content = document.querySelector(".content").content;
let box = document.querySelector(".box");
doStuff();
function doStuff() {
let content = document.querySelector(".content").content;
let box = document.querySelector(".box");
//make a clone of the template
let clone = content.cloneNode(true);
// change content of H2
clone.querySelector("h2").textContent = "First Box";
// change content of H3 with data fetched from API
fetch("http://kea-alt-del.dk/t5/api/product?id=21").then(e => e.json()).then(productJson => otherStuff(productJson));
function otherStuff(productJson) {
clone.querySelector("h3").textContent = productJson.name;
}
// add the clone to the box div
box.appendChild(clone);
}
Working fiddle as Example: https://jsfiddle.net/c1x98hmh/4/
We can see in the result that the h2 content has been changed, however, h3 remains unchanged. Console log tells me that clone is null.
How come this example works when we change the template element to a div?
(I need this for Json, that's why I have a template, function, clone and inside function)
clone is a DocumentFragment object. Here's what appendChild does with a DocumentFragment:
If the given child is a DocumentFragment, the entire contents of the DocumentFragment are moved into the child list of the specified parent node.
keyword being "moved" — that's why clone is empty when otherStuff is called.
My guess is that since the <template> element is not to be rendered by the browser, its content is not part of the DOM and thus it is not possible to query it with a DOM query such as document.querySelector().
EDIT: It is confirmed here: https://www.html5rocks.com/en/tutorials/webcomponents/template/
Content is considered not to be in the document. Using document.getElementById() or querySelector() in the main page won't return child nodes of a template.
Perhaps I'm using $.data incorrectly.
Assigning the data:
var course_li = sprintf('<li class="draggable course">%s</li>', course["fields"]["name"]);
$(course_li).data('pk', course['pk']);
alert(course['pk']); // shows a correct value
alert($(course_li).data('pk')); // shows null. curious...
course_li is later appended to the DOM.
Moving the li to a different ul:
function moveToTerm(item, term) {
item.fadeOut(function() {
item.appendTo(term).fadeIn();
});
}
Trying to access the data later:
$.each($(term).children(".course"), function(index, course) {
var pk = $(course).data('pk');
// pk is undefined
courses.push(pk);
});
What am I doing wrong? I have confirmed that the course li on which I am setting the data is the same as the one on which I am looking for it. (Unless I'm messing that up by calling appendTo() on it?)
When you store the data:
$(course_li).data('pk', course['pk']);
you're creating an element but not saving it, so it's lost. Your alert test test the wrong value; it should be:
$(course_li).data('pk', course['pk']);
alert($(course_li).data('pk'));
which is null. Consider:
$(course_li);
$(course_li);
This creates two different elements with source equal to course_li, which are then promptly lost. What you need to do is create the element first, then work with that single element (i.e. don't call $(course_li) more than once). For example,
var course_li = $(sprintf('<li class="draggable course">%s</li>',
course["fields"]["name"]));
course_li.data('pk', course['pk']);
parent.append(course_li);
Note that course_li now holds an element, rather than a string.
try checking to see if the element being created by this call:
$(course_li)
is a single 'li' element, or a div. From the doco:
When the HTML is more complex than a single tag without attributes, as it is in the above example... snip ...Specifically, jQuery creates a new <div> element and sets the innerHTML property of the element to the HTML snippet that was passed in
So it's probably creating a div that you are assigning the data to, so when you select the 'li' itself, you are getting a child of the actual element that you set the data on.