Adjust width of input field to its input - javascript

<input type="text" value="1" style="min-width:1px;" />
This is my code and it is not working. Is there any other way in HTML, JavaScript, PHP or CSS to set minimum width?
I want a text input field with a dynamically changing width, so that the input field fluids around its contents. Every input has a built-in padding of 2em, that is the problem and second problem is that min-width ain't working on input at all.
If I set width more than it is needed than the whole program is messy, I need the width of 1px, more only if it's needed.

In modern browser versions, CSS unit ch is also available. To my understanding, it is font-independent unit, where 1ch equals to width of character 0 (zero) in any given font.
Thus, something as simple as following could be used as resize function, by binding to the input event:
var input = document.querySelector('input'); // get the input element
input.addEventListener('input', resizeInput); // bind the "resizeInput" callback on "input" event
resizeInput.call(input); // immediately call the function
function resizeInput() {
this.style.width = this.value.length + "ch";
}
input{ font-size:1.3em; padding:.5em; }
<label>Text
<input>
</label>
That example would resize the input to length of the value + 2 characters extra.
One potential problem with the unit ch is that in many fonts (i.e. Helvetica) the width of the character "m" exceeds the width of the character 0 and the character "i" is much narrower. 1ch is usually wider than the average character width, usually by around 20-30% according to this post.

It sounds like your expectation is that the style be applied dynamically to the width of the textbox based on the contents of the textbox. If so you will need some js to run on textbox contents changing, something like this:
<input id="txt" type="text" onkeypress="this.style.width = ((this.value.length + 1) * 8) + 'px';">
Note: this solution only works when every character is exactly 8px wide. You could use the CSS-Unit "ch" (characters) which represents the width of the character "0" in the chosen font. You can read about it here.

To calculate the width of the current input, you'll have to embed it in a temporary span element, attach that thing to the DOM, get the computed width (in pixels) using the scrollWidth property and remove the span again. Of course you'll have to ensure that the same font family, font size, etc., is used in the input as well as in the span element. Therefore I assigned the same class to them.
I attached the function to the keyup event, as on keypress the input character is not yet added to the input value, so that will result in the wrong width. Unfortunately, I don't know how to get rid of the scrolling of the input field (when adding characters to the end of the field); it scrolls, because the character is added and shown before adjustWidthOfInput() is called. And, as said, I can't do this the other way round because then you'll have the value of the input field before the pressed character is inserted. I'll try to solve this issue later.
BTW, I only tested this in Firefox (3.6.8), but you'll get the point, I hope.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Get/set width of <input></title>
<style>
body {
background: #666;
}
.input-element {
border: 0;
padding: 2px;
background: #fff;
font: 12pt sans-serif;
}
.tmp-element {
visibility: hidden;
white-space: pre;
}
</style>
</head>
<body>
<input id="theInput" type="text" class="input-element" value="1">
<script>
var inputEl = document.getElementById("theInput");
function getWidthOfInput() {
var tmp = document.createElement("span");
tmp.className = "input-element tmp-element";
tmp.innerHTML = inputEl.value.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
document.body.appendChild(tmp);
var theWidth = tmp.getBoundingClientRect().width;
document.body.removeChild(tmp);
return theWidth;
}
function adjustWidthOfInput() {
inputEl.style.width = getWidthOfInput() + "px";
}
adjustWidthOfInput();
inputEl.onkeyup = adjustWidthOfInput;
</script>
</body>
</html>

Here is a solution without monospaced font needed, with only a very small piece code of javascript, does not need to calculate computed styles, and even supports IME, supports RTL text.
// copy the text from input to the span
$(function () {
$('.input').on('input', function () { $('.text').text($('.input').val()); });
});
* {
box-sizing: border-box;
}
.container {
display: inline-block;
position: relative;
}
.input,
.text {
margin: 0;
padding: 2px 10px;
font-size: 24px;
line-height: 32px;
border: 1px solid #ccc;
box-radius: 3px;
height: 36px;
font: 20px/20px sans-serif;
/* font: they should use same font; */
}
.text {
padding-right: 20px;
display: inline-block;
visibility: hidden;
white-space: pre;
}
.input {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
}
<script src="https://code.jquery.com/jquery-3.2.0.min.js"></script>
<div class="container">
<span class="text">
some text
</span>
<input class="input" value="some text" />
</div>
Use the span.text to fit width of text, and let the input have same size with it by position: absolute to the container. Copy value of input to the span every time it changes (you may change this piece of code to vanilla JS easily, or use features provided by your frontend framework). So the input will just fit the size of its content.

