Understanding GSAP accordion - javascript

I'm trying to understand this piece:
https://codepen.io/GreenSock/pen/RwVgEgZ
The hard thing for me is to understand the select property. AFAIK, this is a property of a <select> HTML element, that it is no present in this HTML. And a particular use of forEach.
This is the JS as I understand:
// toArray GSAP tool
// https://greensock.com/docs/v3/GSAP/UtilityMethods/toArray()
// Store an array with .accordion-group elements
let groups = gsap.utils.toArray(".accordion-group");
// Store an array with .accordion-menu elements
let menus = gsap.utils.toArray(".accordion-menu");
// Apply createAnimation(element) for each array element
// This creates animations for each .accordion-group
// and store it in a variable
let animations = groups.map(createAnimation);
// Add click event listener to each .accordion-menu
// that fires playAnimation(selected) on click
menus.forEach(menu => {
menu.addEventListener("click", () => playAnimation(menu));
});
//
function playAnimation(selected) {
// I don't undestand this particular use of forEach
// what means animate => animate(selected)?
// what means selected? I search this property on MDN web docs with no luck
animations.forEach(animate => animate(selected))
}
// CreateAnimation function
function createAnimation(element) {
// Create colections of .accordion-menu and .accordion-content
let menu = element.querySelector(".accordion-menu");
let box = element.querySelector(".accordion-content");
// GSAP initial set height of .accordion-content to auto
gsap.set(box, { height: "auto"})
// GSAP tween reversed. I have no problem with this
let tween = gsap.from(box, {
height: 0,
duration: 0.5,
ease: "power1.inOut"
}).reverse();
// CreateAnimation() returns the tween reversed if it is not selected
return function(selected) {
// Ternary operator.
// Store true in the reverse variable if menu is not selected
// Get !tween.reversed() (This means true if tween is not reversed or false if it is reversed) and store it in reversed variable.
let reversed = selected !== menu ? true : !tween.reversed();
// return tween reversed or not reversed regarding reversed variable
tween.reversed(reversed);
}
}
In short, what I want to know is: what does this mean: animate => animate(selected)? What means selected? I searched this property on MDN web docs with no luck.

While chliang is not wrong, I feel like explaining the process of what's going on in general is valuable as I think your understanding is not quite correct.
Here's the steps of what happens when the JS is ran:
The DOM elements are selected.
The groups are looped through and an animation for each group that animates the height of that specific group is created. This animation will be re-used each time that the group is animated (i.e. a new animation will not be created, only the state of this animation will be changed — this is a good technique of animating efficiently).
With that being said, what is actually returned is not a reference to the animation itself but rather a function that controls the reversed state of the animation. It'd probably help to look at the .reversed() documentation.
The menu items are looped through, adding a click event listener to each. The event listener for each menu item passes in that menu item to the function that was returned in the last step. Inside of the function (returned from the last step), if the menu item is the same as the one that is clicked, the reversed state for that animation is set to the opposite of what it currently is, usually being set to true, meaning the animation will play forwards. However if it's the same one that's clicked and it's reversed state is already false this means that it's already opened or opening, so its reversed state will be changed false.
For all other animations, the reversed state will be set to true, meaning it will play backwards if its progress is not 0. It will do nothing if its progress is 0.
To be clear, the HTML <select> element is not being used and is unrelated to the demo. It achieves similar behavior but cannot be customized to behave like this accordion.
With all of that being said, I probably would have written this code in a different way to be more readable, like this.
Let me know if you have any more questions.

First of All "animations" is an Array which has 5 functions .
animations.forEach
will pass every function to the brackets next to it,suppose the first function is fun1 .
selected is the param of The Function "playAnimation" , in fact selected is one menu of the menus ,then passed to the function eg. fun1 , fun1(selected) triggers.
replace fun1 to animate .

Related

Is My Approach to the Todo App Delete Function Wrong?

