Iterate through array elements one by one when clicking on button - javascript

How can I have it so that each time the button is clicked the array is iterated through once? If I click it a second time, it should display the second array element, etc. until it iterates through the entire array.
JavaScript:
var images = [
"<img src='first.png' />",
"<img src='second.png' />",
"<img src='third.png' />",
"<img src='kitten.png' />"
];
var button = document.getElementsByClassName("btn-lg")[0];
button.addEventListener("click", function(){
for(i = 0; i < 5; i++) {
jQuery('.row.text-center').append(images[i]);
}
});
HTML:
<div class="row text-center">
<button type="submit" class="btn btn-lg btn-info">Click Here!</button>
</div>
jsfiddle: http://jsfiddle.net/KVs6S/1/
Thanks!

Since you are using jQuery then you may try (don't mix up vanilla js liike addEventListener)
$(function(){
var images = [
"http://imageshack.us/a/img9/6564/3qv9.png",
"http://imageshack.us/a/img9/4521/3dmc.png",
"http://imageshack.us/a/img28/3608/1x6h.png",
"http://imageshack.us/a/img850/1713/5i6g.png"
];
$(".btn-lg").on("click", function(){
if(images.length) {
var img = $('<img/>', { 'src':images.pop() });
$('.row.text-center').append(img);
}
});
});
Also, Array.pop() is enough for this and just keep the urls instead of images in the array.
DEMO. (Images are getting appended but for your big button these are not visible.)

Externalize the counter :
var i=0, images = [
"<img src='first.png' />",
"<img src='second.png' />",
"<img src='third.png' />",
"<img src='kitten.png' />"
];
jQuery('.btn-lg').on("click", function(){
if (i>=images.length) return;
jQuery('.row.text-center').append(images[i++]);
});

Make i a variable starting from 0 and increment it resp. when lenght is greather than images count - restart:
var i = 0;
var button = document.getElementsByClassName("btn-lg")[0];
button.addEventListener("click", function(){
if (i == 4) { i = 0; /* OR return; to stop */ }
jQuery('.row.text-center').append(images[i++]);
});

Use a closure:
function yield(lenArray) {
var i=0;
console.log("outer",lenArray);
return function() {
if (i<lenArray) {
console.log("inner",i);
jQuery('.row.text-center').append(images[i++]);
}
}
}
button.addEventListener("click", yield(5));
How do JavaScript closures work?

Related

javascript/jquery toggle an element for each list item

I do a for loop in javascript to get the title and description for each item in my json file.
for (var i = 0, len = mydata.texts.length; i < len; i++) {
list +="<li>"+"<div class='circle'></div>" +
"<span onclick='toggleDesc()'>"+ mydata.texts[i]["keyword"] +
"</span>"+
+"</li>"+
"<div id='descr'>"+mydata.texts[i]["description"]+"</div>";
}
function toggleDesc() {
var x = document.getElementById('descr');
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
The result is like:
1. Title1
description1
2. title2
description2
3. Title3
Description3
...
Currently:
it is only toggling the first list item.
question
How can I fix it to show each list item's description on click?
Identifiers in HTML must be unique. However you can use CSS class with multiple time which can be targeted by using Class Selector .class.
for (var i = 0, len = mydata.texts.length; i < len; i++) {
list +="<li>"+"<div class='circle'></div>" +
"<span class='toggleDesc'>"+ mydata.texts[i]["keyword"] +
"</span>"+
"<div class='descr'>"+mydata.texts[i]["description"]+"</div>" +
"</li>";
}
Note: I have placed descr as sibling of SPAN as <ul>/<ol> can only have LI as child
Also I would recommend, unobtrusive event handlers instead of using ugly inline event handlers.
$(document).on('click', '.toggleDesc', function(){
$(this).next('.descr').toggle();
});
References .next() and use .on() method with Event Delegation approach for dynamically generated elements. And In place of document you should use closest static container for better performance.
You have multiple elements with id descr. ids should be unique. Consider doing something this:
for (var i = 0, len = mydata.texts.length; i < len; i++) {
list +="<li>"+"<div class='circle'></div>" +
"<span onclick='toggleDesc(" + i + ")'>"+ mydata.texts[i]["keyword"] +
"</span>"+
"<div id='descr" + i + "'>"+mydata.texts[i]["description"]+"</div>"
+"</li>";
;
}
function toggleDesc(i) {
var x = document.getElementById('descr' + i);
if (x.style.display === 'none') {
x.style.display = 'block';
} else {
x.style.display = 'none';
}
}
Currently:
it is only toggling the first list item.
Because you can't use the same id with multiple elements in a page as it must be unique, and if you use it only the first one will be parsed with document.getElementById() so that's why only the first one is toggled.
Solution:
Use a class instead of an id so you can attach the event to all the elements having this class.
And in your HTML code pass a reference to the clicked span with this to your toggleDescription() function and use it to find the relevant description inside it.
This is how should be your code:
for (var i = 0, len = mydata.texts.length; i < len; i++) {
list +="<li>"+"<div class='circle'></div>" +
"<span onclick='toggleDesc(this)'>"+ mydata.texts[i]["keyword"] +
"</span>"+
"<div class='descr'>"+mydata.texts[i]["description"]+"</div>";
+"</li>"
}
function toggleDesc(item) {
$(item).next('.descr').toggle();
}
And make sure you place the description div inside the li elemnt, because a list can contain only li as direct children.
edit:
it needs a css specification to hide by default:
.descr{
display:none;
}

Generating HTML toggle buttons with JavaScript and initializing them

I have a div:
<div id="div1">
</div>
And I'm trying to generate "toggle" buttons (http://www.bootstraptoggle.com/) with JavaScript:
<script>
var count = 1;
function foo() {
var newButton = "<input type=\"checkbox\" id=\"" + count + "\" checked data-toggle=\"toggle\"><br>";
document.getElementById('div1').innerHTML += newButton;
$('#' + count).bootstrapToggle();
// also tried re-initializing the first created button, this won't work either..
//$('#1').bootstrapToggle();
count = count + 1;
}
setInterval(foo, 4000);
</script>
This creates the buttons just fine, but those have to be initialized first by calling.
$('#1').bootstrapToggle();
The problem is, that when I have generated the first button and generate the second, the first one stops working. I have also tried to re-initialize the first button in the same div, but that won't work either.
Please, if there's a better way to do this I'd be glad to receive some help (or better, example code) on how to do this. The thing is that I cannot do this other way, because I'm generating these from JSON input.
As Rajesh says, the +=innerHTML is causing problems.
This will work:
<div id="container"></div>
var count = 1;
function createButton() {
var newButton = "<input type='checkbox' id='" + count + "' checked data-toggle='toggle'><br>";
$('#container').append(newButton);
$('#' + count).bootstrapToggle();
count = count + 1;
}
setInterval(createButton, 1000);
Edit: You can also tidy things up a little to lose the ugly count global:
function createButton() {
$('<input/>', {
type: 'checkbox',
checked: 'checked',
data: {
toggle: 'toggle'
}
})
.appendTo('#container')
.bootstrapToggle();
}
setInterval(createButton, 1000);

Isotope appended broke remaining jQuery

I have an instance of Isotope on my website that seems to have broken the remainder of my javascript functions after converting it to use the appended method. When I had all of my images were initially in the markup on my index.html, both isotope's filtering as well as a lightbox plugin I had for each image were working beautifully. Now I have implemented it as follows (using most of Desandro's code for Isotope:
HTML:
<div class="main">
<div id="filters" class="button-group">
<p>FILTER:</p>
<button class="selected" data-filter="*">SHOW ALL</button>
<button class="" data-filter=".photography">PHOTOGRAPHY</button>
<button class="" data-filter=".film">FILM</button>
<button class="" data-filter=".design">DESIGN</button>
</div>
<div class="isotope">
<div class="grid-sizer"></div>
</div>
</div>
Javascript:
$(function() {
var $container = $('.isotope');
$container.isotope({
"itemSelector": ".item",
"columnWidth": ".grid-sizer"
});
function getItemPhotography(i) {
var item = '<div class="item photography"><a class = "fluidbox" href = "img/photography/' + i + 'big.jpg" title = "Click to enlarge"><img src = "img/photography/' + i + '.jpg" class = "image"/></a></div >';
return item;
}
function getItemDesign(i) {
var item = '<div class="item design"><a class = "fluidbox" href = "img/design/' + i + 'big.png" title = "Click to enlarge"><img src = "img/design/' + i + '.png" class = "image"/></a></div >';
return item;
}
var $items = getItems();
// hide by default
$items.hide();
// append to container
$container.append($items);
$items.imagesLoaded().progress(function(imgLoad, image) {
// image is imagesLoaded class, not <img>
// <img> is image.img
var $item = $(image.img).parents('.item');
$item.show();
$container.isotope('appended', $item);
});
function getItems() {
var items = '';
for (var i = 1; i < 38; i++) {
items += getItemPhotography(i);
}
for (var i = 1; i < 4; i++) {
items += getItemDesign(i);
}
// return jQuery object
return $(items);
}
});
This part was broken after adding in the above:
//Filtering for isotope
$(function() {
$('#filters').on('click', 'button', function() {
var filterValue = $(this).attr('data-filter');
$container.isotope({
filter: filterValue
});
});
$("button").click(function() {
// Reset them
$("button").removeClass("selected");
// Add to the clicked one only
$(this).addClass("selected");
});
});
//Fluidbox lightbox plugin
$(function() {
$('.fluidbox').fluidbox();
});
The images are appending to the Isotope container and laying out as expected. However, now filtering is broken for Isotope, and the Fluidbox lightbox instance is not working either.
Thanks so much for any help. I have been struggling with this for the past few hours and am starting to think it may be as simple as my function initializations or a semicolon somewhere.
Resolved. Missing a reference for $container in the function for isotope filters.

Javascript Add/Remove unique cloned div

I have a dropdown that I want to be cloned and have a unique id. I managed to do it and it works on my website.
I am trying to make a "x" button to removed added clones and I can't get it to work.
The javascript:
var counter = 1;
function addInput(divName, template){
if (counter == 5) {
document.getElementById("add_more_text").remove();
} else {
var newdiv = document.createElement('div');
newdiv.innerHTML = document.getElementById(divName).innerHTML;
document.getElementById(template).appendChild(newdiv);
counter++;
}
var selectElements = document.querySelectorAll('select');
for (var i = 0; i < selectElements.length; i++){
selectElements[i].id = 'id-' + i;
selectElements[i].name = 'category' + i;
}
}
function removeInput(divName, template){
document.getElementById(template).removeChild(divName);
counter--;
}
The html:
<div id="template">
<select name="category0"><option>hi</option></select>
x
</div>
<div id="add_more"></div>
+ Add more
DEMO
Any help is much appreciated!
Simpler to modify remove function as follows:
function removeInput(obj) {
if (obj.parentNode.className == 'added') {
obj.parentNode.parentNode.removeChild(obj.parentNode);
counter--;
}
}
And have a link in template like this:
x
Class added is to distinguish new clones that can be removed:
newdiv.className = 'added';
Demo: http://jsfiddle.net/rjXXa/2/
in your onClick property
x
you are passing template and add_more
And in the handler function
function removeInput(divName, template){
the parameters are in a different order, so divName will contain 'template' and template will contain 'add_more'. Even if you fix this,
document.getElementById(template).removeChild(divName); // will throw error
because the div#add_more is not a child of div#template.
For fixing this, you need to pass a reference to the clicked element, like the following
x
and in your function
function removeInput(anchor){
var clone = anchor.parentNode; // div containing the anchor
if(clone.id!='template'){ // make sure we're not removing the original template
clone.parentNode.removeChild(clone);
counter--;
}
}
as in this Fiddle
Update
It's better to remove the add more option from display using css and make it visible later than removing/appending it in DOM, as follows
change the following in addInput() function
if (counter > 4) {
document.getElementById("add_more_text").style.display='none';
}
and in removeInput() function add
if (counter < 5) {
document.getElementById("add_more_text").style.display='block';
}
as in this Fiddle

Limit the input in Fieldset

I am trying to create a section of my webstore where the customer can 'build' their own bundle and choose any combination of 5 items.
I have a set of buttons which, when clicked, add their value to a Fieldset along with a button to remove it in case they misclicked or changed their mind.
All the components work fine, but I don't know how to limit the Fieldset to only five items. Is there a way to either count the lines, then stop accepting input
after five or look for 'Remove' five times?
I'm still fairly new to coding and not too sure what is possible.
This input will end up being submitted in a form.
Here is my Fiddle and below is my Javascript code which i have tried for it :
$(document).ready(function () {
$(".buttons").click(function () {
var intId = $().length + 1;
var item = $(this).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + intId + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
removeButton.click(function () {
$(this).parent().remove();
});
fieldWrapper.append(size);
fieldWrapper.append(removeButton);
$("#buildyourkit").append(fieldWrapper);
});
});
This will give you the current quantity of elements added to the . Just make sure that there is still room for another before appending a new one.
$("fieldset .fieldwrapper").length
I've forked your fiddle. Just look at the console while adding new items to the fieldset.
You can have a global variable which will count up and disable all buttons if over 5 every time you add a field, and down and enable all buttons every time you remove a field.
Also, it is a bit nicer to just set a live handler listening for any remove buttons, rather than make a new function and bind a new listener for each button, so I demonstrated; but it is not obligatory (your way works, too, given it's just 5 elements).
$(document).ready(function () {
var buttonMaxID = 0;
var buttonCount = 0;
$('$buildyourkit').on('click', '.remove', function () {
$(this).parent().remove();
if (buttonCount-- >= 5) {
$('.buttons').prop('disabled', false);
}
});
$(".buttons").click(function () {
if (++buttonCount >= 5) {
$('.buttons').prop('disabled', true);
}
var item = $(this).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + (buttonMaxId++) + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
fieldWrapper.append(size);
fieldWrapper.append(removeButton);
$("#buildyourkit").append(fieldWrapper);
});
});
What I propose is designing a manager class to maintain all functions/methods that must interact with the UI. This allows you to define your data set in one place, and keep the UI binds in one place. By doing so, you set yourself up with a cleaner code base, easy refactoring, and quickly make code modifications. Also, you get all this goodness without any global variables, another great bonus.
The code does look like its larger, but once you understand the simplicity of the manager you will see the possibilities I outlined above.
$(document).ready(function () {
//Create a new Kit Manager
var kitManager = new KitManager();
$(".buttons").click(function () {
kitManager.add(this);
});
$(".report").click(function () {
kitManager.getKit();
});
});
function KitManager()
{
//Static amount of items to return
var MAX_ITEMS = 5;
//Where the items should be visually displayed on the UI
var kitLegend = $("#buildyourkit");
//Internal array for storing the items added
var items = []
function add(element)
{
if(items.length < MAX_ITEMS)
{
var itemNumber = items.length + 1;
var item = $(element).html();
var fieldWrapper = $("<div class=\"fieldwrapper\" id=\"field" + itemNumber + "\"/>");
var removeButton = $("<input type=\"button\" class=\"remove\" value=\"Remove\" />");
//Add item to the array collection
items.push(item);
//Bind a remove function to the newly created button
removeButton.click(function () {
kitLegend[0].removeChild(fieldWrapper[0]);
items.splice(itemNumber, 1);
});
//Append UI components to container
fieldWrapper.append(item).append(removeButton);
//Append to main legend
kitLegend.append(fieldWrapper);
}
else
{
//Simple alert to user
alert('You\'ve Reached The Maximum Number of Items');
}
}
//Simple function for demonstration of a reporting feature
//or potential method for returning the stored items
function getKit()
{
for(var i=0,length=items.length;i <length;i++)
{
console.log(items[i]);
}
}
//Expose public method call
return {
add:add,
getKit: getKit
};
}
http://jsfiddle.net/x97S5/
I hope you find the solution acceptable, and if you have any further questions please ask.
For more information on the solution and technique proposed take a look at Key Principles of Maintainable JavaScript

Categories

Resources