Here's a modification of Lyth's answer that takes into account:
Deletion
Initialisation
Placeholders
It also allows for any number of input fields! To see it in action: http://jsfiddle.net/4Qsa8/
Script:
$(document).ready(function () {
var $inputs = $('.resizing-input');
// Resize based on text if text.length > 0
// Otherwise resize based on the placeholder
function resizeForText(text) {
var $this = $(this);
if (!text.trim()) {
text = $this.attr('placeholder').trim();
}
var $span = $this.parent().find('span');
$span.text(text);
var $inputSize = $span.width();
$this.css("width", $inputSize);
}
$inputs.find('input').keypress(function (e) {
if (e.which && e.charCode) {
var c = String.fromCharCode(e.keyCode | e.charCode);
var $this = $(this);
resizeForText.call($this, $this.val() + c);
}
});
// Backspace event only fires for keyup
$inputs.find('input').keyup(function (e) {
if (e.keyCode === 8 || e.keyCode === 46) {
resizeForText.call($(this), $(this).val());
}
});
$inputs.find('input').each(function () {
var $this = $(this);
resizeForText.call($this, $this.val())
});
});
Style:
.resizing-input input, .resizing-input span {
font-size: 12px;
font-family: Sans-serif;
white-space: pre;
padding: 5px;
}
HTML:
<div class="resizing-input">
<input type="text" placeholder="placeholder"/>
<span style="display:none"></span>
</div>
$(document).ready(function() {
var $inputs = $('.resizing-input');
// Resize based on text if text.length > 0
// Otherwise resize based on the placeholder
function resizeForText(text) {
var $this = $(this);
if (!text.trim()) {
text = $this.attr('placeholder').trim();
}
var $span = $this.parent().find('span');
$span.text(text);
var $inputSize = $span.width();
$this.css("width", $inputSize);
}
$inputs.find('input').keypress(function(e) {
if (e.which && e.charCode) {
var c = String.fromCharCode(e.keyCode | e.charCode);
var $this = $(this);
resizeForText.call($this, $this.val() + c);
}
});
// Backspace event only fires for keyup
$inputs.find('input').keyup(function(e) {
if (e.keyCode === 8 || e.keyCode === 46) {
resizeForText.call($(this), $(this).val());
}
});
$inputs.find('input').each(function() {
var $this = $(this);
resizeForText.call($this, $this.val())
});
});
.resizing-input input,
.resizing-input span {
font-size: 12px;
font-family: Sans-serif;
white-space: pre;
padding: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div class="resizing-input">
First:
<input type="text" placeholder="placeholder" />
<span style="display:none"></span>
</div>
<br>

This is an Angular-specific answer, but this worked for me and has been very satisfying in terms of its simplicity and ease-of-use:
<input [style.width.ch]="value.length" [(ngModel)]="value" />
It automatically updates via the character units in Jani's answer.

FOR A NICER LOOK&FEEL
You should use jQuery keypress() event in combination with String.fromCharCode(e.which) to get the pressed character. Hence you can calculate what your width will be. Why? Because it will look a lot more sexy :)
Here is a jsfiddle that results in a nice behaviour compared to solutions using the keyup event : http://jsfiddle.net/G4FKW/3/
Below is a vanilla JS which listens to the input event of an <input> element and sets a span sibling to have the same text value in order to measure it.
document.querySelector('input').addEventListener('input', onInput)
function onInput(){
var spanElm = this.nextElementSibling;
spanElm.textContent = this.value; // the hidden span takes the value of the input;
this.style.width = spanElm.offsetWidth + 'px'; // apply width of the span to the input
};
/* it's important the input and its span have same styling */
input, .measure {
padding: 5px;
font-size: 2.3rem;
font-family: Sans-serif;
white-space: pre; /* white-spaces will work effectively */
}
.measure{
position: absolute;
left: -9999px;
top: -9999px;
}
<input type="text" />
<span class='measure'></span>

Here is an alternative way to solve this using a DIV and the 'contenteditable' property:
HTML:
<div contenteditable = "true" class = "fluidInput" data-placeholder = ""></div>
CSS: (to give the DIV some dimensions and make it easier to see)
.fluidInput {
display : inline-block;
vertical-align : top;
min-width : 1em;
height : 1.5em;
font-family : Arial, Helvetica, sans-serif;
font-size : 0.8em;
line-height : 1.5em;
padding : 0px 2px 0px 2px;
border : 1px solid #aaa;
cursor : text;
}
.fluidInput * {
display : inline;
}
.fluidInput br {
display : none;
}
.fluidInput:empty:before {
content : attr(data-placeholder);
color : #ccc;
}
Note: If you are planning on using this inside of a FORM element that you plan to submit, you will need to use Javascript / jQuery to catch the submit event so that you can parse the 'value' ( .innerHTML or .html() respectively) of the DIV.

Just adding on top of other answers.
I noticed that nowadays in some browsers the input field has a scrollWidth. Which means:
this.style.width = this.scrollWidth + 'px';
should work nicely. tested in chrome, firefox and safari.
For deletion support, you can add '=0' first and then readjust.
this.style.width = 0; this.style.width = this.scrollWidth + 'px';

This answer provides one of the most accurate methods of retrieving text width available in the browser and is more accurate than the accepted answer. It uses the canvas html5 element and unlike other answers does not add the element into the DOM and thus avoids any reflow issues caused by excessively adding elements to the DOM.
Read more about the Canvas element here in relation to text width.
NOTE: According to MDN the shorthand versions of the getPropertyValue() method such as font can be unreliable. I'd recommend getting the values singularly to improve compatibility. I only used it here for speed.
/**
* returns the width of child text of any DOM node as a float
*/
function getTextWidth(el) {
// uses a cached canvas if available
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
// get the full font style property
var font = window.getComputedStyle(el, null).getPropertyValue('font');
var text = el.value;
// set the font attr for the canvas text
context.font = font;
var textMeasurement = context.measureText(text);
return textMeasurement.width;
}
var input = document.getElementById('myInput');
// listen for any input on the input field
input.addEventListener('input', function(e) {
var width = Math.floor(getTextWidth(e.target));
// add 10 px to pad the input.
var widthInPx = (width + 10) + "px";
e.target.style.width = widthInPx;
}, false);
#myInput {
font: normal normal 400 normal 18px / normal Roboto, sans-serif;
min-width: 40px;
}
<input id="myInput" />

You can set an input's width using the size attribute as well. The size of an input determines it's width in characters.
An input could dynamically adjust it's size by listening for key events.
For example
$("input[type='text']").bind('keyup', function () {
$(this).attr("size", $(this).val().length );
});
JsFiddle here

You could do something like this
// HTML
<input id="input" type="text" style="width:3px" />
// jQuery
$(function(){
$('#input').keyup(function(){
$('<span id="width">').append( $(this).val() ).appendTo('body');
$(this).width( $('#width').width() + 2 );
$('#width').remove();
});
});
​
​

You can just set size attribute.
If you're using one of reactive frameworks, the following will be enough:
<input size="{{ yourValue.length }}" [value]="yourValue" />
but if you're using pure js, you should set event handlers, like:
<input oninput="this.setAttribute('size', this.value.length)" />

