TinyMCE: How to insert content and select it? - javascript

How does one insert, or better, replace the current selection with some content and then select it?
Here's my text: Hello nice world!
As you can see nice is selected by the user. Now he clicks a button and this code is run:
editor.execCommand('mceReplaceContent', 'nasty');
This works just fine, the result is: Hello nasty world, but nothing is selected.
How do I make it automatically select nasty in the result content?
This seems like a very natural thing for one to want to do, but can't seem to find a straight-forward solution. I need this to work in mostly two cases 1) I am wrapping the selected text in a f.e. span element or 2) I am removing the wrapping span element.
I know there are better ways of dealing with nodes, but I'm more concerned about the pure text scenario right now.
Thanks in advance!
P.S. I am using TinyMCE 3 not 4.

I found this in the docs (API 3.x)
// Sets some contents to the current selection in the editor
tinyMCE.activeEditor.selection.setContent('Some contents');
// Selects the first paragraph found
tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]);
The setContent function does practically the same as execCommand('mceReplaceContent'). I did not found something like the easy DOM properties selectionStart & selectionEnd.

Related

Place tags around certain text within contenteditable without moving cursor

I am working on a simple (I thought) word processor. It uses contenteditable. I have a list of words that I want to always appear highlighted.
<article contenteditable="true" class="content">
<p>Once upon a time, there were a couple of paragraphs. Some things were <b>bold</b>, and other things were <i>italic.</i></p>
<p>Then down here there was the word highlight. It should have a different color background.</p>
</article>
So basically what I need is a way to wrap a word in <span> tags. This has proven more difficult than I expected.
Here was what I tried first:
var text = document.querySelector('article.content').innerHTML
start = text.indexOf("highlight"),
end = start + "highlight".length;
text = text.splice(end, 0, "</span>");
text = text.splice(start, 0, "<span>");
document.querySelector('article.content').innerHTML = text;
It uses the splice method found here.
And it does exactly what I need it to do, with one big issue: the cursor gets moved. Because all the text is replaced, the cursor loses its place, which isn't a good thing for a text editor.
I've also tried a couple times using document.createRange, but the issue is that while given the start and end points of a range only includes visible characters, text.indexOf("highlight") gives the index including the tags and such.
A few ideas which I'm not sure how to execute:
Figure out where the cursor begins and place it there again after using the code above
Find the difference in indexes between createRange and indexOf
Maybe there's already a library with this kind of functionality that I just can't find
Thank you for your help!
Firstly, I would recommend against doing this by manipulating innerHTML. It's inefficient and error-prone (think of the case where the content contains an element with a class of "highlight", for example). Here's an example of doing this using DOM methods to manipulate the text nodes directly:
https://stackoverflow.com/a/10618517/96100
Maintaining the caret position can be achieved a number of ways. You could use a character offset-based approach, which has some disadvantages due to not considering line breaks implied by <br> and block elements but is relatively simple. Alternatively, you could use the selection save and restore module of my Rangy library, which may be overkill for your needs, but the same approach could be used.
Here is an example using the first approach:
http://jsbin.com/suwogaha/1

Quick tumblr boo-lean + Getting text function

James here. Quick, and simple question for you guys. I'm working with a tumblr theme which uses a text option, which means the user can type whatever they want, and it'll display on the blog. The text options however do not work with javascript, so I can't use the text option for amount of columns and the text option for spacing between posts in my script that organizes the whole page. I know there is a way, but I don't know how to do this, to just create an invisible div with the boo lean text in it, and then use jQuery to get the text inside the div to use as a variable? I was thinking like .text(); or .html(); but I have no clue. Any codes or help would be greatly appreciated. I'm new at this jquery thing, so it'd mean a lot.
EDIT: If this is confusing for anyone, I basically need to use jQuery to get text inside an invisible element and use that text as a variable.
I'm not sure what you mean by 'boolean' here, but per your edit you want:
$('#id-of-element').text()
if you only want the text in the div or,
$('#id-of-element').html()
if you want the HTML. So if the div contains:
<h1>foo</h1>,
text() will give you "foo", while html() will give you "<h1>foo</h1>".

replace text function not working in explorer

