Related
I have edited this puzzle as I want but I need to show a message after completing the crossword puzzle. Is there a way to do that? Any kind comments warmly welcome.
Here's is the GitHub link- https://github.com/jweisbeck/Crossword
Here mainly activePosition and activeClueIndex are the primary vars that set the UI whenever there's an interaction. all worked as x, y coordinates. is there any way I can add that message?
Here is the checking winning function
/*
- Checks current entry input group value against answer
- If not complete, auto-selects next input for user
*/
checkAnswer: function(e) {
var valToCheck, currVal;
util.getActivePositionFromClassGroup($(e.target));
valToCheck = puzz.data[activePosition].answer.toLowerCase();
currVal = $('.position-' + activePosition + ' input')
.map(function() {
return $(this)
.val()
.toLowerCase();
})
.get()
.join('');
//console.log(currVal + " " + valToCheck);
if(valToCheck === currVal){
$('.active')
.addClass('done')
.removeClass('active');
$('.clues-active').addClass('clue-done');
solved.push(valToCheck);
solvedToggle = true;
return;
}
currOri === 'across' ? nav.nextPrevNav(e, 39) : nav.nextPrevNav(e, 40);
//z++;
//console.log(z);
//console.log('checkAnswer() solvedToggle: '+solvedToggle);
}
}; // end puzInit object
Here is the full code
var puzz = {}; // put data array in object literal to namespace it into safety
puzz.data = entryData;
// append clues markup after puzzle wrapper div
// This should be moved into a configuration object
this.after('<div id="puzzle-clues"><h2>Across</h2><ol id="across"></ol><h2>Down</h2><ol id="down"></ol></div>');
// initialize some variables
var tbl = ['<table id="puzzle">'],
puzzEl = this,
clues = $('#puzzle-clues'),
clueLiEls,
coords,
entryCount = puzz.data.length,
entries = [],
rows = [],
cols = [],
solved = [],
tabindex,
$actives,
activePosition = 0,
activeClueIndex = 0,
currOri,
targetInput,
mode = 'interacting',
solvedToggle = false,
z = 0;
var puzInit = {
init: function() {
currOri = 'across'; // app's init orientation could move to config object
// Reorder the problems array ascending by POSITION
puzz.data.sort(function(a,b) {
return a.position - b.position;
});
// Set keyup handlers for the 'entry' inputs that will be added presently
puzzEl.delegate('input', 'keyup', function(e){
mode = 'interacting';
// need to figure out orientation up front, before we attempt to highlight an entry
switch(e.which) {
case 39:
case 37:
currOri = 'across';
break;
case 38:
case 40:
currOri = 'down';
break;
default:
break;
}
if ( e.keyCode === 9) {
return false;
} else if (
e.keyCode === 37 ||
e.keyCode === 38 ||
e.keyCode === 39 ||
e.keyCode === 40 ||
e.keyCode === 8 ||
e.keyCode === 46 ) {
if (e.keyCode === 8 || e.keyCode === 46) {
currOri === 'across' ? nav.nextPrevNav(e, 37) : nav.nextPrevNav(e, 38);
} else {
nav.nextPrevNav(e);
}
e.preventDefault();
return false;
} else {
console.log('input keyup: '+solvedToggle);
puzInit.checkAnswer(e);
}
e.preventDefault();
return false;
});
// tab navigation handler setup
puzzEl.delegate('input', 'keydown', function(e) {
if ( e.keyCode === 9) {
mode = "setting ui";
if (solvedToggle) solvedToggle = false;
//puzInit.checkAnswer(e)
nav.updateByEntry(e);
} else {
return true;
}
e.preventDefault();
});
// tab navigation handler setup
puzzEl.delegate('input', 'click', function(e) {
mode = "setting ui";
if (solvedToggle) solvedToggle = false;
console.log('input click: '+solvedToggle);
nav.updateByEntry(e);
e.preventDefault();
});
// click/tab clues 'navigation' handler setup
clues.delegate('li', 'click', function(e) {
mode = 'setting ui';
if (!e.keyCode) {
nav.updateByNav(e);
}
e.preventDefault();
});
// highlight the letter in selected 'light' - better ux than making user highlight letter with second action
puzzEl.delegate('#puzzle', 'click', function(e) {
$(e.target).focus();
$(e.target).select();`+`
});
// DELETE FOR BG
puzInit.calcCoords();
// Puzzle clues added to DOM in calcCoords(), so now immediately put mouse focus on first clue
clueLiEls = $('#puzzle-clues li');
$('#' + currOri + ' li' ).eq(0).addClass('clues-active').focus();
// DELETE FOR BG
puzInit.buildTable();
puzInit.buildEntries();
},
/*
- Given beginning coordinates, calculate all coordinates for entries, puts them into entries array
- Builds clue markup and puts screen focus on the first one
*/
calcCoords: function() {
/*
Calculate all puzzle entry coordinates, put into entries array
*/
for (var i = 0, p = entryCount; i < p; ++i) {
// set up array of coordinates for each problem
entries.push(i);
entries[i] = [];
for (var x=0, j = puzz.data[i].answer.length; x < j; ++x) {
entries[i].push(x);
coords = puzz.data[i].orientation === 'across' ? "" + puzz.data[i].startx++ + "," + puzz.data[i].starty + "" : "" + puzz.data[i].startx + "," + puzz.data[i].starty++ + "" ;
entries[i][x] = coords;
}
// while we're in here, add clues to DOM!
$('#' + puzz.data[i].orientation).append('<li tabindex="1" data-position="' + i + '">' + puzz.data[i].clue + '</li>');
}
// Calculate rows/cols by finding max coords of each entry, then picking the highest
for (var i = 0, p = entryCount; i < p; ++i) {
for (var x=0; x < entries[i].length; x++) {
cols.push(entries[i][x].split(',')[0]);
rows.push(entries[i][x].split(',')[1]);
};
}
rows = Math.max.apply(Math, rows) + "";
cols = Math.max.apply(Math, cols) + "";
},
/*
Build the table markup
- adds [data-coords] to each <td> cell
*/
buildTable: function() {
for (var i=1; i <= rows; ++i) {
tbl.push("<tr>");
for (var x=1; x <= cols; ++x) {
tbl.push('<td data-coords="' + x + ',' + i + '"></td>');
};
tbl.push("</tr>");
};
tbl.push("</table>");
puzzEl.append(tbl.join(''));
},
/*
Builds entries into table
- Adds entry class(es) to <td> cells
- Adds tabindexes to <inputs>
*/
buildEntries: function() {
var puzzCells = $('#puzzle td'),
light,
$groupedLights,
hasOffset = false,
positionOffset = entryCount - puzz.data[puzz.data.length-1].position; // diff. between total ENTRIES and highest POSITIONS
for (var x=1, p = entryCount; x <= p; ++x) {
var letters = puzz.data[x-1].answer.split('');
for (var i=0; i < entries[x-1].length; ++i) {
light = $(puzzCells +'[data-coords="' + entries[x-1][i] + '"]');
// check if POSITION property of the entry on current go-round is same as previous.
// If so, it means there's an across & down entry for the position.
// Therefore you need to subtract the offset when applying the entry class.
if(x > 1 ){
if (puzz.data[x-1].position === puzz.data[x-2].position) {
hasOffset = true;
};
}
if($(light).empty()){
$(light)
.addClass('entry-' + (hasOffset ? x - positionOffset : x) + ' position-' + (x-1) )
.append('<input maxlength="1" val="" type="text" tabindex="-1" />');
}
};
};
// Put entry number in first 'light' of each entry, skipping it if already present
for (var i=1, p = entryCount; i < p; ++i) {
$groupedLights = $('.entry-' + i);
if(!$('.entry-' + i +':eq(0) span').length){
$groupedLights.eq(0)
.append('<span>' + puzz.data[i].position + '</span>');
}
}
util.highlightEntry();
util.highlightClue();
$('.active').eq(0).focus();
$('.active').eq(0).select();
},
/*
- Checks current entry input group value against answer
- If not complete, auto-selects next input for user
*/
checkAnswer: function(e) {
var valToCheck, currVal;
util.getActivePositionFromClassGroup($(e.target));
valToCheck = puzz.data[activePosition].answer.toLowerCase();
currVal = $('.position-' + activePosition + ' input')
.map(function() {
return $(this)
.val()
.toLowerCase();
})
.get()
.join('');
//console.log(currVal + " " + valToCheck);
if(valToCheck === currVal){
$('.active')
.addClass('done')
.removeClass('active');
$('.clues-active').addClass('clue-done');
solved.push(valToCheck);
solvedToggle = true;
return;
}
currOri === 'across' ? nav.nextPrevNav(e, 39) : nav.nextPrevNav(e, 40);
//z++;
//console.log(z);
//console.log('checkAnswer() solvedToggle: '+solvedToggle);
}
}; // end puzInit object
var nav = {
nextPrevNav: function(e, override) {
var len = $actives.length,
struck = override ? override : e.which,
el = $(e.target),
p = el.parent(),
ps = el.parents(),
selector;
util.getActivePositionFromClassGroup(el);
util.highlightEntry();
util.highlightClue();
$('.current').removeClass('current');
selector = '.position-' + activePosition + ' input';
//console.log('nextPrevNav activePosition & struck: '+ activePosition + ' '+struck);
// move input focus/select to 'next' input
switch(struck) {
case 39:
p
.next()
.find('input')
.addClass('current')
.select();
break;
case 37:
p
.prev()
.find('input')
.addClass('current')
.select();
break;
case 40:
ps
.next('tr')
.find(selector)
.addClass('current')
.select();
break;
case 38:
ps
.prev('tr')
.find(selector)
.addClass('current')
.select();
break;
default:
break;
}
},
updateByNav: function(e) {
var target;
$('.clues-active').removeClass('clues-active');
$('.active').removeClass('active');
$('.current').removeClass('current');
currIndex = 0;
target = e.target;
activePosition = $(e.target).data('position');
util.highlightEntry();
util.highlightClue();
$('.active').eq(0).focus();
$('.active').eq(0).select();
$('.active').eq(0).addClass('current');
// store orientation for 'smart' auto-selecting next input
currOri = $('.clues-active').parent('ol').prop('id');
activeClueIndex = $(clueLiEls).index(e.target);
//console.log('updateByNav() activeClueIndex: '+activeClueIndex);
},
// Sets activePosition var and adds active class to current entry
updateByEntry: function(e, next) {
var classes, next, clue, e1Ori, e2Ori, e1Cell, e2Cell;
if(e.keyCode === 9 || next){
// handle tabbing through problems, which keys off clues and requires different handling
activeClueIndex = activeClueIndex === clueLiEls.length-1 ? 0 : ++activeClueIndex;
$('.clues-active').removeClass('.clues-active');
next = $(clueLiEls[activeClueIndex]);
currOri = next.parent().prop('id');
activePosition = $(next).data('position');
// skips over already-solved problems
util.getSkips(activeClueIndex);
activePosition = $(clueLiEls[activeClueIndex]).data('position');
} else {
activeClueIndex = activeClueIndex === clueLiEls.length-1 ? 0 : ++activeClueIndex;
util.getActivePositionFromClassGroup(e.target);
clue = $(clueLiEls + '[data-position=' + activePosition + ']');
activeClueIndex = $(clueLiEls).index(clue);
currOri = clue.parent().prop('id');
}
util.highlightEntry();
util.highlightClue();
//$actives.eq(0).addClass('current');
//console.log('nav.updateByEntry() reports activePosition as: '+activePosition);
}
}; // end nav object
var util = {
highlightEntry: function() {
// this routine needs to be smarter because it doesn't need to fire every time, only
// when activePosition changes
$actives = $('.active');
$actives.removeClass('active');
$actives = $('.position-' + activePosition + ' input').addClass('active');
$actives.eq(0).focus();
$actives.eq(0).select();
},
highlightClue: function() {
var clue;
$('.clues-active').removeClass('clues-active');
$(clueLiEls + '[data-position=' + activePosition + ']').addClass('clues-active');
if (mode === 'interacting') {
clue = $(clueLiEls + '[data-position=' + activePosition + ']');
activeClueIndex = $(clueLiEls).index(clue);
};
},
getClasses: function(light, type) {
if (!light.length) return false;
var classes = $(light).prop('class').split(' '),
classLen = classes.length,
positions = [];
// pluck out just the position classes
for(var i=0; i < classLen; ++i){
if (!classes[i].indexOf(type) ) {
positions.push(classes[i]);
}
}
return positions;
},
getActivePositionFromClassGroup: function(el){
classes = util.getClasses($(el).parent(), 'position');
if(classes.length > 1){
// get orientation for each reported position
e1Ori = $(clueLiEls + '[data-position=' + classes[0].split('-')[1] + ']').parent().prop('id');
e2Ori = $(clueLiEls + '[data-position=' + classes[1].split('-')[1] + ']').parent().prop('id');
// test if clicked input is first in series. If so, and it intersects with
// entry of opposite orientation, switch to select this one instead
e1Cell = $('.position-' + classes[0].split('-')[1] + ' input').index(el);
e2Cell = $('.position-' + classes[1].split('-')[1] + ' input').index(el);
if(mode === "setting ui"){
currOri = e1Cell === 0 ? e1Ori : e2Ori; // change orientation if cell clicked was first in a entry of opposite direction
}
if(e1Ori === currOri){
activePosition = classes[0].split('-')[1];
} else if(e2Ori === currOri){
activePosition = classes[1].split('-')[1];
}
} else {
activePosition = classes[0].split('-')[1];
}
console.log('getActivePositionFromClassGroup activePosition: '+activePosition);
},
checkSolved: function(valToCheck) {
for (var i=0, s=solved.length; i < s; i++) {
if(valToCheck === solved[i]){
return true;
}
}
},
getSkips: function(position) {
if ($(clueLiEls[position]).hasClass('clue-done')){
activeClueIndex = position === clueLiEls.length-1 ? 0 : ++activeClueIndex;
util.getSkips(activeClueIndex);
} else {
return false;
}
}
}; // end util object
puzInit.init();
}
This is fun to solve. Did you mean show message when all question has answered? If yes, You can simply create a variable that saving every word that has answered. Then, inside function checkAnswer(), check if total word answered same as data entry.
I made that custom code here https://codeshare.io/G81rmE
I have written a code bellow for counting the character inside text box.
the code is working just fine the only problem with it is when i past a text into the text box i have to press any key so system start to count.
Could you please help me sort this problem
function GetAlhpa(text) {
var gsm = "#£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞ^{}\[~]|€ÆæßÉ!\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà";
var i = 0;
while (i <= String(text).length) {
if (gsm.indexOf(String(String(text).charAt(i))) == -1 && (String(text).charCodeAt(i) != 32) && (String(text).charCodeAt(i) != 27) && (String(text).charCodeAt(i) != 10) && (String(text).charCodeAt(i) != 13)) {
UniCodestring = " Unicode ";
Countsms = 70;
if ($('#SndSms_Message').val().length > 70)
Countsms = 67;
return;
}
i++;
}
Countsms = 160;
UniCodestring = "";
if ($('#SndSms_Message').val().length > 160)
Countsms = 153;
}
var Countsms = 160;
var UniCodestring = "";
var CounterSmsLen = 0;
var Two = "|^€{}[]~";
function GetCountSms() {
document.getElementById('SndSms_Message').addEventListener('input', function (e) {
var target = e.SndSms_Message,
position = SndSms_Message.selectionStart;
ConvertGreek();
CounterSmsLen = $('#SndSms_Message').val().length;
GetAlhpa($('#SndSms_Message').val());
var i = 0;
while (i < String(Two).length) {
var oldindex = -1;
while (String($('#SndSms_Message').val()).indexOf(String(String(Two).charAt(i)), oldindex) > -1) {
//if ( String($('#SndSms_Message').val()).indexOf(String(String(Two).charAt(i))) > -1){
CounterSmsLen += 1;
oldindex = String($('#SndSms_Message').val()).indexOf(String(String(Two).charAt(i)), oldindex) + 1;
console.log(i);
}
i++;
}
SndSms_Message.selectionEnd = position; // Set the cursor back to the initial position.
});
if ($('#SndSms_Message').val().length == 0)
CounterSmsLen = 0;
$('#SndSms_Count').html(' ' + CounterSmsLen + ' Characters' + UniCodestring + ' <br /> ' + Math.ceil(CounterSmsLen / Countsms) + ' Sms');
countsmsnumber=Math.ceil(CounterSmsLen / Countsms);
}
var greekchar = "ΑΒΕΖΗΙΚΜΝΟΡΤΥΧ";
var englishchar = "ABEZHIKMNOPTYX";
function ConvertGreek() {
var str = $('#SndSms_Message').val();
var i = 0;
while (i < String(greekchar).length) {
str = str.replace(new RegExp(String(greekchar).charAt(i), 'g'), String(englishchar).charAt(i));
i++;
}
$('#SndSms_Message').val(str);
P.S.
If i paste the number into the text box it will count it correct but if i paste character it wont count them..
You need keyup change event in order to handle paste event.
document.getElementById('SndSms_Message').addEventListener("keyup", function() {
//your code here
});
example
When I click a button I want to insert some text into the currently selected textarea at the current caret position. How do I do this with jQuery or just Javascript?
I've come a across code that inserts text into a specific text area but for my project there are multiple textareas.
This does all what you want (except old browser support).
var myText = "sup?",
lastActiveElement = null;
window.addEventListener("focusin", function(e) {
lastActiveElement = e.target;
});
document.getElementById("go").addEventListener("click", function(e) {
if(!lastActiveElement || lastActiveElement.tagName.toUpperCase() !== "TEXTAREA") {
alert("no textarea selected");
return;
}
var selectionStart = lastActiveElement.selectionStart,
value = lastActiveElement.value;
lastActiveElement.value = value.substr(0, selectionStart) + myText + value.substr(selectionStart);
});
Demo: http://jsfiddle.net/rfYZq/2/
The focusin event stores the last focused element into a var.
(function ($, undefined) {
$.fn.getCursorPosition = function() {
var el = $(this).get(0);
var pos = 0;
if('selectionStart' in el) {
pos = el.selectionStart;
} else if('selection' in document) {
el.focus();
var Sel = document.selection.createRange();
var SelLength = document.selection.createRange().text.length;
Sel.moveStart('character', -el.value.length);
pos = Sel.text.length - SelLength;
}
return pos;
}
})(jQuery);
Basically, to use it on a text box, do the following:
$("#myTextBoxSelector").getCursorPosition();
I got this script from somewhere else but I can't find the bookmark for it.
This adds a method to jQuery that will allow you to do the following:
$("#selectedTextarea").insertAtCaret(myText);
Edit: a working fiddle here
And the code:
jQuery.fn.extend({
insertAtCaret: function(myValue)
{
return this.each(function()
{
if(document.selection)
{
//For browsers like Internet Explorer
this.focus();
sel = document.selection.createRange();
sel.text = myValue;
this.focus();
}
else if(this.selectionStart || this.selectionStart == '0')
{
//For browsers like Firefox and Webkit based
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
var scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos, this.value.length);
this.focus();
this.selectionStart = startPos + myValue.length;
this.selectionEnd = startPos + myValue.length;
this.scrollTop = scrollTop;
}
else
{
this.value += myValue;
this.focus();
}
})
}
});
I would like to create a simple function that adds text into a text area at the user's cursor position. It needs to be a clean function. Just the basics. I can figure out the rest.
Use selectionStart/selectionEnd properties of the input element (works for <textarea> as well)
function insertAtCursor(myField, myValue) {
//IE support
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
}
//MOZILLA and others
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)
+ myValue
+ myField.value.substring(endPos, myField.value.length);
} else {
myField.value += myValue;
}
}
This snippet could help you with it in a few lines of jQuery 1.9+: http://jsfiddle.net/4MBUG/2/
$('input[type=button]').on('click', function() {
var cursorPos = $('#text').prop('selectionStart');
var v = $('#text').val();
var textBefore = v.substring(0, cursorPos);
var textAfter = v.substring(cursorPos, v.length);
$('#text').val(textBefore + $(this).val() + textAfter);
});
New answer:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setRangeText
I'm not sure about the browser support for this though.
Tested in Chrome 81.
function typeInTextarea(newText, el = document.activeElement) {
const [start, end] = [el.selectionStart, el.selectionEnd];
el.setRangeText(newText, start, end, 'select');
}
document.getElementById("input").onkeydown = e => {
if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>
<div>It'll replace a selection with the given text.</div>
Old answer:
A pure JS modification of Erik Pukinskis' answer:
function typeInTextarea(newText, el = document.activeElement) {
const start = el.selectionStart
const end = el.selectionEnd
const text = el.value
const before = text.substring(0, start)
const after = text.substring(end, text.length)
el.value = (before + newText + after)
el.selectionStart = el.selectionEnd = start + newText.length
el.focus()
}
document.getElementById("input").onkeydown = e => {
if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>
Tested in Chrome 47, 81, and Firefox 76.
If you want to change the value of the currently selected text while you're typing in the same field (for an autocomplete or similar effect), pass document.activeElement as the first parameter.
It's not the most elegant way to do this, but it's pretty simple.
Example usages:
typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
For the sake of proper Javascript
HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
text = text || '';
if (document.selection) {
// IE
this.focus();
var sel = document.selection.createRange();
sel.text = text;
} else if (this.selectionStart || this.selectionStart === 0) {
// Others
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
this.value = this.value.substring(0, startPos) +
text +
this.value.substring(endPos, this.value.length);
this.selectionStart = startPos + text.length;
this.selectionEnd = startPos + text.length;
} else {
this.value += text;
}
};
A simple solution that works on firefox, chrome, opera, safari and edge but probably won't work on old IE browsers.
var target = document.getElementById("mytextarea_id")
if (target.setRangeText) {
//if setRangeText function is supported by current browser
target.setRangeText(data)
} else {
target.focus()
document.execCommand('insertText', false /*no UI*/, data);
}
setRangeText function allow you to replace current selection with the provided text or if no selection then insert the text at cursor position. It's only supported by firefox as far as I know.
For other browsers there is "insertText" command which only affect the html element currently focused and has same behavior as setRangeText
Inspired partially by this article
I like simple javascript, and I usually have jQuery around. Here's what I came up with, based off mparkuk's:
function typeInTextarea(el, newText) {
var start = el.prop("selectionStart")
var end = el.prop("selectionEnd")
var text = el.val()
var before = text.substring(0, start)
var after = text.substring(end, text.length)
el.val(before + newText + after)
el[0].selectionStart = el[0].selectionEnd = start + newText.length
el.focus()
}
$("button").on("click", function() {
typeInTextarea($("textarea"), "some text")
return false
})
Here's a demo: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101
Rab's answer works great, but not for Microsoft Edge, so I added a small adaptation for Edge as well:
https://jsfiddle.net/et9borp4/
function insertAtCursor(myField, myValue) {
//IE support
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
}
// Microsoft Edge
else if(window.navigator.userAgent.indexOf("Edge") > -1) {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)+ myValue
+ myField.value.substring(endPos, myField.value.length);
var pos = startPos + myValue.length;
myField.focus();
myField.setSelectionRange(pos, pos);
}
//MOZILLA and others
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)
+ myValue
+ myField.value.substring(endPos, myField.value.length);
} else {
myField.value += myValue;
}
}
function insertAtCaret(text) {
const textarea = document.querySelector('textarea')
textarea.setRangeText(
text,
textarea.selectionStart,
textarea.selectionEnd,
'end'
)
}
setInterval(() => insertAtCaret('Hello'), 3000)
<textarea cols="60">Stack Overflow Stack Exchange Starbucks Coffee</textarea>
If the user does not touch the input after text is inserted, the 'input' event is never triggered, and the value attribute will not reflect the change. Therefore it is important to trigger the input event after programmatically inserting text. Focusing the field is not enough.
The following is a copy of Snorvarg's answer with an input trigger at the end:
function insertAtCursor(myField, myValue) {
//IE support
if (document.selection) {
myField.focus();
sel = document.selection.createRange();
sel.text = myValue;
}
// Microsoft Edge
else if(window.navigator.userAgent.indexOf("Edge") > -1) {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)+ myValue
+ myField.value.substring(endPos, myField.value.length);
var pos = startPos + myValue.length;
myField.focus();
myField.setSelectionRange(pos, pos);
}
//MOZILLA and others
else if (myField.selectionStart || myField.selectionStart == '0') {
var startPos = myField.selectionStart;
var endPos = myField.selectionEnd;
myField.value = myField.value.substring(0, startPos)
+ myValue
+ myField.value.substring(endPos, myField.value.length);
} else {
myField.value += myValue;
}
triggerEvent(myField,'input');
}
function triggerEvent(el, type){
if ('createEvent' in document) {
// modern browsers, IE9+
var e = document.createEvent('HTMLEvents');
e.initEvent(type, false, true);
el.dispatchEvent(e);
} else {
// IE 8
var e = document.createEventObject();
e.eventType = type;
el.fireEvent('on'+e.eventType, e);
}
}
Credit to plainjs.com for the triggerEvent function
More about the oninput event at w3schools.com
I discovered this while creating an emoji-picker for a chat. If the user just select a few emojis and hit the "send" button, the input field is never touched by the user. When checking the value attribute it was always empty, even though the inserted emoji unicodes was visible in the input field. Turns out that if the user does not touch the field the 'input' event never fired and the solution was to trigger it like this. It took quite a while to figure this one out... hope it will save someone some time.
The code below is a TypeScript adaptation of the package https://github.com/grassator/insert-text-at-cursor by Dmitriy Kubyshkin.
/**
* Inserts the given text at the cursor. If the element contains a selection, the selection
* will be replaced by the text.
*/
export function insertText(input: HTMLTextAreaElement | HTMLInputElement, text: string) {
// Most of the used APIs only work with the field selected
input.focus();
// IE 8-10
if ((document as any).selection) {
const ieRange = (document as any).selection.createRange();
ieRange.text = text;
// Move cursor after the inserted text
ieRange.collapse(false /* to the end */);
ieRange.select();
return;
}
// Webkit + Edge
const isSuccess = document.execCommand("insertText", false, text);
if (!isSuccess) {
const start = input.selectionStart;
const end = input.selectionEnd;
// Firefox (non-standard method)
if (typeof (input as any).setRangeText === "function") {
(input as any).setRangeText(text);
} else {
if (canManipulateViaTextNodes(input)) {
const textNode = document.createTextNode(text);
let node = input.firstChild;
// If textarea is empty, just insert the text
if (!node) {
input.appendChild(textNode);
} else {
// Otherwise we need to find a nodes for start and end
let offset = 0;
let startNode = null;
let endNode = null;
// To make a change we just need a Range, not a Selection
const range = document.createRange();
while (node && (startNode === null || endNode === null)) {
const nodeLength = node.nodeValue.length;
// if start of the selection falls into current node
if (start >= offset && start <= offset + nodeLength) {
range.setStart((startNode = node), start - offset);
}
// if end of the selection falls into current node
if (end >= offset && end <= offset + nodeLength) {
range.setEnd((endNode = node), end - offset);
}
offset += nodeLength;
node = node.nextSibling;
}
// If there is some text selected, remove it as we should replace it
if (start !== end) {
range.deleteContents();
}
// Finally insert a new node. The browser will automatically
// split start and end nodes into two if necessary
range.insertNode(textNode);
}
} else {
// For the text input the only way is to replace the whole value :(
const value = input.value;
input.value = value.slice(0, start) + text + value.slice(end);
}
}
// Correct the cursor position to be at the end of the insertion
input.setSelectionRange(start + text.length, start + text.length);
// Notify any possible listeners of the change
const e = document.createEvent("UIEvent");
e.initEvent("input", true, false);
input.dispatchEvent(e);
}
}
function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement) {
if (input.nodeName !== "TEXTAREA") {
return false;
}
let browserSupportsTextareaTextNodes;
if (typeof browserSupportsTextareaTextNodes === "undefined") {
const textarea = document.createElement("textarea");
textarea.value = "1";
browserSupportsTextareaTextNodes = !!textarea.firstChild;
}
return browserSupportsTextareaTextNodes;
}
Posting modified function for own reference. This example inserts a selected item from <select> object and puts the caret between the tags:
//Inserts a choicebox selected element into target by id
function insertTag(choicebox,id) {
var ta=document.getElementById(id)
ta.focus()
var ss=ta.selectionStart
var se=ta.selectionEnd
ta.value=ta.value.substring(0,ss)+'<'+choicebox.value+'>'+'</'+choicebox.value+'>'+ta.value.substring(se,ta.value.length)
ta.setSelectionRange(ss+choicebox.value.length+2,ss+choicebox.value.length+2)
}
/**
* Usage "foo baz".insertInside(4, 0, "bar ") ==> "foo bar baz"
*/
String.prototype.insertInside = function(start, delCount, newSubStr) {
return this.slice(0, start) + newSubStr + this.slice(start + Math.abs(delCount));
};
$('textarea').bind("keydown keypress", function (event) {
var val = $(this).val();
var indexOf = $(this).prop('selectionStart');
if(event.which === 13) {
val = val.insertInside(indexOf, 0, "<br>\n");
$(this).val(val);
$(this).focus();
}
});
Extending on Adriano's answer, we may also take cursor end into consideration which will make the "replace text" work
$('input[type=button]').on('click', function() {
var cursorStart = $('#text').prop('selectionStart');
var cursorEnd = $('#text').prop('selectionEnd');
var v = $('#text').val();
var textBefore = v.substring(0,cursorStart);
var textAfter = v.substring(cursorEnd);
$('#text').val(textBefore + $(this).val() + textAfter);
});
Changed it to getElementById(myField):
function insertAtCursor(myField, myValue) {
// IE support
if (document.selection) {
document.getElementById(myField).focus();
sel = document.selection.createRange();
sel.text = myValue;
}
// MOZILLA and others
else if (document.getElementById(myField).selectionStart || document.getElementById(myField).selectionStart == '0') {
var startPos = document.getElementById(myField).selectionStart;
var endPos = document.getElementById(myField).selectionEnd;
document.getElementById(myField).value =
document.getElementById(myField).value.substring(0, startPos)
+ myValue
+ document.getElementById(myField).value.substring(endPos, document.getElementById(myField).value.length);
} else {
document.getElementById(myField).value += myValue;
}
}
Below is the function which dont work... when try to add "newTextArea" to "document.postform.nTextArea"
Can anyone help me?
function AddText(text, newTextArea) {
nTextArea = newTextArea;
var tarea = document.postform.nTextArea;
alert(nTextArea);
if (typeof tarea.selectionStart != 'undefined'){ // if it supports DOM2
start = tarea.selectionStart;
end = tarea.selectionEnd;
tarea.value = tarea.value.substr(0,tarea.selectionStart)
+ text + tarea.value.substr(tarea.selectionEnd);
tarea.focus();
tarea.selectionStart = ((start - end) == 0) ? start + text.length : start;
tarea.selectionEnd = start + text.length;
} else {
if (tarea.createTextRange && tarea.caretPos) {
var caretPos = tarea.caretPos;
caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
}
else {
tarea.value += text;
}
tarea.focus(caretPos);
}
}
Assuming newTextArea is name of textarea element, the correct way to access it is with such code:
var tarea = document.postform.elements[newTextArea];
if (typeof tarea.selectionStart != 'undefined'){ // if it supports DOM2
//...
}