How to get elements which have no children, but may have text? - javascript

the empty selector says that: Matches all elements that have no children (including text nodes).Finds all elements that are empty - they don't have child elements or text.
What i want is to get elements which has no children but may have text inside., how?
UPDATE:
Example:
I want select these elements which has no children but may have text, with syntax like this:
$('div:empty, a:empty, span, p:empty, td:empty, img, input').mousemove(myMouseOverHandler);

Get any element that doesn't have any other element:
$('*:not(:has(*))');

If an element has only text, children() will have a length of 0:
<div id="test1">
Hello World
</div>
<div id="test2">
<b>Hey there</b>
</div>
<script>
alert($("#test1").children().length); // alerts 0
alert($("#test2").children().length); // alerts 1 (the bold tag)
</script>
EDIT: In response to your edit, jQuery is awesome enough to let you do custom filters:
$.expr[':'].emptyOrText = function(e) {
return $(e).children().length == 0;
};
So, using the above against the HTML above, you could do this:
$('div:emptyOrText'); // will select #test1

I made a pure JavaScript function for anyone that does not want to use jQuery.
const getElementsWithNoChildren = (target) => {
let candidates;
if (target && typeof target.querySelectorAll === 'function') {
candidates = target.querySelectorAll('*');
}
else if (target && typeof target.length === 'number') {
candidates = target;
}
else {
candidates = document.querySelectorAll('*');
}
return Array.from(candidates).filter((elem) => {
return elem.children.length === 0;
});
};

Related

Get div element based on text

I have the code below. I want to retrieve the element that contains the text below. I don't know which div the text is in, so the whole page has to be searched.
$('*', 'body')
.addBack()
.contents()
.filter(function(){
return this.nodeType === 3;
})
.filter(function(){
// Text below: {launch=[MODULE]}
return this.nodeValue.indexOf('{launch=[MODULE]}') != -1;
})
.each(function(){
// How do I receive the element here?
console.log(this);
});
Thanks in advance!
In javascript you can collect all the <div> elements and then loop through them, checking the textContent property, each time.
Example:
// GRAB ALL THE DIVS
const allDivs = [...document.getElementsByTagName('div')];
// SET THE SEARCH PHRASE
const searchPhrase = '{launch=[MODULE]}';
// LOOP THROUGH ALL THE DIVS, LOOKING FOR THE SEARCH PHRASE
for (let div of allDivs) {
if (div.textContent !== searchPhrase) continue;
console.log(div);
}

jQuery closest (inside and outside DOM tree )

