Div Editable and More... More - javascript

Well,
I need to replace a word, in a div contentEdible property on, by the same word but formatted...
Like this:
<div> My balls are big </div>
To this (replace the word: balls):
<div> My <font style="color:blue;">balls</font> are big </div>
In a contentEditable this happens dinamically, while the user type the text the replacements happens. I think that a simple event onkeydown, onkeyup, or onkey press, can solve this part.
But, the trouble is with the caret, that after all that i tryed, it stay before the word replaced, when should be stay after. I tryed write some js code, tryed find some jquery scripts, but all them failed in this situation...
Any one has some ideia, or trick ?
I think:
--> Record the length of the word unformatted.
--> Delete this word
--> Put new word formatted.
--> Walk with the caret, to position based this formatted word length.
--> Is it?
PS: I have to considerate a word in any place of this div.
I don't know how to write this code that do what i say above.
Correct me, if i'm wrong.
Since yet, thanks!
Edit[1]: I want that this works on Mozilla Firefox, specificlly;

I only have IE6/7 on this machine, but maybe you can apply the concept to other browser versions of Ranges (or maybe this is cross-browser?).
Basically we store the cursor position, make our search/replacement, then put the cursor back where it was:
html:
<div id="content" contentEditable="true" onkeyup="highlight(this)">This is some area to type.</div>
and the script:
function highlight(elem) {
// store cursor position
var cursorPos=document.selection.createRange().duplicate();
var clickx = cursorPos.getBoundingClientRect().left;
var clicky = cursorPos.getBoundingClientRect().top;
// copy contents of div
var content = elem.innerHTML;
var replaceStart = '<font style="color:blue">';
var replaceEnd = '</font>';
// only replace/move cursor if any matches
// note the spacebands - this prevents duplicates
if(content.match(/ test /)) {
elem.innerHTML = content.replace(/ test /g,' '+replaceStart+'test'+replaceEnd+' ');
// reset cursor and focus
cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();
}
}

Related

Reset content editable caret position

I am currently trying to create a syntax highlighter for Javascript and I currently facing the issue which I have found out is common with creating something like this which is setting the caret position to the end while the user types or edit contentEditable text.
I researched and found this and many other solutions here on SO but none works. It gets the position of the caret but never resets it so I am trying to find a workaround for this problem.
Below is the code I came up with.
html
<div id="editor" contentEditable="true" onkeyup="resetPosition(this)"></div>
<input type="text" onkeyup="resetPosition(this)" />
js
function getPos(e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
let _range = document.getSelection().getRangeAt(0)
let range = _range.cloneRange()
range.selectNodeContents(e)
range.setEnd(_range.endContainer, _range.endOffset)
return range.toString().length;
}
// for texterea/input element
return e.target.selectionStart
}
function setPos(pos, e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
document.getSelection().collapse(e, pos);
return
}
e.setSelectionRange(pos, pos)
}
function resetPosition(e) {
if(e.isContentEditable) {
let currentPosition = getPos(e);
e.innerHTML=e.innerHTML.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
return;
}
e.value = e.value.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
}
This works fine for text input but not for contentEditable divs.
When I type something like function, I get otincfun.
UPDATE: I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos); but a new bug arose.
When I press ENTER Key, the caret goes back to the first line and first character. Please how do I fix?
Below is the fiddle link
https://jsfiddle.net/oketega/bfeh9nm5/35/
Thanks.
The Problems
document.getSelection().collapse(element, index) collapses the cursor to the child node that index points to, not the character index.
I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos);
That will work if you are only replacing characters, but if you are creating a syntax highlighter, you will want to encase characters in span elements to style them. e.firstChild would then only set the position to an index within e's first child, excluding latter span's
Another thing to consider is that you may want to autocomplete the certain chars. The caret position before you manipulate the text may not be the same as after you do so.
The Solution
I recommend creating a <span id="caret-position"></span> element to track where the caret is.
It would work like this:
function textChanged(element) {
// 1
const text = setCursorMarker(element.innerText, element);
// 2
const html = manipulate(text);
element.innerHTML = html;
// 3
const index = findCursorIndex(element);
document.getSelection().collapse(element, index)
}
Every time the user types, you can get the current caret position and slip in the #caret-position element in there.
Overwrite the existing html with the syntax highlighted text
Find out where #caret-position is and put the caret there.
Note: The recommended way to listen for when the user types in the content-editable element is with the oninput listener, not onkeyup. It is possible to insert many characters by holding down a key.
Example
There is a working js fiddle here: https://jsfiddle.net/Vehmloewff/0j8hzevm/132/
Known Issue: After you hit Enter twice, it looses track of where the caret is supposed to be. I am not quite sure why it does that.

