How to compute the baseline of text - javascript

I am trying to write a test to work out whether the text rendered inside an <input> has the same baseline as a label:
In order to do this, I would like to compute the baseline of the text that has been rendered in each element and compare the two values. Is this possible and if so, how is it done? If not, is there a better way to establish that the baseline of the two elements is the same?
I have found a way to determine the baseline of the label which seems to work reliably:
function getBaseline(element) {
var span = document.createElement("span");
span.setAttribute("style", "font-size:0");
span.innerText = "A";
jQuery(element).prepend(span);
return span.getBoundingClientRect().bottom;
}
However, this method doesn't work for the <input>, as I cannot insert the span in that case.

I found a way to get the computed baseline based on your function. The trick is to create a wrapper and apply all styles from the element to it. I can confirm that it works in Firefox (v38) and Chrome (v44) on OSX. Unfortunately it doesn't work proper in Safari.
DEMO
function getBaseline(element) {
element = jQuery(element);
var span = document.createElement("span");
span.setAttribute("style", "font-size:0");
span.innerText = "A";
var wrapper = document.createElement("span");
applyCSSProperties.call(wrapper, element);
element.wrap(wrapper);
element.before(span);
var computed = span.getBoundingClientRect().bottom;
span.remove();
element.unwrap(wrapper);
return computed;
}
function applyCSSProperties(element) {
var styles = {};
var comp = getComputedStyle(element[0]);
for(var i = 0; i < comp.length; i++){
styles[comp[i]] = comp.getPropertyValue(comp[i]);
}
jQuery(this).css(styles);
}

Related

Javascript - find all classes from array, grab and return inner text from classes

I have an array of class names that I want to search a page for. Then I'd like to find all those elements, grab the text inside, and append it to a div.
var div = document.createElement('div');
div.innerHTML = 'List of names from this page';
document.body.appendChild(div);
var classNameArray = ['user', 'username', 'fullname', 'profile-field', 'author', 'screen-name'];
for (var i = 0; i < classNameArray.length; i++) {
element = classNameArray[i];
getSuggestedAuthors(element);
function getSuggestedAuthors(element) {
var classes = document.getElementsByClassName(element);
var index;
for (var index = 0; index < classes.length; index++) {
var class = classes[index];
var textInsideClass = class.innerHTML;
div.appendChild(textInsideClass);
}
}
}
When I run this, it gives me:
Uncaught NotFoundError: An attempt was made to reference a Node in a context where it does not exist.
I believe the problem is occuring at var textInsideClass = class.innerHTML, because when I remove that, it simply grabs all the classes and appends them to the div. However, I'd like to only get the text inside the class.
Anyone know how to do this without jQuery? I'm injected this hs through Google Chrome's executeScript injection.
Thanks so much!
I think your issue is that appendChild only works with nodes. You might be better off just appending to innerHTML using something along the lines of a.innerHTML += f.innerHTML.
You should also be sure to move the getSuggestedAuthors function out of the loop. It works ok as it is, but it's much better form not to declare functions inside a loop.
If you only need to support chrome then all of the handy methods on the Array.prototype should exist :)
var a = document.createElement('div');
a.innerHTML = 'List of names from this page';
document.body.appendChild(a);
function getSuggestedAuthors(elements) {
for (var d = 0; d < elements.length; d++) {
a.appendChild(document.createTextNode(elements[d].innerText));//append loop items text to a
}
}
['user', 'username', 'fullname', 'profile-field', 'author', 'screen-name'].map(function(cls) {
return document.getElementsByClassName(cls);
}).forEach(getSuggestedAuthors);

Text Highlighting using native javascript no jquery

