Determing caret position in tokenized html input? - javascript

(no jquery please)
Given an html input whose value is split by /\s+/g, how would you find the current token that the caret is positioned at?
For instance, if your input value is
abc ab monkey
And you split it, it will become
["abc, "ab", "monkey"]
But if your caret position in the input is here...
abc ab monk^(caret here)ey
How would you determine which token the caret is currently in?
The api I'm looking for would be something like
var currentToken = getPosition(inputEl.value); // { index: 2, token: "monkey" }
I have most of it down, but when I start backtracking inside the input with the left arrow it gets messed up.
http://jsfiddle.net/dlizik/zmbpq5hz/
html
<input id="input" />
<pre id="test"></pre>
cursor at current token: <span id="res"></span>
js
(function($doc) {
"use strict";
var single = /\s/g;
var spacer = /\s+/g;
var disp = $doc.getElementById("test");
var input = $doc.getElementById("input");
var res = $doc.getElementById("res");
function keyListen(e) {
var tokens = this.value.split(spacer);
var tokenLengths = tokens.map(function(i) { return i.length; });
var cumulative = tokenLengths.map(function(i, n) {
return tokenLengths.slice(0, n + 1).reduce(function(a, b) {
return a + b;
});
});
var cursor = caretPos(this);
var currToken = tokens[getPos(cursor, cumulative)];
res.textContent = currToken;
disp.textContent = JSON.stringify(this.value.split(spacer), null, 2);
}
function getPos(curr, arr) {
for (var i = 0; i < arr.length; i++) {
if (curr <= arr[i]) return i;
}
}
function caretPos(el) {
var val = el.value;
var extra = val.match(single);
var whitespace = extra == null ? 0 : extra.length;
var pos = 0;
if ($doc.selection) {
el.focus();
var sel = $doc.selection.createRange();
sel.moveStart('character', -val.length);
pos = sel.text.length;
}
else if (el.selectionStart || el.selectionStart == '0') pos = el.selectionStart;
return (pos - whitespace);
}
input.addEventListener("keyup", keyListen.bind(input), false);
})(document);

Ok so you can just use regular expressions...wasn't that hard now that I think about it. You just have to find the number of words left of the caret position. That will give you the current token index as well as the string itself. However, you just have to account for a caret whose adjacent strings are both empty spaces.
http://jsfiddle.net/dlizik/zmbpq5hz/1/
This is the function you want to run
(function($doc) {
"use strict";
var single = /\s/g;
var tokenize = /[^\s+]/g;
var ledge = /[\s]*[^\s]+[\s]*/g;
var disp = $doc.getElementById("test");
var input = $doc.getElementById("input");
var res = $doc.getElementById("res");
function keyListen(e) {
var tokens = this.value.match(tokenize);
var pos = caretPos(this);
var adj = this.value.substring(pos - 1, pos + 1);
var left = this.value.slice(0, pos + 1).match(ledge).length - 1;
var curr = adj === " " ? null : tokens[left];
res.textContent = curr + "";
disp.textContent = JSON.stringify(this.value.split(spacer), null, 2);
}
function caretPos(el) {
var pos = 0;
if ($doc.selection) {
el.focus();
var sel = $doc.selection.createRange();
sel.moveStart('character', -el.value.length);
pos = sel.text.length;
}
else if (el.selectionStart || el.selectionStart == '0') pos = el.selectionStart;
return (pos);
}
input.addEventListener("keyup", keyListen.bind(input), false);
})(document);

Related

Stop the cursor moves at the end of a textarea using Angularjs

How to Stop the text cursor from jumping to end of a textarea field using $scope.$watch?
//textarea $watch
$scope.$watch('string', function(s){
var string = s.split('\n');
if(string.length > 0) {
var newElements = [];
for (var i = 0; i < string.length; i++) {
var str = string[i];
var obj = {};
var firstMatch = str.split('|')[0];
var secondMatch = str.substring(firstMatch.length + 1); //return '' if '|' was not found
obj.text = firstMatch;
obj.value = secondMatch;
newElements.push(obj);
}
$scope.elements = newElements;
}
});
//array of objects $watch
$scope.$watch('elements', function(e){
var string = '';
for(var i = 0; i < e.length; i++){
var str = e[i];
var value = str.value;
var pipe = '|';
var text = str.text;
if( value == ''){
pipe = '';
}
string += (text + pipe + value) + (i == e.length - 1 ? '' : "\n");
}
$scope.string = string;
}, true);
The text cursor (inside the textarea) is always jumping to the end after the value is cleared. For example: try to empty the first value (value1) at the textarea, the cursor does move to the end of line (after the value1 is empty, the text cursor goes to the end of line, but I want the cursor to stay to the position that it was emptied)
text1|value1 <-- after the value1 is empty, the text cursor goes to the end of line, but I want the cursor to stay here or to the position that It was emptied
text2|value2
text3|value3
text4|value4 <-- cursor goes here after emptied
I want to prevent the cursor to move to the end.
Give a try here:
https://jsfiddle.net/yegrteaf/3/

How to add wrapper on the word with space

In JS i need to add span as wrapper on the entire document text words.using below code i can able to add wrapper
function walk(root)
{
if (root.nodeType == 3) // text node
{
doReplace(root);
return;
}
var children = root.childNodes;
for (var i = 0;i<children.length ;i++)
{
walk(children[i]);
}
}
function doReplace(text)
{
var start = counter;
counter = counter+text.nodeValue.length;
var div = document.createElement("div");
var string = text.nodeValue;
var len = string.trim();
if(len.length == 0){
return;
}
var nespan = string.replace(/\b(\w+)\b/g,function myFunction(match, contents, offset, s){
var end = start + contents.length;
var id = start+"_"+end;
start = end;
return "<span class='isparent' id='"+id+"'>"+contents+"</span>";
});
div.innerHTML = nespan;
var parent = text.parentNode;
var children = div.childNodes;
for (var i = children.length - 1 ; i >= 0 ; i--)
{
parent.insertBefore(children[i], text.nextSibling);
}
parent.removeChild(text);
}
using above code ill get below result
input :
Stackoverflow is good
out put:
<span>Stackoverflow</span> <span>is</span> <span>good</span>
expecting result:
<span>Stackoverflow </span><span>is </span><span>good</span>
Add optional space character to your regex after the word but before the second word boundary: \b(\w+\s*)\b

Superscripts and subscripts in HTML input

I am making a small JavaScript web app that deals with chemical formulas, so I need to allow users to input superscripts. The easiest thing for users would be having the text editor switch to superscript when they press ^. How can I do this with an HTML textarea?
Borrowing from the function in adeneo's answer
Check this fiddle here : fiddle
Usage : To type a superscript , press ^ --> type in the superscript --> press Esc , the superscript will then appear in the textarea.
$(document).ready(function() {
var temp = {}; // store keypresses here
var current_value = "";
$("#text_area").keydown(function(e) {
temp[e.which] = true;
});
$('#text_area').keyup(function(e) {
if (e.keyCode == 27 && current_value != "") {
var length_1 = current_value.length;
var length_without_sup = length_1 - 5;
var substr_superstring = $('#text_area').val().substr(length_without_sup);
var current_text_2 = current_value + substr_superstring;
current_text_2 = current_text_2 + "</sup>";
$('#text_area').val(current_text_2);
$('#text_area').superScript();
}
var flag_shift = false;
var flag_super = false;
for (var key in temp) {
if (key == 16) {
flag_shift = true;
} else if (key == 54) {
flag_super = true;
}
}
if (flag_shift == true && flag_super == true) {
var current_text = $('#text_area').val();
current_text_2 = current_text.substr(0, current_text.length - 1);
current_text_2 = current_text_2 + "<sup>";
$('#text_area').val(current_text_2);
current_value = hide_superscript_tag();
}
delete temp[e.which];
});
});
function hide_superscript_tag() {
var current_value = $('#text_area').val();
current_value_2 = current_value.substr(0, current_value.length - 5);
$('#text_area').val(current_value_2);
return current_value;
}
$.fn.superScript = function() {
var chars = '+−=()0123456789AaÆᴂɐɑɒBbcɕDdðEeƎəɛɜɜfGgɡɣhHɦIiɪɨᵻɩjJʝɟKklLʟᶅɭMmɱNnɴɲɳŋOoɔᴖᴗɵȢPpɸqrRɹɻʁsʂʃTtƫUuᴜᴝʉɥɯɰʊvVʋʌwWxyzʐʑʒꝯᴥβγδθφχнნʕⵡ',
sup = '⁺⁻⁼⁽⁾⁰¹²³⁴⁵⁶⁷⁸⁹ᴬᵃᴭᵆᵄᵅᶛᴮᵇᶜᶝᴰᵈᶞᴱᵉᴲᵊᵋᶟᵌᶠᴳᵍᶢˠʰᴴʱᴵⁱᶦᶤᶧᶥʲᴶᶨᶡᴷᵏˡᴸᶫᶪᶩᴹᵐᶬᴺⁿᶰᶮᶯᵑᴼᵒᵓᵔᵕᶱᴽᴾᵖᶲqʳᴿʴʵʶˢᶳᶴᵀᵗᶵᵁᵘᶸᵙᶶᶣᵚᶭᶷᵛⱽᶹᶺʷᵂˣʸᶻᶼᶽᶾꝰᵜᵝᵞᵟᶿᵠᵡᵸჼˤⵯ';
return this.each(function() {
this.value = this.value.replace(/<sup[^>]*>(.*?)<\/sup>/g, function(x) {
var str = '',
txt = $.trim($(x).unwrap().text());
for (var i = 0; i < txt.length; i++) {
var n = chars.indexOf(txt[i]);
str += (n != -1 ? sup[n] : txt[i]);
}
return str;
});
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="text_area">
</textarea>
I would recommend leveraging an existing solution. Take a look at MathQuill.
Regarding creating your own such system from scratch, there is no simple solution. You would need to do independent research and experimentation and return to Stack Overflow with more specific questions.

Selected content to upperCase?

I am just wondering if there is a simple solution already to the problem of turning selected content in tinymce to upperCase letters.
Anyone got a solution?
PS: The upperCase-function is known, but won't solve the tinymce setting of selected content alone.
This is what i came up with after some fiddling
// check if a node intersects the given range
rangeIntersectsNode: function (range, node) {
var nodeRange;
if (range.intersectsNode) {
return range.intersectsNode(node);
}
else {
nodeRange = node.ownerDocument.createRange();
try {
nodeRange.selectNode(node);
} catch (e) {
nodeRange.selectNodeContents(node);
}
return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
}
},
// Tinymce-Shortcut: (cmd/ctrl + shift +a)
if ( ( (mac && evt.metaKey)|| (!mac && evt.ctrlKey)) && evt.shiftKey && evt.keyCode == 65 ){
if (!ed.selection.isCollapsed()) {
var selection = ed.getWin().getSelection(); // user selection
var range = selection.getRangeAt(0); // erste range
var start = range.startContainer;
var start_offset = range.startOffset;
var end = range.endContainer;
var end_offset = range.endOffset;
// Get all textnodes of the common ancestor
// Check foreach of those textnodes if they are inside the selection
// StartContainer and EndContainer may be partially inside the selection (if textnodes)
// concatenate those textnode parts and make toUppercase the selected part only
// all textnodes inbetween need to become upperCased (the nodeContents)
// Selection needs to be reset afterwards.
var textnodes = t.getTextNodes(range.commonAncestorContainer);
for (var i=0; i<textnodes.length; i++) {
if (t.rangeIntersectsNode(range, textnodes[i])){
if (textnodes[i] == start && textnodes[i] == end) {
var text_content = start.textContent;
text_content = start.textContent.substring(0, start_offset) + text_content.substring(start_offset, end_offset).toUpperCase() + end.textContent.substring(end_offset);
textnodes[i].nodeValue = text_content;
}
else if (textnodes[i] == start){
var text_content = start.textContent.substring(0, start_offset) + start.textContent.substring(start_offset).toUpperCase();
textnodes[i].nodeValue = text_content;
}
else if (textnodes[i] == end){
var text_content = end.textContent.substring(0, end_offset).toUpperCase() + end.textContent.substring(end_offset);
textnodes[i].nodeValue = text_content;
}
else {
// Textnodes between Start- and Endcontainer
textnodes[i].nodeValue = textnodes[i].nodeValue.toUpperCase();
}
}
}
// reset selection
var r = ed.selection.dom.createRng();
r.setStart(start, start_offset);
r.setEnd(end, end_offset);
ed.selection.setRng(r);
evt.preventDefault();
return false;
}
}

JavaScript Tag Cloud with IBM Cognos - IE is null or not an object

I followed a tutorial/modified the code to get a javascript tag cloud working in IBM Cognos (BI software). The tag cloud works fine in FireFox but in Internet Explorer I get the error:
"Message: '1' is null or not an object"
The line of code where this is present is 225 which is:
var B = b[1].toLowerCase();
I have tried many different solutions that I have seen but have been unable to get this working correctly, the rest of the code is as follows:
<script>
// JavaScript Document
// ====================================
// params that might need changin.
// DON'T forget to include a drill url in the href section below (see ###) if you want this report to be drillable
var delimit = "|";
var subdelimit = "[]"; // change this as needed (ex: Smith, Michael[]$500,000.00|)
var labelColumnNumber = 0; // first column is 0
var valueColumnNumber = 1;
var columnCount = 2; // how many columns are there in the list?
// ====================================
/*
function formatCurrency(num) {
num = num.toString().replace(/\$|\,/g,'');
if(isNaN(num))
num = "0";
sign = (num == (num = Math.abs(num)));
num = Math.floor(num*100+0.50000000001);
cents = num%100;
num = Math.floor(num/100).toString();
if(cents<10)
cents = "0" + cents;
for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
num = num.substring(0,num.length-(4*i+3))+','+ num.substring(num.length-(4*i+3));
return (((sign)?'':'-') + '$' + num + '.' + cents);
}
*/
function formatCurrency(num) {
num = num.toString().replace(/\$|\,/g,'');
if(isNaN(num))
num = "0";
for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
num = num.substring(0,num.length-(4*i+3))+','+ num.substring(num.length-(4*i+3));
return ( num );
}
function filterNum(str) {
re = /\$|,|#|#|~|`|\%|\*|\^|\&|\(|\)|\+|\=|\[|\-|\_|\]|\[|\}|\{|\;|\:|\'|\"|\<|\>|\?|\||\\|\!|\$|/g;
// remove special characters like "$" and "," etc...
return str.replace(re, "");
}
table = document.getElementById("dg");
if ( table.style.visibility != 'hidden'){ //only for visible
/*alert('Visible');*/
tags = document.getElementById("dg").getElementsByTagName("SPAN");
txt = "";
var newText = "a";
for (var i=columnCount; i<tags.length; i++) {
/*
valu = filterNum(tags[i+valueColumnNumber].innerHTML);
txt += valu;
txt += subdelimit+tags[i+labelColumnNumber].innerHTML+delimit;
i = i+columnCount;
*/
if(i%2!=0){
var newValue = filterNum(tags[i].innerHTML);
}else var newName =tags[i].innerHTML;
if((i>2) & (i%2!=0)){
newText = newText+newValue+subdelimit+newName+delimit;
if(typeof newText != 'undefined'){
txt = newText;
txt = txt.substr(9);
/* alert(txt);*/
}
}
}
}/*else alert ('Hidden');*/
function getFontSize(min,max,val) {
return Math.round((150.0*(1.0+(1.5*val-max/2)/max)));
}
function generateCloud(txt) {
//var txt = "48.1[]Google|28.1[]Yahoo!|10.5[]Live/MSN|4.9[]Ask|5[]AOL";
var logarithmic = false;
var lines = txt.split(delimit);
var min = 10000000000;
var max = 0;
for(var i=0;i<lines.length;i++) {
var line = lines[i];
var data = line.split(subdelimit);
if(data.length != 2) {
lines.splice(i,1);
continue;
}
data[0] = parseFloat(data[0]);
lines[i] = data;
if(data[0] > max)
max = data[0];
if(data[0] < min)
min = data[0];
}lines.sort(function (a,b) {
var A = a[1].toLowerCase();
var B = b[1].toLowerCase();
return A>B ? 1 : (A<B ? -1 : 0);
});
var html = "<style type='text/css'>#jscloud a:hover { text-decoration: underline; }</style> <div id='jscloud'>";
if(logarithmic) {
max = Math.log(max);
min = Math.log(min);
}
for(var i=0;i<lines.length;i++) {
var val = lines[i][0];
if(logarithmic) val = Math.log(val);
var fsize = getFontSize(min,max,val);
dollar = formatCurrency(lines[i][0]);
html += " <a href='###Some drillthrough url which includes the param "+lines[i][1]+"' style='font-size:"+fsize+"%;' title='"+dollar+"'>"+lines[i][1]+"</a> ";
}
html += "</div>";
var cloud = document.getElementById("cloud");
cloud.innerHTML = html;
var cloudhtml = document.getElementById("cloudhtml");
cloudhtml.value = html;
}
function setClass(layer,cls) {
layer.setAttribute("class",cls);
layer.setAttribute("className",cls);
}
function show(display) {
var cloud = document.getElementById("cloud");
var cloudhtml = document.getElementById("cloudhtml");if(display == "cloud") {
setClass(cloud,"visible");
setClass(cloudhtml,"hidden");
}
else if(display == "html") {
setClass(cloud,"hidden");
setClass(cloudhtml,"visible");
}
}
generateCloud(txt);
</script>
Any help or explanations is much appreciated
Sorry, I'm not seeing where a[] and b[] are defined, is this done elsewhere? Firefox and IE may be responding differently to the problem of an undefined array.

Categories