Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamically adding and removing cards from the stack #4

Open
jorjordandan opened this issue Nov 1, 2014 · 15 comments
Open

Dynamically adding and removing cards from the stack #4

jorjordandan opened this issue Nov 1, 2014 · 15 comments
Labels

Comments

@jorjordandan
Copy link

I've got the cards working perfectly in a prototype Meteor.js app, and I'm trying to add and remove elements from the stack. I haven't spent too much time trying, but I thought it might make a good example. Thanks again!

@sachasayan
Copy link

I'd appreciate this as well — haven't gone through the source yet, but I'm having trouble getting it to behave properly.

@gajus
Copy link
Owner

gajus commented Nov 27, 2014

Can you describe a scenario? http://gajus.com/sandbox/swing/examples/card-state/ Example shows how to throw out card. Throw in would be close to the same.

@jorjordandan
Copy link
Author

What I was trying to do was add cards to the bottom of the stack without throwing them in. I eventually added an extra targetPosition argument to the createCard function... but i never actually got it working. I can make a fork to show what I did, but not sure how helpful that will be since it didn't work.

@tskweres
Copy link

tskweres commented Apr 6, 2015

any updates or examples on this? What would be nice is to add cards to the bottom of the stack as cards are swiped from the top

@ronobot
Copy link

ronobot commented Apr 16, 2015

I've been looking into how to dynamically add new cards to the bottom of the stack, and I seem to have something working.

As far as I can tell, the issue seems to be a combination of two things: HTML display order (in the absence of declared z-indexes, nodes at the bottom of the tree appear on top of nodes earlier in the tree), and the Card object's appendToParent method, which moves any new card to the bottom of the tree (and thus the top of the visual stack).

My solution was to remove the appendToParent call from createCard, and replace it with a new method called insertBelow, which uses parent.insertBefore(element,parent.firstChild) to add the card to the top of the node list (and thus the bottom of the visual stack). In the case of my prototype, this has the added benefit of the visual stack order matching the order of the JSON data that I'm using to generate the cards.

I declared the new method directly after appendToParent:

Card.insertBelow = function (element) {
    var parent = element.parentNode;
    parent.removeChild(element);
    parent.insertBefore(element,parent.firstChild);
};

(Originally I attempted to alter appendToParent, but then I realized it was also being used for when a thrown card is picked up again. That's why I decided to make the new insertBelow method and leave appendToParent as it was. I also tried reversing the order of the array being used to create the cards, but that only ever had the illusion of effectiveness.)

Code-wise, I don't know if this is a good solution, since it adds an extra DOM manipulation everytime a card is added (whereas appendToParent seems to only act if the card is not at the bottom of the node tree, which is always the case when generating a new stack). And I haven't really tested it yet. But it's working so far, in both a plain JS prototype and an AngularJS prototype, so maybe it could be of use to people?

@jorjordandan
Copy link
Author

Nice! I will have to try that out later in a meteor project I was working on.

@HugoHeneault
Copy link

@ronobot: do you have some working code to provide? I'm faving the same issue than you...
Thanks a lot!

@ronobot
Copy link

ronobot commented Oct 26, 2015

@HugoHeneault The project I was working on this for got indefinitely delayed, so I wasn't able to test it further. But basically what I did was:

  1. Un-minified the swing.js file
  2. At (or around) line 1400, replaced l.appendToParent(e) with l.insertBelow(e)
  3. At (or around) line 1497, added the following function after the i.appendToParent function:
i.insertBelow = function(t) {
    var e = t.parentNode;
    e.removeChild(t);
    e.insertBefore(t,e.firstChild);
}

That's it—now the createCard method uses insertBelow instead of appendToParent.

@samstarling
Copy link

Has anyone ever managed to make this work? I want to be able to add a card to the bottom of the stack, and addCard adds it to the top.

@adelura
Copy link
Contributor

adelura commented Jan 7, 2016

I have some working prototype. Maybe it might help you (please note that, this is pseudocode):

var cards = []; // In a list
var queue = []; // In a queue
var stackMaxLength = 2;

var stack = Swing.Stack();
var ul = document.querySelector('ul.stack'); // Should be initially empty

var allCards = [
    document.createElement('ul'),
    document.createElement('ul'),
    document.createElement('ul'),
    document.createElement('ul'),
    document.createElement('ul')
];

allCards.forEach(addOrKeep);

function addOrKeep(element) {
    if (cards.length < stackMaxLength) {
        cards.push(card);

        // Adding element to DOM tree, as far as I remember to create it have to be in a DOM tree.
        ul.prepend(element);

        var card = stack.createCard(element);

        // On front
        ul.prepend(element);

        card.on('throwout', function () {
            var card = queue.shift();

            if (card) {
                stackMaxLength++;
                addOrKeep(card);
            }
        });

    } else {
        // We don't want to have it in a DOM.
        card.$element.remove();
        queue.push(card);
    }
}

If you want me to make it working example, let me know.

@HugoHeneault
Copy link

If you have some time to implement it, it'll be really awesome!!

2016-01-07 23:30 GMT+01:00 Artur Delura notifications@github.com:

I have some working prototype. Maybe it might help you (please note that,
this is pseudocode):

var cards = []; // In a listvar queue = []; // In a queuevar stackMaxLength = 2;
var stack = Swing.Stack();var ul = document.querySelector('ul.stack'); // Should be initially empty
var allCards = [
document.createElement('ul'),
document.createElement('ul'),
document.createElement('ul'),
document.createElement('ul'),
document.createElement('ul')
];
allCards.forEach(addOrKeep);
function addOrKeep(element) {
if (cards.length < stackMaxLength) {
cards.push(card);

    // Adding element to DOM tree, as far as I remember to create it have to be in a DOM tree.
    ul.prepend(element);

    var card = stack.createCard(element);

    // On front
    ul.prepend(element);

    card.on('throwout', function () {
        var card = queue.shift();

        if (card) {
            stackMaxLength++;
            addOrKeep(card);
        }
    });

} else {
    // We don't want to have it in a DOM.
    card.$element.remove();
    queue.push(card);
}

}

If you want me to make it working example, let me know.


Reply to this email directly or view it on GitHub
#4 (comment).

@miczed
Copy link

miczed commented Jan 9, 2016

I spent a couple of hours trying to add cards to the stack dynamically but I finally made it!

The problem is the following: When you call the function createCard() the function creates a new Card object and its constructor calls the function appendToParent(), which makes the Card appear on top of all other cards. If you change this function call, you can prepend cards to the stack. An even better solution would be to have two separate functions or a function parameter.

1.) Do what @ronobot suggested:
Add a new function called insertBelow() and change the function call in the constructor of Card from appendToParent(..) to insertBelow(...)
I uploaded my modified version to Codepen:
http://codepen.io/miczed/pen/wMdevy?editors=001