I am currently working in native JS and I am trying to build the highlight text feature in a contenteditable div. I have successfully built the highlight feature but I am encountering a problem when I want to toggle between the highlight and unhighlight text using a single button. So I am getting the selected text and the range of the selected text via
var selectedText = window.getSelection();
var range = selectedText.getRangeAt(0);
and I am wrapping the selected text using surroundContents that is a function of range object.
var wrapper = document.createElement("span");
wrapper.setAttribute("class","highlight");
But now when I am trying to unhighlight some part of the highlighted text and some part of plain text the natural behavior should unhighlight the highlighted text and highlight the plain text. To achieve this I am cloning the range via
var clone = range.cloneContents()
var nodeInBetween = clone.childNodes //array of nodes between the start and end nodes.
Now there are two problems I am facing. First I need to remove the span.highlight nodes and replace it with a TextNode again in order to make it unhighlight-ed and I need some method to wrap a textnode with a span. Unfortunately there is no way to wrap a textnode as one can for range variable.
I have experimented with a (recursive) highlighter method in this jsFiddle. It may be of use to you. The actual method:
function highLight(term,root,forElements,styleclass){
root = root || document.querySelector('body');
term = term instanceof Array ? term.join('|') : term;
if (!term) {throw TypeError('Highlighter needs a term to highlight anything');}
forElements = forElements && forElements instanceof Array
? forElements.join(',')
: /string/i.test(typeof forElements) ? forElements : '*';
styleclass = styleclass || 'highlight';
var allDiv = root.querySelectorAll(forElements),
re = RegExp(term,'gi'),
highlighter = function(a){return '<span class="'+styleclass+'">'+a+'</span>'};
for (var i=0; i<allDiv.length; i+=1){
// recurse children
if (allDiv[i].querySelectorAll(forElements).length){
highLight.call(null,term, allDiv[i],forElements,styleclass);
}
// replace term(s) in text nodes
var node = allDiv[i];
for (node=node.firstChild; node; node=node.nextSibling) {
if (node.nodeType===3){
var re = RegExp(term,'gi');
node.data = node.data.replace(re,highlighter);
}
}
}
//finally, replace all text data html encoded brackets
root.innerHTML = root.innerHTML
.replace(/</gi,'<')
.replace(/>/gi,'>');
}

Replace a textNode with HTML text in Javascript?