I have a js replace function to replace text next to two radio buttons on a pre set form.
Script is as follows.
document.body.innerHTML=document.body.innerHTML.replace("Payment by <b>Moneybookers</b>
e-wallet<br>","");
document.body.innerHTML=document.body.innerHTML.replace("Maestro, Visa and other credit/debit cards by <b>Moneybookers</b>","Pago con Diners Club, Mastercard o Visa");}onload=x;
The script works fine in Chrome and Firefox, however, the script is not actioned in Explorer.
I believe it has something to do with there being , / - within the text I am replacing? When I use the function to replace text with no , / - in the text - it works fine in explorer, however, for example when I try to replace text.. - "Maestro, Visa and other credit/debit cards by Moneybookers" this does not work in explorer.. I'm assuming because of the coma and forward slash. Honestly I've tried everything but just can not get this to work. Any help would be greatly appreciated!!
Not sure whether it's related (I'm a Mac user without IE) but you shouldn't use multiline strings. Use \n instead.
What is returned by innerHTML varies from one browser to an other, because there is no standard about it (the content will be the same, but the way it's displayed can be different). Doing replace like that is likely to fail on some browser. You should just take an other approach to do your replace.
A better approach would be to wrap the text you want to replace with a span, this way you can more easily target the content you want to replace.
<span id="thatFirstThing">Payment by <b>Moneybookers</b>e-wallet<br></span>
An after you can do
document.getElementById("thatFirstThing").innerHTML = "";
P.S.: Doing innerHTML replace on the body also has a huge side-effect. Since you are replacing the content of your hole page. All the event handler that where bind on your page will disappear.
Edit: If you can't modify the HTML page, it's a little bit more tricky, because the DOM is not well adapted to do such thing. What you could do is to target parent element by navigating through the DOM with document.getElementById and childNodes. And once you have your parent element just write the new content you want, without doing replace.
In the end it would look something like this :
document.getElementById("someSection").childNodes[0].childNodes[1].childNodes[0].innerHTML = "";

Wrap highlighted text within textarea with strong tags using javascript/jquery

I am looking to create a javascript/jquery function to wrap a piece of highlighted text from a textarea in strong tags - similar to the WYSIWYG editor here.
Is this possible and if so can you point me in the right direction.
EDIT:
OK so here's a hopefully clearer description of what I want...
I have a textbox on my page which I can type in.
I then want to be able to highlight a part of this text and wrap the highlighted part in <strong> tags
So if the text box had the words one two three and I highlighted the word "two", I want to be able to wrap that word in the strong tags - so becoming one <strong>two</strong> three
Hope this is clearer... I know there are plugins out there but I don't need the full WYSIWYG functionality.
My Rangy inputs (terrible name, I know) jQuery plug-in does this.
Example code:
$("#foo").surroundSelectedText("<strong>", "</strong>");
jsFiddle: http://jsfiddle.net/aGJDa/
I love Rangy! Use it often! But I didn't want to include the whole thing just for this little application, so I did it using document.execCommand to wrap the selected text, then used the href (third parameter of the CreateLink execCommand) to find the element, wrap it with what I wanted, and then remove the link:
document.execCommand('CreateLink', false, 'uniqueid');
var sel = $('a[href="uniqueid"]');
sel.wrap('<strong />')
sel.contents().unwrap();
document.execCommand is supported by all major browsers so you should be safe hacking it this way. In the browsers I've tested, the browser itself will close and open tags for you, so if you're selecting from the middle of one html tag to the middle of another, it should nest the tags correctly.

How do I get just the visible text with jQuery (or Javascript)?

I have website that converts Japanese Kanji into Romaji (roman letters):
and the output shows and hides with CSS what the user needs to see depending on their input criteria. For example:
<div id="output"><span class="roman">watashi</span> <span class="english">I</span></div>
The interface allows the user to flip between and output of watashi or I depending on what they want to see. The CSS hides one or the other using jQuery and a toggle button. (the hiding mechanism involves simple adding a class to the body and letting CSS do its thing).
The problem is that when users copy/paste the text into Word it copies everything. So I decided to use a system to copy paste the text using JavaScript and jQuery, but the problem repeats itself:
$('#output').text() outputs watashi I even if I is invisible on the page itself rather than watashi. Is there any way to get just the visible text?
the other solutions did not give me what I needed.
Short Answer
my answer is :
$('#output *:not(:has(*)):visible').text()
plunkr
TL;DR
The problem with marcgg's solution
You should not ask the text of all element under some root element..
why? - it will repeat output and ignore hidden flag
lets look at a simple example
<div id="output" class="my-root">
<div class="some-div">
<span class="first" style="display:none"> hidden text </span>
<span class="second" > visible text </span>
</div>
<div>
now if I do $('#output').children(":visible").text()
I will get .some-div and .second..
when in fact .some-div is of no concern to me..
when I ask for text() on those elements, .some-div will return the hidden text as well..
so technically marcgg's solution is wrong IMHO...
The reason for my answer
Now, in order to properly answer the question, we have to make an assumption. One that, for me, seems reasonable enough.
The assumption is that text only appears in leaf elements..
So we won't see something like this:
<div id="output" class="my-root">
<div class="some-div">
<span class="first" style="display:none"> hidden text </span>
<span class="second" > visible text </span>
</div>
some text here..
<div>
Why does this assumption seem reasonable to me? two reasons:
Because it is hard to maintain a page that is constructed this way - and with time people with experience learn that and avoid it.
It is easy to convert your html to such a structure. just wrap parents' text with spans. So even if this assumption does not exist right now, it is easy to get there.
With that assumption, what you want to do is request all leaf elements (elements without children) , filter out the visible, and ask for their text..
$('#output *:not(:has(*)):visible').text()
This should generate the correct result.
Gotta have text outside leaf element?
the comments suggest sometimes you just got to have text outside leaf element
<div> This is some <strong style="display:none"> text </strong> </div>
As you can see, you have <strong> as a leaf and it is common to have text outside it like in this example.
You could go around it with the workaround I suggest above.. but what if you can't?
You can clone the dom and then remove all hidden elements.
The problem here is that in order for :visible selector or :hidden selectors to work, I must have the dom element on the document (which means actually visible to the user).
And so, this method comes with some side effects, so be careful.
Here is an example
for this html
<div id="output" class="my-root">
<span>
some text <strong style="display:none">here.. </strong>
</span>
</div>
This javascript works
$(function(){
var outputClone = $('#output').clone();
$('#output :hidden').remove();
console.log($('#output').text()); // only visible text
$('#output').replaceWith(outputClone);
console.log($('#output').text()); // show original state achieved.
})
see plunker here
as mentioned - side effects may appear like a momentary flicker, or some initialization script that should run.. some may be avoided with some original thinking (div with size 1px/1px to contain the clone alongside original content?) depending on your scenario.
Use the :visible selector of jQuery
In your case I think you want to do:
$('#output').children(":visible").text()
Try this in modern browsers (here 'element' is a non-JQuery DOM object):
function getVisibleText(element) {
window.getSelection().removeAllRanges();
let range = document.createRange();
range.selectNode(element);
window.getSelection().addRange(range);
let visibleText = window.getSelection().toString().trim();
window.getSelection().removeAllRanges();
return visibleText;
}
then:
getVisibleText(document.getElementById('output'));
Guy has the correct answer.
However, I was dealing with a "this" object, so to get his answer to work you need to use the following syntax...
$('*:not(:has(*)):visible', this).text()
var lookup = function(element, text) {
//DFS Recursive way of finding text on each level
//Visible only works on elements that take up space(i.e. not fixed position elements)
var results = element.children(':visible');
//Look at the text at each level with the children removed
var newText = '';
results.each(function(index, value) {
newText += $(value).clone()
.children()
.remove()
.end()
.text();
});
var moreResultText = '';
results.each(function(index, value) {
moreResultText += lookup($(value), text);
})
if (results.length > 0) {
return text + newText + moreResultText;
} else {
return text;
}
};
lookup($('#output'), ''));
Most of the other functions fall apart when run on large sections of a page, this should be a more accurate way to determine what is actually displayed to the user, without corrupting the page, and without returning text that is not visible to the user.
Be careful of course, this does not preserve any sense of formatting, and the spacing of the output may not be correct between elements. Also, it probably does not correctly order the returned text, in these aspects its usages will be limited. Another consideration is the real definition of visible is a little hard to nail down, but for this example I accept that ":visible" works for most common cases.
I use it to check if a page contains visible text(just run it on the body element), but it would probably work for this example too.
Instead of hiding a span, remove the span element and keep a reference to it. When the user clicks on the toggle button, remove the other one and insert the one you kept a reference to. The user won't be able to select something that isn't in the DOM anymore.

Categories