initial commit

This commit is contained in:
2017-12-14 10:17:13 +01:00
commit db72cb61d3
6942 changed files with 1773088 additions and 0 deletions
+759
View File
@@ -0,0 +1,759 @@
;(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.sortable = factory();
}
}(this, function() {
/*
* HTML5 Sortable library
* https://github.com/lukasoppermann/html5sortable
*
* Original code copyright 2012 Ali Farhadi.
* This version is mantained by Lukas Oppermann <lukas@vea.re>
* Previously also mantained by Alexandru Badiu <andu@ctrlz.ro>
* jQuery-independent implementation by Nazar Mokrynskyi <nazar@mokrynskyi.com>
*
* Released under the MIT license.
*/
'use strict'
/*
* variables global to the plugin
*/
var dragging
var draggingHeight
var placeholders = []
var sortables = []
/**
* Get or set data on element
* @param {Element} element
* @param {string} key
* @param {*} value
* @return {*}
*/
var _data = function (element, key, value) {
if (value === undefined) {
return element && element.h5s && element.h5s.data && element.h5s.data[key]
} else {
element.h5s = element.h5s || {}
element.h5s.data = element.h5s.data || {}
element.h5s.data[key] = value
}
}
/**
* Remove data from element
* @param {Element} element
*/
var _removeData = function (element) {
if (element.h5s) {
delete element.h5s.data
}
}
/**
* Tests if an element matches a given selector. Comparable to jQuery's $(el).is('.my-class')
* @param {el} DOM element
* @param {selector} selector test against the element
* @returns {boolean}
*/
/* istanbul ignore next */
var _matches = function (el, selector) {
return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector)
}
/**
* Filter only wanted nodes
* @param {Array|NodeList} nodes
* @param {Array/string} wanted
* @returns {Array}
*/
var _filter = function (nodes, wanted) {
if (!wanted) {
return Array.prototype.slice.call(nodes)
}
var result = []
for (var i = 0; i < nodes.length; ++i) {
if (typeof wanted === 'string' && _matches(nodes[i], wanted)) {
result.push(nodes[i])
}
if (wanted.indexOf(nodes[i]) !== -1) {
result.push(nodes[i])
}
}
return result
}
/**
* @param {Array|Element} element
* @param {Array|string} event
* @param {Function} callback
*/
var _on = function (element, event, callback) {
if (element instanceof Array) {
for (var i = 0; i < element.length; ++i) {
_on(element[i], event, callback)
}
return
}
element.addEventListener(event, callback)
element.h5s = element.h5s || {}
element.h5s.events = element.h5s.events || {}
element.h5s.events[event] = callback
}
/**
* @param {Array|Element} element
* @param {Array|string} event
*/
var _off = function (element, event) {
if (element instanceof Array) {
for (var i = 0; i < element.length; ++i) {
_off(element[i], event)
}
return
}
if (element.h5s && element.h5s.events && element.h5s.events[event]) {
element.removeEventListener(event, element.h5s.events[event])
delete element.h5s.events[event]
}
}
/**
* @param {Array|Element} element
* @param {string} attribute
* @param {*} value
*/
var _attr = function (element, attribute, value) {
if (element instanceof Array) {
for (var i = 0; i < element.length; ++i) {
_attr(element[i], attribute, value)
}
return
}
element.setAttribute(attribute, value)
}
/**
* @param {Array|Element} element
* @param {string} attribute
*/
var _removeAttr = function (element, attribute) {
if (element instanceof Array) {
for (var i = 0; i < element.length; ++i) {
_removeAttr(element[i], attribute)
}
return
}
element.removeAttribute(attribute)
}
/**
* @param {Element} element
* @returns {{left: *, top: *}}
*/
var _offset = function (element) {
var rect = element.getClientRects()[0]
return {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY
}
}
/*
* remove event handlers from items
* @param {Array|NodeList} items
*/
var _removeItemEvents = function (items) {
_off(items, 'dragstart')
_off(items, 'dragend')
_off(items, 'selectstart')
_off(items, 'dragover')
_off(items, 'dragenter')
_off(items, 'drop')
}
/*
* Remove event handlers from sortable
* @param {Element} sortable a single sortable
*/
var _removeSortableEvents = function (sortable) {
_off(sortable, 'dragover')
_off(sortable, 'dragenter')
_off(sortable, 'drop')
}
/*
* Attach ghost to dataTransfer object
* @param {Event} original event
* @param {object} ghost-object with item, x and y coordinates
*/
var _attachGhost = function (event, ghost) {
// this needs to be set for HTML5 drag & drop to work
event.dataTransfer.effectAllowed = 'move'
// Firefox requires some arbitrary content in the data in order for
// the drag & drop functionality to work
event.dataTransfer.setData('text', 'arbitrary-content')
// check if setDragImage method is available
if (event.dataTransfer.setDragImage) {
event.dataTransfer.setDragImage(ghost.draggedItem, ghost.x, ghost.y)
}
}
/**
* _addGhostPos clones the dragged item and adds it as a Ghost item
* @param {Event} event - the event fired when dragstart is triggered
* @param {object} ghost - .draggedItem = Element
*/
var _addGhostPos = function (event, ghost) {
if (!ghost.x) {
ghost.x = parseInt(event.pageX - _offset(ghost.draggedItem).left)
}
if (!ghost.y) {
ghost.y = parseInt(event.pageY - _offset(ghost.draggedItem).top)
}
return ghost
}
/**
* _makeGhost decides which way to make a ghost and passes it to attachGhost
* @param {Element} draggedItem - the item that the user drags
*/
var _makeGhost = function (draggedItem) {
return {
draggedItem: draggedItem
}
}
/**
* _getGhost constructs ghost and attaches it to dataTransfer
* @param {Event} event - the original drag event object
* @param {Element} draggedItem - the item that the user drags
*/
// TODO: could draggedItem be replaced by event.target in all instances
var _getGhost = function (event, draggedItem) {
// add ghost item & draggedItem to ghost object
var ghost = _makeGhost(draggedItem)
// attach ghost position
ghost = _addGhostPos(event, ghost)
// attach ghost to dataTransfer
_attachGhost(event, ghost)
}
/*
* Remove data from sortable
* @param {Element} sortable a single sortable
*/
var _removeSortableData = function (sortable) {
_removeData(sortable)
_removeAttr(sortable, 'aria-dropeffect')
}
/*
* Remove data from items
* @param {Array|Element} items
*/
var _removeItemData = function (items) {
_removeAttr(items, 'aria-grabbed')
_removeAttr(items, 'draggable')
_removeAttr(items, 'role')
}
/*
* Check if two lists are connected
* @param {Element} curList
* @param {Element} destList
*/
var _listsConnected = function (curList, destList) {
if (curList === destList) {
return true
}
if (_data(curList, 'connectWith') !== undefined) {
return _data(curList, 'connectWith') === _data(destList, 'connectWith')
}
return false
}
/*
* get handle or return item
* @param {Array} items
* @param {selector} handle
*/
var _getHandles = function (items, handle) {
var result = []
var handles
if (!handle) {
return items
}
for (var i = 0; i < items.length; ++i) {
handles = items[i].querySelectorAll(handle)
result = result.concat(Array.prototype.slice.call(handles))
}
return result
}
/*
* Destroy the sortable
* @param {Element} sortableElement a single sortable
*/
var _destroySortable = function (sortableElement) {
var opts = _data(sortableElement, 'opts') || {}
var items = _filter(_getChildren(sortableElement), opts.items)
var handles = _getHandles(items, opts.handle)
// remove event handlers & data from sortable
_removeSortableEvents(sortableElement)
_removeSortableData(sortableElement)
// remove event handlers & data from items
_off(handles, 'mousedown')
_removeItemEvents(items)
_removeItemData(items)
}
/*
* Enable the sortable
* @param {Element} sortableElement a single sortable
*/
var _enableSortable = function (sortableElement) {
var opts = _data(sortableElement, 'opts')
var items = _filter(_getChildren(sortableElement), opts.items)
var handles = _getHandles(items, opts.handle)
_attr(sortableElement, 'aria-dropeffect', 'move')
_data(sortableElement, '_disabled', 'false')
_attr(handles, 'draggable', 'true')
// IE FIX for ghost
// can be disabled as it has the side effect that other events
// (e.g. click) will be ignored
var spanEl = (document || window.document).createElement('span')
if (typeof spanEl.dragDrop === 'function' && !opts.disableIEFix) {
_on(handles, 'mousedown', function () {
if (items.indexOf(this) !== -1) {
this.dragDrop()
} else {
var parent = this.parentElement
while (items.indexOf(parent) === -1) {
parent = parent.parentElement
}
parent.dragDrop()
}
})
}
}
/*
* Disable the sortable
* @param {Element} sortableElement a single sortable
*/
var _disableSortable = function (sortableElement) {
var opts = _data(sortableElement, 'opts')
var items = _filter(_getChildren(sortableElement), opts.items)
var handles = _getHandles(items, opts.handle)
_attr(sortableElement, 'aria-dropeffect', 'none')
_data(sortableElement, '_disabled', 'true')
_attr(handles, 'draggable', 'false')
_off(handles, 'mousedown')
}
/*
* Reload the sortable
* @param {Element} sortableElement a single sortable
* @description events need to be removed to not be double bound
*/
var _reloadSortable = function (sortableElement) {
var opts = _data(sortableElement, 'opts')
var items = _filter(_getChildren(sortableElement), opts.items)
var handles = _getHandles(items, opts.handle)
_data(sortableElement, '_disabled', 'false')
// remove event handlers from items
_removeItemEvents(items)
_off(handles, 'mousedown')
// remove event handlers from sortable
_removeSortableEvents(sortableElement)
}
/**
* Get position of the element relatively to its sibling elements
* @param {Element} element
* @returns {number}
*/
var _index = function (element) {
if (!element.parentElement) {
return 0
}
return Array.prototype.indexOf.call(element.parentElement.children, element)
}
/**
* Whether element is in DOM
* @param {Element} element
* @returns {boolean}
*/
var _attached = function (element) {
// document.body.contains(element)
return !!element.parentNode
}
/**
* Convert HTML string into DOM element.
* @param {Element|string} html
* @param {string} tagname
* @returns {Element}
*/
var _html2element = function (html, tagName) {
if (typeof html !== 'string') {
return html
}
var parentElement = document.createElement(tagName)
parentElement.innerHTML = html
return parentElement.firstChild
}
/**
* Insert before target
* @param {Element} target
* @param {Element} element
*/
var _before = function (target, element) {
target.parentElement.insertBefore(
element,
target
)
}
/**
* Insert after target
* @param {Element} target
* @param {Element} element
*/
var _after = function (target, element) {
target.parentElement.insertBefore(
element,
target.nextElementSibling
)
}
/**
* Detach element from DOM
* @param {Element} element
*/
var _detach = function (element) {
if (element.parentNode) {
element.parentNode.removeChild(element)
}
}
/**
* Make native event that can be dispatched afterwards
* @param {string} name
* @param {object} detail
* @returns {CustomEvent}
*/
var _makeEvent = function (name, detail) {
var e = document.createEvent('Event')
if (detail) {
e.detail = detail
}
e.initEvent(name, false, true)
return e
}
/**
* @param {Element} sortableElement
* @param {CustomEvent} event
*/
var _dispatchEventOnConnected = function (sortableElement, event) {
sortables.forEach(function (target) {
if (_listsConnected(sortableElement, target)) {
target.dispatchEvent(event)
}
})
}
/**
* Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed
* @param {fn} Function to debounce
* @param {delay} time to wait before calling function with latest arguments, 0 - no debounce
* @param {context} time to wait before calling function with latest arguments, 0 - no debounce
* @returns {function} - debounced function
*/
function _debounce (fn, delay, context) {
var timer = null
if (delay === 0) {
return fn
}
return function () {
var eContext = context || this
var args = arguments
clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(eContext, args)
}, delay)
}
}
var _getChildren = function (element) {
return element.children
}
var _serialize = function (list) {
var children = _filter(_getChildren(list), _data(list, 'items'))
return children
}
/*
* Public sortable object
* @param {Array|NodeList} sortableElements
* @param {object|string} options|method
*/
var sortable = function (sortableElements, options) {
var method = String(options)
options = (function (options) {
var result = {
connectWith: false,
placeholder: null,
disableIEFix: false,
placeholderClass: 'sortable-placeholder',
draggingClass: 'sortable-dragging',
hoverClass: false,
debounce: 0,
maxItems: 0
}
for (var option in options) {
result[option] = options[option]
}
return result
})(options)
if (options && typeof options.getChildren === 'function') {
_getChildren = options.getChildren
}
if (typeof sortableElements === 'string') {
sortableElements = document.querySelectorAll(sortableElements)
}
if (sortableElements instanceof window.Element) {
sortableElements = [sortableElements]
}
sortableElements = Array.prototype.slice.call(sortableElements)
if (/serialize/.test(method)) {
var serialized = []
sortableElements.forEach(function (sortableElement) {
serialized.push({
list: sortableElement,
children: _serialize(sortableElement)
})
})
return serialized
}
/* TODO: maxstatements should be 25, fix and remove line below */
/* jshint maxstatements:false */
sortableElements.forEach(function (sortableElement) {
if (/enable|disable|destroy/.test(method)) {
return sortable[method](sortableElement)
}
// get options & set options on sortable
options = _data(sortableElement, 'opts') || options
_data(sortableElement, 'opts', options)
// reset sortable
_reloadSortable(sortableElement)
// initialize
var items = _filter(_getChildren(sortableElement), options.items)
var index
var startParent
var startList
var placeholder = options.placeholder
var maxItems
if (!placeholder) {
placeholder = document.createElement(
/^ul|ol$/i.test(sortableElement.tagName) ? 'li' : 'div'
)
}
placeholder = _html2element(placeholder, sortableElement.tagName)
placeholder.classList.add.apply(
placeholder.classList,
options.placeholderClass.split(' ')
)
// setup sortable ids
if (!sortableElement.getAttribute('data-sortable-id')) {
var id = sortables.length
sortables[id] = sortableElement
_attr(sortableElement, 'data-sortable-id', id)
_attr(items, 'data-item-sortable-id', id)
}
_data(sortableElement, 'items', options.items)
placeholders.push(placeholder)
if (options.connectWith) {
_data(sortableElement, 'connectWith', options.connectWith)
}
_enableSortable(sortableElement)
_attr(items, 'role', 'option')
_attr(items, 'aria-grabbed', 'false')
// Mouse over class
if (options.hoverClass) {
var hoverClass = 'sortable-over'
if (typeof options.hoverClass === 'string') {
hoverClass = options.hoverClass
}
_on(items, 'mouseenter', function () {
this.classList.add(hoverClass)
})
_on(items, 'mouseleave', function () {
this.classList.remove(hoverClass)
})
}
// max items
if (options.maxItems && typeof options.maxItems === 'number') {
maxItems = options.maxItems
}
// Handle drag events on draggable items
_on(items, 'dragstart', function (e) {
e.stopImmediatePropagation()
if ((options.handle && !_matches(e.target, options.handle)) || this.getAttribute('draggable') === 'false') {
return
}
// add transparent clone or other ghost to cursor
_getGhost(e, this)
// cache selsection & add attr for dragging
this.classList.add(options.draggingClass)
dragging = this
_attr(dragging, 'aria-grabbed', 'true')
// grab values
index = _index(dragging)
draggingHeight = parseInt(window.getComputedStyle(dragging).height)
startParent = this.parentElement
startList = _serialize(startParent)
// dispatch sortstart event on each element in group
_dispatchEventOnConnected(sortableElement, _makeEvent('sortstart', {
item: dragging,
placeholder: placeholder,
startparent: startParent
}))
})
// Handle drag events on draggable items
_on(items, 'dragend', function () {
var newParent
if (!dragging) {
return
}
// remove dragging attributes and show item
dragging.classList.remove(options.draggingClass)
_attr(dragging, 'aria-grabbed', 'false')
dragging.style.display = dragging.oldDisplay
delete dragging.oldDisplay
placeholders.forEach(_detach)
newParent = this.parentElement
_dispatchEventOnConnected(sortableElement, _makeEvent('sortstop', {
item: dragging,
startparent: startParent
}))
if (index !== _index(dragging) || startParent !== newParent) {
_dispatchEventOnConnected(sortableElement, _makeEvent('sortupdate', {
item: dragging,
index: _filter(_getChildren(newParent), _data(newParent, 'items'))
.indexOf(dragging),
oldindex: items.indexOf(dragging),
elementIndex: _index(dragging),
oldElementIndex: index,
startparent: startParent,
endparent: newParent,
newEndList: _serialize(newParent),
newStartList: _serialize(startParent),
oldStartList: startList
}))
}
dragging = null
draggingHeight = null
})
// Handle drop event on sortable & placeholder
// TODO: REMOVE placeholder?????
_on([sortableElement, placeholder], 'drop', function (e) {
var visiblePlaceholder
if (!_listsConnected(sortableElement, dragging.parentElement)) {
return
}
e.preventDefault()
e.stopPropagation()
visiblePlaceholder = placeholders.filter(_attached)[0]
_after(visiblePlaceholder, dragging)
dragging.dispatchEvent(_makeEvent('dragend'))
})
var debouncedDragOverEnter = _debounce(function (element, pageY) {
if (!dragging) {
return
}
if (items.indexOf(element) !== -1) {
var thisHeight = parseInt(window.getComputedStyle(element).height)
var placeholderIndex = _index(placeholder)
var thisIndex = _index(element)
if (options.forcePlaceholderSize) {
placeholder.style.height = draggingHeight + 'px'
}
// 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?
var deadZone = thisHeight - draggingHeight
var offsetTop = _offset(element).top
if (placeholderIndex < thisIndex &&
pageY < offsetTop + deadZone) {
return
}
if (placeholderIndex > thisIndex &&
pageY > offsetTop + thisHeight - deadZone) {
return
}
}
if (dragging.oldDisplay === undefined) {
dragging.oldDisplay = dragging.style.display
}
if (dragging.style.display !== 'none') {
dragging.style.display = 'none'
}
if (placeholderIndex < thisIndex) {
_after(element, placeholder)
} else {
_before(element, placeholder)
}
// Intentionally violated chaining, it is more complex otherwise
placeholders
.filter(function (element) { return element !== placeholder })
.forEach(_detach)
} else {
if (placeholders.indexOf(element) === -1 &&
!_filter(_getChildren(element), options.items).length) {
placeholders.forEach(_detach)
element.appendChild(placeholder)
}
}
}, options.debounce)
// Handle dragover and dragenter events on draggable items
var onDragOverEnter = function (e) {
if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || _data(sortableElement, '_disabled') === 'true') {
return
}
if (maxItems && _filter(_getChildren(sortableElement), _data(sortableElement, 'items')).length >= maxItems) {
return
}
e.preventDefault()
e.stopPropagation()
e.dataTransfer.dropEffect = 'move'
debouncedDragOverEnter(this, e.pageY)
}
_on(items.concat(sortableElement), 'dragover', onDragOverEnter)
_on(items.concat(sortableElement), 'dragenter', onDragOverEnter)
})
return sortableElements
}
sortable.destroy = function (sortableElement) {
_destroySortable(sortableElement)
}
sortable.enable = function (sortableElement) {
_enableSortable(sortableElement)
}
sortable.disable = function (sortableElement) {
_disableSortable(sortableElement)
}
return sortable;
}));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
.sortable > *, .sortable-with-handle .sortable-handle {
cursor: move;
}
.sortable-placeholder {
display: block;
height: 42px;
margin-top: 1px;
background-color: #f3f7f9;
border: 1px dashed #e4eaec;
}
.sortable.list-group-gap .sortable-placeholder, .sortable-with-handle.list-group-gap .sortable-placeholder {
margin-bottom: 2px;
}
.sortable.list-task .sortable-placeholder {
height: 65px;
}
+1
View File
@@ -0,0 +1 @@
.sortable-with-handle .sortable-handle,.sortable>*{cursor:move}.sortable-placeholder{display:block;height:42px;margin-top:1px;background-color:#f3f7f9;border:1px dashed #e4eaec}.sortable-with-handle.list-group-gap .sortable-placeholder,.sortable.list-group-gap .sortable-placeholder{margin-bottom:2px}.sortable.list-task .sortable-placeholder{height:65px}