How do I get the text before the character clicked on? - javascript

I want to get the text before the character '/' from the string. It is repeating itself many times in my string and hence not able to get its index. So, how do I get the text before it? For example string "C:/users/username/desktop/lib" and if I click on the slash before username it should show "C:/users".
I have already tried indexOf() and CharAt() functions but in vain as they aren't appropriate to the problem.
Please try to provide the answer in JavaScript and not JQuery.

I think you want to do something like this...
let el = document.querySelector('p'),
text = el['innerText'],
characters = text.split('');
el.innerHTML = '';
characters.forEach(char => {
let span = document.createElement('span');
span.innerText = char;
span.addEventListener('click', onClick);
el.appendChild(span);
});
function onClick() {
let position = 0,
el = this;
while (el.previousSibling !== null) {
position++;
el = el.previousSibling;
}
let newText = text.substring(0, position);
console.log(newText.substring(0, newText.lastIndexOf('/')));
}
<p>C:/users/username/desktop/lib</p>

I tried do something like this
<div class="display">
</div>
const display = document.querySelector(".display");
const begin = "C:/users/username/desktop/lib"
display.textContent= begin
display.addEventListener("click", getSelectionPosition, false);
let bool = false
function getSelectionPosition () {
if(bool == false){
var selection = window.getSelection();
const index = selection.focusOffset;
console.log(display.textContent[index]);
if(display.textContent[index] == "/"){
display.textContent = display.textContent.substring(0, index);
}
else if(display.textContent[index-1] == "/"){
display.textContent = display.textContent.substring(0, index-1);
}
else if(display.textContent[index+1] == "/"){
display.textContent = display.textContent.substring(0, index+1);
}
}
else{
display.textContent = begin;
}
bool = !bool;
}

Related

How can I write a code in JS that deletes the existing output by disappearing it letter by letter, than writing the another one

