How to prevent caret misplacement when deleting elements in a contenteditable div - javascript

My application includes a typeahead feature when a user is typing into a comment box. In a similar fashion to Facebook's and google+'s it will perform a lookup of users and suggest them as tags on the fly.
When a user selects one of the offered tags it gets entered into the input area as an element.
I decided to use a conenteditable div for this, but have come across a few issues.
It turns out that firefox wont delete the inserted elements when they are set to contenteditable false whereas every other major browser does.
In order to work around this I have set the inserted 'user' tags as contenteditble=true and wrote a quick jquery workaround to solve this issue.
It works perfectly apart from one major issue. If there are multiple tags in the area and the first of them is not proceeded by any text, then when the user deletes the final one (assuming deletion with the backspace key from right to left) the caret gets positioned incorrectly following that final deletion.
Typing again appears to then return the position to normal.
This issue only occurs in Firefox
Here is a jsFiddle showing the issue:
http://jsfiddle.net/gordyr/PESky/
Place your cursor at the end of that last tag and then hold down the backspace key until all elements are removed. You will see that the cursor/caret moves up and out of position.
It is quite possible that this is actually a bug in firefox itself, however I am looking for a workaround of some kind as without this automatic element deletion with javascript, the don't get removed at all.
Many thanks

It appears that FF leaves some dirt behind, but you can clean it up by adding:
if (!$.trim(el.text())) {
el.empty();
}
This will check if the innerText of the contenteditable only contains spaces or newlines/breaks. If it does, just empty it. Seems to work in my test: http://jsfiddle.net/cBZ7k/
Side note: you should probably use keydown instead of keypress for webkit support. Also, e.which is enough to get the keyCode, jQuery normalizes this event property for you.

Related

How can I remove an svg element from the taborder? [duplicate]

Is there any way in HTML to tell the browser not to allow tab indexing on particular elements?
On my page though there is a sideshow which is rendered with jQuery, when you tab through that, you get a lot of tab presses before the tab control moves to the next visible link on the page as all the things being tabbed through are hidden to the user visually.
You can use tabindex="-1".
Only do this if you are certain it does not remove functionality for keyboard users.
The W3C HTML5 specification supports negative tabindex values:
If the value is a negative integer
The user agent must set the element's tabindex focus flag, but should not allow the element to be reached using sequential focus navigation.
Watch out though that this is a HTML5 feature and might not work with old browsers.
To be W3C HTML 4.01 standard (from 1999) compliant, tabindex would need to be positive.
Sample usage below in pure HTML.
Focusable
<a tabindex="-1" href="#" onclick="return false">Not focusable</a>
Focusable
Don't forget that, even though tabindex is all lowercase in the specs and in the HTML, in Javascript/the DOM that property is called tabIndex.
Don't lose your mind trying to figure out why your programmatically altered tab indices calling element.tabindex = -1 isn't working. Use element.tabIndex = -1.
If these are elements naturally in the tab order like buttons and anchors, removing them from the tab order with tabindex="-1" is kind of an accessibility smell. If they're providing duplicate functionality removing them from the tab order is ok, and consider adding aria-hidden="true" to these elements so assistive technologies will ignore them.
If you are working in a browser that doesn't support tabindex="-1", you may be able to get away with just giving the things that need to be skipped a really high tab index. For example tabindex="500" basically moves the object's tab order to the end of the page.
I did this for a long data entry form with a button thrown in the middle of it. It's not a button people click very often so I didn't want them to accidentally tab to it and press enter. disabled wouldn't work because it's a button.
The way to do this is by adding tabindex="-1". By adding this to a specific element, it becomes unreachable by the keyboard navigation. There is a great article here that will help you further understand tabindex.
Just add the attribute disabled to the element (or use jQuery to do it for you). Disabled prevents the input from being focused or selected at all.

How does this JS copy trick work?

On this page almost anywhere on the page if you copy you'll get the string Read more at http:// added to the end of your copy. I was wondering how. After looking at the source (post-copypaste.js) and setting a breakpoint I didn't understand. That area seems to be firing when i select text.
I tried looking at the DOM (via view selected source in firefox) and I didn't see the text in the dom. So it must be a javascript trick. I can imagine catching a control C event (i dont know if that is what is happening) but i cant imagine how you can add or affect the text being compied in since it belongs to the dom. I don't see flickering or anything
How does that JS trick work or how do i debug it to figure it out?
But the awkward thing is the selection on the regular window/dom doesn't seem to be affected.
It is, but just not visible. What usually happens is there is a container somewhere else on the page (not necessarily visible). The content you have selected is being pasted in there, then extended, then copied and deleted from the container. It all needs a fraction of a second and by the time you paste it in somewhere, your clipboard is already storing the extended content.
If you look closely on the page you have linked in as an example, there is an empty div tag in the body with a class of pw-root. <div class='pw-root'></div> When you copy the text, for a second (visible in Firebug for instance) it changes as explained above then gets emptied again.

