I am getting the content of a div from my page and storing it to the local storage (the content of the div are variable, dependent of the user input)
var content= document.getElementById('tobestoreddiv').innerHTML;//get the content
localStorage.setItem("content", content);// store the content
I want to remove the function from some of the divs inside the content div. Some of them have onclick events and so on..
I have tried using .replace() to remove some of the tags and functions as follows:
var changedinnerhtml = localStorage.getItem("content");
changedinnerhtml = changedinnerhtml.replace('autoplay="" loop=""', 'autoplay loop muted');//for videos, idk why the tags are set to =""
changedinnerhtml = changedinnerhtml.replace('contenteditable="true"', ''); // as an example for tags
changedinnerhtml = changedinnerhtml.replace('onclick="function();"', ''); // as an example for functions
document.body.innerHTML = changedinnerhtml; // this is displaying on another page
But this method isn't working is there another way to remove the functions and tags?
try it:
function replaceAll(source, search, replace, ignoreCase) {
//SCAPE SPECIAL CHARACTERES.
var search1 = this.regExpEscapeSpecialCharacters(search);
//IGNORE CASE SENSIVITY.
var ignore = (ignoreCase) ? "gi" : "g";
var result = source.replace(new RegExp(search1, ignore), replace);
return result;
}
var changedinnerhtml = localStorage.getItem("content");
changedinnerhtml = replaceAll(changedinnerhtml, 'autoplay="" loop=""', 'autoplay loop muted', true);//for videos, idk why the tags are set to =""
changedinnerhtml = replaceAll(changedinnerhtml, 'contenteditable="true"', '', true); // as an example for tags
changedinnerhtml = replaceAll(changedinnerhtml, 'onclick="function();"', '', true); // as an example for functions
document.body.innerHTML = changedinnerhtml; // this is displaying on another page
I would be more defensive about which attributes you do want to have on your element. Create a list of attributes, note that empty values (muted="") are the same as having no values at all muted, so don't worry about removing those.
const localStorageResult = `<div class='some-class' contenteditable="true" autoplay="" loop="" muted="" some-other-attribute onclick='function() { alert("Click"); }'>Hello world</div>`;
const copyAttributes = [
'class',
'autoplay',
'loop',
'muted'
];
// Create a dummy element
var containerElement = document.createElement( 'div' );
// This parses the element, so it becomes valid HTML
containerElement.innerHTML = localStorageResult;
const storedElement = containerElement.firstElementChild;
const elementType = storedElement.tagName;
const newElement = document.createElement(elementType);
copyAttributes
.filter((attribute) => storedElement.hasAttribute(attribute))
.forEach((attribute) => {
newElement.setAttribute(attribute, storedElement.getAttribute(attribute));
});
document.body.appendChild(newElement);
.some-class {
background: red;
width: 100px;
height: 100px;
}
Related
$('#txtInput').keyup(function(){
var txtInput = $(this).val();
localStorage.setItem('inputData', txtInput);
var returnData = localStorage.getItem('inputData');
$('#txtInput').val(returnData);
var hasTest = returnData.includes("<h1>");
if(hasTest == true){
}
});
I'm creating a text editor using js.Here I'm using localStorage to store data and retrieve data.I need to add highlighting syntax feature.
For example : If 'h1' found from the text, color the 'h1' to red.I used ".includes() and it finds the word but I have no idea how to change the color of the found text.I really appreciate your help
Try this solution:
1. Use a contenteditable element instead of input or textarea.
<div id="txtInput" contenteditable></div>
The reason is we need to display HTML with CSS inside of this input area.
2. Filter the result with highlight text.
// get highlight text from string
const highlighten = (string, highlight) => {
string = stripHtml(string);
// add highlight
if (string.includes(highlight)) {
string = string.replaceAll(highlight, `<span style="color:red">${highlight}</span>`);
}
return string;
};
Use replaceAll to set the highlight CSS. You can see the stripHTML() which is for cleaning the string before doing the filter.
3. Handle keyup event
// on keyup event
$('#txtInput').on('keyup', function(e) {
const $input = $(e.currentTarget);
// you can manage your localStorage data here
// ...
// filter the input
$input.html(highlighten($input.html(), 'h1'));
// set caret
setCaretAtTheEnd($input.get());
});
As we replace the #txtInput with the new result, we will lose the caret position, that's why we need setCaretAtTheEnd() to set the caret back to the end of input.
// https://stackoverflow.com/questions/822452/strip-html-from-text-javascript
function stripHtml(html) {
let tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
}
// https://stackoverflow.com/questions/6249095/how-to-set-the-caret-cursor-position-in-a-contenteditable-element-div
function setCaretAtTheEnd(el) {
var range = document.createRange()
var sel = window.getSelection()
const nodes = el[0].childNodes;
let offset = 0;
nodes.forEach((node, i) => {
offset = node.length || 1;
});
range.setStart(nodes[nodes.length - 1], offset)
range.collapse(true)
sel.removeAllRanges()
sel.addRange(range)
}
// get highlight text from string
const highlighten = (string, highlight) => {
string = stripHtml(string);
// add highlight
if (string.includes(highlight)) {
string = string.replaceAll(highlight, `<span style="color:red">${highlight}</span>`);
}
return string;
};
// on keyup event
$('#txtInput').on('keyup', function(e) {
const $input = $(e.currentTarget);
// you can manage your localStorage data here
// ...
// filter the input
$input.html(highlighten($input.html(), 'h1'));
// set caret
setCaretAtTheEnd($input.get());
});
#txtInput {
width: 400px;
height: 200px;
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="txtInput" contenteditable>
</div>
This should do it for you:
if (hasTest == true) {
returnData = returnData.replace('<h1>', '<h1 style="color:red">')
$('#txtInput').val(returnData);
}
I'd like to use pure JS to check if some String, the textareas .innerHTML = newContent below, contains some tag (h1in my case) at the beginning (=as first child). What would be the best way to do this?
Thanks!
function submitNewSectionContent(e) {
for (var i = 0; i < sections.length; i++)
let newHeading = document.getElementById('edit-title').value;
/* edit-title is text-input*/
let newContent = document.getElementById('edit-sectionText').innerHTML;
/* edit-sectionText is textarea */
if (newContent.indexOf('<h1>') > -1 && newContent.indexOf('<h1>') < 10) { /* <h1> is at beginning so replace with newHeading */
let toberemoved = newContent.match('<h1>.*<\/h1>');
newContent = newContent.replace(toberemoved[0], '').trim();
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + sections[i].innerHTML;
} elseĀ { /* newContent has no h1 as first child, so add h1 from newHeading */
sections[i].innerHTML = '<h1>'+newHeading+'</h1>' + newContent;
}
}
}
Problem with Regular expressions is they do not really work well with HTML. So Your best bet is to convert it to a DOM fragment and do the manipulations and convert it back. Only issue with this method really is you can lose formatting. There are libraries out there that can pretty print HTML.
function updateHeadline(txt) {
const ta = document.querySelector("textarea");
const data = ta.value; // read value, not innerHTML
const temp = document.createElement('div'); // temp div to hold html
temp.innerHTML = data; // set the html to the temp element
let firstChild = temp.firstElementChild // look at the dom
if (!firstChild || firstChild.tagName!=="H1") { // see if we have an h1
firstChild = document.createElement("h1") // if not create one
temp.prepend(firstChild) // add it to the front
}
firstChild.innerHTML = txt // set the new text of the h1
ta.value = temp.innerHTML // put the content back into the textarea
}
const btn = document.querySelector("button");
btn.addEventListener("click", function (e) {
e.preventDefault()
updateHeadline(document.querySelector("#text").value)
})
textarea {
width: 300px;
height: 200px;
}
<textarea>
<p>Some other text</p>
<p>Some more text</p>
</textarea>
<input value="foo" id="text"/>
<button>Set</button>
You could use regex like so, (updated based on comment)
if( /^\s*<h1>/gi.test(stringToTest) ) {
//logic here
}
It checks if the stringToTest begins with ^ tag
See here : https://regex101.com/r/vSo4sL/1
convert to dom, and parse dom
this portion of code makes it possible to treat a chain to retrieve titles placed in the H1 tag and (on the fly) treat the string of characters.
It's easily expandable for future processing : or tag processing or other ...!
commented code
<html>
<script>
var s="<H1>Hey Title</H1>\n Hello,\n other title <H1>Green!</H1>\n Ipsum dolore sit...";
console.log(s);
console.log("-------------------------");
var partialDoc = document.createElement( 'html' );
partialDoc.innerHTML = s;
var parsed='';
var titles=[];
treatment(partialDoc);
console.log("\n-------------------------\n");
console.log("parsed",parsed);
console.log("\n-------------------------\n");
console.log("s var contains "+titles.length+ " H1 tag");
console.log("titles "+titles);
function treatment(root) {
var child = root.firstChild;
while (child) {
// child.nodeName = H1 | H2 | P etc...
// child.nodeType = 1
// catch H1
if (child.nodeName=='H1') {
// append your title,
parsed+=" [H1 FOUND content= {"+child.innerText+"} H1])";
// or
// parsed+="<H1>"+child.innerText+"<H1>";
// add your own process here
// add this title in array
// or what you want...
titles.push(child.innerText);
// next part of document
child = child.nextSibling;
continue;
}
// capture other text than H1
if (child.nodeType==3) { // Node Type Text
parsed+=child.nodeValue;
}
if (child.nodeType==1) { // Node Type ELEMENT, : sub nodes...
treatment(child);
}
// continue the rest of doc
child = child.nextSibling;
}
}
</script>
</html>
One way you could do it is: Node.firstElementChild which will avoid giving child node as #text for white-spaces and Node.nodeName
let firstChild = document.getElementById('edit-sectionText').firstElementChild;
if(firstChild.nodeName === "H1"){
firstChild.innerHTML = "Replacement Value"
}
Note & Update: The earlier api that I had suggested Node.firstChild will not prevent white-spaces which gives #text node and comments as #comment node.
2nd Way: Node.children and picking the first child out of it should have a similar result to Node.firstElementChild.
let elem = document.getElementById('edit-sectionText');
if(elem){
let firstChild = elem.children[0];
}
Update based on comments: Using Dom Parser Interface
The interface allows to parse XML or HTML source from a string based on the mime type provided for its method parseFromString(string, mimeType)
It will give the top level #document node with parsed HTML from the string where if exists <h1> or <H1> at the beginning would be the first child of body and subsequently can be tested via tagName property.
Note: Takes care of preceding HTML comments and spaces at the beginning but a caveat is doesn't check fully closed tags ex: var s = \t <h1>I am a heading <h1> here the <h1> was never closed and in the result will two fully formed headings at the body with content : I am a heading and ""
let textAreaString = document.getElementById("edit-sectionText").value;
const domParser = new DOMParser();
const parsedDoc = domParser.parseFromString(textAreaString, "text/html");
if (parsedDoc.body.firstElementChild.tagName === "H1") {
//yes it starts with <h1> or <H1>
}
I'm trying to highlight a query inside a text coming from an ajax response, before constructing HTML with it and pasting that into the DOM. Right now I'm using this code snippet:
function highlightWords(line, word, htmltag) {
var tag = htmltag || ["<b>", "</b>"];
var regex = new RegExp('(' + preg_quote(word) + ')', 'gi');
return line.replace(regex, tag[0] + "$1" + tag[1]);
}
function preg_quote(str) {
return (str + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
}
However, this is not capeable of highlighting different words if the query is something like sit behind. It will only highlight the complete phrase and not the single words. It also doesn't care about HTML tags and that produces unpretty results if the query is span for example...
I've found various libraries which handle highlighting way better, like https://markjs.io/ or https://www.the-art-of-web.com/javascript/search-highlight/
Those libraries though always want to highlight content which is already present in the DOM.
My search gets an ajax response, which I then turn into HTML with JS and paste the complete HTMLString into a parent container using DOM7 (which is similar to jQuery). Therfor I would prefer to highlight the text before creating the HTMLString and pasting it in the DOM.
Any ideas?
I just make the highlight in the response of ajax request. It's works for me:
$.ajax({
url : url,
type : 'POST',
success: function(response) {
// Highlight
let term = 'word';
$context = $("#selector");
$context.show().unmark();
if (term){
$context.mark(term, {
done: function() {
$context.not(":has(mark)").hide();
}
});
}
}
});
Snippet style: Warning: this uses DOM7 as per Question
Overview: Instead of appending the whole text as HTML string to your #container,
Append the portions of normal text, as text, and the highlighted elements as elements, so you can style them at will.
var text // your ajax text response
var strQuery = 'sit behind' // your query string
var queryWords = strQuery.split(' ')
var textWords = text.split(' ')
var bufferNormalWords = []
textWords.forEach(function (word) {
if (queryWords.indexOf(word) != -1) { // found
var normalWords = bufferNormalWords.splice(0, buffer.length) // empty buffer
// Your DOM7 commands
$$('#container').add('span').text(normalWords.join(' ')) // normal text
$$('#container').add('span').css('color', 'red').text(word + ' ') // why not red
}
else bufferNormalWords.push(word)
})
Do not mess up with text becoming HTMLStrings, just set text, and create the necesary elements to style them as you want with your DOM7.
If your ajax response contains html, I don't think there's an easy way to get around creating DOM elements first. Below gets the job done, even in the case where span is in the query and the ajax results contain <span>
function highlightWords(line, word, htmltag) {
var words = word.split(/\s+/);
var tag = htmltag || ["<b>", "</b>"];
var root = document.createElement("div");
root.innerHTML = line;
root = _highlightWords(words, tag, root);
return root.innerHTML;
}
// Recursively search the created DOM element
function _highlightWords(words, htmlTag, el) {
var children = [];
el.childNodes.forEach(function(el) {
if (el.nodeType != 3) { // anything other than Text Type
var highlighted = _highlightWords(words, htmlTag, el);
children.push(highlighted);
} else {
var line = _highlight(el.textContent, words, htmlTag);
var span = document.createElement("span");
span.innerHTML = line;
children.push(span);
}
});
// Clear the html of the element, so the new children can be added
el.innerHTML = "";
children.forEach(function (c) { el.appendChild(c)});
return el;
}
// Find and highlight any of the words
function _highlight(line, words, htmlTag) {
words.forEach(function(singleWord) {
if (!!singleWord) {
singleWord = htmlEscape(singleWord);
line = line.replace(singleWord, htmlTag[0] + singleWord + htmlTag[1]);
}
});
return line;
}
I think you were on the right track using a library for that.
I have been using for that a great library named mark.js.
It works without dependencies or with jQuery.
The way that you can make it work.
Make the AJAX call.
Load the string to the DOM.
Call the Mark.js API on the content you have loaded.
Here's a code snippet:
document.addEventListener('DOMContentLoaded', getText);
function getText() {
const headline = document.getElementsByTagName("h1")[0];
const p = document.getElementsByTagName("p")[0];
fetch('https://jsonplaceholder.typicode.com/posts/1').
then(response => response.json()).
then(json => {
console.log(json);
headline.innerHTML = json.title;
p.innerHTML = json.body;
addMark('aut facere');
});
}
function addMark(keyword) {
var markInstance = new Mark(document.querySelector('.context'));
var options = {
separateWordSearch: true
};
markInstance.unmark({
done: function() {
markInstance.mark(keyword, options);
},
});
}
<script src="https://cdn.jsdelivr.net/mark.js/8.6.0/mark.min.js"></script>
<div class="context">
<h1></h1>
<p></p>
</div>
I'm writing a javascript program that gets the input in any of the below forms.
"Here is the result \"google\", and \"yahoo\""
or
"Here is a plain result"
and this is stored in a variable say X. and I want to create an anchor tag when ever I come across an anchor tag. I know that a href will by default create an anchor tag but in my case the result is rendered as a text, here is my code that I've tried so far.
var newLink = document.createElement('a');
newLink.href = 'http://google.com';
newLink.innerHTML = 'My anchor';
if (message) {
var x = message;
console.log(x.includes("href"));
if (!x.includes("href")) {
responsePara.appendChild(document.createTextNode(message));
responsePara.appendChild(document.createElement('br'));
} else {
//responsePara.appendChild(document.createTextNode(message));
responsePara.appendChild(newLink);
responsePara.appendChild(document.createElement('br'));
}
}
the output that I'm expecting is in case 1
<p> Here is the result "google", and "yahoo"</p>
in case 2
<p>Here is a plain result</p>
please let me know on how can I do this.
Note I'm using only js, no jquery
I can't see your problem, it should be really easy to implement this, right?
All you need to parse is the input that is coming to HTML. within another element (in your case p element)
UPDATE
I have updated this question, so you can modify (or create if there is not ref) an existing element with not parsed a element or with plain text.
function createOrUpdateCompositeLink(input, ref) {
if (ref) {
var isLinkText = input.match(/href/g);
var elementChild;
if (isLinkText) {
elementChild = document.createElement('span');
elementChild.innerHTML = input;
} else {
elementChild = document.createTextNode(input);
}
ref.appendChild(elementChild);
return ref;
} else {
var element = document.createElement('p');
element.innerHTML = input;
return element;
}
}
/* USAGE */
var message;
var element;
message = "Here is the result ";
message1 = "google\"";
message2 = " something plain text ";
message3 = ", and \"yahoo\"";
var reference = document.querySelector('.ref');
var el;
createOrUpdateCompositeLink(message, reference);
createOrUpdateCompositeLink(message1, reference);
createOrUpdateCompositeLink(message2, reference);
createOrUpdateCompositeLink(message3, reference);
<div class="ref"></div>
I would suggest you consider using jQuery and what you are trying to do becomes:
jQuery(".response").append(message);
I assume that your responsePara variable is defined from an existing <div> somewhere. In my example, that <div> would have a class named response.
<div class="response"></div>
Once you get a message, it gets added to the response div but that one line jQuery() command.
<!DOCTYPE html>
<html><body>
<p id="intro">Hello <em id="abcd">intro</em> World!</p>
<script type="text/javascript">
var txt=document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
var aa = el.getElementById("abcd").innerHTML;
alert( aa );
</script>
</body></html>
The above is a simple snippet. Actually I have an HTML editor and when the user saves the data I should save only the required content. Here I am getting the content of an element and manipulating it with DOM and pass the details to the server. This way I will not change the page content (user view remains the same) and he/she will continue editing the document.
The above is a simple example but in the real case I have to remove, change and move certain elements. The above code fails el.getElementById("abcd").innerHTML. Appreciate any pointers.
You can create a hidden iframe to manipulate all your changes, thus creating a separate DOM, then simply pull back the results you want.
var iframe;
if (document.createElement && (iframe = document.createElement('iframe'))) {
iframe.name = iframe.id = "externalDocument";
iframe.className = "hidden";
document.body.appendChild(iframe);
var externalDocument;
if (iframe.contentDocument) {
externalDocument = iframe.contentDocument;
} else if (iframe.contentWindow) {
externalDocument = iframe.contentWindow.document;
}
else if (window.frames[iframe.name]) {
externalDocument = window.frames[iframe.name].document;
}
if (externalDocument) {
externalDocument.open();
externalDocument.write('<html><body><\/body><\/html>');
externalDocument.close();
/* Run your manipulations here */
var txt = document.getElementById("intro").innerHTML;
var el = document.createElement("span");
el.innerHTML = txt;
/* Attach your objects to the externalDocument */
externalDocument.body.appendChild(el);
/* Reference the externalDocument to manipulate */
var aa = externalDocument.getElementById("abcd").innerHTML;
alert(aa);
}
/* Completed manipulation - Remove iFrame */
document.removeChild(iframe);
}
I have it working here:
http://jsfiddle.net/ucpvP/
Try using jQuery like given below.
function SaveData() //Your add function
{
var txt=$("#intro").html();
$(document).append("<span id='abcd'>" + txt+ "</span>");
var aa = $("#abcd").hmtl();
alert(aa);
}
You can use a DOM Element that is never appended to the DOM.
I use this 'cleanup' function:
function cleanup(str){
var tester = document.createElement('div'),
invalid, result;
tester.innerHTML = str;
//elements I don't allow
invalid = tester.querySelectorAll('script,object,iframe,style,hr,canvas');
// the cleanup (remove unwanted elements)
for (var i=0;i<invalid.length;(i+=1)){
invalid[i].parentNode.removeChild(invalid[i]);
}
result = tester.innerHTML;
tester = invalid = null;
//diacritics to html-encoded
return result.replace(/[\u0080-\u024F]/g,
function(a) {return '&#'+a.charCodeAt(0)+';';}
)
.replace(/%/g,'%25');
}
//usage:
cleanup(document.getElementById("intro").innerHTML);
You can extend the function with your own code to remove, change and move certain elements.