I'm struggling to make a textarea adjust its height if needed after some text is added to it by click.
So, I use eventlistener for my textarea in order to determine when some amount of characters is added and a new line is needed further so that textarea is resized vertically. It works fine with manually added characters, but I want to use the so-called "bb-tags" as well for formatting reasons. Say, I use a button that adds red color formatting tag: [color=red][/color].
The problem is that when I add [color=red][/color] by click, my textarea won't add new lines automatically.
I made the following snippet for testing my codes.
// Autoresize textarea
const textarea = document.getElementById('shoutbox-comment');
textarea.addEventListener('input', function () {
this.rows = 2;
this.rows = countRows(this.scrollHeight);
});
function countRows(scrollHeight) {
return Math.floor(scrollHeight / 18); // 18px = line-height
}
// bbtags formatting
function bbtags(h, a, i) {
var g = document.getElementById(h);
g.focus();
if (g.setSelectionRange) {
var c = g.scrollTop;
var e = g.selectionStart;
var f = g.selectionEnd;
g.value = g.value.substring(0, g.selectionStart) + a + g.value.substring(g.selectionStart, g.selectionEnd) + i + g.value.substring(g.selectionEnd, g.value.length);
g.selectionStart = e;
g.selectionEnd = f + a.length + i.length;
g.scrollTop = c;
} else {
if (document.selection && document.selection.createRange) {
g.focus();
var b = document.selection.createRange();
if (b.text != "") {
b.text = a + b.text + i;
} else {
b.text = a + "REPLACE" + i;
}
g.focus();
}
}
}
// insert bbtag on click
bb_red.onclick = function() {
javascript:bbtags("shoutbox-comment", "[color=red]", "[/color]");
}
#shoutbox-comment {
width: 270px;
line-height: 18px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="shoutbox-comment"></textarea>
<input type="button" id="bb_red" class="bbtag-color-red" value="red">
I wonder if I need to specify something for eventlistener or else.
Jquery may be used.
Thank you.
replace code of function bb_red.onclick with this :
bb_red.onclick = function() {
javascript:bbtags("shoutbox-comment", "[color=red]", "[/color]");
textarea.rows = countRows(textarea.scrollHeight);
}
Related
I have a div with text using monospace font, and I need to display a cursor in the place where I click, I have functions that display text with cursor:
function draw() {
var text = textarea.val();
var html;
if (pos == text.length) {
html = encode(text) + '<span class="cursor"> </span>';
} else {
html = encode(text.slice(0, pos)) + '<span class="cursor">' +
encode(text[pos+1]) + '</span>' + encode(text.slice(pos+1));
}
output.html(html);
}
and function that get cursor position based on x/y coordinate of the mouse event:
function get_char_pos(div, text, event) {
var num_chars = get_num_chars(div);
var cursor = div.find('.cursor');
var rect = cursor[0].getBoundingClientRect();
var width = rect.width;
var height = rect.height;
var offset = div.offset();
var col = Math.floor((event.pageX-offset.left)/width);
var row = Math.floor((event.pageY-offset.top)/height);
var try_pos = col + (row > 0 ? num_chars * row : 0);
return try_pos;
}
It almost working except when text contain tabs (tabs are replaced by 4 spaces by encode function). I've try to fix tabs using this:
var before = text.slice(0, try_pos);
var tabs = before.match(/\t/g);
var fix = tabs ? tabs * 3 : 0;
try_pos += fix;
return try_pos > text.length ? text.lenght : try_pos;
but this don't work. It should also work for a case when I click on space that may be part of tab. How to fix it when text contain tabs?
Here is codepen demo
The tab character is the issue. It's a single character which means the string it's not calculated as four characters in the text.slice. If you replace \t with four spaces your issue is solved.
I've been working on a code snippet for a template module that I have been creating and I've hit a wall so to speak. I'm trying to loop through all textareas on my page, and apply formatting with some very basic validation.
Javascript is not my strongest suit but I can understand it to a point, my question is how do I collect the ID's and then use them to apply the formatting.
For example,
for each (textarea)
{
collect character restriction from html
display character restriction in a formatted manner
}
I have included my JSFiddle which I have been using to build this snippet.
I would suggest creating a prototype class for this, that can be extended to do other things aswell:
var CharWatcher = function(input){
this.max = input.getAttribute('max-length');
this.input = input;
input.onKeyDown = this.update.bind(this);
this.wrapper = document.createElement('div');
this.wrapper.innerHTML = 'Chars left: '+ (max - input.value.length);
/* style wrapper element */
/* append around input */
};
CharWatcher.prototype = {
update: function(){
this.wrapper.innerHTML = 'Chars left: ' + (this.max - this.input.value.length);
}
};
/* Somewhere else */
var textareas = document.getElementsByTagName('textarea');
for(var i = 0, l = textareas.length; i < l; i++)
new CharWatcher(textareas[i]);
I've based on #FodorZoltán's class. My class does now:
append the counter below the textarea;
position the counter in the below part of the textarea;
Yeah, I'm lazy and the code has grown up. I added some events and renamed the class name to "TextAreaRanger". It's working here:
var TextAreaRanger = function(input) {
this.MAX = parseInt(input.getAttribute('maxlength'));
this.INPUT = input;
// add input events
input["oncut"] =
input["onpaste"] =
input["onkeydown"] =
input["onkeyup"] = this.update.bind(this);
// create wrapper element
this.wrapper = document.createElement('div');
this.wrapper.innerHTML = 'Chars left: '+ (this.MAX - input.value.length);
/* input parent element */
var ipar = input.parentNode;
// find input's i
for (var i = 0, el; el = ipar.children[i]; i ++) {
if(el === input) break;
}
// append wrapper below the input
if (ipar.children[++i]) {
ipar.insertBefore(this.wrapper, ipar.children[i]);
} else ipar.appendChild(this.wrapper);
/* stylize wrapper */
this.wrapper.style.position = "relative";
this.wrapper.style.color = '#f00';
this.wrapper.style.fontSize = '11px';
this.wrapper.style.left = (input.offsetLeft + (input.offsetWidth - 100)) + "px";
this.wrapper.style.top = (-parseInt(this.wrapper.style.fontSize) * 2) + "px";
};
// Update the counter
TextAreaRanger.prototype["update"] = function() {
this.wrapper.innerHTML = 'Chars left: ' + (this.MAX - this.INPUT.value.length);
};
I'm working on a BBCode editor and here is the code:
var txtarea = document.getElementById("editor_area");
function boldText() {
var start = txtarea.selectionStart;
var end = txtarea.selectionEnd;
var sel = txtarea.value.substring(start, end);
var finText = txtarea.value.substring(0, start) + '[b]' + sel + '[/b]' + txtarea.value.substring(end);
txtarea.value = finText;
txtarea.focus();
}
Everything is OK except one thing which is the position of the text-cursor. When I click on the boldText button, it sets the cursor position at the end of the Textarea!!
Actually, I want to be able to set the cursor position at a certain index. I want something like this:
txtarea.setFocusAt(20);
After refocusing the textarea with txtarea.focus(), add this line:
txtarea.selectionEnd= end + 7;
That will set the cursor seven positions ahead of where it was previously, which will take [b][/b] into account.
Example
document.getElementById('bold').addEventListener('click', boldText);
function boldText() {
var txtarea = document.getElementById("editor_area");
var start = txtarea.selectionStart;
var end = txtarea.selectionEnd;
var sel = txtarea.value.substring(start, end);
var finText = txtarea.value.substring(0, start) + '[b]' + sel + '[/b]' + txtarea.value.substring(end);
txtarea.value = finText;
txtarea.focus();
txtarea.selectionEnd= end + 7;
}
#editor_area {
width: 100%;
height: 10em;
}
<button id="bold">B</button>
<textarea id="editor_area"></textarea>
if you are using jquery you can do it like this.
$('textarea').prop('selectionEnd', 13);
you can use these 2 functions below written by Jamie Munro (setSelectionRange() & setCaretToPos()):
function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
else if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
}
function setCaretToPos (input, pos) {
setSelectionRange(input, pos, pos);
}
EXAMPLE:
for example, if you want to set the caret at the end of your textarea you can have this:
setCaretToPos(document.getElementById('textarea'), -1);
Through JQuery:
var cursorPos = $('#textarea').prop('selectionStart');
$('#textarea').prop('selectionEnd',cursorPos-2);
Realizing this is an older question, this is offered only as something to think about as an option now because it may likely be more efficient than extracting and assembling pieces of the textarea value string, and it sets the cursor automatically based on the fourth argument of setRangeText and autofocuses also. It works in Firefox 66.0.02 and I haven't tested it elsewhere. The cursor is placed after the '[/b]'.
t = document.getElementById("editor_area");
b = t.selectionStart,
e = t.selectionEnd + 3; // length of '[b]'
t.setSelectionRange( b, b );
t.setRangeText( '[b]' );
t.setSelectionRange( e, e );
t.setRangeText( '[/b]', e, e, 'end' );
This is a little OT, but if anyone is interested:
Brief: Set cursor inside input element throug row and column
Dependency: setSelectionRange() from #ashkan nasirzadeh
Example call: setTextCursor(textarea,textarea.val, 0, 1);
// #brief: set cursor inside _input_ at position (column,row)
// #input: input DOM element. E.g. a textarea
// #content: textual content inside the DOM element
// #param row: starts a 0
// #param column: starts at 0
function setTextCursor(input, content, row, column){
// search row times:
var pos = 0;
var prevPos = 0;
for( var i = 0; (i<row) && (pos != -1); ++i){
prevPos = pos;
pos = content.indexOf("\n",pos+1);
}
// if we can't go as much down as we want,
// go as far as worked
if(-1 == pos){ pos = prevPos; }
if(0 != row)
++pos; // one for the linebreak
// prevent cursor from going beyond the current line
var lineEndPos = content.indexOf("\n", pos+1);
if((-1 != lineEndPos) &&
(column > lineEndPos-pos)){
// go *only* to the end of the current line
pos = lineEndPos;
} else{
// act as usual
pos += column
}
setSelectionRange(input, pos,pos);
}
I am trying to put a font-resizer on my company' website since a lot of our customers are the elderly and they have no idea about Ctrl + "+".
Here are the codes we have. The resizer works fine under FF, Chrome, and IE9. But not in IE8 and IE7. I omit the create cookies/read cookies parts here.
function createCookie(name,value,days) {.....codes for create cookies......}
function changeFont(incfont) {
try{
var p = document.getElementsByClassName('resizable');
for(n=0; n<p.length; n++) {
if(p[n].style.fontSize) {
var size = parseInt(p[n].style.fontSize.replace("px", ""));
} else {
var size = parseInt(window.getComputedStyle(p[n],null).getPropertyValue('font-size').replace("px", ""));
}
p[n].style.fontSize = size+ incfont + 'px';
}
p = document.getElementsByTagName('p');
for(n=0; n<p.length; n++) {
if(p[n].style.fontSize) {
var size = parseInt(p[n].style.fontSize.replace("px", ""));
} else {
var size = parseInt(window.getComputedStyle(p[n],null).getPropertyValue('font-size').replace("px", ""));
}
p[n].style.fontSize = size+ incfont + 'px';
}
} catch(err) {}
}
function readCookie(name) { ....code for read cookies ....}
function increaseFontSize() {
var inc=0;
try {
var x = readCookie('textsize')
if (x && x!=0) {
x = parseInt(x);
inc = x;
}
} catch (e) {}
if (inc<3) {
inc++;
changeFont(1);
createCookie('textsize',inc,1);
}
}
function decreaseFontSize() {
var inc=0;
try {
var x = readCookie('textsize')
if (x && x!=0) {
x = parseInt(x);
inc = x;
}
} catch (e) {}
if (inc>0) {
inc--;
changeFont(-1);
createCookie('textsize',inc,1);
}
}
Thanks in advance!
YN
Your solution seems overcomplex to me, I will suggest you a different approach, set a base text size for the page body, and then for the rest elements set font-sizes in percentage, that way when you want to resize the text of all the site you just have to do:
$("body").css("font-size", newFontSize);
getComputedStyle doesn't work for IE prior to 9.
See:
https://developer.mozilla.org/en-US/docs/DOM/window.getComputedStyle
A fix might be available here : http://snipplr.com/view/13523/
I didn't test it,
Good luck !
I'm writing a mobile web application where scrollbars are not displayed on the device's browser. Due to this, I'm trying to dynamically modify the height of the textarea to make it bigger, however I don't know of any way to actually get the line count on an html textarea. Any help would be greatly appreciated!
EDIT
So I realize now that it's not newlines per se, but actual line wrapping. So when one line finishes it wraps the text to the next line. It appears as if it is a new line. Any way to count the number of these? Thanks!
The number of lines in the textarea would be
textarea.value.match(/\n/g).length + 1
I have created a plugin to handle line counting and wrap detection in a <textarea>.
I hope someone can use it.
Code on BitBucket
Sample Usage
var result = $.countLines("#textarea");
result.actual // The number of lines in the textarea.
result.wraps // The number of lines in the textarea that wrap at least once.
result.wrapped // The total number of times all lines wrap.
result.blank // The number of blank lines.
result.visual // The approximate number of lines that the user actually sees in the textarea
Working Demonstration
/*! Textarea Line Count - v1.4.1 - 2012-12-06
* https://bitbucket.org/MostThingsWeb/textarea-line-count
* Copyright (c) 2012 MostThingsWeb (Chris Laplante); Licensed MIT */
(function($) {
$.countLines = function(ta, options) {
var defaults = {
recalculateCharWidth: true,
charsMode: "random",
fontAttrs: ["font-family", "font-size", "text-decoration", "font-style", "font-weight"]
};
options = $.extend({}, defaults, options);
var masterCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var counter;
if (!ta.jquery) {
ta = $(ta);
}
var value = ta.val();
switch (options.charsMode) {
case "random":
// Build a random collection of characters
options.chars = "";
masterCharacters += ".,?!-+;:'\"";
for (counter = 1; counter <= 12; counter++) {
options.chars += masterCharacters[(Math.floor(Math.random() * masterCharacters.length))];
}
break;
case "alpha":
options.chars = masterCharacters;
break;
case "alpha_extended":
options.chars = masterCharacters + ".,?!-+;:'\"";
break;
case "from_ta":
// Build a random collection of characters from the textarea
if (value.length < 15) {
options.chars = masterCharacters;
} else {
for (counter = 1; counter <= 15; counter++) {
options.chars += value[(Math.floor(Math.random() * value.length))];
}
}
break;
case "custom":
// Already defined in options.chars
break;
}
// Decode chars
if (!$.isArray(options.chars)) {
options.chars = options.chars.split("");
}
// Generate a span after the textarea with a random ID
var id = "";
for (counter = 1; counter <= 10; counter++) {
id += (Math.floor(Math.random() * 10) + 1);
}
ta.after("<span id='s" + id + "'></span>");
var span = $("#s" + id);
// Hide the span
span.hide();
// Apply the font properties of the textarea to the span class
$.each(options.fontAttrs, function(i, v) {
span.css(v, ta.css(v));
});
// Get the number of lines
var lines = value.split("\n");
var linesLen = lines.length;
var averageWidth;
// Check if the textarea has a cached version of the average character width
if (options.recalculateCharWidth || ta.data("average_char") == null) {
// Get a pretty good estimation of the width of a character in the textarea. To get a better average, add more characters and symbols to this list
var chars = options.chars;
var charLen = chars.length;
var totalWidth = 0;
$.each(chars, function(i, v) {
span.text(v);
totalWidth += span.width();
});
// Store average width on textarea
ta.data("average_char", Math.ceil(totalWidth / charLen));
}
averageWidth = ta.data("average_char");
// We are done with the span, so kill it
span.remove();
// Determine missing width (from padding, margins, borders, etc); this is what we will add to each line width
var missingWidth = (ta.outerWidth() - ta.width()) * 2;
// Calculate the number of lines that occupy more than one line
var lineWidth;
var wrappingLines = 0;
var wrappingCount = 0;
var blankLines = 0;
$.each(lines, function(i, v) {
// Calculate width of line
lineWidth = ((v.length + 1) * averageWidth) + missingWidth;
// Check if the line is wrapped
if (lineWidth >= ta.outerWidth()) {
// Calculate number of times the line wraps
var wrapCount = Math.floor(lineWidth / ta.outerWidth());
wrappingCount += wrapCount;
wrappingLines++;
}
if ($.trim(v) === "") {
blankLines++;
}
});
var ret = {};
ret["actual"] = linesLen;
ret["wrapped"] = wrappingLines;
ret["wraps"] = wrappingCount;
ret["visual"] = linesLen + wrappingCount;
ret["blank"] = blankLines;
return ret;
};
}(jQuery));
result = jQuery.countLines("#textarea");
jQuery('#display').html(
'<span>Actual: ' + result.actual + '</span>' +
'<span>Blank: ' + result.blank + '</span>' +
'<span>Visual: ' + result.visual + '</span>' +
'<span>Wrapped: ' + result.wrapped + '</span>' +
'<span>Wraps: ' + result.wraps + '</span>'
);
#textarea {
width: 150px;
height: 80px;
}
#display span {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="textarea">text
here
this is a longer line so that it will wrap in the box longer longer longer</textarea>
<div id="display"></div>
This is an efficient and accurate method to count the number of lines in a text area, including wrapped lines.
/** #type {HTMLTextAreaElement} */
var _buffer;
/**
* Returns the number of lines in a textarea, including wrapped lines.
*
* __NOTE__:
* [textarea] should have an integer line height to avoid rounding errors.
*/
function countLines(textarea) {
if (_buffer == null) {
_buffer = document.createElement('textarea');
_buffer.style.border = 'none';
_buffer.style.height = '0';
_buffer.style.overflow = 'hidden';
_buffer.style.padding = '0';
_buffer.style.position = 'absolute';
_buffer.style.left = '0';
_buffer.style.top = '0';
_buffer.style.zIndex = '-1';
document.body.appendChild(_buffer);
}
var cs = window.getComputedStyle(textarea);
var pl = parseInt(cs.paddingLeft);
var pr = parseInt(cs.paddingRight);
var lh = parseInt(cs.lineHeight);
// [cs.lineHeight] may return 'normal', which means line height = font size.
if (isNaN(lh)) lh = parseInt(cs.fontSize);
// Copy content width.
_buffer.style.width = (textarea.clientWidth - pl - pr) + 'px';
// Copy text properties.
_buffer.style.font = cs.font;
_buffer.style.letterSpacing = cs.letterSpacing;
_buffer.style.whiteSpace = cs.whiteSpace;
_buffer.style.wordBreak = cs.wordBreak;
_buffer.style.wordSpacing = cs.wordSpacing;
_buffer.style.wordWrap = cs.wordWrap;
// Copy value.
_buffer.value = textarea.value;
var result = Math.floor(_buffer.scrollHeight / lh);
if (result == 0) result = 1;
return result;
}
Demo here
I haven't tried using the function discussed in this blog, but you may find it useful.
http://kirblog.idetalk.com/2010/03/calculating-cursor-position-in-textarea.html
Basically, if you create a div and then copy the text into that div, with the same width and font characteristics, you can then get the information you need, such as the number of lines. The number of lines in this example would be easy, in that if you know how many pixels high a single line would be, then just find the width of the test div and you can get a pretty accurate idea as to how many lines are in your textarea.
Get scrollHeight, subtract top+bottom padding, divide by lineHeight.
I'm pretty sure there is no reasonable way to count the number of lines as displayed in the browser especially considering some browsers (Safari) allow the user to resize textareas.
It'd be hacky, but your best bet might be to just estimate based on the total characters divided by average number of characters per line. :-/
Maybe there is a way to get the "raw" number of "visual" lines. You should read the scrollHeight property of the textarea and divide it by the height of a line. Let's try.
Start with this HTML:
<textarea id="ta" cols="50" rows="10"></textarea>
Then:
var line_height = Math.floor($("#ta").height() / parseInt($("#ta").attr("rows")));
var dirty_number_of_lines = Math.ceil($("#ta")[0].scrollHeight / line_height);
I am not sure if that really works, just a mad theory.
You can calculate is as so:
var length = $('#textarea').val().split("\n").length;
The number of characters allowed per line is dictated by the "cols" attribute of the textarea.
<textarea rows="10" cols="80"></textarea>
Assuming 80 characters per line, a good estimate may be:
var approxNumLines = textareaElement.value.length / textareaElement.cols ;
Doesn't account for word-break and word-wrap.