fbpx

Cách xây dựng hiệu ứng Isotope kèm Search trong WordPress

Có thể nói việc xây dựng hiệu ứng Isotope giúp lọc các bản ghi theo các tiêu chí là một tính năng vô cùng hữu ích. Kết quả của việc này là do áp dụng linh hoạt kỹ thuật Isotope, bạn có thể tham khảo tại đây

Dưới đây là một demo về hiệu ứng lọc bằng Isotope

See the Pen Isotope – filtering & sorting by Dave DeSandro (@desandro) on CodePen.

Dưới đây là đoạn code giúp bạn vừa xây dựng tính năng lôi bài viết ra, vừa tạo ở Frontend việc tích hợp & điều khiển thư viện meta isotope

Đầu tiên, Add vào functions hoặc viết ra 1 plugins riêng về chức năng của Isotope. Bạn cần tải xuống thư viện Isotope trên github đóng vai trò Dependencies, tiếp đó bạn cũng cần tạo ra 1 file init riêng (file này có tác dụng điều khiển thư viện làm việc theo ý bạn), mà ở đây là isotope-init.js

// Register Styles and Scripts
function isotope_scripts() {

  wp_enqueue_script('isotope-min-js', plugins_url('/js/isotope.pkgd.min.js', __FILE__), array(), false, true);

  wp_enqueue_script('isotope-init-js', plugins_url('/js/isotope-init.js', __FILE__), array(), false, true);

}

add_action('wp_enqueue_scripts', 'isotope_scripts');

Tiếp đó, bạn tiến hành xây dựng khối Html muốn thực hiện Isotope

Khối Isotope sẽ có dạng như sau: có 1 div là parent với class grid và id là equal-height-overlay, các phần tử con sẽ là item

// Filtering 
<div id="filters-equal-height-overlay" class="filter-buttons btn-group">
        <button data-filter="*" class="active btn btn-outline-primary mb-1">All</button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-1"></button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-2"></button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-3"></button>
</div>           
      </div>
// Grid item
<div class="grid" id="equal-height-overlay">
  <div class="item class-1">...</div>
  <div class="item class-2">...</div>
  <div class="item class-3">...</div>
</div>
// Search bar
<input type="text" class="quicksearch form-control" placeholder="Search" />

Trong mỗi Grid item bạn có thể chọn lựa cách hiển thị mong muốn, có thể đẩy vào 1 bootstrap card chẳng hạn

<div class="card" style="width: 18rem;">
  <img src="..." class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">Card title</h5>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>

Trong file isotope-init.js bạn thực hiện đoạn code như sau:

// Equal Height Overlay
jQuery(function ($) {

  // Ver 2

  // quick search regex
  var qsRegex;
  var buttonFilter;

  function getHashFilter() {
    // get filter=filterName
    var matches = location.hash.match(/filter=([^&]+)/i);
    var hashFilter = matches && matches[1];
    return hashFilter && decodeURIComponent(hashFilter);
  }

  // init Isotope
  var $grid = $('#equal-height-overlay').isotope({
    itemSelector: '.item',
    layoutMode: 'fitRows',
    filter: function () {
      var $this = $(this);
      var searchResult = qsRegex ? $this.text().match(qsRegex) : true;
      var buttonResult = buttonFilter ? $this.is(buttonFilter) : true;
      return searchResult && buttonResult;
    }
  });



  $('#filters-equal-height-overlay').on('click', 'button', function () {
    buttonFilter = $(this).attr('data-filter');

    // set filter in hash
    location.hash = 'filter=' + encodeURIComponent(buttonFilter);
    $grid.isotope();
  });

  // use value of search field to filter
  var $quicksearch = $('.quicksearch').keyup(debounce(function () {
    qsRegex = new RegExp($quicksearch.val(), 'gi');
    $grid.isotope();
  }));


  var isIsotopeInit = false;

  function onHashchange() {
    var hashFilter = getHashFilter();
    if (!hashFilter && isIsotopeInit) {
      return;
    }
    isIsotopeInit = true;
    // filter isotope
    $grid.isotope({
      itemSelector: '.item',
      layoutMode: 'fitRows',
      // use filterFns
      filter: hashFilter
    });
    // set selected class on button
    var $buttonGroup = $('#filters-equal-height-overlay');
    if (hashFilter) {
      $buttonGroup.find('.active').removeClass('active');
      $buttonGroup.find('[data-filter="' + hashFilter + '"]').addClass('active');
    }
  }

  $(window).on('hashchange', onHashchange);

  // trigger event handler to init Isotope
  onHashchange();

  // change is-checked class on buttons
  $('.filter-buttons').each(function (i, buttonGroup) {
    var $buttonGroup = $(buttonGroup);
    $buttonGroup.on('click', 'button', function () {
      $buttonGroup.find('.active').removeClass('active');
      $(this).addClass('active');
    });
  });


  // debounce so filtering doesn't happen every millisecond
  function debounce(fn, threshold) {
    var timeout;
    threshold = threshold || 100;
    return function debounced() {
      clearTimeout(timeout);
      var args = arguments;
      var _this = this;
      function delayed() {
        fn.apply(_this, args);
      }
      timeout = setTimeout(delayed, threshold);
    };
  }
});

