diff --git a/dnd-content.hbs b/dnd-content.hbs new file mode 100644 index 0000000..b11e31b --- /dev/null +++ b/dnd-content.hbs @@ -0,0 +1 @@ + diff --git a/dnd.hbs b/dnd.hbs new file mode 100644 index 0000000..fd47fc0 --- /dev/null +++ b/dnd.hbs @@ -0,0 +1,12 @@ + + + + + {{htmlWebpackPlugin.options.title}} + + +
+ {{> "./dnd-content.hbs"}} +
+ + diff --git a/src/dnd.js b/src/dnd.js new file mode 100644 index 0000000..9ee087c --- /dev/null +++ b/src/dnd.js @@ -0,0 +1,91 @@ +/* Задание со звездочкой */ + +/* + Создайте страницу с кнопкой. + При нажатии на кнопку должен создаваться div со случайными размерами, цветом и позицией на экране + Необходимо предоставить возможность перетаскивать созданные div при помощи drag and drop + Запрещено использовать сторонние библиотеки. Разрешено пользоваться только тем, что встроено в браузер + */ + +/* + homeworkContainer - это контейнер для всех ваших домашних заданий + Если вы создаете новые html-элементы и добавляете их на страницу, то дабавляйте их только в этот контейнер + + Пример: + const newDiv = document.createElement('div'); + homeworkContainer.appendChild(newDiv); + */ +const homeworkContainer = document.querySelector('#homework-container'); + +/* + Функция должна создавать и возвращать новый div с классом draggable-div и случайными размерами/цветом/позицией + Функция должна только создавать элемент и задвать ему случайные размер/позицию/цвет + Функция НЕ должна добавлять элемент на страницу. На страницу элемент добавляется отдельно + + Пример: + const newDiv = createDiv(); + homeworkContainer.appendChild(newDiv); + */ +function createDiv() { + function getRandom(min, max) { + return Math.random() * (max - min) + min; + } + + const newDiv = document.createElement('div'); + + newDiv.style.height = getRandom(1, 600) + 'px'; + newDiv.style.width = getRandom(1, 600) + 'px'; + newDiv.style.backgroundColor = '#' + Math.random().toString(16).slice(-6); + newDiv.style.position = 'absolute'; + newDiv.style.left = getRandom(1, 600) + 'px'; + newDiv.style.top = getRandom(1, 600) + 'px'; + newDiv.classList.add('draggable-div'); + + return newDiv; +} + +/* + Функция должна добавлять обработчики событий для перетаскивания элемента при помощи drag and drop + + Пример: + const newDiv = createDiv(); + homeworkContainer.appendChild(newDiv); + addListeners(newDiv); + */ +function addListeners(target) { + target.addEventListener('mousedown', (event) => { + let oX = event.pageX - target.getBoundingClientRect().left; + let oY = event.pageY - target.getBoundingClientRect().top; + + let moveTo = (event) => { + target.style.left = event.pageX - oX + 'px'; + target.style.top = event.pageY - oY + 'px'; + }; + + let mouseUp = () => { + document.removeEventListener('mousemove', moveTo); + target.removeEventListener('mouseup', mouseUp); + }; + + document.addEventListener('mousemove', moveTo); + target.addEventListener('mouseup', mouseUp); + }); +} + +let addDivButton = homeworkContainer.querySelector('#addDiv'); + +addDivButton.addEventListener('click', function() { + // создать новый div + const div = createDiv(); + + // добавить на страницу + homeworkContainer.appendChild(div); + // назначить обработчики событий мыши для реализации D&D + addListeners(div); + // можно не назначать обработчики событий каждому div в отдельности, а использовать делегирование + // или использовать HTML5 D&D - https://www.html5rocks.com/ru/tutorials/dnd/basics/ +}); + +export { + createDiv +}; diff --git a/src/index.js b/src/index.js index e69de29..3ad23d2 100644 --- a/src/index.js +++ b/src/index.js @@ -0,0 +1,94 @@ +/* ДЗ 5 - DOM Events */ + +/* + Задание 1: + + Функция должна добавлять обработчик fn события eventName к элементу target + + Пример: + addListener('click', document.querySelector('a'), () => console.log('...')) // должна добавить указанный обработчик кликов на указанный элемент + */ +function addListener(eventName, target, fn) { + target.addEventListener(eventName, fn); +} + +/* + Задание 2: + + Функция должна удалять у элемента target обработчик fn события eventName + + Пример: + removeListener('click', document.querySelector('a'), someHandler) // должна удалить указанный обработчик кликов на указанный элемент + */ +function removeListener(eventName, target, fn) { + target.removeEventListener(eventName, fn) +} + +/* + Задание 3: + + Функция должна добавить к элементу target такой обработчик на события eventName, чтобы он отменял действия по умолчанию + + Пример: + skipDefault('click', document.querySelector('a')) // после вызова функции, клики на указанную ссылку не должны приводить к переходу на другую страницу + */ +function skipDefault(eventName, target) { + target.addEventListener(eventName, (e) => { + e.preventDefault(); + }) +} + +/* + Задание 4: + + Функция должна эмулировать событие click для элемента target + + Пример: + emulateClick(document.querySelector('a')) // для указанного элемента должно быть сэмулировано события click + */ +function emulateClick(target) { + let click = new Event('click'); + + target.dispatchEvent(click); +} + +/* + Задание 5: + + Функция должна добавить такой обработчик кликов к элементу target, + который реагирует (вызывает fn) только на клики по элементам BUTTON внутри target + + Пример: + delegate(document.body, () => console.log('кликнули на button')) // добавит такой обработчик кликов для body, который будет вызывать указанную функцию только если кликнули на кнопку (элемент с тегом button) + */ +function delegate(target, fn) { + target.addEventListener('click', e => { + e.target.tagName === 'BUTTON' ? fn() : null; + }); +} + +/* + Задание 6: + + Функция должна добавить такой обработчик кликов к элементу target, + который сработает только один раз и удалится (перестанет срабатывать для последующих кликов по указанному элементу) + + Пример: + once(document.querySelector('button'), () => console.log('обработчик выполнился!')) // добавит такой обработчик кликов для указанного элемента, который вызовется только один раз и затем удалится + */ +function once(target, fn) { + function remove() { + fn(); + target.removeEventListener('click', remove); + } + target.addEventListener('click', remove); +} + +export { + addListener, + removeListener, + skipDefault, + emulateClick, + delegate, + once +}; diff --git a/test/dnd.js b/test/dnd.js new file mode 100644 index 0000000..1c9460c --- /dev/null +++ b/test/dnd.js @@ -0,0 +1,47 @@ +import assert from 'assert'; +let template = require('../dnd-content.hbs'); + +describe('ДЗ 5.2 - Div D&D', () => { + let homeworkContainer = document.createElement('div'); + let addDivButton; + let dndPage; + + homeworkContainer.id = 'homework-container'; + homeworkContainer.innerHTML = template(); + document.body.appendChild(homeworkContainer); + dndPage = require('../src/dnd'); + + describe('Функциональное тестирование', () => { + describe('createDiv', () => { + it('должна создавать div с произвольными размерами/позицией/цветом', () => { + let result = dndPage.createDiv(); + + assert(result instanceof Element, 'не элемент'); + assert.equal(result.tagName, 'DIV', 'имя тега не DIV'); + assert.notEqual(result.style.backgroundColor || result.style.background, '', 'не указан цвет фона'); + assert.notEqual(result.style.top, '', 'не указана позиция по оси Y'); + assert.notEqual(result.style.left, '', 'не указана позиция по оси X'); + assert.notEqual(result.style.width, '', 'не указана ширина'); + assert.notEqual(result.style.height, '', 'не указана высота'); + }); + }); + }); + + describe('Интеграционное тестирование', () => { + it('на старнице должна быть кнопка с id addDiv', () => { + addDivButton = homeworkContainer.querySelector('#addDiv'); + assert(addDivButton instanceof Element, 'не элемент'); + assert.equal(addDivButton.tagName, 'BUTTON', 'имя тега не BUTTON'); + }); + + it('создавать div с классом draggable-div при клике на кнопку', () => { + let divsCount = homeworkContainer.querySelectorAll('.draggable-div').length; + let newDivsCount; + + addDivButton.dispatchEvent(new MouseEvent('click', { view: window })); + newDivsCount = homeworkContainer.querySelectorAll('.draggable-div').length; + + assert.equal(newDivsCount - divsCount, 1); + }); + }); +}); diff --git a/test/index.js b/test/index.js index 9d2a198..8fa5e00 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,111 @@ import assert from 'assert'; +import { + addListener, + removeListener, + skipDefault, + emulateClick, + delegate, + once +} from '../src/index'; -describe('Test', () => { - it('should work', () => { - assert(true == true); +describe('ДЗ 5.1 - DOM Events', () => { + describe('addListener', () => { + it('должна добавлять обработчик событий элемента', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let passed = false; + let fn = () => passed = true; + + addListener(eventName, target, fn); + + assert(!passed); + target.dispatchEvent(new CustomEvent(eventName)); + assert(passed); + }); + }); + + describe('removeListener', () => { + it('должна удалять обработчик событий элемента', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let passed = false; + let fn = () => passed = true; + + target.addEventListener(eventName, fn); + + removeListener(eventName, target, fn); + + target.dispatchEvent(new CustomEvent(eventName)); + assert(!passed); + }); + }); + + describe('skipDefault', () => { + it('должна добавлять такой обработчик, который предотвращает действие по умолчанию', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let result; + + skipDefault(eventName, target); + + result = target.dispatchEvent(new CustomEvent(eventName, { cancelable: true })); + assert(!result); + }); + }); + + describe('emulateClick', () => { + it('должна эмулировать клик по элементу', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let passed = false; + let fn = () => passed = true; + + target.addEventListener(eventName, fn); + + emulateClick(target); + + assert(passed); + }); + }); + + describe('delegate', () => { + it('должна добавлять обработчик кликов, который реагирует только на клики по кнопкам', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let passed = false; + let fn = () => passed = true; + + target.innerHTML = '

'; + + delegate(target, fn); + + assert(!passed); + target.children[0].dispatchEvent(new CustomEvent(eventName, { bubbles: true })); + assert(!passed); + target.children[1].dispatchEvent(new CustomEvent(eventName, { bubbles: true })); + assert(!passed); + target.children[2].dispatchEvent(new CustomEvent(eventName, { bubbles: true })); + assert(!passed); + target.children[3].dispatchEvent(new CustomEvent(eventName, { bubbles: true })); + assert(passed); + }); + }); + + describe('once', () => { + it('должна добавлять обработчик кликов, который сработает только один раз и удалится', () => { + let target = document.createElement('div'); + let eventName = 'click'; + let passed = 0; + let fn = () => passed++; + + once(target, fn); + + assert.equal(passed, 0); + target.dispatchEvent(new CustomEvent(eventName)); + assert.equal(passed, 1); + target.dispatchEvent(new CustomEvent(eventName)); + assert.equal(passed, 1); + target.dispatchEvent(new CustomEvent(eventName)); + }); }); }); diff --git a/webpack.config.js b/webpack.config.js index 275df68..42e45ca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,13 @@ rules.push({ }); module.exports = { - entry: './src/index.js', + entry: { + main: './src/index.js', + dnd: './src/dnd.js' + }, + devServer: { + index: 'dnd.html' + }, output: { filename: '[name].[hash].js', path: path.resolve('dist') @@ -31,8 +37,15 @@ module.exports = { }), new ExtractTextPlugin('styles.css'), new HtmlPlugin({ - title: 'Loft School sample project', - template: 'index.hbs' + title: 'Main Homework', + template: 'main.hbs', + chunks: ['main'] + }), + new HtmlPlugin({ + title: 'Div Drag And Drop', + template: 'dnd.hbs', + filename: 'dnd.html', + chunks: ['dnd'] }), new CleanWebpackPlugin(['dist']) ]