Automatic Textarea cursor movement - javascript

My question is almost similar to Automatic newline in textarea in textarea but my context is more complex. I have multiple textareas which have a 1 row attribute thus giving an impression of writing on air(just meaning empty area on the website with no borders since the textarea border i have set in css as >>> border: 0;).
I would like to use JQuery or Javascript to calculate when the text entered by the user if he/she has reached the end of the row of the textarea so as to automatically move to the next textarea below. One way to solve this would be to try and count the characters but different characters have different widths.
I'm thinking of inserting a hidden element at the edge of the textareas to act as a trigger for the nexttextarea.focus() event but i have no idea of how i can achieve this.
I have tried goofing around and thinking of different hacks but only one seems to be the solution... Try to store each character in an array and give them their default space taking value in px...like 'a'=>0.7px,'b'=>0.9px or something of the sort if their is a list somewhere (although it looks like it would take a lot of overhead in terms of memory as i would have to store both capital, small letters and many other characters.) Then do some calculations depending on the width of the browser if it has been re-sized or if not the full size to determine when the textarea row width becomes full at the browser width edge. (For the time being the textarea width is 100% and has no parent therefore fills the whole browser width).
If anybody has an idea of a complex or simple way i can accomplish this, help me. A big problem is that IE and Mozilla introduce scroll-bars if the browser window is re-sized, and scroll-bars is what i never want the user to see(remember impression of typing into thin air).
Forgive me for being so verbose..just wanted to be accurate and detailed.

This is harder than it looks. Try checking the scrollHeight and scrollWidth of the textarea; if they changed, the text overflowed. At that point, you can move text from the end of the current textarea to the beginning of the next textarea until the scroll height/width goes away.
Here's an example:
document.onkeyup = function(evt) {
var event = evt || window.event;
var target = event.target;
var nextArea = target.nextSibling; // assumes no whitespace between textareas.
var chars;
if (target.tagName == 'TEXTAREA' && (target.scrollLeft || target.scrollTop)) {
chars = 0;
while (target.scrollLeft || target.scrollTop) {
target.value = target.value.replace(/.$/, function(m0) {
nextArea.value = m0 + nextArea.value;
return '';
})
++chars;
target.selectionStart = target.value.length;
}
nextArea.focus();
nextArea.selectionStart = chars;
}
}​
http://jsfiddle.net/L73RG/3/
Note that a fully-working solution will need to bind this to more than just keyup events, because the user can paste with the context menu, for example. You may want to bind it to some mouse events or even run it occasionally on a timer if you find the box can be modified without triggering events (e.g. through a top level "edit" menu).

Check on every keypress if the content of your textarea is overflowing with the answer provided to this question: Determine if an HTML element's content overflows
You'll then have to remove the extra characters and put them into the next textarea. Probably one by one, using your check overflow function after each character is moved to see if the text area is still overflowing.

A possible solution is to measure the size of the text. Ext-JS does it by creating an offscreen absolutely positioned div, giving it style properties for the font you want to measure, and them measuring the div itself.
You can use Ext-JS's code as inspiration http://docs.sencha.com/ext-js/4-1/#!/api/Ext.util.TextMetrics

Related

How do I stop cursor moving to the start every time innerHTML is changed?

I have a function that separates words into separate span tags and updates the div I am typing into but every time I update the innerHTML the cursor moves to the start of the box so the next character I type outputs behind the last one instead of after it. Could someone tell me how I can stop the cursor from doing this?
Here is my code
const editorDiv = document.getElementById('editor');
function wrapWords(str, tmpl) { //separates words into <span>
return str.replace(/\w+/g, tmpl || "<span>$&</span>");
}
editorDiv.addEventListener('keydown', function(event) {
editorDiv.innerHTML = wrapWords(editorDiv.innerText)
});
<div id="editor" contenteditable="true">
<span>hello</span>
</div>
When I type into the div text looks like this:
.siht ekil skool txeT
One way I have solved this in the past is to use 2 seperate elements, positioned on top of each other, with position: absolute, so that they overlap in a pixel-perfect manner.
The element on top is a regular input element, and its text color is set to transparent.
The element behind it contains the styled text (spans etc.)
This way, you can replace the styled content as the user types, without interfering with the input element's cursor at all.
Sorry that I don't have an 'essential code snippet' at hand, but I hope you get the idea. It did work well in practice for me.

Restrict user input to fixed width and height contenteditable div

