Clone div with new calculator for new divs - javascript

I have some Jquery that I am using to clone a div, inside the div is an input that does a calculation.
When I clone the div and it creates a new div, the calculation does not work for the new divs. I understand that the calculation only works once the way I have it written. I have searched, but cannot find what I am looking for.
I also have an issue that when I add a number in the input the calculation works for the first div, but it also removes my buttons.
How can I have a new calculation for each new cloned div?
How can I stop the calculation from removing my add/remove buttons?
function clone() {
$(this).parents(".clonedInput").clone()
.appendTo("body")
.attr("id", "clonedInput" + cloneIndex)
.find("*")
.each(function() {
var id = this.id || "";
var match = id.match(regex) || [];
if (match.length == 3) {
this.id = match[1] + (cloneIndex);
}
})
.on('click', 'button.clone', clone)
.on('click', 'button.remove', remove);
cloneIndex++;
}
function remove() {
$(this).parents(".clonedInput").remove();
}
$("button.clone").on("click", clone);
$("button.remove").on("click", remove);
// calculator
$(document).ready(function() {
$(".calculate").bind("keyup change", function(e) {
var cabwidth = parseFloat($("#cabwidth").val()) || 0;
var ply = 1.4375;
var value = cabwidth - ply;
if (!isNaN(value) && value !== Infinity) {
$("#sum").text(value);
}
});
});
body {
padding: 10px;
}
.clonedInput {
padding: 10px;
border-radius: 5px;
background-color: #def;
margin-bottom: 10px;
}
.clonedInput div {
margin: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="clonedInput1" class="clonedInput">
<input type="text" class="calculate" id="cabwidth" placeholder="Cabinet Width">
<div id="sum" />
<div class="actions">
<button class="clone">Add</button>
<button class="remove">Remove</button>
</div>
</div>
Here is a jsfiddle example: jsfiddle

Your buttons get removed because their parent <div> has its contents overwritten (due to your invalid syntax). You're attempting to self-close your sum <div> with <div id="sum" />.
The <div> element cannot be self-closed, as it is not a void element; you must explicitly state that the element is empty with <div id="sum"></div>. Making this change fixes the problem with your buttons disappearing.
Note that you can validate your HTML markup with the W3C validation service, to ensure that your HTML is valid (and thus behaves in a way that is expected). Also note that .bind() is deprecated as of jQuery 3.0; you should be using .on() instead.
As for your cloning not working, that is due to two reasons:
The first being that you are cloning based on ID, and thus duplicating the ID. IDs must be unique throughout the DOM. Use classes instead of IDs, and use $(this) to refer to the specific cloned element.
Change #sum to .sum, and instead of $("#sum").text(value), use
$(this).parent().find(".sum").text(value) to only affect the
correct element.
Change var cabwidth =
parseFloat($("#cabwidth").val()) || 0 to var cabwidth =
parseFloat($(this).val()) || 0.
Remove all use of IDs to ensure valid markup after the cloning.
The second being that event handlers do not get attached to cloned elements. You need to hoist the scope to an element that is available on DOM load, and make use of event delegation. Instead of $(".calculate").bind("keyup change", function(e), use $("body").on("keyup change", ".calculate", function(e).
This is all fixed in the following example:
function clone() {
$(this).parents(".clonedInput").clone()
.appendTo("body")
.find("*")
.on('click', 'button.clone', clone)
.on('click', 'button.remove', remove);
}
function remove() {
$(this).parents(".clonedInput").remove();
}
$("button.clone").on("click", clone);
$("button.remove").on("click", remove);
// calculator
$(document).ready(function() {
$("body").on("keyup change", ".calculate", function(e) {
var cabwidth = parseFloat($(this).val()) || 0;
var ply = 1.4375;
var value = cabwidth - ply;
if (!isNaN(value) && value !== Infinity) {
$(this).parent().find(".sum").text(value);
}
});
});
body {
padding: 10px;
}
.clonedInput {
padding: 10px;
border-radius: 5px;
background-color: #def;
margin-bottom: 10px;
}
.clonedInput div {
margin: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="clonedInput">
<input type="text" class="calculate" placeholder="Cabinet Width">
<div class="sum"></div>
<div class="actions">
<button class="clone">Add</button>
<button class="remove">Remove</button>
</div>
</div>
Hope this helps! :)

Here is an updated jsFiddle.
Some notes on what I changed:
.bind() is depreciated
Attached the change/keyup to the document, then passed .calculate as the selector, this will work with dynamic elements, whereas before it wasn't
Made sure each clone and child elements have a unique ID
Updated the calculate function to target elements relative to current input

Related

jQuery checkbox (under a ul > li) check event

I have this code to handle checkbox check event:
<html>
<head>
<style>
#leftPane {
width: 200px;
height: 500px;
border: 1px solid;
float: left;
}
#rightPane {
height: 500px;
width: 600px;
border: 1px solid;
border-left: 0px;
float: left;
}
#addTo {
border: 1px solid;
}
#addTo input {
border: none;
}
#showList ul{
list-style: none;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script>
$(document).ready(function(){
$('#todoItem').keypress(function(e){
if (e.which ==13) {
var item = $('#todoItem').val();
var chkbox = "<input type='checkbox' name='"+item+"'>"
$("#showList ul").append("<li>"+chkbox+""+item+"</li>");
}
})
$("#showList li ul input[type=checkbox]").change(function(){
var $this = $(this);
alert("tell me");
/*if ($this.is(':checked')) {
$(this).parent().remove();
}*/
})
})
</script>
</head>
<body>
<div id="leftPane">
<ul>
<li>Shopping List</li>
<li>Movie List`</li>
<ul>
</div>
<div id="rightPane">
<p>Let's add some todo</p>
<div id="addTo">
<input id="todoItem" type="text" placeholder="Add a todo"></input>
</div>
<div id="showList">
<ul>
</ul>
</div>
</div>
</body>
When I am clicking the checkbox the event handler
( $("#showList li ul input[type=checkbox]").change(function(){ )
is not fired (no alert appears). If I select
$('#showList > ul").click (...)
Then the event handler fires but that means clicking anywhere within the ul not necessarily for a checkbox.
I was following these links to develop the above code:
jQuery checkbox checked state changed event
Use JQuery to check a checkbox in a parent list-item?
The jsfiddle page: https://jsfiddle.net/
Any help would be much appreciated.
Thanks
my solution
$(document).on('change', "#showList ul li input[type=checkbox]", function(){
var $this = $(this);
alert("tell me");
/*if ($this.is(':checked')) {
$(this).parent().remove();
}*/
})
Problems in your solution:
you have written li before ul; you are expecting li to enclose ul tag, which is not the case rather opposite
You are dynamically adding checkbox in runtime, so normal event handler wont work like .change. You need to implement event delegation technique to work in your case.
in this code $('#showList > ul").click (...) you know what is your mistake
The majority of browser events bubble, or propagate, from the deepest,
innermost element (the event target) in the document. Delegated events
have the advantage that they can process events from descendant
elements that are added to the document at a later time.
By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers. document object is guranteed to be present all the time. So, document object will delegate the change event to element matching the selector #showList ul li input[type=checkbox].
When you register the onChange event, you do it for the matching elements currently in the page. New elements will not have that event listener registered to them.
To solve the problem, you could either add the event when creating the item itself, or you could use event delegation.
Taking advantage of events bubbling up the DOM and event delegation, register the event on a static parent (i.e #showlist), like so:
$('#showlist').on('change', 'input[type=checkbox]', function(event) {
// your handler here
});
What'll happen is that when you click on the desired element, the event will bubble up until it reaches an element that has a registered handler for its type. You'll notice that event.target is the actual event target, not the element handling the event.
Aside from that, your selector should be #showlist ul li input[type='checkbox'], not #showlist li ul input[type='checkbox'].
Let's follow KISS method.
<script>
$(document).ready(function () {
$('#todoItem').change(function () {
$('<li />').append(
$('<input type="checkbox" name="' + this.value + '" id="' + this.value + '">')
.click(function () {
var $this = this;
alert('tell me ' + $this.name);
})//click
)//append
.append('<label for="' + this.value + '">' + this.value + '</label>')
.appendTo($("#showList ul"));
});
});
</script>
Is it what you want?

HTML: how to prevent moving focus from element?

I have several elements with tabindex attribute. When I click on any area of the page outside of them, they lose focus.
Current Behaviour - In regular desktop applications, if an element is not focusable, clicking on it doesn't move focus from a previous focused element.
But In HTML it's not the case: my focusable elements always lose focus, even if I click on elements with no tabindex.
Required Behaviour - Is it possible to prevent the above behaviour in HTML? I want my elements to lose focus only when I click on other focusable elements like its having in desktop application as I mentioned above.
This is a sort of hack and can be implemented in a better way.
Logic
Create a global variable lastSelectedInput to store id of last visited element.
Add a class to define boundary.
Add a click event on body and if event.path does not contains boundary element, call focus of lastSelectedInput
JSFiddle
Code
(function() {
var lastSelectedInput = "";
function bodyClick(e) {
var inside = false;
for (var i in e.path) {
if (e.path[i].className == "content") inside = true;
}
if (!inside) {
document.getElementById(lastSelectedInput).focus();
}
}
function inputFocus(e) {
lastSelectedInput = e.target.id;
e.stopPropagation()
}
function registerEvents() {
document.getElementsByTagName("body")[0].addEventListener("click", bodyClick);
var inputs = document.getElementsByTagName("input")
for (var i = 0; i < inputs.length; i++) {
inputs[i].onfocus = inputFocus;
}
}
registerEvents();
})();
.content {
margin: 15px;
background: #ddd;
border: 2px solid gray;
border-radius: 4px;
height: 200px;
width: 200px;
padding: 5px;
}
<div>
<div class="content">
<input type="text" id="txt1">
<input type="text" id="txt2">
<input type="text" id="txt3">
<input type="text" id="txt4">
<input type="text" id="txt5">
</div>
</div>

How can I access a DOM element with jQuery that I have "moved" around the page?

I have a page with two areas. There are boxes in each area. If the user clicks on a box in the top area, it gets moved to the bottom and vice versa. This works fine for the first movement. Theoretically, I should be able to move them back and forth between sections as I please.
Box HTML:
<div id="top-area">
<div class="top-box" id="blue-box"></div>
<div class="top-box" id="yellow-box"></div>
<div class="top-box" id="green-box"></div>
</div>
<hr/>
<div id="bottom-area">
<div class="bottom-box" id="red-box"></div>
<div class="bottom-box" id="gray-box"></div>
</div>
I use jQuery.remove() to take it out of the top section and jQuery.append() to add it to the other. However, when I try to move a box back to its original position, the event that I have created to move them doesn't even fire.
jQuery/JavaScript:
$(".top-box").on('click', function ()
{
var item = $(this);
item.remove();
$(this).removeClass("top-box").addClass("bottom-box");
$("#bottom-area").append(item);
});
$(".bottom-box").on('click', function ()
{
var item = $(this);
item.remove();
$(this).removeClass("bottom-box").addClass("top-box");
$("#top-area").append(item);
});
I have verified that the classes I am using as jQuery selectors are getting added/removed properly. I am even using $(document).on() to handle my event. How come my boxes are not triggering the jQuery events after they are moved once?
Please see the Fiddle: http://jsfiddle.net/r6tw9sgL/
Your code attaches the events on the page load to the elements that match the selector right then.
If you attach the listener to #top-area and #bottom-area and then use delegated events to restrict the click events to the boxes, it should work like you expect. See .on: Direct and Delegated Events for more information.
Use the below JavaScript:
$("#top-area").on('click', '.top-box', function ()
{
var item = $(this);
item.remove();
$(this).removeClass("top-box").addClass("bottom-box");
$("#bottom-area").append(item);
});
$("#bottom-area").on('click', '.bottom-box', function ()
{
var item = $(this);
item.remove();
$(this).removeClass("bottom-box").addClass("top-box");
$("#top-area").append(item);
});
Alternatively:
You could also change .on() to .live(), which works for "all elements which match the current selector, now and in the future." (JSFiddle)
JSFiddle
Here's another way you could work it:
function toBottom ()
{
var item = $(this);
item.remove();
item.off('click', toBottom);
item.on('click', toTop);
$(this).removeClass("top-box").addClass("bottom-box");
$("#bottom-area").append(item);
}
function toTop ()
{
var item = $(this);
item.remove();
item.off('click', toTop);
item.on('click', toBottom);
$(this).removeClass("bottom-box").addClass("top-box");
$("#top-area").append(item);
}
$(".top-box").on('click', toBottom);
$(".bottom-box").on('click', toTop);
#top-area, #bottom-area {
height: 100px;
border: 1px solid black;
padding: 10px;
}
.top-box::before {
content: "Top";
}
.bottom-box::before {
content: "Bottom";
}
#blue-box, #red-box, #yellow-box, #green-box, #gray-box {
width: 100px;
cursor: pointer;
float: left;
margin: 0 5px;
text-align: center;
padding: 35px 0;
}
#blue-box {
background-color: blue;
}
#red-box {
background-color: red;
}
#yellow-box {
background-color: yellow;
}
#green-box {
background-color: green;
}
#gray-box {
background-color: gray;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="top-area">
<div class="top-box" id="blue-box"></div>
<div class="top-box" id="yellow-box"></div>
<div class="top-box" id="green-box"></div>
</div>
<hr/>
<div id="bottom-area">
<div class="bottom-box" id="red-box"></div>
<div class="bottom-box" id="gray-box"></div>
</div>
This basically removes the listener that switched the object to bottom to a listener that switches the object to the top and viceversa.

How to find nearest child to a click location?

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.
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
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/
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>
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>
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/

Make HTML element inherit events from custom object

In the webpage I'm working I have a lot of small images to wich I want to assign the same set of events. Instead of adding them one by one, I thought it would be more elegant if I could make that type of element inherit these events.
What comes to my mind is something like :
function InheritEvents(){};
InheritEvents.prototype.onmouseover = function(){...action a..};
InheritEvents.prototype.onmouseout = function(){...action b..};
var temp = originalHTMLElement.constructor; //(in this case img)
originalHTMLElement.prototype = new InheritEvents();
originalHTMLElement.constructor = temp;
a) Am I not disturbing the originalHTMLElement ?
b) Is it possible to name the custom object property, for example
".onmouseover" like in the classic way:
originalHTMLElement.onmouseover = function()... ?
c) More conceptual: Is it possible to mix your custom objects with HTML
elemenst / DOM nodes ?
I would strongly recommend against this. It probably wouldn't work anyway, but messing with the prototypes of host objects is, in general, a bad idea.
I don't think there should really be a problem with iterating through the target elements and attaching events to them, but if you don't like that, you can use event delegation:
window.onload = function() {
document.getElementById("images").onclick = function(e) {
if (e.target && e.target.classList.contains("clickable")) {
e.stopPropagation();
console.log("I've been clicked!");
}
}
}
#images div {
width: 40px;
height: 40px;
float: left;
margin: 5px;
background-color: blue;
}
#images div.clickable {
background-color: red;
}
#images + * {
clear: both;
}
<div>
<div id="images">
<!-- Pretend that these DIVs are your images -->
<div></div>
<div class="clickable"></div>
<div></div>
<div></div>
<div class="clickable"></div>
</div>
<div>
Click one of the red images above
</div>
</div>
Of course, if you're using jQuery, the .on() method can handle both the "add an event handler to all members of a set" option and the event delegation option in a single line.

Categories

Resources