1

So, I've been trying for the last days to get Shuffle.js to work with my cards in Bootstrap 4, in order to have a nice shuffling effect when searching/filtering those cards.

Here follows the structure of my HTML and my JS. You can also find here the JSFiddle.net link.

class Card {
  constructor(ref) {
    this.hmi_ref = ref;

    // Bootstap : container type
    this.BS = {}
    this.BS.container = document.createElement('div');
    this.BS.card = document.createElement('div');
    this.BS.image = document.createElement('img');
    this.BS.info = document.createElement('div');
    this.BS.title = document.createElement('h4');
    this.BS.link = document.createElement('a');

    this.BS.card.appendChild(this.BS.link);
    this.BS.link.appendChild(this.BS.image);
    this.BS.card.appendChild(this.BS.title);
    this.BS.container.appendChild(this.BS.card);

    this.BS.container.className = 'col-4 mb-3';
    this.BS.card.className = 'card h-100';
    this.BS.image.className = 'card-img-top';
    this.BS.title.className = 'card-title text-center align-middle';
  }

  add(name, image, page_link) {
    this.BS.image.src = image;
    this.BS.title.textContent = name;
    this.BS.link.href = page_link;
    let newNode = this.BS.container.cloneNode(true);
    this.hmi_ref.appendChild(newNode);
  }
}