After a lot of research, I haven't found a post with exactly the same requirements so I thought write a new post.
I'm trying to create a fixed area (e.g. 200px by 300px) where the user can enter text input. He should be able to enter any character (including line breaks).
However, he should not be able to 'write outside the box' (i.e. there shouldn't be overflow scroll or hidden for the 200x300 area).
Once user reaches the 'bottom' of the area, they can't enter any more line breaks.
And once they reach the 'bottom right' corner of the 200x300 area, they shouldn't be able to enter any more characters at all.
Is this possible in css, angular, js, jquery, etc?
Limit the length of characters with base in font and div's size, but you must change the font size and family or line height because every browser can have different styles.
To limit the length of characters in the div is need to ignore the HTML tags in the content, like interpreting.
Firstly calculate how many characters fits there.
You can restrict the number of characters per line with the cols="" attribute and set the displayed the number of editable lines with the rows="" attribute. However limiting the number of rows could only be one with the maxlength attribute which would control the number of characters you can have, which you'd have to estimate. There are some hacks to limit the number of rows with event listeners, but they seem to have fairly major bugs.
It is possible, you just need to do following:
Use event handlers to control character input process. It is required to be able to stop processing further keystrokes when limit is reached. Use keypress and keydown, first handles character processing, second - control keys processing.
Each time user presses a key, use a separate buffer to produce final result, compute its bounding rectangle, and if it is bigger than limit, prevent event handling.
Height of text body could be calculated by multiplying number of lines by line height (interpret font-size and line-height CSS properties).
Width of text body could be computed rather easy with help of HTML5 canvas measureText method.
If you don't have canvas, you can use offscreen span (or any other inline) element - just fill innerHTML with text block and use its offsetWidth attribute. Actually, if you replace line break characters with <br>, you may use span approach to get both dimensions in one go - just make sure it has same style as editable container.
ContentEditable containers, as i remember, store text body in HTML format already (in other words - with <br>s instead of line break characters).

Repositioning after changing font size in UIWebView

I have an UIWebView with a huge book in it. I'm changing it's font size via javascript, using "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='150%';
Html-page gets larger, but the scroll position remains the same, causing text to shift out of a users sight.
The only idea that I have, is really weird and inefficient:
Wrap every word in <span> tags;
find the first onscreen <span> and remember it's id;
resize font;
scroll to span, that I've found in step 2.
Is there a better way to preserve the position, that user was reading?
Finally I've found an acceptable way:
Before changing font size I use a little javascript to find and store a position of a first letter on a page:
var range = document.caretRangeFromPoint(0,0); // get a range of a first onscreen letter
var textContainer = range.startContainer.parentNode;// get an element to which it belongs
var path = getElementXPath(textContainer); // get an XPath for that element (this function is not biult in, but you can find it in some other question)
path+='|'+range.startOffset; // stick XPath and index of the letter together
After that I change the font size, find needed element by XPath, insert invisible <a> right before my letter, scroll to that invisible <a>, don't forget to remove it.
Done. That is not a stragihtforward idea, but at least it works and does not consume to much of CPU or RAM, like the idea that I'have explained in original question.
Here is the place to get getElementXPath() function

Calculating xy-position of text selection

I am trying to create my own text selection with DOM-elements. Yes, I mean the blue background you see behind the text when you select it in this element. The idea is to halt the default behavior (the blue color) and use my own elements to do the job by finding the xy-position of the selection and then placing absolute positioned elements. I want to be able to do this with a regular div.
I'm thinking I need 3 elements. One for the top row (which may be incomplete), one for the middle chunk, one for the last (same as top). Here's an image that helps you understand:
I've been thinking of catching mouseup/down and then mousemove and then check window.getSelection() but so far I'm having trouble getting anywhere.
Using the CSS ::selection will not work because the element will not have focus.
I appreciate all help I can get! Thanks in advance.
Edit:
Stumbled upon https://code.google.com/p/rangy/ which might be of help? Anyone with experience with this plugin?
Edit2:
Cross-browser support is required.
You can use getClientRnge:
var element = document.getElementById('element')
element.onmouseup = function(){
var selection = document.getSelection(),
range = selection.getRangeAt(0),
clientRects = range.getClientRects()
console.log(clientRects)
}
http://jsfiddle.net/XjHtG/
This will return the left, right, top, bottom, width and height of all selections made.

TinyMCE limit text on scrollerActived

I have a textarea in my rails application to collect content from user in a database. The rails application is further feeding that text to an XML-driven flex application.
The flex application has number of fixed sized containers which wraps the text inside (from the XML created by Rails app on-the-fly), but truncates the text if it exceeds the container's height. Problem is; there is no way to present the large text in XML, so it gets adjusted automatically in the compiled flex application. And the fact is; the web-based rails app and front-tier flex app are entirely disconnected in terms of having awareness of their internal events. (like in this case; rails app has no knowledge of the overflow event for flex internal containers and relying on font-size and character/line count doesn't work in this scenario!)
Therefore, I wrote a JS function to watch and rescue the textarea's overflow situation and while setting its attributes (viz; line-height, font-size, font-family, width, height... yada yada) matching that of the flex control. The complex form in rails did the trick to have dynamic number of such textarea's control being observed by the JS function.
Here is the Prototype code to handle the overflow event with the corresponding rescue code for cleanup:
var timeout;
document.observe('dom:loaded', attach_obr);
function attach_obr() {
$$('.active_text').each (function(text_element){
text_element.observe('keyup', function(e){
check_limits(text_element.id);
});
text_element.observe('change', function(e){
check_limits(text_element.id);
});
});
}
function check_limits(eyeD) {
if($(eyeD).scrollHeight > $(eyeD).offsetHeight){
// overflow occured, now the rescue code here
timeout = window.setTimeout(function() {
$("error_notice").hide();
}, 4000);
$("error_notice").show().update('There is no space left in this box, please use a new box to continue adding content');
// truncate text till the scrollbar disappears
while($(eyeD).scrollHeight > $(eyeD).offsetHeight){
$(eyeD).value = $(eyeD).value.slice(0, -1);
}
}
else {
if($("error_notice").innerHTML!=""){
$("error_notice").hide().update("");
clearTime(timeout);
}
}
}
[Note: It works with a minor flaw of truncating few more characters than expected in the last line. User can retype these letters till the end of that line. I guess this is because somehow the change in width of textarea due to the appearance of scroll-bar is effecting either the scrollHeight or offsetHeight during the process & there should be something more to the loop's condition ($(eyeD).scrollHeight > $(eyeD).offsetHeight)]
The while loop makes things bit slower, but at least it is serving the purpose. WYSIWYG is achieved. (I would love to hear any suggestion from the viewers to improve that inelegant code :O )
WYSIWYG is not achieved, in terms of rich/formatted text..
Incorporating Rich Text:
Rather than expecting from user to place tags inside the area , in the next phase, I am planning to deploy tinyMCE in my app. Now, to make the above function work with tinyMCE, I have the following code:
tinyMCE.init({
theme_advanced_buttons1 : "bold, italic, underline, strikethrough, separator, justifyleft, justifycenter, justifyright, justifyfull, separator, forecolor, backcolor",
theme:"advanced",
mode:"textareas",
plugins : "safari",
width: '360px',
height: '198px',
setup : function(ed) {
ed.onChange.add(function(ed, i) {
check_limits(ed.id);
});
}
});
The binding and firing of events is working alright. Unfortunately, the aim to control the text overflow is not working. Reason being;
a) ed.id is the id of my textarea not the interactive panel created by tinyMCE. So, the attributes like scrollHeight are offsetHeight are not getting changed for the hidden textarea control.
b) The value of textarea in this case also contains HTML code rather than the actual text. So, it is very implicit to tell what is the actual text without markup (which in our case is required when truncating the overflowed text).
My questions:
Is there a way to get the scrollHeight and offsetHeight of the control created by tinyMCE?
Is there a way to get the only-text version (without markup) of inner content of tinyMCE control?
(So, when I truncate the text in check_limits function, it doesn't effect/breaks the markup/DOM created by tinyMCE for the formatted text. In other words, I would be simulating the user action of pressing backspace on tinyMCE control in the while loop.)
Elegant way to do this whole exercise with & without tinyMCE?
Any suggestions are greatly appreciated!
First you need to know that tinymce creates a contenteditable iframe to let users edit html contents; contents from that iframe get written back to the textarea onSave. The textarea gets hidden in the rtinymce intiatilization process. The editor id is equal to the textarea id.
Here some suggestions:
1. Relevant code
var frameid = editor.id+'_ifr';
var currentiframe = document.getElementById(frameid);
var offsetHeight = currentiframe .contentDocument.body.offsetHeight;
var scrollHeight = currentfr.Document.body.scrollHeight
2. code for this (using jQuery)
var plain_text = $(editor.getBody()).text();
3. The only more efficient way to handle the while loop in the "without tinymce" case will be to slice off some more characters and follow a logarithmic approach. You slice off a bigger part of the string and then get to the final value in half-part paces. Example: You slice of 20 characters, but it fits. Then you slice off 10 characters of the original string. If it does not fit you try 15 characters and so on... this is more effectife then the while approach, but more complicated to develop.
EDIT:
It seems almost impossible to get the line number from the caret position. Problem here is that you do not know where the a text line breaks. Though it is easy to find out in which paragraph the cursor is located at (tinymce uses paragraphs to wrap text nodes).
There is a way to limit insertion in tinymce based on characters (i.e. limit can be set to 100 characters), but i guess this won't work for your use case unless you use a monospace font.
Another approach could be to set the tinymce css to set the editor window to the exact same width as your flex boxes (set the widht to the iframes body element should be sufficient). In this case it sould be easier to use the scrollHeigth approach - you would only need to find out if the heigth did change after insertion of text and then you could divied the heigth with the lineheigth to egt the line number. I suggest you write an own plugin to implement this. This is not that difficult. Here is a link to a tutorial for this.

Categories