2
\$\begingroup\$

I have a top level category accordion + accordions within those and a search that auto-opens the 2 levels when results are found. Since you can't open multiple at once there is a Next button that shows up by the search field. I threw this together but I feel there's got to be a cleaner way to do this.

jsFiddle

$(document).ready(function() {
    
    $("#faq_accordion").accordion({ heightStyle: "content",collapsible: true, active:false, create: function( event, ui ) {$('#faq_loading').hide();$('#faq_accordion').show();} });
    $(".faq_subaccordion").accordion({ heightStyle: "content",collapsible: true, active:false });
    
    $('#faq_search').on('change keyup paste click', function () {
        faqSearch();
    });
    
    $("#faq_btn_next").click(function(){
        var new_skip_index = parseInt($("#faq_btn_next").attr('data')) + 1;
        $("#faq_btn_next").attr('data',new_skip_index);
        faqSearch();
    });
    
});

function faqSearch(){
    $('.faq_highlight').contents().unwrap();
    
    var faq_term = $("#faq_search").val().toLowerCase();
    var accord_num = 0;
    var match_found = false;
    var top_accord_id = 0;
    var last_top_accord_id = 0;
    var title = '';
    var content = '';
    var skip_count = 0;
    var skip_index = parseInt($("#faq_btn_next").attr('data'));
    
    if (faq_term.length >= 3) {
        
        $('.faq_subaccordion h3').each(function() {
            // strip the icon tag
            title = $(this).html().replace(/<[^>]+>/gim, '').toLowerCase();

            if (title.indexOf(faq_term) != -1){
                
                top_accord_id = parseInt($(this).parent().attr('id').split("_")[1]);
                if (last_top_accord_id != top_accord_id) {
                    last_top_accord_id = top_accord_id;
                    accord_num = 0;
                }
                
                if (skip_index != skip_count) {
                    skip_count++;
                } else {
                    
                    var regex = new RegExp('('+faq_term+')', 'ig');
                    $(this).html($(this).html().replace(regex, '<span class="faq_highlight">$1</span>'));
                    
                    $("#faq_accordion").accordion("option", "active", top_accord_id);
                    $(this).parent().accordion("option", "active", accord_num);
                    match_found = true;
                    return false;
                }
            } else {
                top_accord_id = parseInt($(this).parent().attr('id').split("_")[1]);
                if (last_top_accord_id != top_accord_id) {
                    last_top_accord_id = top_accord_id;
                    accord_num = 0;
                }
                
                content = $(this).parent().find('>div').html().replace(/<[^>]+>/gim, '').toLowerCase();
                
                if (content.indexOf(faq_term) != -1){
                    if (skip_index != skip_count) {
                        skip_count++;
                    } else {
                        var regex = new RegExp('('+faq_term+')', 'ig');
                        $(this).parent().find('>div').html(content.replace(regex, '<span class="faq_highlight">$1</span>'));
                        
                        $("#faq_accordion").accordion("option", "active", top_accord_id);
                        $(this).parent().accordion("option", "active", accord_num);
                        match_found = true;
                        return false;
                    }
                }
            }
            
            accord_num = accord_num + 1;
        });
    }
    
    // No match close both accordions
    if (match_found == false) {
        $("#faq_accordion").accordion("option", "active", false);
        $(".faq_subaccordion").accordion("option", "active", false);
        $("#faq_btn_next").attr('data',0);
        $("#faq_btn_next").hide();
    } else {
        $("#faq_btn_next").show();
    }
}
#faq_search_box input {padding:2px;}
#faq_btn_next {display:none; padding: 2px; font-weight:bold; cursor: pointer}
.faq_highlight {font-weight:bold; font-style:italic; background: yellow; color:#000}
h3 .faq_highlight {font-weight:bold; font-style:italic;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="faq_search_box">
    Search FAQ: <input type="text" id="faq_search"/>
    <button id="faq_btn_next" data="0">Next Result</button>
</div>


<div id="faq_accordion">
    <h3>Category 1</h3>
    <div>
        <div class="faq_subaccordion" id="subaccordion_0">
        <h3>Cat 1 - Title 1</h3>
        <div>Testing Text 1 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum</div>
        <h3>Cat 1 - Title 2</h3>
        <div>Testing Text 2 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain c</div>
        <h3>Cat 1 - Title 3</h3>
        <div>Testing Text 3 Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequunt</div>
        </div>
    </div>
    <h3>Category 2</h3>
    <div>
        <div class="faq_subaccordion" id="subaccordion_1">
        <h3>Cat 2 - Title 1</h3>
        <div>Testing Text 1 On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot f</div>
        <h3>Cat 2 - Title 2</h3>
        <div>Testing Text 2 rem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad </div>
        <h3>Cat 2 - Title 3</h3>
        <div>Testing Text 3 But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself,</div>
        </div>
    </div>
    <h3>Category 3</h3>
    <div>
        <div class="faq_subaccordion" id="subaccordion_2">
        <h3>Cat 3 - Title 1</h3>
        <div>Testing Text 1 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain c</div>
        <h3>Cat 3 - Title 2</h3>
        <div>Testing Text 2 Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure</div>
        <h3>Cat 3 - Title 3</h3>
        <div>Testing Text 3 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain c</div>
        </div>
    </div>
</div>

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Well, there is a room for improvement in a few areas,

  • Save references to the elements rather than using them like $("#element_id") every time you need them to avoid DOM Lookup every time
  • When using .each() you are using $(this) at several places you should save the reference to the element on top of .each() and then use that reference.
  • You can create a function to change the state of the accordion as you have the repeated statements like .accordion("option", "active", top_accord_id); and .accordion("option", "active", accord_num); you can make a function to update the accordion and pass it the params with the collection of the accordion elements and the options you want to update along with the value to update for that option.

    See the below function

    function updateAccordion(params) {
    
        for (var i = 0; i < params.length; i++) {
            let elem = params[i].elem;
            let option = params[i].option;
            let optionValue = params[i].optionValue;
    
            elem.accordion("option", option, optionValue);
        }
    
    }
    
  • You should stick to event delegation throughout the code you are using .on('change keyup paste click', function () at one place and on the very next line you are using .click(function(){.

See the full code demo below.

/*jshint esversion: 6 */
/*globals $:true, */

var faqAccordian;
var faqSubAccordian;

var faqNextButton;
var faqSearchInput;

$(document).ready(function() {
  faqNextButton = $("#faq_btn_next");
  faqSearchInput = $('#faq_search');
  var faqAccordionInput = $("#faq_accordion");
  var faqSubAccordionInput = $(".faq_subaccordion");

  faqAccordian = faqAccordionInput.accordion({
    heightStyle: "content",
    collapsible: true,
    active: false,
    create: function(event, ui) {
      $('#faq_loading').hide();
      faqAccordionInput.show();
    }
  });

  faqSubAccordian = faqSubAccordionInput.accordion({
    heightStyle: "content",
    collapsible: true,
    active: false
  });

  faqSearchInput.on('change keyup paste click', function() {
    faqSearch();
  });

  faqNextButton.on('click', function() {
    var new_skip_index = parseInt(faqNextButton.attr('data')) + 1;
    faqNextButton.attr('data', new_skip_index);
    faqSearch();
  });

});

function faqSearch() {
  $('.faq_highlight').contents().unwrap();

  var faq_term = faqSearchInput.val().toLowerCase();
  var accord_num = 0;
  var match_found = false;
  var top_accord_id = 0;
  var last_top_accord_id = 0;
  var title = '';
  var content = '';
  var skip_count = 0;
  var skip_index = parseInt(faqNextButton.attr('data'));
  const faq_term_length = faq_term.length >= 3;

  if (faq_term_length) {

    $('.faq_subaccordion h3').each(function() {

      var h3 = $(this);
      // strip the icon tag
      title = h3.html().replace(/<[^>]+>/gim, '').toLowerCase();
      const title_includes_term = title.indexOf(faq_term) != -1;

      if (title_includes_term) {

        top_accord_id = parseInt(h3.parent().attr('id').split("_")[1]);
        const not_top_accordion = last_top_accord_id != top_accord_id;

        if (not_top_accordion) {
          last_top_accord_id = top_accord_id;
          accord_num = 0;
        }

        const not_skippable = skip_index != skip_count;

        if (not_skippable) {
          skip_count++;
        } else {
          var regex = new RegExp('(' + faq_term + ')', 'ig');
          h3.html(h3.html().replace(regex, '<span class="faq_highlight">$1</span>'));

          var params = [{
              elem: faqAccordian,
              option: "active",
              optionValue: top_accord_id

            },
            {
              elem: h3.parent(),
              option: "active",
              optionValue: accord_num
            }
          ];

          updateAccordion(params);

          match_found = true;
          return false;
        }
      } else {
        top_accord_id = parseInt(h3.parent().attr('id').split("_")[1]);

        const not_top_accordion = last_top_accord_id != top_accord_id;

        if (not_top_accordion) {
          last_top_accord_id = top_accord_id;
          accord_num = 0;
        }

        content = h3.parent().find('>div').html().replace(/<[^>]+>/gim, '').toLowerCase();
        const content_includes_term = content.indexOf(faq_term) != -1;

        if (content_includes_term) {
          const not_skippable = skip_index != skip_count;

          if (not_skippable) {
            skip_count++;
          } else {
            var regex = new RegExp('(' + faq_term + ')', 'ig');
            h3.parent().find('>div').html(content.replace(regex, '<span class="faq_highlight">$1</span>'));
            var params = [{
              elem: faqAccordian,
              option: "active",
              optionValue: top_accord_id
            }, {
              elem: h3.parent(),
              option: "active",
              optionValue: accord_num
            }];

            updateAccordion(params);

            match_found = true;
            return false;
          }
        }
      }

      accord_num = accord_num + 1;
    });
  }

  // No match close both accordions
  if (match_found == false) {

    var params = [{
      elem: faqAccordian,
      option: "active",
      optionValue: false
    }, {
      elem: faqSubAccordian,
      option: "active",
      optionValue: false
    }];

    updateAccordion(params);

    faqNextButton.attr('data', 0);
    faqNextButton.hide();
  } else {
    faqNextButton.show();
  }
}

function updateAccordion(params) {

  for (var i = 0; i < params.length; i++) {
    var elem = params[i].elem;
    var option = params[i].option;
    var optionValue = params[i].optionValue;

    elem.accordion("option", option, optionValue);
  }

}
#faq_search_box input {
  padding: 2px;
}

#faq_btn_next {
  display: none;
  padding: 2px;
  font-weight: bold;
  cursor: pointer
}

.faq_highlight {
  font-weight: bold;
  font-style: italic;
  background: yellow;
  color: #000
}

h3 .faq_highlight {
  font-weight: bold;
  font-style: italic;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="faq_search_box">
  Search FAQ: <input type="text" id="faq_search" />
  <button id="faq_btn_next" data="0">Next Result</button>
</div>


<div id="faq_accordion">
  <h3>Category 1</h3>
  <div>
    <div class="faq_subaccordion" id="subaccordion_0">
      <h3>Cat 1 - Title 1</h3>
      <div>Testing Text 1 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
        Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum</div>
      <h3>Cat 1 - Title 2</h3>
      <div>Testing Text 2 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because
        occasionally circumstances occur in which toil and pain c</div>
      <h3>Cat 1 - Title 3</h3>
      <div>Testing Text 3 Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam
        voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequunt</div>
    </div>
  </div>
  <h3>Category 2</h3>
  <div>
    <div class="faq_subaccordion" id="subaccordion_1">
      <h3>Cat 2 - Title 1</h3>
      <div>Testing Text 1 On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment, so blinded by desire, that they cannot f</div>
      <h3>Cat 2 - Title 2</h3>
      <div>Testing Text 2 rem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad </div>
      <h3>Cat 2 - Title 3</h3>
      <div>Testing Text 3 But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the
        master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself,</div>
    </div>
  </div>
  <h3>Category 3</h3>
  <div>
    <div class="faq_subaccordion" id="subaccordion_2">
      <h3>Cat 3 - Title 1</h3>
      <div>Testing Text 1 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because
        occasionally circumstances occur in which toil and pain c</div>
      <h3>Cat 3 - Title 2</h3>
      <div>Testing Text 2 Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in
        Virginia, looked up one of the more obscure</div>
      <h3>Cat 3 - Title 3</h3>
      <div>Testing Text 3 but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because
        occasionally circumstances occur in which toil and pain c</div>
    </div>
  </div>
</div>

\$\endgroup\$
2
\$\begingroup\$

It seems like there is a defect with how the terms are searched. When you use a word that shows up in an answer and not the question, the answer is replaced in each subsequent answers.

For example, try searching for the word "test".

Cat1 - Title1 will open up and show the answer. However, if you now expand Cat1 - Title2, it will contain the answer from Title1.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.