Button firing twice when clicked - javascript

If you goto the JSFiddle underneath. Add two items and then press 'complete' on one of the items in the console it logs out the same button twice. I can't figure out why.
What am I doing wrong here! :)
https://jsfiddle.net/7k84p3oz/6/
HTML
<input id="inputAdd" type="text" name="" value="">
<button id="submitItem" type="button" name="button">Add</button>
<!-- Start of list -->
<ul id="listContent"></ul>
<!-- End of list -->
<!-- Start of completed -->
<div id="completed">
<h4>Completed</h4>
</div>
<!-- End of completed -->
JS
$(document).ready(function(){
(function() {
var itemTracker = {
// init
init: function() {
this.bindEvents();
},
// cacheDom
cacheDom: {
inputAdd: $('#inputAdd'),
submitAdd: $('#submitItem'),
listContent: $('#listContent'),
completed: $('#completed')
},
// Add item
addItem: function() {
var itemValue = this.cacheDom.inputAdd.val();
var listItem = $("<li></li>");
var buttonRemove = '<button class="remove">Remove</button>';
var buttonComplete = '<button class="complete">Complete</button>';
listItem.append(itemValue);
listItem.append(buttonRemove);
listItem.append(buttonComplete);
var itemContent = this.cacheDom.listContent.append(listItem);
this.cacheDom.inputAdd.val("");
// Remove item
var remove = $('.remove');
remove.on("click", function(e){
$(e.target).closest("li").hide();
});
var complete = $(".complete");
// Complete item
var completedItem = function(e) {
console.log(this);
// var childParent = $(this).parent();
// var rootParent = childParent.parent();
// var parentId = rootParent.prop('id');
//
// if(parentId === "listContent") {
// $('#completed').append(listItem);
// } else {
// $('#listContent').append(listItem);
// }
};
complete.on("click", completedItem);
},
// Bind Events
bindEvents: function() {
this.cacheDom.submitAdd.on("click", this.addItem.bind(this));
}
};
itemTracker.init();
})();
});

This is because every time you add a new item, you are getting ALL the buttons with class .complete var complete = $(".complete"); and appending an action to them.
Hence, if you add 1 button: it will only trigger once.
If you add 2 buttons: the first one will trigger twice, the second one once.
If you add 3 button: the first one will trigger three times, the second twice and the third once.
etc...
You can fix it by replacing: var complete = $(".complete");
For: var complete = $(listItem).find(".complete")
On line 36.
This will ensure that it only selects the .complete button within the listItem that you are creating at that moment.

The reason is you have bind the class name to click event; meaning the event handler is fired for number of HTML elements which has the class name complete.
Since you have e parameter in your event handler function try to use the e.target which will give the clicked element.
var completedItem = function(e) {
console.log(e.target);
}