It's worth noting that a nice-looking resize can be done when the font is monospaced, so we can perfectly resize the input element using the ch unit.
Also in this approach we can update the width of the field by just updating a CSS variable (custom property) on input event and we should also take care of already pre-filled input on DOMContentLoaded event
Codepen demo
Markup
<input type="text" value="space mono font" class="selfadapt" />
CSS
:root { --size: 0; }
.selfadapt {
padding: 5px;
min-width: 10ch;
font-family: "space mono";
font-size: 1.5rem;
width: calc(var(--size) * 1ch);
}
As a root variable we set --size: 0: this variable will contain the length of the input and it will be multiplied by 1ch inside the calc() expression. By default we could also set a min-width, e.g. 10ch
The Javascript part reads the length of the value inserted and updates the variable --size:
JS
let input = document.querySelector('.selfadapt');
let root = document.documentElement.style;
/* on input event auto resize the field */
input.addEventListener('input', function() {
root.setProperty('--size', this.value.length );
});
/* resize the field if it is pre-populated */
document.addEventListener("DOMContentLoaded", function() {
root.setProperty('--size', input.value.length);
});
of course this still works even if you don't use monospaced font, but in that case you will need to change the calc() formula by multiplying the --size variable by another value (which it's strictly dependent on the font-family and font-size) different than 1ch.

Here is a plain JS and a jQuery plugin I wrote that will handle resizing an input element using a canvas and the font size / family to determine the actual string length when rendered. (only works in > IE9, chrome, safari, firefox, opera and most other major browsers that have implemented the canvas element).
PlainJS:
function autoSize(input, o) {
o || (o = {});
o.on || (o.on = 'keyup');
var canvas = document.createElement('canvas');
canvas.setAttribute('style', 'position: absolute; left: -9999px');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
input.addEventListener(o.on, function () {
ctx.font = getComputedStyle(this,null).getPropertyValue('font');
this.style.width = ctx.measureText(this.value + ' ').width + 'px';
});
}
//Usage
autoSize(document.getElementById('my-input'));
jQuery Plugin:
$.fn.autoSize = function(o) {
o = $.extend({}, {
on: 'keyup'
}, o);
var $canvas = $('<canvas/>').css({position: 'absolute', left: -9999});
$('body').append($canvas);
var ctx = $canvas[0].getContext('2d');
return this.on(o.on, function(){
var $this = $(this);
ctx.font = $this.css('font');
$this.width(ctx.measureText($this.val()).width + 'px');
})
}
//Usage:
$('#my-input').autoSize();
Note: this will not handle text-transforms, line spacing and letter spacing, and probably some other text size changing properties. To handle text-transform property set and adjust the text value to match that property. The others are probably fairly straight forward. I will implement if this starts gaining some traction...

A bullet-proof, generic way has to:
Take into account all possible styles of the measured input element
Be able to apply the measurement on any input without modifying the HTML or
Codepen demo
var getInputValueWidth = (function(){
// https://stackoverflow.com/a/49982135/104380
function copyNodeStyle(sourceNode, targetNode) {
var computedStyle = window.getComputedStyle(sourceNode);
Array.from(computedStyle).forEach(key => targetNode.style.setProperty(key, computedStyle.getPropertyValue(key), computedStyle.getPropertyPriority(key)))
}
function createInputMeassureElm( inputelm ){
// create a dummy input element for measurements
var meassureElm = document.createElement('span');
// copy the read input's styles to the dummy input
copyNodeStyle(inputelm, meassureElm);
// set hard-coded styles needed for propper meassuring
meassureElm.style.width = 'auto';
meassureElm.style.position = 'absolute';
meassureElm.style.left = '-9999px';
meassureElm.style.top = '-9999px';
meassureElm.style.whiteSpace = 'pre';
meassureElm.textContent = inputelm.value || '';
// add the meassure element to the body
document.body.appendChild(meassureElm);
return meassureElm;
}
return function(){
return createInputMeassureElm(this).offsetWidth;
}
})();
// delegated event binding
document.body.addEventListener('input', onInputDelegate)
function onInputDelegate(e){
if( e.target.classList.contains('autoSize') )
e.target.style.width = getInputValueWidth.call(e.target) + 'px';
}
input{
font-size:1.3em;
padding:5px;
margin-bottom: 1em;
}
input.type2{
font-size: 2.5em;
letter-spacing: 4px;
font-style: italic;
}
<input class='autoSize' value="type something">
<br>
<input class='autoSize type2' value="here too">

Quite simple:
oninput='this.style.width = (this.scrollWidth - N) + "px";'
Where N is some number (2 in the example, 17 on something I'm developing) that is determined experimentally.
Subtracting N prevents this strange extrenuous space from accumulating long before the text reaches the end of the text box.
Compare. Pay careful attention to how the size changes after even just the first character.
<p>Subtracting N:</p>
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth-2) + "px";'>
<p>Not Subtracting N:</p>
<input type="text" placeholder="enter lots of text here" oninput='this.style.width = (this.scrollWidth) + "px";'>

Here is my React solution, it works with any font size, just make sure you have a monospace font (all font character widths are the same on monospace fonts) like i have in my solution, and it will work perfectly.
JS:
const [value, setValue] = useState(0)
HTML:
<input value={value} onChange={(e) => {setValue(e.target.value)}} style={{width: `${value.toString().length}`ch}}/>
CSS:
#import url("https://fonts.googleapis.com/css2?family=B612+Mono&display=swap");
input{
font-family: "B612 Mono", monospace;
}

You can do it even simpler in angularjs using the built-in ng-style directive.
In your html:
<input ng-style="inputStyle(testdata)" ng-model="testdata" />
In your controller:
$scope.testdata = "whatever";
$scope.inputStyle = function(str) {
var charWidth = 7;
return {"width": (str.length +1) * charWidth + "px" };
};
In your css:
input { font-family:monospace; font-size:12px; }
Adjust the charWidth to match the width of your font. It seems to be 7 at a font-size of 12px;

If you use Bootstrap, it could be done very easily:
<div contenteditable="true" class="form-control" style="display: inline"></div>
You will just need to fetch div's content and put it in a hidden input before submitting the form.

