I am new to Javascript and this is my first question. I want to make autocomplete suggestions purely in HTML, CSS and JS using below JS code. I have gotten this code from GitHub.
The problem is when I type, it downloads the whole JSON file before showing suggestions. I want to reduce this JSON file by splitting the JSON data to multiple JSON files like states1.json, states2.json, states3.json etc. I have tried but failed as I'm new. Here is the code.
Please answer simply to help me understand.
const matchList = document.getElementById("match-list");
//Search states.json and filter it
const searchStates = async searchText => {
const res = await fetch("./data/States1.json");
const states = await res.json();
//Get Matches to current text input
let matches = states.filter(state => {
const regex = new RegExp(`^${searchText}`, "gi");
return state.wr.match(regex);
});
if (searchText.length === 0) {
matches = [];
matchList.innerHTML = "";
}
outputHtml(matches);
};
//Show Results in HTML
const outputHtml = matches => {
if (matches.length > 0) {
const html = matches
.map(
match => `
<div class="card card-body mb-4">
<h4>${match.wr} <span class="text-primary">${
match.mn
}</span></h4>
</div>`
)
.join("");
matchList.innerHTML = html;
}
};
search.addEventListener("input", () => searchStates(search.value));
I will this typescript object convert:
api:{
accessToken:[user1::aaaaa,user2::bbbbb],
accessSecret:[user1:aaaaa,userb::bbbbb]
}
i will split the string inside the arrays by :: sign.
Finish object should look like this
api:{
accessToken:[user1::aaaaa,user2::bbbbb],
accessSecret:[user1:aaaaa,userb::bbbbb],
user1_access:aaaaa,
user2_access:bbbbb,
user1_secret:aaaaa,
user2_secret:bbbbb
}
What is the best solution without compile errors in typescript, please Help.
Here is a sample which gives you an idea of how to go about it,
const api = {
accessToken:["user1::aaaaa","user2::bbbbb"],
accessSecret:["user1::xxxx","user2::yyyy"]
};
const newApiObj: any = {};
const accessTokenvalues = api.accessToken.map(x => x.split("::")) ?? [];
for(const item of accessTokenvalues){
newApiObj[item[0] + '_access'] = item[1];
}
const accessSecretvalues = api.accessSecret.map(x => x.split("::")) ?? [];
for(const item of accessSecretvalues){
newApiObj[item[0] + '_secret'] = item[1];
}
console.log(newApiObj);
I am working on a react native project and I have an html string json api response.
I am using react-native-render-html to render it, and I can get all paragraphs and apply specific things like number of lines ,etc.. . However I want to get only the first paragraph in the response.
str response='<p>text1</p> <p>text2</p> <p>text3</p>';
Is it possible to write a regular expression to get only the content of first paragraph which is for example text1 ?
I don't use React Native but in javascript you could do something like that:
const paragraphs = response.split("</p>")
const firstParagraph = paragraphs[0]+'</p>';
Or with a regex you can do something like that:
// extract all paragraphe from the string
const matches = [];
response.replace(/<p>(.*?)<\/p>/g, function () {
//use arguments[0] if you need to keep <p></p> html tags
matches.push(arguments[1]);
});
// get first paragraph
const firstParagraph = (matches.length) ? matches[0] : ""
Or like that (I think it is the best way in your case)
const response='<p>text1</p> <p>text2</p> <p>text3</p>';
const regex = /<p>(.*?)<\/p>/;
const corresp = regex.exec(response);
const firstParagraph = (corresp) ? corresp[0] : "" // <p>text1</p>
const firstParagraphWithoutHtml = (corresp) ? corresp[1] : "" // text1
Hope it will help
var response='<p>text1</p> <p>text2</p> <p>text3</p>';
var firstParagraphElement=response.split("</p>")[0] //firstparagraphElement="<p>text1"
var paragraphContent=firstParagraphElement.replace("<p>","") //paragraphContent="text1"
javascript split() function reference click
javascript replace() function reference click
In React Native you can also use parse5 to extract a string from HTML code. I have used this code in a project for doing so:
import parse5 from 'parse5'
const isText = (tagName): Boolean => tagName === '#text'
const processNode = (node): String => {
const nodeName = node.nodeName
if (isText(nodeName)) {
return node.value
}
if (!node.childNodes) {
return ''
}
return node.childNodes.map((child, index) => processNode(child)).join(' ')
}
export const htmlToText = (html): String => {
const root = parse5.parseFragment(html)
return processNode(root).replace(/\s+/g, ' ').trim()
}
Here is a simple JEST test for the function above:
test('when full document htmlToText should get text', () => {
const htmlToText1 = htmlToText("<html><head><title>titleTest</title></head><body><a href='test0'>test01</a><a href='test1'>test02</a><a href='test2'>test03</a></body></html>")
expect(htmlToText1)
.toBe(`titleTest test01 test02 test03`);
});
I'm parsing content generated by a wysiwyg into a table of contents widget in React.
So far I'm looping through the headers and adding them into an array.
How can I get them all into one multi-dimensional array or object (what's the best way) so that it looks more like:
h1-1
h2-1
h3-1
h1-2
h2-2
h3-2
h1-3
h2-3
h3-3
and then I can render it with an ordered list in the UI.
const str = "<h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
const patternh1 = /<h1>(.*?)<\/h1>/g;
const patternh2 = /<h2>(.*?)<\/h2>/g;
const patternh3 = /<h3>(.*?)<\/h3>/g;
let h1s = [];
let h2s = [];
let h3s = [];
let matchh1, matchh2, matchh3;
while (matchh1 = patternh1.exec(str))
h1s.push(matchh1[1])
while (matchh2 = patternh2.exec(str))
h2s.push(matchh2[1])
while (matchh3 = patternh3.exec(str))
h3s.push(matchh3[1])
console.log(h1s)
console.log(h2s)
console.log(h3s)
I don't know about you, but I hate parsing HTML using regexes. Instead, I think it's a better idea to let the DOM handle this:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
console.log(tree);
This answer does not require h3 to follow h2; h3 can follow h1 if you so please. If you want to turn this into an ordered list, that can also be done:
const str = `<h1>h1-1</h1>
<h3>h3-1</h3>
<h3>h3-2</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = str.trim();
let tree = [];
let leaf = null;
for (const node of wrapper.querySelectorAll("h1, h2, h3, h4, h5, h6")) {
const nodeLevel = parseInt(node.tagName[1]);
const newLeaf = {
level: nodeLevel,
text: node.textContent,
children: [],
parent: leaf
};
while (leaf && newLeaf.level <= leaf.level)
leaf = leaf.parent;
if (!leaf)
tree.push(newLeaf);
else
leaf.children.push(newLeaf);
leaf = newLeaf;
}
const ol = document.createElement("ol");
(function makeOl(ol, leaves) {
for (const leaf of leaves) {
const li = document.createElement("li");
li.appendChild(new Text(leaf.text));
if (leaf.children.length > 0) {
const subOl = document.createElement("ol");
makeOl(subOl, leaf.children);
li.appendChild(subOl);
}
ol.appendChild(li);
}
})(ol, tree);
// add it to the DOM
document.body.appendChild(ol);
// or get it as text
const result = ol.outerHTML;
Since the HTML is parsed by the DOM and not by a regex, this solution will not encounter any errors if the h1 tags have attributes, for example.
You can simply gather all h* and then iterate over them to construct a tree as such:
Using ES6 (I inferred this is ok from your usage of const and let)
const str = `
<h1>h1-1</h1>
<h2>h2-1</h2>
<h3>h3-1</h3>
<p>something</p>
<h1>h1-2</h1>
<h2>h2-2</h2>
<h3>h3-2</h3>
`
const patternh = /<h(\d)>(.*?)<\/h(\d)>/g;
let hs = [];
let matchh;
while (matchh = patternh.exec(str))
hs.push({ lev: matchh[1], text: matchh[2] })
console.log(hs)
// constructs a tree with the format [{ value: ..., children: [{ value: ..., children: [...] }, ...] }, ...]
const add = (res, lev, what) => {
if (lev === 0) {
res.push({ value: what, children: [] });
} else {
add(res[res.length - 1].children, lev - 1, what);
}
}
// reduces all hs found into a tree using above method starting with an empty list
const tree = hs.reduce((res, { lev, text }) => {
add(res, lev-1, text);
return res;
}, []);
console.log(tree);
But because your html headers are not in a tree structure themselves (which I guess is your use case) this only works under certain assumptions, e.g. you cannot have a <h3> unless there's a <h2> above it and a <h1> above that. It will also assume a lower-level header will always belong to the latest header of an immediately higher level.
If you want to further use the tree structure for e.g. rendering a representative ordered-list for a TOC, you can do something like:
// function to render a bunch of <li>s
const renderLIs = children => children.map(child => `<li>${renderOL(child)}</li>`).join('');
// function to render an <ol> from a tree node
const renderOL = tree => tree.children.length > 0 ? `<ol>${tree.value}${renderLIs(tree.children)}</ol>` : tree.value;
// use a root node for the TOC
const toc = renderOL({ value: 'TOC', children: tree });
console.log(toc);
Hope it helps.
What you want to do is known as (a variant of a) document outline, eg. creating a nested list from the headings of a document, honoring their hierarchy.
A simple implementation for the browser using the DOM and DOMParser APIs goes as follows (put into a HTML page and coded in ES5 for easy testing):
<!DOCTYPE html>
<html>
<head>
<title>Document outline</title>
</head>
<body>
<div id="outline"></div>
<script>
// test string wrapped in a document (and body) element
var str = "<html><body><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3></body></html>";
// util for traversing a DOM and emit SAX startElement events
function emitSAXLikeEvents(node, handler) {
handler.startElement(node)
for (var i = 0; i < node.children.length; i++)
emitSAXLikeEvents(node.children.item(i), handler)
handler.endElement(node)
}
var outline = document.getElementById('outline')
var rank = 0
var context = outline
emitSAXLikeEvents(
(new DOMParser()).parseFromString(str, "text/html").body,
{
startElement: function(node) {
if (/h[1-6]/.test(node.localName)) {
var newRank = +node.localName.substr(1, 1)
// set context li node to append
while (newRank <= rank--)
context = context.parentNode.parentNode
rank = newRank
// create (if 1st li) or
// get (if 2nd or subsequent li) ol element
var ol
if (context.children.length > 0)
ol = context.children[0]
else {
ol = document.createElement('ol')
context.appendChild(ol)
}
// create and append li with text from
// heading element
var li = document.createElement('li')
li.appendChild(
document.createTextNode(node.innerText))
ol.appendChild(li)
context = li
}
},
endElement: function(node) {}
})
</script>
</body>
</html>
I'm first parsing your fragment into a Document, then traverse it to create SAX-like startElement() calls. In the startElement() function, the rank of a heading element is checked against the rank of the most recently created list item (if any). Then a new list item is appended at the correct hierarchy level, and possibly an ol element is created as container for it. Note the algorithm as it is won't work with "jumping" from h1 to h3 in the hierarchy, but can be easily adapted.
If you want to create an outline/table of content on node.js, the code could be made to run server-side, but requires a decent HTML parsing lib (a DOMParser polyfill for node.js, so to speak). There are also the https://github.com/h5o/h5o-js and the https://github.com/hoyois/html5outliner packages for creating outlines, though I haven't tested those. These packages supposedly can also deal with corner cases such as heading elements in iframe and quote elements which you generally don't want in the the outline of your document.
The topic of creating an HTML5 outline has a long history; see eg. http://html5doctor.com/computer-says-no-to-html5-document-outline/. HTML4's practice of using no sectioning roots (in HTML5 parlance) wrapper elements for sectioning and placing headings and content at the same hierarchy level is known as "flat-earth markup". SGML has the RANK feature for dealing with H1, H2, etc. ranked elements, and can be made to infer omitted section elements, thus automatically create an outline, from HTML4-like "flat earth markup" in simple cases (eg. where only section or another single element is allowed as sectioning root).
I'll use a single regex to get the <hx></hx> contents and then sort them by x using methods Array.reduce.
Here is the base but it's not over yet :
// The string you need to parse
const str = "\
<h1>h1-1</h1>\
<h2>h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2</h1>\
<h2>h2-2</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
As you are parsing html content I want to aware you about special cases like presency of \n or space. For example look at the following non-working snippet :
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = str.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
tmp[hNumber].push(x.substr(4, x.length - 9));
return tmp;
}, {}));
console.log(matchesSorted);
We gotta add .replace() and .trim() in order to remove unwanted \n and spaces.
Use this snippet
// The string you need to parse
const str = "\
<h1>h1-1\n\
</h1>\
<h2> h2-1</h2>\
<h3>h3-1</h3>\
<p>something</p>\
<h1>h1-2 </h1>\
<h2>h2-2 \n\
</h2>\
<h3>h3-2</h3>";
// Remove all unwanted \n
const preparedStr = str.replace(/(\r\n\t|\n|\r\t)/gm, "");
// The regex that will cut down the <hx>something</hx>
const regex = /<h[0-9]{1}>(.*?)<\/h[0-9]{1}>/g;
// We get the matches now
const matches = preparedStr.match(regex);
// We match the hx togethers as requested
const matchesSorted = Object.values(matches.reduce((tmp, x) => {
// We get the number behind hx ---> the x
const hNumber = x[2];
// If the container do not exist, create it
if (!tmp[hNumber]) {
tmp[hNumber] = [];
}
// Push the new parsed content into the array
// 4 is to start after <hx>
// length - 9 is to get all except <hx></hx>
// call trim() to remove unwanted spaces
tmp[hNumber].push(x.substr(4, x.length - 9).trim());
return tmp;
}, {}));
console.log(matchesSorted);
I write this code works with JQuery. (Please don't DV. Maybe someone needs a jquery answer later)
This recursive function creates lis of string and if one item has some childern, it will convert them to an ol.
const str =
"<div><h1>h1-1</h1><h2>h2-1</h2><h3>h3-1</h3></div><p>something</p><h1>h1-2</h1><h2>h2-2</h2><h3>h3-2</h3>";
function strToList(stri) {
const tags = $(stri);
function partToList(el) {
let output = "<li>";
if ($(el).children().length) {
output += "<ol>";
$(el)
.children()
.each(function() {
output += partToList($(this));
});
output += "</ol>";
} else {
output += $(el).text();
}
return output + "</li>";
}
let output = "<ol>";
tags.each(function(itm) {
output += partToList($(this));
});
return output + "</ol>";
}
$("#output").append(strToList(str));
li {
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
(This code can be converted to pure JS easily)
Input: 'text __city__ text __city__ text __city__'
Output: 'text <i>city</i> text <i>city</i> text <i>city</i>'
The result of the feature is passed into dangerouslySetInnerHTML.
This component is
<div dangerouslySetInnerHTML={{__html: content.replace(/(?:\r\n|\r|\n)/g, '<br />')}}></div>
for this problem (link).
My function is
const transformText = (text) => {
const tmp = text.split('__');
var res = '';
var check = true;
for (var i = 0; i < tmp.length-1; i++){
check = !check;
if (check) {
res += '<i>'+tmp[i]+'</i>';
} else if (tmp[i] != undefined){
res += tmp[i];
}
}
return res;
};
but breaks the rest of the page into IE11 browser.
Step 1: transforming the input text.
Assuming the input is in the form "text __city__ text __city__ ... text
__city__" as per your example.
const input = "text1 __paris__ text2 __london__ text3 __berlin__";
// the following will turn the input into an array of the form
// [ [text, city], [text, city], ... [text, city] ]
const pairs = input.slice(0, -2).split('__ ').map(pair => pair.split(' __'));
Step 2: using the array in a React component.
Assuming the variable pairs holds the text/city pairs as defined above, the following would be the render method inside your component (if it is class-based).
render() {
return (
<ul>
{pairs.map((pair, index) => {
return <li key={index}>{pair[0]} <i>{pair[1]}</i></li>;
})}
</ul>
);
}