How to find start and end index of DOM text selection? - javascript

In jQuery Terminal I want to add an API that will return indices of the selection.
Example HTML I have:
<div class="cmd" style="width: 100%; --cursor-line:1; top: 0px;">
<div class="cmd-wrapper" style="">
<span class="cmd-prompt" style="visibility: visible; margin-left: 0px;">
<span data-text="> ">
<span style="width: 2ch;">> </span>
</span>
</span>
<div role="presentation" aria-hidden="true" class="cmd-end-line">
<span data-text="H">
<span>H</span>
</span>
<span data-text="e">
<span>e</span>
</span>
<span data-text="l">
<span>l</span>
</span>
<span data-text="l">
<span>l</span>
</span>
<span data-text="o">
<span>o</span>
</span>
<span data-text=" ">
<span> </span>
</span>
<span data-text="W">
<span>W</span>
</span>
<span data-text="o">
<span>o</span>
</span>
<span data-text="r">
<span>r</span>
</span>
<span data-text="l">
<span>l</span>
</span>
<span data-text="d">
<span>d</span>
</span>
<span data-text=" ">
<span> </span>
</span>
</div>
<div class="cmd-cursor-line" role="presentation" aria-hidden="true">
<span>
<span data-text="x">
<span>x</span>
</span>
<span data-text="x">
<span>x</span>
</span>
<span data-text="x">
<span>x</span>
</span>
<span data-text="x">
<span>x</span>
</span>
<span data-text="x">
<span>x</span>
</span>
</span>
<span class="cmd-cursor" style="">
<span data-text="" class="end">
<span> <span></span></span>
</span>
</span>
<span></span>
</div>
</div>
<textarea autocapitalize="off" spellcheck="false" tabindex="1" class="cmd-clipboard" data-cmd-prompt="> " style=""></textarea>
</div>
This is copy-paste of the DOM after entering "Hello World\nxxxxx" and formatted and pretty printed using https://jsonformatter.org/html-pretty-print
My question is what should I do to get the selection indices?
For example, I have a command like this:
> He|lo wor|d
I should get [2, 8] and if the selection is outside of the range: example
>|>> Hello| world
where >>> is prompt I should get [0, 5] I don't care about the negative. I should also handle when the whole selection is outside
|>>>| Hello World
it should return [0, 0] or null.
How would to implement something like this? Note: that I only care about window.getSelection API it's 100% support, not need to be silly and support
IE8.

You want something like
var range = window.getSelection().getRangeAt(0);
var start = range.startOffset;
var end = range.endOffset;
Note that this code assumes that range.startContainer === range.endContainer (which it often does). If you want to get the text / the length of the text between the start and the end containers, you need to recursively traverse the DOM between them. There is also an issue where the length of the text in the DOM is not the same as the length of the text in HTML (browsers sometimes add spaces and other HTML elements)
You'd be right if you guessed that I've worked a bunch in Javascript with selections. IMO it's kind of a nightmare. Tim Down has written a very popular package called Rangy which I recommend it a lot. You should check it out and see if it meets the requirements of what you are doing.

I've solved the issue myself:
var selection = window.getSelection();
var start = $(selection.anchorNode);
var end = $(selection.focusNode);
var before = start.closest('.cmd [role="presentation"]').prevUntil('.cmd-prompt');
var count = 0;
if (before.length > 1) {
count = before.find('[data-text]').length;
}
var s = start.closest('.cmd [role="presentation"] [data-text]');
var e = end.closest('.cmd [role="presentation"] [data-text]');
if ((s.length || e.length)) {
start = count + s.index();
end = count + e.index() + 1;
console.log({start, end});
}

Related

How to for loop span tags in html/javascript?

How can I condense this many span tags? I am trying to get it as small as possible. Any advice helps, thanks!
<header>
cat <i class="fa-d fa-ct"></i>>
<div class="particle">
<span style="--i:50;"></span>
<span style="--i:21;"></span>
<span style="--i:16;"></span>
<span style="--i:18;"></span>
<span style="--i:13;"></span>
<span style="--i:22;"></span>
<span style="--i:15;"></span>
<span style="--i:24;"></span>
<span style="--i:17;"></span>
<span style="--i:28;"></span>
<span style="--i:12;"></span>
<span style="--i:26;"></span>
<span style="--i:23;"></span>
<span style="--i:13;"></span>
<span style="--i:17;"></span>
<span style="--i:11;"></span>
<span style="--i:21;"></span>
<span style="--i:16;"></span>
</div>
</div>
This is why I am trying to get. Each --i:Number creates a particle at random times.
In case you were looking for a more compact way of generating the HTML, then maybe the following is helpful?
const html=[...new Array(18)].map(_=>`<span style="--i:${Math.ceil(Math.random()*40+10)};"></span>`).join("\n");
console.log(html); // can be commented out ...
document.querySelector(".particle").innerHTML=html;
<header>
cat <i class="fa-d fa-ct"></i>
<div class="particle"></div>
</header>

