Reputation: 41423
Fiddle: http://jsfiddle.net/1bgun0k0/1/
I have a DIV with several child SPANs.
<div id="parent">
<span class="child">Alpha</span>
<span class="child">Beta</span>
<span class="child">Gamma</span>
</div>
Child elements have a margin:
#parent { padding: 5px; }
.child { margin: 5px; }
When user clicks between child elements (or at either end of the parent DIV outside a child element), I need to insert a new child there.
How do I detect between which child elements user clicked?
Update: I need to support multiple rows of children (thanks, Roko, for the heads-up). Clicks between rows should be ignored. Clicks on the left and right side of the whole row should be handled correctly.
Upvotes: 3
Views: 694
Reputation: 35670
On click:
Iterate through each child
, grabbing children on the left and right based on position in relation to the cursor. (Check event.pageY to make sure they're on the right row.)
If a child to the left is found, keep iterating to the end. The last left child will be the true left child.
If a child to the right is found, stop iterating. This is the one you want.
If a left child is found, insert the text and a space after it. You're done.
If a right child is found, insert the text and a space before it.
Snippet
var n = 0;
$('#parent').click(function(e) {
if(e.target.id === 'parent') {
var parent= $(this),
firstChild= $(this).find('.child').first(),
lastChild = $(this).find('.child').last(),
leftChild,
rightChild,
text= function() {return $('<span class="child new">New child '+(++n)+'</span>');}
parent.find('.child').each(function() {
var pos= $(this).offset(),
h= $(this).height();
if(pos.top <= e.pageY && pos.top+h >= e.pageY) {
if(pos.left <= e.pageX) {
leftChild= $(this);
}
else if(pos.left >= e.pageX) {
rightChild= $(this);
return false;
}
}
});
if(leftChild) {
leftChild.after(text()).after(' ');
}
else if(rightChild) {
rightChild.before(text()).before(' ');
}
else if(!firstChild.length || e.pageY < firstChild.offset().top) {
parent.prepend(text()).before(' ');
}
else if(e.pageY > lastChild.offset().top) {
lastChild.after(text()).after(' ');
}
//otherwise, we're between rows
}
});
body {
margin: 10px;
padding: 10px;
}
#parent {
padding: 5px;
background-color: green;
cursor: pointer;
width: 450px;
line-height: 2.7em;
}
.child {
margin: 5px;
white-space: nowrap;
background-color: yellow;
border: 1px solid black;
}
.new {
background-color: lightblue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
<span class="child">Alpha</span>
<span class="child">Beta</span>
<span class="child">Gamma</span>
<span class="child">Delta</span>
<span class="child">Epsilon</span>
<span class="child">Zeta</span>
<span class="child">Eta</span>
<span class="child">Theta</span>
<span class="child">Iota</span>
<span class="child">Kappa</span>
<span class="child">Lambda</span>
<span class="child">Mu</span>
<span class="child">Nu</span>
<span class="child">Xi</span>
<span class="child">Omicron</span>
<span class="child">Pi</span>
<span class="child">Rho</span>
<span class="child">Sigma</span>
<span class="child">Tau</span>
<span class="child">Upsilon</span>
<span class="child">Phi</span>
<span class="child">Chi</span>
<span class="child">Psi</span>
<span class="child">Omega</span>
</div>
Upvotes: 1
Reputation: 6768
http://jsfiddle.net/tukqnujm/2
This was suggested previously, but you can add transparent clickable elements between the others to act as margins.
var parent = $("#parent");
parent.on("click", function(e) {
if(e.target.classList.contains("between")) {
var item = document.createElement("span");
item.classList.add("item");
var between = document.createElement("span");
between.classList.add("between");
e.target.parentNode.insertBefore(item, e.target);
e.target.parentNode.insertBefore(between, item);
}
});
Be careful though, inline-block tags add a space to the document if there are spaces between elements, and those spaces won't be clickable. That's why I put comments in the jsfiddle.
<div id="parent"><!--
--><span class="between"></span><!--
--><span class="item"></span><!--
--><span class="between"></span><!--
--></div>
Upvotes: 1
Reputation: 858
Since margins don't receive click events, I might suggest using a 5px placeholder div instead of a margin, so that clicking between children will result in a click to the placeholder.
On that click, you insert a new child (and a new 5px placeholder) underneath.
UPDATE: Since you changed the question from clicking between to clicking beside, you might try this trick using pseudo elements (to prevent divitis) https://stackoverflow.com/a/23243996/1998238
Upvotes: 4
Reputation: 21
An alternative solution is to use CSS to mask the area you are clicking. You can do this by wrapping your elements like so:
<div id="parent">
<div id="alpha" class="spacer">
<span class="child">Alpha</span>
</div>
<div id="beta" class="spacer">
<span class="child">Beta</span>
</div>
<div id="gamma" class="spacer">
<span class="child">Gamma</span>
</div>
</div>
Here is the code: https://jsfiddle.net/theodin/5enfs52t/2/
Upvotes: 2
Reputation: 66173
You can track the position of the cursor (because the cursor will not be immediately over any .child
element, so binding the click event to it is useless), and then retrieve a list of .child
elements that are to the left of this coordinate. Get the last .child
element that satisfy this criterea, append new element behind it.
Also, you should use .on()
for the click event binding for .child
, because newly added elements will not have the click event registered (because with your existing code, you are binding the click event at runtime, where newly added elements are not present on the page).
$('.child').on('click', function(e) {
e.stopImmediatePropagation();
$(".debug").removeClass("debug");
$(this).toggleClass("debug");
})
$("#parent").click(function(e) {
e.stopImmediatePropagation();
$(".debug").removeClass("debug");
$(this).toggleClass("debug");
// Get mouse position along x-axis
var xPos = e.pageX,
yPos = e.pageY;
// Get x-axis offset of child
// Return array of child elements that is to the left of mouseclick
// and get the last child, append new element after it
var $prevChild = $('.child').filter(function() {
return ($(this).offset().left < xPos && $(this).offset().top < yPos)
}),
$content = $('<span class="child">New child</span>');
// Additional logic from @AlexanderGladysh
if ($prevChild.length > 0) {
$content.insertAfter($prevChild.last());
} else {
$(this).prepend($content);
}
})
See fiddle here: http://jsfiddle.net/teddyrised/1bgun0k0/9/
Upvotes: 0