I am learning React and just created a simple todo app using only React. My todo app has the standard structure of having a text input and an "ADD" button next to it. The user would type their todo in the input and every time they click on the "ADD" button next to it, a new ordered list of their inputs would appear underneath the input and "ADD" button.
The user can also delete a todo entry by clicking on the entries individually, like this:
To accomplish this behaviour of deleting entries, I used this delete function:
delete(elem) {
for (var i = 0; i < this.state.listArray.length; i++) {
if (this.state.listArray[i] === elem) {
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
break;
}
}
}
My todo app works exactly the way that I want it to work, but as I look at other people's more conventional approach to this delete function, they either just simply use the splice method or the filter method.
For the splice method approach, they apparently just simply "remove" the unwanted entry from the listArray when the user clicks the particular entry. This does not work for me as using this method results in all my entries getting deleted except for the entry that I clicked on, which is the one that I want to delete.
On the other hand, the filter method approach apparently works by comparing the elem, which is the data passed from a child component, with each element in the listArray, and if the element in the for loop does not equal to the elem, then it would be passed onto a new array. This new array would be the one to not be deleted. This approach works better than the simple splice approach, however, one problem that I had encountered with this approach is that if I have more than one entry of the same value, for example, "Feed the dog". I only want one of the "Feed the dog" entries to be deleted, but it deletes both of them.
I thought of an approach to tackle this problem, eventually coming up with the current version of my code, which uses the splice method, but the splice method is used before I set it in the state. As evident here:
this.state.listArray.splice(i, 1);
this.setState({
listArray: this.state.listArray
});
My question can be broken down into three subquestions:
Considering that React states should be immutable, is the first line of the code above mutating my state? Is this approach not okay?
I thought that all React states were only possible to be changed inside a "setState" function, but my first line of code from above is not inside a setState function, yet it changed the state of listArray. How is this possible?
If my approach is mutating the state and is not ideal, how would you go about making the delete function so that it only deletes one entry and not more than one if there are multiple similar entries?
Yes, splice affects the array it acts on so don't use in this way. Instead you need to create a new array of the correct elements:
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== i);
});
If you want to remove only the first instance, maybe couple with a findIndex (although indexOf would work in your example as well) first:
delete(elem) {
const idxToFilter = this.state.listArray.findIndex(el => el === elem);
if (idxToFilter < 0) {
return;
}
this.setState({
listArray: this.state.listArray.filter((el, idx) => idx !== idxToFilter);
});
}
This creates a new array without modifying the old which will cause anything that reacts to listArray changing to be notified since the reference has changed.

How to swap json

In my angular code on page load there are list of buttons like button 1, button 2, button 3.. etc on click of every button it shows J-SON on console the structure is same for all buttons but the values vary. There are two more buttons on page on up and down.
My question is if button 2 is selected and i click on up button then the position of button 2 should moved up to button 1 for that we have use one attribute in j-son but problem m facing is that how should i swap whole j-son like how to swap position of button 2 to button 1 and vice-versa for down arrow
searched for swapping on google but it showing items in array to swap. i want whole j-son to swap
up(){
exchange(this.jsondata,up,up+1);
}
private exchange(array: any, x: any, y: any) {
const temp = array[x];
array[x] = array[y];
array[y] = temp;
return array;
}
it should change the position of buttons on click of up down buttons
If what you want is swapping two elements in an array, your exchange function seems ok.
But you are calling it with only 2 parameters : up(up, up+1) while your function takes 3 parameters :
the array where the items are
the first item
the second item
If your buttons were in an array named myButtons, you would probably want to call it that way :
up(myButtons, up, up+1);
Side notes :
be careful what you're naming your parameters. Array is a global JavaScript object. It's not a good practice to use that name for a variable.
up is obviously the name of a function and a variable. That is kind of confusing.
your function returns the array that was passed as a paramter. That is not necessary since arrays are passed as references. You can make your function immutable and have it return a different array. Or modify the reference and not return anything.

Semantic UI React Transition Group animate first element