Here's the code i've written, where when i write a word into e search field, it appends it in the element "word" by displaying it letter by letter. But, the problem is, that i don't know how to write the code that when i write another word in the search field, it deletes the word that appear to element "Word", then writes the new one i've written.
let text = document.getElementById("txt");
let elem = document.getElementsByClassName("target")[0];
let word = elem.querySelector(".word");
let btn = document.getElementsByClassName("btn")[0];
let error = document.querySelector('#error');
i = 0;
word.style.color = "#ffe100";
btn.addEventListener("click", function init() {
if (text.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
let save = word.textContent += text.value.charAt(i);
i++;
}
if (i < text.value.length) {
window.setTimeout(init, 100);
}
});
I've try many of alternatives, but there's no result.
I will iteratively change and/or improve your code in this answer, and will try to comment on each change in the code.
Refactor
First off, I'll have an easier time explaining the different approaches after refactoring your code:
// Prefer `const` over `let` when applicable
const input = document.querySelector("input"); // Previously: text
const output = document.querySelector("output"); // Previously: word
const button = document.querySelector("button"); // Previously: btn
const error = document.getElementById("error");
// You forgot `let`. Always declare your variables!
let i = 0;
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
reveal();
}
});
// Moved code from listener to here
function reveal() {
// Removed `save`; was unused
output.textContent += input.value[i];
++i;
// Moved here because it only runs if `input.value !== ""` anyways
if (i < input.value.length) {
setTimeout(reveal, 100);
}
}
/* Prefer to use CSS for static styles! */
.word {
color: #ffe100;
}
<!-- I assume your HTML to have looked similar to this: -->
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Now let's take a look at your refactored code from above:
There is no resetting: Revealing can only continue (text can only be added).
The value of input is referenced directly: When its value changes...
Then revealing may stop prematurely.
Then the further revealed text may not represent the entered text upon button-press.
Allow reusability
The issue of point 1 can be solved by (re-)setting i and output.textContent in the listener. To solve point 2, we need to use some buffer for the text:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
let i = 0;
let text = ""; // The buffer
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// (Re-)setting
i = 0;
text = input.value;
output.textContent = "";
reveal();
}
});
function reveal() {
output.textContent += text[i];
++i;
if (i < text.length) {
setTimeout(reveal, 100);
}
}
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
With these two small changes, your code now successfully deletes the revealed text in place for new text!
Adding states
But the deletion doesn't happen letter-by-letter. That would require some way to keep track of whether we are deleting or revealing.
Let's use a state-machine that –upon prompting– deletes the already revealed text (if any) and then reveals the new text:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
let i = 0;
let text = "";
let state = "nothing"; // The state
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// Remember previous state (for below)
const previousState = state;
// Issue deletion and update text-to-reveal
state = "delete";
text = input.value;
if (previousState === "nothing") {
// Start state-machine
nextCharacter();
}
}
});
// Rename function
function nextCharacter() {
if (state === "nothing") return;
if (state === "delete") {
output.textContent = output.textContent.slice(0, i);
// Finished deleting?
if (i === 0) {
const requiresRevealing = i < text.length;
state = requiresRevealing ? "reveal" : "nothing";
} else {
--i;
}
} else if (state === "reveal") {
output.textContent += text[i];
++i
// Finished revealing?
if (i === text.length) {
state = "nothing";
}
}
// Requires continuing?
if (state !== "nothing") {
setTimeout(nextCharacter, 100);
}
}
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Code quality refactoring
The code now works, but(!) the logic is scattered everywhere, and you need to know what variables to update for the revealing to work correctly. Instead, we could make use of classes:
const input = document.querySelector("input");
const output = document.querySelector("output");
const button = document.querySelector("button");
const error = document.getElementById("error");
// Move related variables and functions into class
class Revealer {
output;
index = 0;
text = "";
state = "nothing";
constructor(output) {
this.output = output;
}
// Moved from listener
reveal(text) {
const previousState = this.state;
this.state = "delete";
this.text = text;
if (previousState === "nothing") {
this.next();
}
}
// Previously nextCharacter()
next() {
if (this.state === "nothing") return;
if (this.state === "delete") {
this.deleteCharacter();
} else if (this.state === "reveal") {
this.revealCharacter();
}
if (this.state !== "nothing") {
setTimeout(() => this.next(), 100);
}
}
// Use more specific functions for readability
deleteCharacter() {
this.output.textContent = this.output.textContent.slice(0, this.index);
if (this.index === 0) {
const requiresRevealing = this.index < this.text.length;
this.state = requiresRevealing ? "reveal" : "nothing";
} else {
--this.index;
}
}
revealCharacter() {
this.output.textContent += this.text[this.index];
++this.index;
if (this.index === this.text.length) {
this.state = "nothing";
}
}
}
const revealer = new Revealer(output);
button.addEventListener("click", () => {
if (input.value == "") {
error.style.opacity = '1.0';
} else {
error.style.opacity = '0.0';
// Use simple call
revealer.reveal(input.value);
}
});
.word {
color: #ffe100;
}
<input><button>Submit</button>
<div>
<output></output>
</div>
<p id="error">Please submit (actual) text.</p>
Ideas for practicing
While the above code works, there are still ways to improve it or implement it differently:
Only delete until the remaining text is the same as the leading substring of the text-to-add.
You can use promises (with async/await) instead of using setTimeout() directly.
You can implement the revealing as functionality of a custom element.
...
Try implementing (one of) these as practice!

Want to Filter a list of items(li) for a partial match rather than exact(phrase) match using JavaScript

