I have a book type project. I have functions that all users to add annotations to pages, and if necessary drag and drop to make adjustments in their position. The d & d is working successfully. I have a mouseup event bound to the drag div to capture the new location such as left , top etc...
This works fine, later I trigger a function to index all the divs (annotations) present on the page based on class "rect"
$.each('.rect',function(){
addToIndex(this)
})
I store all the div properties in an index so they can be recreated when the user returns to the page. This works fine if the div has not been dragged. All properties are correct and can be recreated on the page later.
However if the div has been dragged the div still retains the original properties not the new dragged-to location.
For some reason the dragged div is not being updated in the DOM.
I can trace the properties for the div after dropping and they are updated
otherwise the div would not be displayed where it was dropped.
How can I force the dropped object to be updated in the DOM using jQuery ?
Do I have to work directly in the DOM in this case instead of jQuery ?
Okay here is more detail
Once the drag object is dropped I capture position and then want to add this as data to the element...snippet here
// capturing the literal position of element after dragging
var lx = $(obj).position().left - offPos.left
var uy = $(obj).position().top
var ux = $(obj).position().left - offPos.left + $(obj).width()
var ly = $(obj).position().top + $(obj).height()
var rb = new Array(lx,uy,ux-lx,ly-uy)
var rbstr = rb.join(",")
// before dragging rbstr value is 593,31,135,40
console.log(rbstr) // outputs 948,13,135,40
//updating the element with new data here
$(obj).attr("data-rb",rbstr)
console.log(rbstr) // outputs 948,13,135,40 which is correct
// everything here indicates the element has been updated
But when I try to read all the elements with the same class to store their properties the updated element's properties are not updated but just as they where before the drag and drop.
$.each('.rect',function(){
var allData = $(this).data()
console.log($(this).data())
// is correct for all elements except the dragged element
pageAnnots[thisDoc.currentPage].push(allData)
})
Hope this gives more detail about the problem.Note I can resize the 'rect' and the data gets updated just fine.
Seems to me when the 'rect' is dropped it is a proxy, and updating the data is not happening on the original dragged 'rect'
UPDATE: I found the problem - I was assigning data using
$(this).attr('data-x',n)
but then retrieving the data using
$(this)data()
Apparently if you assign data using 'attr' you have to likewise retrieve it using the same 'attr' method
HTML
<body>
<div id="draggable" class="ui-widget-content">
<p>Drag me around</p>
</div>
<input type="Button" name="" value="nextPage" id="next">
</body>
jQuery
$(function() {
$("#draggable").draggable();
$("#next").on("click",function() {
var top = $("#draggable").css("top");
var bottom = $("#draggable").css("bottom");
var left = $("#draggable").css("left");
var right = $("#draggable").css("right");
//send them to next page
});
});
Hope this was what you wanted. The dragable function adds a style attribute to div, if that was what you meant by update DOM.
Related
I am using drag and drop so that students can take pieces of a jumbled up image and drop them on to a grid to recompose the original image.
Both grids (jumbled up and reconstruction) allow for dropping, and everything works fine.
However, there might be cases where a student drops one object on top of another, and I want to have the code move the original object to the one recently vacated by the selected item.
I have experimented with different concepts and might be going down the wrong route but used the following for testing:
function drop(ev) {
ev.preventDefault();
var tgt = ev.dataTransfer.getData("text");
var currentContents = document.getElementById(tgt);
ev.target.appendChild(document.getElementById(tgt));
var newContents = document.getElementById(tgt);
alert("Current = " + currentContents + " - New = " + newContents);
}
The idea here was that I could simply store the element details itself, or the innerHTML into a variable, allow the drop to happen as it would normally, then assign the captured HTML to the previous div.
However, the code above only reports [object HTMLimageElement] (even though, in this case, the recipient does not contain any image data).
I tried innerHTML but simply received nothing at all.
And I haven't even begun to work out how to identify the original div that the dragged image came from.
Okay, spent some time exploring all of the values I could make sense of.
I ended up with this:
function drop(ev) {
ev.preventDefault();
var prntId = ev.target.parentNode.id;
var tgt = ev.dataTransfer.getData("text");
var tgtId = ev.target.id;
if (prntId === "targetDiv") {
ev.target.appendChild(document.getElementById(tgt));
imgPos[searchList(tgt)] = tgtId;
} else {
var thought = document.getElementById(prntId);
var orgHTML = thought.innerHTML;
var listPos = imgPos[searchList(tgt)];
thought.removeChild(thought.firstChild);
thought.appendChild(document.getElementById(tgt));
document.getElementById(listPos).innerHTML = orgHTML;
imgPos[searchList(tgtId)] = listPos;
imgPos[searchList(tgt)] = prntId;
}
}
function searchList(searchItem) {for (i = 0; i < 24; i++) {if (imgId[i] === searchItem) {return i;}}}
Not the most beautiful piece of code I have written but functional.
I identified that when the DIV was empty, the ID being returned was the DIV's ID, yet when a child was added, the ID being returned was of the image ID. I could get the HTML img tag in its entirety, so I had everything I need.
Basically, if there is no child element to the DIV, the event target.parentNode.id returns "targetDiv". I, therefore, append a child to the DIV (ID returned by the stored data in dataTransfer), and update the image list with its new position.
However, any other value is the ID of the parent DIV. I, therefore, copy the current HTML in the target DIV into a variable (orgHTML), locate the original position that the tgt image has been dragged from, place the orgHTML data in this, now vacated, DIV.
I then continue with the drop part of the process, once I have removed any children from the DIV, updating the new position of the image within the relevant list.
That is as clear as mud (I am tired and it is late), but it might help someone, one day. :)
I want to access current position that defined in variables. After that I want to make the element of that position was colored automatically when I load the page. The problem is I don't know how to access that defined position and append the color to html when its load.
I've try it in this, but it gets nothing colored when I load the page. Maybe my code was wrong and can anyone help?
https://jsfiddle.net/ax47kvu5/1/
var gigi = "P15";//id of g
var posisi = "C";//id of polygon
var kondisi = "amf";
if(kondisi=="amf"){
var group = $('polygon').parentNode().attr(gigi);
group.attr(posisi).css({fill: "#333333"});
$('polygon').html('XX');
}
https://jsfiddle.net/9n9jack8/ - In your example you didn't load jQuery, this is the first issue.$("#P15").find("#C").css({fill: "#333333"});
I have a contenteditable div as follow (| = cursor position):
<div id="mydiv" contenteditable="true">lorem ipsum <spanclass="highlight">indol|or sit</span> amet consectetur <span class='tag'>adipiscing</span> elit</div>
I would like to get the current cursor position including html tags. My code :
var offset = document.getSelection().focusOffset;
Offset is returning 5 (full text from the last tag) but i need it to handle html tags. The expected return value is 40. The code has to work with all recents browsers.
(i also checked this : window.getSelection() offset with HTML tags? but it doesn't answer my question).
Any ideas ?
Another way to do it is by adding a temporary marker in the DOM and calculating the offset from this marker. The algorithm looks for the HTML serialization of the marker (its outerHTML) within the inner serialization (the innerHTML) of the div of interest. Repeated text is not a problem with this solution.
For this to work, the marker's serialization must be unique within its div. You cannot control what users type into a field but you can control what you put into the DOM so this should not be difficult to achieve. In my example, the marker is made unique statically: by choosing a class name unlikely to cause a clash ahead of time. It would also be possible to do it dynamically, by checking the DOM and changing the class until it is unique.
I have a fiddle for it (derived from Alvaro Montoro's own fiddle). The main part is:
function getOffset() {
if ($("." + unique).length)
throw new Error("marker present in document; or the unique class is not unique");
// We could also use rangy.getSelection() but there's no reason here to do this.
var sel = document.getSelection();
if (!sel.rangeCount)
return; // No ranges.
if (!sel.isCollapsed)
return; // We work only with collapsed selections.
if (sel.rangeCount > 1)
throw new Error("can't handle multiple ranges");
var range = sel.getRangeAt(0);
var saved = rangy.serializeSelection();
// See comment below.
$mydiv[0].normalize();
range.insertNode($marker[0]);
var offset = $mydiv.html().indexOf($marker[0].outerHTML);
$marker.remove();
// Normalizing before and after ensures that the DOM is in the same shape before
// and after the insertion and removal of the marker.
$mydiv[0].normalize();
rangy.deserializeSelection(saved);
return offset;
}
As you can see, the code has to compensate for the addition and removal of the marker into the DOM because this causes the current selection to get lost:
Rangy is used to save the selection and restore it afterwards. Note that the save and restore could be done with something lighter than Rangy but I did not want to load the answer with minutia. If you decide to use Rangy for this task, please read the documentation because it is possible to optimize the serialization and deserialization.
For Rangy to work, the DOM must be in exactly the same state before and after the save. This is why normalize() is called before we add the marker and after we remove it. What this does is merge immediately adjacent text nodes into a single text node. The issue is that adding a marker to the DOM can cause a text node to be broken into two new text nodes. This causes the selection to be lost and, if not undone with a normalization, would cause Rangy to be unable to restore the selection. Again, something lighter than calling normalize could do the trick but I did not want to load the answer with minutia.
EDIT: This is an old answer that doesn't work for OP's requirement of having nodes with the same text. But it's cleaner and lighter if you don't have that requirement.
Here is one option that you can use and that works in all major browsers:
Get the offset of the caret within its node (document.getSelection().anchorOffset)
Get the text of the node in which the caret is located (document.getSelection().anchorNode.data)
Get the offset of that text within #mydiv by using indexOf()
Add the values obtained in 1 and 3, to get the offset of the caret within the div.
The code would look like this for your particular case:
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = $("#mydiv").html().indexOf( text );
offsetCaret = textOffset + offset;
You can see a working demo on this JSFiddle (view the console to see the results).
And a more generic version of the function (that allows to pass the div as a parameter, so it can be used with different contenteditable) on this other JSFiddle:
function getCaretHTMLOffset(obj) {
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = obj.innerHTML.indexOf( text );
return textOffset + offset;
}
About this answer
It will work in all recent browsers as requested (tested on Chrome 42, Firefox 37, and Explorer 11).
It is short and light, and doesn't require any external library (not even jQuery)
Issue: If you have different nodes with the same text, it may return the offset of the first occurrence instead of the real position of the caret.
NOTE: This solution works even in nodes with repeated text, but it detects html entities (e.g.: ) as only one character.
I came up with a completely different solution based on processing the nodes. It is not as clean as the old answer (see other answer), but it works fine even when there are nodes with the same text (OP's requirement).
This is a description of how it works:
Create a stack with all the parent elements of the node in which the caret is located.
While the stack is not empty, traverse the nodes of the containing element (initially the content editable div).
If the node is not the same one at the top of the stack, add its size to the offset.
If the node is the same as the one at the top of the stack: pop it from the stack, go to step 2.
The code is like this:
function getCaretOffset(contentEditableDiv) {
// read the node in which the caret is and store it in a stack
var aux = document.getSelection().anchorNode;
var stack = [ aux ];
// add the parents to the stack until we get to the content editable div
while ($(aux).parent()[0] != contentEditableDiv) { aux = $(aux).parent()[0]; stack.push(aux); }
// traverse the contents of the editable div until we reach the one with the caret
var offset = 0;
var currObj = contentEditableDiv;
var children = $(currObj).contents();
while (stack.length) {
// add the lengths of the previous "siblings" to the offset
for (var x = 0; x < children.length; x++) {
if (children[x] == stack[stack.length-1]) {
// if the node is not a text node, then add the size of the opening tag
if (children[x].nodeType != 3) { offset += $(children[x])[0].outerHTML.indexOf(">") + 1; }
break;
} else {
if (children[x].nodeType == 3) {
// if it's a text node, add it's size to the offset
offset += children[x].length;
} else {
// if it's a tag node, add it's size + the size of the tags
offset += $(children[x])[0].outerHTML.length;
}
}
}
// move to a more inner container
currObj = stack.pop();
children = $(currObj).contents();
}
// finally add the offset within the last node
offset += document.getSelection().anchorOffset;
return offset;
}
You can see a working demo on this JSFiddle.
About this answer:
It works in all major browsers.
It is light and doesn't require external libraries (apart from jQuery)
It has an issue: html entities like are counted as one character only.
There are, as of now, 3,898 posts in StackOverflow regarding mouse click coordinates. They all cover everything on how to find the coordinates for the mouse .... in relation to an element.
Has anyone been able to implement a solution where, at any point of your processing, you can recall the coordinates of the very last mouse click?
I have tried everything and every solution for the last 8 hours or so but cannot come accross anything that can recall the last mouse click coordinates.
Apparently, you have to catch it right where you are processing $('...').click(function(e){...});
How about if I sent the processing somewhere and, within that processing (a few nanoseconds later) I want to find out the coordinates of the last mouse click? How can I retrieve it outside an specific function?
EDIT:
Based on sdleihssirhc's suggestion, I was able to implement a solution that is working for me:
On the main page I have created two divs (to avoid dealing with arrays and global variables):
<div id='mouseX' style='display:none; ' ></div>
<div id='mouseY' style='display:none; ' ></div>
On the main page within the script tags:
$('#wrapper').click(function(e) {
var offset = $(this).offset();
$('#mouseX').text(e.clientX - offset.left);
$('#mouseY').text(e.clientY - offset.top);
});
Whenever needed, I can just:
var clickX = $('#mouseX').text();
var clickY = $('#mouseY').text();
Thank you!
You can set up a global array, for keeping track of coordinates (maybe in an object, whatever). Then you can add an event listener to the document, listening for click. Then it's just a matter of logging the position.
Whenever you want to refer to the position during the last click, just refer to the appropriate element of your global array. (If your logging function unshifted instead of pushing, you could always just look at yourArray[0].)
In the $('...').click(function(e){...}); handler, you can set a global variable with the coordinates. For example:
Globally:
var x = -1, // no previous click
y = -1; // no previous click
In the click handler:
x = e.pageX();
y = e.pageY();
Then, you can just reference x and y when you need the coordinates of the last click.
I've a main div of which inside I've some more divs. I want the inner divs to be draggable inside the main div and when saved, saves their positions.
Can anyone help me on how to save the positions? [Permanently, not just for the current session]
You can do it with jQuery UI: http://jqueryui.com/demos/draggable/#constrain-movement
You can get the position of each div, fetching the left and top css attributes. You can see the jQuery documentation: http://api.jquery.com/position/ and use:
var obj = jQuery("#inner_div").position();
var coord_x = position.left;
var coord_y = position.top;
Finally, if you want to update some input you can do it adding a callback to the stop event of the draggable interaction of jQuery UI. Hope that helps!.