There are numerous WYSIWYG editors available on the internet, but I'm yet to find one that implements some form of drag-n-drop implementation.
It is easy to create one's own editor, but I want to the user to be able to drag elements (ie. tokens) from outside the editable area and have them drop it at a location of their choice inside the editable area.
It is easy to inject html at a specific location of an editable element, but how do one determine where the caret should be when the user is dragging a DIV over some element in the editable area. To better illustrate what I'm trying to explain, see the following scenario.
The editable area (either an IFRAME in edit mode or a DIV with its contentEditable attribute set to true) already contains the following text:
"Dear , please take note of ...."
The user now drags an element representing some token from a list of elements, over the editable area, moving the cursor over the text until the caret appear just before the comma (,) in the text as shown above. When the user releases the mouse button at that location, HTML will be injected which could result in something like this:
"Dear {UserFirstName}, please take note of ...".
I do not know if anyone has ever done anything similar to this, or at least know of how one would go about doing this using JavaScript.
Any help will be greatly appreciated.
Here is my approach to solving the issue of custom drag elements on editable elements. The big issue is that one cannot determine the text offset of the mouse cursor when hovering over the editable element. I have tried faking a mouse click to set the caret at the desired position but that did not work. Even if it did, one would not visually see the placement of the caret while dragging, but only the resulting drop.
Since one can bind mouse-over events to elements and not text-nodes, one can set the editable element to be temporarily un-editable. Find all elements and wrap each text-node in a span as to not breaking the flow of the text. Each span should be given a classname so we can find them again.
After the wrapping, one should again find all the wrapped text-nodes and wrap each character with another span with a classname that one can find them again.
Using event delegation one can add an event to the main editable element that will apply a style to each character span that will display the caret, a blinking GIF image as a background.
Again, using event delegation, one should add an event for the mouse-up event (drop event) on each character. One can now determine the offset using the character span's position (offset) within its parent (wrapped text-node). One can now undo all the wrapping, keeping a reference to the calculated offset and while undoing the wrapping keeping a reference to the applicable text-node.
Using the range & selection objects of the browser, one can now set the selection using the calculated offset to the applicable text-node and inject the required HTML at the newly set selection (caret position), et viola!
Here follows a snippet using jQuery that will find textnodes, wrap them:
editElement.find("*:not(.text-node)").contents().filter(function(){
return this.nodeType != 1;
}).wrap("<span class=\"text-node\"/>");
To find each text-node and wrap each character, use:
editElement.find(".text-node").each(function()
{
var textnode = $(this), text = textnode.text(), result = [];
for (var i = 0; i < text.length; i++) result.push(text.substr(i, 1));
textnode.html("<span class=\"char\">"
+ result.join("</span><span class=\"char\">") + "</span>");
});
To undo the wrapping:
editElement.find(".text-node").each(function()
{
this.parentNode.replaceChild(document.createTextNode($(this).text()), this);
});
Hope this approach helps those having similar challenges
If I understand what you're saying, then this is just basic drag and drop. The solution below should be close to the best answer for FIREFOX. I'm sure there are different functions for IE. Go to http://www.html5rocks.com/en/tutorials/dnd/basics/ for more help.
Set the "draggable" attribute of the object you want to drag, and set the object's "ondragstart" method to "dragStartHandler" or whatever your function is called.
// You can set this to 'text/plain' if you don't want to insert HTML content
var internalDNDType = 'text/html';
function dragStartHandler(event) {
// This is whatever html data you want to insert.
var textContent = "<b>"+userFirstName+"</b>";
event.dataTransfer.setData(internalDNDType, textContent);
event.dataTransfer.effectAllowed = 'copy';
}
function dragEnterHandler(event)
{
event.preventDefault();
return false;
}
function dragOverHandler(event)
{
event.preventDefault();
return false;
}
function dropHandler(event) {
event.preventDefault();
event.stopPropogation();
return false;
}
Currently an HTML5 API is being developed to do this, but unfortunately IE doesn't support it. Edit: Apparently IE actually does support drag and drop, but I'm not very familiar with how it works. Try Googling "IE drag and drop".
Try looking at these sites:
https://developer.mozilla.org/en/DragDrop/Drag_and_Drop
http://html5doctor.com/native-drag-and-drop/
http://www.thebuzzmedia.com/html5-drag-and-drop-and-file-api-tutorial/
http://www.webreference.com/programming/javascript/dragdropie/ (Drag and Drop in IE)
Related
If a contenteditable div's contents are already bold, then execCommand("bold") will remove the bold style.
That's great in normal circumstances, but I have a situation where I'd like to loop through a bunch of contenteditable divs and set any non-bold text to bold, sort of a way to style multiple rich text elements at once, using the proper or or 'font-weight' whatevers that execCommand uses.
I can do it a hard way by scrutinizing each node in each div in isolation, but I want to make sure there isn't a simpler way first.
It depends on how complex the editor you are making is. If you don't want to allow normal text inside a bold region, it may be enough to test only the deepest containers of editable texts, so one element for each of the editable divs.
Here's an example,
function bold(node) {
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
if (!isBold(getDeepestContainer(selection.anchorNode)))
document.execCommand("bold");
selection.removeAllRanges();
node.blur();
}
function getDeepestContainer(node) {
var result = node;
while (result.childNodes.length === 1) {
result = result.firstChild;
}
if (result instanceof Element)
return result;
return result.parentElement;
}
http://codepen.io/anon/pen/NqZQzR?editors=101
Okay, I couldn't find an easy way, so I did it the hard way.
I already had a function that copied the contents of a single element to a working space div with clean CSS, and then recursed through each node within that working space. For each text node, it would loop through each style I'm looking for and see if that style is set. If it's NOT, it would clear an "every node has this style" flag set before its run. So I ended up with a list of every style set for every node.
The first step for this current task was to identify which styles applied to all selected elements. So I took that function and created a modified version that first added each element's contents to the working space div. Then it ran the normal recursive walk through the working space, basically treating each separate element as if they're part of the same complex rich text string.
That gave me the status of each style, like whether everything was bold and thus the bold button should show as pressed.
I also added an array for each style. As I recursed through the elements, tracking their individual "every node has this style" value, I would come up with a list of elements where every node had the style, and thus execcommand("bold") would toggle that element.
So now, for each style (like for the bold button), I knew whether it would toggle any elements, or all elements. If it would toggle all elements, or no elements, then that's fine because the behavior's consistent. But if it toggled a subset of elements (if "elements_that_would_toggle" array length for a style was greater than zero but less than the total_elements count), then I would ignore those toggle-able elements on the first button click.
At the end of that click processing, I then blank out those arrays for each style, thus reverting the behavior across the board to toggling, because now every element has been set to the same status.
It's sort of a disgusting approach, but it's controlled and consistent, and it works really well.
And it only took a day to get working.
I want to ask question is it something possible with text range or
other approach. I have a text. For example like this:
<div id="test">abcdefghij</div>
I use text range to highlight fragment of text with yellow color.
(I use example posted into SO). It is possible to add event listener
for mouser over on highlighted text? I want to do something when user
mouseover on 'cdefg'. Here is working jsfiddle.
http://jsfiddle.net/8mdX4/673/
I would appreciate any ideas and recommendations.
Best regards.
Edit:
I want to share the approach that I have usied after the topic.
I'm using focusNode property of selection because commonAncestorContainer
get container of all nodes - not that node currently selected.
Here is demo:
http://jsfiddle.net/zono/Q3ptC/2/
Mouseover on yellow text displaing tooltip (content of title property).
You are using document.execCommand a lot in your code to style elements, this will most likely create a <span> but that's up to the browser. When doing so, each browser will create elements in your target text as it sees fit. To my knowledge, there is no event management command available when using execCommand, so using this technique will make specific event assignments difficult to these elements.
If I were to write your code from scratch, I would use an approach with window.getSelection and create the elements myself, so that I could add the events as I see fit.
Another alternative is just to add window.getSelection() once you know that execCommand has created an element, find the selection's parent, and add the event to the parent node. Like this:
/*At a point where I know a selection is made*/
var sel = window.getSelection();
if (sel.rangeCount) {
parent = sel.getRangeAt(0).commonAncestorContainer
if (parent.nodeType != 1) {
parent = parent.parentNode
}
parent.onclick = function() {
alert("lookie lookie");
}
}
Your code updated with my example:
http://jsfiddle.net/8mdX4/680/
I've noticed that, on some platforms, it can be very difficult to click text that is being continually modified. The click event doesn't always trigger when the user clicks elements that are being modified resulting in an unresponsive interface.
For example, see this http://jsfiddle.net/hq9Rh/2/
In that example, there are four elements:
Replaces its text value every 100th millisecond and is almost impossible to click.
Replaces its text with an identical text. Is equally difficult to click.
Only writes to the element if the text is different. It's much more responsive to clicks.
Static, and perfectly clickable.
Here's the code to update the four elements:
setInterval(updateList, 100);
var iteration = 0;
function updateList(){
iteration++;
$(".updating").text(iteration);
$(".replaceSameText").text("SameText");
var oldText = $(".replaceDifferentText").text();
var newText = "SameText";
if(oldText != newText)
$(".replaceDifferentText").text(newText);
}
What is going on? Is there any way to fix this?
I'm running Chrome 32 on Windows.
It's because the text node within the element you're updating keeps getting destroyed and recreated, so the click may miss the text node.
You can avoid it by updating the value of the text node, rather than destroying and recreating it:
$(".replaceSameText")[0].firstChild.nodeValue = "SameText";
Updated Fiddle
I'm looking at implementing a simple 2 button toggle on a webpage to switch some selected text between having a H1 heading and H2. The surroundContents method works great, however I'm encountering a problem when trying to replace an existing parent tag element node. I've played around with all sorts of ways trying to do this but not had much success.
The basic functions are below. Using the same selected text and running both of these functions one after the other will result on some output such as the following:
After selecting text of "test text" and then selecting the H1 option:
<h1>test text</h1>
If the same text is selected again and this time the H2 option pressed: <h1><h2>test text</h2></h1>
function surroundSelectedWithH1() {
var element = document.createElement("h1");
// removed code to setup range to save space
if (range) {
range.surroundContents(element);
}
}
function surroundSelectedWithH2() {
var element = document.createElement("h2");
// removed code to setup range to save space
if (range) {
range.surroundContents(element);
}
}
This is fine, and what would be expected, but I'm really looking for a way to remove the original parent heading element so that the heading elements do not become nested (for example - the text is surrounded by either h1 or h2, not both). I did experiment accessing the parentNode etc but did not manage to get this approach functional. I've tried looking at the following parentElement suggestion Getting the parent node for selected text with rangy library however I wasn't able to have rangy write the changed parent element back to the DOM or have a satisfactory way of determining where in the DOM the object was in order to replace it. It quickly became an unwieldy approach and there must be a better option.
I do know that the rangy CssApplier module can handle this situation but I need to work with actual elements and not css.
I also noticed that on the raptor editor which uses rangy for a text editor implementation suffers from the exact same problem when applying headings: http://www.raptor-editor.com/demo
This question was also relevant but this particular element problem can't be handled with execCommand as far as I'm aware - Javascript: how to un-surroundContents range
Any help or advice graciously received.
Try with:
highlighter.unhighlightSelection()
How do I set the cursor on a div-element in javascript?
I have a form(div-element not input) with a textstring attached on it. I want the textstring removed and the cursor set at the beginning of the element onclick.
I have removed the content in the div-element with onclick event with: divElement.innerHTML = '';
Now I want the cursor to be set?
If you mean the mouse pointer, use the CSS cursor style like this:
#mydiv {
cursor: help;
}
There are a whole load of standard options you can use. You can also define a graphic to use as the pointer (though this has cross-browser compatibility issues).
See this page on Quirksmode for more info.
Similarly, if you want to do it dynamically in Javascript, then just set the object's style:
document.getElementById('mydiv').style.cursor = 'help';
If by 'cursor', you mean the text cursor (aka the caret), I presume what you're really asking is how to make a div into an editable content box.
What you need is to set the contentEditable attribute on your div.
If you want it editable from the start, just include it in your HTML code:
<div contentEditable="true">....</div>
If you want to switch it on/off, you can set it in javascript:
mydiv.contentEditable="true"
However, the only time I can think of when it's better to use contentEditable rather than a textarea is if you're writing a WYSIWYG HTML editor.
Most of the rest of the time I would say it's probably preferable to use a <textarea>. You can style it to look like the rest of your page, and you can make it readonly or disabled when you don't want it changed. But it is much easier to work with in a form and in Javascript. The problem with using a div is that it can contain other html tags, which may affect how it works, and will likely open you up to security problems if you make it directly editable.
divElement.style.cursor = 'whatever';
If you want to move the cursor to be over the divElement, then you can't.