Here is my 2 cents.
Create an empty invisible div. Fill it with the input content and return the width to the input field. Match text styles between each box.
$(".answers_number").keyup(function(){
$( "#number_box" ).html( $( this ).val() );
$( this ).animate({
width: $( "#number_box" ).width()+20
}, 300, function() {
});
});
#number_box {
position: absolute;
visibility: hidden;
height: auto;
width: auto;
white-space: nowrap;
padding:0 4px;
/*Your font styles to match input*/
font-family:Arial;
font-size: 30px;
}
.answers_number {
font-size: 30px;
font-family:Arial;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" class="answers_number" />
<div id="number_box">
</div>

Here's a simple function to get what is needed:
function resizeInput() {
const input = document.getElementById('myInput');
input.style.width = `${input.scrollWidth}px`;
};

I think you're misinterpreting the min-width CSS property. min-width is generally used to define a minimum DOM width in a fluid layout, like:
input {
width: 30%;
min-width: 200px;
}
That would set the input element to a minimum width of 200 pixels. In this context, "px" stands for "pixels".
Now, if you're trying to check to make sure that input field contains at least one character when a user submits it, you'll need to do some form validation with JavaScript and PHP. If that is indeed what you're attempting to do, I'll edit this answer and do my best to help you out.

I really liked Lyth's answer, but also really wanted it to:
Handle backspace and delete
Not require you to manually add an adjacent tag.
Enforce a min width.
Automatically be applied to elements with a specific class
I adapted his JSFiddle and came up with this. One improvement not present in this fiddle would be to use something like the jQuery CSS Parser to actually read the initial width from the input.textbox-autosize rule, and use that as the minWidth. Right I'm simply using an attribute on the , which makes for a compact demo but is not ideal. as it requires an extra attribute on each input. You might also just want to put the minWidth as 100 right in the JavaScript.
HTML:
<div id='applicationHost'>
<div>Name: <input class='textbox-autosize' data-min-width='100' type="text" /></div>
<div>Email: <input class='textbox-autosize' data-min-width='100' type="email" /></div>
<div>Points: <input class='textbox-autosize' data-min-width='100' type="number" /></div>
</div>
CSS:
#applicationHost {
font-family: courier;
white-space: pre;
}
input.textbox-autosize, span.invisible-autosize-helper {
padding:0;
font-size:12px;
font-family:Sans-serif;
white-space:pre;
}
input.textbox-autosize {
width: 100px; /* Initial width of textboxes */
}
/*
In order for the measurements to work out, your input and the invisible
span need to have the same styling.
*/
JavaScript:
$('#applicationHost').on('keyup', '.textbox-autosize', function(e) {
// Add an arbitary buffer of 15 pixels.
var whitespaceBuffer = 15;
var je = $(this);
var minWidth = parseInt(je.attr('data-min-width'));
var newVal = je.val();
var sizingSpanClass = 'invisible-autosize-helper';
var $span = je.siblings('span.' + sizingSpanClass).first();
// If this element hasn't been created yet, we'll create it now.
if ($span.length === 0) {
$span = $('<span/>', {
'class': sizingSpanClass,
'style': 'display: none;'
});
je.parent().append($span);
}
$span = je.siblings('span').first();
$span.text(newVal) ; // the hidden span takes
// the value of the input
$inputSize = $span.width();
$inputSize += whitespaceBuffer;
if($inputSize > minWidth)
je.css("width", $inputSize) ; // apply width of the span to the input
else
je.css("width", minWidth) ; // Ensure we're at the min width
});

Better is onvalue:
<input id="txt" type="text" onvalue="this.style.width = ((this.value.length + 1) * 8) + 'px';">
It also involves pasting, dragging and dropping, etc.

The best solution is <input ... size={input.value.length} />

Svelte version:
<input type="text" style="width: {tag.length}ch" bind:value={tag} />

I just spend some time figuring out how to do it.
Actually the simplest way I found is to move input value to span just before the input, keeping input 1 symbol width. Though I can't be sure that it fit for your initial need.
Maybe it some extra code, but in react+flux based application it is quite natural solution.

Based off Michael's answer, I have created my own version of this using jQuery. I think it is a cleaner/shorter version of most answers here and it seems to get the job done.
I am doing the same thing as most of the people here by using a span to write the input text into then getting the width. Then I am setting the width when the actions keyup and blur are called.
Here is a working codepen. This codepen shows how this can be used with multiple input fields.
HTML Structure:
<input type="text" class="plain-field" placeholder="Full Name">
<span style="display: none;"></span>
jQuery:
function resizeInputs($text) {
var text = $text.val().replace(/\s+/g, ' '),
placeholder = $text.attr('placeholder'),
span = $text.next('span');
span.text(placeholder);
var width = span.width();
if(text !== '') {
span.text(text);
var width = span.width();
}
$text.css('width', width + 5);
};
The function above gets the inputs value, trims the extra spaces and sets the text into the span to get the width. If there is no text, it instead gets the placeholder and enters that into the span instead. Once it enters the text into the span it then sets the width of the input. The + 5 on the width is because without that the input gets cut off a tiny bit in the Edge Browser.
$('.plain-field').each(function() {
var $text = $(this);
resizeInputs($text);
});
$('.plain-field').on('keyup blur', function() {
var $text = $(this);
resizeInputs($text);
});
$('.plain-field').on('blur', function() {
var $text = $(this).val().replace(/\s+/g, ' ');
$(this).val($text);
});
If this could be improved please let me know as this is the cleanest solution I could come up with.

You would like to change the size attribute as the text changes.
# react
const resizeInput = (e) => {
e.target.setAttribute('size', e.target.value.length || 1);
}
<input
onChange={resizeInput}
size={(propertyInput.current && propertyInput.current.value.length) || 1}
ref={propertyInput}
/>

Related

Get cursor position when a file is dropped in textarea in Chrome