Semantic's Transition.Group example shown here https://react.semantic-ui.com/modules/transition/#types-group only animates the bottom most element. How would I change it so the top most element gets animated instead.
If I press the add or subtract button in the example above the animation only applies to the last element in the list, how would I make it only animate the top most element. Or essentially make the selector opposite of default so it goes from top to bottom
If you read the sample code you realize all it does is to display a slice of the array based on the current length in the state. It appears to be appending / removing from the bottom because it's taking elements starting from the index 0. If you want to show it as if it's animating at the top, you just need to take the elements starting from the last element. For example, you can do this with reverse:
#8 state = { items: users.slice(0, 3).reverse() }
#10 handleAdd = () => this.setState({ items: users.slice(0, this.state.items.length + 1).reverse() })
#12 handleRemove = () => this.setState({ items: this.state.items.slice(1) })
An easier solution using reverse would be to just reverse the array when you map through it and create the children.
render() {
const { items } = this.state;
return (<div>
{items.clone().reverse().map((item, index) => <div key={item}>
{ /* Stuff */ }
</div>)}
</div>);
}
Yes, it would create a new array on each call to render, but the key prop identifies an element and prevents unnecessary re-renders of the child elements, because though it is a new array, object references will still be the same. But I would recommend to use a more identifiable and unique value for the key prop, e.g. IDs' or slugs.
Also this way you wouldn't have to recalculate the array using reverse everywhere you use it, just at one point in your code.

.splice method not removing correct item in array in d3

I am working with a scatterplot in d3 and dots on the graph represent a paper. Dots are set to a type. The dots I want to talk about are all of type In_library . removeFromlibrary is a function which is defined in an option in a drop down menu. In this function I make a connection to the data base to update a boolean false value in the column Library.
But I also want to remove this item from my data array. And then I want to call a function called refreshGraph() which clears the graph and redraws dots using the updated array. The way I am trying to remove this item from the array (thus removing it from display once the refreshGraph function is called) is by doing the following:
title: 'Remove from Library',
action: function removeFromLibrary (elem, d, i) {
d3.json("connection9.php?paperID="+d.ID, function(error, dataJson) {
if (d.type === "In_library") {
allData.splice(d, 1);
}
refreshGraph();
})
}
So, if the dot I select is of type In_library (which it is) I want to remove THAT ITEM from the array.
This doesnt work and when I select a dot and select the remove from library option in the menu it will remove a different paper that has the type "In_library". A possible cause of this is that the index given to the .splice method is wrong..?
I am new to d3 so any help is appreciated! Thanks in advance!
Per the comments, your use of splice is wrong (reference: MDN) - rather than inputting the item itself, you need to use the item's index in the Array.
Making use of Array.prototype.indexOf should do the trick:
if (d.type === "In_library") {
var index_d = allData.indexOf(d);
if (index_d != -1) allData.splice(index_d, 1);
}

IN CQ, how to set value of all the items in Panel to blank

In ExtJS panel I need to set value of all items (e.g. textfield, pathfield) to blank. I don't want to set value of each individual item to blank but of whole panel in one go.
I am able to get list of items
function getAllChildren (panel) {
/*Get children of passed panel or an empty array if it doesn't have thems.*/
var children = panel.items ? panel.items.items : [];
/*For each child get their children and concatenate to result.*/
CQ.Ext.each(children, function (child) {
children = children.concat(getAllChildren(child));
});
return children;
}
but how to set to blank for whole panel? Please suggest what need to be done in this case.
Actually, it's not possible to do it with one liner - all at the same time. What your method returns is purely an array of objects. In fact if such syntax existed, it would iterate over all fields anyway.
Though clearing all fields, having the method you've proposed is very trivial to do. Just iterate over them all and call reset method. Mind some (especially custom) widgets might not handle it.
var fields = getAllChildren(panel);
CQ.Ext.each(fields, function(field) {
if (child.reset) {
child.reset();
}
});
You've got similar loop in your getAllChildren code - you might reset field at the same place.
The method is defined in Field type which is usually a supertype of each dialog widget. You can read more here.

Categories