Guest User

Untitled

a guest
Jul 7th, 2025
26
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* ----------------- Start Document ----------------- */
  2. (function ($) {
  3.   "use strict";
  4.  
  5.   $(document).ready(function () {
  6.    
  7.       // Global default categories if none are provided (optional)
  8.       var defaultCategories = [
  9.         {
  10.           label: "Default",
  11.           children: [{ label: "Item 1" }, { label: "Item 2" }],
  12.         },
  13.       ];
  14.  
  15.       // Helper function to check if an item has real children (not just duplicates)
  16.       function hasRealChildren(item) {
  17.         if (!item.children || item.children.length === 0) {
  18.           return false;
  19.         }
  20.        
  21.         // If there's only one child and it's essentially the same as the parent, treat as leaf
  22.         if (item.children.length === 1) {
  23.           var child = item.children[0];
  24.           // Check if the child is a duplicate of the parent
  25.           if (child.value === item.value && child.id === item.id) {
  26.             return false;
  27.           }
  28.           // Also check if it's an "All in X" pattern
  29.           if (child.label === "All in " + item.label) {
  30.             return false;
  31.           }
  32.         }
  33.        
  34.         return true;
  35.       }
  36.  
  37.       // Set up each drilldown menu instance
  38.       $(".drilldown-menu").each(function () {
  39.         var $menu = $(this);
  40.         var selectedItems = [];
  41.         var menuStack = []; // Array to keep track of drilldown levels
  42.         var initialized = false; //
  43.         // Add this option - read from data attribute or default to false
  44.         var singleSelect = $menu.data("single-select") === true;
  45.  
  46.         // Read categories from the data attribute; fallback to defaultCategories if needed.
  47.         var categories = $menu.data("categories");
  48.         if (typeof categories === "string") {
  49.           try {
  50.             categories = JSON.parse(categories);
  51.           } catch (e) {
  52.             categories = defaultCategories;
  53.           }
  54.         } else if (!categories) {
  55.           categories = defaultCategories;
  56.         }
  57.  
  58.         // Cache commonly used elements within this menu
  59.         var $menuToggle = $menu.find(".menu-toggle");
  60.         var $menuPanel = $menu.find(".menu-panel");
  61.         var $menuLevelsContainer = $menu.find(".menu-levels");
  62.         var $menuLabel = $menu.find(".menu-label");
  63.         var $menuLabelText = $menu.data("label");
  64.         var $resetButton = $menu.find(".reset-button");
  65.  
  66.         // Recursive function to check if an item (or any descendant) matches the search term
  67.         function itemMatchesSearch(item, searchTerm) {
  68.           if ($.trim(searchTerm) === "") return true;
  69.           var lowerSearch = searchTerm.toLowerCase();
  70.           if (item.label.toLowerCase().indexOf(lowerSearch) !== -1) {
  71.             return true;
  72.           }
  73.           if (item.children && item.children.length > 0) {
  74.             for (var i = 0; i < item.children.length; i++) {
  75.               if (itemMatchesSearch(item.children[i], searchTerm)) {
  76.                 return true;
  77.               }
  78.             }
  79.           }
  80.           return false;
  81.         }
  82.  
  83.         // Initialize the menu at the root level
  84.         function initMenu() {
  85.           menuStack = [];
  86.           menuStack.push({ data: categories, parent: null });
  87.           $menuLevelsContainer.empty();
  88.           var $levelElement = createMenuLevel(categories, 0);
  89.           $menuLevelsContainer.append($levelElement);
  90.           updateMenuLevelPosition();
  91.           updateMenuHeight();
  92.           initializePreselectedValues();
  93.         }
  94.  
  95.         // Create a new menu level element for the given data
  96.         function createMenuLevel(data, levelIndex) {
  97.           var $levelDiv = $("<div/>")
  98.             .addClass("menu-level")
  99.             .attr("data-level", levelIndex);
  100.  
  101.           // Add a "Back" button if not at the root level
  102.           if (levelIndex > 0) {
  103.             var $backButton = $("<button/>")
  104.               .addClass("back-button")
  105.               .text(listeo_core.back)
  106.               .on("click", function (e) {
  107.                 e.stopPropagation();
  108.                 drillUp();
  109.               });
  110.             $levelDiv.append($backButton);
  111.           }
  112.  
  113.           // Add a search input field
  114.           var $searchInput = $("<input/>", {
  115.             type: "text",
  116.             placeholder: listeo_core.search,
  117.             class: "menu-search",
  118.           }).on("input", function () {
  119.             filterMenuLevel($levelDiv, $searchInput.val());
  120.           });
  121.           $levelDiv.append($searchInput);
  122.  
  123.           // Create a container for menu items
  124.           var $itemsContainer = $("<div/>").addClass("menu-items");
  125.           $levelDiv.append($itemsContainer);
  126.  
  127.           // Iterate over the items and create each menu item element
  128.           $.each(data, function (i, item) {
  129.             var $itemDiv = $("<div/>")
  130.               .addClass("menu-item")
  131.               .attr("data-label", item.label);
  132.  
  133.             // Add value attribute if it exists
  134.             if (item.value) {
  135.               $itemDiv.attr("data-value", item.value);
  136.             }
  137.             if (item.id) {
  138.               $itemDiv.attr("data-id", item.id);
  139.             }
  140.             // Store the entire item object for use in search filtering
  141.             $itemDiv.data("item", item);
  142.             var $labelSpan = $("<span/>")
  143.               .addClass("item-label")
  144.               .text(item.label);
  145.             $itemDiv.append($labelSpan);
  146.  
  147.             // FIXED: Use hasRealChildren instead of just checking if children exist
  148.             if (hasRealChildren(item)) {
  149.               // Item with subcategories: add an arrow and set up drilldown
  150.               var $arrowSpan = $("<span/>").addClass("arrow");
  151.               $itemDiv.append($arrowSpan);
  152.               $itemDiv.on("click", function (e) {
  153.                 e.stopPropagation();
  154.                 drillDown(item);
  155.               });
  156.             } else {
  157.               // Leaf item: toggle selection on click
  158.               $itemDiv.on("click", function (e) {
  159.                 e.stopPropagation();
  160.                 // Remove 'active' class from any .category-item
  161.                 $(".category-item").removeClass("active");
  162.                 toggleSelection(item, $itemDiv);
  163.               });
  164.               if (isSelected(item)) {
  165.                 $itemDiv.addClass("selected");
  166.               }
  167.             }
  168.             $itemsContainer.append($itemDiv);
  169.           });
  170.  
  171.           return $levelDiv;
  172.         }
  173.  
  174.         // Modified filter function that checks parent items and their descendants.
  175.         // It also highlights matches using <mark>.
  176.         function filterMenuLevel($levelDiv, searchTerm) {
  177.           var $itemsContainer = $levelDiv.find(".menu-items");
  178.           var $items = $itemsContainer.find(".menu-item");
  179.           var anyVisible = false;
  180.           $levelDiv.find(".no-results").remove();
  181.  
  182.           $items.each(function () {
  183.             var $item = $(this);
  184.             var itemObj = $item.data("item"); // get the complete data object
  185.             var label = itemObj.label;
  186.             var lowerSearch = $.trim(searchTerm).toLowerCase();
  187.             // Determine if there is a direct match in the label
  188.             var directMatch =
  189.               lowerSearch !== "" &&
  190.               label.toLowerCase().indexOf(lowerSearch) > -1;
  191.             // Determine if the item or any descendant matches
  192.             var matches = itemMatchesSearch(itemObj, searchTerm);
  193.             if (matches) {
  194.               $item.css("display", "flex");
  195.               anyVisible = true;
  196.               var $labelSpan = $item.find(".item-label");
  197.               // Reset any previous highlighting and classes
  198.               $item.removeClass("child-match");
  199.               $labelSpan.text(label);
  200.               if ($.trim(searchTerm) !== "") {
  201.                 if (directMatch) {
  202.                   // Highlight the matching substring in the label
  203.                   var regex = new RegExp(
  204.                     "(" + escapeRegExp(searchTerm) + ")",
  205.                     "gi"
  206.                   );
  207.                   $labelSpan.html(label.replace(regex, "<mark>$1</mark>"));
  208.                 } else {
  209.                   // No direct match—but a descendant matches: add a special class
  210.                   $item.addClass("child-match");
  211.                 }
  212.               }
  213.             } else {
  214.               $item.css("display", "none");
  215.             }
  216.           });
  217.           if (!anyVisible) {
  218.             var $noResults = $("<div/>")
  219.               .addClass("no-results")
  220.               .text("No results");
  221.             $itemsContainer.append($noResults);
  222.           }
  223.           updateMenuHeight();
  224.         }
  225.  
  226.         // Utility function to escape regex special characters
  227.         function escapeRegExp(string) {
  228.           return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  229.         }
  230.  
  231.         // Drill down into a submenu for the given category.
  232.         // Also, propagate the parent's search term to the child's search field.
  233.         function drillDown(category) {
  234.           if (!category.children || category.children.length === 0) return;
  235.           // Get parent's search term from the current active level.
  236.           var $currentLevel = $menuLevelsContainer.children().last();
  237.           var parentSearchTerm = $currentLevel.find(".menu-search").val();
  238.           menuStack.push({ data: category.children, parent: category });
  239.           var levelIndex = menuStack.length - 1;
  240.           var $newLevel = createMenuLevel(category.children, levelIndex);
  241.           // Propagate parent's search term to child's search input and filter.
  242.           $newLevel.find(".menu-search").val(parentSearchTerm);
  243.           filterMenuLevel($newLevel, parentSearchTerm);
  244.           $menuLevelsContainer.append($newLevel);
  245.           updateMenuLevelPosition();
  246.           setTimeout(updateMenuHeight, 0);
  247.         }
  248.  
  249.         // Return to the previous menu level
  250.         function drillUp() {
  251.           if (menuStack.length <= 1) return;
  252.           menuStack.pop();
  253.           $menuLevelsContainer.children().last().remove();
  254.           updateMenuLevelPosition();
  255.           updateMenuHeight();
  256.         }
  257.  
  258.         function findItemByValue(categories, value) {
  259.           for (var i = 0; i < categories.length; i++) {
  260.             var item = categories[i];
  261.             // Check if this item matches
  262.             if ("#submit-listing-form".length) {
  263.               if (item.id == value) {
  264.                 return item;
  265.               }
  266.             } else {
  267.               if (
  268.                 item.value === value ||
  269.                 (!item.value && item.label === value)
  270.               ) {
  271.                 return item;
  272.               }
  273.             }
  274.  
  275.             // Check children if they exist
  276.             if (item.children && item.children.length > 0) {
  277.               var found = findItemByValue(item.children, value);
  278.               if (found) return found;
  279.             }
  280.           }
  281.           return null;
  282.         }
  283.  
  284.         // Make drilldown control functions available globally
  285.         if (!window.ListeoDrilldown) window.ListeoDrilldown = {};
  286.  
  287.         window.ListeoDrilldown[$menu.attr("id")] = {
  288.           initMenu: initMenu,
  289.           selectById: function (categoryId) {
  290.             initMenu();
  291.  
  292.             setTimeout(function () {
  293.               const item = findItemByValue(categories, categoryId);
  294.               if (item) {
  295.                 // Fake a jQuery element just to pass to toggleSelection
  296.                 const $fake = $("<div>")
  297.                   .addClass("menu-item")
  298.                   .attr("data-id", categoryId);
  299.                 toggleSelection(item, $fake);
  300.               } else {
  301.                 // reset the menu if no item found
  302.                 resetSelections();
  303.               }
  304.             }, 20);
  305.           },
  306.         };
  307.        
  308.  
  309.         function initializePreselectedValues() {
  310.           selectedItems = []; // Clear existing selections
  311.  
  312.           // Get the original input class and name
  313.           var $originalInput = $menu.find("input.drilldown-values");
  314.           var inputName = $menu.data("name");
  315.  
  316.           // Get all existing array inputs
  317.           var $existingInputs = $menu.find('input[name="' + inputName + '[]"]');
  318.  
  319.           $existingInputs.each(function () {
  320.             var value = $(this).val();
  321.             if (value) {
  322.               var item = findItemByValue(categories, value.trim());
  323.               if (item) {
  324.                 selectedItems.push(item);
  325.               }
  326.             }
  327.           });
  328.  
  329.           if (selectedItems.length > 0) {
  330.             updateMainButton();
  331.             // Ensure hidden inputs are created for preselected items
  332.             updateHiddenInput();
  333.           }
  334.         }
  335.         initializePreselectedValues();
  336.  
  337.         function updateHiddenInput() {
  338.           // Find the hidden input by class within this menu instance
  339.           var $originalInput = $menu.find("input.drilldown-values");
  340.  
  341.           var inputName = $menu.data("name");
  342.  
  343.           // First remove any existing array inputs
  344.           $menu.find('input[name="' + inputName + '[]"]').remove();
  345.  
  346.           // Create new hidden inputs for each selected value
  347.           selectedItems.forEach(function (item) {
  348.             if ($("#submit-listing-form").length) {
  349.               var value = item.id;
  350.             } else {
  351.               var value = item.value || item.label;
  352.             }
  353.  
  354.             $("<input>", {
  355.               type: "hidden",
  356.               name: inputName + "[]",
  357.               value: value,
  358.               class: "drilldown-generated", // Add a class to identify generated inputs
  359.             }).appendTo($menu);
  360.           });
  361.           var target = $("div#listeo-listings-container");
  362.           target.triggerHandler("update_results", [1, false]);
  363.  
  364.           $menu.trigger("drilldown-updated");
  365.  
  366.           // Verify inputs were created
  367.         }
  368.  
  369.         // Update the container's transform to slide to the active level
  370.         function updateMenuLevelPosition() {
  371.           var levelIndex = menuStack.length - 1;
  372.           $menuLevelsContainer.css(
  373.             "transform",
  374.             "translateX(-" + levelIndex * 100 + "%)"
  375.           );
  376.         }
  377.  
  378.         // Update the panel height to match the active level's natural height
  379.         function updateMenuHeight() {
  380.           var $levels = $menuLevelsContainer.children();
  381.           if ($levels.length === 0) return;
  382.           var $activeLevel = $levels.last();
  383.           $menuPanel.height($activeLevel[0].scrollHeight);
  384.         }
  385.  
  386.         // Toggle selection of a leaf item.
  387.         // Also update the main button with highlighting of the current search term.
  388.         function toggleSelection(item, $itemDiv) {
  389.           // remove class active from any .category-item
  390.          
  391.           var index = selectedItems.findIndex(function (selected) {
  392.             return item.value && selected.value
  393.               ? selected.value === item.value
  394.               : selected.label === item.label;
  395.           });
  396.  
  397.           if (index > -1) {
  398.             // Deselect
  399.             selectedItems.splice(index, 1);
  400.             $itemDiv.removeClass("selected");
  401.           } else {
  402.             // Select
  403.             if (singleSelect) {
  404.               // Remove 'selected' class from all items
  405.               $menu.find(".menu-item.selected").removeClass("selected");
  406.               // Clear the array
  407.               selectedItems = [];
  408.             }
  409.             // Check if item already exists before pushing
  410.             if (!isSelected(item)) {
  411.               selectedItems.push(item);
  412.             }
  413.             $itemDiv.addClass("selected");
  414.            
  415.            
  416.           }
  417.           //$(".category-item").removeClass("active");
  418.           updateMainButton();
  419.           updateHiddenInput();
  420.         }
  421.  
  422.         // Check if an item is already selected
  423.         // function isSelected(item) {
  424.         //   var exists = false;
  425.         //   $.each(selectedItems, function (i, sel) {
  426.         //     if (sel.label === item.label) {
  427.         //       exists = true;
  428.         //       return false;
  429.         //     }
  430.         //   });
  431.         //   return exists;
  432.         // }
  433.  
  434.         function isSelected(item) {
  435.           return selectedItems.some(function (selected) {
  436.             if ($("#submit-listing-form").length) {
  437.               if (item.id && selected.id) {
  438.                 return selected.id === item.id;
  439.               }
  440.             } else {
  441.               if (item.value && selected.value) {
  442.                 return selected.value === item.value;
  443.               }
  444.             }
  445.  
  446.             return selected.label === item.label;
  447.           });
  448.         }
  449.  
  450.         // Update the main button text.
  451.         // If a search term is active in the current level, highlight it in the label.
  452.         function updateMainButton() {
  453.           var searchTerm =
  454.             $menuLevelsContainer.children().last().find(".menu-search").val() ||
  455.             "";
  456.           if (selectedItems.length === 0) {
  457.             $menuLabel.html($menuLabelText);
  458.             $resetButton.hide();
  459.             $menuToggle.removeClass("dd-chosen"); // Remove class when no selection
  460.           } else if (selectedItems.length === 1) {
  461.             var label = selectedItems[0].label;
  462.             if ($.trim(searchTerm) !== "") {
  463.               var regex = new RegExp(
  464.                 "(" + escapeRegExp(searchTerm) + ")",
  465.                 "gi"
  466.               );
  467.               label = label.replace(regex, "<mark>$1</mark>");
  468.             }
  469.             $menuLabel.html(label);
  470.             $resetButton.show();
  471.             $menuToggle.addClass("dd-chosen"); // Add class when selection exists
  472.           } else {
  473.             var label = selectedItems[0].label;
  474.             if ($.trim(searchTerm) !== "") {
  475.               var regex = new RegExp(
  476.                 "(" + escapeRegExp(searchTerm) + ")",
  477.                 "gi"
  478.               );
  479.               label = label.replace(regex, "<mark>$1</mark>");
  480.             }
  481.             $menuLabel.html(label + " +" + (selectedItems.length - 1));
  482.             $resetButton.show();
  483.             $menuToggle.addClass("dd-chosen"); // Add class when selection exists
  484.           }
  485.         }
  486.  
  487.         // Clear all selections in this menu
  488.         function resetSelections() {
  489.           selectedItems = [];
  490.           $menuPanel.find(".menu-item.selected").removeClass("selected");
  491.           updateMainButton();
  492.           updateHiddenInput();
  493.         }
  494.  
  495.         // Open the menu and initialize it; also close any other open menus
  496.         function openMenu() {
  497.           // Close all other menus on the page
  498.          
  499.           $(".drilldown-menu")
  500.             .not($menu)
  501.             .each(function () {
  502.               $(this).find(".menu-panel").removeClass("open");
  503.               $(this).find(".menu-toggle").removeClass("dd-active"); // Remove class from other menus
  504.             });
  505.  
  506.           if ($.fn.selectpicker) {
  507.             // For Bootstrap 4+
  508.             $(".bootstrap-select.show").each(function () {
  509.               $(this).removeClass("show");
  510.               $(this).find(".dropdown-menu").removeClass("show");
  511.             });
  512.  
  513.             // For older Bootstrap versions
  514.             $(".bootstrap-select.open").each(function () {
  515.               $(this).removeClass("open");
  516.               $(this).find(".dropdown-menu").removeClass("open");
  517.             });
  518.           }
  519.           $menuPanel.addClass("open");
  520.           $menuToggle.addClass("dd-active"); // Add class when menu is opened
  521.           initMenu();
  522.         }
  523.  
  524.         // Close the menu
  525.         function closeMenu() {
  526.           $menuPanel.removeClass("open");
  527.           $menuToggle.removeClass("dd-active"); // Remove class when menu is closed
  528.         }
  529.  
  530.         // Toggle the menu when clicking the main button
  531.         $menuToggle.on("click", function (e) {
  532.           e.stopPropagation();
  533.           if ($menuPanel.hasClass("open")) {
  534.             closeMenu();
  535.           } else {
  536.             openMenu();
  537.           }
  538.         });
  539.  
  540.         // Reset selections when clicking the reset button
  541.         $resetButton.on("click", function (e) {
  542.           e.stopPropagation();
  543.           resetSelections();
  544.           $(".category-item").removeClass("active");
  545.         });
  546.  
  547.         // Close this menu if clicking outside it
  548.         $(document).on("click", function (e) {
  549.           if (!$menu.is(e.target) && $menu.has(e.target).length === 0) {
  550.             closeMenu();
  551.           }
  552.         });
  553.       });
  554.  
  555.       window.selectDrilldownCategoryById = function (categoryId) {
  556.         $(".drilldown-menu").each(function () {
  557.           var $menu = $(this);
  558.           var $matchingItem = $menu.find('.menu-item[data-id="${categoryId}"]');
  559.           console.log("Matching item:", $matchingItem);
  560.           if ($matchingItem.length) {
  561.             $matchingItem.trigger("click");
  562.             console.log("Item clicked:", $matchingItem.text());
  563.             // Open the menu if needed and close it after selection
  564.          
  565.           }
  566.         });
  567.       };
  568.     });
  569.  
  570. })(this.jQuery);
Advertisement
Add Comment
Please, Sign In to add comment