When you drag a file from your OS filesystem over a textarea or text input, a cursor appears near the mouse pointer (this is different from positionStart), showing the user where the dragged content would be inserted.
UPDATE: here is an image, I'm dragging a file (test.sh) over the text input. You can see the drop cursor if the middle of the "text" word. The selection cursor is at the end of the string (not visible on this picture).
(Chrome's default behavior is to open the dropped file, but I'm overriding this behavior in the drop event. I want to insert the name of the file in the textarea.)
I'm trying to get this position (in terms of index in the textarea value string) when drop occurs. Any idea?
Phew, what you want to do isn't easy, since there is no way to reference this specific caret!
Off the top of my head, you could only implement this through heavy workarounds: What you can obtain upon drop occuring is the mouse cursor position.
You would have to make an invisible div-clone identical to the textarea in shape, text-size, margins, etc that automatically gets filled with the text from your textarea.
Next you'd have to create a span for each possible caret position (i.e. 1 span for every character of text) inside this div and get each span's x and y coordinates and save them into an array.
.txtarea {
width: 300px;
height: 150px;
font-size: 12px;
font-family: Arial;
padding: 5px;
text-align: left;
border: 1px solid red;
}
<textarea class="txtarea">Mytext</textarea>
<!--just an example, auto-fill this content through a JS oninput event -->
<div class="txtarea"><span>M</span><span>y</span><span>t</span><span>e</span><span>x</span><span>t</span></div>
Then, upon drop occuring, get the mouse coordinates of the event, compare them to the array of coordinates, approximate the closest x/y position and set a new selectionStart index in your textarea based on that, insert the file name, and then restore the previous selectionStart.
This is only a partial answer: The following works in IE11, but not in Chrome (didn't test in other browsers).
Just select some letters from the upper input and drag them into the bottom one:
let to = document.getElementById("to");
to.addEventListener("dragover", function (e) {
if (e.target.selectionStart) {
console.log(e.target.selectionStart);
}
});
<input type="text" id="from" value="select some letters from this sentence, and drag them into the other input element"/ style="width: 500px;"><br/>
<input type="text" id="to" value="drag over here"/>
Few notes for future research:
In Chrome, dragover event is fired only after focus is first set inside the input element.
In Chrome, the value of selectionStart is the value of the last selected text position within the target input (put differently: the last position of the cursor within the target input).
Setting type="number" fires with selectionStart assigned to null in Chrome, but not in IE11.
From Your picture, it looks like an input text (I admit, I was too lazy to code this for a textarea...) but - anyway - this is a nice question.
To get the text width, You need to clone the original element and loop over the text char by char. Just to mention an example, see some answers here on SO about the topic: "how to set the ellipsis text using JavaScript".
I also noticed that by moving the mouse pointer over the text, the caret is shifting left and right from the center of each char. At the end, the main problem here is: how to find the x of the middle of each char.
So, my code is splitted in two parts: text input clone and character position. After that, to position the caret, You can use an existing library like this: jQuery Caret Plugin or jQuery Caret - up to You - I would skip this part of the question.
I tested the code below in FF, Safari, IE & Chrome, there are obviously some small and annoying issues in the drag & drop behavior of these browser, but they seems to me irrelevant.
function getCaretPos(target, x) {
var txt = target.value, doc = target.ownerDocument,
clone = doc.createElement('span'), /* Make a clone */
style = doc.defaultView.getComputedStyle(target, null),
mL = style.getPropertyValue('margin-left'),
pL = style.getPropertyValue('padding-left'),
mouseX = x - parseFloat(mL) - parseFloat(pL);
clone.style.fontFamily = style.getPropertyValue('font-family');
clone.style.fontSize = style.getPropertyValue('font-size');
clone.style.fontWeight = style.getPropertyValue('font-weight');
clone.style.position = 'absolute'; /* Keep layout */
clone.style.left = -9999;
target.parentNode.appendChild(clone);
clone.style.width = 'auto';
clone.style.whiteSpace = 'pre'; /* Keep whitespaces */
clone.style.marginLeft = 0;
clone.style.paddingLeft = 0;
clone.innerText = txt; /* Start from full length */
var i = txt.length, pos = -1, xL = 0, xC = 0, xR = clone.clientWidth;
while (i--) { /* Find the caret pos */
clone.innerText = txt.slice(0, i);
xL = clone.clientWidth;
xC = (0.5 * (xR + xL)) | 0; /* We need the center */
if (xC < mouseX) { pos = i; break }
xR = xL;
}
pos++; /* Restore the correct index */
target.parentNode.removeChild(clone); /* Clean up */
clone = null;
return pos;
}
function onFileDragOver(e) {
if(!window.chrome) e.preventDefault(); /* Show the caret in Chromium */
}
function onFileDrop(e) {
e.preventDefault();
var target = e.currentTarget, txt = target.value,
pos = getCaretPos(target, e.offsetX),
tok1 = txt.substr(0, pos), tok2 = txt.substr(pos);
/* Get the drop-action result */
var result = '', files = e.dataTransfer.files,
data = e.dataTransfer.getData('text');
for (var i = 0, f; (f = files[i]); i++) { result += f.name }
target.value = tok1 + result + tok2;
target.focus();
/* Up to You how to position the caret */
if(target.setSelectionRange) target.setSelectionRange(pos, pos);
//$(target).caret(pos);
}
$(document).ready(function () {
var target = document.getElementById('search-input');
target.addEventListener('dragover', onFileDragOver);
target.addEventListener('drop', onFileDrop);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.2/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.js"></script>
</head>
<body>
<div data-role="page">
<div data-role="header"><h1>Caret Position</h1></div>
<div data-role="content">
<div class="ui-field-contain">
<label for="search-input">Products</label>
<input type="search" name="search-input" id="search-input" value="target text box"
spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="none"/>
</div>
</div>
</div>
</body>
</html>
Try jQuery. What you have to do is get the text box with a query selector, then bind that to mousemove and get $(this).caret().start.
Example:
let cursorPosition = 0;
$("#textinput").bind("mousemove", function() {
cursorPosition = $(this).caret().start;
});
// Do what you want with cursorPosition when file is dropped
And I think that's all you need to do here.
As long as you're willing to use jQuery you should be good.

How do I restrict the style of the text pasted in a contenteditable area?

I came across this Stack Overflow post where was discussed the exact thing I needed: to be able to paste text into a contenteditable area, retaining only a few styles. I ran the code snippet there and it work fine. However, when I tried it on my page, all styles were being removed, including those I wanted to keep, like bold and italic. After comparing the codes and a few experimentations, I realized that the reason it was not working was because I was using external CSS, instead of inline.
Is there any way I can make it work with external CSS? I will never know the origin of the text users will post in that contenteditable, and how was style applied to it, so I am looking to address all possibilities.
Also, is there a way to make it work with dragged and dropped text, instead of just pasted text? I tried replacing the event it is listening to from "paste" to "drop", but I get the error e.clipboardData is undefined
const el = document.querySelector('p');
el.addEventListener('paste', (e) => {
// Get user's pasted data
let data = e.clipboardData.getData('text/html') ||
e.clipboardData.getData('text/plain');
// Filter out everything except simple text and allowable HTML elements
let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
// Insert the filtered content
document.execCommand('insertHTML', false, data);
// Prevent the standard paste behavior
e.preventDefault();
});
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.big {
font-size: 20px;
}
.red {
color: red;
}
.bold {
font-weight: bold;
}
.italic {
text-decoration: italic;
}
<p class="editable" contenteditable></p>
<p class="notEditable">
Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span> All styles are inline, and it works as expected.
</p>
<p>Now, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold">bold</span> <span class="italic">italic</span>. It no longer works.</p>
As other answer pointed out I don't know any way of getting CSS styles out of other pages using clipboard. . But at your own you could do something like this:
Get getComputedStyle (CSS only) of all elements filter out wanted style, in this example fontStyle and fontWeight. Then you can condition if fontStyle==="italic" or fontweight==="700" (bold), textDecoration==="underline rgb(0, 0, 0)" and wrap that elements into its HTML tags.
You do this because your regex function is only targeting tags, not even inline CSS property font-style: italic;. Witch is a shame, it would make things a bit easier as you could just read every elements CSS class style and apply it inline, but this way you need to condition it and apply HTML tag.
if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}
if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}
if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
In example below if you copy Now, try pasting this paragraph with external styles. Big red bold italic. It no longer works. you will get bold,underline and italic. You can do the same for rest of your filtering options.
const el = document.querySelector('p');
el.addEventListener('paste', (e) => {
// Get user's pasted data
let data = e.clipboardData.getData('text/html') ||
e.clipboardData.getData('text/plain');
//console.log(data)
// Filter out everything except simple text and allowable HTML elements
let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
//console.log(data)
// Insert the filtered content
document.execCommand('insertHTML', false, data);
// Prevent the standard paste behavior
e.preventDefault();
});
[...document.querySelectorAll('body *')].forEach(element=>{
const style = getComputedStyle(element)
if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}
if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}
if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
});
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.big {
font-size: 20px;
}
.red {
color: red;
}
.bold {
font-weight: bold;
}
.underline{
text-decoration: underline;
}
.italic {
font-style: italic;
}
<p class="editable" contenteditable></p>
<p class="notEditable">
Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span> All styles are inline, and it works as expected.
</p>
<p id="container"><span class="underline">Now</span>, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold" >bold</span> <span class="italic">italic</span>. It no longer works.</p>
Unfortunately, there is no way to keep the properties of a class from an external source. If you would print the content of the clipboard, you will see that you receive the raw HTML content as it is on the external page, for example:
<div class="some-class">this is the text</div>
The class properties would not be inlined by the browser! And as the content is from an external source, you have no power over it.
On the other hand, if the content is from your page (so the class is defined), you could parse the received HTML and filter the CSS properties, keeping only what you want. Here you have a code sample using vanilla Javascript, no libraries required (also available on Codepen):
const targetEditable = document.querySelector('p');
targetEditable.addEventListener('paste', (event) => {
let data = event.clipboardData.getData('text/html') ||
event.clipboardData.getData('text/plain');
// Filter the string using your already existing rules
// But allow <p> and <div>
let regex = /<(?!(\/\s*)?(div|b|i|em|strong|u|p)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
const newElement = createElementFromHTMLString(data);
const cssContent = generateFilteredCSS(newElement);
addCssToDocument(cssContent);
document.execCommand('insertHTML', false, newElement.innerHTML);
event.preventDefault();
});
// Scan the HTML elements recursively and generate CSS classes containing only the allowed properties
function generateFilteredCSS(node) {
const newClassName = randomString(5);
let content = `.${newClassName}{\n`;
if (node.className !== undefined && node.className !== '') {
// Get an element that has the class
const elemOfClass = document.getElementsByClassName(node.className)[0];
// Get the computed style properties
const styles = window.getComputedStyle(elemOfClass);
// Properties whitelist, keep only those
const propertiesToKeep = ['font-weight'];
for (const property of propertiesToKeep) {
content += `${property}: ${styles.getPropertyValue(property)};\n`;
}
}
content += '}\n';
node.className = newClassName;
for (const child of node.childNodes) {
content += generateFilteredCSS(child);
}
return content;
}
function createElementFromHTMLString(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
return div;
}
function addCssToDocument(cssContent) {
var element = document.createElement("style");
element.innerHTML = cssContent;
var header = document.getElementsByTagName("HEAD")[0];
header.appendChild(element);
}
function randomString(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.red-bg {
background-color: red;
font-weight: bold;
}
<p class="editable" contenteditable></p>
<p class="red-bg test">
This is some text
</p>
About the drag and drop functionality, you have to use event.dataTransfer.getData() in the drop event listener, the rest is the same.
References
How to generate a DOM element from a HTML string
How to add CSS classes at runtime using Javascript
How to generate a random string (unique ID) in Javascript
Drag&drop data transfer
You could accomplish what you want, but would require an additional set of considerations.
First you add an "Add Source Assets" button, which the user would supply the source page URL... Then you would fetch the source HTML and match the pasted content, with the source content, then interrogate the source elements for all related attributes, referenced classes etc.
You could even process the source URL's markup to extract it's css and images as references... Essentially onboarding the resources based on a whitelist of acceptable media and styles.
Depending on your needs and DOM Kung Fu, you could extract all "consumed" css and import those styles... It really depends how far you want to go.

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

Trying and failing to make a linux terminal

This may be a stupid question that might be easy to find but i'm quite new to all of this and i can't seem to find what i'm looking for or atleast i don't know what i need to look for, thus I'm here.
So what I'm trying to do is create a kind of Linux terminal... This is what i got so far.
What I'm stuck on is the actual entering text part...
I've been trying to create a div with contenteditable=true as well as trying out Input elements but neither seems to be working how i want it to.
The current structure that i'm using for this is:
<div class="title" contenteditable="false" >
admin#localhost:~$
<div class="write-point" contenteditable="true" ></div>
<div class="linux-cursor" contenteditable="false"></div>
However this only deletes the whole line of text. "admin#localhost:~$" as well as the cursor.
I've also tried using JavaScript to put the cursor after the text but its not working at all.
function forStackOverFlow() {
var textInput = document.getElementsByClassName('write-point');
textInput.onkeydown = function(e) {
console.log(textInput.value);
var childTag = document.getElementsByClassName("write-point");
childTag.parentNode.insertBefore(textInput.value, childTag.nextSibling);
}};
So my main questions are:
How and what is needed to move a div(cursor element) to the end of input text(user input)
Is it possible to allow a user to type immediately once the webpage has loaded?
Thanks, any help would be great :)
You can do that in a better way with some CSS to make sure the "caret" element always comes after the contenteditable one and some JS to make sure the contenteditable element is always focused. You might try to do this last thing by adding autofocus to the contenteditable element and using a <label> for the caret element, but that doesn't work on contenteditable elements. Note no keyboard event listeners are needed:
const input = document.getElementById('input');
const caret = document.getElementById('caret');
// Move the focus back to the input if it moves away from it:
input.addEventListener('blur', (e) => {
input.focus();
});
// Set the focus to the input so that you can start typing straight away:
input.focus();
body {
background: #000;
color: #0F0;
font-family: monospace;
height: 100vh;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
margin: 0;
padding: 16px;
}
#input {
display: inline;
word-break: break-all;
outline: none;
visibility: visible;
}
#caret {
border: 0;
padding: 0;
outline: none;
background-color: #0F0;
display: inline-block;
font-family: monospace;
}
admin#localhost:~$
<div id="input" contenteditable="true"></div><button id="caret" for="input"> </button>
In a more realistic example, you might want to:
Avoid trapping the focus in the contenteditable element, as that would prevent selecting previous commands. Instead, focus to the contenteditable element only once the user presses some key.
Show a different caret depending on its position: square if it's at the end of the input, line if it's somewhere else (unless overtype mode is enabled with the Ins key).
Add a new command/entry if ↵ is pressed.
Prevent entering formatted text and automatically split it up into multiple commands/entries when needed.
const history = document.getElementById('history');
const input = document.getElementById('input');
const cursor = document.getElementById('cursor');
function focusAndMoveCursorToTheEnd(e) {
input.focus();
const range = document.createRange();
const selection = window.getSelection();
const { childNodes } = input;
const lastChildNode = childNodes && childNodes.length - 1;
range.selectNodeContents(lastChildNode === -1 ? input : childNodes[lastChildNode]);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
function handleCommand(command) {
const line = document.createElement('DIV');
line.textContent = `admin#localhost:~$ ${ command }`;
history.appendChild(line);
}
// Every time the selection changes, add or remove the .noCursor
// class to show or hide, respectively, the bug square cursor.
// Note this function could also be used to enforce showing always
// a big square cursor by always selecting 1 chracter from the current
// cursor position, unless it's already at the end, in which case the
// #cursor element should be displayed instead.
document.addEventListener('selectionchange', () => {
if (document.activeElement.id !== 'input') return;
const range = window.getSelection().getRangeAt(0);
const start = range.startOffset;
const end = range.endOffset;
const length = input.textContent.length;
if (end < length) {
input.classList.add('noCaret');
} else {
input.classList.remove('noCaret');
}
});
input.addEventListener('input', () => {
// If we paste HTML, format it as plain text and break it up
// input individual lines/commands:
if (input.childElementCount > 0) {
const lines = input.innerText.replace(/\n$/, '').split('\n');
const lastLine = lines[lines.length - 1];
for (let i = 0; i <= lines.length - 2; ++i) {
handleCommand(lines[i]);
}
input.textContent = lastLine;
focusAndMoveCursorToTheEnd();
}
// If we delete everything, display the square caret again:
if (input.innerText.length === 0) {
input.classList.remove('noCaret');
}
});
document.addEventListener('keydown', (e) => {
// If some key is pressed outside the input, focus it and move the cursor
// to the end:
if (e.target !== input) focusAndMoveCursorToTheEnd();
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleCommand(input.textContent);
input.textContent = '';
focusAndMoveCursorToTheEnd();
}
});
// Set the focus to the input so that you can start typing straigh away:
input.focus();
body {
background: #000;
color: #0F0;
font-family: monospace;
height: 100vh;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
word-break: break-all;
margin: 0;
padding: 16px;
}
#input {
display: inline;
outline: none;
visibility: visible;
}
/*
If you press the Insert key, the vertical line caret will automatically
be replaced by a one-character selection.
*/
#input::selection {
color: #000;
background: #0F0;
}
#input:empty::before {
content: ' ';
}
#keyframes blink {
to {
visibility: hidden;
}
}
#input:focus + #caret {
animation: blink 1s steps(5, start) infinite;
}
#input.noCaret + #caret {
visibility: hidden;
}
#caret {
border: 0;
padding: 0;
outline: none;
background-color: #0F0;
display: inline-block;
font-family: monospace;
}
<div id="history"></div>
admin#localhost:~$
<div id="input" contenteditable="true"></div><button id="caret" for="input"> </button>
In general, it's usually a bad idea to listen for keyboard events (keydown / keypress / keyup) to handle text input or cursors, as the value of the input can also be updated by pasting or dropping text into it and there are many edge cases, such as arrows, delete, escape, shortcuts such as select all, copy, paste... so trying to come up with an exhaustive list of all the keys we should take care of is probably not the best approach.
Moreover, that won't work on mobile, where most keys emit the same values e.key = 'Unidentified', e.which== 229 and e.keyCode = 229.
Instead, it's usually better to rely on other events such as input and use KeyboardEvents to handle very specific keys, like ↵ in this case.
If you need to check KeyboardEvent's properties values such as e.key, e.code, e.which or e.keyCode you can use https://keyjs.dev. I will add information about these kinds of cross-browser incompatibilities soon!
Disclaimer: I'm the author.
I suggest to use span instead of div because it's an inline element, more easy to manage in your case.
Next you can catch eatch keyboard's entry with a listener :
document.body.onkeydown
and tell him to append each key into a variable that you can display.
I let you think about all the functionnality you have to manage, like an enter or a backspace event.
you can play and find code you will need here : http://keycode.info/
Here is a working snippet :
var command = "";
document.body.onkeydown = function(e){
var writePoint = document.getElementById('writePoint');
command += String.fromCharCode(e.keyCode);
writePoint.innerHTML = command;
};
.linux-cursor{
width:7px;
height:15px;
background-color:green;
display:inline-block;
}
<span class="title" contenteditable="false" >
admin#localhost:~$
</span>
<span class="write-point" id="writePoint" contenteditable="true" ></span>
<span class="linux-cursor" contenteditable="false"></span>
Hope this help you, it looks like a nice project.

