JavaScript document.execCommand remove formatBlock formatting? - javascript

If I format a piece of text on a page like this:
document.execCommand('formatBlock', false, 'h1');
What do I do to remove this formatting?

I suppose document.execCommand('removeFormat',false,false) would do it?
Issuing document.execCommand('formatBlock', false, 'div') on the <h1>-block will remove the <h1>-tag and replace it with a <div>-tag 1. Would that be viable?
1 If you're not using IE that is

I clear the effect of h1 using this:
document.execCommand('formatBlock', false, 'p');
You have changed its format to h1, so we can change it back to normal paragraph format in the same way.
If your put each paragraph in a <div> , you can also use this:
document.execCommand('formatBlock', false, 'div');
to set the format to the same as other blocks.

I had the same problem where I need to delete the h1 tag wrapping my text.
What I did was get the parent node of the selected text:
var elem_parent_node =
window.getSelection().getRangeAt(0).startContainer.parentNode;
And then check if it's nodeName is "H1"; if yes, then store the selected text in a selected_text variable and then delete the node itself:
elem_parent_node.remove();
Then,
document.execCommand('insertText', false, select_text);

To replace the h1 with its text content node, then use the code below.
For a complete code, you would need to add some checks to make sure you remove what you want.
const button = document.getElementsByTagName('button')[0]
button.addEventListener('click', event => {
const selection = window.getSelection()
if (!selection.isCollapsed) {
selection.anchorNode.parentNode.replaceWith(selection.anchorNode)
}
})
<div contenteditable="true">
<p>Some text</p>
<h1>Select me then click the button</h1>
<p>Some text</p>
</div>
<button>Click to remove H1</button>

you may have to find the parent tag, then use innerHTML to get the text and replace the original data between the parent tag and end-tag with the innerHTML. This would however remove all formatting.

Seems that you cannot "undo" a formatBlock. You can always replace one "formatBlock" with some other "formatBlock" as long as it is another block level HTML tag.

Proposed solutions above are feasible, although they don't proof against the parent beeing the conteneditable-container (div?), so this could potentially remove/destroy the writable area.
A bit safer workaround for this issue may be to replace the "formatBlock" with a less used one, as f.ex. <address> and then remove this <address> with a regexp-replacer:
function Header() { //formatBlock: "h1"-"h6", "p", "pre", "div", "dd", "dt" or unformat it(=empty) - except using "address" (⇓see⇓)
var Fo = prompt('Please enter: h1-h6(=number), p, pre, ...','1');
if (Number(Fo)) {
Fo = 'h' + Fo; //format with h1-h6 directly by entering numbers - or p, pre, etc...
document.execCommand('formatblock', false, Fo);
} else if (Fo=='') { //if let empty, then unformat the block:
document.execCommand('formatBlock', false, 'address'); //using "address" as a temporary substitute - hoping that it's not used anywhere else in the document...
let inner = document.getElementById('content').innerHTML; //internal code of document
inner = inner.replace(/<address>/g,'<br>').replace(/n\n\n|\n\n|\n\s/g,'\n'); //Regex deleting (all) <address>-headings (and spaces) from document, replacing them with a line-break
document.getElementById('content').innerHTML = inner; //restore the content with replaced paragraph's
} else { document.execCommand('formatblock', false, Fo) }
}

Related

JQuery is() returning true

