How to move the textbox viewport when gaining focus? - javascript

I have a textbox that may contain strings larger than the textbox size. When I'm typing, the textbox "viewport" always moves to show the last character I typed (for example when you write a very large title in a SO question).
a
The problem is that if the texbox loses focus, when it is focused again the viewport always is set at the start of the text, and I want it at the end.
I tried moving the caret programatically to the end of the text and it works, but the viewport is still at the beginning of the text, so the user still has to press any key to move the viewport to the end of the text.
Example
This is the textbox before losing focus:
alt text http://img35.imageshack.us/img35/6697/10437837.jpg
And this after focus is lost:
alt text http://img11.imageshack.us/img11/1390/33816597.jpg
I'd like that when the txtbox gains focus again the viewport is set like in the first image.
Is possible to do this ?

Sry, but I don't think it's possible. At least not in clean and browser-consistent way.

Okay, you are battling browser and event limitations here.
You can not simulate a UI-Event in such a way that it mimics human interaction (it's a security issue). However, there are certain built-in ways to manipulate the control of text in a few DOM elements -- The textarea element is a good example of this with its selectionStart/selectionEnd (browser-specific implementation) variables. You should probably note that in Firefox, the selectionStart/selectionEnd make your desired effect, but, again, only under Firefox.
So, your issue can not be solved in a cross-browser way. You would have to go to simulating the effect through text slicing and so forth.
Here is a quick pseudo-code example of what I mean:
element.onblur = function (event) {
this.hidden = this.value;
if (this.value.length > (this.offsetsetWidth / 12)) {
this.shown = this.value.slice(this.value.length - (this.offsetWidth / 12));
this.value = this.shown;
}
};
element.onfocus = function (event) {
this.value = this.hidden || this.value;
};
The slice hack here is based off a ratio of a monospaced font's width (12px) to the width of the element (whatever it may be). So, if we have a box of 144px and we are dealing with a monospaced font, we know that we can fit 12 characters in the box at a time -- so, we slice in this manner.
If the length of the value in the input is 24 characters, we do simple math to slice at (24 - (w/12))'th position into the string.
It's all a hack -- And, in all honesty, is there any practical application to this? Perhaps you should add some subtext under the input that gives an ellipsis-like preview of the last part of the text.

I feel like I might have misread the question after reading the other answers talking about hacks and such, but I knocked together a quick sample that works fine in IE8 and Firefox 3.5.2. Assuming that an input element with an id="Test" and calling the following function onfocus:
function TestFocus()
{
var Test = document.getElementById("Test");
if (document.selection)
{
var SEnd = document.selection.createRange();
SEnd.moveStart("character", Test.value.length);
SEnd.select();
}
else if (Test.setSelectionRange)
{
Test.setSelectionRange(Test.value.length, Test.value.length);
//- Firefox work around, insert a character then delete it
var CEvent = document.createEvent("KeyboardEvent");
if (CEvent.initKeyEvent)
{
CEvent.initKeyEvent("keypress", true, true, null, false, false, false, false, 0, 32);
Test.dispatchEvent(CEvent);
CEvent = document.createEvent("KeyboardEvent");
CEvent.initKeyEvent("keypress", true, true, null, false, false, false, false, 8, 0);
Test.dispatchEvent(CEvent);
}
}
//- The following line works for Chrome, but I'm not sure how to set the caret position
Test.scrollLeft = Test.scrollWidth;
}
In Firefox, it uses initKeyEvent to send a space key and then the backspace key immediately after. In Chrome, I'm not sure how to set the caret to go to the end, but setting scrollLeft on the input almost works, maybe you can play around with it a little? As for Opera, I have no idea. I certainly hope this helps you on your way though.

One way that's a little hack-y would be to dynamically reload the text-box after focus is lost.
Just store the string in a variable and remove the text-field, then add it back in with $.html() or whatever method you like with the string in it.
Simple, works across browsers, but a little hack-y.

set the text-align css property of the textbox to right aligned when losing focus.
with mootools:
$('#yourtextboxid').setStyle('text-align', 'right');

Related

Get caret position (character index) in input field on drop event (FF, Chrome)

I want an input field to accept only numbers, so, among other techniques, I also prevented the default drop event, if the result in the input filed is not a correct number. To achieve this, in IE I focus the field where I'm dropping the text, I search for the cursor position (character index), then construct the new value, from the old one and the dragged text.
To get caret position:
IE: Math.abs(document.selection.createRange().moveStart("character", -1000000)) Works
FF, Chrome: inputfield.selectionStart Does not work
Any ideas how can I get the character index in the drop handler?
There is a lot of discussion around how drag/drop support is implemented in browsers but the truth is they want to keep things protected. I mean theoretically you can call
event.dataTransfer.setData("Text", newValue)
inside your drop handler but it won't work due to "security reasons" they say. It's a pity since this would have been the best solution for you, I mean changing the text before it would have been dropped.
Instead I think you can overcome your problem with a small "hack"
<input id="target" type="text" value="aaa bbb ccc" />
<input id="source" type="text" value="good bad" />
and js
<script type="text/javascript">
$(function () {
var drop = false;
$("#target").bind("drop", function (e) {
// you can store the dragged text if you like
//var text = e.originalEvent.dataTransfer.getData("Text");
drop = true;
});
$("#target").bind("focus", function (e) {
if (drop) {
// you can get caret here, etc
$("#target").val($("#target").val().replace("bad", ""));
drop = false;
}
});
});
</script>
Tested in Chrome but it should work in all browsers too.
I hope this is what you were looking for.
Visit the following Link
https://groups.google.com/forum/?fromgroups=#!topic/javascript-information-visualization-toolkit/GJVG2laTaUo
I think it is usefull for you.

How to identify if user is typing in RTL or LTR language?

I want imitate google's input,
It automatically change input's typing direction based on the language you're typing in.
How can I identify if user is typing in RTL or LTR language?
It must work cross-browser.
You should use the attribute dir="auto"
e.g.
<html dir="auto">
This way the browser will look at the first strongly typed character and adjust the text automatically.
For reference, here is the W3C documentation on this: http://www.w3.org/International/tutorials/new-bidi-xhtml/qa-html-dir
If you want to mimic Google's directionality recognition algorithm, you will need to listen to input change, recognize whether the character inserted was RTL or LTR (or neutral) and change the textbox's dir="" attribute accordingly.
Google's algorithm, for the most part, seems to calculate the majority of strong characters in the string and decide the directionality from that. If you type RTL it will switch the context to RTL, and if you then switch to LTR for the same paragraph, it may switch the context again to LTR if those characters outnumber the RTL ones.
For comparison, Facebook uses a direction algorithm as well, but it is slightly different - it seems to use the first strong character to decide the direction of the paragraph rather than the overall number.
(For the record, Google also seems to have several algorithms for this; Gmail behaves slightly differently than Google Hangouts which is different than how the input in Google search is aligning itself. In these things, there are mostly no "right" or "wrong" answers but rather what fits your use case)
Whichever method you choose to implement, you first need to identify what the user is typing. There are several ways to do this, but I would recommend the following:
Read a little about Unicode BiDirectional Algorithm (especially about "strong" type characters) http://unicode.org/reports/tr9/
Find a good way to identify strong characters in your context. An example of a regex to do that can be found in MediaWiki's Language file (where group 1 is LTR and group 2 is RTL): https://github.com/wikimedia/mediawiki/blob/6f19bac69546b8a5cc06f91a81e364bf905dee7f/languages/Language.php#L174
You can create a JavaScript method that listens to the user's input, uses the regex above to identify which strong character is used (either by first character or by counting them all, whichever works best for your use and scale) -- and change the textbox's dir="" attribute accordingly.
Make sure you later display the submitted text with the correct alignment later, so you may have to either use something to store the alignment you picked or to re-recognize whenever you render it. Either way, don't forget that the display needs the same dir="" attribute as well.
Too late but maybe it can help someone one day.
This function will add direction attribute to the input field based on the first inputed character, and when user clears input text the function will detect the new language of text again.
$.fn.set_input_direction = function()
{
$(this).off('keypress').on('keypress',function(e){
_this = $(this);
setTimeout(function()
{
if(_this.val().length > 1){
return;
} else {
var rtl_regex = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
var is_rtl = rtl_regex.test(String.fromCharCode(e.which));
var direction = is_rtl ? 'rtl' : 'ltr';
_this.css({'direction' : direction});
}
});
});
};
To use it:
$('input').set_input_direction();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
<script>
function checkRTL(s) {
var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + '\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
rtlDirCheck = new RegExp('^[^' + ltrChars + ']*[' + rtlChars + ']');
return rtlDirCheck.test(s);
};
// BIND KEYPRESS
var input = $('input').on('keypress', keypress)[0];
function keypress(e) {
// need to wait for the character
setTimeout(function () {
var isRTL = checkRTL(String.fromCharCode(e.charCode)),
dir = isRTL ? 'RTL' : 'LTR';
input.style.direction = dir;
}, 0);
}
</script>
</head>
</body>
<h1>Auto Direction
<sup>(RTL | LTR)</sup>
</h1>
<input type="text" onkeypress="keypress()" placeholder="Type something…" />
</body>
</html>

Rangy and IE8 - positioning caret after element at end of paragraph

I'm using Rangy to perform several operations in a rich text editor (designmode = "on"). One of these functions is pasting formatted content which can represent certain pre-defined characters the user has created before-hand. All of the text content is held in paragraph elements. The user may start with this:
<p>The following is a special character: |</p>
where the pipe (|) is the caret position. They then choose to paste one of the 'special' characters via a button on the editor to end up with this:
<p>The following is a special character: <span class="read-only" contenteditable="false">SPECIAL</span>|</p>
The action uses Rangy behind the scenes to maintain the position of the caret (SelectionSaveRestoreModule) during the internal paste process which could be post-paste-processing the text in the editor and which likely messes up the position of the cursor otherwise.
However, in IE8 the caret cannot be placed after the <span> since there appears to be a bug which makes it an invalid position. As a result the cursor appears before the <span> element and it is not even possible to move the cursor after the span with the keyboard cursor controls. In fact, it even prevents the cursor moving on to any following paragraphs.
I have experimented with several techniques over recent days, including placing extra characters after the <span>s with some success. However those extra characters obviously cause confusion for the user when they appear and are not ideal. Using the zero-width space is visually better but attempting to tidy them up after the paste operation causes issues.
I need a 'tidy' method of supporting this user requirement for the special characters and I freely accept I may be approaching this in the wrong way.
I have a solution which seems to be working in my tests so far, but when I look at it it still fills me with a feeling that there must be a better way (not to mention a sense of dread).
What this code tries to do is place a zero-width-space after any read-only span which is at the end of a paragraph. It does this by inspecting the nodes after these elements to determine if there is actually text in them. At the same time it removes any zero-width-spaces which may still be in the text from previous inspections which are now no longer needed.
var ZWS = '\ufeff';
jQuery(_doc.body).find('p').each(function () {
var lastContentEditable = undefined;
// Look through the root contents of each paragraph to remove no-longer require zws fixes
jQuery(this).contents().each(function () {
if (this.nodeType === 3) {
if (this.nodeValue.indexOf(ZWS) != -1) {
// Text node containing a ZWS - remove for now
this.nodeValue = this.nodeValue.replace(new RegExp(ZWS, 'g'), '');
}
// Does this node now contain text?
if (this.nodeValue.length > 0 && lastContentEditable) {
// Found text after a read-only node, ergo we do not need to modify that read-only node at the end
lastContentEditable = undefined;
}
} else if (this.nodeType === 1 && this.getAttribute('contenteditable') === "false") {
// Indicate that this is currently the last read-only node
lastContentEditable = this;
}
});
if (lastContentEditable) {
// It appears that there is a read-only element at the end of the paragraph.
// Add the IE8 fix zws after it.
var node = document.createTextNode(ZWS);
jQuery(lastContentEditable).after(node);
}
});

Change HTML Textbox: Overwrite Instead of Insert as User Types

I am working on a service which will allow editing of text. To aid the user in the process, I'd like to allow the user to set a text field to overwrite mode, as is possible in Word, etc. How can the behaviour of an HTML text box be changed to overwrite instead of insert text as the user types?
For example, if the textbox had the text:
This is a trst.
The user could click between the r and the t, type a single e and the text would then be
This is a test.
with the cursor between the e and the s. I'm currently using jQuery, so methods using either that or pure javascript would be preferred. I would accept any reasonable solution, however.
That's a bit of crazy but it seems to work somehow :)
Based on this answer and this answer this piece of code was created.
$("textarea").on("keypress", function(e) {
var key = String.fromCharCode(e.which || e.charCode || e.keyCode);
if (/[A-Za-z0-9 ]/.test(key)) {
var text = this.innerHTML;
var caret = getCaret(this);
var output = text.substring(0, caret);
this.innerHTML = output + key + text.substring(caret + 1);
setCaretPosition(this, caret + 1);
return false;
}
return true;
});​
DEMO: http://jsfiddle.net/aHSzC/
It works but now I have no time to fix a small bug I found.
If you press Backspace it seems to behave like a forward eraser.
Anyway, here is the code that can be improved. Feel free to edit my answer and do whatever you like.
Input elements have an onkeypress attribute. You could make it call a function that gets rid of the character after the cursor. jQuery has a Caret extension that you could use to find out where the caret is.

JavaScript convert mouse position to selection range

I would like to be able to convert the current mouse position to a range, in CKEditor in particular.
The CKEditor provides an API for setting the cursor according to a range:
var ranges = new CKEDITOR.dom.range( editor.document );
editor.getSelection().selectRanges( [ ranges ] );
Since CKEditor provides this API, the problem may be simplified by removing this requirement and just find a way to produce the range from the mouse coordinates over a div containing various HTML elements.
However, this is not the same as converting a mouse coordinate into the cursor position in a textarea since textareas have fixed column widths and row heights where the CKEditor renders HTML through an iframe.
Based on this, it looks like the range may be applied to elements.
How would you figure out the start/end range which is closest to the current mouse position?
Edit:
An example of how one might use the ckeditor API to select a range on the mouseup event.
editor.document.on('mouseup', function(e) {
this.focus();
var node = e.data.$.target;
var range = new CKEDITOR.dom.range( this.document );
range.setStart(new CKEDITOR.dom.node(node), 0);
range.collapse();
var ranges = [];
ranges.push(range);
this.getSelection().selectRanges( ranges );
});
The problem with the above example is that the event target node (e.data.$.target) is only firing for nodes such as HTML, BODY, or IMG but not for text nodes. Even if it did, these nodes represent chunks of text which wouldn't support setting the cursor to the position of the mouse within that chunk of text.
What you're trying to do is really hard in a browser. I'm not familar with ckeditor in particular, but regular javascript allows you to select text using a range so I don't think it's adding anything special. You have to find the browser element that contains the click, then find the character within the element that was clicked.
Detecting the browser element is the easy bit: you need to either register your handler on every element, or use the event's target field. There is lots of info on this out there, ask a more specific question on stackoverflow if that's what you're having trouble with.
Once you have the element you need to find out which character within the element was clicked, then create an appropriate range to put the cursor there. As the post you linked to stated, browser variations make this really hard. This page is a bit dated, but has a good discussion of ranges: http://www.quirksmode.org/dom/range_intro.html
Ranges can't tell you their positions on the page, so you'll have to use another technique to find out what bit of text was clicked.
I've never seen a complete solution to this in javascript. A few years ago I worked on one but I didn't come up with an answer I was happy with (some really hard edge cases). The approach I used was a horrible hack: insert spans into the text then use them to perform binary search until you find the smallest possible span containing the mouse click. Spans don't change the layout, so you can use the span's position_x/y properties to find out they contain the click.
E.g. suppose you have the following text in a node:
<p>Here is some paragraph text.</p>
We know the click was somewhere in this paragraph. Split the paragraph in half with a span:
<p><span>Here is some p</span>aragraph text.</p>
If the span contains the click coordinates, continue binary search in that half, otherwise search the second half.
This works great for single lines, but if the text spans multiple lines you have to first find line breaks, or the spans can overlap. You also have to work out what to do when the click wasn't on any text but was in the element --- past the end of the last line in a paragraph for example.
Since I worked on this browsers have got a lot faster. They're probably fast enough now to add s around each character, then around each two characters etc to create a binary tree which is easy to search. You could try this approach - it would make it much easier to work out which line you're working on.
TL;DR this is a really hard problem and if there is an answer, it might not be worth your time to come up with it.
Sorry for bumping an old thread, but I wanted to post this here in case anyone else stumbles across this question, as there is very little information on this. I just had to write a function that does this for an Outlook for web userscript, because they override the default drag-n-drop functionality and break it in the compose box. This is the solution I came up with:
function rangeFromCoord(x, y) {
const closest = {
offset: 0,
xDistance: Infinity,
yDistance: Infinity,
};
const {
minOffset,
maxOffset,
element,
} = (() => {
const range = document.createRange();
range.selectNodeContents(document.elementFromPoint(x, y));
return {
element: range.startContainer,
minOffset: range.startOffset,
maxOffset: range.endOffset,
};
})();
for(let i = minOffset; i <= maxOffset; i++) {
const range = document.createRange();
range.setStart(element, i);
range.setEnd(element, i);
const marker = document.createElement("span");
marker.style.width = "0";
marker.style.height = "0";
marker.style.position = "absolute";
marker.style.overflow = "hidden";
range.insertNode(marker);
const rect = marker.getBoundingClientRect();
const distX = Math.abs(x - rect.left);
const distY = Math.abs(y - rect.top);
marker.remove();
if(closest.yDistance > distY) {
closest.offset = i;
closest.xDistance = distX;
closest.yDistance = distY;
} else if(closest.yDistance === distY) {
if(closest.xDistance > distX) {
closest.offset = i;
closest.xDistance = distX;
closest.yDistance = distY;
}
}
}
const range = document.createRange();
range.setStart(element, closest.offset);
range.setEnd(element, closest.offset);
return range;
}
All you do is pass in the client coordinates, and the function will automatically select the most specific element at that position. It will use that selection to get the parent element used by the browser (most notably contenteditable elements), as well as the maximum and minimum offsets. It will then proceed, iterating through the offsets, placing marker span elements with position: absolute; width: 0; height: 0; overflow: hidden; at each offset to probe their position, removing them, and checking distance. As per most text editors, it will first get as close as it can on the Y coordinate, and then move in on the X coordinate. Once it finds the closest position, it will create a new selection and return it.
There are two ways of doing this, just like every WYSIWYG does.
First:
- you give up because it is too hard and it will end up to be a browser killer;
Second:
- you try to parse the text and put it in the exact place in a semitransparent textarea or div above the original, but here we have two problems:
1) how would you parse the dynamic chunks of data to get only the text and to be sure you map it over the exact position of the actual content
2) how would you solve the update to parse for every darn character you type or every action you do in the editor.
In the end this is just a "A brutal odyssey to the dark side of the DOM tree", but if you choose the second way, than the code from your post will work like a charm.
I was working on a similar task to allow TinyMCE (inline mode) to initialize with a caret placed in mouse click position. The following code works in the latest Firefox and Chrome, at least:
let contentElem = $('#editorContentRootElem');
let editorConfig = { inline: true, forced_root_block: false };
let onFirstFocus = () => {
contentElem.off('click focus', onFirstFocus);
setTimeout(() => {
let uniqueId = 'uniqueCaretId';
let range = document.getSelection().getRangeAt(0);
let caret = document.createElement("span");
range.surroundContents(caret);
caret.outerHTML = `<span id="${uniqueId}" contenteditable="false"></span>`;
editorConfig.setup = (editor) => {
this.editor = editor;
editor.on('init', () => {
var caret = $('#' + uniqueId)[0];
if (!caret) return;
editor.selection.select(caret);
editor.selection.collapse(false);
caret.parentNode.removeChild(caret);
});
};
tinymce.init(editorConfig);
}, 0); // after redraw
}; // onFirstFocus
contentElem.on('click focus', onFirstFocus);
Explanation
It seems that after mouse click/focus event and redraw (setTimeout ms 0) document.getSelection().getRangeAt(0) returns valid cursor range. We can use it for any purpose. TinyMCE moves caret to start on initialization, so I create special span 'caret' element at current range start and later force editor to select it, then remove it.

Categories

Resources