Run function when element enters to viewport with getBoundingClientRect or intersection observer

i'm trying to run a function called txtAnim when the element .txt-anim enters to the viewport and want to use getBoundingClientRect or intersection observer
Is that possible? I didn't get it working
(I have the animation working but i want it fired when it enters to the viewport)
function txtAnim(speed){
jQuery('.txt-anim').css("opacity", "1");
var skills = jQuery('.txt-anim p').contents().filter(function() {
return this.nodeType === 3; // only immediate text in div, not in span
}).map(function() {
var txt = "<span class='letter'>" + jQuery(this).text().split("").join("</span><span class='letter'>") + "</span>";
// console.log(txt);
jQuery(this).replaceWith(txt);
});
var i = 0;
var span = jQuery('.txt-anim').find('span');
jQuery(".txt-anim p").empty();
typeWriter();
function typeWriter() {
if (i < span.length) {
jQuery('.txt-anim p').append(span[i]) ;
i++;
setTimeout(typeWriter, speed);
}else{
console.log("text animation ended");
jQuery('.home-page-cities-mobile').css("opacity", "1");
jQuery('.flecha-home').css("opacity", "1");
}
}
}
//Intersection Observer
const textAnim = document.querySelector('.txt-anim');
let options = {
root: textAnim,
rootMargin: "0px",
threshold: 1.0
};
let observer = new IntersectionObserver(txtAnim(20), options);
observer.observe(textAnim);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="logo-message txt-anim">
<p>
<span class="letter">L</span>
<span class="letter">i</span>
<span class="letter">q</span>
<span class="letter">u</span>
<span class="letter">i</span>
<span class="letter">d</span>
<span class="letter empty"> </span>
<span class="letter">i</span>
<span class="letter">d</span>
<span class="letter">e</span>
<span class="letter">a</span>
<span class="letter">s</span>
<span class="letter empty"> </span>
<span class="letter">t</span>
<span class="letter">o</span>
<span class="letter empty"> </span>
<span class="letter">g</span>
<span class="letter">e</span>
<span class="letter">t</span>
<span class="letter empty"> </span>
<span class="letter">a</span>
<span class="letter">n</span>
<span class="letter">y</span>
<span class="letter">w</span>
<span class="letter">h</span>
<span class="letter">e</span>
<span class="letter">r</span>
<span class="letter">e</span>
</p>
</div>
Can anyone help? I've tried with intersection observer beucase i thought it would be easier
OK, so there's a couple problems with the way you're using intersection observer here.
First, your root is wrong. You're using the same element for root as you're trying to observe, which means you're asking the question, When does this element intersect with itself? which is obviously not a very useful question.
If you want it to show when you've scrolled to it in the viewport, you actually don't need to specify a root at all.
Secondly, you're not checking the thing you really need to check: the isIntersecting property. When the intersection observer callback fires, the first argument is an array of entries, and each entry has an isIntersecting property which can be true or false.
I've made this jsfiddle that maybe comes close to doing what you want.
https://jsfiddle.net/xapjz2fe/
As you can see, i've also added in a check to make sure the function txtAnim doesn't run itself more than once -- without that check, if you scrolled to the element, then scrolled away and scrolled back, txtAnim would run a second time and it would mess everything up.
let txtAnimStarted = false;
function txtAnim(speed){
if (txtAnimStarted) return;
txtAnimStarted = true;
console.log('starting txtAnim');
jQuery('.txt-anim').css("opacity", "1");
var skills = jQuery('.txt-anim p').contents().filter(function() {
return this.nodeType === 3; // only immediate text in div, not in span
}).map(function() {
var txt = "<span class='letter'>" + jQuery(this).text().split("").join("</span><span class='letter'>") + "</span>";
// console.log(txt);
jQuery(this).replaceWith(txt);
});
var i = 0;
var span = jQuery('.txt-anim').find('span');
jQuery(".txt-anim p").empty();
typeWriter();
function typeWriter() {
if (i < span.length) {
jQuery('.txt-anim p').append(span[i]) ;
i++;
setTimeout(typeWriter, speed);
}else{
console.log("text animation ended");
jQuery('.home-page-cities-mobile').css("opacity", "1");
jQuery('.flecha-home').css("opacity", "1");
}
}
}
//Intersection Observer
const textAnim = document.querySelector('.txt-anim');
let options = {
rootMargin: "0px",
threshold: 1.0
};
let observer = new IntersectionObserver((entries) => {
const entry = entries[0];
if (entry.isIntersecting) txtAnim(20);
}, options);
observer.observe(textAnim);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div style="margin-bottom:1000px">top div, scroll down</div>
<div class="logo-message txt-anim" style="padding-bottom:20px">
<p>
<span class="letter">L</span>
<span class="letter">i</span>
<span class="letter">q</span>
<span class="letter">u</span>
<span class="letter">i</span>
<span class="letter">d</span>
<span class="letter empty"> </span>
<span class="letter">i</span>
<span class="letter">d</span>
<span class="letter">e</span>
<span class="letter">a</span>
<span class="letter">s</span>
<span class="letter empty"> </span>
<span class="letter">t</span>
<span class="letter">o</span>
<span class="letter empty"> </span>
<span class="letter">g</span>
<span class="letter">e</span>
<span class="letter">t</span>
<span class="letter empty"> </span>
<span class="letter">a</span>
<span class="letter">n</span>
<span class="letter">y</span>
<span class="letter">w</span>
<span class="letter">h</span>
<span class="letter">e</span>
<span class="letter">r</span>
<span class="letter">e</span>
</p>
</div>
My indentation of the javascript got ruined copying over from jsfiddle, sorry about that