2.) Write a function in JS (I used jQuery) to add a Card to the stack:

  function addNewCard(data,id) {
    var newCard = $(cardTemplate).clone();
    $(newCard).find('.content').html(data);
    $(newCard).attr('id','card_' + id);
    $('.stack').prepend($(newCard));
    stack.createCard(document.getElementById('card_' + id));
  }

I'm no JS / jQuery expert so this might not be best practice but it seems to work so far.

I have a working extended example here:
http://codepen.io/miczed/pen/yebbKE

Hope this helps someone out and thanks to @gajus for the awesome plugin!

@rajeevriitm
Copy link

tried the first solution . I am unable to get it working . In my case I do not add cards one by one. Cards are created when i initialize it with an array from an API call. One more card is thrown out when first card is thrown out. What does e.removeChild(t); do in insertBelow function ?

This is my card.js

Card.insertBelow = function (element) {
  var parentNode = element.parentNode;
  parentNode.removeChild(element);
  parentNode.insertBefore(element, parentNode.firstChild);
};

in construct
Card.insertBelow(targetElement);

Please help.

@rajeevriitm
Copy link

The issue was caused by pop() on array, which had to be replaced by shift()

gajus pushed a commit that referenced this issue Apr 18, 2017
* Bugfix [onSpringUpdate used Card.transform instead of config.transform]

* Optionally prepend cards instead of appending. Possible fix for #4

* Syntax consistency + fixed comments

* updating param for stack.createCard function

* adding @param for prepend to card constructor
@tonysbriggs
Copy link

Hey guys,

Having a similar issue...

I'm looking to add cards into the stack when a checkbox is turned on.

When a user clicks a checkbox, the associated cards are entered into the stack and users are able to swipe through them.

I can't seem to get the card stack to update when the selectedCards array is updated.

Would appreciate any help / advice.

Cheers

