jQuery select matching text within element - javascript

I need a way to pass an element id and a string to a function which selects any occurrences of that string in the element text, which is not already wrapped in a sub element.
I have an existing event handler which performs some actions, including wrapping the text in an element, when a user highlights a section of text. However some strings can be matched automatically without needing to be selected by a user, which is what I am trying to accomplish here. A different event will sometimes produce an element id and a string, and all instances of the string in the element text which are not all ready tagged, should be passed to the event handler as if a user had selected them.
Example:
var selectionString = 'word';
<p id="1">select this word, but not this <span>word</span>, but yes to this word.</p>
// My approach so far which may not be on the right track.
function selectStringInElement(selectionString, elementId){
var el = document.getElementById(elementId);
var selections = el.innerHTML.split(/<.+>/);
for(var i=0; i < selections.length; i++){
// if the selection contains the selectionString, find its range, select it, and pass to existing handler.
}
}
Any help would be much appreciated.

HTML nodes have children accessible by node.childNodes, and you can tell if a child node is text or an element by node.nodeType.
This example takes the search string and the element id to search, filters it so only the text nodes are left, and then does a replace to generate a new html string. You can't just jam a HTML string into a text node, so I used a temporary div to parse the html string into nodes, which are then injected into the element's parent before removing the source text node itself.
function selectStringInElement(selectionString, elementId, className) {
$("#" + elementId).contents()
.filter(function(i, el){
// only process text nodes
return el.nodeType == 3
}).each(function(i, el){
// create a div to process our html string with new tags
var fake = document.createElement('div');
fake.innerHTML = el.textContent.replace(
new RegExp(selectionString,'g'),
"<span class='" + className + "'>" + selectionString + "</span>"
);
// take all the nodes in our div and append them to the actual element's parent
$(fake.childNodes).each(function(i, child) {
el.parentNode.insertBefore(child, el);
});
// we've now duplicated our actual element with a number of new elements, we don't need to keep the original
el.parentNode.removeChild(el);
});
}
selectStringInElement('chowder','test', 'red');
selectStringInElement('ukulele','test', 'blue');
.red {
color: #f00;
}
.blue {
color: #00f;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='test'>
The bottle of shampoo just returned from a safari, that he left the tea and the church bazaar. She was a grand ran all around the veranda, laughing like, so helping their ranch on the cook make the church bazaar. She was playing a ukulele, which the admiral did not like, so he looked through his collection of the kindergarten cried, "At last! Hurrah!" -- and by accident spilled the ketchup all over the taffy apples! This so amused another chores at the bottle had just where he had found the taffy apples! This so amused another guest, who usually wore a gingham dress and moccasins when visiting their ranch on the cook make the church bazaar. She was no helping the chowder and kayak he had seen.
Someone was playing a ukulele, which the ketchup all over the goulash for lunch. the label.
The admiral heard them talking in the label.
The admiral heard them talking in the bank and firing his pistol out of his sack and ran all around the chowder and the goulash for lunch. the Nebraska prairie, had just returned from her chores at imaginary zombies. It was a grand party.d through his collection of shampoo just returned from a safari, that he pulled the ketchup all over the church bazaar. She was no helping the church bazaar. She was no helping the admiral hated to a pretty mazurka by Chopin. Then he looked through his sack and ran all around the kitchen.
Someone was a grand party.ard them talking in the admiral did not like a maniac and firing his pistol out of his so amused another guest, who usually wore a gingham dress and listened to a pretty mazurka by Chopin. The bottle had seen.
Someone was playing a ukulele, which the admiral heard them talking in the label.
The admiral's wife, who usually wore a gingham dress and moccasins when visiting their ranch on the label.
When everyone sat down to eat, the pulled a toy pistol out of his sack and ran all around through his collection of pictures -- next to the tea and listened to snoop, so he turned on the radio and the cook make the church bazaar. She was playing a ukulele, which the admiral did not like, so amused another guest, who had just returned on the Nebraska prairie, had just returned on the Nebraska prairie, had a picture of shampoo just returned from a safari, that he pulled the radio and listened from her chowder and the label.
</div>

Here is JQuery implementation:
JSFiddle
selectStringInElement('word','1');
function selectStringInElement(selectionString, elementId)
{
var $el = $("#"+elementId);
$el.contents()
.filter(function(){
return this.nodeType === 3 // Only text nodes
})
.each(function(){
this.textContent = this.textContent.replace(new RegExp(selectionString,'g'),"<span class='highlight'>"+selectionString+"</span>");
});
// Decoding lt/gt
var html = $("<textarea/>").html($("#1").html()).val()
// Replacing original content
$el.html(html);
// Attaching onMouseOver events
console.log($el.find("span.highlight"));
$el.find("span.highlight").on("mouseover",function(){
alert($(this).text());
});
}
Result: "word" is surrounded with span tag, and onMouseOver event attached to that tags.
HTML resulting code:
<p id="1">select this <span class="highlight">word</span>, but not this <span>word</span>, but yes to this <span class="highlight">word</span>.</p>

Related

How to get variable, array, nodelist from different function?

I am trying to write a ToDoList with JavaScript.
I have an input-element. Whenever I type something and press enter, it creates a new fieldset(in my example its a fieldset but it can also be a Div) with the class name ".fieldListClass" and a P-Tag as a child of fieldset. the P-tag innerHTML is the the value of input. I used Click-EventListener for that.
After each click, I assigned the query selector of all .fieldListClass to a nodeList "fieldListQuery". I even converted this nodeList into an Array but no result.
Now I want to create an addEventListner but outside the previous one. it should be a new one. And It should be a click-EventListener for all fieldListQuery which where created inside the previous function.(this part is at the bottom of my code)
When I click on it something should happen like removing the current target etc. But it wont work because outside the function it always says that this variable is undefined. I don't get it because I declared it global outside of the function.
I don't want to use DOMNodeInserted or MutationObserver yet for detecting changes inside the DOM. Simple because the first one is not recommended anymore it and the last one I have no idea how to use it. Many people saying that this is not a safe way.
Any Help please?
let addDiv = document.createElement("div"); addDiv.id = "addDivId";
let listDiv = document.createElement("div"); listDiv.id = "listDivId";
let inputText = document.createElement("input"); inputText.id = "inputTextId";
let fieldList; // = document.createElement("fieldset");
let fieldDiv; // = document.createElement("div");
let fieldDivP; // = document.createElement("P");
let fieldListArr;
let fieldListQuery;
document.body.appendChild(addDiv);
addDiv.appendChild(inputText);
document.body.appendChild(listDiv);
inputText.addEventListener("keypress", event => {
if (event.key === "Enter") {
fieldList = document.createElement("fieldset");
fieldDiv = document.createElement("div");
fieldDivP = document.createElement("P");
listDiv.appendChild(fieldList);
fieldList.className = "fieldListClass";
fieldList.appendChild(fieldDiv);
fieldDiv.appendChild(fieldDivP);
fieldDivP.innerHTML = inputText.value;
fieldListQuery = document.querySelectorAll(".fieldListClass") ;
}
})
fieldListQuery.forEach(element => { // <- it say fieldListQuery is undefined.
fieldListQuery.addEventListener("click", e => {
e.currentTarget.innerHTML="test";
})
});
ยดยดยด
Since I offered critique of your approach, I thought it is only fair I at least try to offer you some code that accomplishes (on the overall level, in light of absence of much detail about your solution) something along of what you have.
First off, I think creating trees of elements through a script when other solutions are more viable, tends to show an anti-pattern. Your script is invariably loaded in the context of an HTML document, which may already contain a lot of useful markup -- including an input field (that you were creating with createElement). If the input field is a "constant" there is no need to waste code on creating it -- just put it in your markup.
Second, even for elements or hierarchies of elements that are created "on demand" -- as a reaction to an event or however else -- it typically is much more readable and manageable to use templates. As a fallback -- if template cannot be used for some reason -- using innerHTML to create entire element trees is actually an appealing and more readable option than a lot of "boilerplate" containing createElement, appendChild, etc.
Third, you should always try to see if you can have your interactive controls be part of a form. I won't go into all reasons to do so, but suffice to say it helps user agents that screen-read content and for other accessibility systems, to name one. There are exceptions to this rule, but I don't recall looking at code where a control should not be part of a form -- so the rule is a good one.
Here is a proof-of-concept bare-bones to-do application:
<html>
<head>
<script>
function submit_create_todo_item_form() {
const new_todo_fragment = document.getElementById("todo-item-template").content.cloneNode(true);
new_todo_fragment.querySelector(".body").textContent = document.forms[0].elements[0].value;
document.body.appendChild(new_todo_fragment);
}
</script>
<template id="todo-item-template">
<div class="todo-item">
<p class="body"></p>
</div>
</template>
</head>
<body>
<form action="javascript: submit_create_todo_item_form()">
<input>
</form>
</body>
<html>
Take note that I use textContent instead of innerHTML to create content for a to-do item's body. innerHTML invokes the HTML parser and unless you plan to be typing hypertext into that single line of input field, innerHTML only costs you extra for no clear benefit. If you need to interpret the value verbatim, textContent is instead exactly what's needed. So, approach your solution with that in mind.
I hope this is useful, I worked with what I thought I had.

How to hide specific sentence on page load with Javascript

I need to hide a specific sentence "The leader of tomorrow, are great" from the content below without calling any class or div.
Example:
<p>The leader of tomorrow, are great leaders</p>
<br>
The leader of tomorrow, are great leaders of tomorrow.
Any solution to this would be appreciated.
Why does jQuery('*:contains("sentence to hide")') not work on this scenario?
It will match all elements that contain the sentence. So it will match the closest node but in addition all parent nodes all the way up. So instead of matching element eg in this case the "P-node" it will also match for body, html and even the script itself if it is inline because it also contains the sentence! And if the element that actual contains the sentence to hide is wrapped by lets say:
<div id="_1">
<div id="_2">
<div id="_3">
<p id="_4">sentence to hide</p>
</div>
</div>
</div>
Then with jQuery('*:contains("sentence to hide")') you will get a jQuery collection with [{html}, {body}, {#_1}, {#_2}, {#_3}, {#_4}]
Here is another approach...
jQuery( document ).ready(function(){
jQuery('body').html(function(iIndex, sHtml) {
return sHtml.replace(/The leader of tomorrow, are great/g, '');
});
});
Or to have more control you can wrap the sentence and style the wrapper as you like...
jQuery( document ).ready(function(){
var sNeedle = 'The leader of tomorrow, are great';
jQuery('body').html(function(iIndex, sHtml) {
return sHtml.replace(
new RegExp(sNeedle, 'g'),
'<span class="hide-specific-sentence">' + sNeedle + '</span>'
);
});
});
and then in your CSS:
.hide-specific-sentence{
display: none;
}
NOTES:
I would not recommend to treat web content like this but if you have to for whatever reason you can do it this way
narrow the selcetor to the closest possible parent that actual contains the sentence(s) maybe ".content" or whatever
make shure that you do this action before anything else because
attached event handlers could get lost (depending on the way they are bound)
If you try the code as SO snippet you will get an error. This is propably due to some internal restrictions (I guess). But I tried it local and it works like a charme or you can try with this working PEN that also works like a charme...
jQuery has the contains-method. Here's a snippet for you:
<script type="text/javascript">
$(function() {
var result = $('*:contains("The leader of tomorrow, are great")');
});
</script>
The selector above selects any element that contains the target string. The result will be a jQuery object that contains any matched element. See the API information at: http://docs.jquery.com/Selectors/contains#text
One thing to note with the '*' wildcard is that you'll get all elements, including your html and body elements, which you probably don't want. That's why most of the examples at jQuery and other places use $('p:contains("The leader of tomorrow, are great")')

Replace every instance of a word in the dom with Javascript

I am trying to parse with a chrome extension I am making, and replace ever instance of one word with another. This is what I have that is not working for me
function jamify() {
$("body").html().replace(/James/g,"Jamie");
}
The quick and rather dirty replacement of .html() has a couple of downsides.
it will actually replace the entire DOM structure, removing any event bindings if these are not bound 'live' on an element higher up the hierarchy
it will replace a lot more than you may expect. You should be safe with 'James' to 'Jamie', but it may get funky when 'em' wants to be named 'emmy' and suddenly certain italic texts get straightened out.
A better way is to replace only strings in actual text nodes, as jQuery is not (currently) a tag on the question, I assume vanilla javascript is a proper option.
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
while (walker.nextNode()) {
walker.currentNode.data = walker.currentNode.data.replace(/James/g, 'Jamie');
}
<!-- James -->
<div data-name="James" class="James">
James
</div>
This example will only touch the actual text element(s), the comment and both the attributes (data-name and class) will not get replaced, so it remains safe to have javascript and/or css referring to these.
If the words are in the textContent you can try:
var all = document.querySelectorAll('.test')
//using .test as a wrapper section, try using body in production as selector (in the snippets it breaks)
all.forEach(x => x.textContent = x.textContent.replace(/James/gi, "Jamie"))
// keep in mind forEach for nodes has limited support, tested in chrome
<div class="test">
<p>James is here</p>
<div >this div is James</div>
</div>
I am showing it this way to show that you have to call some function to reset the html to the newly replaced string
NOTE: This will destroy any DOM event you had attached before the replace
you can shorten this by nesting the call all into one if you wanted
function jamify() {
var str = $(".test").html();
console.log('jamify', str);
str2 = str.replace(/James/g,"Jamie");
$(".test").html(str2);
//to simplify it could be done this way too
//$(".test").html($(".test").html().replace(/James/g,"Jamie"))
}
$(document).ready(function(){
//alert('ready');
$('.inner').click(function(){console.log('inner click')})
//Yea!, my click event is all good.
jamify();
//Now all your inner click EVENT is broken so this is not good
//solution if there are any events attached in your DOM
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">
<p>James is here</p>
<div class="inner">this div is James</div>
</div>

jquery - javascript - project for fine art school

I am a (french) student in a fine art school, and I make a project about memory in this project, the computer needs to forget the text that you write...
I know html and css but I just began javascript...
I need to change or to erase randomly some letters (or some words) in a text area; like you write your text normally, and when you have written 5 lines of text, the first line begins to change: some letters change (A became F or M) or some are erased...and the text means nothing. But the writers musn't see the change, it has to be very discreet (with a slow change of opacity or something like that.)
Thank you for your help!
Welcome to SO! Your project sounds interesting. On SO we ask that your questions be specific. You have received a couple of down votes because your question is open ended. So to help you get started I will give you these thoughts.
You can't run animation effects in a textarea. You can certainly change the content, but doing so while a user is typing probably wouldn't work very well.
That being said, perhaps the user could type in a text area, but the actual content gets copied into a div somewhere else.
Animation effects need to take place on elements. To fade out individual letters you have to wrap them in a span.
This is actually pretty complex, so try and ask your questions piece by piece on SO. Start with your main question, and then provide a short paragraph about what you are trying to accomplish.
Here are some basics that will hopefully help you get started.
http://jsfiddle.net/6Btxb/
<div id="content"> </div><br />
<input type="text" /><br />
Key Pressed: <div id="keycode"></div>
<button>Fade out and change a letter</button>
$('input').keyup(function(e) {
//in javascript you are returned a keycode as opposed to the actual
//letter pressed. So to work with all languages
//I monitor keyup instead. But this means you have to clear the textarea as
//the user types
//show the keycode of the pressed key
$('#keycode').html(e.keyCode);
//append the typed letter to the content div
//do you need to handle delete / cut and paste?
$('#content').append('<span>' + $(this).val() + '</span>');
//clear textbox
$(this).val('');
});
$('button').click(function() {
//find how many spans are in the content div
var spanCount = $('#content span').length;
//get the index of a random letter
var randomNum = randomFromTo(0, spanCount);
//create a random letter
$('#content span').eq(randomNum).fadeOut(1000, function() {
//this is a callback to the fade out animation
//change the letters value
$(this).html(randomLetter());
//fade back in
$(this).fadeIn(1000);
});
});
//generate random number between
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function randomLetter() {
var letters = 'abcdefghijklmnopqrstuvwxyz';
return letters [Math.floor(Math.random()*letters .length)];
}

Applying DIV/Span tag to a word by specific co-ordinates

Sample HTML Data
<body style="width:300px;">
<h3>Long-Text</h3>
A simple tool to store and display texts longer than a few lines.
The search button will highlight all the words matching the name of objects that are members of the classes listed in searchedClasses, itself a member of the KeySet class. The highlighted words are hypertext.
Edit invokes wscripts/acedb.editor, which by default launches emacs. Edit that file to start another editor in its place.
Save will recover from the emacs but will not destroy it.
Read will read a text file, so you could Search it.
**general** grep is a way to annotate a set of longtexts versus the searchedClasses. It outputs an ace file that you can then hand check and read back in acedb to create XREF from longTexts to genes etc.
<h3>World Wide NotePad</h3>
World wide notepad is a small text editor similar to Microsoft's notepad but has some more useful features like an auto typer to make typing the same sentence or word more easy, also World Wide NotePad has a text to speech feature which reads all text in the current open document and speaks it out load to you.
<h3>Etelka Wide Text Pro Bold Italic</h3>
</body>
For example -> "general" (between ** ) is at x=0 and y=465. I know the x,y position. But How to highlight a word located at specific location ?
Let me explain once again. I want to highlight a word by location.
for example I have a location value (x,y)=(0,625). I want to extract the first word by that location ( assume - at that location - we have word "World" ) Then how to highlight that word ?
Edit :
Here Y co-ordinate is absolute position of entire html document.
The only method I can think of involves wrapping every word in a span element, and then using document.elementFromPoint(x,y) to get the span element at the given location. Something like this:
function highlightWordAtXY(x, y) {
// Get the element containing the text
var par = document.elementFromPoint(x, y),
// textContent or innerText ?
t = "textContent" in par ? "textContent" : "innerText",
// Get the text of the element. No pun intended on the par[t].
text = par[t],
result;
// Wrap a span around every word
par.innerHTML = text.replace(/\b(\w+)\b/g, "<span>$1</span>");
// Get the elementFromPoint again, should be a span this time
result = document.elementFromPoint(x, y);
// Check that we actually clicked on a word
if (result == par)
return false;
// Wrap HTML text around the text at x, y
result[t] = '<span class="highlight">' + result[t] + '</span>';
// Restore the content with the wrapped text
par.innerHTML = par[t];
}
Example at http://jsfiddle.net/BSHYp/1/show/light/ - click a word and watch it highlight.
Some important caveats here:
Each block of text must be wrapped in an element (such as <p> or <div>). You should be wrapping paragraphs in <p> tags anyway,
The element at the given location (x, y) must only have text in it, no child HTML elements. Text nodes with sibling HTML elements will have them removed (e.g. Clicking "Some" or "here" in Some <b>text</b> here will remove the <b> tags). Dividing them into separate <span> elements would be the only solution without building a much more complex routine,
IE will throw an "Unknown runtime error" if you try and add a block level element to a <p> tag,
On very, very, very large blocks of text you might run into performance issues. Break them up where applicable.

Categories

Resources