Given an element and any selector, I need to find the closest element which matches it, not matter if it's inside the element or outside of it.
Currently jQuery doesn't provide such traversing functionality, but there is a need. Here is the scenario:
A list of many items where the <button> element reside inside <a>
<ul>
<li>
<a>
<button>click me</button>
<img src="..." />
</a>
</li>
<li>
<a>
<button>click me</button>
<img src="..." />
</a>
</li>
...
</ul>
Or the <button> element might reside outside of the <a> element
<ul>
<li>
<a>
<img src="..." />
</a>
<button>click me</button>
</li>
<li>
<a>
<img src="..." />
</a>
<button>click me</button>
</li>
...
</ul>
The very very basic code would look like this:
$('a').closest1('button'); // where `closest1` is a new custom function
// or
$('a').select('> button') // where `select` can parse any selector relative to the object, so it would also know this:
$('a').select('~ button') // where the button is a sibling to the element
the known element is <a> and anything else can change. I want to locate the nearest <button> element for a given <a> element, no matter if that button is inside or outside of <a>'s DOM tree.
It would be very logical that native jQuery function "closest" would do as the name suggests and find the closest, but it only searches upwards as you all know. (it should have been named differently IMO).
Does anyone know any custom traversing function which does the above?
Thanks. (i'm asking you people because someone must have written this for sure but I was unlucky to find a lead on the internet)
Here is another attempt using the idea I mentioned in comment:
$(this).parents(':has(button):first').find('button').css({
"border": '3px solid red'
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/40/
It basically looks for the first ancestor that contains both the elements (clicked and target), then finds the target.
Performance:
With regard to speed, this is used at human interaction speeds, i.e. a few times per second maximum, so being a "slow selector" is irrelevant if it solves the problem, in a reasonably obvious way, with minimal code. You would have to click 100s of times per second to notice any different compared to a fast selector :)
None of the built-in selectors allow searching up and down the tree. I did create a custom findThis extension that allows you to do things like $elementClicked.('li:has(this) button') which would allow you to do something similar.
// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
// If we have a :this selector
if (selector.indexOf(':this') > 0) {
var ret = $();
for (var i = 0; i < this.length; i++) {
var el = this[i];
var id = el.id;
// If not id already, put in a temp (unique) id
el.id = 'id' + new Date().getTime();
var selector2 = selector.replace(':this', '#' + el.id);
ret = ret.add(jQuery(selector2, document));
// restore any original id
el.id = id;
}
ret.selector = selector;
return ret;
}
// do a normal find instead
return this.find(selector);
}
// Test case
$(function () {
$('a').click(function () {
$(this).findThis('li:has(:this) button').css({
"border": '3px solid red'
});
});
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/38/
note: Click the images/links to test.
A while ago I wanted to do the same for a completely DOM-based text editor, and needed to find the previous (ARR LEFT) and next (ARR RIGHT) text nodes, both up and down the tree. Based on this code I have made an adaptation suiting this question. Be warned, it's quite performance-heavy, but it is adapted to any scenario. There are two functions findPrevElementNode and findNextElementNode which both return an object with properties:
match - returns the closest matching node for the search or FALSE if none is found
iterations - returns the number of iterations done to find the node. This allows you to check whether the previous node is closer than the next or vice-versa.
The parameters are as follows:
//#param {HTMLElement} referenceNode - The node from which to start the search
//#param {function} truthTest - A function that returns true for the given element
//#param {HTMLElement} [limitNode=document.body] - The limit up to which to search to
var domUtils = {
findPrevElementNode: function(referenceNode, truthTest, limitNode) {
var element = 1,
iterations = 0,
limit = limitNode || document.body,
node = referenceNode;
while (!truthTest(node) && node !== limit) {
if (node.previousSibling) {
node = node.previousSibling;
iterations++;
if (node.lastChild) {
while (node.lastChild) {
node = node.lastChild;
iterations++;
}
}
} else {
if (node.parentNode) {
node = node.parentNode;
iterations++;
} else {
return false;
}
}
}
return {match: node === limit ? false : node, iterations: iterations};
},
findNextElementNode: function(referenceNode, truthTest, limitNode) {
var element = 1,
iterations = 0,
limit = limitNode || document.body,
node = referenceNode;
while (!truthTest(node) && node !== limit) {
if (node.nextSibling) {
node = node.nextSibling;
iterations++;
if (node.firstChild) {
while (node.firstChild) {
node = node.firstChild;
iterations++;
}
}
} else {
if (node.parentNode) {
node = node.parentNode;
iterations++;
} else {
return false;
}
}
}
return {match: node === limit ? false : node, iterations: iterations};
}
};
In your case, you could do:
var a = domUtils.findNextElementNode(
document.getElementsByTagName('a')[0], // known element
function(node) { return (node.nodeName === 'BUTTON'); }
);
var b = domUtils.findPrevElementNode(
document.getElementsByTagName('a')[0], // known element
function(node) { return (node.nodeName === 'BUTTON'); }
);
var result = a.match ? (b.match ? (a.iterations < b.iterations ? a.match :
(a.iterations === b.iterations ? fnToHandleEqualDistance() : b.match)) : a.match) :
(b.match ? b.match : false);
See it in action.
DEMO PAGE / GIST
I have solved working by logic, so I would first look inside the elements, then their siblings, and last, if there are still unfound items, I would do a recursive search on the parents.
JS CODE:
jQuery.fn.findClosest = function (selector) {
// If we have a :this selector
var output = $(),
down = this.find(selector),
siblings,
recSearch,
foundCount = 0;
if(down.length) {
output = output.add(down);
foundCount += down.length;
}
// if all elements were found, return at this point
if( foundCount == this.length )
return output;
siblings = this.siblings(selector);
if( siblings.length) {
output = output.add(siblings);
foundCount += siblings.length;
}
// this is the expensive search path if there are still unfound elements
if(foundCount < this.length){
recSearch = rec(this.parent().parent());
if( recSearch )
output = output.add(recSearch);
}
function rec(elm){
var result = elm.find(selector);
if( result.length )
return result;
else
rec(elm.parent());
}
return output;
};
// Test case
var buttons = $('a').findClosest('button');
console.log(buttons);
buttons.click(function(){
this.style.outline = "1px solid red";
})
I think using sibling selector (~) or child selector (>) will solve your purpose(What ever your case is!!).

CKEDITOR - Apply bold to numbered list including numbers

I am facing an issue with the numbered list in ckeditor. When I try to bold some text in li, only the text is getting bold, without the preceding number. This is how it looks like,
One
Two
Three
It should be like this
2. Two
When I check the source, I found the code like below
<li><strong>Two</strong></li>
I would like to know is there any way to change the working of bold button, so that it will add something like below
<li style="font-weight:bold">Two</li>
<p> Hello <strong>World</strong></p>
I tried to solve your problem.
My solution isn't the best, because I guess that create a bold plugin, that takes care about list items would be the best solution.
I make it without using jQuery; however, using it the code should became simpler and more readable.
First of all, we need to define something useful for the main task:
String trim. See this.
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
String contains. See this
String.prototype.contains = function(it) {
return this.indexOf(it) != -1;
};
First child element. The following function obtains the first child element, or not-empty text node, of the element passed as argument
function getFirstChildNotEmpty(el) {
var firstChild = el.firstChild;
while(firstChild) {
if(firstChild.nodeType == 3 && firstChild.nodeValue && firstChild.nodeValue.trim() != '') {
return firstChild;
} else if (firstChild.nodeType == 1) {
return firstChild;
}
firstChild = firstChild.nextSibling;
}
return firstChild;
}
Now, we can define the main two functions we need:
function removeBoldIfPresent(el) {
el = el.$;
var elStyle = el.getAttribute("style");
elStyle = (elStyle) ? elStyle : '';
if(elStyle.trim() != '' && elStyle.contains("font-weight:bold")) {
el.setAttribute("style", elStyle.replace("font-weight:bold", ''));
}
}
CKEDITOR.instances.yourinstance.on("change", function(ev) {
var liEls = ev.editor.document.find("ol li");
for(var i=0; i<liEls.count(); ++i) {
var el = liEls.getItem(i);
var nativeEl = el.$.cloneNode(true);
nativeEl.normalize();
var firstChild = getFirstChildNotEmpty(nativeEl);
if(firstChild.nodeType != 1) {
removeBoldIfPresent(el);
continue;
}
var firstChildTagName = firstChild.tagName.toLowerCase()
if(firstChildTagName == 'b' || firstChildTagName == 'strong') {
//TODO: you also need to consider the case in which the bold is done using the style property
//My suggest is to use jQuery; you can follow this question: https://stackoverflow.com/questions/10877903/check-if-text-in-cell-is-bold
var textOfFirstChild = (new CKEDITOR.dom.element(firstChild)).getText().trim();
var textOfLi = el.getText().trim();
if(textOfFirstChild == textOfLi) {
//Need to make bold
var elStyle = el.getAttribute("style");
elStyle = (elStyle) ? elStyle : '';
if(elStyle.trim() == '' || !elStyle.contains("font-weight:bold")) {
el.setAttribute("style", elStyle + ";font-weight:bold;");
}
} else {
removeBoldIfPresent(el);
}
} else {
removeBoldIfPresent(el);
}
}
});
You need to use the last release of CkEditor (version 4.3), and the onchange plugin (that is included by default in the full package).
CKEditor 4.1 remove your classes, styles, and attributes that is not specified in its rules.
If that's the problem, you might want to disable it by adding this line:
CKEDITOR.config.allowedContent = true;
Here is full code to use it:
window.onload = function() {
CKEDITOR.replace( 'txt_description' );
CKEDITOR.config.allowedContent = true; //please add this line after your CKEditor initialized
};
Please check it out here
<ul class="test">
<li><span>hello</span></li>
</ul>
.test li
{
font-weight:bold;
}
.test li span
{
font-weight:normal;
}

Jquery remove the innertext but preserve the html

I have something like this.
<div id="firstDiv">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</div>
I want to remove 'This is some text' and need the html elements intact.
I tried using something like
$("#firstDiv")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text("");
But it didn't work.
Is there a way to get (and possibly remove, via something like .text("")) just the free text within a tag, and not the text within its child tags?
Thanks very much.
Filter out text nodes and remove them:
$('#firstDiv').contents().filter(function() {
return this.nodeType===3;
}).remove();
FIDDLE
To also filter on the text itself, you can do:
$('#firstDiv').contents().filter(function() {
return this.nodeType === 3 && this.nodeValue.trim() === 'This is some text';
}).remove();
and to get the text :
var txt = [];
$('#firstDiv').contents().filter(function() {
if ( this.nodeType === 3 ) txt.push(this.nodeValue);
return this.nodeType === 3;
}).remove();
Check out this fiddle
Suppose you have this html
<parent>
<child>i want to keep the child</child>
Some text I want to remove
<child>i want to keep the child</child>
<child>i want to keep the child</child>
</parent>
Then you can remove the parent's inner text like this:
var child = $('parent').children('child');
$('parent').html(child);
Check this fiddle for a solution to your html
var child = $('#firstDiv').children('span');
$('#firstDiv').html(child);
PS: Be aware that any event handlers bounded on that div will be lost as you delete and then recreate the elements
Why try to force jQuery to do it when it's simpler with vanilla JS:
var div = document.getElementById('firstDiv'),
i,
el;
for (i = 0; i< div.childNodes.length; i++) {
el = div.childNodes[i];
if (el.nodeType === 3) {
div.removeChild(el);
}
}
Fiddle here: http://jsfiddle.net/YPKGQ/
Check this out, not sure if it does what you want exactly... Note: i only tested it in chrome
http://jsfiddle.net/LgyJ8/
cleartext($('#firstDiv'));
function cleartext(node) {
var children = $(node).children();
if(children.length > 0) {
var newhtml = "";
children.each(function() {
cleartext($(this));
newhtml += $('<div/>').append(this).html();
});
$(node).html(newhtml);
}
}

Javascript replace an element with a string

How can you change this
<div id='myDiv'><p>This div has <span>other elements</span> in it.</p></div>
into this
<div id='myDiv'>This div has other elements in it.</div>
hopefully using something like this
var ele = document.getElementById('myDiv');
while(ele.firstChild) {
replaceFunction(ele.firstChild, ele.firstChild.innerHTML);
}
function replaceFunction(element, text) {
// CODE TO REPLACE ELEMENT WITH TEXT
}
You can use innerText and textContent if you want to remove all descendant nodes, but leave the text:
// Microsoft
ele.innerText = ele.innerText;
// Others
ele.textContent = ele.textContent;
If you only want to flatten certain ones, you can define:
function foldIntoParent(element) {
while(ele.firstChild) {
ele.parentNode.insertBefore(ele.firstChild, ele);
}
ele.parentNode.removeChild(ele);
}
should pull all the children out of ele into its parent, and remove ele.
So if ele is the span above, it will do what you want. To find and fold all the element children of #myDiv do this:
function foldAllElementChildren(ele) {
for (var child = ele.firstChild, next; child; child = next) {
next = child.nextSibling;
if (child.nodeType === 1 /* ELEMENT */) {
foldIntoParent(child);
}
}
}
foldAllElementChildren(document.getElementById('myDiv'));
If you're not opposed to using jQuery, stripping the HTML from an element and leaving only the text is as simple as:
$(element).html($(element).text());
You can just take the innerText
console.log(ele.innerText)

Categories

Resources