Hãy cùng đọc qua Code này và hiểu cùng nhau nhé, đầu tiên xây dựng 2 biến là:

  • qsRegex
  • buttonFilter

Xây dựng hàm getHashFilter trong đó có 2 biến được định nghĩa và kết quả trả về sẽ là kết hợp của 2 biến này

Tạo liên kết URL khi bấm vào từng nút bấm trên filter isotope

  // quick search regex
  var qsRegex;
  var buttonFilter;

  function getHashFilter() {
    // get filter=filterName
    var matches = location.hash.match(/filter=([^&]+)/i);
    var hashFilter = matches && matches[1];
    return hashFilter && decodeURIComponent(hashFilter);
  }
  • Biến matches là sẽ sử dụng hàm match để bắt cặp thành phần hash(#) khớp với #filter=giá trị bất kỳ
  • Biến hashFilter bao gồm đồng thời biến matches dưới dạng mảng (Array) và giá trị thứ 2 của biến này (Chỗ này Nam không hiểu lắm) ^^!
  • Giá trị return từ Function này là Hashfilter và giá trị chưa được mã hóa của Hashfilter

Khởi tạo Isotope

Khi đã import thư viện vào sử dụng trên WordPress, bước tiếp theo bạn sẽ cần khởi tạo giá trị Isotope và gắn vào đoạn html muốn áp dụng hiệu ứng

  // init Isotope
  var $grid = $('#equal-height-overlay').isotope({
    itemSelector: '.item',
    layoutMode: 'fitRows',
    filter: function () {
      var $this = $(this);
      var searchResult = qsRegex ? $this.text().match(qsRegex) : true;
      var buttonResult = buttonFilter ? $this.is(buttonFilter) : true;
      return searchResult && buttonResult;
    }
  });

Sử dụng hàm isotope (Đã định nghĩa sẵn trong thư viện) để gọi ra biến $grid (Chỗ này bạn cần lưu ý là khi enqueue script (Đăng ký script với wordpress cần ưu tiên chạy thư viện trước, rồi mới chạy file isotope-init.js này nhé

Biến $grid bằng tích hợp isotope vào phần tử có id = equal-height-overlay

  • Các phần tử con cần target .item
  • Layout muốn áp dụng: fitRows
  • Chức năng Filters
    • Định nghĩa biến $this = $(this) – chính phần tử ta sẽ lựa chọn (tức là div có id là equal-height-overlay)
    • biến searchResult = kiểm tra biến qsRegex, nếu có giá trị true thì biến này sẽ bằng truy vấn tìm kiếm, còn nếu là false thì sẽ chuyển thành true. Tóm lại biến này sẽ kiểm tra xem thanh search hiện tại có giá trị hay không?
    • biến buttonResult = kiểm tra biến buttonFilter (Một biến ta sẽ định nghĩa luôn sau đây), nếu biến này có giá trị true thì ta sẽ theo kết quả của buttonResult, nếu không thì bằng true
    • Giá trị của filter sẽ bằng cả kết quả từ searchResult lẫn buttonResult
// Grid item
<div class="grid" id="equal-height-overlay">
  <div class="item class-1">...</div>
  <div class="item class-2">...</div>
  <div class="item class-3">...</div>
</div>

Tích hợp #filter vào trong đường dẫn trình duyệt và định nghĩa giá trị search từ search bar

  $('#filters-equal-height-overlay').on('click', 'button', function () {
    buttonFilter = $(this).attr('data-filter');

    // set filter in hash
    location.hash = 'filter=' + encodeURIComponent(buttonFilter);
    $grid.isotope();
  });

  // use value of search field to filter
  var $quicksearch = $('.quicksearch').keyup(debounce(function () {
    qsRegex = new RegExp($quicksearch.val(), 'gi');
    $grid.isotope();
  }));

Khi bấm vào thành phần button nằm trong #filter-equal-height-overlay, thành phần button Filter sẽ bằng giá trị thuộc tính data-filter của button đó

<div id="filters-equal-height-overlay" class="filter-buttons btn-group">
        <button data-filter="*" class="active btn btn-outline-primary mb-1">All</button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-1"></button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-2"></button>
<button class="btn btn-outline-primary mb-1" data-filter=".class-3"></button>
</div>  

Ở đây thì có 3 nút bấm và data-filter lần lượt là

  • class-1
  • class-2
  • class-3

Đồng thời khi thực hiện nút bấm thì isotope cũng chạy với giá trị filter đồng thời URL trên trình duyệt sẽ đính kèm cụm filter (Điều này rất có ích khi bạn muốn xây dựng thanh menu trỏ tới isotope theo những liên kết mong muốn)

Truyền giá trị quicksearch vào thanh search để hiển thị ra isotope

Xây dựng đường dẫn match với phần filter mong muốn

Phần tiếp theo, bạn cần xây dựng đường dẫn thay đổi, kéo theo giá trị ở khối filter và isotope cũng thay đổi theo

var isIsotopeInit = false;

  function onHashchange() {
    var hashFilter = getHashFilter();
    if (!hashFilter && isIsotopeInit) {
      return;
    }
    isIsotopeInit = true;
    // filter isotope
    $grid.isotope({
      itemSelector: '.item',
      layoutMode: 'fitRows',
      // use filterFns
      filter: hashFilter
    });
    // set selected class on button
    var $buttonGroup = $('#filters-equal-height-overlay');
    if (hashFilter) {
      $buttonGroup.find('.active').removeClass('active');
      $buttonGroup.find('[data-filter="' + hashFilter + '"]').addClass('active');
    }
  }

  $(window).on('hashchange', onHashchange);

  // trigger event handler to init Isotope
  onHashchange();

  // change is-checked class on buttons
  $('.filter-buttons').each(function (i, buttonGroup) {
    var $buttonGroup = $(buttonGroup);
    $buttonGroup.on('click', 'button', function () {
      $buttonGroup.find('.active').removeClass('active');
      $(this).addClass('active');
    });
  });


  // debounce so filtering doesn't happen every millisecond
  function debounce(fn, threshold) {
    var timeout;
    threshold = threshold || 100;
    return function debounced() {
      clearTimeout(timeout);
      var args = arguments;
      var _this = this;
      function delayed() {
        fn.apply(_this, args);
      }
      timeout = setTimeout(delayed, threshold);
    };
  }

Xây dựng giá trị isoTopeInit bằng false mặc định

hàm onHashchange sẽ định nghĩa 1 hashFilter bằng hàm getHashFilter();

Nếu hàm hashFilter không chứa cả giá trị Search lẫn giá trị button lọc đồng thời isoTopeInit bằng true thì đơn giản là chương trình dừng xử lý

Còn nếu không thì isIsotopeInit sẽ gán bằng true và thực hiện filter isotop theo giá trị hashFilter (đã được truyền vào cả Search lẫn lọc theo nút bấm)

    // set selected class on button
    var $buttonGroup = $('#filters-equal-height-overlay');
    if (hashFilter) {
      $buttonGroup.find('.active').removeClass('active');
      $buttonGroup.find('[data-filter="' + hashFilter + '"]').addClass('active');
    }

Chỗ này hầu như để quyết định theo là hashFilter khớp với nút bấm vào thì button tại đó sẽ có class active

Khi trình duyệt thay đổi hash (#) thì hàm onHashchange sẽ chạy lại

Và đây là kết quả nhé bạn

// Filtering
// Grid item
...
Card title

Some quick example text to build on the card title and make up the bulk of the card’s content.

Go somewhere
...
Card title

Some quick example text to build on the card title and make up the bulk of the card’s content.

Go somewhere
...
Card title

Some quick example text to build on the card title and make up the bulk of the card’s content.

Go somewhere
// Search bar

Nam là 1 Growth Hacker, Developer đam mê với sự nghiệp phát triển web