From 0dcfde6d13b249c154242f54aa923096e375c23e Mon Sep 17 00:00:00 2001 From: jmuzsik Date: Sat, 24 Mar 2018 16:09:24 -0400 Subject: [PATCH 1/3] beginning of conversion of sortable to a class --- src/html5sortable.ts | 150 +++++++++++++++++++++++--------------- src/types/extensions.d.ts | 6 ++ src/types/main.d.ts | 7 +- src/types/temp.ts | 52 +++++++++++++ tsconfig.json | 3 +- 5 files changed, 154 insertions(+), 64 deletions(-) create mode 100644 src/types/extensions.d.ts create mode 100644 src/types/temp.ts diff --git a/src/html5sortable.ts b/src/html5sortable.ts index c3371820..1de4e607 100644 --- a/src/html5sortable.ts +++ b/src/html5sortable.ts @@ -229,11 +229,10 @@ const _reloadSortable = function (sortableElement) { * @param {Array|NodeList} sortableElements * @param {object|string} options|method */ -export default function sortable (sortableElements, options: object|string|undefined) { - // get method string to see if a method is called - const method = String(options) - // merge user options with defaultss - options = Object.assign({ +export default class sortable { + sortableElements: any + options: configuration + defaultOptions: configuration = { connectWith: false, acceptFrom: null, copy: false, @@ -247,67 +246,96 @@ export default function sortable (sortableElements, options: object|string|undef itemSerializer: undefined, containerSerializer: undefined, customDragImage: null - // if options is an object, merge it, otherwise use empty object - }, (typeof options === 'object') ? options : {}) - // check if the user provided a selector instead of an element - if (typeof sortableElements === 'string') { - sortableElements = document.querySelectorAll(sortableElements) } - // if the user provided an element, return it in an array to keep the return value consistant - if (sortableElements instanceof HTMLElement) { - sortableElements = [sortableElements] + constructor(sortableElements, options: configuration) { + this.sortableElements = sortableElements + this.options = options } - sortableElements = Array.prototype.slice.call(sortableElements) - - if (/serialize/.test(method)) { - return sortableElements.map((sortableContainer) => { - let opts = _data(sortableContainer, 'opts') - return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer) - }) + serialize(method) { + if (/serialize/.test(method)) { + return this.sortableElements.map((sortableContainer) => { + let configuration = _data(sortableContainer, 'opts') + return _serialize(sortableContainer, configuration.itemSerializer, configuration.containerSerializer) + }) + } } - sortableElements.forEach(function (sortableElement) { + checkForSpecificMethod(method, sortableElement) { if (/enable|disable|destroy/.test(method)) { return sortable[method](sortableElement) } - // init data store for sortable - store(sortableElement).config = options - // get options & set options on sortable - options = _data(sortableElement, 'opts') || options - _data(sortableElement, 'opts', options) - // property to define as sortable - sortableElement.isSortable = true - // reset sortable - _reloadSortable(sortableElement) - // initialize - const listItems = _filter(sortableElement.children, options.items) - // create element if user defined a placeholder element as a string - let customPlaceholder - if (options.placeholder !== null && options.placeholder !== undefined) { + } + + createCustomPlaceholder(sortableElement) { + if (this.options.placeholder !== null && this.options.placeholder !== undefined) { let tempContainer = document.createElement(sortableElement.tagName) - tempContainer.innerHTML = options.placeholder - customPlaceholder = tempContainer.children[0] + tempContainer.innerHTML = this.options.placeholder + return tempContainer.children[0] } - // add placeholder - store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass) + } - _data(sortableElement, 'items', options.items) + alterData(sortableElement) { + _data(sortableElement, 'items', this.options.items) + if (this.options.acceptFrom) { + _data(sortableElement, 'acceptFrom', this.options.acceptFrom) + } else if (this.options.connectWith) { + _data(sortableElement, 'connectWith', this.options.connectWith) + } + } - if (options.acceptFrom) { - _data(sortableElement, 'acceptFrom', options.acceptFrom) - } else if (options.connectWith) { - _data(sortableElement, 'connectWith', options.connectWith) + main() { + // get method string to see if a method is called + const method = String(this.options) + // merge user this.options with defaults + this.options = Object.assign(this.defaultOptions, + // if this.options is an object, merge it, otherwise use empty object + (typeof this.options === 'object') ? this.options : {}) + + // check if the user provided a selector instead of an element + if (typeof this.sortableElements === 'string') { + this.sortableElements = document.querySelectorAll(this.sortableElements) } + console.log(this.sortableElements instanceof HTMLElement, typeof this.sortableElements) + // if the user provided an element, return it in an array to keep the return value consistant + if (this.sortableElements instanceof HTMLElement) { + this.sortableElements = [this.sortableElements] + } + this.sortableElements = Array.prototype.slice.call(this.sortableElements) + + this.serialize(method) + this.sortableElements.forEach((sortableElement) => { + this.checkForSpecificMethod(method, sortableElement) + // init data store for sortable + store(sortableElement).config = this.options + // get this.options & set this.options on sortable + this.options = _data(sortableElement, 'opts') || this.options + _data(sortableElement, 'opts', this.options) + // property to define as sortable + sortableElement.isSortable = true + // reset sortable + _reloadSortable(sortableElement) + // initialize + const items = _filter(sortableElement.children, this.options.items) + const itemStartIndex + const startList + // create element if user defined a placeholder element as a string + let customPlaceholder = this.createCustomPlaceholder(sortableElement) + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, this.options.placeholderClass) + this.alterData(sortableElement) + } + } + + this.sortableElements.forEach(function (sortableElement) { _enableSortable(sortableElement) _attr(listItems, 'role', 'option') _attr(listItems, 'aria-grabbed', 'false') // Mouse over class - // TODO - only assign hoverClass if not dragging - if (typeof options.hoverClass === 'string') { - let hoverClasses = options.hoverClass.split(' ') + if (typeof this.options.hoverClass === 'string') { + let hoverClasses = this.options.hoverClass.split(' ') // add class on hover _on(listItems, 'mouseenter', function (e) { e.target.classList.add(...hoverClasses) @@ -330,7 +358,7 @@ export default function sortable (sortableElements, options: object|string|undef } e.stopImmediatePropagation() - if ((options.handle && !e.target.matches(options.handle)) || e.target.getAttribute('draggable') === 'false') { + if ((this.options.handle && !e.target.matches(this.options.handle)) || e.target.getAttribute('draggable') === 'false') { return } @@ -344,11 +372,11 @@ export default function sortable (sortableElements, options: object|string|undef originContainer = sortableContainer // add transparent clone or other ghost to cursor - setDragImage(e, dragItem, options.customDragImage) + setDragImage(e, dragitem, this.options.customDragImage) // cache selsection & add attr for dragging - draggingHeight = _getElementHeight(dragItem) - dragItem.classList.add(options.draggingClass) - dragging = _getDragging(dragItem, sortableContainer) + draggingHeight = _getElementHeight(dragitem) + dragitem.classList.add(this.options.draggingClass) + dragging = _getDragging(dragitem, sortableElement) _attr(dragging, 'aria-grabbed', 'true') // dispatch sortstart event on each element in group @@ -385,7 +413,7 @@ export default function sortable (sortableElements, options: object|string|undef return } - dragging.classList.remove(options.draggingClass) + dragging.classList.remove(this.options.draggingClass) _attr(dragging, 'aria-grabbed', 'false') if (dragging.getAttribute('aria-copied') === 'true' && _data(dragging, 'dropped') !== 'true') { @@ -501,7 +529,7 @@ export default function sortable (sortableElements, options: object|string|undef } // set placeholder height if forcePlaceholderSize option is set - if (options.forcePlaceholderSize) { + if (this.options.forcePlaceholderSize) { store(sortableElement).placeholder.style.height = draggingHeight + 'px' } // if element the draggedItem is dragged onto is within the array of all elements in list @@ -554,12 +582,12 @@ export default function sortable (sortableElements, options: object|string|undef return data.placeholder }) // check if element is not in placeholders - if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) { + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, this.options.items).length) { placeholders.forEach((element) => element.remove()) element.appendChild(store(sortableElement).placeholder) } } - }, options.debounce) + }, this.options.debounce) // Handle dragover and dragenter events on draggable items const onDragOverEnter = function (e) { let element = e.target @@ -568,8 +596,8 @@ export default function sortable (sortableElements, options: object|string|undef if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || _data(sortableElement, '_disabled') === 'true') { return } - const options = _data(sortableElement, 'opts') - if (parseInt(options.maxItems) && _filter(sortableElement.children, _data(sortableElement, 'items')).length >= parseInt(options.maxItems)) { + var this.options = _data(sortableElement, 'opts') + if (parseInt(this.options.maxItems) && _filter(sortableElement.children, _data(sortableElement, 'items')).length >= parseInt(this.options.maxItems)) { return } e.preventDefault() @@ -581,10 +609,12 @@ export default function sortable (sortableElements, options: object|string|undef _on(listItems.concat(sortableElement), 'dragover', onDragOverEnter) _on(listItems.concat(sortableElement), 'dragenter', onDragOverEnter) }) - - return sortableElements + return this.sortableElements } + + + sortable.destroy = function (sortableElement) { _destroySortable(sortableElement) } diff --git a/src/types/extensions.d.ts b/src/types/extensions.d.ts new file mode 100644 index 00000000..5534cacf --- /dev/null +++ b/src/types/extensions.d.ts @@ -0,0 +1,6 @@ +interface ObjectConstructor { + assign(target: T, source: U): T & U; + assign(target: T, source1: U, source2: V): T & U & V; + assign(target: T, source1: U, source2: V, source3: W): T & U & V & W; + assign(target: any, ...sources: any[]): any; +} \ No newline at end of file diff --git a/src/types/main.d.ts b/src/types/main.d.ts index 039f21ab..da4d6b82 100644 --- a/src/types/main.d.ts +++ b/src/types/main.d.ts @@ -6,9 +6,9 @@ interface configuration { connectWith: boolean, - acceptFrom: void, + acceptFrom: null, copy: boolean, - placeholder: void, + placeholder: null, disableIEFix: boolean, placeholderClass: string, draggingClass: string, @@ -17,7 +17,8 @@ interface configuration { maxItems: number, itemSerializer: void, containerSerializer: void, - items: string + items?: string, + customDragImage: null } interface data { diff --git a/src/types/temp.ts b/src/types/temp.ts new file mode 100644 index 00000000..2945c1bb --- /dev/null +++ b/src/types/temp.ts @@ -0,0 +1,52 @@ +export default class sortable { + sortableElements: any + options: object|string|undefined + constructor(sortableElements, options: object|string|undefined) { + this.sortableElements = sortableElements + this.options = options + this.setOptions(); + this.reformatSortableElements(); + this.serialize(); + } + // get method string to see if a method is called + method: string = String(this.options) + // merge user this.options with defaults + setOptions() { + this.options = Object.assign({ + connectWith: false, + acceptFrom: null, + copy: false, + disableIEFix: false, + placeholder: null, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null + // if this.options is an object, merge it, otherwise use empty object + }, (typeof this.options === 'object') ? this.options : {}) + } + reformatSortableElements() { + // check if the user provided a selector instead of an element + if (typeof this.sortableElements === 'string') { + this.sortableElements = document.querySelectorAll(this.sortableElements) + } + // if the user provided an element, return it in an array to keep the return value consistant + if (this.sortableElements instanceof Element) { + this.sortableElements = [this.sortableElements] + } + + this.sortableElements = Array.prototype.slice.call(this.sortableElements) + } + + serialize() { + if (/serialize/.test(method)) { + return this.sortableElements.map((sortableContainer) => { + let opts = _data(sortableContainer, 'opts') + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer) + }) + } + } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c026593f..062db686 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "module": "commonjs", "target": "ESNext", - "strictNullChecks": true + "strictNullChecks": true, + "lib": ["dom", "es6", "scripthost"] }, "typeRoots": [ "./src/types", From 721834f8da007744aa9fd5cc9a891e1782674b47 Mon Sep 17 00:00:00 2001 From: jmuzsik Date: Sat, 24 Mar 2018 21:59:29 -0400 Subject: [PATCH 2/3] WIP - turn sortable into a class --- __tests__/api.test.ts | 45 +- __tests__/events.test.ts | 33 +- __tests__/options.test.ts | 10 +- __tests__/serialize.test.ts | 16 +- .../_listsConnected.test.ts | 78 +-- .../_removeItemData.test.ts | 12 +- .../_removeItemEvents.test.ts | 6 +- src/eventListener.ts | 2 +- src/html5sortable.ts | 623 +++++++++--------- src/types/main.d.ts | 3 +- 10 files changed, 412 insertions(+), 416 deletions(-) diff --git a/__tests__/api.test.ts b/__tests__/api.test.ts index e44330a0..3c573f5a 100644 --- a/__tests__/api.test.ts +++ b/__tests__/api.test.ts @@ -1,7 +1,8 @@ /* global describe,expect,test,beforeEach,beforeAll */ -import sortable from '../src/html5sortable' +import Sortable from '../src/html5sortable' import store from '../src/store' /* eslint-env jest */ +/* eslint-disable no-new */ describe('Testing api', () => { document.body.innerHTML = `
` @@ -22,7 +23,7 @@ describe('Testing api', () => { secondLi = ul.querySelector('.item-second') thirdLi = ul.querySelector('.item-second') - sortable(ul, { + new Sortable(ul, { 'items': 'li', 'connectWith': '.test', placeholderClass: 'test-placeholder', @@ -31,11 +32,11 @@ describe('Testing api', () => { }) test('should have a data-opts object', () => { - expect(typeof sortable.__testing._data(ul, 'opts')).toBe('object') + expect(typeof Sortable.__testing._data(ul, 'opts')).toBe('object') }) test('should have correct options set on options object', () => { - let opts = sortable.__testing._data(ul, 'opts') + let opts = Sortable.__testing._data(ul, 'opts') expect(opts.items).toEqual('li') expect(opts.connectWith).toEqual('.test') expect(opts.placeholderClass).toEqual('test-placeholder') @@ -47,11 +48,11 @@ describe('Testing api', () => { }) test('should have a data-items object', () => { - expect(typeof sortable.__testing._data(ul, 'items')).toBe('string') + expect(typeof Sortable.__testing._data(ul, 'items')).toBe('string') }) test('should have a h5s.connectWith object', () => { - expect(typeof sortable.__testing._data(ul, 'connectWith')).toBe('string') + expect(typeof Sortable.__testing._data(ul, 'connectWith')).toBe('string') }) test('should have aria-grabbed attributes', () => { @@ -80,7 +81,7 @@ describe('Testing api', () => { }) test('string placehodler', () => { - sortable(ul, { + new Sortable(ul, { 'items': 'li', 'connectWith': '.test', placeholderClass: 'test-placeholder', @@ -92,15 +93,15 @@ describe('Testing api', () => { describe('Destroy', () => { beforeEach(() => { - sortable(ul, { + new Sortable(ul, { 'items': 'li', 'connectWith': '.test' }) - sortable(ul, 'destroy') + new Sortable(ul, 'destroy') }) test('should not have a data-opts object', () => { - expect(typeof sortable.__testing._data(ul, 'opts')).toBe('undefined') + expect(typeof Sortable.__testing._data(ul, 'opts')).toBe('undefined') }) test('should not have a aria-dropeffect attribute', () => { @@ -108,11 +109,11 @@ describe('Testing api', () => { }) test('should not have a data-items object', () => { - expect(sortable.__testing._data(ul, 'items')).not.toBeDefined() + expect(Sortable.__testing._data(ul, 'items')).not.toBeDefined() }) test('should not have a h5s.connectWith object', () => { - expect(sortable.__testing._data(ul, 'connectWith')).not.toBeDefined() + expect(Sortable.__testing._data(ul, 'connectWith')).not.toBeDefined() }) test('should not have an aria-grabbed attribute', () => { @@ -130,40 +131,40 @@ describe('Testing api', () => { describe('Reload', () => { beforeAll(function () { - sortable(ul, { + new Sortable(ul, { 'items': 'li:not(.disabled)', 'connectWith': '.test', placeholderClass: 'test-placeholder' }) - sortable(ul, 'reload') + new Sortable(ul, 'reload') }) test('should keep the options of the sortable', () => { - let opts = sortable.__testing._data(ul, 'opts') + let opts = Sortable.__testing._data(ul, 'opts') expect(opts.items).toEqual('li:not(.disabled)') expect(opts.connectWith).toEqual('.test') expect(opts.placeholderClass).toEqual('test-placeholder') }) test('should keep items attribute of the sortable', () => { - let items = sortable.__testing._data(ul, 'items') + let items = Sortable.__testing._data(ul, 'items') expect(items).toEqual('li:not(.disabled)') }) test('should keep connectWith attribute of the sortable', () => { - let connectWith = sortable.__testing._data(ul, 'connectWith') + let connectWith = Sortable.__testing._data(ul, 'connectWith') expect(connectWith).toEqual('.test') }) }) describe('Disable', () => { beforeAll(function () { - sortable(ul, { + new Sortable(ul, { 'items': 'li:not(.disabled)', 'connectWith': '.test', placeholderClass: 'test-placeholder' }) - sortable(ul, 'disable') + new Sortable(ul, 'disable') }) test('should remove attributes from sortable', () => { @@ -186,13 +187,13 @@ describe('Testing api', () => { describe('Enable', () => { beforeAll(function () { - sortable(ul, { + new Sortable(ul, { 'items': 'li:not(.disabled)', 'connectWith': '.test', placeholderClass: 'test-placeholder' }) - sortable(ul, 'disable') - sortable(ul, 'enable') + new Sortable(ul, 'disable') + new Sortable(ul, 'enable') }) test('should readd attributes to sortable', () => { diff --git a/__tests__/events.test.ts b/__tests__/events.test.ts index 385bed56..f459eac0 100644 --- a/__tests__/events.test.ts +++ b/__tests__/events.test.ts @@ -1,6 +1,7 @@ /* global describe,test,expect,beforeEach,CustomEvent */ -import sortable from '../src/html5sortable' +import Sortable from '../src/html5sortable' /* eslint-env jest */ +/* eslint-disable no-new */ describe('Testing events', () => { let body = document.querySelector('body') @@ -81,11 +82,11 @@ describe('Testing events', () => { }) function addEventListener (ul) { - sortable(ul, null)[0].addEventListener('sortstart', function (e) { + new Sortable(ul, null)[0].addEventListener('sortstart', function (e) { startEventOriginItem = e.detail.item startEventOriginContainer = e.detail.origin.container }) - sortable(ul, null)[0].addEventListener('sortupdate', function (e) { + new Sortable(ul, null)[0].addEventListener('sortupdate', function (e) { sortupdateitem = e.detail.item sortupdateitemEndIndex = e.detail.endSortableIndex sortupdateitemStartIndex = e.detail.startSortableIndex @@ -97,14 +98,14 @@ describe('Testing events', () => { sortupdateitemNewStartList = e.detail.newStartList sortupdateitemOldStartList = e.detail.oldStartList }) - sortable(ul, null)[0].addEventListener('sortstop', function (e) { + new Sortable(ul, null)[0].addEventListener('sortstop', function (e) { sortstopitem = e.detail.item sortstopStartparent = e.detail.startParent }) } test('should correctly run dragstart event', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', connectWith: '.test', placeholderClass: 'test-placeholder', @@ -129,7 +130,7 @@ describe('Testing events', () => { test( 'should correctly copy element on run dragstart/dragover event', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', copy: true, connectWith: '.test', @@ -157,7 +158,7 @@ describe('Testing events', () => { ) test('dragstart/dragover event with maxitems', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', maxItems: 1, connectWith: '.test', @@ -178,7 +179,7 @@ describe('Testing events', () => { }) test('should not add class on hover event', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', hoverClass: false }) @@ -188,7 +189,7 @@ describe('Testing events', () => { expect(li.classList.contains('sortable-over')).toBe(false) }) test('should correctly add class on hover event', () => { - sortable(ul, { + new Sortable(ul, { 'items': 'li', hoverClass: 'sortable-item-over' }) @@ -203,7 +204,7 @@ describe('Testing events', () => { test( 'should correctly add and remove both classes on hover event', () => { - sortable(ul, { + new Sortable(ul, { 'items': 'li', hoverClass: 'sortable-item-over sortable-item-over-second' }) @@ -219,7 +220,7 @@ describe('Testing events', () => { ) test.skip('should correctly place moved item into correct index', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', placeholderClass: 'test-placeholder' }) @@ -265,13 +266,13 @@ describe('Testing events', () => { test.skip( 'should correctly place moved item into correct index using acceptFrom', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', acceptFrom: false, placeholderClass: 'test-placeholder' }) - sortable(ul2, { + new Sortable(ul2, { items: 'li', acceptFrom: '.sortable', placeholderClass: 'test-placeholder2' @@ -303,7 +304,7 @@ describe('Testing events', () => { ) test.skip('should correctly place non-moved item into correct index', () => { - sortable(ul, { + new Sortable(ul, { items: 'li', placeholderClass: 'test-placeholder' }) @@ -335,7 +336,7 @@ describe('Testing events', () => { test( 'should revert item into correct index when dropped outside', () => { - sortable(ul, { + new Sortable(ul, { 'items': 'li', placeholderClass: 'test-placeholder' }) @@ -364,7 +365,7 @@ describe('Testing events', () => { test('should find sortable child dragover event', () => { var item4 = ul.querySelector('.item4') - sortable(ul, { + new Sortable(ul, { items: 'li', placeholderClass: 'test-placeholder', draggingClass: 'test-dragging' diff --git a/__tests__/options.test.ts b/__tests__/options.test.ts index c0ab059f..90ec84eb 100644 --- a/__tests__/options.test.ts +++ b/__tests__/options.test.ts @@ -1,13 +1,13 @@ /* global describe,test,expect */ /* eslint-env jest */ -import sortable from '../src/html5sortable' +import Sortable from '../src/html5sortable' describe('Test options from sortable', () => { test('options: undefined', () => { let div = window.document.createElement('div') // init sortable & get first one - let sortableElement = sortable(div, undefined)[0] + let sortableElement = new Sortable(div, undefined)[0] // test a default value to check if they stay the same expect(sortableElement.h5s.data.opts).toEqual({ connectWith: false, @@ -29,8 +29,8 @@ describe('Test options from sortable', () => { test('options: method string', () => { let div = window.document.createElement('div') // init sortable & get first one - let sortableElement = sortable(div, null) - sortableElement = sortable(div, 'enable')[0] + let sortableElement = new Sortable(div, null) + sortableElement = new Sortable(div, 'enable')[0] // test a default value to check if they stay the same expect(sortableElement.h5s.data.opts.draggingClass).toEqual('sortable-dragging') }) @@ -39,7 +39,7 @@ describe('Test options from sortable', () => { // fake sortable let div = window.document.createElement('div') // init sortable & get first one - let sortableElement = sortable(div, { + let sortableElement = new Sortable(div, { maxItems: 5 })[0] // assert diff --git a/__tests__/serialize.test.ts b/__tests__/serialize.test.ts index c43c232f..be03c79c 100644 --- a/__tests__/serialize.test.ts +++ b/__tests__/serialize.test.ts @@ -2,7 +2,7 @@ /* eslint-env jest */ import serialize from '../src/serialize' -import sortable from '../src/html5sortable' +import Sortable from '../src/html5sortable' describe('Testing serialize', () => { test('serialize: sortableContainer is not an element', () => { @@ -18,7 +18,7 @@ describe('Testing serialize', () => { test('serialize: element that is not part of the DOM', () => { // setup - let isASortable = sortable(window.document.createElement('div'), {})[0] + let isASortable = new Sortable(window.document.createElement('div'), {})[0] // assert expect(serialize(isASortable)).toEqual(expect.objectContaining({ items: expect.any(Array), @@ -28,7 +28,7 @@ describe('Testing serialize', () => { test('serialize: empty sortableContainer', () => { // setup - let isASortable = sortable(window.document.createElement('div'), {})[0] + let isASortable = new Sortable(window.document.createElement('div'), {})[0] // assert expect(serialize(isASortable)).toEqual(expect.objectContaining({ items: expect.arrayContaining([]), @@ -41,7 +41,7 @@ describe('Testing serialize', () => { test('serialize: with elements', () => { // setup - let isASortable = sortable(window.document.createElement('div'), { + let isASortable = new Sortable(window.document.createElement('div'), { items: 'div' })[0] isASortable.innerHTML = '
Item1
Item2
' @@ -72,7 +72,7 @@ describe('Testing serialize', () => { test('serialize: with elements that are not items sortable', () => { // setup - let isASortable = sortable(window.document.createElement('div'), { + let isASortable = new Sortable(window.document.createElement('div'), { items: 'div' })[0] isASortable.innerHTML = 'Title
Item1
Item2
' @@ -103,21 +103,21 @@ describe('Testing serialize', () => { test('serialize: with invalid customItemSerializer', () => { // setup - let isASortable = sortable(window.document.createElement('div'), {})[0] + let isASortable = new Sortable(window.document.createElement('div'), {})[0] // assert expect(() => { serialize(isASortable, 'fake') }).toThrow('You need to provide a valid serializer for items and the container.') }) test('serialize: with invalid customContainerSerializer', () => { // setup - let isASortable = sortable(window.document.createElement('div'), {})[0] + let isASortable = new Sortable(window.document.createElement('div'), {})[0] // assert expect(() => { serialize(isASortable, () => {}, 'fake') }).toThrow('You need to provide a valid serializer for items and the container.') }) test('serialize: with custom serializer', () => { // setup - let isASortable = sortable(window.document.createElement('div'), { + let isASortable = new Sortable(window.document.createElement('div'), { items: 'div' })[0] isASortable.innerHTML = '
Item1
Item2
' diff --git a/__tests__/sortableMethodsTests/_listsConnected.test.ts b/__tests__/sortableMethodsTests/_listsConnected.test.ts index 7e69136f..0b955fb0 100644 --- a/__tests__/sortableMethodsTests/_listsConnected.test.ts +++ b/__tests__/sortableMethodsTests/_listsConnected.test.ts @@ -1,6 +1,6 @@ /* global describe,test,expect */ import { mockInnerHTML } from '../helpers' -import sortable from '../../src/html5sortable' +import Sortable from '../../src/html5sortable' /* eslint-env jest */ describe('_removeSortableEvents', () => { @@ -8,9 +8,9 @@ describe('_removeSortableEvents', () => { beforeEach(() => { document.body.innerHTML = mockInnerHTML ul = document.body.querySelector('.sortable') - sortable(ul, 'destroy') + new Sortable(ul, 'destroy') // init sortable - sortable(ul, null) + new Sortable(ul, null) document.body.innerHTML = `
    @@ -24,87 +24,87 @@ describe('_removeSortableEvents', () => { notConnectedUl = document.body.querySelector('.sortable3') // create additional sortables - sortable(connectedUl, { + new Sortable(connectedUl, { connectWith: '.sortable' }) - sortable(notConnectedUl, null) + new Sortable(notConnectedUl, null) }) test('each sortable ul should connect with itself by default', () => { // test if sortable is connected to itself (should be true) - expect(sortable.__testing._listsConnected(ul, ul)).toEqual(true) + expect(Sortable.__testing._listsConnected(ul, ul)).toEqual(true) }) test('connectWith: both uls must connect to a class at instantiation to be connected', () => { // because ul was never instantiated with connectWith: '.sortable' they are not connected, so all should be false - expect(sortable.__testing._listsConnected(ul, connectedUl)).toEqual(false) - expect(sortable.__testing._listsConnected(connectedUl, ul)).toEqual(false) - expect(sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(false) - expect(sortable.__testing._listsConnected(ul, notConnectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(ul, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, ul)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(ul, notConnectedUl)).toEqual(false) }) test('connectWith: when both lists are connected to a class when instantiated they should be connected', () => { - sortable(ul, 'destroy') - sortable(ul, { + new Sortable(ul, 'destroy') + new Sortable(ul, { connectWith: '.sortable' }) // as both were instantiated with connectWith these should be true - expect(sortable.__testing._listsConnected(connectedUl, ul)).toEqual(true) - expect(sortable.__testing._listsConnected(ul, connectedUl)).toEqual(true) + expect(Sortable.__testing._listsConnected(connectedUl, ul)).toEqual(true) + expect(Sortable.__testing._listsConnected(ul, connectedUl)).toEqual(true) // notConnectedUl was never connected - expect(sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(false) }) test(('acceptFrom: when set to another sortable it should be able to accept only from that list if set to false'), () => { - sortable(connectedUl, 'destroy') - sortable(notConnectedUl, 'destroy') - sortable(connectedUl, { + new Sortable(connectedUl, 'destroy') + new Sortable(notConnectedUl, 'destroy') + new Sortable(connectedUl, { acceptFrom: '.sortable3' }) - sortable(notConnectedUl, { + new Sortable(notConnectedUl, { acceptFrom: false }) // test .sortable2 only accepts from .sortable3 (should be true) - expect(sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) - expect(sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) + expect(Sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) // test .sortable3 does not accept from anyone (should be false) - expect(sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) - expect(sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(false) }) test(('acceptFrom: when unconnected is set to empty string it should also not accept'), () => { - sortable(connectedUl, 'destroy') - sortable(connectedUl, { + new Sortable(connectedUl, 'destroy') + new Sortable(connectedUl, { acceptFrom: '.sortable3' }) - sortable(notConnectedUl, 'destroy') - sortable(notConnectedUl, { + new Sortable(notConnectedUl, 'destroy') + new Sortable(notConnectedUl, { acceptFrom: '' }) // test .sortable2 only accepts from .sortable3 (should be true) - expect(sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) + expect(Sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) // test .sortable2 only accepts from .sortable3 (should be false) - expect(sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) // test .sortable3 does not accept from anyone (should be false) - expect(sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) - expect(sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(false) }) test(('acceptFrom: when set to null it should be able to accept from itself'), () => { - sortable(connectedUl, 'destroy') - sortable(connectedUl, { + new Sortable(connectedUl, 'destroy') + new Sortable(connectedUl, { acceptFrom: '.sortable3' }) - sortable(notConnectedUl, 'destroy') - sortable(notConnectedUl, { + new Sortable(notConnectedUl, 'destroy') + new Sortable(notConnectedUl, { acceptFrom: null }) // test .sortable2 only accepts from .sortable3 (should be true) - expect(sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) + expect(Sortable.__testing._listsConnected(connectedUl, notConnectedUl)).toEqual(true) // test .sortable2 only accepts from .sortable3 (should be false) - expect(sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(connectedUl, connectedUl)).toEqual(false) // test .sortable3 does not accept from anyone (should be false) - expect(sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) - expect(sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(true) + expect(Sortable.__testing._listsConnected(notConnectedUl, connectedUl)).toEqual(false) + expect(Sortable.__testing._listsConnected(notConnectedUl, notConnectedUl)).toEqual(true) }) }) diff --git a/__tests__/sortableMethodsTests/_removeItemData.test.ts b/__tests__/sortableMethodsTests/_removeItemData.test.ts index 28d668e5..4082c219 100644 --- a/__tests__/sortableMethodsTests/_removeItemData.test.ts +++ b/__tests__/sortableMethodsTests/_removeItemData.test.ts @@ -1,6 +1,6 @@ /* global describe,test,expect */ import { mockInnerHTML } from '../helpers' -import sortable from '../../src/html5sortable' +import Sortable from '../../src/html5sortable' /* eslint-env jest */ describe('_removeItemData', () => { @@ -8,9 +8,9 @@ describe('_removeItemData', () => { beforeEach(() => { document.body.innerHTML = mockInnerHTML ul = document.body.querySelector('.sortable') - sortable(ul, 'destroy') + new Sortable(ul, 'destroy') // init sortable - sortable(ul, null) + new Sortable(ul, null) // get all li elements allLiElements = ul.querySelectorAll('li') // get first li element @@ -18,12 +18,12 @@ describe('_removeItemData', () => { }) test('should remove the role, draggable, and aria-grabbed attributes', () => { // destroy, so it does not use old values - sortable(ul, 'destroy') - sortable(ul, { + new Sortable(ul, 'destroy') + new Sortable(ul, { items: 'li', connectWith: '.test' }) - sortable.__testing._removeItemData(li) + Sortable.__testing._removeItemData(li) expect(li.getAttribute('role')).toBeNull() expect(li.getAttribute('draggable')).toBeNull() expect(li.getAttribute('aria-grabbed')).toBeNull() diff --git a/__tests__/sortableMethodsTests/_removeItemEvents.test.ts b/__tests__/sortableMethodsTests/_removeItemEvents.test.ts index b5fbcf3f..d62893c7 100644 --- a/__tests__/sortableMethodsTests/_removeItemEvents.test.ts +++ b/__tests__/sortableMethodsTests/_removeItemEvents.test.ts @@ -1,6 +1,6 @@ /* global describe,test,expect */ import { mockInnerHTML } from '../helpers' -import sortable from '../../src/html5sortable' +import Sortable from '../../src/html5sortable' import store from '../../src/store' /* eslint-env jest */ @@ -8,13 +8,13 @@ describe('_removeItemEvents', () => { document.body.innerHTML = mockInnerHTML let ul = document.body.querySelector('.sortable') let li = ul.querySelector('.li-first') - sortable(ul, null) + new Sortable(ul, null) test('_removeItemEvents', () => { expect(typeof store(li).getData('eventdragover')).toBe('function') expect(typeof store(li).getData('eventdragenter')).toBe('function') // remove item events - sortable.__testing._removeItemEvents(li) + Sortable.__testing._removeItemEvents(li) // assert expect(typeof store(li).getData('eventdragover')).toBe('undefined') expect(typeof store(li).getData('eventdragenter')).toBe('undefined') diff --git a/src/eventListener.ts b/src/eventListener.ts index 61014ee5..999d09e7 100644 --- a/src/eventListener.ts +++ b/src/eventListener.ts @@ -4,7 +4,7 @@ import store from './store' * @param {Function} callback * @param {string} event */ -function addEventListener (element: Array|HTMLElement, eventName:string, callback: () => void) { +function addEventListener (element: Array|HTMLElement, eventName:string, callback: (() => void)|void) { if (element instanceof Array) { for (var i = 0; i < element.length; ++i) { addEventListener(element[i], eventName, callback) diff --git a/src/html5sortable.ts b/src/html5sortable.ts index 1de4e607..2ebbb326 100644 --- a/src/html5sortable.ts +++ b/src/html5sortable.ts @@ -229,45 +229,41 @@ const _reloadSortable = function (sortableElement) { * @param {Array|NodeList} sortableElements * @param {object|string} options|method */ -export default class sortable { - sortableElements: any - options: configuration - defaultOptions: configuration = { - connectWith: false, - acceptFrom: null, - copy: false, - placeholder: null, - disableIEFix: false, - placeholderClass: 'sortable-placeholder', - draggingClass: 'sortable-dragging', - hoverClass: false, - debounce: 0, - maxItems: 0, - itemSerializer: undefined, - containerSerializer: undefined, - customDragImage: null - } - constructor(sortableElements, options: configuration) { +export default class Sortable { + constructor (sortableElements, options) { this.sortableElements = sortableElements this.options = options + return this.main() } - serialize(method) { - if (/serialize/.test(method)) { - return this.sortableElements.map((sortableContainer) => { - let configuration = _data(sortableContainer, 'opts') - return _serialize(sortableContainer, configuration.itemSerializer, configuration.containerSerializer) - }) + get defaultOptions () { + return { + connectWith: false, + acceptFrom: null, + copy: false, + placeholder: null, + disableIEFix: false, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null } } - checkForSpecificMethod(method, sortableElement) { - if (/enable|disable|destroy/.test(method)) { - return sortable[method](sortableElement) + serialize (method) { + if (/serialize/.test(method)) { + return this.sortableElements.map((sortableContainer) => { + let opts = _data(sortableContainer, 'opts') + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer) + }) } } - createCustomPlaceholder(sortableElement) { + createCustomPlaceholder (sortableElement) { if (this.options.placeholder !== null && this.options.placeholder !== undefined) { let tempContainer = document.createElement(sortableElement.tagName) tempContainer.innerHTML = this.options.placeholder @@ -275,7 +271,7 @@ export default class sortable { } } - alterData(sortableElement) { + addData (sortableElement) { _data(sortableElement, 'items', this.options.items) if (this.options.acceptFrom) { _data(sortableElement, 'acceptFrom', this.options.acceptFrom) @@ -284,351 +280,348 @@ export default class sortable { } } - main() { - // get method string to see if a method is called - const method = String(this.options) - // merge user this.options with defaults - this.options = Object.assign(this.defaultOptions, - // if this.options is an object, merge it, otherwise use empty object - (typeof this.options === 'object') ? this.options : {}) - - // check if the user provided a selector instead of an element - if (typeof this.sortableElements === 'string') { - this.sortableElements = document.querySelectorAll(this.sortableElements) - } - console.log(this.sortableElements instanceof HTMLElement, typeof this.sortableElements) - // if the user provided an element, return it in an array to keep the return value consistant - if (this.sortableElements instanceof HTMLElement) { - this.sortableElements = [this.sortableElements] - } - this.sortableElements = Array.prototype.slice.call(this.sortableElements) - - this.serialize(method) - this.sortableElements.forEach((sortableElement) => { - this.checkForSpecificMethod(method, sortableElement) - // init data store for sortable - store(sortableElement).config = this.options - // get this.options & set this.options on sortable - this.options = _data(sortableElement, 'opts') || this.options - _data(sortableElement, 'opts', this.options) - // property to define as sortable - sortableElement.isSortable = true - // reset sortable - _reloadSortable(sortableElement) - // initialize - const items = _filter(sortableElement.children, this.options.items) - const itemStartIndex - const startList - // create element if user defined a placeholder element as a string - let customPlaceholder = this.createCustomPlaceholder(sortableElement) - // add placeholder - store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, this.options.placeholderClass) - this.alterData(sortableElement) - } - } - - this.sortableElements.forEach(function (sortableElement) { - - _enableSortable(sortableElement) - _attr(listItems, 'role', 'option') - _attr(listItems, 'aria-grabbed', 'false') - - // Mouse over class + mouseOverClass (listItems) { if (typeof this.options.hoverClass === 'string') { let hoverClasses = this.options.hoverClass.split(' ') // add class on hover - _on(listItems, 'mouseenter', function (e) { + _on(listItems, 'mouseenter', (e) => { e.target.classList.add(...hoverClasses) }) // remove class on leave - _on(listItems, 'mouseleave', function (e) { + _on(listItems, 'mouseleave', (e) => { e.target.classList.remove(...hoverClasses) }) } + } - /* - Handle drag events on draggable items - Handle is set at the sortableElement level as it will bubble up - from the item - */ - _on(sortableElement, 'dragstart', function (e) { - // ignore dragstart events - if (_isSortable(e.target)) { - return - } - e.stopImmediatePropagation() + dragstartEvent (e, sortableElement) { + // ignore dragstart events + if (_isSortable(e.target)) { + return + } + e.stopImmediatePropagation() - if ((this.options.handle && !e.target.matches(this.options.handle)) || e.target.getAttribute('draggable') === 'false') { - return + if ((this.options.handle && !e.target.matches(this.options.handle)) || e.target.getAttribute('draggable') === 'false') { + return + } + const sortableContainer = findSortable(e.target) + const dragItem = findDragElement(sortableContainer, e.target) + + // grab values + originItemsBeforeUpdate = _filter(sortableContainer.children, this.options.items) + originIndex = originItemsBeforeUpdate.indexOf(dragItem) + originElementIndex = _index(dragItem, sortableContainer.children) + originContainer = sortableContainer + + // add transparent clone or other ghost to cursor + setDragImage(e, dragItem, this.options.customDragImage) + // cache selsection & add attr for dragging + draggingHeight = _getElementHeight(dragItem) + dragItem.classList.add(this.options.draggingClass) + dragging = _getDragging(dragItem, sortableElement) + _attr(dragging, 'aria-grabbed', 'true') + + // dispatch sortstart event on each element in group + sortableContainer.dispatchEvent(new CustomEvent('sortstart', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging } + })) + } - const sortableContainer = findSortable(e.target) - const dragItem = findDragElement(sortableContainer, e.target) - - // grab values - originItemsBeforeUpdate = _filter(sortableContainer.children, options.items) - originIndex = originItemsBeforeUpdate.indexOf(dragItem) - originElementIndex = _index(dragItem, sortableContainer.children) - originContainer = sortableContainer - - // add transparent clone or other ghost to cursor - setDragImage(e, dragitem, this.options.customDragImage) - // cache selsection & add attr for dragging - draggingHeight = _getElementHeight(dragitem) - dragitem.classList.add(this.options.draggingClass) - dragging = _getDragging(dragitem, sortableElement) - _attr(dragging, 'aria-grabbed', 'true') - - // dispatch sortstart event on each element in group - sortableContainer.dispatchEvent(new CustomEvent('sortstart', { - detail: { - origin: { - elementIndex: originElementIndex, - index: originIndex, - container: originContainer - }, - item: dragging - } - })) - }) + dragenterEvent (e, sortableElement) { + if (_isSortable(e.target)) { + return + } + const sortableContainer = findSortable(e.target) + destinationItemsBeforeUpdate = _filter(sortableContainer.children, _data(sortableContainer, 'items')) + .filter(item => item !== store(sortableElement).placeholder) + } - /* - We are capturing targetSortable before modifications with 'dragenter' event - */ - _on(sortableElement, 'dragenter', (e) => { - if (_isSortable(e.target)) { - return - } - const sortableContainer = findSortable(e.target) - destinationItemsBeforeUpdate = _filter(sortableContainer.children, _data(sortableContainer, 'items')) - .filter(item => item !== store(sortableElement).placeholder) - }) - /* - * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend - * Fires each time dragEvent end, or ESC pressed - * We are using it to clean up any draggable elements and placeholders - */ - _on(sortableElement, 'dragend', function (e) { - if (!dragging) { - return - } + dragendEvent (e, sortableElement) { + if (!dragging) { + return + } - dragging.classList.remove(this.options.draggingClass) - _attr(dragging, 'aria-grabbed', 'false') + dragging.classList.remove(this.options.draggingClass) + _attr(dragging, 'aria-grabbed', 'false') - if (dragging.getAttribute('aria-copied') === 'true' && _data(dragging, 'dropped') !== 'true') { - dragging.remove() - } + if (dragging.getAttribute('aria-copied') === 'true' && _data(dragging, 'dropped') !== 'true') { + dragging.remove() + } - dragging.style.display = dragging.oldDisplay - delete dragging.oldDisplay + dragging.style.display = dragging.oldDisplay + delete dragging.oldDisplay - const visiblePlaceholder = Array.from(stores.values()).map(data => data.placeholder) - .filter(placeholder => placeholder instanceof HTMLElement) - .filter(isInDom)[0] + const visiblePlaceholder = Array.from(stores.values()).map(data => data.placeholder) + .filter(placeholder => placeholder instanceof HTMLElement) + .filter(isInDom)[0] - if (visiblePlaceholder) { - visiblePlaceholder.remove() + if (visiblePlaceholder) { + visiblePlaceholder.remove() + } + // dispatch sortstart event on each element in group + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging } + })) + dragging = null + draggingHeight = null + } - // dispatch sortstart event on each element in group - sortableElement.dispatchEvent(new CustomEvent('sortstop', { - detail: { - origin: { - elementIndex: originElementIndex, - index: originIndex, - container: originContainer - }, - item: dragging - } - })) + dropEvent (e, sortableElement) { + if (!_listsConnected(sortableElement, dragging.parentElement)) { + return + } + e.preventDefault() + e.stopPropagation() - dragging = null - draggingHeight = null + _data(dragging, 'dropped', 'true') + // get the one placeholder that is currently visible + const visiblePlaceholder = Array.from(stores.values()).map((data) => { + return data.placeholder }) + // filter only HTMLElements + .filter(placeholder => placeholder instanceof HTMLElement) + // filter only elements in DOM + .filter(isInDom)[0] + // attach element after placeholder + _after(visiblePlaceholder, dragging) + // remove placeholder from dom + visiblePlaceholder.remove() /* - * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop - * Fires when valid drop target area is hit - */ - _on(sortableElement, 'drop', function (e) { - if (!_listsConnected(sortableElement, dragging.parentElement)) { - return + * Fires Custom Event - 'sortstop' + */ + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging } - e.preventDefault() - e.stopPropagation() - - _data(dragging, 'dropped', 'true') - // get the one placeholder that is currently visible - const visiblePlaceholder = Array.from(stores.values()).map((data) => { - return data.placeholder - }) - // filter only HTMLElements - .filter(placeholder => placeholder instanceof HTMLElement) - // filter only elements in DOM - .filter(isInDom)[0] - // attach element after placeholder - _after(visiblePlaceholder, dragging) - // remove placeholder from dom - visiblePlaceholder.remove() - - /* - * Fires Custom Event - 'sortstop' - */ - sortableElement.dispatchEvent(new CustomEvent('sortstop', { + })) + + const placeholder = store(sortableElement).placeholder + const originItems = _filter(originContainer.children, this.options.items) + .filter(item => item !== placeholder) + const destinationContainer = _isSortable(this) ? this : this.parentElement + const destinationItems = _filter(destinationContainer.children, _data(destinationContainer, 'items')) + .filter(item => item !== placeholder) + const destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) + .filter(item => item !== placeholder)) + const destinationIndex = _index(dragging, destinationItems) + /* + * When a list item changed container lists or index within a list + * Fires Custom Event - 'sortupdate' + */ + if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { + sortableElement.dispatchEvent(new CustomEvent('sortupdate', { detail: { origin: { elementIndex: originElementIndex, index: originIndex, - container: originContainer + container: originContainer, + itemsBeforeUpdate: originItemsBeforeUpdate, + items: originItems + }, + destination: { + index: destinationIndex, + elementIndex: destinationElementIndex, + container: destinationContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate, + items: destinationItems }, item: dragging } })) + } + } + + main () { + // get method string to see if a method is called + const method = String(this.options) + // merge user this.options with defaults + this.options = Object.assign(this.defaultOptions, + // if this.options is an object, merge it, otherwise use empty object + (typeof this.options === 'object') ? this.options : {}) + // check if the user provided a selector instead of an element + if (typeof this.sortableElements === 'string') { + this.sortableElements = document.querySelectorAll(this.sortableElements) + } + // if the user provided an element, return it in an array to keep the return value consistant + if (this.sortableElements instanceof HTMLElement) { + this.sortableElements = [this.sortableElements] + } - const placeholder = store(sortableElement).placeholder - const originItems = _filter(originContainer.children, options.items) - .filter(item => item !== placeholder) - const destinationContainer = _isSortable(this) ? this : this.parentElement - const destinationItems = _filter(destinationContainer.children, _data(destinationContainer, 'items')) - .filter(item => item !== placeholder) - const destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) - .filter(item => item !== placeholder)) - const destinationIndex = _index(dragging, destinationItems) + this.sortableElements = Array.prototype.slice.call(this.sortableElements) + this.serialize(method) + + this.sortableElements.forEach((sortableElement) => { + if (/enable|disable|destroy/.test(method)) { + return Sortable[method](sortableElement) + } + // init data store for sortable + store(sortableElement).config = this.options + // get options & set options on sortable + this.options = _data(sortableElement, 'opts') || this.options + _data(sortableElement, 'opts', this.options) + // property to define as sortable + sortableElement.isSortable = true + // reset sortable + _reloadSortable(sortableElement) + // initialize + const listItems = _filter(sortableElement.children, this.options.items) + // create element if user defined a placeholder element as a string + let customPlaceholder = this.createCustomPlaceholder(sortableElement) + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, this.options.placeholderClass) + // add specified data to element (items and connectWith or acceptFrom) + this.addData(sortableElement) + _enableSortable(sortableElement) + // add attributes + _attr(listItems, 'role', 'option') + _attr(listItems, 'aria-grabbed', 'false') + // add mouse over classes + this.mouseOverClass(listItems) /* - * When a list item changed container lists or index within a list - * Fires Custom Event - 'sortupdate' + Handle drag events on draggable items + Handle is set at the sortableElement level as it will bubble up + from the item */ - if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { - sortableElement.dispatchEvent(new CustomEvent('sortupdate', { - detail: { - origin: { - elementIndex: originElementIndex, - index: originIndex, - container: originContainer, - itemsBeforeUpdate: originItemsBeforeUpdate, - items: originItems - }, - destination: { - index: destinationIndex, - elementIndex: destinationElementIndex, - container: destinationContainer, - itemsBeforeUpdate: destinationItemsBeforeUpdate, - items: destinationItems - }, - item: dragging + _on(sortableElement, 'dragstart', (e) => { this.dragstartEvent(e, sortableElement) }) + /* + We are capturing targetSortable before modifications with 'dragenter' event + */ + _on(sortableElement, 'dragenter', (e) => { this.dragenterEvent(e, sortableElement) }) + /* + * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend + * Fires each time dragEvent end, or ESC pressed + * We are using it to clean up any draggable elements and placeholders + */ + _on(sortableElement, 'dragend', (e) => { this.dragendEvent(e, sortableElement) }) + /* + * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop + * Fires when valid drop target area is hit + */ + _on(sortableElement, 'drop', (e) => { this.dropEvent(e, sortableElement) }) + const debouncedDragOverEnter = _debounce((sortableElement, element, pageY) => { + if (!dragging) { + return + } + // set placeholder height if forcePlaceholderSize option is set + if (this.options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px' + } + // if element the draggedItem is dragged onto is within the array of all elements in list + // (not only items, but also disabled, etc.) + if (Array.from(sortableElement.children).indexOf(element) > -1) { + const thisHeight = _getElementHeight(element) + const placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children) + const thisIndex = _index(element, element.parentElement.children) + // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering + if (thisHeight > draggingHeight) { + // Dead zone? + const deadZone = thisHeight - draggingHeight + const offsetTop = _offset(element).top + if (placeholderIndex < thisIndex && pageY < offsetTop) { + return + } + if (placeholderIndex > thisIndex && + pageY > offsetTop + thisHeight - deadZone) { + return + } } - })) - } - }) - const debouncedDragOverEnter = _debounce((sortableElement, element, pageY) => { - if (!dragging) { - return - } + if (dragging.oldDisplay === undefined) { + dragging.oldDisplay = dragging.style.display + } - // set placeholder height if forcePlaceholderSize option is set - if (this.options.forcePlaceholderSize) { - store(sortableElement).placeholder.style.height = draggingHeight + 'px' - } - // if element the draggedItem is dragged onto is within the array of all elements in list - // (not only items, but also disabled, etc.) - if (Array.from(sortableElement.children).indexOf(element) > -1) { - const thisHeight = _getElementHeight(element) - const placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children) - const thisIndex = _index(element, element.parentElement.children) - // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering - if (thisHeight > draggingHeight) { - // Dead zone? - const deadZone = thisHeight - draggingHeight - const offsetTop = _offset(element).top - if (placeholderIndex < thisIndex && pageY < offsetTop) { - return + if (dragging.style.display !== 'none') { + dragging.style.display = 'none' } - if (placeholderIndex > thisIndex && - pageY > offsetTop + thisHeight - deadZone) { - return + if (placeholderIndex < thisIndex) { + _after(element, store(sortableElement).placeholder) + } else { + _before(element, store(sortableElement).placeholder) } - } - - if (dragging.oldDisplay === undefined) { - dragging.oldDisplay = dragging.style.display - } - - if (dragging.style.display !== 'none') { - dragging.style.display = 'none' - } - if (placeholderIndex < thisIndex) { - _after(element, store(sortableElement).placeholder) + // get placeholders from all stores & remove all but current one + Array.from(stores.values()) + // remove empty values + .filter(data => data.placeholder !== null) + // foreach placeholder in array if outside of current sorableContainer -> remove from DOM + .forEach((data) => { + if (data.placeholder !== store(sortableElement).placeholder) { + data.placeholder.remove() + } + }) } else { - _before(element, store(sortableElement).placeholder) + // get all placeholders from store + let placeholders = Array.from(stores.values()) + .filter((data) => data.placeholder !== null) + .map((data) => { + return data.placeholder + }) + // check if element is not in placeholders + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, this.options.items).length) { + placeholders.forEach((element) => element.remove()) + element.appendChild(store(sortableElement).placeholder) + } } - // get placeholders from all stores & remove all but current one - Array.from(stores.values()) - // remove empty values - .filter(data => data.placeholder !== null) - // foreach placeholder in array if outside of current sorableContainer -> remove from DOM - .forEach((data) => { - if (data.placeholder !== store(sortableElement).placeholder) { - data.placeholder.remove() - } - }) - } else { - // get all placeholders from store - let placeholders = Array.from(stores.values()) - .filter((data) => data.placeholder !== null) - .map((data) => { - return data.placeholder - }) - // check if element is not in placeholders - if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, this.options.items).length) { - placeholders.forEach((element) => element.remove()) - element.appendChild(store(sortableElement).placeholder) + }, this.options.debounce) + // Handle dragover and dragenter events on draggable items + const onDragOverEnter = (e) => { + let element = e.target + const sortableElement = _isSortable(element) ? element : findSortable(element) + element = findDragElement(sortableElement, element) + if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || _data(sortableElement, '_disabled') === 'true') { + return } + var options = _data(sortableElement, 'opts') + if (parseInt(this.options.maxItems) && _filter(sortableElement.children, _data(sortableElement, 'items')).length >= parseInt(options.maxItems)) { + return + } + e.preventDefault() + e.stopPropagation() + e.dataTransfer.dropEffect = _isCopyActive(sortableElement) ? 'copy' : 'move' + debouncedDragOverEnter(sortableElement, element, e.pageY) } - }, this.options.debounce) - // Handle dragover and dragenter events on draggable items - const onDragOverEnter = function (e) { - let element = e.target - const sortableElement = _isSortable(element) ? element : findSortable(element) - element = findDragElement(sortableElement, element) - if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || _data(sortableElement, '_disabled') === 'true') { - return - } - var this.options = _data(sortableElement, 'opts') - if (parseInt(this.options.maxItems) && _filter(sortableElement.children, _data(sortableElement, 'items')).length >= parseInt(this.options.maxItems)) { - return - } - e.preventDefault() - e.stopPropagation() - e.dataTransfer.dropEffect = _isCopyActive(sortableElement) ? 'copy' : 'move' - debouncedDragOverEnter(sortableElement, element, e.pageY) - } - _on(listItems.concat(sortableElement), 'dragover', onDragOverEnter) - _on(listItems.concat(sortableElement), 'dragenter', onDragOverEnter) - }) - return this.sortableElements + _on(listItems.concat(sortableElement), 'dragover', onDragOverEnter) + _on(listItems.concat(sortableElement), 'dragenter', onDragOverEnter) + }) + return this.sortableElements + } } - - - -sortable.destroy = function (sortableElement) { +Sortable.destroy = function (sortableElement) { _destroySortable(sortableElement) } -sortable.enable = function (sortableElement) { +Sortable.enable = function (sortableElement) { _enableSortable(sortableElement) } -sortable.disable = function (sortableElement) { +Sortable.disable = function (sortableElement) { _disableSortable(sortableElement) } /* START.TESTS_ONLY */ -sortable.__testing = { +Sortable.__testing = { // add internal methods here for testing purposes _data: _data, _removeItemEvents: _removeItemEvents, diff --git a/src/types/main.d.ts b/src/types/main.d.ts index da4d6b82..af57a453 100644 --- a/src/types/main.d.ts +++ b/src/types/main.d.ts @@ -18,7 +18,8 @@ interface configuration { itemSerializer: void, containerSerializer: void, items?: string, - customDragImage: null + customDragImage: null, + forcePlaceholderSize?: any } interface data { From 31106dcaeeb30149cd7926562d1e31618dd08f6c Mon Sep 17 00:00:00 2001 From: jmuzsik Date: Sat, 24 Mar 2018 22:01:22 -0400 Subject: [PATCH 3/3] WIP - turn sortable into a class --- src/types/temp.ts | 52 ----------------------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/types/temp.ts diff --git a/src/types/temp.ts b/src/types/temp.ts deleted file mode 100644 index 2945c1bb..00000000 --- a/src/types/temp.ts +++ /dev/null @@ -1,52 +0,0 @@ -export default class sortable { - sortableElements: any - options: object|string|undefined - constructor(sortableElements, options: object|string|undefined) { - this.sortableElements = sortableElements - this.options = options - this.setOptions(); - this.reformatSortableElements(); - this.serialize(); - } - // get method string to see if a method is called - method: string = String(this.options) - // merge user this.options with defaults - setOptions() { - this.options = Object.assign({ - connectWith: false, - acceptFrom: null, - copy: false, - disableIEFix: false, - placeholder: null, - placeholderClass: 'sortable-placeholder', - draggingClass: 'sortable-dragging', - hoverClass: false, - debounce: 0, - maxItems: 0, - itemSerializer: undefined, - containerSerializer: undefined, - customDragImage: null - // if this.options is an object, merge it, otherwise use empty object - }, (typeof this.options === 'object') ? this.options : {}) - } - reformatSortableElements() { - // check if the user provided a selector instead of an element - if (typeof this.sortableElements === 'string') { - this.sortableElements = document.querySelectorAll(this.sortableElements) - } - // if the user provided an element, return it in an array to keep the return value consistant - if (this.sortableElements instanceof Element) { - this.sortableElements = [this.sortableElements] - } - - this.sortableElements = Array.prototype.slice.call(this.sortableElements) - } - - serialize() { - if (/serialize/.test(method)) { - return this.sortableElements.map((sortableContainer) => { - let opts = _data(sortableContainer, 'opts') - return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer) - }) - } - } \ No newline at end of file