How to make a certain part of textarea read-only - javascript

I have a text area and I dynamically add data to it and it works fine. What I wanted to achieve was after the data is appended that data cant be altered (edited) but after the last element of the data user can start typing on the textarea. I was thinking of maybe calculating the length of string data the set read-only to that part. How can I achieve this? Any help is appreciated. Thanks in advance.
For a visual example take a look at the terminal of this website: https://www.online-python.com/
function test() {
x = 'this is a string to be appended to text area'
document.getElementById("textArea").value = x;
}
<textarea id="textArea"></textarea>
<button onclick="test()">Append</button>

You can add a keydown event listener that checks whether the selectionStart is smaller than the length of the textarea's value minus the length of the string appended:
let x = 'this is a string to be appended to text area'
var hasAppended = false;
function test() {
hasAppended = true
document.getElementById("textArea").value = x;
}
textArea.addEventListener('keydown', function(e) {
if (hasAppended) {
if (this.selectionStart > this.value.length - x.length && this.selectionStart != this.value.length) {
e.preventDefault()
e.stopPropagation()
}
}
})
<textarea id="textArea"></textarea><button onclick="test()">Append</button>

It is not possible to selectively mark parts of a <textarea> read-only, however, a similar effect can be achieved with contenteditable elements:
function test() {
const x = 'this is a string to be appended to text area'
const span = document.createElement('span')
span.appendChild(document.createTextNode(x))
span.setAttribute('contenteditable', 'false')
document.getElementById("textArea").appendChild(span);
}
#textArea{
border: 1px solid black;
height: 50px;
}
<div id="textArea" contenteditable="true"></div>
<button onclick="test()">Append</button>
However, this will still allow the user to delete the read-only block, or write before it.

This almost works.
EDIT:
Improved it a bit by changing keydown to keyup.
Now there is no need for space at the end of the read-only text and CTRL+a and then backspace will make the text come back almost instantly.
Maybe you can improve on it.
window.onload = function() {
document.getElementById('textArea').addEventListener('keyup', function (e){
var readOnlyLength = parseInt(document.getElementById("textArea").getAttribute('data-readOnlyLength') );
var currentLength = parseInt(document.getElementById("textArea").value.length );
if (readOnlyLength >= currentLength ) {
document.getElementById("textArea").value = document.getElementById("textArea").getAttribute('data-readonly') ;
}
}, false);
};
function test() {
x = 'this is a string to be appended to text area'
document.getElementById("textArea").value = x;
document.getElementById("textArea").setAttribute('data-readonly' , x);
document.getElementById("textArea").setAttribute('data-readOnlyLength' , x.length);
}
<textarea id="textArea"></textarea>
<button onclick="test()">Append</button>

Related

How to output a textarea in HTML to a variable in Javascript?

In a HTML website I have a textarea created like this:
<textarea id = "myTextArea" rows = "30" cols = "80"></textarea>
I would like after something is written in the text area, for that text to be sent to a variable in javascript.
I have tried doing this, but it did not work:
var x = document.getElementById("myTextArea").value;
The console.log(x); gives back nothing, not null, just empty space. However, if I log out console.log(document.getElementById("myTextArea").value) then I get the text that I have written in my textarea.
Why does var x = document.getElementById("myTextArea").value; not work?
My Javascript:
<script>
var x = document.getElementById("myTextArea").value;
const regex = /([a-z]+)/;
const match = regex.exec(x);
var intervalID = window.setInterval(myCallback, 500); <!-- Calls every 5s -->
function myCallback() {
if(match){
const name = match[1];
console.log(name);
}
else{
console.log('no match');
console.log(match);
}
}
To achieve this, you can register an event listener on your textarea:
var textArea = document.getElementById('myTextArea');
textArea.addEventListener('input', function(event){
console.log(event.target.value);
});
The listener listens for any input events on your textara and logs the value of your textarea as the value changes.
Here's a live demo for your quick reference.
You will need to use onkeyup and onchange for this. The onchange will prevent context-menu pasting, and the onkeyup will fire for every keystroke.
See my answer on How to impose maxlength on textArea for a code sample.
In my example, the variable is the text variable. This variable is filled with the text of the text by clicking on the button.
Was it necessary?
var x = document.getElementById("myTextArea");
var button = document.querySelector("button");
var text = "";
button.onclick = function() {
text = x.value;
console.log(text);
}
#myTextArea {
width: 100%;
height: 100px;
}
<textarea id="myTextArea" rows="30" cols="80"></textarea>
<button>Check variable with text</button>
Second example using oninput event.
var x = document.getElementById("myTextArea");
var text = "";
x.oninput = function() {
text = this.value;
console.log(text);
}
#myTextArea {
width: 100%;
height: 100px;
}
<textarea id="myTextArea" rows="30" cols="80"></textarea>

