jQuery .click() doesn't fire when element is being modified with .text() - javascript

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

Related

Where could `appendTo()` be usable with an `htmlString`?

The API docs for appendTo list the method being able to select an HTML string for a target.
However, there seems to be no use to this since the set still includes the original elements, and the HTML string seems not to have been added anywhere in the DOM nor do I see a circumstance where it could be available.
var b = button.appendTo('<div>').appendTo('body');
b is a button, and yet it is not wrapped in a div or anything.
You can see this at http://jsfiddle.net/0dgLe5sj/
Where would it be useful to append to a HTML string (which doesn't yet exist on the page)?
appendTo() returns the item being appended.
So your code is:
var btn = button.appendTo('<div>');
btn.appendTo('body');
As you can see, you move it inside a div, then immediately move it inside the body. So you when you look at it at the end, it's inside the body.
Perhaps you meant:
var b = button.appendTo($('<div>').appendTo('body'));
which will append a div to the body and then append the btn to that div.
Updated fiddle: http://jsfiddle.net/0dgLe5sj/8/
or, if you wanted to add to the div first:
var b = button.appendTo("<div>");
b.parent().appendTo("body")
but if you combine it into a single line, you can't get the button back into the variable using .appendTo as you're adding the div to the body so you're going to get the div or the body back.
To address the 'where would this be useful part':
Being able to create detached DOM elements is extremely useful for parsing HTML strings and can also be used to 'batch' up some changes without forcing page redraws between.
Moving one button to a detached div and the back to the body doesn't have a lot of point, but it proves the principles.

Can execCommand or a similar function be used to set, rather than to toggle, a simple style like bold?

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.

Removing child elements

I have an AJAX chat application that constantly gets new data and appends it to a div (#container) in the form of another div (.child). Multiple .child's can be inserted per second, but I only want to keep the most recent 10. Every time a download occurs, I call the following function:
function cleanup(){
var current = $('#container');
var allData = current.children();
if(allData.length > 10){
for(var i = 0;i<allData.length-10;i++){
allData[i].remove();
}
}
}
This works, but it lags horrendously. I have to switch my current tab just to see the css work correctly. Am I doing something wrong?
I cannot change the data flow, as the chat depends on getting all the data that is sent. I am just looking for the most efficient way of deleting old elements.
Example:
If I had 30 children in my div, the first 20 children would be .remove()'d and only the last 10 would remain.
Rather than letting some code add divs and the cleanup function clean it up afterwards - which if I understand you correctly, could add a whole bunch of children, not just one - why not have effectively a queue of length 10 in memory, and keep pushing things on (and popping them off the back once you reach 10+ items), and then set those children on your #container periodically.
This way you are always going to get the latest 10 elements, but you can update the container at a rate that makes sense (and therefore reflow the visual document a a rate that makes sense).
This could be on an interval, or you could even do it every time you process a message - but the point is, you are not adding to the document, reflowing it, then removing from it again. That seems like an inefficient way to approach the problem.
You can use :lt() selector to limit to target last divs( as :lt selector is zero index based):
$('#container .child:lt('+$('.child').length-11+')').remove();
also its better to replace the content rather than appending new one.
CSS Solution:
#container:nth-of-type(10) ~ #container {
display: none;
}

Javascript Loop <UL> rotate a list with one click

I have the following function below that is supposed to rotate/loop through a list of 4 items, which does work through an onclick. However, the user has to click twice for the loop to perform.
function rotateList(){
var list = document.getElementById("arrange");
var first = list.removeChild(list.firstChild);
list.appendChild(first);
}
Does anyone know how to make the above work with one click?
Many thanks in advance.
function rotateList() {
var list = document.getElementById("arrange");
list.appendChild(list.firstElementChild);
}
Note that any DOM node can only ever exist once in the document. If you append it in one place, it gets removed in its original location.
This way appending the first child node has the desired effect.
firstElementChild gives you the "real" first child, whereas firstChild also gives you the (blank) text node that can occur between elements, which is what's making you click twice. However, firstElementChild is not supported before IE9. Other browsers are no problem.
http://jsfiddle.net/Vb8dY/1/

Drag-n-Drop on contentEditable elements

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)

Categories

Resources