Home > other >  Keep div always as bottom when multiple gets appended
Keep div always as bottom when multiple gets appended

Time:12-24

This is a follow up to this question: Sorting divs alphabetically in its own parent (due to many lists)

Hopefully the last problem I have with this. Now the lists sort themself nicely alphabetically and not depending on each other but I need to have one div stricly in the bottom in all lists. See code below, the div with green background (class: last), is it possible to always put this (with its hidden next div if existing) in the bottom in the lists? Maybe append it last and not alphabetically?

Since there is many lists, actually alot of them, I cant wrap some parts of the lists in divs or simular. So if its possible to append it when the rest as been apended alphabetically it would maybe work?

Many thanks for the help so far.

$('.parent').each(function(_, parent) {
  $('.sort', parent).sort(function(a, b) {
    return a.textContent.localeCompare(b.textContent);
  }).each((index, el) => {
      const hidden = $(el).next();
      $(el).appendTo(parent);
      if (hidden.hasClass('hidden')) {
          hidden.appendTo(parent);
      }
  });
});

$(".btn_expand").on('click', function () {
  $(this).next("div").toggle();
});
.btn_expand{background:red;}  
.hidden{display: none;}
.last{background: green;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

<br><br>

<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

CodePudding user response:

You can achieve this in several ways. One is to adapt the sorting callback so that it considers items with a "last" class to be "greater" than others. This would also allow you to have multiple "last" items in one parent -- these would all end up at the end, and among themselves they would be sorted by their texts.

$('.parent').each(function(_, parent) {
  $('.sort', parent).sort(function(a, b) {
    return a.classList.contains("last") - b.classList.contains("last") || 
           a.textContent.localeCompare(b.textContent);
  }).each((index, el) => {
      const hidden = $(el).next();
      $(el).appendTo(parent);
      if (hidden.hasClass('hidden')) {
          hidden.appendTo(parent);
      }
  });
});

$(".btn_expand").on('click', function () {
  $(this).next("div").toggle();
});
.btn_expand{background:red;}  
.hidden{display: none;}
.last{background: green;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

<br><br>

<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

CodePudding user response:

One approach is as follows, with explanatory comments in the JavaScript:

// caching a reference to the document for use within utility functions:
const D = document,
  // two functions serving as an alias for both <document || element>.querySelector()
  // and <document || element>.querySelectorAll(), depending on the context
  // passed to the function:
  get = (selector, context = D) => context.querySelector(selector),
  getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
  // caching the parents:
  parents = getAll('.parent'),
  // creating a Map() to be used to associate the "button" elements and
  // the hidden elements to be toggled:
  relationPairs = new Map();

// iterating over the parents Array using Array.prototype.forEach():
parents.forEach(
  // passing in a reference to the current '.parent' element of the
  // Array of '.parent' elements we're iterating over:
  (parent) => {
    // here we retrive all .hidden elements that immediately follow
    // a .btn_expand element, within the current .parent element:
    getAll('.btn_expand   .hidden', parent).forEach(
      // and we iterate over that Array of elements, again using
      // an Arrow function and passing the current '.hidden'
      // element into the function body;
      // here we set one key of the Map() to be the
      // previous-element sibling of the current element, and
      // the value is set to the current element:
      (el) => relationPairs.set(el.previousElementSibling, el)
    );

    // here we find the ',sort' elements within the current parent,
    // and sort them:
    getAll('.sort', parent).sort(
      // we pass in the first element (a) and the second ('b')
      // to the function, and use String.prototype.localeCompare()
      // to return a number which determines the sort alphabetical
      // sort-order, with the following assumptions:
      // a: referenceString,
      // b: comparisonString
      // the function returns:
      // negative number: if a occurs before b (a is then placed ahead of b),
      // zero (0): if a and be are equal (a and b remain in the same position),
      // positive number: a occurs after b (a is moved after b):

      (a, b) => a.textContent.localeCompare(b.textContent)
      // we then iterate over the sorted Array, using Array.prototype.forEach():
    ).forEach(
      // passing in a reference to the current Array-element to the
      // function body, and here we access the element's parentNode
      // and use Element.append() to append el to the Node (moving
      // it to be the last-child of the element):
      (el) => el.parentNode.append(el)
    );

    // here we find all elements with the .last class, within the current
    // '.parent', and iterate over that Array:
    getAll('.last', parent).forEach(
      // here we - again - add the element as the last-child of its
      // parent:
      (el) => el.parentNode.append(el)
    );

    // here we iterate over the iterable Map() converted to an
    // Array:
    [...relationPairs].forEach(
      // destructuring assignment to retrieve the 'button' ('.btn_expand')
      // and 'response' ('.hidden') elements:
      ([button, response]) => {

        // here we use EventTarget.addEventListener() to bind the anonymous
        // Arrow function as the event-handler for the 'click' event:
        button.addEventListener('click', () => {
          // in which we toggle the element.hidden property to be
          // the inverse of it's current value (it's a Boolean
          // property, so is either true/false; if true we change
          // it to false, and vice versa):
          response.hidden = !response.hidden;
        });

        // here we use Element.after() to move the
        // 'response' element to be after the
        // 'button':
        button.after(response);

        // we set the Element.hidden property of each
        // 'response' to be true (so hidden) on page-load:
        response.hidden = true;

        // and remove the 'button' key (and associated value)
        // from the relationPairs Map();
        relationPairs.delete(button);
      });
  });
.parent {
  border: 2px solid currentColor;
}

.parent:not(:first-child) {
  margin-block: 1em;
}

.btn_expand {
  background: red;
  cursor: pointer;
}

.last {
  background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

JS Fiddle demo.

And an uncommented version:

const D = document,
      get = (selector, context = D) => context.querySelector(selector),
      getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
      parents = getAll('.parent'),
      relationPairs = new Map();

parents.forEach(
  (parent) => {
    getAll('.btn_expand   .hidden', parent).forEach(
      (el) => relationPairs.set(el.previousElementSibling, el)
    );

    getAll('.sort', parent).sort(
      (a, b) => a.textContent.localeCompare(b.textContent)
    ).forEach(
      (el) => el.parentNode.append(el)
    );

    getAll('.last', parent).forEach(
      (el) => el.parentNode.append(el)
    );

    [...relationPairs].forEach(
      ([button, response]) => {
        button.addEventListener('click', () => {
          response.hidden = !response.hidden;
        });

        button.after(response);
        response.hidden = true;
        relationPairs.delete(button);
      });
  });
.parent {
  border: 2px solid currentColor;
}

.parent:not(:first-child) {
  margin-block: 1em;
}

.btn_expand {
  background: red;
  cursor: pointer;
}

.last {
  background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

<div >
  <div >CCC</div>
  <div >Hidden content shown by press CCC</div>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <div >BBB</div>
  <div >Hidden content shown by press BBB</div>
</div>

It's also worth noting that some of the requirements could be met by simply using appropriate HTML, and CSS; with the <summary> element handling the show/hide visibility of the related content, and the order property being used in CSS – if the .parent elements have display: grid or display: flex – to move the .last element(s) to the end of the .parent:

const D = document,
      get = (selector, context = D) => context.querySelector(selector),
      getAll = (selector, context = D) => [...context.querySelectorAll(selector)],
      parents = getAll('.parent');

getAll('.parent > .sort').sort(
  (a, b) => a.textContent.localeCompare(b.textContent)
).forEach(
  (el) => el.parentNode.append(el)
);
.parent {
  border: 2px solid currentColor;
  display: grid;
  padding: 0.25em;
}

.parent:not(:first-child) {
  margin-block: 1em;
}

.btn_expand:not(sumamry) {
  background: red;
  cursor: pointer;
}

.last {
  background: lime;
  order: 100;
}

summary {
  background-color: inherit;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div >
  <details>
    <summary >CCC</summary>
    <div >Hidden content shown by press CCC</div>
  </details>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <details>
    <summary >BBB</summary>
    <div >Hidden content shown by press BBB</div>
  </details>
</div>

<div >
  <details >
    <summary >CCC</summary>
    <div >Hidden content shown by press CCC</div>
  </details>
  <div >EEE</div>
  <div >AAA</div>
  <div >DDD</div>
  <details>
    <summary >BBB</summary>
    <div >Hidden content shown by press BBB</div>
  </details>
</div>

JS Fiddle demo.

  • Related