Preserve tabs when reading from a textarea after adding via JavaScript - javascript

I'm having a problem with preserving tab characters when reading the value of a textarea.
I'm adding the tabs as follows:
$("#code-editor").keydown(function (e) {
if (e.keyCode === 9) { // tab was pressed
// get caret position/selection
var start = this.selectionStart;
var end = this.selectionEnd;
var $this = $(this);
var value = $this.val();
// set textarea value to: text before caret + tab + text after caret
$this.val(value.substring(0, start)
+ "\t"
+ value.substring(end));
// put caret at right position again (add one for the tab)
this.selectionStart = this.selectionEnd = start + 1;
// prevent the focus lose
e.preventDefault();
}
});
The tabs are inserted correctly and are displayed in the textarea as expected.
The problem occurs when I then read the value of the textrea and try to replace to tab characters with html formatting.
$("#code-editor").keyup(function (e) {
var value = $(this).val();
//Do formatting
var lines = value.split("\n");
var newvalue = "";
for (var i = 0; i < lines.length; i++) {
lines[i].replace(/\t/g, "<span style='padding-left:3em'></span>");
lines[i] += "<br />";
newvalue += lines[i];
}
$('#editor-displayarea').html(newvalue);
});
I have discovered that the tabs do not seem to be preserved when reading from textarea.
Is there a way around this or have I taken the wrong approach?
Thanks.
Update:
I have tried using a few variations on the regular expression as stated in the comment below but to no avail.

You are not storing the value when you replace the tabs. Try something like this (I would leave the in the span as well):
for (var i = 0; i < lines.length; i++) {
var replaced = lines[i].replace(/\t/g, "<span style='padding-left:3em'> </span>");
replaced += "<br />";
newvalue += replaced;
}

Related

Stop the cursor moves at the end of a textarea using Angularjs

How to Stop the text cursor from jumping to end of a textarea field using $scope.$watch?
//textarea $watch
$scope.$watch('string', function(s){
var string = s.split('\n');
if(string.length > 0) {
var newElements = [];
for (var i = 0; i < string.length; i++) {
var str = string[i];
var obj = {};
var firstMatch = str.split('|')[0];
var secondMatch = str.substring(firstMatch.length + 1); //return '' if '|' was not found
obj.text = firstMatch;
obj.value = secondMatch;
newElements.push(obj);
}
$scope.elements = newElements;
}
});
//array of objects $watch
$scope.$watch('elements', function(e){
var string = '';
for(var i = 0; i < e.length; i++){
var str = e[i];
var value = str.value;
var pipe = '|';
var text = str.text;
if( value == ''){
pipe = '';
}
string += (text + pipe + value) + (i == e.length - 1 ? '' : "\n");
}
$scope.string = string;
}, true);
The text cursor (inside the textarea) is always jumping to the end after the value is cleared. For example: try to empty the first value (value1) at the textarea, the cursor does move to the end of line (after the value1 is empty, the text cursor goes to the end of line, but I want the cursor to stay to the position that it was emptied)
text1|value1 <-- after the value1 is empty, the text cursor goes to the end of line, but I want the cursor to stay here or to the position that It was emptied
text2|value2
text3|value3
text4|value4 <-- cursor goes here after emptied
I want to prevent the cursor to move to the end.
Give a try here:
https://jsfiddle.net/yegrteaf/3/

HTML input - getting the value via innerHTML or XMLSerializer

I have an input box on a html page. I know I can get just the value, but I want the entire input string, i.e. , but with the value present:
<input id="myInput" value="my entry that I just typed in"/>
I have tried innerHTML, I have tried XMLSerializer, etc.
var htmlDiv = document.getElementById('myInput');
var str = s.serializeToString(htmlDiv);
The value is always empty.
If you are wondering why I want to do this - it is because this is my simple example of what in reality is about 60 inputs, all part of an HTML string that I want to send to XSLT translation. That part works like gangbusters, I just have to get it HTML with values intact.
Another related problem is that innerHTML has a nasty habit of getting rid of the / at the end of the input box, which throws off the XSLT translation.
Try this:
console.log(document.getElementById("myInput").outerHTML);
<input id="myInput" value="my entry that I just typed in"/>
And if you want to add / at the end:
myVar = document.getElementById("myInput").outerHTML;
if(myVar.charAt(myVar.length - 1) !== "/"){
console.log(myVar.slice(0, myVar.length-1) + "/>");
}
<input id="myInput" value="my entry that I just typed in"/>
I ended up doing the following: Serializing with XMLSerializer, which solved the / problem. And I just got the values from the DOM and inserted them myself.
function htmlCleanup(htmlDiv) {
//serialize the html. It will lack the latest user changes in the inputs.
var s = new XMLSerializer();
var str = s.serializeToString(htmlDiv);
var lines = str.split("\n");
//get all of the inputs in the div
var inputs = htmlDiv.getElementsByTagName('input');
//Here we put in the latest values
var inputIndex = 0;
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
if (line.indexOf('<input') >= 0) {
var value = inputs[inputIndex].value;
lines[i] = fixInputValue(line, value);
inputIndex++;
}
}
str = lines.join('\n');
//Some other weird aftertaste stuff that needs fixing. <tbody> is added to tables - wrongly.
//the div at the top is also a problem.
//Then we turn it all into proper xhtml for the trip back to the server.
var contents = str.replace('<div xmlns="http://www.w3.org/1999/xhtml" id="documentEditBody">', '');
contents = contents.replace(/<tbody>/g, '');
contents = contents.replace(/<\/tbody>/g, '');
contents = '<?xml version="1.0" encoding="UTF-8"?><html><head></head><body><div>' + contents + '</body></html>';
return contents;
}
function fixInputValue(input, value) {
var valuePos = input.indexOf('value');
var result = "";
if (valuePos > -1) {
for (var i = valuePos + 7; i < input.length; i++) {
var chr = input[i];
if (chr === '"') {
var last = input.substring(i + 1, input.length)
result = input.substring(0, valuePos - 1) + ' value="' + value + '" ' + last;
break;
}
}
}
return result;
}