consider this following function:
//This piece of code works for exact word match but I want something more of a partial match.
function filterItems(ev) {
let text = ev.target.value.toLowerCase();
let items = item_list.getElmentsByTagName("li");
Array.from(items).forEach(item => {
let item_name = item.firstChild.textContent;
if(item_name.toLowerCase().indexOf(text) !== -1) {
item.style.display = "block";
} else {
item.style.display = "none";
}
})
}
Here is what I'm trying to do instead, something like using filter on search phrase itself
function filterItems(ev) {
let text = ev.target.value.toLowerCase();
let items = item_list.getElmentsByTagName("li");
Array.from(items).forEach(item => {
let item_name = item.firstChild.textContent;
if(item_name.toLowerCase().split("").filter(ch => ch.indexOf(text) !== -1)) {
item.style.display = "block";
} else {
item.style.display = "none";
}
})
}
Is it anywhere near what I'm trying to do, it doesn't seem to be responding to this piece of code on browser!!
you can leverage regular expression here with i flag which will ignore case, to use dynamic regular rexpression here we are using constructor.
function filterItems(ev) {
const regExp = new RegExp(`[${ev.target.value}]`, 'i');
let items = item_list.getElmentsByTagName("li");
Array.from(items).forEach(item => {
let item_name = item.firstChild.textContent;
if(regExp.test(item_name)) {
item.style.display = "block";
} else {
item.style.display = "none";
}
})
}
if you want to display all items when search box is empty then it should be
function filterItems(ev) {
// using regex
let regExp = new RegExp(`[${ev.target.value}]`, 'i');
// converting text to lowerCase
// let text = ev.target.value.toLowerCase();
// get ul li elemnts
let items = item_list.getElementsByTagName("li");
// Convert it to an array
Array.from(items).forEach(item => {
let item_name = item.firstChild.textContent;
if (!ev.target.value || regExp.test(item_name)) {
item.style.display = "inline-block";
} else {
item.style.display = "none";
}
});
}
From your last comment, I believe this achieves the behaviour you are after.
My this.target is your text variable. You could call the following doesFilterMatchTarget() inside your forEach item loop.
I have added inline comments for further explanation.
function doesFilterMatchTarget(filter) {
filter = filter.toLowerCase();
if (filter.length === -1) { //empty state should not match
return false;
}
let currentTarget = this.target; //initialise to your starting target e.g. 'list 01'
for (let i = 0; i < filter.length; i++) {
//call our helper function below to check and update our target word
currentTarget = this.updateTargetWord(currentTarget, filter[i]);
if (currentTarget === null) {
return false;
}
}
return true; //we have looped through every letter and they were all found in a correct location
}
function updateTargetWord(currentTarget, letterToSearchFor) {
let index = currentTarget.search(letterToSearchFor);
if (index === -1) {
return null; //the letter was not in the target
}
// then we want to return the remainder of this target string from the next character to the end of the string
return currentTarget.substring(index + 1, currentTarget.length);
}
Note: this may be able to be optimised, but it seems to return what you are looking for.

Highlight text fragments selected by user without nesting tags

