how to show array element one by one onclick event in javascript? - javascript

I have textarea and storing in array onclick i need to show one by one from the last element and redo onclick one by one from where user clicks. i am doing a custom undo and redo functionality.
var stack =[];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
})
jQuery('#undo_text').click(function() {
console.log(stack.pop());
})
jQuery('#redo_text').click(function() {
// how to redo onclik where user undo text
})
I have created jsfiddle
https://jsfiddle.net/k0nr53e0/4/

Make an array of old values, so something like this:
var deleted_stack = [];
// your other code
jQuery('#undo_text').click(function() {
deleted_stack.push(stack.pop());
})
jQuery('#redo_text').click(function () {
stack.push(deleted_stack.pop());
}
If you need to be able to do this also in the middle of the text, you should keep track of cursor position. Then your deleted stack should look more like this:
deleted_stack = [
{
char_idx: 2, // index of position in string where it was deleted, but
// then you shouldn't be deleting characters with .pop()
char_val: 'a'
},
... // other objects like first one
]
But then deleting also becomes more complex...

instead if keeping different stacks for the actions you've done, and undone, you can keep them in one Array, and memorize the current position:
var stack = [ jQuery('#enter-text').val() ], index = 0;
updateButtonsDisabled();
jQuery('#enter-text').keypress(function() {
//adding the current action
stack[++index] = jQuery('#enter-text').val();
//removing the entries after the last one you added: they belong to a different redo-stack
stack.length = index+1;
updateButtonsDisabled();
})
jQuery('#undo_text').click(function() {
if(!index) return;
jQuery('#enter-text').val(stack[--index]);
updateButtonsDisabled();
})
jQuery('#redo_text').click(function() {
if(index === stack.length-1) return;
jQuery('#enter-text').val(stack[++index]);
updateButtonsDisabled();
})
//just some sugar
function updateButtonsDisabled(){
jQuery('#undo_text').toggleClass("disabled", index === 0);
jQuery('#redo_text').toggleClass("disabled", index === stack.length-1);
}
index holds the position in stack of the currently shown value. You can undo and redo as much as you want, but as soon as you start typing, the redo-stack will be cleared.
You should consider limiting the items you want to keep in stack, or you'll allocate quite some memory. And you could change the logic for keypress to wait for a pause of like 300ms before you update the stack. That would decrease the items in your stack tremendously.
Edit: made a snippet implementing the possible changes I mentioned, like detached update, and limited stacksize. Take a look at that
//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;
//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize),
left = 0,
right = 0,
index = 0,
timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();
$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);
//detach update
function detachedUpdateText() {
clearTimeout(timeout);
timeout = setTimeout(updateText, 500);
}
function updateButtons() {
//disable buttons if the index reaches the respective border of the frame
//write the amount of steps availabe in each direction into the data-count attribute, to be processed by css
$("#undo")
.prop("disabled", index === left)
.attr("data-count", index - left);
$("#redo")
.prop("disabled", index === right)
.attr("data-count", right - index);
//show status
$("#stat").text(JSON.stringify({
left,
right,
index,
"index in stack": index % stackSize,
stack
}, null, 2))
}
function updateText() {
var val = $("#enter-text").val().trimRight();
//skip if nothing really changed
if (val === stack[index % stackSize]) return;
//add value
stack[++index % stackSize] = val;
//clean the undo-part of the stack
while (right > index)
stack[right-- % stackSize] = null;
//update boundaries
right = index;
left = Math.max(left, right + 1 - stackSize);
updateButtons();
}
function undo() {
if (index > left) {
$("#enter-text").val(stack[--index % stackSize]);
updateButtons();
}
}
function redo() {
if (index < right) {
$("#enter-text").val(stack[++index % stackSize]);
updateButtons();
}
}
#enter-text {
width: 100%;
height: 100px;
}
#undo,
#redo {
position: relative;
padding-right: 1em;
}
#undo:after,
#redo:after {
content: attr(data-count);
position: absolute;
bottom: 0;
right: 0;
font-size: 0.75em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<button id="undo">undo</button>
<button id="redo">redo</button>
<pre id="stat">
</pre>

