I have a label in a form defined as follows and the jQuery code below locates the 'Description' label and changes it for me to 'Program Description':
$(".ms-accentText").filter(function () {
return $(this).text() == "Description";
}).text('Program Description');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="ms-accentText">Description</h3>
But I have another label of 'Name' that has html markup that is used to tag the label with an asterisk, to define the associated field as being required, as listed below:
The similar jQuery code which is listed below does not locate and change the label:
$(".ms-accentText").filter(function () {
return $(this).text() == "Name";
}).text('Program Name');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="ms-accentText">Name<span class="ms-formvalidation"> * </span></h3>
How can I target the 'Name' label, when the extra asterisk related html mark-up code included?
I'm new to using jQuery.
Thanks,
Wayne
By using combination of :contains jquery selector + append you can achieve more efficient flow:
$(() => {
const query = 'Name'
const $name = $(`.ms-accentText:contains(${query})`)
.filter(function () {
const textChildTexts = [...this.childNodes]
// filtering to select only direct text nodes
// to skip the asterisk part
.filter(node => node && node.nodeType === Node.TEXT_NODE)
// getting the text out of node
.map(node => node && node.textContent)
// joining all the texts into a string to compare
.join('');
return textChildTexts === query;
});
const $asterisk = $name.find('.ms-formvalidation');
$name.text('Program Name').append($asterisk);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="ms-accentText">Name<span class="ms-formvalidation"> * </span></h3>
<h3 class="ms-accentText">Project Full Name<span class="ms-formvalidation"> * </span></h3>
You could create a custom filtering function that removes any descendant elements and trims the containing text, and use that like this
function f(what) {
return function(index, elem) {
var clone = $(elem).clone();
clone.find('*').remove();
return clone.text().trim() === what;
}
}
$(".ms-accentText").filter(f('Description')).css('color', 'blue');
$(".ms-accentText").filter(f('Name')).css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h3 class="ms-accentText">Description</h3>
<h3 class="ms-accentText">Name<span class="ms-formvalidation"> * </span></h3>
Related
function setParagraph(paraList) {
$.each(paraList, function (i, field) {
var pElement = document.createElement('p');
$(pElement).text(field);
//a line that combines the p elements like so:
//<p>First para</p>
//<p>Second para</p>
});
return //all elements for append;
}
I am trying to write code with minimum number of lines to return a "collection" of p elements that needs to be appended to the following div:
$("#somediv").append(setParagraph(jsonValue));
To produce:
<div id="somediv">
<p>First para</p>
<p>Second para</p>
</div>
The method setParagraph is passed json string with collection of string items that are translated into p elements in the method.
I have tried pushing the elements into an array but I don't think that is the right way to go.
Also, I do not wish to use string concatenation in the loop to produce the desired results, unless of course that is the only best way to handle it.
EDIT:
The below works but as I said I am looking for some other solution besides array:
function setParagraph(paraList) {
var arrElements = [];
$.each(paraList, function (i, field) {
var $pElement = $("<p/>").text(field);
arrElements.push($pElement);
});
return arrElements;
}
var jsonValue = ["First para","Second para"];
$("#somediv").append(setParagraph(jsonValue));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="somediv">
</div>
Shorter
const par = ["one", "two", "three"]
$("#somediv").html(par.map(p => $("<p>", { text: p })))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="somediv"></div>
You can use $.map(..) for that, here is an example:
function setParagraph(paraList) {
return $.map(paraList, function(item) {
return $("<p>", {
text: item
});
});
}
$("#somediv").append(setParagraph(["one", "two", "three"]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="somediv"></div>
I've created a simple fiddle example that I think would suit your needs. It works in a different way, a little bit at least, but with a bit of changing you'd get your desired result.
Here's the fiddle: https://jsfiddle.net/fkoLn4v9/
Here's the code:
// index.js
const paragraphsCollection = [
{
tag: 'p',
content: 'This is the first parargraph',
},
{
tag: 'p',
content: 'This is the second paragraph',
},
];
const setParagraph = (parentElem, contentData) => {
contentData.forEach(({ tag, content }) => {
const domElem = document.createElement(tag);
domElem.innerText = content;
parentElem.appendChild(domElem);
});
};
const divElem = document.querySelector('.parentDiv');
setParagraph(divElem, paragraphsCollection);
HTML:
<div class="parentDiv"></div>
I am using cheerio to parse HTML code in different nodes. I can easily do $("*"), but this gets me only normal HTML nodes and not the separate text nodes. Lets consider 3 user inputs:
One:
text only
I need: single text node.
Two:
<div>
text 1
<div>
inner text
</div>
text 2
</div>
I need: text node + div node + text node in same sequence.
Three:
<div>
<div>
inner text 1
<div>
inner text 2
</div>
</div>
<div>
inner text 3
</div>
</div>
I need: 2 div nodes
Possible?
In hope to help someone, filter function seems to return text nodes also.
I got help from this answer: https://stackoverflow.com/a/6520267/3800042
var $ = cheerio.load(tree);
var iterate = function(node, level) {
if (typeof level === "undefined") level = "--";
var list = $(node).contents().filter(function() { return true; });
for (var i=0; i<=list.length-1; i++) {
var item = list[i];
console.log(level, "(" + i + ")", item.type, $(item).text());
iterate(item, level + "--");
}
}
iterate($.root());
HTML input
<div>
text 1
<div>
inner text
</div>
text 2
</div>
Result
-- (0) tag
text 1
inner text
text 2
---- (0) text
text 1
---- (1) tag
inner text
------ (0) text
inner text
---- (2) text
text 2
I hope the following codes can help you.
const cheerio = require("cheerio");
const htmlText = `<ul id="fruits">
<!--This is a comment.-->
<li class="apple">Apple</li>
Peach
<li class="orange">Orange</li>
<li class="pear">Pear</li>
</ul>`;
const $ = cheerio.load(htmlText);
const contents = $('ul#fruits').contents();
console.log(contents.length);// 9, since nodes like '\n' are included
console.log(new RegExp('^\\s*$').test('\n '));
function isWhitespaceTextNode(node){
if(node.type !== 'text'){
return false;
}
if(new RegExp('^\\s*$').test(node.data)){
return true;
}
return false;
}
//Note here: filter is a function provided by cheerio, not Array.filter
const nonWhitespaceTextContents = contents.filter(nodeIndex=>{
const node = contents[nodeIndex];
if(isWhitespaceTextNode(node)){
return false;
}else{
return true;
}
});
console.log(nonWhitespaceTextContents.length);// 5, since nodes like '\n ' are excluded
nonWhitespaceTextContents.each((_, node)=>console.log(node));
//[comment node]
//[li node] apple
//[text node] peach
//[li node] orange
//[li node] pear
If you want all of the immediate children of a node, both text nodes and tag nodes, use .contents() and filter out whitespace-only text nodes.
Here's the code running on your examples:
const cheerio = require("cheerio"); // 1.0.0-rc.12
const tests = [
// added a div container to make the parent selector consistent
`<div>text only</div>`,
`<div>
text 1
<div>
inner text
</div>
text 2
</div>`,
`<div>
<div>
inner text 1
<div>
inner text 2
</div>
</div>
<div>
inner text 3
</div>
</div>`
];
tests.forEach(html => {
const $ = cheerio.load(html);
const result = [...$("div").first().contents()]
.filter(e => e.type !== "text" || $(e).text().trim())
// the following is purely for display purposes
.map(e => e.type === "text" ? $(e).text().trim() : e.tagName);
console.log(result);
});
Output:
[ 'text only' ]
[ 'text 1', 'div', 'text 2' ]
[ 'div', 'div' ]
If you only want the text nodes and not the tags, see How to get a text that's separated by different HTML tags in Cheerio.
I need to find all elements in a page by attribute value only (ignoring the key) using jquery.
Is there a way to do this easily?
Currently, I am just iterating on all elements in the page, on every property etc..
You can use $.expr, Element.attributes, Array.prototype.some()
$.expr[":"].attrValue = function(el, idx, selector) {
return [].some.call(el.attributes, function(attr) {
return attr.value === selector[selector.length - 1]
})
};
// filter element having attribute with `value` set to `"abc"`
$(":attrValue(abc)").css("color", "blue");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<div title="abc">abc</div>
<div title="def">def</div>
<div title="ghi">ghi</div>
<div title="jkl">jkl</div>
Use brackets []
var ElementsWithAttributeKeyTest = $('[attributeKey="Test"]');
Or pass an object with the attribute name and value as parameter to this function:
var getElemsByAttribute = function(obj) {
if (obj) {
if (obj.attributeKey && obj.attributeValue) {
return $('[' + obj.attributeKey + '="' + obj.attributeValue + '"]');
}
}
}
var attrObj = {
attributeKey: 'data-color',
attributeValue: 'red'
}
getElemsByAttribute(attrObj).css('color', 'red');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<span data-color="red">Red</span>
<span data-color="red">Red</span>
<span data-color="green">Green</span>
<span data-color="blue">Blue</span>
<span data-color="red">Red</span>
<span data-color="green">Green</span>
If you want to search all attributes values you can use this small plugin:
$.fn.search_by_attr_value = function(regex) {
return this.filter(function() {
var found = false;
$.each(this.attributes, function() {
if (this.specified && this.value.match(regex)) {
found = true;
return false;
}
});
return found;
});
};
and you can use it like this:
$('*').search_by_attr_value(/^some value$/);
Based on this answer
You could define new function take as parameter the value you want to filter with (e.g get_elements_by_value(filter)), then inside this function parse all the elements of the page using $('*').each(), after that parse the attributes of every element el of those elements using attribute attributes like below :
$.each(el.attributes, function(){ })
Then inside the each loop you could make your condition and push the matched values with the passed filter inside matched[] that should be returned.
Check working example below, hope this helps.
function get_elements_by_value(filter){
var matched=[];
$('*').each(function(index,el) {
$.each(el.attributes, function() {
if( this.value===filter )
matched.push(el);
})
})
return $(matched);
}
get_elements_by_value('my_value').css('background-color','green');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div title="my_value">AA</div>
<div title="def">BB</div>
<input type='text' name='my_value' value='CC'/>
<p class='my_value'>DD</p>
<span title="test">EE</span>
I want to create pages with urls such as:
http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins
http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones
http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver
These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, "http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.
I imagine something like this is required, client-side:
var employee = getLastURLPortion(); // return "Billy_Bones" (or whatever)
employee = humanifyTheName(employee); // replaces underscores with spaces, so that it's "Billy Bones" (etc.)
Highlight(employee); // this I have no clue how to do
Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?
If you call the function
highlight(employee);
this is what that function would look like in ECMAScript 2018+:
function highlight(employee){
Array.from(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)"))
.flatMap(({childNodes}) => [...childNodes])
.filter(({nodeType, textContent}) => nodeType === document.TEXT_NODE && textContent.includes(employee))
.forEach((textNode) => textNode.replaceWith(...textNode.textContent.split(employee).flatMap((part) => [
document.createTextNode(part),
Object.assign(document.createElement("mark"), {
textContent: employee
})
])
.slice(0, -1))); // The above flatMap creates a [text, employeeName, text, employeeName, text, employeeName]-pattern. We need to remove the last superfluous employeeName.
}
And this is an ECMAScript 5.1 version:
function highlight(employee){
Array.prototype.slice.call(document.querySelectorAll("body, body *:not(script):not(style):not(noscript)")) // First, get all regular elements under the `<body>` element
.map(function(elem){
return Array.prototype.slice.call(elem.childNodes); // Then extract their child nodes and convert them to an array.
})
.reduce(function(nodesA, nodesB){
return nodesA.concat(nodesB); // Flatten each array into a single array
})
.filter(function(node){
return node.nodeType === document.TEXT_NODE && node.textContent.indexOf(employee) > -1; // Filter only text nodes that contain the employee’s name.
})
.forEach(function(node){
var nextNode = node.nextSibling, // Remember the next node if it exists
parent = node.parentNode, // Remember the parent node
content = node.textContent, // Remember the content
newNodes = []; // Create empty array for new highlighted content
node.parentNode.removeChild(node); // Remove it for now.
content.split(employee).forEach(function(part, i, arr){ // Find each occurrence of the employee’s name
newNodes.push(document.createTextNode(part)); // Create text nodes for everything around it
if(i < arr.length - 1){
newNodes.push(document.createElement("mark")); // Create mark element nodes for each occurrence of the employee’s name
newNodes[newNodes.length - 1].innerHTML = employee;
// newNodes[newNodes.length - 1].setAttribute("class", "highlighted");
}
});
newNodes.forEach(function(n){ // Append or insert everything back into place
if(nextNode){
parent.insertBefore(n, nextNode);
}
else{
parent.appendChild(n);
}
});
});
}
The major benefit of replacing individual text nodes is that event listeners don’t get lost. The site remains intact, only the text changes.
Instead of the mark element you can also use a span and uncomment the line with the class attribute and specify that in CSS.
This is an example where I used this function and a subsequent highlight("Text"); on the MDN page for Text nodes:
(The one occurrence that isn’t highlighted is an SVG node beyond an <iframe>).
I used the following regex to replace all the matching url to create anchors with highlighted text:
(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)
Debuggex Demo
The following code will replace all plain urls. If you don't need them to be replaced to links, just highlight them, remove the tags:
var str = "http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver ";
var highlighted = str.replace( new RegExp("(http://xyzcorp/schedules/(.*?)/)(.*?)( |<|\n|\r|$)","g"), "<a href='$1$3'>$1<span style='background-color: #d0d0d0'>$3</span></a>" );
The content of the highlighted string will be:
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Jim_Hawkins</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>Billy_Bones</span></a>
<a href='http://xyzcorp/schedules/2015Aug24_Aug28/John_Silver'>http://xyzcorp/schedules/2015Aug24_Aug28/<span style='background-color: #d0d0d0'>John_Silver</span></a>
UPDATE:
This function will replace the matching names from the input text:
function highlight_names( html_in )
{
var name = location.href.split("/").pop().replace("_"," ");
return html_in.replace( new RegExp( "("+name+")", "g"), "<span style='background-color: #d0d0d0'>$1</span>" );
}
One solution would be, after window is loaded, to traverse all nodes recursively and wrap search terms in text nodes with a highlight class. This way, original structure and event subscriptions are not preserved.
(Here, using jquery, but could be done without):
Javascript:
$(function() {
// get term from url
var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
// search regexp
var re = new RegExp('(' + term + ')', 'gi');
// recursive function
function highlightTerm(elem) {
var contents = $(elem).contents();
if(contents.length > 0) {
contents.each(function() {
highlightTerm(this);
});
} else {
// text nodes
if(elem.nodeType === 3) {
var $elem = $(elem);
var text = $elem.text();
if(re.test(text)) {
$elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
}
}
}
}
highlightTerm(document.body);
});
CSS:
.highlight {
background-color: yellow;
}
$(function() {
// get term from url
//var term = window.location.href.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
var term = 'http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones/'.match(/\/(\w+)\/?$/)[1].replace('_', ' ');
// search regexp
var re = new RegExp('(' + term + ')', 'gi');
// recursive function
function highlightTerm(elem) {
var contents = $(elem).contents();
if(contents.length > 0) {
contents.each(function() {
highlightTerm(this);
});
} else {
// text nodes
if(elem.nodeType === 3) {
var $elem = $(elem);
var text = $elem.text();
if(re.test(text)) {
$elem.wrap("<span/>").parent().html(text.replace(re, '<span class="highlight">$1</span>'));
}
}
}
}
highlightTerm(document.body);
});
.highlight {
background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div class="post-text" itemprop="text">
<p>I want to create pages with urls such as:</p>
<pre style="" class="default prettyprint prettyprinted">
<code>
<span class="pln">http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Jim_Hawkins</span>
<span class="pln">
http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</span>
<span class="pln">
http</span>
<span class="pun">:</span>
<span class="com">//xyzcorp/schedules/2015Aug24_Aug28/John_Silver</span>
</code>
</pre>
<p>These particular URLs would all contain the exact same content (the "2015Aug24_Aug28" page), but would highlight all instances of the name tagged on to the end. For example, " <code>http://xyzcorp/schedules/2015Aug24_Aug28/Billy_Bones</code>
" would show every instance of the name "Billy Bones" highlighted, as if a "Find" for that name was executed on the page via the browser.</p>
<p>I imagine something like this is required, client-side:</p>
<pre style="" class="default prettyprint prettyprinted">
<code>
<span class="kwd">var</span>
<span class="pln"> employee </span>
<span class="pun">=</span>
<span class="pln"> getLastURLPortion</span>
<span class="pun">();</span>
<span class="pln"></span>
<span class="com">// return "Billy_Bones" (or whatever)</span>
<span class="pln">
employee </span>
<span class="pun">=</span>
<span class="pln"> humanifyTheName</span>
<span class="pun">(</span>
<span class="pln">employee</span>
<span class="pun">);</span>
<span class="pln"></span>
<span class="com">// replaces underscores with spaces, so that it's "Billy Bones" (etc.)</span>
<span class="pln"></span>
<span class="typ">Highlight</span>
<span class="pun">(</span>
<span class="pln">employee</span>
<span class="pun">);</span>
<span class="pln"></span>
<span class="com">// this I have no clue how to do</span>
</code>
</pre>
<p>Can this be done in HTML/CSS, or is JavaScript or jQuery also required for this?</p>
</div>
Demo: http://plnkr.co/edit/rhfqzWThLTu9ccBb1Amy?p=preview
I need to set the attribute ID to a group of <div> elements which are within a parent <div>.
The parent <div> has an ID, and some of the child <div> have an ID but others don't.
The HTML code would be:
<div id="my_parent_div">
<div id="my_child_div_a">Here is some text A</div>
<div>Here is some text B</div>
<div>Here is some text C</div>
<div>Here is some text D</div>
</div>
After applying Javascript/JQuery it should look:
<div id="my_parent_div">
<div id="my_child_div_a">Here is some text A</div>
<div id="new_added_id_b">Here is some text B</div>
<div id="new_added_id_c">Here is some text C</div>
<div id="new_added_id_d">Here is some text D</div>
</div>
I tried the following:
<script type="text/javascript">
$('div').each(function(eq, el) {
el = $(el);
if(typeof(el.attr('id')) === "undefined") {
el.attr('id', 'div-' + eq);
}
});
</script>
But it will give IDs to all <div> without an ID in the whole HTML document. I need to set the IDs only to the child elements of #my_parent_div that do not have one and I would like to set specific IDs (instead of id="div-10", id="div-11", id=div-12)
I appreciate your suggestions
Your selector is $('div') which will target all div elements on the page. To make it only select div under #my_parent_div use this selector instead : $('#my_parent_div div')
The code will now look like this :
<script type="text/javascript">
$('#my_parent_div div').each(function(eq, el) {
el = $(el);
if(typeof(el.attr('id')) === "undefined") {
el.attr('id', 'div-' + eq);
}
});
</script>
Update:
Answering your question on the comment
If you want to have a specific id name for each element I would say you create an array listing all the names.
var ids = ["cat", "dog", "rat", "chicken"];
Then create a variable which will count every time it loops so you can use that to get the name on that array on a certain loop.
So putting it all together, will look like this :
var count = 0;
$('#my_parent_div div').each(function(eq, el) {
var ids = ["cat", "dog", "rat", "chicken"];
el = $(el);
if(typeof(el.attr('id')) === "undefined") {
el.attr('id', ids[count]);
count++;
}
});
I'd suggest the following:
// select the relevant element(s),
// set the 'id' property of those elements using prop():
$('#my_parent_div div').prop('id', function (i,v) {
// the first argument ('i') is the index of the current
// element from amongst the collection,
// the second ('v') is the current value of the property
// we're accessing:
// if the current id is an empty string (v === '') or
// it's undefined ('undefined === typeof v)
// we set the id to the string 'new_added_id_' plus
// the String created from the character-code of 97 ('a')
// plus the element's index in the collection. Otherwise,
// if the id is set we return the existing id:
return v === '' || 'undefined' === typeof v ? 'new_added_id_' + String.fromCharCode(97 + i) : v;
});
$('#my_parent_div div').prop('id', function(i, v) {
return v === '' || 'undefined' === typeof v ? 'new_added_id_' + String.fromCharCode(97 + i) : v;
});
div {
width: 80%;
margin: 0 auto 0.5em auto;
border: 2px solid #000;
}
div > div[id]::before {
content: 'ID: ' attr(id);
color: #f00;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="my_parent_div">
<div id="my_child_div_a">Here is some text A</div>
<div>Here is some text B</div>
<div>Here is some text C</div>
<div>Here is some text D</div>
</div>
External JS Fiddle demo, for experimentation.
References:
JavaScript:
String.fromCharCode().
typeof.
jQuery:
prop().