How to build a Smart Compose like Gmail? Possible in a textarea?

The new predictive type feature Smart Compose of Gmail is quite interesting.
Let's say we want to implement such a functionality ourselves:
User enters beginning of text, e.g. How and in gray behind it appears are you?.
User hits TAB and the word tomorrow is set.
Example:
Can a textarea with Javascript be used to achieve this?
And if not, how could this be implemented otherwise?
My previous answer got deleted, so here's a better attempt at explaining how I've somewhat replicated Smart Compose. My answer only focuses on the pertinent aspects. See https://github.com/jkhaui/predictable for the code.
We are using vanilla js and contenteditable in our solution (just like Gmail does). I bootstrap my example with create-react-app and Medium-Editor, but neither React nor Medium-Editor are necessary.
We have a database of "suggestions" which can be an array of words or phrases. For our purposes, in my example, I use a static array containing 50,000+ common English phrases. But you can easily see how this could be substituted for a dynamic data-source - such as how Gmail uses its neural network API to offer suggestions based on the current context of users' emails: https://ai.googleblog.com/2018/05/smart-compose-using-neural-networks-to.html
Smart Compose uses JavaScript to insert a <span></span> element immediately after the word you are writing when it detects a phrase to suggest. The span element contains only the characters of the suggestion that have not been typed.
E.g. Say you've written "Hi, how a" and a suggestion appears. Let's say the entire suggestion is "how are you going today". In this case, the suggestion is rendered as "re you going today" within the span. If you continue typing the characters in the placeholder - such as "Hi, how are you goi" - then the text content of the span changes dynamically - such that "ng today" is now the text within the span.
My solution works slightly differently but achieves the same visual effect. The difference is I can't figure out how to insert an inline span adjacent to the user's current text and dynamically mutate the span's content in response to the user's input.
So, Instead, I've opted for an overlay element containing the suggestion. The trick is now to position the overlay container exactly over the last word being typed (where the suggestion will be rendered). This provides the same visual effect of an inline typeahead suggestion.
We achieve correct positioning of the overlay by calculating the top + left coordinates for the last word being typed. Then, using JavaScript, we couple the top + left CSS attributes of the overlay container so that they always match the coordinates of the last word. The tricky part is getting these coordinates in the first place. The general steps are:
Call window.getSelection().anchorNode.data.length which retrieves the current text node the user is writing in and returns its length, which is necessary to calculate the offset of the last word within its parent element (explained in the following steps).
For simplicity's sake, only continue if the caret is at the end of the text.
Get the parent node of the current text node we're in. Then get the length of the parent node's text content.
The parent node's text length - the current text node's (i.e the last word's) text length = the offset position of the last text node within its contenteditable parent.
Now we have the offset of the last word, we can use the various range methods to insert a span element immediately preceding the last word: https://developer.mozilla.org/en-US/docs/Web/API/Range
Let's call this span element a shadowNode. Mentally, you can now picture the DOM as follows: we have the user's text content, and we have a shadowNode placed at the position of the last word.
Finally, we call getBoundingClientRect on the shadowNode which returns specific metadata, including the top + left coordinates we're after.
Apply the top + left coordinates to the suggestions overlay container and add the appropriate event handlers/listeners to render the suggestion when Tab is pressed.
Visit this link for documentation https://linkkaro.com/autocomplete.html .
May be you need to make few adjustment in CSS ( padding and width ).
I hope it will help.[![
$(document).ready(function(){
//dummy random output. You can use api
var example = {
1:"dummy text 1",
2:"dummy text 2"
};
function randomobj(obj) {
var objkeys = Object.keys(obj)
return objkeys[Math.floor(Math.random() * objkeys.length)]
}
var autocomplete = document.querySelectorAll("#autocomplete");
var mainInput = document.querySelectorAll("#mainInput");
var foundName = '';
var predicted = '';
var apibusy= false;
var mlresponsebusy = false;
$('#mainInput').keyup(function(e) {
//check if null value send
if (mainInput[0].value == '') {
autocomplete[0].textContent = '';
return;
}
//check if space key press
if (e.keyCode == 32) {
CallMLDataSetAPI(e);
scrolltobototm();
return;
}
//check if Backspace key press
if (e.key == 'Backspace'){
autocomplete[0].textContent = '';
predicted = '';
apibusy = true;
return;
}
//check if ArrowRight or Tab key press
if(e.key != 'ArrowRight'){
if (autocomplete[0].textContent != '' && predicted){
var first_character = predicted.charAt(0);
if(e.key == first_character){
var s1 = predicted;
var s2 = s1.substr(1);
predicted = s2;
apibusy = true;
}else{
autocomplete[0].textContent = '';
apibusy= false;
}
}else{
autocomplete[0].textContent = '';
apibusy= false;
}
return;
}else{
if(predicted){
if (apibusy == true){
apibusy= false;
}
if (apibusy== false){
mainInput[0].value = foundName;
autocomplete[0].textContent = '';
}
}else{
return;
}
}
function CallMLDataSetAPI(event) {
//call api and get response
var response = {
"predicted": example[randomobj(example)]
};
if(response.predicted != ''){
predicted = response.predicted;
var new_text = event.target.value + response.predicted;
autocomplete[0].textContent = new_text;
foundName = new_text
}else{
predicted = '';
var new_text1 = event.target.value + predicted;
autocomplete[0].textContent = new_text1;
foundName = new_text1
}
};
});
$('#mainInput').keypress(function(e) {
var sc = 0;
$('#mainInput').each(function () {
this.setAttribute('style', 'height:' + (0) + 'px;overflow-y:hidden;');
this.setAttribute('style', 'height:' + (this.scrollHeight+3) + 'px;overflow-y:hidden;');
sc = this.scrollHeight;
});
$('#autocomplete').each(function () {
if (sc <=400){
this.setAttribute('style', 'height:' + (0) + 'px;overflow-y:hidden;');
this.setAttribute('style', 'height:' + (sc+2) + 'px;overflow-y:hidden;');
}
}).on('input', function () {
this.style.height = 0;
this.style.height = (sc+2) + 'px';
});
});
function scrolltobototm() {
var target = document.getElementById('autocomplete');
var target1 = document.getElementById('mainInput');
setInterval(function(){
target.scrollTop = target1.scrollHeight;
}, 1000);
};
$( "#mainInput" ).keydown(function(e) {
if (e.keyCode === 9) {
e.preventDefault();
presstabkey();
}
});
function presstabkey() {
if(predicted){
if (apibusy == true){
apibusy= false;
}
if (apibusy== false){
mainInput[0].value = foundName;
autocomplete[0].textContent = '';
}
}else{
return;
}
};
});
#autocomplete { opacity: 0.6; background: transparent; position: absolute; box-sizing: border-box; cursor: text; pointer-events: none; color: black; width: 421px;border:none;} .vc_textarea{ padding: 10px; min-height: 100px; resize: none; } #mainInput{ background: transparent; color: black; opacity: 1; width: 400px; } #autocomplete{ opacity: 0.6; background: transparent;padding: 11px 11px 11px 11px; }
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<textarea id="autocomplete" type="text" class="vc_textarea"></textarea>
<textarea id="mainInput" type="text" name="comments" placeholder="Write some text" class="vc_textarea"></textarea>
]1]1

Overflowing input text

Here is the JSFiddle demo: http://jsfiddle.net/qox0yxb4/
I am using type() to create a typewriter affect (each character is printed on the screen with a delay in between). I use addTextToScreen(textForScreen) to add text to the queue which is then added to the screen through type(). When I call addTextToScreen() from within the JavaScript, the text seems to be formatted as it does NOT overflow on the x-axis, however when I accept input from an HTML <input> tag (printText()), the text overflows on the x-axis.
Here are the JavaScript methods:
var i = 0,
isTag=false,
text;
var typeTime = 45;
function type() {
if (text === textOnScreen){
setTimeout(function(){type();}, typeTime);
return;
}
text = textOnScreen.slice(0, i+1);
i++;
document.getElementById('paraText').innerHTML = text;
var char = text.slice(-1);
console.log(char);
if( char === '<' ) isTag = true;
if( char === '>' ) isTag = false;
if (isTag) return type();
setTimeout(function(){type();}, typeTime);
}
function addTextToScreen(textForScreen){
textOnScreen = textOnScreen + textForScreen;
}
type();
function printText(event) {
if(event.keyCode == 13){
printText = document.getElementById("inputText").value.toString();
addTextToScreen("<br>" + printText.toString());
x = document.getElementById("inputText");
x.value = "";
}
}
I also noticed that whenever I paste text into the input box (the text can be from anywhere) it seems to be formatted, and it does NOT overflow.
Add this css property to #paraText:
word-wrap:break-word;
JS Fiddle Demo
Josh suggested using break-all, here is the difference.
The magic CSS rule you're missing is word-break: break-all;. Add that, and it works just like you'd expect.
Proof

Detect if text-overflow:ellipsis is active on input field

I am wondering it there is a way to detect if text-overflow:ellipsis is active on an input field so i can show a tooltip with the full text.
Css:
input[type='text']
{
text-overflow:ellipsis;
}
Html:
<input type="text" onmouseover="Tooltip.Show(this)" value="some long text" />
Javascript:
Tooltip.Show = function (input)
{
// This is where i need the see if the current input show ellipsis.
if ($(input).isEllipsisActive())
{
// Show the tooltip
}
}
BR
Andreas
Please note, this question is about input element. A normal HTML element (e.g., div) also has to have
white-space: nowrap;
overflow: hidden;
for text-overflow: ellipsis; to apply. (Overflow can also be scroll or auto.) See this link for more information.
If you want to know when the input text is too long and hidden ... there is no native support for checking thinks like this. You can hack it. You can make a tmp container with the same text, look the width of that container/text and compare it with the length of the input. If the tmp container is longer ... you have too long text and.
something like this:
function isEllipsisActive() {
return_val = false;
var text = $('input_selector').val();
var html = "<span id="tmpsmp">" + text + "</span>";
$(body).append(html);
if($('input_selector').width() < $('#tmpsmp').width()) {
return_val = true;
}
return return_val;
}
Thanks Aleksandrenko.
Just edited a little your solution:
function isEllipsisActive(el) {
return_val = false;
var text = el.val();
var html = "<span id='tmpsmp'>" + text + "</span>";
$("body").append(html);
if(el.width() < $('#tmpsmp').width()) {
return_val = true;
}
$('#tmpsmp').remove();
return return_val;
}

Auto-scroll <textarea> with Javascript

I have this textarea that shows inputted text, but when the number of lines exceeds the size and width of the textarea, the user has to scroll down to see what they have last inputted.
I'd like the textarea to be set to the bottom everytime the enter button is pressed.
I've tried the following, but I can't get it to work:
function inputKeyDown(evt, input) {
if (evt.keyCode == 13) {
var textarea = document.getElementById("textarea");
textarea.value += "\n" + ">" + " " + input.value;
input.value = "";
return false;
}
var elem = document.getElementById('textarea');
elem.scrollTop = elem.scrollHeight;
}
and then I call the function keyDown in <input onKeyDown="return keyDown(event, this);" ...>
Any idea why no workie?
Try the following:
textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
It depends on what sort of input you are looking at, but forcing the cursor back to the bottom could potentially be a serious usability problem. If you need a textarea that automatically expands, have a look at existing solutions such as this jQuery plugin
I'm not really sure if this is what you want but have a look this: http://jsfiddle.net/yV76p/
var textarea = document.getElementById("textarea");
textarea.onkeyup = function(evt) {
this.scrollTop = this.scrollHeight;
}

Categories