This is a design decision, and it depends fully on your user needs and how complex you are willing to make it.
The way it is usually done is to keep 2 lists, one for undo, and one for redo.
Every time an action is undone, it is added to the redo list. Every time a new action is taken after undoing, the redo list is destroyed.
var stack = [];
var redo = [];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
redo = []; // <-----
})
jQuery('#undo_text').click(function() {
redo.push(stack.pop()); // <-----
})
jQuery('#redo_text').click(function() {
stack.push(redo.pop()) // <-----
})

You can use the keyup event and keep always the entered data on the stack array. These way you can update the value of field $('#enter-text') with the appropriate array index that will be updated in the variable index:
var $textEnter = $('#enter-text'),
stack = [],
index = 0;
$textEnter.on('keyup', function () {
stack.push($textEnter.val());
index = stack.length - 1;
});
$('#undo_text').on('click', function () {
if (index >= 0) {
$textEnter.val(stack[--index]);
}
});
$('#redo_text').on('click', function () {
if (index < stack.length - 1) {
$textEnter.val(stack[++index]);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<div id="drag-area-selected">
</div>
<button id="undo_text">
Undo
</button>
<button id="redo_text">
Redo
</button>

Related

My jquery function is pushing elements to all my arrays

On my website, users can click on some text to open up a Modal. This Modal allows users to choose a bunch of toppings to add to their Pizza.
Through Javascript, I add each selected topping to an array and change the text display to match their selected toppings. This more or less works, but the problem is for some reason, whenever they add a topping, it is added to ALL arrays, not just the item it's selected for. Can someone help me find why this is happening?
// Open Toppings Modal
$(document).ready(function() {
var count = -1
var tplist = []
$(".order").each(function(){
count += 1
tplist.push([])
var listeners = 0
setModal(count, tplist, listeners)
});
function setModal(count, tplist, listeners) {
$("#openModal" + count).click(function(){
console.log("clicked")
$("#toppingModal" + count).modal()
if (listeners == 0) {
listeners += 1
$("input[type='checkbox']").change(function() {
// TODO: Fix Bug
// Adding to all javascript lists
if (this.checked) {
tplist[count].push($(this).val());
console.log(tplist)
}
else {
ele = $(this).val();
pos = $.inArray(ele, tplist[count])
if ( ~pos ) tplist[count].splice(pos, 1);
}
// Change text to list
if (tplist[count].length > 0) {
$("#openModal" + count).text(tplist[count])
}
else {
$("#openModal" + count).text("Select Toppings")
}
})
}
});
};
});
I am suspecting your $("input[type='checkbox']").change(function() {} is called for every model. Try setting count number somewhere when you click select topping and compare inside $("input[type='checkbox']").change(function() {} to prevent adding of toppings in all arrays

How to detect each "this" on click javascript

I want to basically detect click event on each card and save this card in a variable but secondGuess is shadowed by firstGuess. What's the solution?
for (let i = 0; i < cards.length; i++) {
//ADDING FLIP EFFECT TO EACH CARD
toggleFlipEffect(cards[i]);
cards[i].addEventListener("click", compareCards, false);
}
function compareCards() {
let points = 0;
let tries = 0;
const firstGuess = this.getElementsByTagName("img")[0].alt;
const secondGuess = this.getElementsByTagName("img")[0].alt;
console.log(firstGuess);
console.log(secondGuess);
if (firstGuess === secondGuess) {
points += 1;
console.log(points);
} else {
firstGuess.classList.remove("card--flip");
secondGuess.classList.remove("card--flip");
}
}
}
Without the HTML and code for toggleFlipEffect it is not possible to know how you have implemented the card flipping. In essence you need to act differently to a click on the first card than on the second card. On the first, you really only need to note which card it is. Only when it is the second time, you need to perform the comparison.
Note that when a guess is wrong, you would need to have a delay before concealing those cards again, or else the user would not have the time to see the second card.
Here is how you could do it:
function toggleFlipEffect() {} // mock-up.
var cards = document.querySelectorAll('div');
for (let i = 0; i < cards.length; i++) {
//ADDING FLIP EFFECT TO EACH CARD
toggleFlipEffect(cards[i]);
// *** add data attribute for quicker access
cards[i].dataset.value = cards[i].getElementsByTagName("img")[0].alt;
cards[i].addEventListener("click", compareCards, false);
}
// Your variables should not be local to the click event handler:
let points = 0;
let tries = 0;
let revealed = [];
function compareCards() {
// do not allow more than 2 cards to be revealed in one turn
if (revealed.length >= 2) return;
// reveal clicked card
this.classList.add("card--flip");
// add it to array with the previous one (if there was one)
revealed.push(this);
// Need another card?
if (revealed.length < 2) return; // allow for second choice
// We have two revealed cards. Check if they are the same
if (revealed[0].dataset.value === revealed[1].dataset.value) {
points += 1; // Yes, keep score
revealed = []; // initialise for next turn
score.textContent = points;
} else {
// Different: conceal these two cards again, but after a delay
setTimeout(_ => {
revealed.forEach(elem => elem.classList.remove("card--flip"))
revealed = []; // initialise for next turn
}, 1000);
}
}
.card--flip {
background-color: white;
}
div {
background-color: black;
margin: 5px;
width: 50px;
height: 50px;
display: inline-block;
}
<div><img src="https://image.flaticon.com/icons/svg/418/418278.svg" alt=1></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418268.svg" alt=2></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418275.svg" alt=3></div><br>
<div><img src="https://image.flaticon.com/icons/svg/418/418268.svg" alt=2></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418278.svg" alt=1></div>
<div><img src="https://image.flaticon.com/icons/svg/418/418275.svg" alt=3></div><br>
score: <span id="score"></span>
The question isn't very clear, but I think you need to distinguish between the first and second click on cards. For that, I suggest you to use a global bool variable, initially set to false.
var first_click = false;
Then, modify your function like this:
function compareCards() {
first_click = !first_click;
if (first_click) {
//code you need after first click
} else {
//code you need after second click
}
//code you need to do anyway independently if first or second click.
}

Event listener fails to attach or remove in some contexts

I've created a script that attaches an event listener to a collection of pictures by default. When the elements are clicked, the listener swaps out for another event that changes the image source and pushes the id of the element to an array, and that reverses if you click on the swapped image (the source changes back and the last element in the array is removed). There is a button to "clear" all of the images by setting the default source and resetting the event listener, but it doesn't fire reliably and sometimes fires with a delay, causing only the last element in a series to be collected.
TL;DR: An event fires very unreliably for no discernible reason, and I'd love to know why this is happening and how I should fix it. The JSFiddle and published version are available below.
I've uploaded the current version here, and you can trip the error by selecting multiple tables, pressing "Cancel", and selecting those buttons again. Normally the error starts on the second or third pass.
I've also got a fiddle.
The layout will be a bit wacky on desktops and laptops since it was designed for phone screens, but you'll be able to see the issue and inspect the code so that shouldn't be a problem.
Code blocks:
Unset all the selected tables:
function tableClear() {
//alert(document.getElementsByClassName('eatPlace')[tableResEnum].src);
//numResTables = document.getElementsByClassName('eatPlace').src.length;
tableArrayLength = tableArray.length - 1;
for (tableResEnum = 0; tableResEnum <= tableArrayLength; tableResEnum += 1) {
tableSrces = tableArray[tableResEnum].src;
//alert(tableSrcTapped);
if (tableSrces === tableSrcTapped) {
tableArray[tableResEnum].removeEventListener('click', tableUntap);
tableArray[tableResEnum].addEventListener('click', tableTap);
tableArray[tableResEnum].src = window.location + 'resources/tableBase.svg';
} /*else if () {
}*/
}
resTableArray.splice(0, resTableArray.length);
}
Set/Unset a particular table:
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'resources/tableBase.svg');
resTableArray.shift(this);
};
tableTap = function () {
$(this).unbind('click', tableTap);
$(this).bind('click', tableUntap);
this.setAttribute('src', 'resources/tableTapped.svg');
resTableArray.push($(this).attr('id'));
};
Convert the elements within the 'eatPlace' class to an array:
$('.eatPlace').bind('click', tableTap);
tableList = document.getElementsByClassName('eatPlace');
tableArray = Array.prototype.slice.call(tableList);
Table instantiation:
for (tableEnum = 1; tableEnum <= tableNum; tableEnum += 1) {
tableImg = document.createElement('IMG');
tableImg.setAttribute('src', 'resources/tableBase.svg');
tableImg.setAttribute('id', 'table' + tableEnum);
tableImg.setAttribute('class', 'eatPlace');
tableImg.setAttribute('width', '15%');
tableImg.setAttribute('height', '15%');
$('#tableBox').append(tableImg, tableEnum);
if (tableEnum % 4 === 0) {
$('#tableBox').append("\n");
}
if (tableEnum === tableNum) {
$('#tableBox').append("<div id='subbles' class='ajaxButton'>Next</div>");
$('#tableBox').append("<div id='cazzles' class='ajaxButton'>Cancel</div>");
}
}
First mistake is in tapping and untapping tables.
When you push a Table to your array, your pushing its ID.
resTableArray.push($(this).attr('id'));
It will add id's of elements, depending on the order of user clicking the tables.
While untapping its always removing the first table.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
resTableArray.shift(this);
So, when user clicks tables 1, 2, 3. And unclicks 3, the shift will remove table 1.
Lets fix this by removing untapped table
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'http://imgur.com/a7J8OJ5.png');
var elementID = $(this).attr('id');
var elementIndex = resTableArray.indexOf(elementID);
resTableArray.splice(elementIndex, 1);
};
So you were missing some tables after untapping.
Well lets fix tableClear,
You have a array with tapped tables, but you are searching in main array.
function tableClear() {
tableLen = resTableArray.length;
for (var i = 0; i < tableLen; i++) {
var idString = "#" + resTableArray[i];
var $element = $(idString);
$element.unbind('click', tableUntap);
$element.bind('click', tableTap);
$element.attr("src", 'http://imgur.com/a7J8OJ5.png');
}
resTableArray = [];
}
Im searching only tapped tables, and then just untap them and remove handlers.
fiddle: http://jsfiddle.net/r9ewnxzs/
Your mistake was to wrongly remove at untapping elements.

