Tim Marshall Tim Marshall - 22 days ago 8
jQuery Question

Finding the correct place to append content

My jQuery Script:



My jQuery script is quite a large file however I have trimmed it down to the most relevant parts for this question as seen below;

$(document).ready(function() {
"use strict";

$(document).on('click', function(e) {

if (($(e.target).attr('data-action')) || $(e.target).parent().data('action')) {

if ($(e.target).attr('data-action')) {
action = $(e.target).data('action');
} else {
action = $(e.target).parent().data('action');
}

switch (action) {

case 'mail-pin':
// Code for 'mail-pin' click
break;

default:
return;
}
}
});
});


My HTML Structure:



<!-- === INBOX LIST STARTS === -->
<div class="inbox-content clearfix">
<div class="Head">
<!-- Code for inbox header -->
</div>
<div class="Body clearfix">
<div class="Pinned">
<div class="Mail clearfix" data-mail-ID="1234">
<!-- Mail Item -->
</div>
<div class="Mail clearfix" data-mail-ID="1235">
<!-- Mail Item -->
</div>
</div>
<div class="Standard">
<div class="Mail clearfix" data-mail-ID="1233">
<!-- Mail Item -->
</div>
<div class="Mail clearfix" data-mail-ID="1236">
<!-- Mail Item -->
</div>
</div>
</div>
</div>


Question



First of all, I know how to check where the item is to be moved either from the
.Pinned
element or the
.Standard
to the other element via such means seen below;

case 'mail-pin':
console.log('Hello');
if ($(e.target).closest('.Mail').parent().hasClass('Pinned')) {
console.log('It is pinned.');
} else {
console.log('It is not pinned.');
}
break;


What I don't understand how to achieve is how to append the content in the correct location and by this I refer to the order taken from the
data-mail-ID="1233"
so that upon clicking to either pin or unpin, the content will be appended to the place, so if you click to pin mail ID X it will append after X - 1 and visa versa if the item is unpinned.

Important Note:



Each list only displays 20 items per page and upon clicking to go to the next or previous page, the page fetches the content which would have been modified upon clicking to pin or unpin therefore this script would only be relevant for those ID's which can be found on that page, thus meaning if you unpin ID 123 and 122 cannot be found, it is simply removed and for pinning, the append part would only occur if
.Pinned
is visible otherwise that item just removes.

Answer

Search the first mail with a larger id and append the clicked element before it.

(The function below uses a closure to store the relevant DOM parts so I only need to query the DOM once. It's not really needed but that's the way I'm doing such things^^)

var togglePinState = (function () {
    var pinned = document.querySelector(".Pinned"),
        unpinned = document.querySelector(".Standard"),
        pinnedMails = pinned.getElementsByClassName("Mail"),
        unpinnedMails = unpinned.getElementsByClassName("Mail");
        // .getElementsByClassName() because it returns a live HTMLCollection
        // pinnedMails and unpinnedMails will always have the currently un/pinned "mails" without re-querying the DOM

    return function (mailItem) {
        var mailId = parseInt(mailItem.getAttribute("data-mail-ID"), 10),
            mailItemIsPinned = (mailItem.parentNode.className === "Pinned"),
            newParent = (mailItemIsPinned ? unpinned : pinned),
            mailsToCheck = (mailItemIsPinned ? unpinnedMails : pinnedMails),
            // variables for the loop below
            i = 0,
            l = mailsToCheck.length,
            currentId;

        for (; i < l; i++) {
            currentId = parseInt(mailsToCheck[i].getAttribute("data-mail-ID"), 10);
            if (currentId > mailId) {
                // insert before first pinned mail with a bigger id
                mailsToCheck[i].insertAdjacentElement("beforebegin", mailItem);
                return;
            }
        }

        // at this point we have not found a mail with a bigger id so we can safely append it at the end of the list
        newParent.appendChild(mailItem);
    }
}());

To use it in your script just call it in the mail-pin branch:

case 'mail-pin':
    togglePinState(e.target);
    break;

And here is the function in action:

    var togglePinState = (function() {
      var pinned = document.querySelector(".Pinned"),
        unpinned = document.querySelector(".Standard"),
        pinnedMails = pinned.getElementsByClassName("Mail"),
        unpinnedMails = unpinned.getElementsByClassName("Mail");
      // .getElementsByClassName() because it returns a live HTMLCollection
      // pinnedMails and unpinnedMails will always have the currently un/pinned "mails" without re-querying the DOM

      return function(mailItem) {
        var mailId = parseInt(mailItem.getAttribute("data-mail-ID"), 10),
          mailItemIsPinned = (mailItem.parentNode.className === "Pinned"),
          newParent = (mailItemIsPinned ? unpinned : pinned),
          mailsToCheck = (mailItemIsPinned ? unpinnedMails : pinnedMails),
          // variables for the loop below
          i = 0,
          l = mailsToCheck.length,
          currentId;

        for (; i < l; i++) {
          currentId = parseInt(mailsToCheck[i].getAttribute("data-mail-ID"), 10);
          if (currentId > mailId) {
            // insert before first pinned mail with a bigger id
            mailsToCheck[i].insertAdjacentElement("beforebegin", mailItem);
            return;
          }
        }

        // at this point we have not found a mail with a bigger id so we can safely append it at the end of the list
        newParent.appendChild(mailItem);
      }
    }());

document.querySelector("div.inbox-content")
					.addEventListener("click", function(e) {
          	if (e.target.nodeName === "DIV" && e.target.classList.contains("Mail")) {
            	togglePinState(e.target);
            }
          });
div { padding: 2px }
div.Mail {
  border: dotted 1px black;
  text-align: center;
}
.Pinned { border: solid 1px red }
.Standard { border: solid 1px blue }
<!-- === INBOX LIST STARTS === -->
<div class="inbox-content clearfix">
  <div class="Head">
    <!-- Code for inbox header -->
  </div>
  <div class="Body clearfix">
    <div class="Pinned">
      <div class="Mail clearfix" data-mail-ID="1234">1234</div>
      <div class="Mail clearfix" data-mail-ID="1237">1237</div>
    </div>
    <div class="Standard">
      <div class="Mail clearfix" data-mail-ID="1233">1233</div>
      <div class="Mail clearfix" data-mail-ID="1235">1235</div>
      <div class="Mail clearfix" data-mail-ID="1236">1236</div>
      <div class="Mail clearfix" data-mail-ID="1238">1238</div>
    </div>
  </div>
</div>

Comments