let myCard = new Card( document.getElementById('card-space') );
[
    {title: 'Vacanza studio Londra', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Roma', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Bangkok', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Catania', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanze studio"},
    {title: 'Vacanza studio Siracusa', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Ragusa', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Trapani', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
].map(e => myCard.add(e.title, e.img, e.link, e.category));

class Shuffler {
    constructor(element) {
        this.shuffle = new window.Shuffle(element, {
            itemSelector: '.card',
            sizer: element.querySelector('.sizer'),
        }); 
        document.getElementById('searchBox').addEventListener('keyup', this._handleSearchKeyup.bind(this));
    }

    /**
     * Filter the shuffle instance by items with a title that matches the search input.
     * @param {Event} evt Event object.
     */
    _handleSearchKeyup(evt) {
        const searchText = evt.target.value.toLowerCase();
        this.shuffle.filter(element => {
            console.log('filtering...');
            const titleText = element.querySelector('.card-title').textContent.toLowerCase().trim();
            return titleText.indexOf(searchText) !== -1;
        });
    }
}

window.onload = () => {
    window.demo = new Shuffler(document.querySelector('#card-space'));
}  
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

<div class="container pt-3">
  <div class="row">
    <div class="col">
      <!-- Main column -->
      <div class="row pt-4">
        <div class="col-9">
          <div id="card-space" class="row h-100">
            <div class="col-1@sm sizer"></div>
          </div>
        </div>
        <div class="col-3">
          <div class="row">
            <form class="form-inline" action="javascript:void(0);">
              <div class="input-group">
                <div class="input-group-prepend">
                  <div class="input-group-text"><i class="fa fa-search" aria-hidden="true"></i></div>
                </div>
                <input id="searchBox" class="form-control" type="search" placeholder="Cerca" aria-label="Cerca">
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<script src="https://unpkg.com/shufflejs@5"></script>
<!-- jQuery first, then Popper.js and then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>

In addition, the point in which I think it definitely breaks is the following

this.shuffle.filter(element => {
    const titleText = element.querySelector('.card-title').textContent.toLowerCase().trim();
    return titleText.indexOf(searchText) !== -1;
});

as I cannot be able to debug inside it.

Does anyone have any ideas about the solution to this problem? I've been finding the Shuffle.js library pretty as complicate as smooth is the feeling I get when seeing the final (desired!) effect.

Fabs
  • 13
  • 4
  • Hi @NishargShah Thanks for the edit! I've edited the post with a working version with all the js included and needed. Let me know if this helps! – Fabs Apr 18 '20 at 22:39
  • I tried to make your snippet working but still not working, I think you need to create a snippet in codepen or jsFiddle – Nisharg Shah Apr 18 '20 at 22:49
  • @NishargShah I've added the code on jsfiddle and it is having the same behavior I have on my code. I'll post it here and edit it in the post for everyone to see it: https://jsfiddle.net/8srk391z/ – Fabs Apr 18 '20 at 22:52
  • After initializing the `new window.Shuffle`, the `#card-space` height is set to 0. Next, inside the `filter`, the items array is 0. – Tim Vermaelen Apr 18 '20 at 23:18
  • @NishargShah the html should show the cards which should be in the HTML if you inspect the page @TimVermaelen Thanks for making me notice. do you have any suggestions about how to solve it? I wouldn't use css, but would tweak that `new window.Shuffle` in order to solve it. Regarding the `filter`, which items array are you referring to? – Fabs Apr 18 '20 at 23:22
  • Probably the way the options are built into the library, you might need some more setup. Looking at it as we speak, although, accoring to the [documentation](https://vestride.github.io/Shuffle/) it seems setup alright. – Tim Vermaelen Apr 18 '20 at 23:28
  • Well, I've just solved the problem of the height by putting a `h-100` from `Bootstrap 4`, so at least one is solved. The only thing I am missing is that array which I don't understand why should not be caught by the itemSelector – Fabs Apr 18 '20 at 23:35
  • Looking into the `data-groups` and/or `data-title`, I believe it's important. `this.BS.link.setAttribute('data-title', name); this.BS.link.setAttribute('data-groups', name);` – Tim Vermaelen Apr 18 '20 at 23:49
  • @TimVermaelen I believe that the `data-groups` are needed for the grouping in the Demo example of the docs, but I'll try to add the `data-title` attribute. Edit: not working even if adding those two things in the `add` function of the `cards.js`. The `items` array is still empty – Fabs Apr 18 '20 at 23:53

2 Answers2

2

Take a look at this demo. What I've done is remove the entire grid structure completely and went for Bootstrap's card-deck. The reason for this, because of the way this library looks for the items array to filter on.

_getItems() {
    return Array.from(this.element.children)
        .filter(el => matches(el, this.options.itemSelector))
        .map(el => new ShuffleItem(el));
}

This basically means it takes the direct children and matches your itemSelector. In your HTML structure it takes all the columns, and can't find any itemSelector classes on your columns.

Another important step was to use the data-groups and/or data-title. Now I've set it only for the title (name) but I believe your goal is to add separate groups as well. You can fill those in from the category selector you've already created (with only one option tho).

this.BS.card.setAttribute('data-title', name);
this.BS.card.setAttribute('data-groups', name);

This solution enables the filter, enables the height and only left is now making .card-deck responsive, as card-deck is great (I'm on repeat here).

Arrange multiple divs in CSS/JS?
loop every 3 row using bootstrap card
How do I add spacing between rows of a card-deck in bootstrap

responsive card-deck CSS demo

Tim Vermaelen
  • 6,869
  • 1
  • 25
  • 39
  • 1
    nice brother, you solved his/her problem +1 for it – Nisharg Shah Apr 19 '20 at 00:18
  • Thank you Tim, this was great! I'll try to tweak it now a bit in order to have a more "grid like" layout, but this really did it – Fabs Apr 19 '20 at 00:25
  • You're welcome, feel free to look at [Bootstrap card-layout](https://getbootstrap.com/docs/4.0/components/card/#card-layout) to switch the layout. Card-deck, -columns (masonry) style is all possible these days. – Tim Vermaelen Apr 19 '20 at 00:27
0

You can filter Array not an Object. I do console.log so you can see it.

class Card {
  constructor(ref) {
    this.hmi_ref = ref;

    // Bootstap : container type
    this.BS = {}
    this.BS.container = document.createElement('div');
    this.BS.card      = document.createElement('div');
    this.BS.image     = document.createElement('img');
    this.BS.info      = document.createElement('div');
    this.BS.title     = document.createElement('h4');
    this.BS.link      = document.createElement('a');

    this.BS.card.appendChild(this.BS.link);
    this.BS.link.appendChild(this.BS.image);
    this.BS.card.appendChild(this.BS.title);
    this.BS.container.appendChild(this.BS.card);

    this.BS.container.className = 'col-4 mb-3';
    this.BS.card.className      = 'card h-100';
    this.BS.image.className     = 'card-img-top';
    this.BS.title.className     = 'card-title text-center align-middle';
  }

  add ( name, image, page_link){
    this.BS.image.src = image;
    this.BS.title.textContent = name;
    this.BS.link.href = page_link;
    let newNode = this.BS.container.cloneNode(true);
    this.hmi_ref.appendChild(newNode);
  }
}
      
let myCard = new Card( document.getElementById('card-space') );
[
    {title: 'Vacanza studio Londra', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Roma', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Bangkok', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Catania', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanze studio"},
    {title: 'Vacanza studio Siracusa', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Ragusa', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
    {title: 'Vacanza studio Trapani', img: 'https://source.unsplash.com/random/1920x1080', link: 'https://source.unsplash.com/random/1920x1080', category: "Vacanza studio"},
].map(e => myCard.add(e.title, e.img, e.link, e.category));

class Shuffler {
    constructor(element) {
        this.shuffle = new window.Shuffle(element, {
            itemSelector: '.card',
            sizer: element.querySelector('.sizer'),
        }); 
        document.getElementById('searchBox').addEventListener('keyup', this._handleSearchKeyup.bind(this));
    }

    /**
     * Filter the shuffle instance by items with a title that matches the search input.
     * @param {Event} evt Event object.
     */
    _handleSearchKeyup(evt) {
        const searchText = evt.target.value.toLowerCase();
        Object.values(this.shuffle.element.children).filter(element => {
            const titleText = element.textContent.toLowerCase().trim();
            console.log(element.textContent.toLowerCase().trim(), titleText.indexOf(searchText));
            return titleText.indexOf(searchText) !== -1;
        });
    }
}

window.onload = () => {
    window.demo = new Shuffler(document.querySelector('#card-space'));
}   
</script>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
</head>

<body>
  <div class="container pt-3">
    <div class="row">
      <div class="col">
        <!-- Main column -->
        <div class="row pt-4">
          <div class="col-9">
            <div id="card-space" class="row">
              <!-- <div class="col-1@sm sizer"></div> -->
            </div>
          </div>
          <div class="col-3">
            <div class="row">
              <form class="form-inline" action="javascript:void(0);">
                <div class="input-group">
                  <div class="input-group-prepend">
                    <div class="input-group-text"><i class="fa fa-search" aria-hidden="true"></i></div>
                  </div>
                  <input id="searchBox" class="form-control" type="search" placeholder="Cerca" aria-label="Cerca">
                </div>
              </form>
            </div>
            <div class="row">
              <select class="custom-select my-3" id="eventCategories">
                <option selected>Scegli una categoria</option>
              </select>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/Shuffle/5.2.3/shuffle.min.js"></script>
  <!-- jQuery first, then Popper.js and then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
</body>

</html>
Nisharg Shah
  • 16,638
  • 10
  • 62
  • 73
  • Thank you @NishargShah, this solves the problem of having the `items` array empty, but it still does not filter through when trying to input a string in the `search` input :( – Fabs Apr 19 '20 at 00:09
  • bro, you need to solve this problem self, because for that I need to read whole documentation and too many stuff, for applying that configuration. – Nisharg Shah Apr 19 '20 at 00:12
  • Well, thanks anyway for the help though, it was already a big step ahead! I'll upvote your solution – Fabs Apr 19 '20 at 00:14
  • Do some try on it and If not working, Post another question on StackOverflow, Thanks!! – Nisharg Shah Apr 19 '20 at 00:15