Convert to markdown but how to set caret appropriate (in Javascript)?

I can not come up with a suitable solution...
<p class="session" contenteditable="true">
Everything contained <b>**within**</b> this div is editable in browsers
that support <code>`HTML5`</code>. Go on, give it a try: click it and
start typing.
</p>
I want to add the appropriate HTML-tags while typing.
Of course it is no problem doing it by simple regex replacements. But I have problems setting the caret at the right place.
Say, in the example above, I want to emphasize the word "try". While typing the right * first and then the left star *, the text is converted to <i>*try*</i> but I want to preserve the position of the caret (set the the caret after the left *). And of cource vice versa, where the right * is set at last.
I find it more difficult to set the part contained <b>**within**</b> this say underlined. _contained <b>**within**</b> this_ becomes <u>_contained <b>**within**</b> this_</u>. So we are not in the same node.
I can think of complicated solutions, but since I am no expert in these text conversions techniques (range, window selection etc.) I wonder if there is a well known pratice of doing it.
Check out the Fiddle
I have tried to get the caret position as a first step and in the second step i have tried to put the caret back to the position .
$('.session').keyup(function(){
//alert("happening");
var sel = window.getSelection();
var offset=sel.anchorOffset;
// you can change the text in div by uncommeting the line below and replace your text with mathcing regex .
//$('.session').text($('.session').text().replace('/(\*.\*)+/','<i>$1<i>'));
setCaret(offset);
$('.session')
});
function setCaret(offset) {
var el = $('.session');
//alert();
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el[0].childNodes[0],offset);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
el.focus();
}
You need to figure out how to change the text in the div as per your needs. While replacing the text inside the div also maintain the formatting so that the solution works.

iFrame WYSIWYG font size won't reset

Here's my jsFiddle: https://jsfiddle.net/wsounka3/
When I change the font size within the WYSIWYG and then delete all of the written text and begin writing again it retains the last font size instead of starting fresh again.
How can I make the iframe "forget" the last font size?
Code:
function iwrap(elem,iwin,idoc){
var element = idoc.createElement(elem);
var sel = iwin.getSelection();
if(sel.rangeCount){
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(element);
sel.removeAllRanges();
sel.addRange(range);
}
}
$('#smaller').click(function(){
iwrap('small',iwin,idoc);
update_code();
});
$('#larger').click(function(){
iwrap('big',iwin,idoc);
update_code();
});
Side question: I find it strange that by encasing the text in <big> and <small> tags, it decides to translate that into pixels. Can anyone explain why it does this?
Because when you click delete you are deleting the textual content and not the wrapping tag. You can clear the markup when there is no textual content - plus there is a Chrome bug that retains the last element size, so wrapping in a [span] tag prior to removing seams to fix that
Since you call update_code on keyup, simply add a cleanup check there
function update_code(){
var $body = $(idoc).contents().find('body');
if ($.trim($body.text()).length == 0) {
iwrap('span', iwin, idoc); // chrome bug
$body.empty();
}
var icontent = $body.html();
$('div#code').html(icontent);
}
BIG/SMALL to SPAN:size also appears to be an automatic chrome thing

How can I fix caret issues in a contenteditable that contains non-editable HTML elements?

What I am having is a contenteditable div that is supposed to contain addtional unmodifiable HTML content. Something like this:
<div id="txt" contenteditable="true">
<div class="tagged" id="annyoing-html" contenteditable="false" style="display: initial;">
<span>I annoy people!</span>
</div>
</div>
The problem is that the cursor/caret does not play well with this e.g. you won't see the caret blinking unless you type something. The same problem comes with the Left and Right keys - sometimes the caret just disappears and the only thing that makes it re-appear is typing something.
I thought I could just insert text nodes that would make the caret jump correctly:
insertWhitespace = function() {
var contentEditable = document.getElementById("txt");
var element = document.getElementById("annyoing-html");
var textNodeLeft = document.createTextNode("");
var textNodeRight = document.createTextNode("");
contentEditable.insertBefore(textNodeLeft, element);
if(element.nextSibling != null) {
contentEditable.insertBefore(textNodeRight, element.nextSibling);
} else {
element.appendChild(textNodeRight);
}
};
but that does not work the way I thought it would..
First of all the caret does not "blink" the way it should and additionally the zero-width whitespace character \u200b does still count as a character which means you have to press Left or Right one time additionally to jump over that invisible character ..
Just try the JSFiddle and try to add text to the left and right side of the non-editable content. Clicking the button will just add those zw whitespaces but your cursor won't blink.
Of course I tried to remove the content after inserting it but I think Chrome simply removes the text node.
So what can I do to arrive at a "natural" feel for my contenteditable?

