Get index of element in HTML - javascript

I have this code:
//Callback function
var done = arguments[arguments.length - 1];
//Take all the events
var array_events = []
var retour = (e) => {
array_events.push(e.target.outerHTML)
}
var quit = (key) => {
console.log(array_events);
(key.keyCode == 27 )? done(JSON.stringify(array_events)) : undefined
}
// Listen to the clicks
getPath = document.addEventListener("click", retour, true)
getIndex = document.addEventListener("click", detour, true)
// Listen to the key "esc" which means user has gathered all needed events
getKey = document.addEventListener("keydown", quit, true)
This gets me the value of outerHTML of an element clicked, so far so good.
I'm building a program that needs to find that element,but in the case where the outerHTML of the element is not unique (meaning there are multiple occurrences of that element in the DOM), I cannot find out which one of the elements the user wants, so I think the easiest way would be to include index of some sort.
My question is ->
How can I get the index of that element relative to whole HTML document?
If that is not possible, any index of some sort that I can take and calculate which one is first or second or last does help too!

I am not aware of any indexing of DOM elements that is accessible with JS.
Not sure how you want to use this information about what element has been selected. So here are several ideas, hope one will be useful.
If you need to do something with that element, like modify it in some way in your callback, then you may want to use:
e.target.parentElement
This will return the actual DOM element, and you can do whatever you want with it. It does not matter how many copies you have in the page. And if you actually need the HTML text, you can always use .innerHTML to get it.
If you need this for some type of analytics where you want to save this interaction somewhere instead of modifying the page you can do something like this:
e.target.parentElement.getBoundingClientRect()
This will return an object that looks something like this:
this way you will know the exact coordinates of the element that was selected, and even if you have multiple elements with the exact same HTML you can know that this is some specific copy of it.
If you do not have too many elements that need to be affected by this, maybe you can add the index yourself. When all elements are created, you can do something like this:
let maxId = 0;
const allElements = document.querySelectorAll('.SelectorForYourElement');
allElements.forEach(element => {
element.dataset.id = ++maxId;
});

Related

how to select element with getByTestId that also has specific value with react-testing-library

