title: Adding search functionality in a bootstrap accordion control
body: <p>I am trying to implement a search ability to a Bootstrap collapse/accordion component.  The feature works....kind of.  The problem I am having is tying together the actual bootstrap functionality to the search term and how the control opens and closes.</p>
  
  <p>The search term should look at the lowest level of the array and open the panels if the search term is included in that text value.</p>
  
  <p>I am using Knockout JS to control the viewmodel and obviously Bootstrap for the component display.</p>
  
  <p><a href="http://jsfiddle.net/ascendantofrain/d0rt2b29/6/" rel="nofollow">http://jsfiddle.net/ascendantofrain/d0rt2b29/6/</a></p>
  
  <p><strong>HTML</strong></p>
  
  <pre><code>&lt;script type="text/html" id="accordion-template"&gt;
      &lt;!-- ko foreach: accordionData --&gt;
          &lt;div class="grouping-wrapper"&gt;
              &lt;div class="group-by"&gt;
                  &lt;!-- ko if: $parent.searchEnabled --&gt;
                  &lt;input type="search" class="search-input show" name="search_group-by" placeholder="Search" data-bind="textInput: $parent.searchTerm" /&gt;
                  &lt;!-- /ko --&gt;
                  &lt;div class="group-dropdown" data-bind="css: $parent.helperClass"&gt;
                      &lt;div data-bind="attr: { id: 'accordion-' + id }" class="group-dropdown-top-level"&gt;
                          &lt;!-- ko if: $data.hasOwnProperty('children') &amp;&amp; children.length &gt; 0 --&gt;
                          &lt;div class="panel" data-bind="foreach: children"&gt;
                              &lt;!-- ko if: $parents[1].leafNodesCount($data) &gt; 0 --&gt;
                              &lt;span data-toggle="collapse" data-bind="attr: { 'data-parent': '#accordion-' + $parent.id, href: '#collapse-' + id }, css: 'group-' + ($index() + 1)"&gt;
                                  &lt;!-- ko text: text --&gt;&lt;!-- /ko --&gt;
                                  &lt;i class="icon icon-arrow-right"&gt;&lt;/i&gt;
                              &lt;/span&gt;
                              &lt;div class="collapse group-dropdown-second-level" data-bind="attr: { id: 'collapse-' + id, 'isOpen': li_attr.isOpen }"&gt;
                                  &lt;!-- ko if: $data.hasOwnProperty('children') &amp;&amp; children.length &gt; 0 --&gt;
                                  &lt;div class="panel" data-bind="foreach: children"&gt;
                                      &lt;!-- ko if: children.length &gt; 0 --&gt;
                                      &lt;span data-toggle="collapse" data-bind="attr: { 'data-parent': '#collapse-' + $parent.id, href: '#collapse-' + id }"&gt;
                                          &lt;!-- ko text: text --&gt;&lt;!-- /ko --&gt;
                                          &lt;i class="icon icon-arrow-right"&gt;&lt;/i&gt;
                                      &lt;/span&gt;
                                      &lt;div class="collapse group-dropdown-third-level" data-bind="attr: { id: 'collapse-' + id, 'isHierarchy': li_attr.isHierarchy, 'isOpen': li_attr.isOpen }"&gt;
                                          &lt;!-- ko if: $data.hasOwnProperty('children') &amp;&amp; children.length &gt; 0 --&gt;
                                          &lt;!-- ko foreach: children --&gt;
                                          &lt;div class="panel" data-bind="click: $parents[3].action, text: text, css: { in: $parents[3].search($data) }"&gt;&lt;/div&gt;
                                          &lt;!-- /ko --&gt;
                                          &lt;!-- /ko --&gt;
                                      &lt;/div&gt;
                                      &lt;!-- /ko --&gt;
                                  &lt;/div&gt;
                                  &lt;!-- /ko --&gt;
                              &lt;/div&gt;
                              &lt;!-- /ko --&gt;
                          &lt;/div&gt;
                          &lt;!-- /ko --&gt;
                      &lt;/div&gt;
                  &lt;/div&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;!-- /ko --&gt;
  &lt;/script&gt;
  
  &lt;accordion params="data: metricData, searchEnabled: true"&gt;&lt;/accordion&gt;
  </code></pre>
  
  <p><strong>JS</strong></p>
  
  <pre><code>ko.bindingHandlers.accordionToggleNodePropertyOnClick = {
      init: function(element, valueAccessor, allBindings, viewModel) {
          var options = ko.unwrap(valueAccessor());
  
          //$(element).on('click', function() {
          //    options.property(!options.property());
          //});
      }
  };
  
  ko.components.register('accordion', {
      viewModel: function (params) {
          var self = this;
  
          self.accordionData = params.data || [];
          self.accordionType = params.accordionType || 'default';
          self.searchEnabled = params.searchEnabled || false;
          self.panelPrefix = params.panelPrefix || 'collapse';
          self.helperClass = params.helperClass || '';
  
          self.leafNodesCount = function (node) {
              var cnt = 0;
              _.each(node.children, function (c) {
                  _.each(c.children, function () {
                      cnt++;
                  });
              });
              return cnt;
          };
  
          self.action = function () {
              var action = params.action || 'default';
  
              if (action === 'none') {
                  return;
              }
              if (action === 'pivot') {}
          };
  
          self.searchTerm = ko.observable('');
          self.search = function (node) {
              if (self.searchTerm() &amp;&amp; self.searchTerm().length &gt;= 3) {
                  _.each(self.accordionData, function (c) {
                      _.each(c.children, function (c1) {
                          var open1 = _.find(c1.children, function (c2) {
                              return c2.li_attr.isOpen() === true;
                          });
                          c1.li_attr.isOpen(open1 ? true : false);                        
  
                          if (open1) {
                              $('#' + self.panelPrefix + '-' + c1.id).collapse('show');
                          } else {
                              $('#' + self.panelPrefix + '-' + c1.id).collapse('hide');
                          }
  
                          _.each(c1.children, function (c2) {
                              var open = _.find(c2.children, function (c3) {
                                  return c3.text.toLowerCase().indexOf(self.searchTerm()) !== -1;
                              });
                              c2.li_attr.isOpen(open ? true : false);
  
                              if (open) {
                                  $('#' + self.panelPrefix + '-' + c2.id).collapse('show');
                              } else {
                                  $('#' + self.panelPrefix + '-' + c2.id).collapse('hide');
                              }
                          });
                      });
                  });
  
                  return node.text.toLowerCase().indexOf(self.searchTerm()) !== -1 ? true : false;
              }
          };
  
          self.toggleOpen = function(node) {
  
          };
      },
      template: {
          element: 'accordion-template'
      }
  });
  
  function daVM() {
      // Define Alerts Configuration
      self.metricData = [{
          id: '1',
          text: 'Define Alerts Metric Tab',
          children: [{
              id: '1-1',
              text: 'First',
              li_attr: {
                  isOpen: ko.observable(false)
              },
              children: [{
                  id: '2-1',
                  text: 'Second Level 1',
                  li_attr: {
                      isHierarchy: false,
                      isOpen: ko.observable(false)
                  },
                  children: [{
                      id: '2-3-4',
                      text: 'Thard Level 1',
                      li_attr: {
                          isHierarchy: false,
                          isOpen: ko.observable(false)
                      }
                  }]
              }, {
                  id: '3-1',
                  text: 'Second Level 2',
                  li_attr: {
                      isHierarchy: false,
                      isOpen: ko.observable(false)
                  },
                  children: [{
                      id: '3-1',
                      text: 'Third Level 1',
                      li_attr: {
                          isHierarchy: false,
                          isOpen: ko.observable(false)
                      }
                  }]
              }, {
                  id: '4-1',
                  text: 'Second Level 3',
                  li_attr: {
                      isHierarchy: false,
                      isOpen: ko.observable(false)
                  },
                  children: [{
                      id: '4-1',
                      text: 'Therd Level 1',
                      li_attr: {
                          isHierarchy: false,
                          isOpen: ko.observable(false)
                      }
                  }]
              }],
          }]
      }];
  }
  
  ko.applyBindings(new daVM());
  </code></pre>