In your code, you were using var complete = $(".complete");
to find complete button. Due to this when you add second entry, $(".complete"); will return 2 elements and this will also attach event to the earlier node as well.
What you can do wrap the complete button in jquery like
var buttonComplete = $('<button class="complete">Complete</button>')
and use this buttonComplete to attach click event.
buttonComplete.on("click", completedItem);
$(document).ready(function(){
(function() {
var itemTracker = {
// init
init: function() {
this.bindEvents();
},
// cacheDom
cacheDom: {
inputAdd: $('#inputAdd'),
submitAdd: $('#submitItem'),
listContent: $('#listContent'),
completed: $('#completed')
},
// Add item
addItem: function() {
var index=this.cacheDom.listContent.children().length;
var itemValue = this.cacheDom.inputAdd.val();
var listItem = $("<li></li>");
var buttonRemove = '<button class="remove">Remove</button>';
var buttonComplete = $('<button class="complete">Complete</button>')
listItem.append(itemValue);
listItem.append(buttonRemove);
listItem.append(buttonComplete);
var itemContent = this.cacheDom.listContent.append(listItem);
this.cacheDom.inputAdd.val("");
// Remove item
var remove = $('.remove');
remove.on("click", function(e){
$(e.target).closest("li").hide();
});
var complete = $(".complete");
// Complete item
var completedItem = function(e) {
console.log(this);
// var childParent = $(this).parent();
// var rootParent = childParent.parent();
// var parentId = rootParent.prop('id');
//
// if(parentId === "listContent") {
// $('#completed').append(listItem);
// } else {
// $('#listContent').append(listItem);
// }
};
buttonComplete.on("click", completedItem);
},
// Bind Events
bindEvents: function() {
this.cacheDom.submitAdd.on("click", this.addItem.bind(this));
}
};
itemTracker.init();
})();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<input id="inputAdd" type="text" name="" value="">
<button id="submitItem" type="button" name="button">Add</button>
<!-- Start of list -->
<ul id="listContent"></ul>
<!-- End of list -->
<!-- Start of completed -->
<div id="completed">
<h4>Completed</h4>
</div>
<!-- End of completed -->

Related

Concatenate function

The idea behind this to animate section with mousewheel - keyboard and swipe on enter and on exit. Each section has different animation.
Everything is wrapp inside a global variable. Here is a bigger sample
var siteGlobal = (function(){
init();
var init = function(){
bindEvents();
}
// then i got my function to bind events
var bindEvents = function(){
$(document).on('mousewheel', mouseNav());
$(document).on('keyup', mouseNav());
}
// then i got my function here for capture the event
var mouseNav = function(){
// the code here for capturing direction or keyboard
// and then check next section
}
var nextSection = function(){
// Here we check if there is prev() or next() section
// if there is do the change on the section
}
var switchSection = function(nextsection){
// Get the current section and remove active class
// get the next section - add active class
// get the name of the function with data-name attribute
// trow the animation
var funcEnter = window['section'+ Name + 'Enter'];
}
// Let's pretend section is call Intro
var sectionIntroEnter = function(){
// animation code here
}
var sectionIntroExit = function(){
// animation code here
}
}();
So far so good until calling funcEnter() and nothing happen
I still stuck to call those function...and sorry guys i'm really not a javascript programmer , i'm on learning process and this way it make it easy for me to read so i would love continue using this way of "coding"...Do someone has a clue ? Thanks
Your concatenation is right but it'd be better if you didn't create global functions to do this. Instead, place them inside of your own object and access the functions through there.
var sectionFuncs = {
A: {
enter: function() {
console.log('Entering A');
},
exit: function() {
console.log('Exiting A');
}
},
B: {
enter: function() {
console.log('Entering B');
},
exit: function() {
console.log('Exiting B');
}
}
};
function onClick() {
var section = this.getAttribute('data-section');
var functions = sectionFuncs[section];
functions.enter();
console.log('In between...');
functions.exit();
}
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', onClick);
}
<button data-section="A">A</button>
<button data-section="B">B</button>
You could have an object that holds these functions, keyed by the name:
var enterExitFns = {
intro: {
enter: function () {
// animation code for intro enter
},
exit: function () {
// animation code for intro exit
}
},
details: {
enter: function () {
// animation code for details enter
},
exit: function () {
// animation code for details exit
}
}
};
var name = activeSection.attr('data-name');
enterExitFns[name].enter();

Handling focus on a toggle button

I am trying to keep the focus on the toggle button after it gets pressed.
Right now, when I click on the button, the focus jumps to the top of the page. I am not sure what I am doing wrong.
Here is the code:
HTML
<button ng-if=“test” ng-click=“deactivate()">Deactivate</button>
<button ng-if=“!test” ng-click="activate()”>Activate </button>
JS
$scope.activate = function () {
var activateButton = document.activeElement;
if ($scope.some.length) {
$scope.enableSome($scope.some, true);
}
activateButton.onclick = function () {
activateButton.focus();
};
};
$scope.deactivate = function () {
var activateButton = document.activeElement;
if ($scope.some.length) {
$scope.enableSome($scope.some, false);
}
activateButton.onclick = function () {
activateButton.focus();
};
};
You actually have two buttons that are added/removed from the DOM based on the ng-if condition (documentation).
Your button is not getting focused because the activate button is getting removed while the deactivate button is added, and vice versa.
We can refactor the two buttons into one using a toggleActivation() method.
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.test = false;
$scope.some = [1, 2, 3];
$scope.enableSome = function(some, activate) {
//Do whatever
console.log('enableSome called with activate = ' + activate);
}
$scope.toggleActivation = function() {
//Toggle the state
$scope.test = !$scope.test;
if ($scope.some.length) {
$scope.enableSome($scope.some, $scope.test);
}
//The button is still focused!
console.log('Focused element: ', document.activeElement);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<button ng-click="toggleActivation()">
{{test ? "Deactivate" : "Activate"}}
</button>
</div>

JQuery load() fires multiple times

This is my code:
$(document).ready(function () {
var na = $("#navDivA");
var nb = $("#navDivB");
var ca = $("#contentA");
var cb = $("#contentB");
var la = $("#home");
var lb = $("#about");
var firstSession = true;
na.on("click", function () {
ca.load("home.html", function(){
alert("Loaded");
});
});
nb.on("click", function () {
cb.load("about.html");
});
});
<div id="wrapper">
<div id="navDivA"><a id="home">Home</a></div>
<div id="navDivB"><a id="about">About</a></div>
<div id="contentA"></div>
<div id="contentB"></div>
</div>
Why does it alert multiple times every time I start to click? How can I avoid this? I want to load the html file once.
You can use function one instead of on. According to documentation
The handler is executed at most once per element per event type.
which seems to be what you need. Change the code to
na.one("click", function () {
ca.load("home.html", function(){
alert("Loaded");
});
});
nb.one("click", function () {
cb.load("about.html");
});

jQuery Tree issue - add first child li

I have 2 columns, on the left side a team with users, on the right column, will be displayed the users i have selected. so everything its working but i'm trying to implement a new feature as follow:
I have 2 list level like a tree (only 2 levels). When i click on a user, i'm able to select it sending to the right column. Also, when i click (single click) on the first level (team name), the second level (users) appear as toggle jquery function. i need so, when i double click on a team (level 1) all users on that tree turns selected and go to column on the right side.
Also, when i click on the team (first level) on the right side, all the users get removed back.
My code to add the users jquery current is:
$(document).ready(function () {
var maxAllowed = 10000;
var $selectTable = $("#mytable");
var $selectList = $("#selected_users ul")
$("#max-count").html(maxAllowed);
var getActivated = function () {
var activated = new Array();
$selectTable.find('input[type="checkbox"]:checked').closest("li").each(function () {
var $obj = new Object;
var currentBox = $(this).find('input[type="checkbox"]');
$obj.id = currentBox.val();
$obj.boxid = currentBox.attr("id");
$obj.name = $(this).find("label").text();
activated.push($obj);
});
return activated;
}
var updateActiveList = function () {
// Truncate list
$selectList.html("");
$(getActivated()).each(function () {
$selectList.append("<li><a href='#' class='remove' data-id='" + this.id + "' data-box-id='" + this.boxid + "'>" + this.name + "</li></a>");
});
}
var countActivated = function () {
return getActivated().length;
}
$('#view').click(function () {
allIds = new Array();
getActivated().each(function () {
allIds.push($(this).attr("id"));
});
alert(allIds);
});
$selectList.on("click", "a.remove", function () {
$('#' + $(this).data("box-id")).prop("checked", false);
updateActiveList();
});
$selectTable.on("change", 'input[type="checkbox"]', function (event) {
if ($(this).is(":checked") && countActivated() > maxAllowed) {
event.preventDefault();
console.log("max reached!");
$(this).prop("checked", false);
}
updateActiveList();
});
});
Here's a jsFiddle with working example:
http://jsfiddle.net/muzkle/LMbV3/7/
Thanks all!
EDIT
Hi, i just added a code to separate single click from double click. So when the user single click, will open the tree. now i need when the user double click on the first level, add both (first level and they're childrens to the right side.
Follow code for single and double clicks:
alreadyclicked=false;
$(document).ready(function () {
$('#mytable').on('click', '.toggle', function (ul) {
//Gets all <tr>'s of greater depth
//below element in the table
var findChildren = function (ul) {
var depth = ul.data('depth');
return ul.nextUntil($('ul').filter(function () {
return $(this).data('depth') <= depth;
}));
};
var el = $(this);
var ul = el.closest('ul'); //Get <tr> parent of toggle button
var children = findChildren(ul);
var el=$(this);
if (alreadyclicked){
alreadyclicked=false; // reset
clearTimeout(alreadyclickedTimeout); // prevent this from happening
}else{
alreadyclicked=true;
alreadyclickedTimeout=setTimeout(function(){
alreadyclicked=false; // reset when it happens
//Remove already collapsed nodes from children so that we don't
//make them visible.
//(Confused? Remove this code and close Item 2, close Item 1
//then open Item 1 again, then you will understand)
var subnodes = children.filter('.expand');
subnodes.each(function () {
var subnode = $(this);
var subnodeChildren = findChildren(subnode);
children = children.not(subnodeChildren);
});
//Change icon and hide/show children
if (ul.hasClass('collapse')) {
ul.removeClass('collapse').addClass('expand');
children.hide();
} else {
ul.removeClass('expand').addClass('collapse');
children.show();
}
return children;
// do what needs to happen on single click.
// use el instead of $(this) because $(this) is
// no longer the element
},300); // <-- dblclick tolerance here
}
return false;
});
});
And new jsFiddle is: http://jsfiddle.net/muzkle/LMbV3/8/
To distinguish different groups I am wrapping each group/section in a wrapper div with class .wrapper
<div class="wrapper">
.
.
</div>
Also I attached a double click event to .wrapper and currently I have made it to alert its inner labels.Just write some additional code to add these labels to the right side like you are currently adding one element on click.Below is the code with jQuery .dblclick() function which attaches a double-click event to .wrapper.
$('.wrapper').dblclick(function(){
$(this).find('label').each(function(){
alert($(this).text());
});
});
Check this fiddle

Problems to run Jquery code on button click on my Wizard-step Jquery code

I am using Wizard-step for my form in a view, My project is on MVC3.
I have a form that is made on 3 steps which means I have three tags for each step in my form
and two buttons that are following:
<p><input type="button" id="next-step" class="next-step-client-val" name="next-step" value="Next" /></p><
<p><input type="button" value="Back id="back-step" name="back-step"></p>
In my first step I have only a bunch of TextBoxes, DropDownlists and TextAreas, second step have alot of client-side functionality, one example is that user can move rows from a table to another one etc.. And I have a Jquery validation that is following:
var customTbl = $('#CustomPickedTable');
var has1 = customTbl.find('td[data-row="1"]').is('*');
var has2 = customTbl.find('td[data-row="2"]').is('*');
var has3 = customTbl.find('td[data-row="3"]').is('*');
var has4 = customTbl.find('td[data-row="4"]').is('*');
if ((has1 === true) && (has2 === true) && (has3 === true) && (has4 === true)) {
jAlerts("Saved", "Info");
} else {
jAlert('You have to move atleast one row from each table', "Varning"); ;
return false
}
On the 3th step its just a review on what was created and my next-step button submits the form, when a user clicks on it.
What I want to be able to do is that when a user is on the 2th step and clicks on next-step button the jquery validation above should run. With my Wizard-step code I cant do that beacuse it uses next-step button selector for everything. Is there any solutions for this?
I have tried to put my Jquery validation code inside
$("#next-step").click(function () {
}
But then my jquery validation code run everytime a user clicks on next button, beacuse my tables are shown in the form but hidden, the validation triggers on first step when a user click on next. So that solution didnt work.
This is my Wizard-step Jquery Code and right now I have my Jquery validation in the bottom which means that when im on 3th step and click on next-step button it will validate and then post. But I dont want it to be like that. I want the validation to happen on the 2th step.
Here is the code:
$(function () {
$(".wizard-step:first").fadeIn(); // show first step
// attach backStep button handler
// hide on first step
$("#back-step").hide().click(function () {
var $step = $(".wizard-step:visible"); // get current step
if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
$step.hide().prev().fadeIn(4500); // show it and hide current step
// disable backstep button?
if (!$step.prev().prev().hasClass("wizard-step")) {
$("#back-step").hide();
}
}
});
// attach nextStep button handler
$("#next-step").click(function () {
var $step = $(".wizard-step:visible"); // get current step
var validator = $("form").validate(); // obtain validator
var anyError = false;
$step.find("select").each(function () {
if (!this.disabled && !validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
$step.find("input").each(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
$("#next-step").click(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
if (anyError)
return false; // exit if any error found
if ($step.next().hasClass("confirm")) { // is it confirmation?
// show confirmation asynchronously
$.post("/wizard/confirm", $("form").serialize(), function (r) {
// inject response in confirmation step
$(".wizard-step.confirm").html(r);
});
}
if ($step.next().hasClass("wizard-step")) { // is there any next step?
$step.hide().next().fadeIn(4500); // show it and hide current step
$("#back-step").show(); // recall to show backStep button
}
else { // this is last step, submit form
var selectedQuestions = $("#SelectedQuestions");
var selectedCustomQuestions = $("#SelectedCustomQuestions");
var currentIds = new Array();
var currentText = new Array();
$("#CustomPickedTable td[data-question-id]").each(function () {
var clickedId = $(this).attr("data-question-id");
currentIds.push(clickedId);
});
$('#CustomPickedTable td[data-attr-id]').each(function () {
var ClickedText = $(this).html();
currentText.push(ClickedText);
});
selectedCustomQuestions.val(currentText.join("|"));
selectedQuestions.val(currentIds.join(","));
var customTbl = $('#CustomPickedTable');
var has1 = customTbl.find('td[data-row="1"]').is('*');
var has2 = customTbl.find('td[data-row="2"]').is('*');
var has3 = customTbl.find('td[data-row="3"]').is('*');
var has4 = customTbl.find('td[data-row="4"]').is('*');
if ((has1 === true) && (has2 === true) && (has3 === true) && (has4 === true)) {
jAlerts("saved", "Info");
} else {
jAlert('You have to move atleast one row from each table', "Varning"); ;
}
return false;
}
});
My html code looks something like this:
<div class="wizard-step>
//step 1 content
</div>
<div class="wizard-step>
//step 2 content
</div>
<div class="wizard-step>
//step 3 content
</div>
<p><input type="button" id="next-step" class="next-step-client-val" name="next-step" value="Next" /></p><
<p><input type="button" value="Back id="back-step" name="back-step"></p>
I think it would be better if you detect at which wizard step you are using jquery .index() function. This way you can do the validation in your next step click handler only if you are on second step moving on to the third step. The code would look something like this :
$("#next-step").click(function () {
var $step = $(".wizard-step:visible"); // get current step
var stepIndex = $(".wizard-step").index($step); //index returns 0 based index so second step would be 1.
var validator = $("form").validate(); // obtain validator
var anyError = false;
$step.find("select").each(function () {
if (!this.disabled && !validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
$step.find("input").each(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
if (anyError)
return false; // exit if any error found
if(stepIndex == 1) //if you are on second step then validate your table
{
var customTbl = $('#CustomPickedTable');
var has1 = customTbl.find('td[data-row="1"]').is('*');
var has2 = customTbl.find('td[data-row="2"]').is('*');
var has3 = customTbl.find('td[data-row="3"]').is('*');
var has4 = customTbl.find('td[data-row="4"]').is('*');
if ((has1 === true) && (has2 === true) && (has3 === true) && (has4 === true)) {
jAlerts("Saved", "Info");
} else {
jAlert('You have to move atleast one row from each table', "Varning"); ;
return false
}
}
else if ($step.next().hasClass("confirm")) { // is it confirmation?
// show confirmation asynchronously
$.post("/wizard/confirm", $("form").serialize(), function (r) {
// inject response in confirmation step
$(".wizard-step.confirm").html(r);
});
}
if ($step.next().hasClass("wizard-step")) { // is there any next step?
$step.hide().next().fadeIn(4500); // show it and hide current step
$("#back-step").show(); // recall to show backStep button
}
else { // this is last step, submit form
var selectedQuestions = $("#SelectedQuestions");
var selectedCustomQuestions = $("#SelectedCustomQuestions");
var currentIds = new Array();
var currentText = new Array();
$("#CustomPickedTable td[data-question-id]").each(function () {
var clickedId = $(this).attr("data-question-id");
currentIds.push(clickedId);
});
$('#CustomPickedTable td[data-attr-id]').each(function () {
var ClickedText = $(this).html();
currentText.push(ClickedText);
});
}
});
I think you could approach this problem by refactoring the code
// validate the inputs in a form
// #param {string|object} jquery selector or jquery object
function validateStep (selector){
var $step = $(selector);
var validator = $("form").validate();
var anyError = false;
$step.find("select").each(function () {
if (!this.disabled && !validator.element(this)) {
anyError = true;
}
$step.find("input").each(function () {
if (!validator.element(this)) {
anyError = true;
}
if (!validator.element(this)) {
anyError = true;
}
return anyError;
}
This way you could validate the second step by calling
// this works because jquery returns an array of objects
// as a result of the css selector we call it with
// the eq() function accepts an index value that returns
// the jquery object at that position
// see. http://api.jquery.com/eq-selector/
validateStep($('.wizard-step').eq(1));
Or the first
validateStep('.wizard-step:first');
Etc
We could incorporate this into your code like this -
$('#next-step').click(function (event){
var $step = $('.wizard-step');
if(validateStep($step.filter(':visible'))){
// go to the next step
if ($step.next().hasClass("wizard-step")) {
$step.hide().next().fadeIn(4500);
$("#back-step").show();
} else {
// submit form
}
}
});
NB: You can read more about jQuery selectors here http://api.jquery.com/category/selectors/

Categories

Resources