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 =...