javascript array from existing divs innerHTML

Let's say i create dynamically some divs, each has it's dynamically created id (div0, div1, div2, etc.) and i'd like with a function to pass through currently existent divs and put their innerHTML into an array (one, two, three in this case), how can i achieve this in javascript?
html example:
<div contenteditable="false" id="div0">
one
<span id="one-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div1">
two
<span id="two-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div2">
three
<span id="three-close">
<i class="material-icons">close</i>
</span>
</div>
You could also use spread syntax
const divsContents = [...document.querySelectorAll("div>a")].map(e=>e.innerHTML);
console.log(divsContents);
<div contenteditable="false" id="div0">
one
<span id="one-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div1">
two
<span id="two-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div2">
three
<span id="three-close">
<i class="material-icons">close</i>
</span>
</div>
Using some magic from here, because document.querySelectorAll returns a NodeList and not an array, we can get the div elements into an array and use .map() to return the div content into an array.
var divs = [].slice.call(document.querySelectorAll('div'));
console.log(divs.map(div => div.innerHTML));
<div contenteditable="false" id="div0">
one
<span id="one-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div1">
two
<span id="two-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div2">
three
<span id="three-close">
<i class="material-icons">close</i>
</span>
</div>
Ideally you should be using a selector like #divcontainer > div to fetch all the divs in the container, but if you know all the ID's, you can use a selector such as:
document.querySelectorAll('#div0, #div1, #div2')
you can use jquery or javascript function for get your div:
myArray[0] = document.getElementByID("div0").innerHtml;
myArray[1] = document.getElementByID("div1").innerHtml;
myArray[2] = document.getElementByID("div2").innerHtml;
Give same class to divs and access by $('.class-name') or add a container div and get your div array by $('#divId div').
Use a loop after creating a divs collection using querySelectorAll:
let divs = document.querySelectorAll('div');
let arr = [];
for (let i = 0; i < divs.length; i++) {
arr.push(divs[i].innerHTML);
}
console.log(arr);
<div>hi</div>
<div>hi2</div>
<div>hi3</div>
Here is your solution
var arr = [];
function myFunction() {
var anchors = document.getElementsByTagName("a");
for(var i = 0; i < anchors.length; i++){
arr.push(anchors[i].text);
}
}
console.log(arr);
So many ways of doing this. Yet an other way: using ES6 Array.from
let divsA = Array.from(document.querySelectorAll("[id^='div'] a"));
divsA.map(a => console.log(a.innerHTML));
<div contenteditable="false" id="div0">
one
<span id="one-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div1">
two
<span id="two-close">
<i class="material-icons">close</i>
</span>
</div>
<div contenteditable="false" id="div2">
three
<span id="three-close">
<i class="material-icons">close</i>
</span>
</div>

jQuery: Find specific number & replace with specific string