TinyMCE / IE9 / DIV with Width/Height / ContentEditable / How to prevent DIV Selection?

When using TinyMCE (3.5.4.1) in IE9, if I place a DIV in my content, and if that DIV has either/both Height & Width specified, IE treats it, at least partially, as a contenteditable DIV.
This means when the user comes to edit such a document in TinyMCE the first time they click in the editor IE highlights the DIV in question, and allows the user to move it - which they often do by accident. They then have to click again to edit the content inside the DIV and while they do this the DIV is outlined on the page which is a distraction.
I have seen a number of posts (e.g. http://www.tinymce.com/forum/viewtopic.php?id=3939) saying that IE fires the "controlselect" event in this case and to place the cursor within the content and return false. I eventually managed to get this example to run, but while it suppresses the initial selection of the content it then seems to become random as to whether a given click inside the content places a cursor there or not.
Is there a reliable way to make IE handle this situation like the other browsers do i.e. with no unexpected side effects from adding height/width to a DIV?
I have got it mostly working as here http://www.tinymce.com/forum/viewtopic.php?pid=103272
And here http://www.tinymce.com/forum/viewtopic.php?id=29485
Its not perfect but seems as good as can get.
For this issue you could try to use object_resizing configuration parameter in your tinymce init
object_resizing: false,

disableSelection on everything but input[type=text]

I have a requirement to disable selection on a web page for everything except input[type=text] elements.
This accepted answer to a similar question almost does the trick, but it doesn't disable selection for containers that contain input[type=text] elements. Therefore the user can still select by starting a drag operation from within one of these containers.
Is this even possible, i.e. is it possible to disable selection for a container element, while enabling it for child elements (specifically, child input=text elements).
#Pointy, "Why not just take out that first .not() call?"
Taking out the first .not call, will give:
$('body').not('input').disableSelection();
which, as pointed out in the linked question, will still disable everything on the page, including the input[type=text] elements.
#David Thomas, "Do you have a live demo ..."
I don't have a live demo, but it's fairly trivial. For example, a div with a bit of padding that contains an input[type=text] element. The result is:
With $('body').not('input').disableSelection(); selectiopn is disabled for all the page, including the input elements.
With $('body *').not(':has(input)').not('input').disableSelection(); selection is disabled for all elements that don't contain an input element. But it is possible to select the whole page by starting a drag operation from within a container that contains an input element.
Well, cinch up your suspenders and get ready for a really dirty hack.
Disclaimer:
I don't think this is a good way to do things. I simply wanted to tackle the challenge of getting the OP's desired functionality. If someone else can get this to work in a cleaner way, please post it.
After playing around with the disableSelection() function, it seemed that if a parent element had been disabled, all of its children would be unselectable as well (please correct me if I'm wrong). So, I decided that if you wanted everything to be unselectable except small parts, you could put all of your markup in one unselectable <div> and use absolute positioning to place selectable clones of your <input> tags (or any tag, really) on top of the unselectable ones. These clones would reside in a second <div> that was not disabled.
Here's an example of this idea: http://jsfiddle.net/pnCxE/2/.
Drawbacks:
Styling becomes a big headache. Any element that relies on a parent's style (i.e., position, size, colors, etc.) cannot be cloned since the clones reside in a separate place.
Forms become much harder to manage since (again) the clone isn't in the same place as the cloned element.
You have to deal with naming collisions since the clone will have the same ID as the cloned element. (It's doable; I just didn't want to code it since it would probably need specific attention by anyone that uses this idea)
So, while you can work around the selectable limitations, you might be better off just accepting the container selection. I would think long and hard before putting this code into a production environment.
I've found a solution that appears to do what I want, and would be interested in comments / improvements from jquery / javascript experts.
$(document).ready(function () {
$("body").disableSelection();
$("body").delegate('input[type=text],textarea', "focus", function () {
$("body").enableSelection();
});
$("body").delegate("input[type=text],textarea", "blur", function () {
$("body").disableSelection();
});
});
When a textbox (input[type=text] or textarea) has the focus, then dragging with the mouse only selects text within the textbox. Therefore it's "safe" to enable selection for the whole page while a textbox has focus (between focus and blur events).
There is a noticeable delay when tabbing between textboxes on IE8/9. It's not noticeable on Google Chrome, which I understand has a faster javascript engine. So I can live with the performance hit, especially since IE10 is going to have a faster javascript engine.
UPDATE
When using ASP.NET UpdatePanel, this needs to be modified to disable selection after each partial postback:
Sys.Application.add_load(function () {
$("body").disableSelection();
});
Try this, although it is same with what you're already using:
$('* :not(input)').disableSelection();
I don't get though why do you have to use entire body element and not narrow it down to text nodes (p, h[..], ul, ol etc.)
And I agree with #David Thomas - it would be easier to see a test page you're working on.

Javascript context menu click event/detection - filter paste content

Scenario: I'm trying to intercept paste events inside a textarea/input text, and filter the content being pasted.
Webkit/IE are handled rather well, as I can attach code to the onpaste event, and then read from the clipboard what is being pasted. Plenty of examples around.
Gecko is trickier, because as far as I know it isn't possible to read the clipboard content on Firefox (unless somebody knows a workaround for that?)
I just use the input swap trick for that.
Opera is being annoying tho. I can trap CTRL+V and SHIFT+INS, but there's no onpaste event.
Not to mention any sort of clipboard interaction, apparently.
So, my question is:
Can I detect if the user clicked on paste in the context menu on Opera? Is there any other way to detect the event?
EDIT:
Thanks everybody for the answers - they all add a good input, even if there's no definitive solution.
Having to choose, I'll pick the only one that tried to address the original question, and that would probably work if it wasn't too much of an hack to even try.
Notes for those that have my same problem (input filtering):
it is possible to capture content being dragged: mouseup + setTimeout does the trick everywhere almost perfectly.
without flash, there is probably no solution bar polling. Even with flash, it's not a completely solid solution either. Too much effort to support 100% of the cases.
I ran into this last year. In short, no.
I ended up using an onchange handler and filtering the content after it's already been pasted into the text box.
You can intercept the paste with jQuery using the bind('paste', function() {});, compare string before and after pasting and apply your formatting.
The following was tested in IE7/FF3.6/Chrome/Safari 5
$("#textarea").bind('paste', function(e){
// Do whatever you needed to do with the code here.
});
Live Example http://jsfiddle.net/VSrTg/2/
Edit An approach would be something like this:
$("#textarea").bind('paste', function(e){
var oldText = this.value;
setTimeout(function() {
// Compare oldText to $("#textarea").val() and format accordingly.
}, 1000);
});
​Edit 2 Given your revisions to your original post, if you're worried about the giant market share that is Opera, you're going to have to monitor the value of your textbox with a setInterval() and compare it against itself for changes.
Ultimately there will always be a way around your script, even the above example is susceptible to simply dragging text from another text box (or the address bar) into it without triggering the paste event defined above.
I would like to point out DOJO menu widget that is creating context menus perfectly in different browsers. http://www.dojotoolkit.org/reference-guide/dijit/Menu.html#dijit-menu
What you can do is that detect paste event in browsers that are supporting it and override context menu in browsers that are not supporting this event like opera.
Once you create your own context menu then you can add copy paste menu item or create context menu similar to the default using css.
Edited
Some browsers might not allow us to fetch clipboard content, in this case we can always revert back to flash for borrowing some of its features that are cross browser. See couple of links I posted in comments.
Its complete implementation might have more issues than anticipated but it is possible and we can always give it a try (I will for sure).
The answer to the question is a simple no. The main browsers that have no paste event are recent versions of Opera and Firefox 2. Given that there is no paste event, you need to find an alternative event or set of events to detect a paste from the context menu as it actually happens. You can add handlers for every event there is (I've done this) and you simply get nothing in the relevant browsers when a paste is triggered from the context menu by the user.
This only leaves polling the text input's value regularly, which is not the same thing. You could keep track of keypresses and observe in your polling code that the text input's value has changed by some means other than keyboard input and do a diff, but that's hacky and unreliable.
I use the setTimeout for paste events. But for context menu select nothing seems to work(as stated above). I bind a mousemove to the input's form which fires the update function. Then unbind/bind so they don't stack up.
This handles the context menu select and dragging value to input field.
If your form is small, say with only one input field and the mouse will not land on it after selecting from context menu, bind to the form's parent or document. Sure, it has to wait until the mouse moves but that is the general user action after selecting from context menu.
Works fine.

Categories