Replacing multiple CSS color values simultaneously

I am making a website where the user can select the colors of the page. The site is using only two colors. A background color and a color for everything else. (Text, Borders and background colors in separate boxes.)
I try to get spectrum.js to change all colors other than the background color and text over the colored boxes.
I am trying to modify the code found here by ariel.
var initialColor = "#E84E1B";
$("body").css("color", initialColor);
$("divwithfill").css("border-color", initialColor);
function updateColor(element, color) {
$(element).css("color", (color ? color.toHexString() : ""));
}
$("#colorChanger").spectrum({
color: initialColor,
move: function (color) {
updateColor(".output.render", color);
},
hide: function (color) {
updateColor(".output.render", color);
}
});
Fiddle:
http://jsfiddle.net/mjHUD/31/
This is a copy of a fiddle found of here. I am trying to change all off the #E84E1B values to the color chosen by the user.
EDIT:
To specify: I want the user to be able to use #colorChanger to change all CSS values that are # E84E1B by default. (Includes background-color and border) All CSS values with # c6c6c6 I want to be unchanged. I hope this makes more sense.
So the problem is that you can't actually select elements by their css values, but you can access the stylesheets attached to the document and do some pretty funky things with them.
A little bit of a different method here. No extra classes have to be added. No external libraries required. Basically...
Loop through every stylesheet attached to the document.
Loop through every rule in those stylesheets.
Loop through every property in those rules.
Test the value of each property against the input string.
If the value matches the input string
Set the value of the property to the new color.
When the looping is done, return the input color to save it so we can start all over again.
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
rgb = 'rgb(';
rgb += parseInt(result[1], 16) + ", ";
rgb += parseInt(result[2], 16) + ", ";
rgb += parseInt(result[3], 16) + ')';
return rgb;
}
var initialColor1 = hexToRgb("#C6C6C6");
var initialColor2 = hexToRgb("#E84E1B");
function chameleon(hex, initial) {
var rgb = hexToRgb(hex);
var stylesheets = document.styleSheets, stylesheet, i;
for (i = 0; (stylesheet = stylesheets[i]); i++) {
var rules = stylesheet.cssRules, rule, j;
if(!rules) continue;
for(j = 0; (rule = rules[j]); j++) {
var styles = rule.style, style, k;
for(k = 0; (style = styles[k]); k++) {
var value = styles.getPropertyValue(style);
if(initial == value) {
styles.setProperty(style, rgb);
}
}
}
}
return rgb;
}
document.getElementById('picker1').oninput = function(e) {
initialColor1 = chameleon(e.target.value, initialColor1);
}
document.getElementById('picker2').oninput = function(e) {
initialColor2 = chameleon(e.target.value, initialColor2);
}
body { background-color: #C6C6C6; color: #E84E1B;}#divwithfill { height: 50px; width: 50px; border: solid 1px #E84E1B; background-color: #E84E1B; color: #C6C6C6; }#divnofill { height: 50px; width: 50px; border: solid 1px #E84E1B;}
<span class="output render" id="text1">sample text</span>
<div id="divwithfill">sample text</div>
<div id="divnofill">sample text</div>
<label for="picker1">Color #1: <input type="color" id="picker1" value="#C6C6C6"></label><br>
<label for="picker2">Color #2: <input type="color" id="picker2" value="#E84E1B"></label>
I've got a solution for you that might be easier just using plain old javascript and css. demo
Some example html:
<div id="container">
<div class="box changeable">
Sample Text
</div>
<br />
<div class="box unchangeable">
Sample Text
</div>
</div>
<div>border<input id="border-colorer" type="color" /></div>
<div>text<input id="text-colorer" type="color" /></div>
<div>background<input id="background-colorer" type="color" /></div>
Set up your css like this to inherit colors from the container:
#container .changeable {
background-color:inherit;
color:inherit;
border-color:inherit;
}
.box {
border-width:1px;
border-style:solid;
}
Then set up listeners for the color-selects which will set the style on just the container.
var parent = document.getElementById('container');
var changeable = container.querySelectorAll('.changeable');
document.getElementById('border-colorer').addEventListener('change', function(e) {
[].forEach.call(changeable, function(el){
el.style['border-color'] = e.target.value;
})
});
document.getElementById('text-colorer').addEventListener('change', function(e) {
[].forEach.call(changeable, function(el){
el.style.color = e.target.value;
})
});
document.getElementById('background-colorer').addEventListener('change', function(e) {
[].forEach.call(changeable, function(el){
el.style['background-color'] = e.target.value;
})
});
Now when you change the color in a select, the style will be set on the only the changeable elements in the container.

Categories