MailerLite "JavaScript PHP developer" test assignment

by Alexander Terehov

HTML

<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>MailerLite "JavaScript PHP developer" test assignment</title>
  </head>

  <body>
    <div class="items">
      <div class="item" data-id="div1">Div #1</div>
      <div class="item" data-id="div2">Div #2</div>
      <div class="item" data-id="div3">Div #3</div>
      <div class="item" data-id="div4">Div #4</div>
      <div class="item" data-id="div5">Div #5</div>
    </div>

    <div class="actions">
      <button class="action" id="add">Add</button>
    </div>
  </body>

</html>

CSS

.item {
  border: solid 1px orange;
  padding: 15px;
  margin: 15px;
  background-color: lightyellow;
}

.action {
  margin: 15px;
}

/* Introduced changes ---------------------------- */

.item {
  cursor: grab;
}

.item::before {
  content: '•• •• ••';
  display: inline-block;
  overflow: hidden;
  vertical-align: middle;
  width: 7px;
  height: 20px;
  margin: -0.5em 5px -0.3em -16px;
  padding: 3px 4px;
  font-family: monospace;
  font-size: 9px;
  line-height: 6px;
  color: #cccccc;
}

 .sortable-chosen {
  cursor: grabbing;
  box-shadow: 5px 5px 5px 0px rgba(0, 0, 0, 0.75);
}

.sortable-ghost.sortable-chosen {
  opacity: 0.2;
  box-shadow: none;
}

.sortable-drag {
  opacity: 1;
  box-shadow: 5px 5px 5px 0px rgba(0, 0, 0, 0.75);
}

JavaScript

const listEl = document.querySelector('.items')
const listSortable = Sortable.create(listEl, getSortableOptions(listEl))

document.getElementById('add').addEventListener('click', function() {
  const increment = listEl.children.length + 1
  listEl.appendChild(createItemEl(increment))
  listSortable.save()
  document.body.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' })
})

function getSortableOptions(listEl) {
  return {
    animation: 250,
    easing: 'cubic-bezier(0.075, 0.82, 0.165, 1)',

    delay: 250,
    delayOnTouchOnly: true,

    group: 'itemsListSortable',
    store: {
      get: function(sortableInstance) {
        const savedDataIdsStr = window.localStorage.getItem(sortableInstance.options.group.name)
        const savedDataIds = savedDataIdsStr ? savedDataIdsStr.split('|') : []
        listEl.appendChild(getMissingElements({ existingElementsArr: [...listEl.querySelectorAll('.item')], savedDataIds }))
        return savedDataIds;
      },
      set: function(sortableInstance) {
        const list = sortableInstance.toArray()
        window.localStorage.setItem(sortableInstance.options.group.name, list.join('|'))
      }
    }
  }
}

function createItemEl(increment) {
  return createDiv({
    innerText: `Div #${increment}`,
    attributes: {
      class: 'item',
      'data-id': `div${increment}`
    }
  })
}

function createDiv({ innerText, attributes }) {
  const div = document.createElement('div');

  if (innerText) {
    div.innerText = innerText
  }

  for (const [attribute, value] of Object.entries(attributes)) {
    div.setAttribute(attribute, value)
  }

  return div
}

function getMissingElements({ existingElementsArr, savedDataIds }) {
  const tempContainer = document.createDocumentFragment();
  const existingDataIds = existingElementsArr.map(item => item.dataset.id);
  const newDataIds = savedDataIds.filter(dataId => !existingDataIds.includes(dataId))

  newDataIds.forEach(dataId => {
    const increment =...