I am using a food menu in my website.
In my food menu you can select what you want with your meat.
For example:
shawarma: pita:10$ Baguette:12$ Plate: 17$
steak: pita: 20 Baguette:none Plate:35$
the problem is when some of the menu items dont have one of the pita or Baguette or Plate, the script will print zero "0"
steak: pita: 20 Baguette:0 Plate:35$
I am trying to Find all the 0 number and replace with -.
the problem is that in some items the price come with 0 like 10, 20, 30, etc...
here i don't want to change the "0" to "-"
I tried to use this code: (but this code not makes me what i need)
Any suggestions?
jQuery(document).ready(function($) {
$("div").find(":contains('0')").each(function(){
$this = $(this);
$this.text("-");
});
});
the html looks:
<div id="wppizza-article-tiers-118" class="wppizza-article-tiers">
<span id="wppizza-118-4-0" class="wppizza-article-price ">
<span>0 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Toast</div>
</span>
<span id="wppizza-118-4-1" class="wppizza-article-price ">
<span>20 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Big Pita</div>
</span>
<span id="wppizza-118-4-2" class="wppizza-article-price ">
<span>33 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Baguette</div>
</span>
<span id="wppizza-118-4-3" class="wppizza-article-price ">
<span>35 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">לאפה</div>
</span>
<span id="wppizza-118-4-4" class="wppizza-article-price ">
<span>49 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Plate</div>
</span>
</div>
A simple Regular Expression with .replace() will do that: /\b0\b/g
Working Code Snippet:
var myString = "steak: pita: 20 Baguette:0 Plate:35$";
myString = myString.replace("/\b0\b/g", "-");
alert(myString);
jsFiddle Demo
Readup: .replace() | MDN
EDIT:
Try this for your HTML code:
$('#wppizza-article-tiers-118 span span').each(function(){
var text = $(this).text();
text = text.replace(/\b0\b/g, "-");
$(this).text(text);
});
Regex Explained:
/\b0\b/g
Here,
/ is what your regex pattern is enclosed in.
\b means boundary.
0 is what you actually need to replace.
g means global. The replacement will be done globally.
Various Regex tutorials | Google
EDIT2: Here is the working code snippet for the HTML in your question.
$('.wppizza-article-price span').each(function(){
var text = $(this).text();
text = text.replace(/\b0\b/g, "-");
$(this).text(text);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="wppizza-article-tiers-118" class="wppizza-article-tiers">
<span id="wppizza-118-4-0" class="wppizza-article-price ">
<span>0 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Toast</div>
</span>
<span id="wppizza-118-4-1" class="wppizza-article-price ">
<span>20 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Big Pita</div>
</span>
<span id="wppizza-118-4-2" class="wppizza-article-price ">
<span>33 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Baguette</div>
</span>
<span id="wppizza-118-4-3" class="wppizza-article-price ">
<span>35 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">לאפה</div>
</span>
<span id="wppizza-118-4-4" class="wppizza-article-price ">
<span>49 $</span>
<div class="wppizza-article-price-lbl wppizza-no-cart">Plate</div>
</span>
</div>
jQuery(document).ready(function() {
$(".wppizza-article-price").each(function(){
var value = $(this).find('span').text();
if (value == '0 $') {
$(this).find('span').text('-');
}
});
});
it will work now DEMO

I have 2 price values and I want to calculate the difference

I have 2 price range. Regular Price and Offer Price displayed on screen with the below HTML rendered.
I am trying to calculate the price difference using Javascript/jquery and insert a div with the discount price.
<div class="price-box">
<p class="old-price">
<span class="price-label">Regular Price:</span>
<span class="price" id="old-price-221">Rs17.00 </span>
</p>
<p class="special-price">
<span class="price-label">Special Price</span>
<span class="price" id="product-price-221">Rs14.45 </span>
</p>
<div>
<span>Discount Price:</span>
<span>XXXXXX</span>
</div>
</div>
I have this currency symbol also displayed along with the price. So, I am wondering how to calculate the difference between "Regular Price" and "Special Price".
Can some one please help me?
P.S. I searched the site and did not find the relevant answers.
There it is : http://jsfiddle.net/s7w98ngt/2/
jQuery part :
$('.price-box').each(function(){
var element = $(this),
oldPriceRaw = element.find('.old-price .price').text(),
specialPriceRaw = element.find('.special-price .price').text(),
oldPrice = parseFloat(oldPriceRaw.replace('Rs','')),
specialPrice = parseFloat(specialPriceRaw.replace('Rs','')),
diff = 'Rs'+(oldPrice - specialPrice).toFixed(2), // 2 numbers after the comma
diffElement = element.find('.diff-price');
diffElement.text(diff);
});
HTML a bit modified :
<div class="price-box">
<p class="old-price">
<span class="price-label">Regular Price:</span>
<span class="price" id="old-price-221">Rs17.00 </span>
</p>
<p class="special-price">
<span class="price-label">Special Price</span>
<span class="price" id="product-price-221">Rs14.45 </span>
</p>
<div>
<span>Discount Price:</span>
<span class="diff-price"></span>
</div>
</div>
The tricky thing was to get the current prices value as float numbers. Then, we just calcul the difference.

Categories

Resources