I have a <div>some test phrase<div> and I need to allow user to select different fragments of text and highlight them in different colors. Also I need to allow user to delete the the highlighting (but keep the text).
I use Angular, but the solution might be in pure JS.
I've got the partial solution received in response to my previous question:
function mark() {
var rng = document.getSelection().getRangeAt(0);
var cnt = rng.extractContents();
var node = document.createElement('MARK');
node.style.backgroundColor = "orange";
node.appendChild(cnt);
rng.insertNode(node);
}
document.addEventListener('keyup', mark);
document.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if (tgt.tagName === 'MARK') {
if (e.ctrlKey) {
var txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
}
document.addEventListener('click', unmark);
::selection {
background: orange;
}
<p>some test phrase</p>
However, if user selects some test and test phrase after that, the selections will intersect and the mark tags will be nesting, while I need them to be like this: <mark>some</mark><mark>test phrase</mark>.
So, the general rule is: the last selection always wins, i.e. its color is always on top. How could I achieve this for any number of selections done?
Also deletion seems not to be working from time to time and I don't know why.
UPDATE:
Kind of implemented this, but I won't be surprised if there is a better way to do this.
Here is the code
I think you can start with this. You should thouroughly test it if it satisfy your case. Perhaps you should also refactor it to better fit you needs.
function mark() {
let selection = document.getSelection();
if(selection.type !== 'Range') { return;}
let pos = window.placeOfSelections;
let ranges = [];
let start = 0;
Array.prototype.forEach.call(pos.childNodes, function(chD)
{
ranges.push([start, start + chD.textContent.length, chD.nodeName === 'MARK']);
start += chD.textContent.length;
});
let text = pos.textContent;
let range = selection.getRangeAt(0);
let firstNode = range.startContainer;
let lastNode = range.endContainer;
selection.removeAllRanges();
let firstNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === firstNode || node.firstChild === firstNode);
let lastNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === lastNode || node.firstChild === lastNode);
let newSelectionStart = ranges[firstNodeIndex][0] + range.startOffset;
let newSelectionEnd = ranges[lastNodeIndex][0] + range.endOffset;
pos.innerHTML = text;
range.setStart(pos.childNodes[0], newSelectionStart);
range.setEnd(pos.childNodes[0], newSelectionEnd);
let node = document.createElement('MARK');
let cnt = range.extractContents();
node.appendChild(cnt);
range.insertNode(node);
let marks = ranges.filter(r => r[2]);
while(marks.length != 0)
{
let startEnd = marks.shift();
if(startEnd[0]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
continue;
}
if(startEnd[0]>= newSelectionStart && startEnd[0] <= newSelectionEnd)
{
startEnd[0] = newSelectionEnd;
}
else
if(startEnd[1]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
startEnd[1] = newSelectionStart;
}
else
if(startEnd[0] <=newSelectionStart && startEnd[1] >= newSelectionEnd)
{
marks.push([newSelectionEnd, startEnd[1]]);
startEnd[1] = newSelectionStart;
}
let tnStart = 0, tnEnd = 0;
let textNode = Array.prototype.find.call(pos.childNodes, function(tn)
{
tnEnd += tn.textContent.length;
if(tnStart <= startEnd[0] && startEnd[1] <= tnEnd )
{
return true;
}
tnStart += tn.textContent.length ;
});
range.setStart(textNode, startEnd[0] - tnStart);
range.setEnd(textNode, startEnd[1] - tnStart);
node = document.createElement('MARK');
node.appendChild(range.extractContents());
range.insertNode(node);
}
}
window.placeOfSelections.addEventListener('keyup', mark);
window.placeOfSelections.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if ((tgt.tagName === 'MARK' || (e.parentNode && e.parentNode.tagName === "MARK")) && e.ctrlKey) {
let txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
window.placeOfSelections.addEventListener('mousedown', unmark);
mark {background-color: #BCE937 ;}
<p id="placeOfSelections">some test phrase</p>

Prefix/Suffix highlighted text in textbox

I have to make a small javascript function that adds a prefix and suffix to a selected text within a textbox.
This is what I have so far:
function AddTags(name, prefix, suffix) {
try
{
var textArea = document.getElementById(name).value;
var i = 0;
var textArray = textArea.split("\n");
if (textArray == null) {
document.getElementById(name).value += prefix + suffix
}
else {
for (i = 0; i < textArray.length; i++) {
textArray[i] = prefix + textArray[i] + suffix;
}
document.getElementById(name).value = textArray.join("\n");
}
}
catch (err) { }
}
Now this function adds the provided prefix and suffix to every line, but I need to find out how to break up my textbox's text in Text before selection, Selected text and Text after selection.
Anybody any experience on this?
EDIT:
TriniBoy's function set me on the right track. I didn't need the whole suggestion.
This is the edited version of my original code:
function AddTags(name, prefix, suffix) {
try
{
var textArea = document.getElementById(name);
var i = 0;
var selStart = textArea.selectionStart;
var selEnd = textArea.selectionEnd;
var textbefore = textArea.value.substring(0, selStart);
var selected = textArea.value.substring(selStart, selEnd);
var textAfter = textArea.value.substring(selEnd);
if (textAfter == "") {
document.getElementById(name).value += prefix + suffix
}
else {
document.getElementById(name).value = textbefore + prefix + selected + suffix + textAfter;
}
}
catch (err) { }
}
Thx TriniBoy, I'll mark your leg-up as answer.
Based on your demo and your explanation, hopefully I got your requirements correct.
See code comments for a break down.
See demo fiddle here
var PreSuffApp = PreSuffApp || {
selText: "",
selStart: 0,
selEnd: 0,
getSelectedText: function (id) {
var text = "",
docSel = document.selection, //For IE
winSel = window.getSelection,
P = PreSuffApp,
textArea = document.getElementById(id);
if (typeof winSel !== "undefined") {
text = winSel().toString(); //Grab the current selected text
if (typeof docSel !== "undefined" && docSel.type === "Text") {
text = docSel.createRange().text; //Grab the current selected text
}
}
P.selStart = textArea.selectionStart; //Get the start of the selection range
P.selEnd = textArea.selectionEnd; //Get the end of the selection range
P.selText = text; //Set the value of the current selected text
},
addTags: function (id, prefix, suffix) {
try {
var textArea = document.getElementById(id),
P = PreSuffApp,
range = P.selEnd - P.selStart; //Used to calculate the lenght of the selection
//Check to see if some valuable text is selected
if (P.selText.trim() !== "") {
textArea.value = textArea.value.splice(P.selStart, range, prefix + P.selText + suffix); //Call the splice method on your text area value
} else {
alert("You've selected a bunch of nothingness");
}
} catch (err) {}
}
};
//Extend the string obj to splice the string from a start character index to an end range, like an array.
String.prototype.splice = function (index, rem, s) {
return (this.slice(0, index) + s + this.slice(index + Math.abs(rem)));
};

Basic JS + WebDev help

I have a little Problem. I have seven <select>'s. The go from left to right counting up.
<select id="sel_1" onchange="evalonsubmit('sel_1',1);">
<select id="sel_2" onchange="evalonsubmit('sel_2',2);">
That goes from 1 to 7 in this way.
The logic is easy. On click check if the value is -1 if it is disable everything on the right and set it to -1. if it is not -1 then enable the the right of the clicked one (+1 so to say)
And that's the code:
function evalonsubmit(ID, n)
{
var ElementID = document.getElementById(ID);
if(ElementID.value = -1) {
for (var i = n + 1; i <= 7; i++){
var newID = "sel_" + i;
var newValue = document.getElementById();
newValue.disable = true;
newValue.value = -1
}
} else {
var newID = "sel_"+(n+1)
var newValue = document.getElementById();
newValue.disable = false;
}
}
Can somebody kind JS hacker help me?
I just fixed some simple mistakes in your code ..
function evalonsubmit(ID, n)
{
var ElementID = document.getElementById(ID);
if (ElementID.value == -1){
for (var i=n+1; i <= 7; i++){
var newID = "sel_" + i;
var newValue = document.getElementById(newID);
newValue.disable = true;
newValue.value = -1
}
} else {
var newID = "sel_"+(n+1)
var newValue = document.getElementById(newID);
newValue.disable = false;
}
}
Not quite sure what you want, but it probably should be:
if (ElementID.value == -1){
// ^--- two = , otherwise you assign the value
and
var newID = "sel_" + i;
var newValue = document.getElementById(newID);
// pass parameter ---^
Same in the else branch.
Besides that, I would give your variables more meaningful names. E.g. ElementID lets you assume that the value is an ID. But it is not. It is a DOM element. Same for newValue.
You're missing the parameter in a couple of your calls to document.getElementById, and the property for disabling a <select> is disabled, not disable. You also have = where you need ==.
function evalonsubmit(ID, n)
{
var ElementID = document.getElementById(ID);
if (ElementID.value == -1){
for (var i=n+1; i <= 7; i++){
var newID = "sel_" + i;
var newValue = document.getElementById(newID);
newValue.disabled = true;
newValue.value = -1;
}
} else {
var newID = "sel_"+(n+1);
var newValue = document.getElementById(newID);
newValue.disabled = false;
}
}
Why not do this:
HTML:
<div class="wrapper">
<select onchange="evalonsubmit(this);" />
<select onchange="evalonsubmit(this);" />
<select onchange="evalonsubmit(this);" />
</div>
JS:
function nextElement(current)
{
do
current = current.nextSibling;
while (current && current.nodeType != 1);
return current;
}
function evalonsubmit(elem)
{
if(elem.value == -1)
while(elem = nextElement(elem)) {
elem.disabled = true;
elem.value = -1
}
else if(elem = nextElement(elem))
elem.disabled = false;
}
That removes the need for ids on the <select> elements, as following elements can be grabbed with .nextSibling. The nextElement() function is to avoid grabbing text nodes.

Categories

Resources