Issues with Javascript Selection API with Chrome (setBaseAndExtent method) - javascript

I am facing a very strange issue with the Selection API (https://developer.mozilla.org/en-US/docs/Web/API/Selection) on Chrome. I think I am using the API correctly, and tried different things but I always end up with the same outcome.
Considering the example HTML page below, with some static behaviour, the backwards selection doesn't work correclty: I always get the focus back to the right-most span element. (To get the Selection API running on this example, for a backwards selection, press the left key at the beginning of the right-most span element, the one with "abc..").
It doesn't seem to be something already mentioned on forums, so I must be doing something wrong but... I can't find out why. Firefox works as I expect: running that example, I get the caret and focus on the first span (the one with numbers).
Thanks a lot for your help!
<html>
<head>
</head>
<body>
<div id="parent1">
<span id="span0" contenteditable="true" onfocus="onFocus('span0')" onblur="onBlur('span0')">987654</span>
<span>+</span>
<span id="span1" contenteditable="true" onfocus="onFocus('span1')" onblur="onBlur('span1')">1234 6789</span>
<span>ยต</span>
<span id="span2" contenteditable="true"onfocus="onFocus('span2')" onblur="onBlur('span2')">abcdefg</span>
</div>
<br><br>
<button onclick="doForwardSelection()">click</button>
<script>
span0 = document.getElementById("span0");
span2 = document.getElementById("span2");
function onFocus(id){
console.log("FOCUS on " + id);
}
function onBlur(id){
console.log("BLUR on " + id);
}
function doForwardSelection(){
docSel = document.getSelection();
// remove existing selection
docSel.removeAllRanges();
// set a new forward selection
docSel.setBaseAndExtent(span0.firstChild, 1, span2.firstChild, 2);
}
function onKeyDown(event){
//console.log(event);
if(event.key == "ArrowLeft" && document.getSelection().getRangeAt(0).startOffset == 0){
event.stopImmediatePropagation();
event.stopPropagation();
event.preventDefault();
// Do the selection manually
doMultiSelection();
}
}
function doMultiSelection(){
docSel = document.getSelection();
// first focus target
span0.focus();
console.log(docSel)
// then do the extent
console.log("I call Selection.setBaseAndExtent() now...")
//docSel.setBaseAndExtent(range.endContainer, range.endOffset, range.startContainer, range.startOffset);
docSel.setBaseAndExtent(span2.firstChild, 2, span0.firstChild, 1);
console.log(docSel);
}
document.getElementById("span2").addEventListener('keydown', onKeyDown);
</script>
</body>
</html>

So, I think I understand why what I tried didn't work.
The selection I want to make isn't in a single editable element (multiple spans), and their common ancestor isn't an editable element (div).
If I changed things by setting the div parent editable (contenteditable="true"), I can do the selection as I wanted to.
Based on the documentation on MDN, it may explain why Chrome behaved the way it did with my initial example.

Related

How do I create an update function in a textarea?

If the question's cryptic to you then imagine how it is to me. I'm not even sure what to look up to begin with.
Anyway, I'm making a to-do list and have just about everything else done, but I'm supposed to:
Update the click code of adding the item to the list, not only should it add the text, it should also add a textarea and a button. (Figured this out myself.)
Give this textarea and button a CLASS. (Easy.)
Once you have appended the textarea and button, along with the to-do text to the list, you can now give the button a click handler. (Wait, doesn't it already have one?)
This click handler will get the value of the textarea (from step 1) and then replace it with the existing item. (What?)
Yes, it's help with homework; please don't throw rotten produce at me. Collaboration's allowed, as is pasting code so long as it isn't done blindly (i.e., I pull something from the web and have to ask why it still doesn't work).
Moving on, I asked the teacher what existing item he meant:
We have a button that creates list items, each of which has a button that lets you delete it. But I don't know about any "update" buttons; and why would need an extra function to replace text when each list item is already an updatable text box?
To which he said:
The reason for the extra update function is because the data in the text box does not exist in JavaScript, only in that text box, by making the update button pull the text from the text box we now have that text in JavaScript. While our simple app does not save any data, it's best practice to have data flowing from and to JavaScript, even though our app doesn't "need" it. The HTML and CSS is really only a simple interface that visitors can interact with, but JavaScript is how data is handled in the front-end.
I know the answer is just a few simple lines of code that I'll feel real dumb for not having figured out myself, but I don't want to spend hours on something so small because "suffering builds character." What can I do to make the program work as instructed? And how can I get better at figuring it out myself quickly?
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>To-Do List Front-End App</title>
<meta name="description" content="A to-do list app.">
<meta name="author" content="">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<style>
/*vvvvvvvv CSS GOES BELOW THIS COMMENT vvvvvvvv*/
#list-header {
background-color: #e6e6fa;
}
#list-content {
background-color: #bf94e4;
}
/*^^^^^^^^ CSS GOES ABOVE THIS COMMENT ^^^^^^^^*/
</style>
</head>
<body>
<!-- vvvvvvvv HTML GOES BELOW THIS COMMENT vvvvvvvv -->
<div id="list-header">
<h1>To-Do List</h1>
</div>
<div id="list-content">
<form>
<p>New Note: </p>
<textarea id="new-note"></textarea>
</form>
<button id="submit-note">Create To-Do Item</button>
<ul id="list">
</ul>
</div>
<!-- ^^^^^^^^ HTML GOES ABOVE THIS COMMENT ^^^^^^^^ -->
<script>
/*global $*/
/*vvvvvvvv JAVASCRIPT GOES BELOW THIS COMMENT vvvvvvvv*/
$(document).ready(function () {
$("#new-note").keypress(function (event) {
if (event.which == 13) { // Enters new list item when you press Return/Enter
event.preventDefault();
var noteText = $("#new-note").val();
$("#list").append("<li>" + "<textarea class='notes'>" + noteText + "</textarea>" + " <button class='remove'>Delete</button></li>");
$(".remove").click(function () { // Removes list item when you press the Delete button.
$(this).parent().remove();
});
$("#new-note").val(""); // Blanks the text area when you enter an item.
} // <- An IF statement, not a function
});
$("#submit-note").click(function () { // Enters a new list item when you click Create To-Do Item back in the HTML.
var noteText = $("#new-note").val();
$("#list").append("<li>" + "<textarea class='notes'>" + noteText + "</textarea>" + " <button class='remove'>Delete</button></li>");
$(".remove").click(function () {
$(this).parent().remove();
});
$("#new-note").val("");
});
});
/*^^^^^^^^ JAVASCRIPT GOES ABOVE THIS COMMENT ^^^^^^^^*/
</script>
</body>
</html>
Assuming your teacher simply wants you to grab the value of the textarea by clicking a button, you would do something like this in javascript. I'll leave you up to thinking of how to do it with jQuery.
The html
<textarea id="textarea">Blah blah blah</textarea>
<button id="btn">Get textarea</button>
The JS
var textarea = document.getElementById('textarea');
var btn = document.getElementById('btn');
btn.addEventListener('click', function () {
console.log(textarea.value);
})
// The better way to do it as requiring the user to click a button is just....rediculous
// but perhaps you haven't learned about events yet.
textarea.addEventListener('change', function (e) {
console.log(e.target.value);
})
https://jsfiddle.net/jamesbrndwgn/f9zb5rm7/4/
EDIT
Based on clearer requirements in comment.
Your#1 - Add a text area, and add an update button;
Your#2 - attach click handler to update button;
Your#3 - this handler will take the value of the text area created in step
1);
Extra step - Let the user modity the text.
Your#4 - use this value and replace the existing list item text;
Your#5 - the update should empty out text area.
Here is the code you need.
$(document).ready(function () {
$("#new-note").keypress(function (event) {
if (event.which == 13) {
event.preventDefault();
var noteText = $("#new-note").val();
var listItem = "<li>" + noteText + " <button class='remove'>Delete</button><button class='update'>Update</button></li>";
$("#list").append(listItem);
$("#new-note").val("");
}
});
$("#submit-note").click(function () {
var noteText = $("#new-note").val();
var listItem = "<li>" + noteText + " <button class='remove'>Delete</button><button class='update'>Update</button></li>"; // Your #1
$("#list").append(listItem);
$("#new-note").val("");
});
//
// Button handlers using delegation (since those are dynamically created)
//
// REMOVE
$("#list").on("click",".remove",function () {
$(this).parent().remove();
});
// UPDATE
$("#list").on("click",".update",function () { // Your #2
var thisListItem = $(this).parent();
thisListItem.find("button").remove();
var thisListText = thisListItem.text().trim(); // Your #3
var newTextarea = $("<textarea>").text(thisListText);
thisListItem.empty().append(newTextarea).append(" <button class='updateOk'>Ok</button>"); // Needs two append. One for the jquery object and one for a string
});
// UPDATE OK
$("#list").on("click",".updateOk",function () {
var thisListItem = $(this).parent();
var thetextareaVal = thisListItem.find("textarea").val();
thisListItem.empty().append(thetextareaVal + " <button class='remove'>Delete</button><button class='update'>Update</button>"); // Your #4
});
}); // END ready
In the code above, you'll find "Your #n" 1 to 4. The #5 is achieved by .empty() two places, before and after user action.
The "extra step" occurs between the two update handlers... Because that needs an extra "ok" button... That's why it's 2 handlers.
That is all about event handlers...
You are dealing with dynamically created buttons, so again... Make sure to do at least the minimal reading about delegation.
Delegation in short (and in my words): Normally, the code will only apply only on existing elements (in the DOM) when the script is parsed. The only way around for this big problem is delegation.... That is about to attach the handlers needed for future element on a STATIC parent... The result is this static element WILL "distribute" the event on its matching child, if it has any when the event occurs.
To define an event handler within another is a bad practice. Please stop that now and forever. If you continue to do so anyway because it works... You will sooner or later encounter some strange behaviors sometimes hard to narrow down. Examples
And never loop over a handler definition. Loop over the HTML markup to add and give it classes already defined once. -- One class, one handler. Many events on a class can be one handler.
Beyond that, it only about to get/set values and replace/overwite/create elements.
A working demo is avalable here: Your JsFiddle updated.