Show Next/Previous item of an array

I'm writing the first item of an array to the screen, and would like to create Next/Previous buttons for array, but I can't get it to work. I have tried several methods, but I can't find suitable solution.
Can anyone help?
This is the last one I have tried:
<div>
<script type="text/javascript">
sav = new Array(
"first item",
"second item",
"third item",
);
document.write(sav[0] + " <br>");
</script>
</div>
<div>
Previous
Next!
</div>
Say you have an Array var arr = ['foo', 'bar', 'baz'];.
If you want to dynamically choose items from this Array, you'll need a new variable. Let's call this i and give it a default value var i = 0;
So far, arr[i]; // "foo" (i === 0)
Next and Previous
Now, lets write a function to choose the next item by modifying i. We may want to consider what we want to happen when i is bigger than (or equal to) arr.length as well.
function nextItem() {
i = i + 1; // increase i by one
i = i % arr.length; // if we've gone too high, start from `0` again
return arr[i]; // give us back the item of where we are now
}
Next, lets do the reverse, this time we might want to consider what should happen for negative i
function prevItem() {
if (i === 0) { // i would become 0
i = arr.length; // so put it at the other end of the array
}
i = i - 1; // decrease by one
return arr[i]; // give us back the item of where we are now
}
So far,
nextItem(); // "bar" (i === 1)
prevItem(); // "foo" (i === 0 as we did `0 + 1 - 1`)
// also
prevItem(); // "baz" (decreased on 0)
nextItem(); // "foo" (increased at end of arr)
Great, so we've got the basic algorithms down.
Connecting this to the DOM
First thing to note is that document.write is nearly always a bad idea. Instead, why not give our Elements some unique id attributes and use DOM methods in JavaScript after the Elements exist.
<div id="output"></div>
<div>
<span id="prev_button">Previous</span>
<span id="next_button">Next!</span>
</div>
So now we can access the first <div> in JavaScript as document.getElementById('output'), and the two <span>s similarly.
Now, let's set the initial text in the <div>, this is quite easy
document.getElementById('output').textContent = arr[0]; // initial value
// if i is still at it's default value, we could have used i instead of 0
Next, we need to add event listeners to the <span>s so they perform an action. The handler of each will set the text of the <div> in a similar way to above, but using the relevant function from earlier.
document.getElementById('prev_button').addEventListener(
'click', // we want to listen for a click
function (e) { // the e here is the event itself
document.getElementById('output').textContent = prevItem();
}
);
document.getElementById('next_button').addEventListener(
'click', // we want to listen for a click
function (e) { // the e here is the event itself
document.getElementById('output').textContent = nextItem();
}
);
This is great! Now the only thing left to do is make sure it runs "after the Elements exist". There are two ways to do this, either by putting the <script> element after the elements it uses, or by listening for the load event on window, i.e.
window.addEventListener('load', function () {
// DOM related JavaScript goes here
});
DEMO of everything together
If you want to do this multiple times or are mixing it with other JavaScript, you may need to consider variable name conflicts. The easiest way to get around this is by using an IIFE to create a "safe place" for your variables, but this answer is already long enough.
Try this way easier and corrected.
<script type="text/javascript">
var text = [
"first item",
"second item",
"third item"
];
var Current = 0;
document.getElementById("textHere").innerHTML = text[Current];
function Prev(){
if(Current == 0){
Current = text.length - 1;}
else{
Current--;}
document.getElementById("textHere").innerHTML = text[Current];
}
function Next(){
if(Current == text.length - 1){
Current = 0}
else{
Current++;}
document.getElementById("textHere").innerHTML = text[Current];
}
</script>
<div id="textHere"></div>
<div>
<button onclick="Next();">Next!</button>
<button onclick="Prev();">Previous</button>
</div>
Try this way, easier and corrected. -Alfa College Application Developer.
<script type="text/javascript">
var text = [
"first item",
"second item",
"third item"
];
var Current = 0;
document.getElementById("textHere").innerHTML = text[Current];
function Prev(){
if(Current == 0){
Current = text.length - 1;}
else{
Current--;}
document.getElementById("textHere").innerHTML = text[Current];
}
function Next(){
if(Current == text.length - 1){
Current = 0}
else{
Current++;}
document.getElementById("textHere").innerHTML = text[Current];
}
</script>
<div id="textHere"></div>
<div>
<button onclick="Next();">Next!</button>
<button onclick="Prev();">Previous</button>
</div>