In react, I am mapping out some elements from an array. For example
{options.map((option)=>{
return <div data-testid="option">{option}</div>
})
I have multiple tests where I want to select an option (without knowing what the textContent of the option is) so I used data-testid="option" and select with screenGetAllByTestId('option')[0] to select the first option...
However, there are some times that I know what particular option I want, and I want to getByTestId but because they all share the same data-testid its a bit harder.
What I'm trying to do, is something similar to this pseudo code:
screen.getAllByTestId('option').getByText("Apples")
which will get all the options, but then get the specific one that has Apples as text
You can write custom matcher functions too, like:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})
From the docs
I think in your case, that translates to:
screen.getByText((content, element) => {
return element.getAttribute('data-testid').toLowerCase() === 'option' && content === 'Apples'
})
An even better solution might be to have legible text labels attached to each group of options, so your tests could say:
// Find your menu / list header
const fruitsListHeader = screen.getByText('Fruits list');
// Find the menu or whatever it's in
// You can use plain old query selectors
const fruitsList = fruitsListHeader.closest('[role="menu"]')
// Look for your option in the right place, instead of
// praying there are no duplicated options on the page.
const appleOption = within(fruitsList.closest('[role="menu"))
.getByText('Apples');
within docs

How to add a class to all similar elements with an index less than the clicked element with jquery?

I have 8 elements in sequence that each represent a yoga pose. When one of these elements is clicked I want to add/remove classes for all the pose cards that come before and after the clicked element. So far I have been able to get the index of the clicked element using the following:
$(".pose-card").click(function () {
clickedPoseIndex = $(".pose-card").index(this);
});
And then I tried to use a filter function to get the ones whose index is less than the clicked one with something like this:
let prevPoses = $(".pose-card").filter(function () {
return parseInt($(".pose-card").index(this) < clickedPoseIndex);
});
But that did not work! Please let me know if you can think of any better solutions. Much appreciated!
What you want to do inside filter is get the index as int (hence the parseInt function) and compare with the value you've stored in clickedPoseIndex which is already an int. You've simply missed a bracket or misplaced one. All you have to do is:
let prevPoses = $(".pose-card").filter(function () {
return parseInt($(".pose-card").index(this)) < clickedPoseIndex;
});
Edit
Don't need to use parseInt either as the value returned is already an int so:
return $(".pose-card").index(this) < clickedPoseIndex;

How do I copy an HTML document and edit the copy based upon the selection, without altering the original document?

I have an HTML document, and I would like to remove some of the tags from it dynamically using Javascript, based on whether the tags are within the current selection or not. However, I do not want to update the actual document on the page, I want to make a copy of the whole page's HTML and edit that copy. The problem is that the Range object I get from selection.getRangeAt(0) still points to the original document, as far as I can see.
I've managed to get editing the original document in place with this code:
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
var allWithinRangeOfParent = node.getElementsByTagName("*");
for (var i=0, el; el = allWithinRangeParent[i]; i++) {
// The second parameter says to include the element
// even if it's not fully selected
if (selection.containsNode(el, true) ) {
el.remove();
}
}
But what I want to do is to somehow perform the same operation with removing elements, but remove them from a copy of the original HTML. I've made the copy like this: var fullDocument = $('html').clone(); How could I accomplish this?
Either dynamically add a class or data attribute to all your elements on load before you clone so that you have a point of reference then grab the class or data attribute on the common ancestor and remove it from the clone. I can give an example if you like? Along these lines - http://jsfiddle.net/9s9hpc2v/ isn't properly working exactly right but you get the gist.
$('*').each(function(i){
$(this).attr('data-uniqueId', i);
});
var theclone = $('#foo').clone();
function laa(){
var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
if(node.getElementsByTagName){
var allWithinRangeOfParent = $(node).find('*');
console.log(allWithinRangeOfParent, $(allWithinRangeOfParent).attr('data-uniqueId'));
$.each(allWithinRangeOfParent, function(){
theclone.find('[data-uniqueId="'+$(this).attr('data-uniqueId')+'"]').remove();
});
console.log(theclone.html());
}
}
$('button').click(laa);

CKEditor - grab content from editor to setup live total word count

I'm trying to access the live content from each instance of CKEditor so I can setup a total word count. Before using CKEditor I would get the textarea's content with .getElementById(), and then I would get the live word count by passing the textarea element into my Countable() function which appends an event listener to the area. Is there a way to grab the live content of a CKEditor instance? I know it's an iframe so I'm not sure if it's possible to grab the live content.
Code I used to use with simple textarea:
var area1 = document.getElementById('textarea1');
Countable.live(area1, function(counter1) {
jQuery("#word_count1").text(counter1.words);
a1_count = counter1.words;
total_count();
});
This depends a lot on your Countable function and it's requirements - it would have helped to seen it and to know it's requirements. You can get the contents of each CKEditor instance in a few different methods, this is one
var contentArray = [];
var i = 0;
for (var instance in CKEDITOR.instances) {
var tmpEditor = CKEDITOR.instances[instance];
contentArray[i] = tmpEditor.getData();
i++;
}
Now the contents are in the contentArray. But form your code it looks like Countable needs an element. I'm unsure as to what kind of element reference it can use, but something like this might get you further:
var editor = CKEDITOR.instances.editor1; // change "editor1" to suit your editor
var element = editor.editable().$; // Get a reference to the body of the instance
Countable.live(element, function(counter1) {
jQuery("#word_count1").text(counter1.words);
a1_count = counter1.words;
total_count();
});
Now of course this only supports one editor instance, but the two examples combined might do the trick. Hopefully you don't use inline instances (it needs some additional work but is doable). Also note that naturally these return the source, not the text.
Also not that you do not want to loop this very quickly, it is very cpu intensive. I recommend a slow loop combined with the the CKEditor change event and maybe some other events that trigger the update. Do not trigger on every change, rather set a timeout to buffer the update trigger (you don't want to do the update when the user is typing).

Copying children elements of one division to another along with all associated events

I am trying to replace a certain div element parent with another one newparent. I want to copy only some of parent's children and put them in newparent, and then replace the parent by newparent.
Here is a snippet of my code:
var sb_button = parent.firstChild;
var temp;
while(sb_button) {
console.log("loop: ");
console.log(sb_button.id);
temp = sb_button;
if(sb_button.id != curr_button.id && sb_button.id != prev_button.id) {
console.log("if");
newparent.appendChild(temp);
}
else if(sb_button.id == curr_button.id) {
console.log("elseif");
newparent.appendChild(temp);
newparent.appendChild(prev_button);
}
else {
console.log("else");
}
sb_button.parentNode = parent;
console.log(sb_button.id)
console.log(sb_button.parentNode.children);
sb_button = sb_button.nextSibling;
}
parent.parentNode.replaceChild(newparent,parent);
EDIT :
So when I do newparent.appendChild(temp) it modifies sb_button. What's the workaround for this?
I haven't run your code, but there's a few weird things, perhaps one of which may cause the issue or help clear up the code so the issue is more obvious.
the variable temp seems to be an alias for sb_button: you could remove the variable declaration and replace all references with temp
sb_button is a confusing name for an arbitrary child node
you're appending the node in sb_button to newparent within the if statement, but right after you're trying to set sb_button_.parentNode to parent - that's not possible since parentNode is readonly and it certainly doesn't make sense - you can't append the element to one element but have a different parent.
are you trying to copy or move nodes?
Edit: given that you want to copy nodes, I believe you're looking for cloneNode: make a copy of the node and append that copy, not the original node.
As a matter of clean design, when things get complicated, I'd avoid this kind of hard-to-reason-about while loop. Instead, simply make an array of the nodes, order those the way you want (you could even do this using sort to make it immediately obvious you're just rearranging things), and then make a function that takes a newparent and the array and appends copies of all elements in array order to newparent. Your example isn't that complex, but even here, I'd change the order of the if-clauses to have the "default" case in the final else. e.g.:
for(var child = parent.firstChild; child; child = child.nextSibling)
if(child.id == curr_button.id) { //insert prev_button after curr_button
newparent.appendChild(child.cloneNode(true));
newparent.appendChild(prev_button.cloneNode(true));
} else if(child.id != prev_button.id) {
newparent.appendChild(child.cloneNode(true));
}
parent.parentNode.replaceChild(newparent, parent);
The idea being to make it instantly obvious to the reader that all children are processed exactly once.

Categories