Select and Copy input text onclick?

I've actually seen a few questions about this, most of them are from at least 5 or 6 years ago.
I want to have an input box:
<input id="copy-text" type="text" value="Click this text!">
Here's the JavaScript I've been trying to work with:
document.getElementById("copy-text").onclick = function() {
this.select();
execCommand('copy');
alert('This is a test...');
}
I know my code doesn't work. If I remove execCommand('copy'); then the alert() pops up, but it seems to be hitting an error at that line. I've tried making it this.execCommand('copy'); as well, not really sure what to do here.
Fiddle: https://jsfiddle.net/6v24k4sk/
The idea is that I want the user to click the input box, it will select all the text, and then copy it to the clipboard.
Any ideas?
You should put a document. in front of the execCommand.
document.getElementById("copy-text").onclick = function() {
this.select();
document.execCommand('copy');
alert('This is a test...');
}
Here you can find a working example:
https://jsfiddle.net/9q3c1k20/
edit:
The function also returns whether this functionality is supported in the browser. I think you should check the value, because execCommand still has no final specification and is therefore not guaranteed to work in all browsers.
Use this function with your copy_btn (without onclick function).
function function_name() {
var c = document.getElementById("copy");
c.select();
document.execCommand('copy');
}

replacing focused span by input with focus