`export default class Cards extends Component {
constructor(props, context) {
super(props, context);
this.config = {
throwOutDistance: () => Math.max(window.innerWidth, window.innerHeight),
throwOutConfidence: () => 1,
allowedDirections: [
Direction.LEFT,
Direction.RIGHT,
]
};

const fakeEvent = {
  target: {
    id: 'digiDesign',
  }
};

this.cardRefs = new Map();

this.state = {
  selectedCategories: ['digiDesign', 'branding'],
  selectedCards: this.filterArray(fakeEvent),
  cardStack: cardData,
  currentCard: cardData.length - 1,
  cardIndex: 0,
  checked: false,
};

}

componentDidMount() {
const { selectedCards } = this.state;

//Intiates key listener
document.addEventListener("keydown", this.onDirection);

//Configures Swing.js
this.stack = Stack(this.config);
this.stack.on("dragstart", () => this.setState({ dragging: true }));
this.stack.on("dragend", () => this.setState({ dragging: false }));
this.stack.on("throwout", this.onThrowOut.bind(this));

//Config stack of cards
this.configStack();

//Logs initial cards
console.log(selectedCards, 'selected cards')

}

componentWillUnmount() {
this.cardRefs.forEach((ref, i) => {
const el = ReactDOM.findDOMNode(this.cardRefs.get(i));
const card = this.stack.getCard(el);
card.destroy();
});
}

configStack(){
const {selectedCards} = this.state;
this.cardRefs.forEach((ref, i) => {
const el = ReactDOM.findDOMNode(this.cardRefs.get(i));
this.stack.createCard(el, true);
});

this.setState({
  currentCard: selectedCards.length - 1
})

}

//Controls keycodes
onDirection = e => {
if(e.keyCode === 37){
this.throwoutLeft();
} if (e.keyCode === 39) {
this.throwoutRight();
}
}

//Controls throwout right with key
throwoutRight(){
const el = ReactDOM.findDOMNode(
this.cardRefs.get(this.state.currentCard)
);
const card = this.stack.getCard(el);
card.throwOut(1000, 0);
}

//Controls throwout left with key
throwoutLeft(){
const el = ReactDOM.findDOMNode(
this.cardRefs.get(this.state.currentCard)
);
const card = this.stack.getCard(el);
card.throwOut(-1000, 0);
}

//Controls swipe/grab
onThrowOut() {
const { currentCard } = this.state
const activeCard = this.state.currentCard

if (activeCard > 0){
  this.setState({
    currentCard: activeCard - 1
  })
}

if (activeCard === 0){
  this.setState({
    currentCard: activeCard - 1
  })
  this.resetDeck();
}

const card  = this.cardRefs.get(currentCard)
console.log(card.props, 'thrown out')

}

//Resets the deck on finish
resetDeck() {
const { cardStack } = this.state

this.setState({
  currentCard:cardStack.length - 1,
  resetting: true
});

this.cardRefs.forEach((ref, i) => {
  const el = ReactDOM.findDOMNode(this.cardRefs.get(i));
  const card = this.stack.getCard(el);
  card.throwIn(0, 0);
});

this.setState({
  resetting: false,
});

}

//Controls checkboxes
filterArray(e){
let filteredArray = cardData.filter(function(x){
return x.id === e.target.id
});
return filteredArray
}

removeArray(e){
const{ selectedCards } = this.state;
for (let i = selectedCards.length-1; i >= 0; i--){
if(selectedCards[i].id === e.target.id){
selectedCards.splice(i, 1)
}
}
}

handleChange(e){
const{ selectedCards } = this.state;

if (e.target.checked){
  for (let i = 0; i < this.filterArray(e).length; i++){
    selectedCards.push(this.filterArray(e)[i])
  }
} if (!e.target.checked){
  this.removeArray(e);
}

this.setState({
  selectedCards
})

console.log(selectedCards, 'selected cards')

}

render() {
const { selectedCards } = this.state;
return (




{selectedCards.map((card, i) => {
return (
<Card
selectedCategories={this.state.selectedCategories}
key={card.advice}
ref={c => this.cardRefs.set(i, c)}
advice={card.advice}
id={card.id}
tag={card.tag}
isSelected={this.state.selectedCategories.indexOf(card.id) > -1}
active={i === this.state.currentCard}
next={i === this.state.currentCard - 1}
previous={i > this.state.currentCard}
dragging={
(i === this.state.currentCard && this.state.dragging) ||
this.state.resetting
}
/>
)
})}


{
filterData.map((item, i) => {

          const active = i === 0;
          return (
            <Filters
              ref='input'
              key={item.id}
              id={item.id}
              label={item.label}
              onChange={(e) => this.handleChange(e)}
              active={active}
            />
          )
        })
      }
    </FilterContainer>
  </Viewport>
  </div>
);

}
}`

gajus pushed a commit that referenced this issue Apr 5, 2020
* Bugfix [onSpringUpdate used Card.transform instead of config.transform]

* Optionally prepend cards instead of appending. Possible fix for #4

* Syntax consistency + fixed comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests