Search code examples
wordpresswoocommercecategoriesnested-lists

Woocommerce - generate nested ul hierarchy of parent categories and subcategories


I'm trying to create a nested ul/li hierarchy with links to load products of subcategories. the subcategory links shall be nested in the parent categories (if there are any), and clicking on the parent category li elements does not load all the products of the subcategories that belong to that parent category, but clicking on the parent category li elements expands/collapses the subcategories in the nested ul, and clicking on a subcategory link in there will load the products of the subcategory link that is selected.

Here's what I've achieved so far:

I'm successfully getting all categories including parent and subcategories at least, and clicking on the links loads the corresponding products. So the links work, but I don't know how to successfully build that hierarchy of subcategory links nested in their parent category.

function loadProductCategoriesAndGenerateLinks() {
// Show the loading spinner
$("#loading-spinner2").show();

$.ajax({
  url: twentytwentyone_ajax_object.ajax_url,
  type: 'POST',
  dataType: 'json',
  data: {
    action: 'get_product_categories',
  },
  success: function(response) {
    if (response && response.categories) {
      var categories = response.categories;
      var treeContainer = $("#category-tree");
      treeContainer.empty();

      // Recursive function to generate the category tree
      function generateCategoryTree(categories, parentElement) {
        var ul = $('<ul>');
        $.each(categories, function(index, category) {
          var li = $('<li>');
          var link = $('<a>')
            .addClass('load-category-products')
            .attr('href', '#')
            .attr('data-category', category.slug)
            .text(category.name);
          li.append(link);

          if (category.children && category.children.length > 0) {
            // If the category has children, recursively generate their tree
            generateCategoryTree(category.children, li);
          }

          ul.append(li);
        });

        parentElement.append(ul); // Append the generated <ul> with <li> elements to the parent <li>
      }

      // Start generating the category tree from the top-level categories
      generateCategoryTree(categories, treeContainer);

      // Hide the loading spinner after the category tree is generated
      $("#loading-spinner2").hide();
    }
  },
  error: function(error) {
    // Handle error if necessary
    // Hide the loading spinner in case of an error
    $("#loading-spinner2").hide();
  }
});

}

Of course what I'm getting right now looks like this:

<ul>
   <li><a>Link to Subcategory 1</a></li>
   <li><a>Link to Subcategory 2</a></li>
   <li><a>Link to Subcategory 3</a></li>
   <li><a>Link to Subcategory 4</a></li>
   <li><a>Link to Parent category 1 (for example of Subcategory 3 and 4)</a></li>
   <li><a>Link to Subcategory 5</a></li>
</ul>

The problem is I don't know how these parent-child-relationships of categories are addressed in woocommerce. I would be very grateful for any help!


Solution

  • So I've achieved what i needed, a generic aproach. I connect to the database and take the data from the tables to generate the ul:

    <?php
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);
    // Get the correct path to the db-config.php file, don't forget to use a .htaccess file containing DENY FROM ALL, ideally you should place the db-config.php and its .htaccess file somewhere outside of the root folder of your project
    $db_config_path = get_theme_file_path( 'config/db-config.php' );
    
    // Check if the db-config.php file exists before attempting to include it
    if ( file_exists( $db_config_path ) ) {
        // Include the database configuration file
        require_once $db_config_path;
    
        // Create a database connection
        try {
            $pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            die("Database connection failed: " . $e->getMessage());
        }
    } else {
        die("Database configuration file not found.");
    }
    
    // Function to fetch nested categories from the database and display them as a nested unordered list with links
    function fetchNestedCategories($parent_id = 0, $level = 0)
    {
        global $pdo;
        $sql = "SELECT t.term_id, t.name, tt.parent, t.slug
            FROM wc_terms AS t
            LEFT JOIN wc_term_taxonomy AS tt ON t.term_id = tt.term_id
            WHERE tt.taxonomy = 'product_cat' AND tt.parent = :parent_id
            ORDER BY t.name";
    
        $stmt = $pdo->prepare($sql);
        $stmt->bindParam(':parent_id', $parent_id, PDO::PARAM_INT);
        $stmt->execute();
        $categories = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
        if ($categories) {
            echo '<ul class="some-ul-class">';
            foreach ($categories as $category) {
                echo '<li class="some-li-class"><a href="#" class="category" data-category="' . $category['slug'] . '">' . $category['name'] . '</a></li>';
                fetchNestedCategories($category['term_id'], $level + 1);
            }
            echo '</ul>';
        }
    }
    

    the HTML output I'm getting is just fine and provides everything i need. Then you just show the categories wherever you want them to show:

    <?php fetchNestedCategories(); ?>
    

    I just take that as the basis to generate the actual links to the categories via javascript, so that when you click on a subcategory link, the products of that subcategory are loaded via ajax.