Duplicate and change a function

I have this code here:
function slideDown(){
//get the element to slide
var sliding = document.getElementById('slideDiv'+x);
//add 1px to the height each time
sliding.style.height = parseInt(sliding.style.height)+5+'px';
t=setTimeout(slideDown,15);
if (sliding.style.height == "401px"){clearTimeout(t);}
}
which gets element slideDivx and increases its height until it reaches 401px.
I have 20 of these divs (each called slideDiv1, slideDiv2, ...) and what I want is for div 10 to start, then when it has reached about 100px in height, I want 9 and 11 to start and so on so forth.
I've tried setInterval and increased x each time, but that doesn't work because it stops the previous div animating and moves on to the next one before it's finished. I think that's because I'm changing that function right?
What I'm wondering is, do I have to duplicate the function for each set of divs or can I automate that?
I have to be able to do it all in native JavaScript rather than jQuery because it's for a university project.
It sounds like you simply need to create slideDown with a parameter and then send your set of elements in an array like so:
var elementIntervals = [];
function animateElements(elementArray)
{
for(var j = 0; j < elementArray; j++)
{
(function(element, index)
{
elementIntervals[index] = setTimeout(function()
{
slideDown(element, index);
}, 15 + (100 * index));
})(elementArray[j], j);
}
}
function slideDown(sliding, index)
{
sliding.style.height = parseInt(sliding.style.height)+5+'px';
if(sliding.style.height == "401px")
clearTimeout(elementIntervals[index]);
else
elementIntervals[index] = setTimeout(function()
{
slideDown(sliding, index);
}, 15);
}

Categories