I'm trying to create a bookmarklet which when clicked inserts a specific text into the currently active text control. Don't judge.
So this is my code so far:
(function()
{
var t="<<< VALUE >>>";
var a=document.activeElement;
if(!a) alert("No active element found.");
if(!!a)
if(a.nodeName=="TEXTAREA"||a.nodeName=="INPUT")
if(!a.value) a.value=t;
else a.value+=t;
else if(a.nodeName=="DIV"&&a.isContentEditable==true)
if(!a.textContent) a.textContent=t;
else a.textContent+=t;
}
)();
It works for textareas and inputs, but not for editable divs. It does change the text, but the control is broken afterwards in a way that I can't use backspace or delete anymore.
How do I set the content of such a div without braking anything?
The best place to test this is supposedly facebook. Put the focus on the text control for a new post, then execute the function. You'll notice that you can't use backspace and delete anymore.
// EDIT
After some comments and jsfiddle experiments it looks like this is indeed a Facebook related problem. I've updated the title accordingly. Still baffles me why this is the behavior and how to overcome it.
Related
The coder's fantasy
I created a simple user script to act very quickly on the text I selected. It goes like this:
I select a word, a website (doesn't have to be a link), or a phrase from, let's say, a p element
When I press the trigger key, the algorithm will try to figure out if it's a website or text. It will open a tab: if it's a website, that's what it'll load; if it's text, it will google it.
The problem shows its ugly head
It works great except when I'm editing text. If I'm editing something I've written in a textarea/input it will fire, potentially losing what I wrote. Fortunately, there's usually cache, or even the site will warn me for having unsaved changes, which saves me from losing whatever I wrote. But it's something to fix.
The challenge
The userscript should only run on text that can't be edited. You'd think it is as easy as not calling the function if the selected text is within a textarea. But there are many ways to display editable content without using classical elements. The "best" filter I've found is to check for document.activeElement.isContentEditable. However, in this very box, that returns false. This is a textarea element, so I can add it to the filter, and I can do so with a few more I can think of. But apart from being an ugly solution, it is not foolproof.
Besides adding a "did you run me by accident?" prompt, is there a better way to do this?
Edit: my current code
If I understand correctly .... here is an example of how to go about it.
if (['TEXTAREA', 'INPUT'].includes(document.activeElement.nodeName)) {
// it is in textarea, input
}
else if (document.activeElement.isContentEditable) {
// it is in contentEditable element
}
else {
// not above
}
Above is not the only method, e.g. the following using window.getSelection():
const sel = window.getSelection();
const text = sel.toString();
if (!text.trim()) {
// there is no selection
// or selection is white-space
// or selection is in textarea, input
}
else if (document.activeElement.isContentEditable) {
// it is in contentEditable element
}
else {
// not above
}
I've been struggling with getting a field working properly. This field displays a lot of data, and the user wants to select and copy a large portion of it. The data is basically a big list and the user wants to select all entries below a certain point. The way that they achieve the selection is by highlighting a word or two in the first entry they want then pressing ctrl+shft+end to select everything to the bottom. This was working until a new feature on the page was added below the contents of the list. Now the hot key select also selects the contents of the rest of the page.
The current implementation is simply :
<div id='diff-contents'>[content here]</div>
<div id='trailing-content'>blah blah blah...</div>
I have tried a read-only input field:
<input id='diff-contents' value='[content here]' readonly/>
This works in Firefox to some extent however the contents contains HTML, and the input field show html literally, not rendered. In addition to that Chrome doesn't show a blinking caret and the hot keys do nothing, so the input field is sadly not viable for me in this situation.
How can I make a selectable field that maintains focus for the cursor and shows a blinking caret but is not editable using javascript, CSS, HTML, or JQuery?
Edit: jsfiddle example that should clarify a bit.
Look at these questions how to determine the current selection: Getting selected text in a browser, cross-platform
The next step is to create a new range which starts at the end tag of #diff-contents. With this information, you should be able to extend/modify the existing selection.
I suggest to either add a button to the UI or use JavaScript with a key-press handler to trigger this code.
With that, the correct amount of HTML should be selected. Users can then copy that into the clipboard with Ctrl+C.
#Aaron Digulla mentioned key listeners, and that got me thinking about simply stopping the events.
The diff-content element is still a div but it is set to editable. This gives both HTML rendering and a blinking caret.
$(this).keydown(function (event) {
if (document.activeElement.id == 'diff-content') {
if (!allowedKeys(event.keyCode)) {
//The only other key presses that should be processed are ctrl+c (keycode 67) and ctrl+a (65)
if (!event.ctrlKey || !(event.keyCode == 67 || event.keyCode == 65)) {
event.preventDefault();
}
}
}
});
The javascript adds a keydown event listener to the entire page. This is necessary since if you just add it to the element, the event has already propagated through the rest of the page and will still be processed, and this was causing funny issues for me. Next we check if it's the diff-content that is active since we want other input elements to still operate normally. Then we check if the key event is an allowed key (tab, home, end, arrows). And finally, check for ctrl+c and ctrl+a and allow those too. I tried event.stopPropogation() and event.stopImmediatePropogation(), and neither of those worked, but preventDefault did.
Lastly, I added style="outline-style:none" to the element so that the blue border would not appear when the element has focus.
The only issue that I have yet to resolve is that since it is editable, the browser still allows you to select and then right click to either cut or paste, which will allow you to alter the text.
Here is the final jsfiddle for what I am using: http://jsfiddle.net/wh3nzmj8/12/
I am using the highlighter module in Rangy.
I have a div element, which has some html. The html is actually loaded from a file using ajax, I have a button which does this loading.
Once the text is loaded, I can select a portion of the displayed html and press my "Highlight" button. This calls some Rangy code and highlights the text as desired...
//called on document load
rangy.init();
cssApplier = rangy.createCssClassApplier(highlightClassName, { normalize: true });
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
//called on "Highlight" button click
highlighter.highlightSelection(highlightClassName, selection);
For the purpose of replicating, please select a large portion for first highlight.
Next, I click my load html button to reload the html. The highlight is gone, as expected. But now I select another bit of text, which happens to overlap the first highlight that I did. Now when I press the "Highlight" button, for some reason the highlight is the one from the previous highlight. Why is this happening?
I know there must be something to do with the merging, but I can't understand why. When I debug the JS I can see that the selection (from rangy.getSelection()) is what I expect it to be.
Here is a JSFiddle replication of the problem
The reason this is happening is because each highlight exists as a pair of character offsets rather than having references to actual ranges in the DOM, meaning that when some part of the DOM is replaced, existing highlights remain blissfully unaware and continue to assume they are applied to the original character range.
Your workaround is fine. Another way would be to call the highlighter's removeHighlights() method:
highlighter.removeHighlights(highlighter.highlights);
Demo: http://jsfiddle.net/8pMEt/1/
I'm going to add a removeAllHighlights() method that will do the same thing.
One thing that the documentation doesn't make clear is that highlighting is designed to work on static DOMs, or at least DOMs with text content that doesn't change. Changing the DOM after highlights have been created could obviously throw character offsets off and the whole thing falls down.
I solved this problem by re-creating the highlighter prior to each highlightSelection call. I don't know why this works, but the highlighter must store some data regarding previous highlights that is uses for highlight merging or something.
The code in my question can be changed as follows to solve the problem:
//called on document load
rangy.init();
cssApplier = rangy.createCssClassApplier(highlightClassName, { normalize: true });
//called on "Highlight" button click
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
highlighter.highlightSelection(highlightClassName, selection);
I have been working on the last bit of my php + ajax based datagrid project.Everything works as I designed except one thing : I cannot stop user opening multiple selection boxes...
Go my research page and use username "ChenxiMao" and password "accedo" to login(without double quotes).
Note that perhaps the images used in this datagrid would not be displayed when page is loaded for the first time(weird, I am trying to fix this, browser incompatibilities, perhaps).
If you double click on one cell in the "CONSULTANT" column, a html select box would be displayed, you can select one consultant to assign him to this task or unassign the consultant from this task. No problem for this.
The problem is : when user leaves this selection box OPEN, he/she can still open another selection box... My jquery code cannot stop people from opening multiple selection boxes.
You can ctrl-U to see the source code on this page, and check the content inside the "gridview-helper.js" for what I have been done.
I want to let user only open a single selection box. When he/she leaves the cell, the selection box should be closed, without changing the html inside...
Puzzled, screwed up for this afternoon...
Thanks for any suggestons in advance!
JavaScript is single-threaded, so you can add a mutex variable and check its value before opening a new select box.
At the top of gridview-helper.js:
var is_choice_visible = false;
In your double-click handler:
$(this).dblclick(function()
{
if (is_choice_visible)
return;
is_choice_visible = true;
...
For your select box, add an onblur handler which sets is_choice_visible back to false and deletes itself.
Unrelated tip: Growing a string in a loop is slow on older versions of Internet Explorer. It's more efficient to append to an array and join the array, e.g.:
var html = ["<select>..."];
for (var i in consultantnames)
{
html.push("<option>...</option>");
}
html.push("</select>");
return html.join("");
Have you tried using the onmouseout event on the cell, and removing the child dropdown box element if mouse out is triggered? Seems that should work.
I'm working on implementing sprited buttons in Stackoverflow's beloved WMD markdown editor and I've run into an odd bug. On all versions of IE, the selected text is lost upon button clicks, so, say, highlighting a block of text and clicking the code button acts like you placed the cursor at the end of the selection and clicked the button.
e.g. highlighting this:
This
Is
Code
and clicking the code button give you:
This
Is
Code`enter code here`
What's really weird is that I left the original non-sprited button bar in and that works just fine. In fact ALL buttons and keyboard shortcuts code use the same doClick(button) function!
Old-style non-sprited buttons: OK
Keyboard shortcuts: OK
Sprited buttons in non-IE browsers: OK
Sprited buttons in IE: WTF
I've isolated the problem down to a call to selection.createRange() which finds nothing only when the sprited button is clicked. I've tried screwing around with focus()ing and making sure as little as possible happens before the doClick() but no joy. The keyboard shortcuts seem to work because the focus is never lost from the input textarea. Can anyone think of a hack that will let me somehow collect the selected text in IE?
The onclick handler looks like this:
button.onmouseout = function(){
this.style.backgroundPosition = this.XShift + " " + normalYShift;
};
button.onclick = function() {
if (this.onmouseout) {
this.onmouseout();
}
doClick(this);
}
I've tried moving the onmouseout call to after the doClick in case that was causing a loss of focus but that's not the problem.
EDIT:
The only thing that seems to be different is that, in the original button code, you are clicking on an image. In the sprited code, you are clicking on a list item <li> with a background image set. Perhaps it's trying to select the non-existent text in my list item?
/EDIT
Actual code is located in my wmd repository on git in the button-cleanup branch.
If you revert to the 0d6d1b32bb42a6bd1d4ac4e409a19fdfe8f1ffcc commit you can see both button bars. The top one is sprited and exhibits the weird behavior. The bottom one contains the remnants of the original button bar and works fine. The suspect code is in the setInputAreaSelectionStartEnd() function in the TextareaState object.
One last thing I should mention is that, for the time being, I'm trying to keep the control in pure Javascript so I'd like to avoid fixing this with an external library like jQuery if that's possible.
Thanks for your help!
I know what the answer to my own question is.
The sprited buttons are implemented using an HTML list and CSS, where all the list items have a background image. The background image is moved around using CSS to show different buttons and states (like mouseover highlights). Standard CSS button spriting stuff.
This works fine in IE with one exception: IE tries to select the empty list text when you click on the background image "button". The selection in the input textarea goes away and the current selection (which will be returned by document.selection.createRange()) is moved to the empty text in the list item.
The fix for this is simple - I created a variable to cache the selection and a flag. In IE I cache the selection and set the flag in a mousedown event handler. In the text processing, I check for the presence of the flag - if it's set I use the cached range instead of querying document.selection.createRange().
Here are some code snippets:
wmd.ieCachedRange = null;
wmd.ieRetardedClick = false;
if(global.isIE) {
button.onmousedown = function() {
wmd.ieRetardedClick = true;
wmd.ieCachedRange = document.selection.createRange();
};
}
var range;
if(wmd.ieRetardedClick && wmd.ieCachedRange) {
range = wmd.ieCachedRange;
wmd.ieRetardedClick = false;
}
else {
range = doc.selection.createRange();
}
The solution is only a few lines of code and avoids messing around with the DOM and potentially creating layout engine issues.
Thanks for your help, Cristoph. I came up with the answer while thinking and googling about your answer.
You have to blur() a button before IE can select anything else on a page.
Can you provide a minimal example (only containing relevant code) which reproduces the bug?