I was directed to the Linkify project on GitHub (https://github.com/cowboy/javascript-linkify) for finding and "linkifying" URLs and domains just floating in text.
It's awesome! It totally works on text!
However, I'm not quite sure how to make it work on a textNode which has the text I want to Linkify.
I understand the textNode only has textContent since.. it's all text. Since this Linkify function returns HTML as text, is there a way to take a textNode and "rewrite" the HTML within it with the Linkify output?
I've been playing with it on JSFiddle here: http://jsfiddle.net/AMhRK/9/
function repl(node) {
var nodes = node.childNodes;
for (var i = 0, m = nodes.length; i < m; i++) {
var n = nodes[i];
if (n.nodeType == n.TEXT_NODE) {
// do some swappy text to html here?
n.textContent = linkify(n.textContent);
} else {
repl(n);
}
}
}
You'll need to replace the textNode with an HTML element, like a span, and then set your linkified-text as that element's innerHTML.
var replacementNode = document.createElement('span');
replacementNode.innerHTML = linkify(n.textContent);
n.parentNode.insertBefore(replacementNode, n);
n.parentNode.removeChild(n);
Additionally to previous answer I propose more short way (based on jQuery):
$(n).replaceWith('Some text with <b>html</b> support');
where n - is textNode.
Or the native version
var txt = document.createElement("span");
txt.innerHTML = "Some text with <b>html</b> support";
node.replaceWith(txt);
where node is the textNode
Build on #AlexJeffcott's answer: Perf optimized version utilizing DocumentFragment instead of messing around with <span>, innerHTML and childNodes😁
const enhanceNodes = (textNodes) => {
textNodes.forEach((node) => {
const oldText = node.textContent;
const newText = fancyTextTranformation(oldText);
const fragment = document.createRange().createContextualFragment(newText);
node.replaceWith(fragment);
})
}
Build on Will Scott's accepted answer, if you do not wish to have to wrap everything in a span, you could do the following:
const enhanceNodes = (textNodes) => {
const renderNode = document.createElement('span');
textNodes.forEach((node) => {
const oldText = node.textContent;
renderNode.innerHTML = fancyTextTranformation(oldText);
node.replaceWith(...renderNode.childNodes);
})
}

Inserting arbitrary HTML into a DocumentFragment

I know that adding innerHTML to document fragments has been recently discussed, and will hopefully see inclusion in the DOM Standard. But, what is the workaround you're supposed to use in the meantime?
That is, take
var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();
I want both the div and the span inside of frag, with an easy one-liner.
Bonus points for no loops. jQuery is allowed, but I've already tried $(html).appendTo(frag); frag is still empty afterward.
Here is a way in modern browsers without looping:
var temp = document.createElement('template');
temp.innerHTML = '<div>x</div><span>y</span>';
var frag = temp.content;
or, as a re-usable
function fragmentFromString(strHTML) {
var temp = document.createElement('template');
temp.innerHTML = strHTML;
return temp.content;
}
UPDATE:
I found a simpler way to use Pete's main idea, which adds IE11 to the mix:
function fragmentFromString(strHTML) {
return document.createRange().createContextualFragment(strHTML);
}
The coverage is better than the <template> method and tested ok in IE11, Ch, FF.
Live test/demo available http://pagedemos.com/str2fragment/
Currently, the only way to fill a document fragment using only a string is to create a temporary object, and loop through the children to append them to the fragment.
Since it's not appended to the document, nothing is rendered, so there's no performance hit.
You see a loop, but it's only looping through the first childs. Most documents have only a few semi-root elements, so that's not a big deal either.
If you want to create a whole document, use the DOMParser instead. Have a look at this answer.
Code:
var frag = document.createDocumentFragment(),
tmp = document.createElement('body'), child;
tmp.innerHTML = '<div>x</div><span>y</span>';
while (child = tmp.firstElementChild) {
frag.appendChild(child);
}
A one-liner (two lines for readability) (input: String html, output: DocumentFragment frag):
var frag =document.createDocumentFragment(), t=document.createElement('body'), c;
t.innerHTML = html; while(c=t.firstElementChild) frag.appendChild(c);
Use Range.createContextualFragment:
var html = '<div>x</div><span>y</span>';
var range = document.createRange();
// or whatever context the fragment is to be evaluated in.
var parseContext = document.body;
range.selectNodeContents(parseContext);
var fragment = range.createContextualFragment(html);
Note that the primary differences between this approach and the <template> approach are:
Range.createContextualFragment is a bit more widely supported (IE11 just got it, Safari, Chrome and FF have had it for a while).
Custom elements within the HTML will be upgraded immediately with the range, but only when cloned into the real doc with template. The template approach is a bit more 'inert', which may be desirable.
No one ever provided the requested "easy one-liner".
Given the variables…
var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();
… the following line will do the trick (in Firefox 67.0.4):
frag.append(...new DOMParser().parseFromString(html, "text/html").body.childNodes);
#PAEz pointed out that #RobW's approach does not include text between elements. That's because children only grabs Elements, and not Nodes. A more robust approach might be as follows:
var fragment = document.createDocumentFragment(),
intermediateContainer = document.createElement('div');
intermediateContainer.innerHTML = "Wubba<div>Lubba</div>Dub<span>Dub</span>";
while (intermediateContainer.childNodes.length > 0) {
fragment.appendChild(intermediateContainer.childNodes[0]);
}
Performance may suffer on larger chunks of HTML, however, it is compatible with many older browsers, and concise.
createDocumentFragment creates an empty DOM "container". innerHtml and other methods work only on DOM nodes (not the container) so you have to create your nodes first and then add them to the fragment. You can do it using a painful method of appendChild or you can create one node and modify it's innerHtml and add it to your fragment.
var frag = document.createDocumentFragment();
var html = '<div>x</div><span>y</span>';
var holder = document.createElement("div")
holder.innerHTML = html
frag.appendChild(holder)
with jquery you simply keep and build your html as a string. If you want to convert it to a jquery object to perform jquery like operations on it simply do $(html) which creates a jquery object in memory. Once you are ready to append it you simply append it to an existing element on a page
Like #dandavis said, there is a standard way by using the template-tag.
But if you like to support IE11 and you need to parse table elements like '<td>test', you can use this function:
function createFragment(html){
var tmpl = document.createElement('template');
tmpl.innerHTML = html;
if (tmpl.content == void 0){ // ie11
var fragment = document.createDocumentFragment();
var isTableEl = /^[^\S]*?<(t(?:head|body|foot|r|d|h))/i.test(html);
tmpl.innerHTML = isTableEl ? '<table>'+html : html;
var els = isTableEl ? tmpl.querySelector(RegExp.$1).parentNode.childNodes : tmpl.childNodes;
while(els[0]) fragment.appendChild(els[0]);
return fragment;
}
return tmpl.content;
}
Here is a x-browser solution, tested on IE10, IE11, Edge, Chrome and FF.
function HTML2DocumentFragment(markup: string) {
if (markup.toLowerCase().trim().indexOf('<!doctype') === 0) {
let doc = document.implementation.createHTMLDocument("");
doc.documentElement.innerHTML = markup;
return doc;
} else if ('content' in document.createElement('template')) {
// Template tag exists!
let el = document.createElement('template');
el.innerHTML = markup;
return el.content;
} else {
// Template tag doesn't exist!
var docfrag = document.createDocumentFragment();
let el = document.createElement('body');
el.innerHTML = markup;
for (let i = 0; 0 < el.childNodes.length;) {
docfrag.appendChild(el.childNodes[i]);
}
return docfrag;
}
}
I would go with something like this..
function fragmentFromString(html) {
const range = new Range();
const template = range.createContextualFragment(html);
range.selectNode(template.firstElementChild);
return range;
}
// Append to body
// document.body.append(fragmentFromString(`<div>a</div>`).cloneContents())
This way you keep the content inside a Range object and you get all the needed methods for free.
You can find the list of all Range methods and properties here https://developer.mozilla.org/en-US/docs/Web/API/Range
Note: Remember to use detatch() method once you are done with it to avoid leaks and improve performance.
var html = '<div>x</div><span>y</span>';
var frag = document.createDocumentFragment();
var e = document.createElement('i');
frag.appendChild(e);
e.insertAdjacentHTML('afterend', html);
frag.removeChild(e);
To do this with as little lines as possible, you could wrap your content above in another div so you do not have to loop or call appendchild more than once. Using jQuery (as you mentioned is allowed) you can very quickly create an unattached dom node and place it in the fragment.
var html = '<div id="main"><div>x</div><span>y</span></div>';
var frag = document.createDocumentFragment();
frag.appendChild($​(html)[0]);

Find and change a div using a regular expression in Javascript

I am trying to first find a div using a regular expression (since its class name is somewhat dynamic).
Once found, I then need to place the div inside of a fieldset, so I end up having a final output of
<fieldset class="...">
<div class="the one I found">...</div>
</fieldset>
How can I do this in javascript?
Much thanks,
Steve
This is going to be difficult to do with regexes and ill-advised. For example, what if the div contains other divs? Finding the correct closing div tag is not something a regular expression can do because HTML is not a regular language.
On the other hand, this is a trivial one liner with jQuery:
$("div.someClass").wrap("<fieldset class='...'></fieldset>");
It can of course be done with vanilla Javascript DOM using something like:
var divs = document.getElementsByTagName("div");
for (var i=0; i<divs.length; i++) {
if (divs[i].className == "...") {
var fs = document.createElement("fieldset");
fs.className = "...";
var parent = divs[i].parentNode;
parent.insertBefore(fs, divs[i]);
fs.appendChild(divs[i]);
}
}
You of course need to fill in what class to put on the fieldset and change the test on the div to figure out if you need to manipulate it or not.
using jquery, you can try this:
var classes = $(document.body).html().match(/class="pattern"/g); // find classname matchin pattern
for(i in classes) {
var nodes = $('.'+classes[i].substr (7, str.length - 8));
nodes.wrap("<fieldset class='...' />");
}
window.onload = function() {
var params = {
has: "something"
};
// var fieldset = doc... get field the same as with div.
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {
if (params.has.indexOf(divs[i].className) > 0) {
// fieldset.innerHTML = divs[i].innerHTML;
divs[i].innerHTML = "<fieldset class=''> + divs[i].innerHTML + "</fieldset>";
}
}
}
No need to use regular expression, indexof method is sufficient. And no need to use jquery. Javascript has good string and array functions - use them, but the DOM is a mess.

Categories