Set cursor of contenteditable div after changing the innerHTML on keypress in chrome

I need to have customized spellchecker for div. Div should underline the misspelled words with red lines as in chrome when the user types a wrong word. But on changing the innerHTML the cursor is moving to the front of the text.
Functionality should work in chrome
Need suggestions.
var n = document.createElement("div");
n.id = "div_";
n.setAttribute("onkeydown", "CopyField(" + "div_" + ")");
function CopyField(sender)
{
if (event.keyCode == 32) {
var Value = document.getElementById(sender).innerText.split(' ');
for (var i = 0; i < Value.length; i++)
{
Value[i] = "<span class='redwiggle'>" + Value[i] + "</span>";
}
var FinalValue = Value.join(' ');
document.getElementById(sender).innerHTML = FinalValue;
}
}

Focus cursor after inserting text in textfield

I made the following function to insert a message (msg) into a textfield.
After inserting the text the cursor needs to be after the last character of the msg that was inputted. The textfield already contains some text.
When I insert the message the cursor gets focussed somewhere near the end of the msg but certainly not after the last character. It seems like some characters don't get counted by .length?
function insertAtCursor(msg) {
var textArea = document.getElementsByName("message")[0];
textArea.value = textArea.value.substr(0, textArea.selectionStart) + msg + textArea.value.substr(textArea.selectionEnd);
var endMsgPos = textArea.value.lastIndexOf(msg) + msg.length;
textArea.setSelectionRange(endMsgPos, endMsgPos);
}
There's no need for the textArea.value.lastIndexOf(msg).
function insertAtCursor(msg) {
var textArea = document.getElementsByName("message")[0];
var selStart = textArea.selectionStart, val = textArea.value;
textArea.value = val.slice(0, selStart) + msg +
val.slice(textArea.selectionEnd);
var endMsgPos = selStart + msg.length;
textArea.setSelectionRange(endMsgPos, endMsgPos);
}
First of all, you'll have an issue with IE 8 if you try to use textArea.selectionStart because it uses a different API.
Secondly lastIndexOf will behave strange in your code if the caret is at the begging of the string and somewhere at the end the value of the msg is present.
Try this:
function insertAtCursor(msg) {
var e = document.getElementsByName('message')[0],
length = msg.length, val = e.value;
/* mozilla / dom 3.0 */
if ('selectionStart' in e){
var partial = val.slice(0, e.selectionStart) + msg;
e.value = partial + val.slice(e.selectionEnd);
var end = partial.length;
e.setSelectionRange(end, end);
}
/* exploder */
if (document.selection){
e.focus();
document.selection.createRange().text = msg;
}
}
The demo is here : http://jsbin.com/UnOBUbU/6

Slow highlighting in Firefox

We need to add anchors and highlights for some keywords/sentences in the html page. It turns out the highlighting is really slow in Firefox.
In the following code, all ranges which need to be highlighted are stored in array hiliteRanges:
for (var i = 0; i < hiliteRanges.length; i++){
document.designMode = "on";
var selHilites = window.getSelection();
if (selHilites.rangeCount > 0)
selHilites.removeAllRanges();
selHilites.addRange(hiliteRanges[i]);
var anchorId = 'index'+i;
var insertedHTML = '<span id="' + anchorId + '" style="background-color: #FF8C00;" >'+hiliteRanges[i].toString()+'</span>';
document.execCommand('inserthtml', false, insertedHTML);
document.designMode = "off";
}
Is there any way to speed up the processing? We could have hundreds of ranges in the array hiliteRanges. We once tried moving the designMode setting outside of the loop, but we can see some sections are editable in the html page when the loop is running.
This is my default highlighting snippet and works fine in every browser. Try it out.
Demo: http://jsbin.com/adeneh/1/edit
function highlight(text, words, tag) {
// Default tag if no tag is provided
tag = tag || 'span';
var i, len = words.length, re;
for (i = 0; i < len; i++) {
// Global regex to highlight all matches
re = new RegExp(words[i], 'g');
if (re.test(text)) {
text = text.replace(re, '<'+ tag +' class="highlight">$&</'+ tag +'>');
}
}
return text;
}
// Usage:
var el = document.getElementById('element');
el.innerHTML = highlight(
el.innerHTML,
['word1', 'word2', 'phrase one', 'phrase two', ...]
);
And to unhighlight:
function unhighlight(text, tag) {
// Default tag if no tag is provided
tag = tag || 'span';
var re = new RegExp('(<'+ tag +'.+?>|<\/'+ tag +'>)', 'g');
return text.replace(re, '');
}
There's no need to use document.execCommand() for this. Just use range methods instead, and then there's no need for designMode.
var anchorId, hiliteTextNode, hiliteSpan;
for (var i = 0; i < hiliteRanges.length; i++){
// Create the highlight element
hiliteSpan = document.createElement("span");
hiliteSpan.id = anchorId;
hiliteSpan.style.backgroundColor = "#FF8C00";
hiliteTextNode = document.createTextNode(hiliteRanges[i].toString());
hiliteSpan.appendChild(hiliteTextNode);
// Replace the range content
hiliteRanges[i].deleteContents();
hiliteRanges[i].insertNode(hiliteSpan);
}
Also, since ranges are affected by DOM mutation, I would suggest doing this part at the same time as you collect the ranges with window.find(). Here's an example:
http://jsfiddle.net/YgFjT/

Categories