Highlighting when HTML and Xpath is given - javascript

Given the HTML as a string, the Xpath and offsets. I need to highlight the word.
In the below case I need to highlight Child 1
HTML text:
<html>
<body>
<h2>Children</h2>Joe has three kids:<br/>
<ul>
<li>
Child 1 name
</li>
<li>kid2</li>
<li>kid3</li>
</ul>
</body>
</html>
XPATH as : /html/body/ul/li[1]/a[1]
Offsets: 0,7
Render - I am using react in my app.
The below is what I have done so far.
public render(){
let htmlText = //The string above
let doc = new DOMParser().parseFromString(htmlRender,'text/html');
let ele = doc.evaluate("/html/body/ul/li[1]/a[1]", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); //This gives the node itself
let spanNode = document.createElement("span");
spanNode.className = "highlight";
spanNode.appendChild(ele);
// Wrapping the above node in a span class will add the highlights to that div
//At this point I don't know how to append this span to the HTML String
return(
<h5> Display html data </h5>
<div dangerouslySetInnerHTML={{__html: htmlText}} />
)
I want to avoid using jquery. Want to do in Javascript(React too) if possible!
Edit:
So if you notice the Render function it is using dangerouslySetHTML.
My problem is I am not able manipulate that string which is rendered.

This is what I ended up doing.
public render(){
let htmlText = //The string above
let doc = new DOMParser().parseFromString(htmlRender,'text/html');
let xpathNode = doc.evaluate("/html/body/ul/li[1]/a[1]", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
const highlightedNode = xpathNode.singleNodeValue.innerText;
const textValuePrev = highlightedNode.slice(0, char_start);
const textValueAfter = highlightedNode.slice(char_end, highlightedNode.length);
xpathNode.singleNodeValue.innerHTML = `${textValuePrev}
<span class='pt-tag'>
${highlightedNode.slice(char_start, char_end)}
</span> ${textValueAfter}`;
return(
<h5> Display html data </h5>
<div dangerouslySetInnerHTML={{__html: doc.body.outerHTML}} />
)

Xpath is inherently cross component, and React components shouldn't know much about each other. Xpath also basically requires all of the DOM to be created in order to query it. I would render your component first, then simply mutate the rendered output in the DOM using the Xpath selector.
https://jsfiddle.net/69z2wepo/73860/
var HighlightXpath = React.createClass({
componentDidMount() {
let el = document.evaluate(this.props.xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
el.singleNodeValue.style.background = 'pink';
},
render: function() {
return this.props.children;
}
});
Usage:
<HighlightXpath xpath="html//body//div/p/span">
... app ...
</HighlightXpath>

Related

How to create an onclick function that retrieves the object ID on the HTML and attach the ID onto the API to be fetched and used?

i'm a beginner developer in javascript trying to make a website to impress recruiters. I've succesfully retrieve information from my API and used it on my website which took me ages to figure out. Now the next problem is how do i make an onclick event that gets the ID i hardcoded onto the specific movie poster on my HTML, to be used in javascript to change the API address and get the data for that specific movie.
Notes: i have a modal onclick to host all the movie data on that modal.
My Javascript and HTML is below.
HTML
<div class="poster" onclick="toggleModal(); getimdbID()" id="tt4154796">
<div id="imdbID">tt4154796</div>
<img
src="https://m.media-amazon.com/images/M/MV5BMTc5MDE2ODcwNV5BMl5BanBnXkFtZTgwMzI2NzQ2NzM#._V1_SX300.jpg"
alt=""
class="poster__img"
/>
<p class="movie__title">Avengers: Endgame</p>
<p class="year">2019</p>
</div>
Javascript
async function getMovie() {
const movies = await fetch(`https://www.omdbapi.com/?i=tt4154796&apikey=4148fa0f`);
const movieData = await movies.json();
const { Title, Year, Runtime, Genre, Director, Actors, Plot, Awards, Poster, Metascore, imdbRating, imdbVotes, BoxOffice, } = movieData
document.getElementById('title').textContent = Title;
document.getElementById('year').textContent = Year;
document.getElementById('genre').textContent = Genre;
document.getElementById('runtime').textContent = Runtime;
document.getElementById('plot').textContent = Plot;
document.getElementById('director').textContent = Director;
document.getElementById('actors').textContent = Actors;
document.getElementById('awards').textContent = Awards;
document.getElementById('metascore').textContent = Metascore;
document.getElementById('boxoffice').textContent = BoxOffice;
document.getElementById('imdbRating').textContent = imdbRating;
document.getElementById('imdbVotes').textContent = imdbVotes;
document.getElementById('poster').src = Poster;
}
getMovie()
// ToggleModal
let isModalOpen = false;
function toggleModal() {
if(isModalOpen) {
isModalOpen = false;
return document.body.classList.remove("modal--open");
}
isModalOpen = true;
document.body.classList += " modal--open";
}
I've tried to create a function onclick to get the value of the ID onclick but that didn't work.
function getimdbID() {
let x = document.querySelector("#imdbID").value
console.log(x)
}
Im guessing you want your function to return the "tt4154796" thats contained within the div. If this is the case you'll want to use document.querySelector("#imdbID").innerText.
Since using .value is typically used to retrieve or change values entered into input fields.

How to replace a className on vanilla JS?

How can i replace a className (List) into Javascript? I have this code:
document.addEventListener("DOMContentLoaded", (e) => {
const url = window.location;
let urlReq = url.href;
if(urlReq.includes('/home')){
document.getElementById('default-sidenav-menu').className.replace('sidenav-main nav-expanded nav-lock nav-collapsible sidenav-active-rounded sidenav-dark', 'sidenav-main nav-collapsed nav-collapsible sidenav-active-rounded sidenav-dark');
console.log(document.getElementById('default-sidenav-menu').className);
}
});
this is my html:
<aside class="sidenav-main nav-expanded nav-lock nav-collapsible sidenav-active-rounded sidenav-dark" id="default-sidenav-menu">
<div class="brand-sidebar">
...
</aside>
but i am still getting the default className: sidenav-main nav-expanded nav-lock nav-collapsible sidenav-active-rounded sidenav-dark when my page was loaded.
What error i made?
You do not need to replace, simply assign the string classes to the className property:
The className property of the Element interface gets and sets the value of the class attribute of the specified element.
Syntax:
elementNodeReference.className = cName;
Where:
cName is a string variable representing the class or space-separated classes of the current element.
Demo:
document.addEventListener("DOMContentLoaded", (e) => {
// other code
document.getElementById('default-sidenav-menu').className = 'sidenav-main nav-expanded nav-lock nav-collapsible sidenav-active-rounded sidenav-dark', 'sidenav-main nav-collapsed nav-collapsible sidenav-active-rounded sidenav-dark';
console.log(document.getElementById('default-sidenav-menu').className);
// other code
});
<aside class="nav-lock nav-collapsible" id="default-sidenav-menu">
<div class="brand-sidebar">
</div>
</aside>
document.getElementById('default-sidenav-menu').className.replace
This means that you are replacing the string with another string because "className" is just a string when you get it.
document.body.className.constructor.name
=> "String"
If you want to replace it, then you should assign the new class name like this.
document.getElementById('default-sidenav-menu').className =
document.getElementById('default-sidenav-menu').className.replace("something", "something_new");

Child appending / DOM ordering JavaScript

<body>
<div class = "order-1-a">
<div class = "order 2-a">
<div class = "order 3-a"></div>
</div>
<div class = "order 2-b"></div>
<div class = "order 2-c"></div>
<div class = "order 2-d"></div>
</div>
<div class = "order-1-b"></div>
</body>
If I want a div to wrap only class "order-2-a" + being the first child of "class-1-a", how should I script the div with JavaScript?
Probably your best bet is to:
Create a new Element with .createElement().
Append 2-a to the new Element with .appendChild().
Insert the new element before 2b with .insertBefore().
var one_a = document.getElementsByClassName("order-1-a")[0];
var two_a = document.getElementsByClassName("order-2-a")[0];
var two_b = document.getElementsByClassName("order-2-b")[0];
var new_node = document.createElement("div");
new_node.appendChild(two_a);
one_a.insertBefore(new_node, two_b);
console.log(one_a.innerHTML);
<body>
<div class="order-1-a">
<div class="order-2-a">
<div class="order-3-a"></div>
</div>
<div class="order-2-b"></div>
<div class="order-2-c"></div>
<div class="order-2-d"></div>
</div>
<div class="order-1-b"></div>
</body>
This provides the structure you're looking for (albeit not displayed well with console.log()).
Also, please be aware that class names cannot start with numbers, and may yield unexpected results. I've updated most of your classes to start with order in my example, as is with your order-1-a class.
Hope this helps!
You can create a general wrapping function based on a selector. It should get the subject node, then its parent and either it's next sibling or null if there isn't one.
Then create an element of the required type, append the subject node and insert it before the next sibling or as the last node if there wasn't one.
PS.
I've modified the class names to be valid, they can't start with a digit.
// Wrap element with selector in element with tagName
function wrapEl(selector, tagName) {
var node = document.querySelector(selector);
// If there is no subject node, return
if (!node) return;
// Get parent and sibling (or null if there isn't one)
var parent = node.parentNode;
var sibling = node.nextSibling;
// Append stuff
var wrapper = document.createElement('tagName');
wrapper.textContent = 'inserted wrapper'; // Just to show it's there
wrapper.appendChild(node);
parent.insertBefore(wrapper, sibling);
}
window.onload = function() {
wrapEl('.order-2-a', 'div');
}
<body>
<div class = "order-1-a">
<div class = "order-2-a">
<div class = "order-3-a"></div>
</div>
<div class = "order-2-b"></div>
<div class = "order 2-c"></div>
<div class = "order 2-d"></div>
</div>
<div class = "order-1-b"></div>
</body>

React search highlighing

I am trying to make a search that highlights the matching characters within the displayed list.
I having trouble figuring out how I can add a DOM node within a list as it is being created/updated. The following code is where I got to. I think I understand why its not working (i keep getting 'Stephine Ma[object Object]ks'as the output). I am fairly sure I need to add it as an actual DOM node using .HTMl or .innerHTML but with react im not sure how one would do that.
import React from 'react';
import { Router, Route, Link } from 'react-router';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
var divImage = {
backgroundImage : "url(" + this.props.image + ")"
};
var test = this.props.name;
if(this.props.name.indexOf(this.props.filterText) != -1 ) {
var pattern = this.props.filterText.toString();
test = test.replace(pattern, <span className="highlight">+pattern+</span>)
}
return (
<li className="panelItem">
<a className="item-title" style={divImage}>{test}</a>
</li>
);
}
}
Here is an example if you can use indexOf instead of regex matching. Builds all the nodes and wraps them in spans.
https://jsfiddle.net/2zx84koy/
var Hello = React.createClass({
render: function() {
var name = this.props.name;
var startIdx = name.indexOf(this.props.filterText);
var textNodes = <span>{name}</span>
if(startIdx > -1 ) {
textNodes = (
<span>
{name.substring(0, startIdx)}
<span className="highlight">{name.substring(startIdx, startIdx + this.props.filterText.length)}</span>
{name.substring(startIdx + this.props.filterText.length)}
</span>
)
}
return (
<li className="panelItem">
<a className="item-title">{textNodes}</a>
</li>
);
}
});
You can do innerHTML in react but in general its not advised unless you know for sure it would not leave you vulnerable to xss attacks. I put an example below of how to convert your code to that style just for reference.
var test = this.props.name;
if(this.props.name.indexOf(this.props.filterText) != -1 ) {
var pattern = this.props.filterText.toString();
test = test.replace(pattern, '<span class="highlight">' + pattern + '</span>')
}
return (
<li className="panelItem">
<a className="item-title" dangerouslySetInnerHTML={{__html: test}}></a>
</li>
);
I was working on something similar quite recently, I created a library (prelude-extension) and a component (react-selectize) for it, here's a demo, maybe it is what you are looking for.

How to create dynamic content within syntaxhighlighter

I want to display a property name based on user input and display this inside of SyntaxHighlighter. Another post says this is supposed to be easy.
JS
$('#inputText').keyup(function () {
var outputValue = $('#codeTemplate').html();//Take the output of codeTemplate
$('#codeContent').html(outputValue);//Stick the contents of code template into codeContent
var finalOutputValue = $('#codeContent').html();//Take the content of codeContent and insert it into the sample label
$('.popover #sample').html(finalOutputValue);
SyntaxHighlighter.highlight();
});
SyntaxHighlighter.all();
Markup
<div style="display: none;">
<label class="propertyName"></label>
<label id="codeTemplate">
<label class="propertyName"></label>
//Not using Dynamic object and default Section (appSettings):
var actual = new Configuration().Get("Chained.Property.key");
//more code
</label>
<pre id="codeContent" class="brush: csharp;">
</pre>
</div>
<div id="popover-content" style="display: none">
<label id="sample">
</label>
</div>
This outputs plain text. As if SyntaxHighlighter never ran. I suspect that the issue has to do with the fact that <pre> doesn't exist after the page is rendered. However, updating
SyntaxHighlighter.config.tagName = "label";
along with pre to label did not work either.
There were many problems that had to be overcome to get this to function. I feel this is best explained with code:
JS
<script>
$(function () {
$('#Key').popover({
html: true,
trigger: 'focus',
position: 'top',
content: function () {
loadCodeData(true);
console.log('content updated');
var popover = $('#popover-content');
return popover.html();//inserts the data into .popover-content (a new div with matching class name for the id)
}
});
$('#Key').keyup(function () {
loadCodeData();
});
function loadCodeData(loadOriginal) {
var userData = $('#Key').val();
var codeTemplate = $('#codeTemplate').html();
var tokenizedValue = codeTemplate.toString().replace('$$propertyNameToken', userData);
$('#codeContent').html(tokenizedValue);
$('#codeContent').attr('class', 'brush: csharp');//!IMPORTANT: re-append the class so SyntaxHighlighter will process the div again
SyntaxHighlighter.highlight();
var syntaxHighlightedResult = $('#codeContent').html();//Take the content of codeContent and insert it into the div
var popover;
if(loadOriginal)
popover = $('#popover-content');//popover.content performs the update of the generated class for us so well we need to do is update the popover itself
else {
popover = $('.popover-content');//otherwise we have to update the dynamically generated popup ourselves.
}
popover.html(syntaxHighlightedResult);
}
SyntaxHighlighter.config.tagName = 'div';//override the default pre because pre gets converted to another tag on the client.
SyntaxHighlighter.all();
});
</script>
Markup
<div style="display: none;">
<label id="codeTemplate">
//Not using Dynamic object and default Section (appSettings):
var actual = new Configuration().Get("$$propertyNameToken");
//Using a type argument:
int actual = new Configuration().Get<int>("asdf");
//And then specifying a Section:
var actual = new Configuration("SectionName").Get("test");
//Using the Dynamic Object and default Section:
var actual = new Configuration().NumberOfRetries();
//Using a type argument:
int actual = new Configuration().NumberOfRetries<int>();
//And then specifying a Section:
var actual = new Configuration("SectionName").NumberOfRetries();
</label>
<div id="codeContent" class="brush: csharp;">
</div>
</div>
<div id="popover-content" style="display: none">
</div>

Categories