I have this string that can represent two things either some text or an anchor tag with text. I wrote something to always return the text like follows:
$(text).is('a') ? $(text).text() : text;
My logic is that if text is an anchor tag ? return the content of the anchor tag : if it's not then it's already just text so return that.
My question is that why does the following expression return true:
$('a').is('a');
Is is checking for the letter or anchor element? How can I check if my text is an anchor tag.
I would prefer not using any regular expressions
Edit:
I have a variable x that can have these values:
x = 'some text' or x = '<a>some text</a>'
How can I always extract the text from my variable x.
'a' is a valid query selector. It will select all the anchor elements in your document. So $('a').is('a') is saying "find anchor tags in the document and tell me if they are anchor tags" -- this will always be true unless there are no anchor tags in your document.
I have this string that can represent two things either some text or an anchor tag with text. I wrote something to always return the text like follows:
$(text).is('a') ? $(text).text() : text;
My logic is that if text is an anchor tag ? return the content of the anchor tag : if it's not then it's already just text so return that.
If possible, I would avoid having text be vague like that.
You certainly can't just dump the text into $() and assume all will be well. For instance, $("Some text here") searches the DOM for elements with the tag here inside elements with the tag text inside elements with the tag Some.
You've said you want to differentiate between
text = "Some text here"
and
text = "<a>Some text here</a>"
I'd just look at the string. Inspired partially by jQuery's check for whether what you pass it is a selector or HTML (here), how about:
text = text.trim();
if (text.substring(0, 2) === "<a" && text.substr(-1) === ">") {
text = $(text).text();
}
or similar?
But again, I'd avoid putting myself in this position in the first place if you have any choice.
Just set the string you have to an element's html. Grab the text of the element and this way you do not worry about if it is an anchor or plain text.
function getText (content) {
var div = document.createElement("div")
div.innerHTML = content
return div.textContent
}
console.log('some text', getText('some text'))
console.log('<a>some text</a>', getText('<a>some text</a>'))
If you want to use jQuery
function getText (content) {
return $("<div></div>", {html: content}).text()
}
console.log('some text', getText('some text'))
console.log('<a>some text</a>', getText('<a>some text</a>'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I have been unable to replicate the issue as you explained it. Please see: https://api.jquery.com/is/
Here is my testing:
$(function() {
var tableData = [
"Text 1",
"Text 2",
"<a>Text 3</a>",
"Text 4"
];
function isLink(el) {
return $(el).is("a");
}
var x = [];
$.each(tableData, function(i, s) {
x.push(isLink(s) ? $(s).text().trim() : s);
});
$(".results").html(x.join(", "));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="results"></div>
You can pass Text, Element, or even a jQuery Object to the function.

How to wrap DOM keywords in a span tag

I am writing a JavaScript script that will walk the DOM and wrap a specific keyword in a <span> tag. I want my script to wrap any occurrences of the word pan in a <span> so I can style it using <span style='color: red'. I don't actually want to use the word, pan, I am just using it as an example.
I have already reviewed many similar posts here, but none of them solve my problem. Most are either nuclear, over-complicated and confusing, or over-simplified and do not work as I've intended.
Here is what I have written so far:
<html>
<body>
<p>My <span style='font-weight: bold'>favorite</span> kitchen item is the pan.</p>
<p>A pan can also be used as a weapon.</p>
<script>
// walk the document body
function walk (node) {
// if text node
if (node.nodeType == 3) {
// will always be an element
const parent = node.parentNode;
// ignore script and style tags
const tagName = parent.tagName;
if (tagName !== 'SCRIPT' && tagName !== 'STYLE') {
// wrap occurrences of 'pan' in a red `<span>` tag
const span = '<span style="color: red">pan</span>';
parent.innerHTML = parent.innerHTML.replace (/pan/g, span)
}
}
node = node.firstChild;
while (node) {
walk (node);
node = node.nextSibling;
}
}
walk (document.body)
</script>
</body>
</html>
This code runs as intended most of the time. However, in this example, it doesn't. If you were to run this code, this would be the result.
I know what is causing this. However, I have no idea how to resolve it.
Two of the text nodes, My and kitchen item is the pan. have a parent element with the following innerHTML: My <span style="font-weight: bold">favorite</span> kitchen item is the pan. The "pan" in <span> is being replaced, and is causing the problem.
If I use parentNode.textContent instead of parentNode.innerHTML, it does not wrap it in a <span> tag, it inserts it as visible text.
I understand this could be fixed by changing /pan/g to /\bpan\b/g, but that only fixes this example I created. I need the <span> tag to be only inserted into text content, and not tag names or other HTML.
What should I do?
Search a given htmlString with an escaped search string. Doing so (with appropriate escaping) will help avoid problems like matching HTML tags (ex. <span>) or substrings (ex. Pandora).
/*
highlight(selector, string)
# Params:
selector [String]: Same syntax as CSS/jQuery selector
string [String]: Seach string
*/
// A: Get the htmlString of the target's content
// B: Escape the search string
// C: Create a RegExp Object of the escaped search string
// D: Find and replace all matches with match wrapped in a <mark>
// E: Remove original HTML
// F: Insert new HTML
function highlight(selector, string) {
let dom = document.querySelector(selector);
let str = dom.innerHTML; //A
let esc = `(?!(?:[^<]+>|[^>]+<\\/a>))\\b(${string})\\b`; //B
let rgx = new RegExp(esc, "gi"); //C
let txt = str.replace(rgx, `<mark>$1</mark>`); //D
dom.replaceChildren(); //E
dom.insertAdjacentHTML('beforeend', txt); //F
}
highlight('body', "pan");
<html>
<body>
<p>My <span style='font-weight: bold'>favorite</span> kitchen item is the pan.</p>
<p>A pan can also be used as a weapon.</p>
<p>Pan was the Greek god of the wild.</p>
<p>Eboli was breifly a pandemic threat.</p>
</body>
</html>

TinyMCE - Button to wrap selection not selecting surrounding tags

In WordPress I have added a custom editor button to get the contents of a selection and wrap it in a shortcode. The content is not getting the tags surrounding the selection.
For example:
<h1>Heading text</h1>
becomes
<h1>[shortcode]Heading Text[/shortcode]</h1>
Not
[shortcode]<h1>Heading Text</h1>[/shortcode]
It does work for
<h1>Header</h1>
<h2>Sub-Header</h2>
becomes
[shortcode]<h1>Header</h1>
<h2>Sub-Header</h2>[/shortcode]
This is the code I have.
(function() {
tinymce.create('tinymce.plugins.zgwd', {
init : function(ed, url) {
ed.addButton('headerblock', {
title : 'Add a Header Block',
cmd: 'headerblock',
});
ed.addCommand('headerblock', function() {
var selected_text = ed.selection.getContent({ format: 'html' });
var return_text = '';
if( !(selected_text && selected_text.length > 0) ) {
selected_text = 'Add header block text here';
}
return_text = '[header-block]' + selected_text + '[/header-block]';
ed.execCommand('mceInsertContent', 0, return_text);
});
},
createControl : function(n, cm) {
return null;
},
});
tinymce.PluginManager.add('zgwd', tinymce.plugins.zgwd);
})();
How can I get the content including the <h1> tags (or similar). Am I using the wrong function to get the content or is there a setting I am missing somewhere? I tried .getnode() but get the same result.
If you want to do something with a single block tag, getNode is your best bet. If you look at this Fiddle with the console open in Dev Tools, try selecting one line, and then try selecting both. getNode() and getContent() behave a little differently based on selection.
getNode() Returns the selected element, but, if you have more than one element selected, it returns the common ancestor (in this case body).
getSelection() will return tags if you select multiple blocks, which is why it works when trying to surround more than one block tag.

Dynamically add ID to header based on header name?

I'm using Jquery for a small project and I have run into an issue.
I'm trying to create a small script that will look at a page, and automatically assign an ID to H1 headers based on the header text, i.e:
<h1> This is a Test </h1>
to
<h1 id="This_is_a_Test"> This is a Test</h1>
I've been able to select every H1, but I haven't been able to modify the ID based on the header text.
var $headers = $('.body h1);
$headers.attr("id", not sure exactly how to pass headers text to this?);
I'm obviously very much a novice, but would appreciate some help in this!
Thanks!
If you want to do this for every h1 element you have in your page you can do something like that:
$(function() {
$('h1').each(function() {
$(this).attr('id', $(this).text().replace(/ /g, '_'))
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Some text</h1>
<h1>Some other text</h1>
The function will take every h1 element, for each it will set the value of the id attribute (using the attr function) to the text() of that h1 element (while changing every space with underscore).
Use a jQuery's .attr() with the callback option to read the text of the headers, converting it to snake case, and setting it as the id:
var $headers = $('.body h1');
$headers.attr("id", function() {
return $(this)
.text() // get the h1 text
.trim() // remove spaces from start and the end
.toLowerCase() // optional
.replace(/\s/g, '_'); // convert all spaces to underscores
});
console.log($headers[0]);
console.log($headers[1]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="body">
<h1>This is a Test </h1>
<h1>This is a another Test </h1>
</div>
First you need to generate the id by using the text method for h1.
var $headers = $('.body h1'); // select the element
var text = $headers.text(); // get the text of the element
var id = text.trim().split(' ').join('_'); // split by space and join using _
$headers.attr("id", id);

How to change text inside span with jQuery, leaving other span contained nodes intact?

I have the following HTML snippet:
<span class="target">Change me <a class="changeme" href="#">now</a></span>
I'd like to change the text node (i.e. "Change me ") inside the span from jQuery, while leaving the nested <a> tag with all attributes etc. intact. My initial huch was to use .text(...) on the span node, but as it turns out this will replace the whole inner part with the passed textual content.
I solved this with first cloning the <a> tag, then setting the new text content of <span> (which will remove the original <a> tag), and finally appending the cloned <a> tag to my <span>. This works, but feels such an overkill for a simple task like this. Btw. I can't guarantee that there will be an initial text node inside the span - it might be empty, just like:
<span class="target"><a class="changeme" href="#">now</a></span>
I did a jsfiddle too. So, what would be the neat way to do this?
Try something like:
$('a.changeme').on('click', function() {
$(this).closest('.target').contents().not(this).eq(0).replaceWith('Do it again ');
});
demo: http://jsfiddle.net/eEMGz/
ref: http://api.jquery.com/contents/
Update:
I guess I read your question wrong, and you're trying to replace the text if it's already there and inject it otherwise. For this, try:
$('a.changeme').on('click', function() {
var
$tmp = $(this).closest('.target').contents().not(this).eq(0),
dia = document.createTextNode('Do it again ');
$tmp.length > 0 ? $tmp.replaceWith(dia) : $(dia).insertBefore(this);
});
​Demo: http://jsfiddle.net/eEMGz/3/
You can use .contents():
//set the new text to replace the old text
var newText = 'New Text';
//bind `click` event handler to the `.changeme` elements
$('.changeme').on('click', function () {
//iterate over the nodes in this `<span>` element
$.each($(this).parent().contents(), function () {
//if the type of this node is undefined then it's a text node and we want to replace it
if (typeof this.tagName == 'undefined') {
//to replace the node we can use `.replaceWith()`
$(this).replaceWith(newText);
}
});
});​
Here is a demo: http://jsfiddle.net/jasper/PURHA/1/
Some docs for ya:
.contents(): http://api.jquery.com/contents
.replaceWith(): http://api.jquery.com/replacewith
typeof: https://developer.mozilla.org/en/JavaScript/Reference/Operators/typeof
Update
var newText = 'New Text';
$('a').on('click', function () {
$.each($(this).parent().contents(), function () {
if (typeof this.tagName == 'undefined') {
//instead of replacing this node with the replacement string, just replace it with a blank string
$(this).replaceWith('');
}
});
//then add the replacement string to the `<span>` element regardless of it's initial state
$(this).parent().prepend(newText);
});​
Demo: http://jsfiddle.net/jasper/PURHA/2/
You can try this.
var $textNode, $parent;
$('.changeme').on('click', function(){
$parent = $(this).parent();
$textNode= $parent.contents().filter(function() {
return this.nodeType == 3;
});
if($textNode.length){
$textNode.replaceWith('Content changed')
}
else{
$parent.prepend('New content');
}
});
Working demo - http://jsfiddle.net/ShankarSangoli/yx5Ju/8/
You step out of jQuery because it doesn't help you to deal with text nodes. The following will remove the first child of every <span> element with class "target" if and only if it exists and is a text node.
Demo: http://jsfiddle.net/yx5Ju/11/
Code:
$('span.target').each(function() {
var firstChild = this.firstChild;
if (firstChild && firstChild.nodeType == 3) {
firstChild.data = "Do it again";
}
});
This is not a perfect example I guess, but you could use contents function.
console.log($("span.target").contents()[0].data);
You could wrap the text into a span ... but ...
try this.
http://jsfiddle.net/Y8tMk/
$(function(){
var txt = '';
$('.target').contents().each(function(){
if(this.nodeType==3){
this.textContent = 'done ';
}
});
});
You can change the native (non-jquery) data property of the object. Updated jsfiddle here: http://jsfiddle.net/elgreg/yx5Ju/2/
Something like:
$('a.changeme3').click(function(){
$('span.target3').contents().get(0).data = 'Do it again';
});
The contents() gets the innards and the get(0) gets us back to the original element and the .data is now a reference to the native js textnode. (I haven't tested this cross browser.)
This jsfiddle and answer are really just an expanded explanation of the answer to this question:
Change text-nodes text
$('a.changeme').click(function() {
var firstNode= $(this).parent().contents()[0];
if( firstNode.nodeType==3){
firstNode.nodeValue='New text';
}
})
EDIT: not sure what layout rules you need, update to test only first node, otherwise adapt as needed

Categories