I have an app which is mostly about studying a new language. In my application there is an input field which expects text in a language that user study.
So if user focuses on that input, ideally, I want automatically to switch a layout to a target language. To make user experience more enjoyable (you don't need to worry about a wrong layout).
What I know and have tried
As I know browsers not able to provide API which I can use to determine a current layout.
I have tried to detect if the last entered character is not a typical character for the target language then I use my mapping between key codes and target language letters and replace that entered character in particular position in input. After that I get caret reset. So I return it to previous position.
The problem with the second approach that if you type fast enough you can encounter problems with a caret position. And it leads to a wrong string in the input.
Do you have any ideas how to achieve that behavior which works even when you type text with a speed when you almost immediately press two keys at the same time?
P.S. Code for that described approach
const codeToEn = {
65: 'a',
// ... and so on
}
const acceptableChars = /^[a-zA-Z0-9 .+_)(%#!?,&$*'"`~]+$/g;
document.getElementById('some-input-id').addEventListener('keyup', function (e) {
if (codeToEn[e.which]
&& !acceptableChars.test(this.value)) {
const char = codeToEn[e.which];
const {selectionStart, selectionEnd} = this;
const currentVal = this.value;
let leftPart = currentVal.substring(0, selectionStart - 1);
let rightPart = currentVal.substring(selectionStart );
this.value = leftPart + char + rightPart;
this.setSelectionRange(selectionStart, selectionEnd);
}
});
Convert Layout will help you:
https://github.com/ai/convert-layout
It really small and supports many languages.
Before you look into the code below, please note that my solution assumes that all the users use QWERTY / ЙЦУКЕН keyboard layout. This may be a huge simplification and you'll have to find a more complex approach to the keyboard layout detection (generally it's all about finding a correct symbols mapping).
The more useful part here is a fast substitution of symbols. So type fast or even copy-paste text. Hope it helps!
const En = "qwertyuiop[]asdfghjkl;'zxcvbnm,.",
Ru = "йцукенгшщзхъфывапролджэячсмитьбю";
const RuEn = [...Ru].reduce((a, e, i) => (a[e] = En[i]) && (a[e.toUpperCase()] = En[i].toUpperCase()) && a, {});
let corrected = 0;
document.getElementById('ta').addEventListener('input', function() {
let end = this.selectionEnd;
for (let i = !!corrected * (this.value.length - corrected - 1); i < end; i++) {
let s = RuEn[this.value[i]];
if (s) this.value = this.value.split(this.value[i]).join(s);
}
this.selectionEnd = end;
corrected = this.value.length - 1;
});
<textarea id="ta" cols="50" rows="10"></textarea>
Related
I am posting this question with my answer so far but would like to invite other solutions as I am not 100% sure about mine.
It will:
Automatically place the dashes in the right place so the user only has to enter the digits.
Can be any size. You can set a maxlength attribute on your input and it will continue to apply dashes intil it runs out of space. It will default to 8 characters max
Allowsuser to delete digits without the need to delete the dashes too.
Why am I posting a this?
I could not find the answer myself on StackOverflow and when you search this question on Google, it keeps returning a PHP answer for StackOverflow instead! There are even answers in there for Javascript.
Hopefully this question can produce other solutions too!
How does it work?
This is designed to work with a real-time input.
It works out the maximum length
It captures the event and works out if the delete key was pressed
The 'regex' part is saying to replace ever 2nd character with itself
plus a dash.
The next line first replaces anything that's not a number, then uses the regex to inject dashes and finally the string is sliced to remove any trailing slash
You would apply this function to your onkeyup or onpaste events, passing 'this' in.
function checkSortCode(el,ev){
var len = el.maxLength || 8;
ev = ev || window.event;
if(ev.keyCode == 8 && el.value.slice(-1) == "-"){
el.value = el.value.slice(0,-1);
} else {
var regex = new RegExp("(\\S{" + (2 - 1) + "}\\S)", "g");
el.value = el.value.replace(/[^0-9]/g,"").replace(regex,("$1"+"-")).slice(0,len);
}
}
.sortcode::placeholder{color:#eeeeee;}
body{font-family:arial,sans-serif;font-size:1.4em;}
input{font-size:1.4em;}
<label>Sort Code</label><br>
<input type="text" name="sortcode" onkeyup="checkSortCode(this,event)" onpaste="checkSortCode(this,event)" class="sortcode" size="8" maxlength="8" placeholder="00-00-00" />
Ideally, I wanted it to show the 00-00-00 format all the time and then the user would fill it in but have padded zeros where they hadn't. That's not easy as the cursor wants to go to the end of the input.
What you're looking for is called Input Masking. You can implement it yourself but I would recommend using a library to separate the actual input value and the mask.
Here an implementation using native js, you'll notice it's a bit janky.
<html>
<body>
<input id="input">
<script>
const pattern = '00-00-00-00'
const patternRegex = /^[0-9]{2}\-[0-9]{2}\-[0-9]{2}\-[0-9]{2}$/
const separator = '-'
/* returns current value completed by the pattern (filled with 0) */
const fill = value => {
return `${value}${pattern.substring(value.length)}`
}
/* format the input on keyup */
const format = event => {
/* only format the input at cursor position (to ignore filled pattern) */
const position = event.target.selectionStart
const value = event.target.value.substring(0, position)
/* rollback invalid inputs */
if (!patternRegex.test(fill(value))) {
event.target.value = event.target.value.substring(0, position - 1)
return
}
/* change target valuer to include pattern and restore carret position */
event.target.value = fill(value)
const newPosition = event.target.value[position] === separator ? position + 1 : position
event.target.setSelectionRange(newPosition, newPosition)
}
const input = document.getElementById('input')
input.addEventListener('keyup', format)
</script>
</body>
</html>
You can check some other implementation here : https://css-tricks.com/input-masking/
The reason why it's janky is because we format the input after a change occured. When using a library (or React), you can control the input value before it's rendered.
I bought a Chromebook. I really like it, but for one thing: since I am in Canada, it comes with this horrible bilingual keyboard that has half length Shift and Enter keys, with the other half as extra backslash keys.
I'm awful with Javascript, but by following along from the code here, I've managed to remap the right backslash to an enter key with the following code:
var context_id = -1;
chrome.input.ime.onFocus.addListener(function(context) {
context_id = context.contextID;
});
chrome.input.ime.onKeyEvent.addListener(
function(engineID, keyData) {
var handled = false;
if (keyData.code == "Backslash") {
keyData.key = "Enter";
keyData.code = "Enter";
chrome.input.ime.sendKeyEvents({"contextID": context_id, "keyData": [keyData]});
handled = true;
}
return handled;
})
I figured the same trick would work for the Shift key:
if (keyData.code == "IntlBackslash") {
keyData.key = "ShiftLeft";
keyData.code = "ShiftLeft";
chrome.input.ime.sendKeyEvents({"contextID": context_id, "keyData": [keyData]});
handled = true;
}
Unfortunately, this does not work as I expected. The IntlBackslash key no longer makes a backslash, which is a step in the right direction, but it also does not seem to behave as a shift key.
Please help! I don't want to have to return this thing but the tiny shift key makes it unusable!
If I do:
var number = 3500;
alert(number.toLocaleString("hi-IN"));
I will get ३,५०० in Hindi.
But how can I convert it back to 3500.
I want something like:
var str='३,५००';
alert(str.toLocaleNumber("en-US"));
So, that it can give 3500.
Is it possible by javascript or jquery?
I think you are looking for something like:
https://github.com/jquery/globalize
Above link will take you to git project page. This is a js library contributed by Microsoft.
You should give it one try and try to use formt method of that plugin. If you want to study this plugin, here is the link for the same:
http://weblogs.asp.net/scottgu/jquery-globalization-plugin-from-microsoft
I hope this is what you are looking for and will resolve your problem soon. If it doesn't work, let me know.
Recently I've been struggling with the same problem of converting stringified number formatted in any locale back to the number.
I've got inspired by the solution implemented in NG Prime InputNumber component. They use Intl.NumberFormat.prototype.format() (which I recommend) to format the value to locale string, and then create set of RegExp expressions based on simple samples so they can cut off particular expressions from formatted string.
This solution can be simplified with using Intl.Numberformat.prototype.formatToParts(). This method returns information about grouping/decimal/currency and all the other separators used to format your value in particular locale, so you can easily clear them out of previously formatted string. It seems to be the easiest solution, that will cover all cases, but you must know in what locale the value has been previously formatted.
Why Ng Prime didn't go this way? I think its because Intl.Numberformat.prototype.formatToParts() does not support IE11, or perhaps there is something else I didn't notice.
A complete code example using this solution can be found here.
Unfortunately you will have to tackle the localisation manually. Inspired by this answer , I created a function that will manually replace the Hindi numbers:
function parseHindi(str) {
return Number(str.replace(/[०१२३४५६७८९]/g, function (d) {
return d.charCodeAt(0) - 2406;
}).replace(/[०१२३४५६७८९]/g, function (d) {
return d.charCodeAt(0) - 2415;
}));
}
alert(parseHindi("३५००"));
Fiddle here: http://jsfiddle.net/yyxgxav4/
You can try this out
function ConvertDigits(input, source, target) {
var systems = {
arabic: 48, english: 48, tamil: 3046, kannada: 3302, telugu: 3174, hindi: 2406,
malayalam: 3430, oriya: 2918, gurmukhi: 2662, nagari: 2534, gujarati: 2790,
},
output = [], offset = 0, zero = 0, nine = 0, char = 0;
source = source.toLowerCase();
target = target.toLowerCase();
if (!(source in systems && target in systems) || input == null || typeof input == "undefined" || typeof input == "object") {
return input;
}
input = input.toString();
offset = systems[target] - systems[source];
zero = systems[source];
nine = systems[source] + 9;
for (var i = 0 ; i < input.length; i++) {
var char = input.charCodeAt(i);
if (char >= zero && char <= nine) {
output.push(String.fromCharCode(char + offset));
} else {
output.push(input[i]);
}
}
return output.join("");
}
var res = ConvertDigits('१२३४५६७८९', 'hindi', 'english');
I got it from here
If you need a jquery thing then please try this link
Use the Globalize library.
Install it
npm install globalize cldr-data --save
then
var cldr = require("cldr-data");
var Globalize = require("globalize");
Globalize.load(cldr("supplemental/likelySubtags"));
Globalize.load(cldr("supplemental/numberingSystems"));
Globalize.load(cldr("supplemental/currencyData"));
//replace 'hi' with appropriate language tag
Globalize.load(cldr("main/hi/numbers"));
Globalize.load(cldr("main/hi/currencies"));
//You may replace the above locale-specific loads with the following line,
// which will load every type of CLDR language data for every available locale
// and may consume several hundred megs of memory!
//Use with caution.
//Globalize.load(cldr.all());
//Set the locale
//We use the extention u-nu-native to indicate that Devanagari and
// not Latin numerals should be used.
// '-u' means extension
// '-nu' means number
// '-native' means use native script
//Without -u-nu-native this example will not work
//See
// https://en.wikipedia.org/wiki/IETF_language_tag#Extension_U_.28Unicode_Locale.29
// for more details on the U language code extension
var hindiGlobalizer = Globalize('hi-IN-u-nu-native');
var parseHindiNumber = hindiGlobalizer.numberParser();
var formatHindiNumber = hindiGlobalizer.numberFormatter();
var formatRupeeCurrency = hindiGlobalizer.currencyFormatter("INR");
console.log(parseHindiNumber('३,५००')); //3500
console.log(formatHindiNumber(3500)); //३,५००
console.log(formatRupeeCurrency(3500)); //₹३,५००.००
https://github.com/codebling/globalize-example
A common scenario for this problem is to display a float number to the user and then want it back as a numerical value.
In that case, javascript has the number in the first place and looses it when formatting it for display. A simple workaround for the parsing is to store the real float value along with the formatted value:
var number = 3500;
div.innerHTML = number.toLocaleString("hi-IN");
div.dataset.value = number;
Then get it back by parsing the data attribute:
var number = parseFloat(div.dataset.value);
This is a Columbus's egg style answer. It works provided the problem is an egg.
var number = 3500;
var toLocaleString = number.toLocaleString("hi-IN")
var formatted = toLocaleString.replace(',','')
var converted = parseInt(formatted)
I've written an extension for firefox which highlights all words on a web page (excluding some words in a given list).
What i've noticed is that (besides that my extension is terribly slow) some web pages get "destroyed", more specifically the layout gets destroyed (particularly websites with overlay advertising or fancy drop-down menus).
My code wraps <span> tags around every "word", or to be precise around every token, because i'm splitting the text nodes with a whitespace as seperator.
So is it possible anyway to realize this task without destroying the page's layout?
I'm iterating over all text nodes, split them, and iterate over every token.
When the token is in my list, i don't highlight it, else i wrap the <span> tag around it.
So any suggestions how this could be done faster would be helpful, too.
Here are some screenshots for a correctly highlighted and a not correctly highlighted web page:
right:
en.wikipedia.org before highlighting,
en.wikipedia.org after highlighting.
wrong:
developer.mozilla.org before highlighting,
developer.mozilla.org after highlighting.
OK. Study this code. It searches for all instances of "is" and highlights if it is not surrounded by word characters. Put this in your scratchpad while this tab is focused. You will see that words like "List" and other words containing "Is" are no highlighted, but all the "Is"'s are.
I basically made an addon here for you. You can now release this as an addon called RegEx FindBar and take all the credit....
var doc = gBrowser.contentDocument;
var ctrler = _getSelectionController(doc.defaultView);
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
let startPt = searchRange.cloneRange();
startPt.collapse(true);
let endPt = searchRange.cloneRange();
endPt.collapse(false);
let retRane = null;
let finder = Cc["#mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Ci.nsIFind);
finder.caseSensitive = false;
var i = 0;
while (retRange = finder.Find('is', searchRange, startPt, endPt)) {
i++;
var stCont = retRange.startContainer;
var endCont = retRange.endContainer;
console.log('retRange(' + i + ') = ', retRange);
console.log('var txt = retRange.commonAncestorContainer.data',retRange.commonAncestorContainer.data);
//now test if one posiion before startOffset and one position after endOffset are WORD characters
var isOneCharBeforeStCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.startOffset == 0) {
//no characters befor this characte so obviously not a word char
isOneCharBeforeStCharWordChar = false;
} else {
var oneCharBeforeStChar = stCont.data.substr(retRange.startOffset-1,1);
if (/\w/.test(oneCharBeforeStChar)) {
isOneCharBeforeStCharWordChar = true;
} else {
isOneCharBeforeStCharWordChar = false;
}
console.log('oneCharBeforeStChar',oneCharBeforeStChar);
}
var isOneCharAfterEndCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.endOffset == endCont.length - 1) {
//no characters after this characte so obviously not a word char
isOneCharAfterEndCharWordChar = false;
} else {
var oneCharAferEndChar = endCont.data.substr(retRange.endOffset,1); //no need to subtract 1 from endOffset, it takes into account substr 2nd arg is length and is treated like length I THINK
if (/\w/.test(oneCharAferEndChar)) {
isOneCharAfterEndCharWordChar = true;
} else {
isOneCharAfterEndCharWordChar = false;
}
console.log('oneCharAferEndChar',oneCharAferEndChar);
}
if (isOneCharBeforeStCharWordChar == false && isOneCharAfterEndCharWordChar == false) {
//highlight it as surrounding characters are no word characters
_highlightRange(retRange, ctrler);
console.log('highlighted it as it was not surrounded by word charactes');
} else {
console.log('NOT hilte it as it was not surrounded by word charactes');
}
//break;
startPt = retRange.cloneRange();
startPt.collapse(false);
}
/*********************/
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
Oh edit my solution out, will update with proper solution, I see you want to highlight all words
This is the code how firefox highlights stuff without changing document: Finder.jsm - _highlight function. You will have to copy this and use it for the whole document, if you need help let me know and I'll do it.
Here was my solution to highlight all matches of single word: https://stackoverflow.com/a/22206366/1828637
Here man this is how you are going to highlight the whole document, I didn't finish the snippet but this is the start of it: Gist - HighlightTextInDocument
Here's the copy paste answer to highlight everything in the document. As you learn more about it share with us, like how you can highlight with a different color, right now its all pink O_O
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
var doc = gBrowser.contentDocument;
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
_highlightRange(searchRange,_getSelectionController(gBrowser.contentWindow))
#jervis, I can't make a comment on your comment under #Noitidart code as I don't have 50rep yet. So I have to post here.
Re:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 40 mins ago
But I tested his code and and it works.
Don't use gFindBar.
Copy it and then paste it into your Scratchpad.
Why are you using gFindBar._highlightDoc(true, word) ? I thoght you wanted to highlight everything in the document? Where did you get _highlightDoc from? I don't see that anywhere in #Noitidart's code.
Regading yoru comment on iterate all words and use gFindBar._highlightDoc:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 39 mins ago
Dude why do that.... I saw #Noitidart posted a per word solution on the linked topic: gBrowser.tabContainer.childNodes[0].linkedBrowser.finder.highlight(true, 'YOUR_WORD_HERE'); that is extremely easy, one line and no need to create text nodes spans or anything. You have to run this code on each tab you want to highlight in.
I have two masks G999-9 and H99-9 which stand for graduates and honor which both need to be entered for a single input text html control.
While first typing, if it hits a G I would like it to show the format for the Graduate format, and for the Honors, I would like it to show the format for the Honors format while typing it out. Is there a simple way to do this in javascript or jQuery?
Examples:
G111-1
H91-5
G001-3
G___-_ (If you hit G it should show the Graduate format)
H__-_ (If you hit H it should show the Honors format.)
____-_ (Type anything else, or nothing do this.)
Thanks,
Marc
Regex Based Demo
Yay! regex! Yes, like every other (non-HTML) parsing questions there's a regex for that. :)
$("input").keyup(function(e){
if(e.keyCode > 40 || e.keyCode < 37)
{
var validInput = $(this).val().match(/(([G])(([0-9]{3}[-]([0-9]{0,1})?|[0-9]{0,3})?)?|([H])(([0-9]{2}[-]([0-9]{0,1})?|[0-9]{0,2})?)?)/);
$(this).val(validInput?validInput[0]:"")
}
});
It's a bit convoluted (help in optimizing it would be much appreciated), but it works.
Simple way? You're either going to have to walk the string or use some fancy regex.
Normally, we'd use regexes for this, but with the assumption that you don't know so much about regexes (perhaps a bad assumption, but most people that can read them will gravitate towards them first) and in the hopes of keeping you following what is happening, I wrote it more procedurally:
<input type="text" class="formatted" />
$(document).ready(function(){
function applyMask(instr){
var pOut = [], delimitter = '-', format = 0, pSplit = 4, pMax = 6;
// There are a lot of ways to express this. I just chose one that should
// be understandable.
if ((instr.length > 0) && (instr[0].toUpperCase() == 'H'))
{
pSplit = 3;
pMax = 5;
}
pMax = Math.min(pMax, instr.length);
for (var i=0; i < pMax; i++)
{
if ((i==pSplit) && (instr[i] != delimitter))
{
pOut.push(delimitter);
}
else
{
pOut.push(instr[i]);
}
}
return pOut.join('');
}
$('.formatted').keyup(function(){
$(this).val(applyMask($(this).val()).toUpperCase());
});
});
Here's a fiddle, even: http://jsfiddle.net/UdcND/
There are some jQuery plugins for easily converting a text input to a mask edit:
a simple but useful plugin: http://www.pengoworks.com/workshop/js/mask/
another good option: http://digitalbush.com/projects/masked-input-plugin/