how to get selected text, but can I get surrounding context in javascript?

I am able to grab the text that a user has selected on a web page,
using this code:
function getSelected() {
var userSelection;
if (window.getSelection) {
selection = window.getSelection();
} else if (document.selection) {
selection = document.selection.createRange();
}
}
is it posible for me to get the words around the
selected word.
Take these sentences for example: "If you need to
framglubble the zartbox, then you should buy the red widget.
Otherwise you can buy the blue widget and save some money."
My code will tell me if the person has selected the word "widget".
But I'd like to know if the selection is after "red" or "blue". Is
this possible? I've been scouring the Internet for some advice, and
I'm having trouble finding an answer.
thank you for your help
I have written quick script that can identify the part before selection and after selection inside the same DIV element.
However if the same DIV contains the same word more than one time and you select only that word, the current code I wrote can't identify if it's the first or second selected word so bottom line it will not answer your needs.
Anyway, you can see/copy/test the code here: http://jsfiddle.net/kvHxJ/ just select something and see the alert that appears.
If it's enough for your needs after all then great, accept this answer and move on... otherwise I need to know: can we assume the user will select whole words only, one word only? If the answer is yes I do have idea how to go around this.
The way to do this in non-IE browsers is to obtain a Range object from the selection. The range has a start and end boundary, and each boundary of the range is expressed as an offset within a node; if the boundary is within a text node, this offset will be a character offset.
For example, if the following was a text node and the selection is delimited by pipes:
"red |widget| blue widget"
... then the range you'd get from the selection would have a start offset of 4 within the text node.
The following will get you a Range representing the selection and alert the start boundary:
var sel = window.getSelection();
var selectedRange = sel.rangeCount ? sel.getRangeAt(0) : null;
if (range) {
alert("Offset " + selectedRange.startOffset
+ " in node " + selectedRange.startContainer.nodeName);
}
Ranges may be compared to other Ranges, so if you wanted to know, for example, if the current selection came after the word "blue" in the above text node, you could create a Range encompassing the word "blue" and compare it with the selected Range:
// Assume the text node is stored in a variable called textNode
var blueRange = document.createRange();
blueRange.setStart(textNode, 11);
blueRange.setEnd(textNode, 15);
var selectionIsAfterBlue =
(selectedRange.compareBoundaryPoints(Range.END_TO_START, blueRange) == 1);
In IE, none of this works and everything is done differently, generally with much more difficulty. To normalize this to single consistent interface, you could use my Rangy library.
IE has the move set of methods, which reduces this problem to just a couple of lines to expand the selection forward or backward any number of words (see http://www.webreference.com/js/column12/trmethods.html). From there, it's just a matter of comparing text against any arbitrary list of values. Other browsers don't have this feature AFAIK. Fate of the browser wars: one develops an awesome feature ignored or barred by patent from any other, so the feature is forever lost and avoided as burden of cross-browser support for all these innovations inevitably falls squarely on the website designers.
So, below is a generalized function to only get the ID of the parent element of the selected text. And, to work with this cross-browser solution, you have to wrap each word in it's own element complete with unique ID or other attribute. With this setup, it should then be a relatively painless jump to looking ahead and back at sibling or sequentially ID'd/named elements.
The catch here is that the client has to click/drag from the start of the word or phrase to the end, and absolutely no bordering spaces. Even double-clicking on a word will cause it to reference the next element (or in the case of IE, the parent DIV). Additionally, you should add code to restrict the selection boundary to a single parent DIV, as the below code may also expand the selection to surrounding elements. But hopefully you can take fixing that up from here. Otherwise, it's up to using vectors to pinpoint the coordinates of a text compared to all surrounding text.
<script type="text/javascript">
function get_selected_element_id() {
if (window.getSelection) {
// FF
var range = window.getSelection();
}
else if (document.selection) {
// IE
var range = document.selection.createRange();
}
if (range.focusNode) {
// FF
var test_value = range.focusNode.parentNode.id;
}
else {
// IE
var test_value = range.parentElement().id;
}
return test_value;
}
</script>
</head>
<body>
<div id="test_div">
<span id="test1">test</span> <span id="test2">asdf</span> <span id="test3">test2</span> <span id="test4">bla</span>
</div>
<button onclick="alert(get_selected_element_id());">go</button>

Categories