This question gives more details about context and motivation. Notice that I am on Linux and cares only about recent Firefox (at least 38) & Chrome.
Basically, I want to edit some AST interactively with a web interface.
In the MELT monitor on github commit 7b869102332bd29309 I would like to have a focusable <span> (which has tabindex='0' so can get focus) which, when I press the spacebar, is replaced by some <input type='text'/> which has already the focus...
I am not using contenteditable anymore, see this, because it looks that contenteditable is really messy (and don't work as well as I want)!
I've made a jsfiddle containing a simple example with:
<div id='mydiv_id'>
*before* <span id='myspan_id' tabindex='0'>in span</span> !after!
</div>
and the JQuery 2 code:
var $mydiv = null;
var $myspan = null;
$(document).ready(function() {
$myspan = $('#myspan_id');
$mydiv = $('#mydiv_id');
console.log (" mydiv=", $mydiv, " myspan=", $myspan);
$myspan.on("keypress", function (ev) {
console.log ("myspan keypress ev=", ev);
if (ev.keyCode === 32 || ev.key === ' ') {
console.log ("myspan got space ev=", ev);
var myinput =
$("<input type='text' id='myinput_id' width='16' class='myinp_cl'/>");
$myspan.replaceWith(myinput);
myinput.focus();
console.log ("myspan replaced with myinput=", myinput);
}
});
console.log (" mydiv=", $mydiv, " myspan=", $myspan);
});
but it does not work as expected.
Or perhaps a focused <span> element cannot be replaced (on space keypress) with a focused <input> element?
(in the MELT monitor, I'm using jquery 2.1.4 embedded inside)
addenda
the updated jsfiddle works (sorry for my mistake, it needs jquery 2.1.4, with which it is working -and I regret having asked the question here), and since the Javascript of the MELT monitor is AJAX generated, I am not seeing every error in the console (see this question).
NB: In commit df3bdf3984bc202f I now have a case when, after programmatically moving the focus to a newly created <input>, $(':focus') is an empty object, and document.activeElement is the <body> itself....
I am now tempted to delete this question, it is probably useless...
As i saw your fiddle, what i noticed:
You are using .on() method.
.on() is not introduced in jQuery version 1.6.x but 1.7+.
So you can change to this:
$myspan.keypress(function (ev) {
Updated fiddle.
cares only about recent Firefox (at least 38) & Chrome.
So, best to upgrade the jQuery version to latest one as possible and take the version 2.x tree.
You can bind focus event on the span like this:
var $mydiv = null;
var $myspan = null;
$(document).ready(function() {
$myspan = $('#myspan_id');
$mydiv = $('#mydiv_id');
$myspan.on('focus', function() { var myinput =
$("<input type='text' id='myinput_id' width='16' class='myinp_cl'/>");
$myspan.replaceWith(myinput);
myinput.focus();
});
});
Demo:
http://jsfiddle.net/wLf0e3cs/

My firefox addon code is destroying navigation bar

I want to add a toolbar button before the firefox search container in my addon. But it is completely clearing my navigation bar.
I suspect the offending code is due to an empty array or something but i cant be certain.
//insert before search container
if(navBar && navBar.currentSet.indexOf("mybutton-id")== -1 )//navBar exist and our button doesnt
{
var arrayCurrentSet= navBar.currentSet.split(',');
var arrayFinalSet= [];//empty at first
if(arrayCurrentSet.indexOf("search-container") != -1)//if search-container exists in current set
{
// check item by item in current set
var i= null;
while(i=arrayCurrentSet.shift() != undefined)
{
if(i == "search-container")//"search-container" found !!
{
/*insert our button after it but only if our button does not already exist*/
if(arrayFinalSet.indexOf("mybutton-id") == -1) arrayFinalSet.push("mybutton-id");
}
arrayFinalSet.push(i);
dump("arrayFinalSet "+ i);
}
}
else //damn search-container doesnt exist
{
arrayFinalSet= arrayCurrentSet;
arrayFinalSet.push("mybutton-id");//add our button to the end of whatever is available in nav bar
}
//set new navBar
navBar.currentSet= arrayFinalSet.join(',');
}
The full code is available
https://builder.addons.mozilla.org/addon/1052494/latest/
http://jsfiddle.net/CQ4wA/
I'm not too sure why the navigation bar has been removed, but I think it would be better to approach this from a different angle. Rather than messing around with an array of strings, try using DOM methods instead.
e.g.
var sC=navBar.querySelector("#search-container");
navBar.insertBefore(btn, sC);
The code you have here seems to work - but the toolbar needs to find your button somehow. Your current code doesn't even insert the button into the document, meaning that the toolbar has no chance to find it by its ID. It should be in the toolbar palette palette however, the palette also determines which buttons the user can choose from when customizing the toolbar. So you probably want to do something like this first:
var toolbox = navBar.toolbox;
toolbox.palette.appendChild(btn);
You might also want to simplify your code:
var arrayCurrentSet = navBar.currentSet.split(',');
var insertionPoint = arrayCurrentSet.indexOf("search-container");
if (insertionPoint >= 0)
arrayCurrentSet.splice(insertionPoint, 0, "mybutton-id");
else
arrayCurrentSet.push("mybutton-id");
navBar.currentSet = arrayCurrentSet.join(',');
And finally, you probably want to make the browser remember the current button set, it doesn't happen automatically:
document.persist(navBar.id, "currentset");
Note that the button that will be inserted into the toolbar is not the same as the button you added to the palette - the toolbar code clones the button, with one copy being left in the palette. So event listeners added via addEventListener will sadly be lost. It is better to use a command attribute and insert a <command> element into the document that you will attach your listener to.
Note: in XUL you usually want the command and not the click event - unless you are really interested in mouse clicks only and want to ignore the button being triggered by keyboard or other means.

Image resize handles in IE's contenteditable mode

How can I selectively turn off image resize handles in IE 7+'s contenteditable mode? I've tried setting the image's contentEditable to false and its onresizestart to "return false" to no avail.
I'm using tinyMCE.
Set unselectable="on" for your images. Works for older IEs, but is lately deprecated.
I was using the advimagescale plugin in tinyMCE to stop all images from being resized.
However, I found it stopped images from being dragged and dropped into an editor instance.
I found I could strip the size attributes on mouseup using:
setup : function(ed) {
ed.onMouseUp.add(function(ed, e) {
if (e.target.nodeName == "IMG") {
e.target.removeAttribute("width");
e.target.removeAttribute("height");
}
});
}
I would dearly love to get rid of those damn handles though.
This took pain, time and luck to find: You want the 'controlselect' event to remove the resize handles in IE.
element.oncontrolselect = function () { return false; };
The above line of code worked for me (caveat: Not using TinyMCE, but then, this seems to be a contentEditable headache, not a specific TinyMCE one). You need to set this handler on any element you want to not have these drag handles (in my case, images).
You can disable the function of the handles by defining a behaviour file. I couldn't find anything which would let you hide the handles. The result of the code below is that dragging the handles has no effect.
noresize.htc:
<public:component lightweight="true">
<script language="javascript">
function CancelEvent()
{
return false ;
}
this.onresizestart = CancelEvent ;
this.onbeforeeditfocus = CancelEvent ;
</script>
</public:component>
Then in css:
img.noresize {
behaviour:url(/css/noresize.htc);
}
Note that you'll need to get that url() path right. Add the css class to the images you want to selectively disable.
This article has an alternative .htc file which didn't work for me:
http://nickw101.wordpress.com/2011/02/25/disabling-image-resizing-in-ie-contenteditable-elements/
Just the best fix ever:
<div contenteditable="true">
<label contenteditable="false"><input/></label>
</div>
or any html element that wraps your input/img
Works like a charm on IE11 with img too.
Often you will not want the resize functionnality for any element to be accessible. You can set the onresizestart handler of the contenteditable container so it cancels any resize.
That is:
<div id="editor" contenteditable="true" onresizestart="return false;">
<img src="..." />
</div>
Or with JS:
var editor = document.getElementById('editor');
editor.onresizestart=function(){return false;}
That will not hide the handles but the users will not be able to resize the element, whatever type it is.
Hope this helps!

Categories

Resources