diff options
Diffstat (limited to '')
-rw-r--r-- | public/css/module.less | 1103 | ||||
-rw-r--r-- | public/img/ack.gif | bin | 0 -> 564 bytes | |||
-rw-r--r-- | public/img/downtime.gif | bin | 0 -> 601 bytes | |||
-rw-r--r-- | public/img/help.gif | bin | 0 -> 1057 bytes | |||
-rw-r--r-- | public/img/icon_collapse.png | bin | 0 -> 178 bytes | |||
-rw-r--r-- | public/img/icon_expand.png | bin | 0 -> 177 bytes | |||
-rw-r--r-- | public/js/behavior/sortable.js | 47 | ||||
-rw-r--r-- | public/js/module.js | 297 | ||||
-rw-r--r-- | public/js/vendor/Sortable.js | 2349 | ||||
-rw-r--r-- | public/js/vendor/jquery.fn.sortable.js | 76 |
10 files changed, 3872 insertions, 0 deletions
diff --git a/public/css/module.less b/public/css/module.less new file mode 100644 index 0000000..78f42c3 --- /dev/null +++ b/public/css/module.less @@ -0,0 +1,1103 @@ +a:focus { + outline: none; + text-decoration: underline; + &::before { + text-decoration: none; + } +} + +.action-bar { + display: flex; + align-items: center; + font-size: 1.3em; + color: @icinga-blue; + + > a { + &:hover::before { + text-decoration: none; + } + + &:not(:last-child) { + margin-right: 1em; + } + + &.button-link { + color: @text-color-on-icinga-blue; + background: @icinga-blue; + + &:active, &:focus { + text-decoration: none; + } + + &:last-child { + margin-left: auto; + } + } + } + + > div.view-toggle { + margin-right: 1em; + + span { + color: @gray; + margin-right: .5em; + } + + a { + display: inline-block; + + i { + padding: .25em .5em; + border: 1px solid @icinga-blue; + + &:before { + margin-right: 0; + } + + &.active { + color: @text-color-on-icinga-blue; + background-color: @icinga-blue; + } + + &:first-of-type { + border-top-left-radius: .25em; + border-bottom-left-radius: .25em; + } + &:last-of-type { + border-top-right-radius: .25em; + border-bottom-right-radius: .25em; + } + } + } + } + + span.disabled { + color: @gray; + } +} + +form a { + color: @icinga-blue; +} + +div.bp { + margin-bottom: 4px; +} + +div.bp.sortable > .sortable-ghost { + opacity: 0.5; +} + + +/* TreeView */ + +@vertical-tree-item-gap: .5em; + +ul.bp { + margin: 0; + padding: 0; + list-style-type: none; + + .action-link { + font-size: 1.3em; + line-height: 1; + } + + // cursors!!!1 + &:not([data-sortable-disabled="true"]) { + .movable { + cursor: grab; + + &.sortable-chosen { + cursor: grabbing; + } + } + + &.progress .movable { + cursor: wait; + } + } + &[data-sortable-disabled="true"] { + li.process > div { + cursor: pointer; + } + } + + li { + i.icon-service { + opacity: .75; + } + + i.icon-sitemap { + opacity: .8; + } + + i.icon-ok { + opacity: .8; + } + + i.icon-plug { + opacity: .7; + } + } + + // ghost style + &.sortable > li.sortable-ghost { + position: relative; + overflow: hidden; + max-height: 30em; + background-color: @gray-lighter; + border: .2em dotted @gray-light; + border-left-width: 0; + border-right-width: 0; + + &.process:after { + // TODO: Only apply if content overflows? + content: " "; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50%; + background: linear-gradient(transparent, @body-bg-color); + } + } + + // header style + li.process > div { + padding: .291666667em 0; + border-bottom: 1px solid @gray-light; + + > a.toggle { + min-width: 1.25em; // So that process icons align with their node's icons + color: @gray; + } + + > span { + font-size: 1.25em; + + &.op { + padding: .1em .5em; + border-radius: .5em; + background-color: @gray-semilight; + font-weight: bold; + font-size: 1em; + color: @text-color-on-icinga-blue; + } + } + } + + // subprocess style + li.process > ul { + padding-left: 2em; + list-style-type: none; + + &.sortable { + min-height: 1em; // Required to be able to move items back to an otherwise empty list + } + } + + // vertical layout + > li { + padding: @vertical-tree-item-gap 0; + + &:first-child { + margin-top: @vertical-tree-item-gap; + } + + &.process { + padding-bottom: 0; + + &:first-child { + margin-top: 0; + padding-top: 0; + } + } + } + + // horizontal layout + li.process > div, + li:not(.process) { + display: flex; + align-items: center; + padding-left: .25em; + + > * { + margin-right: .5em; + } + + > :not(.overridden-state) + a.action-link { + margin-left: auto; // Let the first action link move everything to the right + + & + a.action-link { + margin-left: 0; // But really only the first one + } + } + + .overridden-state { + margin-left: auto; + margin-right: 1em; + } + } + + // collapse handling + li.process { + // toggle, default + > div > a.toggle > i:before { + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + -o-transition: -o-transform 0.3s; + transition: transform 0.3s; + } + + // toggle, collapsed + &.collapsed > div > a.toggle > i:before { + -moz-transform:rotate(-90deg); + -ms-transform:rotate(-90deg); + -o-transform:rotate(-90deg); + -webkit-transform:rotate(-90deg); + transform:rotate(-90deg); + } + + &.collapsed { + margin-bottom: (@vertical-tree-item-gap * 2); + + > ul.bp { + display: none; + } + } + } + + // hover style + li.process:hover > div { + background-color: @tr-active-color; + } + li:not(.process):hover { + background-color: @tr-active-color; + } + + li.process > div > .state-ball, + li:not(.process) > .state-ball { + border: .15em solid @body-bg-color; + + &.size-s { + width: 7em/6em; + height: 7em/6em; + line-height: 7em/6em; + } + } +} + +/** BEGIN Dashboard **/ +.overview-dashboard { + .header { + font-weight: bold; + display: block; + font-size: 1.25em; + } + + i { + float: left; + font-size: 2.5em; + margin-top: -0.1em; + margin-bottom: 2em; + color: inherit; + } + + .bp-root-tiles { + margin-left: 3em; + } + + .dashboard-tile { + cursor: pointer; + padding: 1em; + + &:hover { + background-color: @tr-hover-color; + } + + .bp-link { + > a { + text-decoration: none; + color: @gray; + vertical-align: middle; + word-wrap: break-word; + width: 100%; + overflow: hidden; + + > span.header { + color: @text-color; + } + } + } + } + + .dashboard-tile, + div.action { + width: 20em; + display: inline-block; + vertical-align: top; + } + + .action { + > a { + text-decoration: none; + color: @gray; + vertical-align: middle; + display: block; + padding: 1em; + word-wrap: break-word; + width: 100%; + overflow: hidden; + box-sizing: border-box; + + &.addnew:hover { + background-color: @tr-hover-color; + } + + > span.header { + color: @text-color; + } + } + } +} +/** END Dashboard **/ + +// State summary badges +.state-badges { + .state-badges(); + + &.state-badges li > ul > li:last-child { + margin-left: 0; + } + + li > ul > li:first-child:not(:last-child) .state-badge { + border-right: 0; + } +} + +// Node children count +.item-count { + font-size: 1em; + text-align: center; + color: @text-color-inverted; +} + +div.bp .state-badges { + display: inline-block; + padding-top: 0; +} + +td > a > .state-badges { + background-color: transparent; +} + +.state-badge { + font-size: .8em; + border: 1px solid @body-bg-color; + + &.state-missing { + background: @gray-semilight; + color: @text-color-on-icinga-blue; + } + + &.state-critical.handled, &.state-down.handled { background: @color-critical-handled; opacity: 1; } + &.state-unknown.handled { background-color: @color-unknown-handled; opacity: 1; } + &.state-warning.handled { background: @color-warning-handled; opacity: 1; } +} + +/** END Badges **/ + +/** BEGIN Tiles **/ +.tiles:after { + content:''; + display:block; + clear: both; +} + +.tiles.sortable > .sortable-ghost { + opacity: 0.5; + border: .2em dashed @gray; +} + +.tiles > div { + color: @text-color-on-icinga-blue; + width: 12em; + display: inline-block; + float: left; + vertical-align: top; + margin-right: 0.2em; + margin-bottom: 0.2em; + height: 6em; + cursor: pointer; + position: relative; + + .item-count { + margin-right: .5em; + } + + .state-badges { + position: absolute; + bottom: 0; + right: 0; + margin: 0.5em; + text-align: center; + font-size: 0.5em; + } + + .overridden-state { + font-size: .75em; + position: absolute; + left: 0; + bottom: 0; + margin: .5em; + border: 1px solid @body-bg-color; + } + + > a { + display: block; + text-decoration: none; + font-size: 0.7em; + text-align: center; + padding: 1em 1em 0; + font-weight: bold; + word-wrap: break-word; + } + + &:hover { + box-shadow: 0 0 .2em @gray; + } + + .actions { + opacity: 0.8; + margin: 0.5em 0 0 0.5em; + font-size: 0.75em; + height: 1.8em; + + i { + float: none; + display: block; + width: 100%; + font-size: 1em; + line-height: normal; + margin: 0; + padding: 0 0 0 0.25em; + } + a { + margin: 0; + padding: 0; + display: inline-block; + width: 1.5em; + height: 1.5em; + border-radius: 0.3em; + } + + a:hover { + background-color: @body-bg-color; + color: @text-color; + } + + > .node-info { + margin-right: 0.3em; + float: right; + } + } +} + +.tiles.sortable:not([data-sortable-disabled="true"]) { + > div { + cursor: grab; + + &.sortable-chosen { + cursor: grabbing; + } + } + + &.progress > div { + cursor: wait; + } +} + +.tiles > div.parent::before { + content: '&'; + position: absolute; + font-size: 1.2em; +} + +.tiles > div.parent { + width: 100%; + height: 2em; +} + +.tiles { + > .critical { background-color: @color-critical; > a { text-shadow: 0 0 1px mix(#000, @color-critical, 40%); }} + > .critical.handled { background-color: @color-critical-handled; > a { text-shadow: 0 0 1px mix(#000, @color-critical-handled, 40%); }} + > .down { background-color: @color-critical; > a { text-shadow: 0 0 1px mix(#000, @color-critical, 40%); }} + > .down.handled { background-color: @color-critical-handled; > a { text-shadow: 0 0 1px mix(#000, @color-critical-handled, 40%); }} + > .unknown { background-color: @color-unknown; > a { text-shadow: 0 0 1px mix(#000, @color-unknown, 40%); }} + > .unknown.handled { background-color: @color-unknown-handled; > a { text-shadow: 0 0 1px mix(#000, @color-unknown-handled, 40%); }} + > .unreachable { background-color: @color-unknown; > a { text-shadow: 0 0 1px mix(#000, @color-unknown, 40%); }} + > .unreachable.handled { background-color: @color-unknown-handled; > a { text-shadow: 0 0 1px mix(#000, @color-unknown-handled, 40%); }} + > .warning { background-color: @color-warning; > a { text-shadow: 0 0 1px mix(#000, @color-warning, 40%); }} + > .warning.handled { background-color: @color-warning-handled; > a { text-shadow: 0 0 1px mix(#000, @color-warning-handled, 40%); }} + > .ok { background-color: @color-ok; > a { text-shadow: 0 0 1px mix(#000, @color-ok, 40%); }} + > .up { background-color: @color-ok; > a { text-shadow: 0 0 1px mix(#000, @color-ok, 40%); }} + > .pending { background-color: @color-pending; > a { text-shadow: 0 0 1px mix(#000, @color-pending, 40%); }} + > .missing { background-color: @gray-semilight; > a { color: @text-color-on-icinga-blue; }} + > .empty { background-color: @gray-semilight; > a { color: @text-color-on-icinga-blue; }} +} + +.tiles.few { font-size: 2.5em; } +.tiles.normal { font-size: 2.1em; } +.tiles.many { font-size: 1.8em; } + +#layout.twocols, #layout.layout-minimal, div.compact { + .tiles.few { font-size: 1.8em; } + .tiles.normal { font-size: 1.8em; } + .tiles.many { font-size: 1.8em; } +} + +#layout.fullscreen-layout .controls { + padding: 0 1em; +} + +/** END of tiles **/ + +.content.restricted { + h1 { + font-size: 2em; + } + + p > a { + margin-left: 1em; + } +} + +/** BEGIN breadcrumb **/ + +.breadcrumb { + list-style: none; + overflow: hidden; + padding: 0; +} + +.breadcrumb:after { + content:''; + display:block; + clear: both; +} +.breadcrumb li { + float: left; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + +} +.breadcrumb li a { + color: @icinga-blue; + margin: 0; + font-size: 1.2em; + text-decoration: none; + padding-left: 2em; + line-height: 2.5em; + position: relative; + display: block; + float: left; + &:focus { + outline: none; + } + + > .state-ball { + margin-right: .5em; + border: .15em solid @body-bg-color; + + &.size-s { + width: 7em/6em; + height: 7em/6em; + line-height: 7em/6em; + } + } +} +.breadcrumb li { + border: 1px solid @gray-lighter; + + &:first-of-type { + border-radius: .25em; + } + + &:last-of-type { + border-radius: .25em; + border: 1px solid transparent; + background: @icinga-blue; + color: @text-color-on-icinga-blue; + padding-right: 1.2em; + + a { + color: @text-color-on-icinga-blue; + } + } +} + +.breadcrumb li:not(:last-of-type) a:before, .breadcrumb li:not(:last-of-type) a:after { + content: " "; + display: block; + width: 0; + height: 0; + border-top: 1.3em solid transparent; + border-bottom: 1.2em solid transparent; + position: absolute; + margin-top: -1.2em; + top: 50%; + left: 100%; +} + +.breadcrumb li:not(:last-of-type) a:before { + border-left: 1.2em solid @gray-lighter; + margin-left: 1px; + z-index: 1; +} + +.breadcrumb li:not(:last-of-type) a:after { + border-left: 1.2em solid @body-bg-color; + z-index: 2; +} + +&.impact { + .breadcrumb li:not(:last-of-type) a:after { + .transition(border-left-color 2s 0.66s linear ~'!important'); + border-left-color: @gray-lighter; + } + + .breadcrumb li:not(:last-of-type) a:before { + .transition(border-left-color 2s 1s linear ~'!important'); + border-left-color: @gray-light; + } + + .breadcrumb li:not(:last-of-type) { + .transition(border-color 2s 1s linear ~'!important'); + border-color: @gray-light; + } + .breadcrumb li:not(:last-of-type) a:hover { + background-color: transparent !important; + color: @icinga-blue; + } +} + +.tabs > .dropdown-nav-item > ul { + z-index: 100; +} + +.breadcrumb li:first-child a { + padding-left: 1em; + padding-right: 0.5em; +} + +.breadcrumb li:not(:last-child) a:hover { background: @icinga-blue; color: @text-color-on-icinga-blue; } +.breadcrumb li:not(:last-child) a:hover:after { border-left-color: @icinga-blue; } +.breadcrumb li:last-child:hover, .breadcrumb li:last-child a:hover { background: @icinga-blue; border-color: @icinga-blue; } + +.breadcrumb li a:focus { + text-decoration: underline; +} + +#layout.twocols, #layout.layout-minimal, div.compact { + .breadcrumb { + font-size: 0.833em; + } +} + +/** END of breadcrumb **/ + + +ul.error, ul.warning { + padding: 0; + list-style-type: none; + background-color: @color-critical; + + li { + font-weight: bold; + color: @text-color-on-icinga-blue; + padding: 0.3em 0.8em; + } + + li a { + color: inherit; + text-decoration: underline; + + &:hover { + text-decoration: none; + } + } +} + + +ul.warning { + background-color: @color-warning; +} + +table.sourcecode { + font-family: monospace; + white-space: pre-wrap; + + th { + vertical-align: top; + padding-right: 0.5em; + user-select: none; + -moz-user-select: none; + -o-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + font-weight: bold; + } + td { + vertical-align: top; + } +} + +/** Forms stolen from director **/ +.content form { + margin-bottom: 2em; +} + +.content form.inline { + margin: 0; +} + +.invisible { + position: absolute; + left: -100%; +} + +form input[type=file] { + padding-right: 1em; +} + +form input[type=submit]:first-of-type { + border-width: 2px; +} + +form p.description { + padding: 1em 1em; + margin: 0; + font-style: italic; + width: 100%; +} + +form ul.form-errors { + margin-bottom: 0.5em; + + ul.errors li { + background: @color-critical; + font-weight: bold; + padding: 0.5em 1em; + color: @text-color-on-icinga-blue; + } +} + +input[type=text], input[type=password], input[type=file], textarea, select { + max-width: 36em; + min-width: 20em; + width: 100%; +} + +label { + line-height: 2em; +} + +form dl { + margin: 0; + padding: 0; +} + +select option { + padding-left: 0.5em; +} + +form dt label { + width: auto; + font-weight: normal; + font-size: inherit; + + &.required { + &::after { + content: '*' + } + } + + &:hover { + text-decoration: underline; + cursor: pointer; + } +} + +#stateOverrides-element { + display: inline-table; + table-layout: fixed; + border-spacing: .5em; + padding: 0; + + label { + display: table-row; + + span, select { + display: table-cell; + } + + span { + width: 10em; + } + + select { + width: 26em; + } + } +} + +form fieldset { + min-width: 36em; +} + +form dd input.related-action[type='submit'] { + display: none; +} + +form dd.active li.active input.related-action[type='submit'] { + display: inline-block; +} + +form dd.active { + p.description { + color: inherit; + font-style: normal; + } +} + +form dd { + padding: 0.3em 0.5em; + margin: 0; +} + +form dt { + padding: 0.5em 0.5em; + margin: 0; +} + +form dt.active, form dd.active { + background-color: @tr-active-color; +} + +form dt { + display: inline-block; + vertical-align: top; + min-width: 12em; + min-height: 2.5em; + width: 30%; + &.errors label { + color: @color-critical; + } +} + +form .errors label { + color: @color-critical; +} + +form dd { + display: inline-block; + width: 63%; + min-height: 2.5em; + vertical-align: top; + margin: 0; + &.errors { + input[type=text], select { + border-color: @color-critical; + } + } + + &.full-width { + padding: 0.5em; + width: 100%; + } +} + +form dd:after { + display: block; + content: ''; +} + +form textarea { + height: auto; +} + +form dd ul.errors { + list-style-type: none; + padding-left: 0.3em; + + li { + color: @color-critical; + padding: 0.3em; + } +} + +form { + #_FAKE_SUBMIT { + position: absolute; + left: -100%; + } +} + +/** END of forms **/ + +/* Form fallback styles, remove once <=2.9.5 support is dropped */ + +.icinga-controls { + input[type="file"] { + background-color: @low-sat-blue; + } + + button[type="button"] { + background-color: @low-sat-blue; + } +} + +form.icinga-form { + input[type="file"] { + flex: 1 1 auto; + width: 0; + } + + button[type="button"] { + line-height: normal; + } +} + +/* Form fallback styles end */ + +/** Custom font styling **/ +textarea.smaller { + font-size: 0.833em; + max-width: 60em; +} +/** END of custom font styling **/ + +/** php-diff **/ +.Differences { + width: 100%; + table-layout: fixed; + empty-cells: show; +} + +.Differences thead { + display: none; +} + +.Differences thead th { + text-align: left; + padding-left: 4 / 14 * 16em; +} +.Differences tbody th { + text-align: right; + width: 4em; + padding: 1px 2px; + border-right: 1px solid @gray-light; + background: @gray-lightest; + font-weight: normal; + vertical-align: top; +} + +.Differences tbody td { + width: 50%; + .preformatted(); + word-break: break-all; +} + +@color-diff-ins: #bfb; +@color-diff-del: #faa; +@color-diff-changed-old: #fdd; +@color-diff-changed-new: #efe; + +.diff { + font-family: monospace; + white-space: pre-wrap; + + del, ins { + text-decoration: none; + } + + del { + color: @color-critical; + background-color: #fdd; + } + + ins { + color: @color-ok; + background-color: #dfd; + } +} + +.DifferencesSideBySide { + ins, del { + text-decoration: none; + } + + .ChangeInsert { + td.Left { + background: @gray-lighter; + } + td.Right { + background: @color-diff-ins; + } + } + + .ChangeDelete { + td.Left { + background: @color-diff-del; + } + td.Right { + background: @gray-lighter; + } + } + + .ChangeReplace { + td.Left { + color: black; + background: @color-diff-changed-old; + del { + background: @color-diff-del; + } + } + + td.Right { + color: black; + background: @color-diff-changed-new; + ins { + background: @color-diff-ins; + } + } + + } +} + +.Differences .Skipped { + background: @gray-lightest; +} + +.DifferencesInline .ChangeReplace .Left, +.DifferencesInline .ChangeDelete .Left { + background: #fdd; +} + +.DifferencesInline .ChangeReplace .Right, +.DifferencesInline .ChangeInsert .Right { + background: #dfd; +} + +.DifferencesInline .ChangeReplace ins { + background: #9e9; +} + +.DifferencesInline .ChangeReplace del { + background: #e99; +} +/** END of php-diff **/ diff --git a/public/img/ack.gif b/public/img/ack.gif Binary files differnew file mode 100644 index 0000000..cda95a8 --- /dev/null +++ b/public/img/ack.gif diff --git a/public/img/downtime.gif b/public/img/downtime.gif Binary files differnew file mode 100644 index 0000000..1687798 --- /dev/null +++ b/public/img/downtime.gif diff --git a/public/img/help.gif b/public/img/help.gif Binary files differnew file mode 100644 index 0000000..9226497 --- /dev/null +++ b/public/img/help.gif diff --git a/public/img/icon_collapse.png b/public/img/icon_collapse.png Binary files differnew file mode 100644 index 0000000..0c7f37b --- /dev/null +++ b/public/img/icon_collapse.png diff --git a/public/img/icon_expand.png b/public/img/icon_expand.png Binary files differnew file mode 100644 index 0000000..19862cf --- /dev/null +++ b/public/img/icon_expand.png diff --git a/public/js/behavior/sortable.js b/public/js/behavior/sortable.js new file mode 100644 index 0000000..8f32ab7 --- /dev/null +++ b/public/js/behavior/sortable.js @@ -0,0 +1,47 @@ +/*! Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +(function(Icinga, $) { + + 'use strict'; + + Icinga.Behaviors = Icinga.Behaviors || {}; + + var Sortable = function (icinga) { + Icinga.EventListener.call(this, icinga); + this.on('rendered', this.onRendered, this); + }; + + Sortable.prototype = new Icinga.EventListener(); + + Sortable.prototype.onRendered = function(e) { + $(e.target).find('.sortable').each(function() { + var $el = $(this); + var options = { + scroll: $el.closest('.container')[0], + onMove: function (/**Event*/ event, /**Event*/ originalEvent) { + if (typeof this.options['filter'] !== 'undefined' && $(event.related).is(this.options['filter'])) { + // Assumes the filtered item is either at the very start or end of the list and prevents the + // user from dropping other items before (if at the very start) or after it. + return false; + } + } + }; + + $.each($el.data(), function (i, v) { + if (i.length > 8 && i.substring(0, 8) === 'sortable') { + options[i.charAt(8).toLowerCase() + i.substr(9)] = v; + } + }); + + if (typeof options.group !== 'undefined' && typeof options.group.put === 'string' && options.group.put.substring(0, 9) === 'function:') { + var module = icinga.module($el.closest('.icinga-module').data('icingaModule')); + options.group.put = module.object[options.group.put.substr(9)]; + } + + $(this).sortable(options); + }); + }; + + Icinga.Behaviors.Sortable = Sortable; + +})(Icinga, jQuery); diff --git a/public/js/module.js b/public/js/module.js new file mode 100644 index 0000000..ca9e238 --- /dev/null +++ b/public/js/module.js @@ -0,0 +1,297 @@ + +(function(Icinga) { + + var Bp = function(module) { + /** + * YES, we need Icinga + */ + this.module = module; + + this.idCache = {}; + + this.initialize(); + + this.module.icinga.logger.debug('BP module loaded'); + }; + + Bp.prototype = { + + initialize: function() + { + /** + * Tell Icinga about our event handlers + */ + this.module.on('rendered', this.onRendered); + + this.module.on('focus', 'form input, form textarea, form select', this.formElementFocus); + + this.module.on('click', 'li.process a.toggle', this.processToggleClick); + this.module.on('click', 'li.process > div', this.processHeaderClick); + this.module.on('end', 'ul.sortable', this.rowDropped); + + this.module.on('click', 'div.tiles > div', this.tileClick); + this.module.on('click', '.dashboard-tile', this.dashboardTileClick); + this.module.on('end', 'div.tiles.sortable', this.tileDropped); + + this.module.on('choose', '.sortable', this.suspendAutoRefresh); + this.module.on('unchoose', '.sortable', this.resumeAutoRefresh); + + this.module.icinga.logger.debug('BP module initialized'); + }, + + onRendered: function (event) { + var $container = $(event.currentTarget); + this.fixFullscreen($container); + this.restoreCollapsedBps($container); + this.highlightFormErrors($container); + this.hideInactiveFormDescriptions($container); + this.fixTileLinksOnDashboard($container); + }, + + processToggleClick: function (event) { + event.stopPropagation(); + + var $li = $(event.currentTarget).closest('li.process'); + $li.toggleClass('collapsed'); + + var $bpUl = $(event.currentTarget).closest('.content > ul.bp'); + if (! $bpUl.length || !$bpUl.data('isRootConfig')) { + return; + } + + var bpName = $bpUl.attr('id'); + if (typeof this.idCache[bpName] === 'undefined') { + this.idCache[bpName] = []; + } + + var index = this.idCache[bpName].indexOf($li.attr('id')); + if ($li.is('.collapsed')) { + if (index === -1) { + this.idCache[bpName].push($li.attr('id')); + } + } else if (index !== -1) { + this.idCache[bpName].splice(index, 1); + } + }, + + processHeaderClick: function (event) { + this.processToggleClick(event); + }, + + hideInactiveFormDescriptions: function($container) { + $container.find('dd').not('.active').find('p.description').hide(); + }, + + tileClick: function(event) { + $(event.currentTarget).find('> a').first().trigger('click'); + }, + + dashboardTileClick: function(event) { + $(event.currentTarget).find('> .bp-link > a').first().trigger('click'); + }, + + suspendAutoRefresh: function(event) { + // TODO: If there is a better approach some time, let me know + $(event.originalEvent.from).closest('.container').data('lastUpdate', (new Date()).getTime() + 3600 * 1000); + event.stopPropagation(); + }, + + resumeAutoRefresh: function(event) { + var $container = $(event.originalEvent.from).closest('.container'); + $container.data('lastUpdate', (new Date()).getTime() - ($container.data('icingaRefresh') || 10) * 1000); + event.stopPropagation(); + }, + + tileDropped: function(event) { + var evt = event.originalEvent; + if (evt.oldIndex !== evt.newIndex) { + var $source = $(evt.from); + $source.addClass('progress') + .data('sortable').option('disabled', true); + + var data = { + csrfToken: $source.data('csrfToken'), + movenode: 'movenode', // That's the submit button.. + parent: $(evt.to).data('nodeName') || '', + from: evt.oldIndex, + to: evt.newIndex + }; + + var actionUrl = [ + $source.data('actionUrl'), + 'action=move', + 'movenode=' + $(evt.item).data('nodeName') + ].join('&'); + + var $container = $source.closest('.container'); + var req = icinga.loader.loadUrl(actionUrl, $container, data, 'POST'); + req.always(function() { + icinga.loader.loadUrl( + $container.data('icingaUrl'), $container, undefined, undefined, undefined, true); + }); + } + }, + + rowDropped: function(event) { + var evt = event.originalEvent, + $source = $(evt.from), + $target = $(evt.to); + + if (evt.oldIndex !== evt.newIndex || !$target.is($source)) { + var $root = $target.closest('.content > ul.bp'); + $root.addClass('progress') + .find('ul.bp') + .add($root) + .each(function() { + $(this).data('sortable').option('disabled', true); + }); + + var data = { + csrfToken: $target.data('csrfToken'), + movenode: 'movenode', // That's the submit button.. + parent: $target.closest('.process').data('nodeName') || '', + from: evt.oldIndex, + to: evt.newIndex + }; + + var actionUrl = [ + $source.data('actionUrl'), + 'action=move', + 'movenode=' + $(evt.item).data('nodeName') + ].join('&'); + + var $container = $target.closest('.container'); + var req = icinga.loader.loadUrl(actionUrl, $container, data, 'POST'); + req.always(function() { + icinga.loader.loadUrl( + $container.data('icingaUrl'), $container, undefined, undefined, undefined, true); + }); + event.stopPropagation(); + } + }, + + /** + * Called by Sortable.js while in Tree-View + * + * See group option on the sortable elements. + * + * @param to + * @param from + * @param item + * @param event + * @returns boolean + */ + rowPutAllowed: function(to, from, item, event) { + if (to.options.group.name === 'root') { + return $(item).is('.process'); + } + + // Otherwise we're facing a nesting error next + var $item = $(item), + childrenNames = $item.find('.process').map(function () { + return $(this).data('nodeName'); + }).get(); + childrenNames.push($item.data('nodeName')); + var loopDetected = $(to.el).parents('.process').toArray().some(function (parent) { + return childrenNames.indexOf($(parent).data('nodeName')) !== -1; + }); + + return !loopDetected; + }, + + fixTileLinksOnDashboard: function($container) { + if ($container.closest('div.dashboard').length) { + $container.find('div.tiles').data('baseTarget', '_next'); + } + }, + + fixFullscreen: function($container) { + var $controls = $container.find('div.controls'); + var $layout = $('#layout'); + var icinga = this.module.icinga; + if ($controls.hasClass('want-fullscreen')) { + if (!$layout.hasClass('fullscreen-layout')) { + + $layout.addClass('fullscreen-layout'); + $controls.removeAttr('style'); + $container.find('.fake-controls').remove(); + icinga.ui.currentLayout = 'fullscreen'; + } + } else if (! $container.parent('.dashboard').length) { + if ($layout.hasClass('fullscreen-layout')) { + $layout.removeClass('fullscreen-layout'); + icinga.ui.layoutHasBeenChanged(); + icinga.ui.initializeControls($container); + } + } + }, + + restoreCollapsedBps: function($container) { + var $bpUl = $container.find('.content > ul.bp'); + if (! $bpUl.length || !$bpUl.data('isRootConfig')) { + return; + } + + var bpName = $bpUl.attr('id'); + if (typeof this.idCache[bpName] === 'undefined') { + return; + } + + var _this = this; + $bpUl.find('li.process') + .filter(function () { + return _this.idCache[bpName].indexOf(this.id) !== -1; + }) + .addClass('collapsed'); + }, + + /** BEGIN Form handling, borrowed from Director **/ + formElementFocus: function(ev) + { + var $input = $(ev.currentTarget); + var $dd = $input.closest('dd'); + $dd.find('p.description').show(); + if ($dd.attr('id') && $dd.attr('id').match(/button/)) { + return; + } + var $li = $input.closest('li'); + var $dt = $dd.prev(); + var $form = $dd.closest('form'); + + $form.find('dt, dd, li').removeClass('active'); + $li.addClass('active'); + $dt.addClass('active'); + $dd.addClass('active'); + $dd.find('p.description.fading-out') + .stop(true) + .removeClass('fading-out') + .fadeIn('fast'); + + $form.find('dd').not($dd) + .find('p.description') + .not('.fading-out') + .addClass('fading-out') + .delay(2000) + .fadeOut('slow', function() { + $(this).removeClass('fading-out').hide() + }); + }, + + highlightFormErrors: function($container) + { + $container.find('dd ul.errors').each(function(idx, ul) { + var $ul = $(ul); + var $dd = $ul.closest('dd'); + var $dt = $dd.prev(); + + $dt.addClass('errors'); + $dd.addClass('errors'); + }); + } + /** END Form handling **/ + }; + + Icinga.availableModules.businessprocess = Bp; + +}(Icinga)); + diff --git a/public/js/vendor/Sortable.js b/public/js/vendor/Sortable.js new file mode 100644 index 0000000..edb4e1c --- /dev/null +++ b/public/js/vendor/Sortable.js @@ -0,0 +1,2349 @@ +/**! + * Sortable + * @author RubaXa <trash@rubaxa.org> + * @author owenm <owen23355@gmail.com> + * @license MIT + */ + +(function sortableModule(factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(factory); + } + else if (typeof module != "undefined" && typeof module.exports != "undefined") { + module.exports = factory(); + } + else { + /* jshint sub:true */ + window["Sortable"] = factory(); + } +})(function sortableFactory() { + "use strict"; + + if (typeof window === "undefined" || !window.document) { + return function sortableError() { + throw new Error("Sortable.js requires a window with a document"); + }; + } + + var dragEl, + parentEl, + ghostEl, + cloneEl, + rootEl, + nextEl, + lastDownEl, + + scrollEl, + scrollParentEl, + scrollCustomFn, + + oldIndex, + newIndex, + + activeGroup, + putSortable, + + autoScrolls = [], + scrolling = false, + + awaitingDragStarted = false, + ignoreNextClick = false, + sortables = [], + + pointerElemChangedInterval, + lastPointerElemX, + lastPointerElemY, + + tapEvt, + touchEvt, + + moved, + + + lastTarget, + lastDirection, + pastFirstInvertThresh = false, + isCircumstantialInvert = false, + lastMode, // 'swap' or 'insert' + + targetMoveDistance, + + + forRepaintDummy, + realDragElRect, // dragEl rect after current animation + + /** @const */ + R_SPACE = /\s+/g, + + expando = 'Sortable' + (new Date).getTime(), + + win = window, + document = win.document, + parseInt = win.parseInt, + setTimeout = win.setTimeout, + + $ = win.jQuery || win.Zepto, + Polymer = win.Polymer, + + captureMode = { + capture: false, + passive: false + }, + + IE11OrLess = !!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie|iemobile)/i), + Edge = !!navigator.userAgent.match(/Edge/i), + // FireFox = !!navigator.userAgent.match(/firefox/i), + + CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float', + + // This will not pass for IE9, because IE9 DnD only works on anchors + supportDraggable = ('draggable' in document.createElement('div')), + + supportCssPointerEvents = (function() { + // false when <= IE11 + if (IE11OrLess) { + return false; + } + var el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + })(), + + _silent = false, + _alignedSilent = false, + + abs = Math.abs, + min = Math.min, + + savedInputChecked = [], + + _detectDirection = function(el, options) { + var elCSS = _css(el), + elWidth = parseInt(elCSS.width), + child1 = _getChild(el, 0, options), + child2 = _getChild(el, 1, options), + firstChildCSS = child1 && _css(child1), + secondChildCSS = child2 && _css(child2), + firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + _getRect(child1).width, + secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + _getRect(child2).width; + if (elCSS.display === 'flex') { + return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse' + ? 'vertical' : 'horizontal'; + } + if (child1 && firstChildCSS.float !== 'none') { + var touchingSideChild2 = firstChildCSS.float === 'left' ? 'left' : 'right'; + + return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ? + 'vertical' : 'horizontal'; + } + return (child1 && + ( + firstChildCSS.display === 'block' || + firstChildCSS.display === 'flex' || + firstChildCSS.display === 'table' || + firstChildCSS.display === 'grid' || + firstChildWidth >= elWidth && + elCSS[CSSFloatProperty] === 'none' || + child2 && + elCSS[CSSFloatProperty] === 'none' && + firstChildWidth + secondChildWidth > elWidth + ) ? + 'vertical' : 'horizontal' + ); + }, + + /** + * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold. + * @param {Number} x X position + * @param {Number} y Y position + * @return {HTMLElement} Element of the first found nearest Sortable + */ + _detectNearestEmptySortable = function(x, y) { + for (var i = 0; i < sortables.length; i++) { + if (sortables[i].children.length) continue; + + var rect = _getRect(sortables[i]), + threshold = sortables[i][expando].options.emptyInsertThreshold, + insideHorizontally = x >= (rect.left - threshold) && x <= (rect.right + threshold), + insideVertically = y >= (rect.top - threshold) && y <= (rect.bottom + threshold); + + if (insideHorizontally && insideVertically) { + return sortables[i]; + } + } + }, + + _isClientInRowColumn = function(x, y, el, axis, options) { + var targetRect = _getRect(el), + targetS1Opp = axis === 'vertical' ? targetRect.left : targetRect.top, + targetS2Opp = axis === 'vertical' ? targetRect.right : targetRect.bottom, + mouseOnOppAxis = axis === 'vertical' ? x : y; + + return targetS1Opp < mouseOnOppAxis && mouseOnOppAxis < targetS2Opp; + }, + + _isElInRowColumn = function(el1, el2, axis) { + var el1Rect = el1 === dragEl && realDragElRect || _getRect(el1), + el2Rect = el2 === dragEl && realDragElRect || _getRect(el2), + el1S1Opp = axis === 'vertical' ? el1Rect.left : el1Rect.top, + el1S2Opp = axis === 'vertical' ? el1Rect.right : el1Rect.bottom, + el1OppLength = axis === 'vertical' ? el1Rect.width : el1Rect.height, + el2S1Opp = axis === 'vertical' ? el2Rect.left : el2Rect.top, + el2S2Opp = axis === 'vertical' ? el2Rect.right : el2Rect.bottom, + el2OppLength = axis === 'vertical' ? el2Rect.width : el2Rect.height; + + return ( + el1S1Opp === el2S1Opp || + el1S2Opp === el2S2Opp || + (el1S1Opp + el1OppLength / 2) === (el2S1Opp + el2OppLength / 2) + ); + }, + + _getParentAutoScrollElement = function(el, includeSelf) { + // skip to window + if (!el || !el.getBoundingClientRect) return win; + + var elem = el; + var gotSelf = false; + do { + // we don't need to get elem css if it isn't even overflowing in the first place (performance) + if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) { + var elemCSS = _css(elem); + if ( + elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') || + elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll') + ) { + if (!elem || !elem.getBoundingClientRect || elem === document.body) return win; + + if (gotSelf || includeSelf) return elem; + gotSelf = true; + } + } + /* jshint boss:true */ + } while (elem = elem.parentNode); + + return win; + }, + + _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl, /**Boolean*/isFallback) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (options.scroll) { + var _this = rootEl ? rootEl[expando] : window, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + + x = evt.clientX, + y = evt.clientY, + + winWidth = window.innerWidth, + winHeight = window.innerHeight, + + scrollThisInstance = false; + + // Detect scrollEl + if (scrollParentEl !== rootEl) { + _clearAutoScrolls(); + + scrollEl = options.scroll; + scrollCustomFn = options.scrollFn; + + if (scrollEl === true) { + scrollEl = _getParentAutoScrollElement(rootEl, true); + scrollParentEl = scrollEl; + } + } + + + var layersOut = 0; + var currentParent = scrollEl; + do { + var el = currentParent, + rect = _getRect(el), + + top = rect.top, + bottom = rect.bottom, + left = rect.left, + right = rect.right, + + width = rect.width, + height = rect.height, + + scrollWidth, + scrollHeight, + + css, + + vx, + vy, + + canScrollX, + canScrollY, + + scrollPosX, + scrollPosY; + + + if (el !== win) { + scrollWidth = el.scrollWidth; + scrollHeight = el.scrollHeight; + + css = _css(el); + + canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll'); + canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll'); + + scrollPosX = el.scrollLeft; + scrollPosY = el.scrollTop; + } else { + scrollWidth = document.documentElement.scrollWidth; + scrollHeight = document.documentElement.scrollHeight; + + css = _css(document.documentElement); + + canScrollX = width < scrollWidth && (css.overflowX === 'auto' || css.overflowX === 'scroll' || css.overflowX === 'visible'); + canScrollY = height < scrollHeight && (css.overflowY === 'auto' || css.overflowY === 'scroll' || css.overflowY === 'visible'); + + scrollPosX = document.documentElement.scrollLeft; + scrollPosY = document.documentElement.scrollTop; + } + + vx = canScrollX && (abs(right - x) <= sens && (scrollPosX + width) < scrollWidth) - (abs(left - x) <= sens && !!scrollPosX); + + vy = canScrollY && (abs(bottom - y) <= sens && (scrollPosY + height) < scrollHeight) - (abs(top - y) <= sens && !!scrollPosY); + + + if (!autoScrolls[layersOut]) { + for (var i = 0; i <= layersOut; i++) { + if (!autoScrolls[i]) { + autoScrolls[i] = {}; + } + } + } + + if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) { + autoScrolls[layersOut].el = el; + autoScrolls[layersOut].vx = vx; + autoScrolls[layersOut].vy = vy; + + clearInterval(autoScrolls[layersOut].pid); + + if (el && (vx != 0 || vy != 0)) { + scrollThisInstance = true; + /* jshint loopfunc:true */ + autoScrolls[layersOut].pid = setInterval((function () { + // emulate drag over during autoscroll (fallback), emulating native DnD behaviour + if (isFallback && this.layer === 0) { + Sortable.active._emulateDragOver(true); + } + var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0; + var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0; + + if ('function' === typeof(scrollCustomFn)) { + if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layer].el) !== 'continue') { + return; + } + } + if (autoScrolls[this.layer].el === win) { + win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY); + } else { + autoScrolls[this.layer].el.scrollTop += scrollOffsetY; + autoScrolls[this.layer].el.scrollLeft += scrollOffsetX; + } + }).bind({layer: layersOut}), 24); + } + } + layersOut++; + } while (options.bubbleScroll && currentParent !== win && (currentParent = _getParentAutoScrollElement(currentParent, false))); + scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not + } + }, 30), + + _clearAutoScrolls = function () { + autoScrolls.forEach(function(autoScroll) { + clearInterval(autoScroll.pid); + }); + autoScrolls = []; + }, + + _prepareGroup = function (options) { + function toFn(value, pull) { + return function(to, from, dragEl, evt) { + var sameGroup = to.options.group.name && + from.options.group.name && + to.options.group.name === from.options.group.name; + + if (value == null && (pull || sameGroup)) { + // Default pull value + // Default pull and put value if same group + return true; + } else if (value == null || value === false) { + return false; + } else if (pull && value === 'clone') { + return value; + } else if (typeof value === 'function') { + return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt); + } else { + var otherGroup = (pull ? to : from).options.group.name; + + return (value === true || + (typeof value === 'string' && value === otherGroup) || + (value.join && value.indexOf(otherGroup) > -1)); + } + }; + } + + var group = {}; + var originalGroup = options.group; + + if (!originalGroup || typeof originalGroup != 'object') { + originalGroup = {name: originalGroup}; + } + + group.name = originalGroup.name; + group.checkPull = toFn(originalGroup.pull, true); + group.checkPut = toFn(originalGroup.put); + group.revertClone = originalGroup.revertClone; + + options.group = group; + }, + + _checkAlignment = function(evt) { + if (!dragEl || !dragEl.parentNode) return; + dragEl.parentNode[expando] && dragEl.parentNode[expando]._computeIsAligned(evt); + }, + + _isTrueParentSortable = function(el, target) { + var trueParent = target; + while (!trueParent[expando]) { + trueParent = trueParent.parentNode; + } + + return el === trueParent; + }, + + _artificalBubble = function(sortable, originalEvt, method) { + // Artificial IE bubbling + var nextParent = sortable.parentNode; + while (nextParent && !nextParent[expando]) { + nextParent = nextParent.parentNode; + } + + if (nextParent) { + nextParent[expando][method](_extend(originalEvt, { + artificialBubble: true + })); + } + }, + + _hideGhostForTarget = function() { + if (!supportCssPointerEvents && ghostEl) { + _css(ghostEl, 'display', 'none'); + } + }, + + _unhideGhostForTarget = function() { + if (!supportCssPointerEvents && ghostEl) { + _css(ghostEl, 'display', ''); + } + }; + + + // #1184 fix - Prevent click event on fallback if dragged but item not changed position + document.addEventListener('click', function(evt) { + if (ignoreNextClick) { + evt.preventDefault(); + evt.stopPropagation && evt.stopPropagation(); + evt.stopImmediatePropagation && evt.stopImmediatePropagation(); + ignoreNextClick = false; + return false; + } + }, true); + + var nearestEmptyInsertDetectEvent = function(evt) { + evt = evt.touches ? evt.touches[0] : evt; + if (dragEl) { + var nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY); + + if (nearest) { + nearest[expando]._onDragOver({ + clientX: evt.clientX, + clientY: evt.clientY, + target: nearest, + rootEl: nearest + }); + } + } + }; + // We do not want this to be triggered if completed (bubbling canceled), so only define it here + _on(document, 'dragover', nearestEmptyInsertDetectEvent); + _on(document, 'mousemove', nearestEmptyInsertDetectEvent); + _on(document, 'touchmove', nearestEmptyInsertDetectEvent); + + /** + * @class Sortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + function Sortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw 'Sortable: `el` must be HTMLElement, not ' + {}.toString.call(el); + } + + this.el = el; // root element + this.options = options = _extend({}, options); + + + // Export instance + el[expando] = this; + + // Default options + var defaults = { + group: null, + sort: true, + disabled: false, + store: null, + handle: null, + scroll: true, + scrollSensitivity: 30, + scrollSpeed: 10, + bubbleScroll: true, + draggable: /[uo]l/i.test(el.nodeName) ? '>li' : '>*', + swapThreshold: 1, // percentage; 0 <= x <= 1 + invertSwap: false, // invert always + invertedSwapThreshold: null, // will be set to same as swapThreshold if default + removeCloneOnHide: true, + direction: function() { + return _detectDirection(el, this.options); + }, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + ignore: 'a, img', + filter: null, + preventOnFilter: true, + animation: 0, + easing: null, + setData: function (dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + touchStartThreshold: parseInt(window.devicePixelRatio, 10) || 1, + forceFallback: false, + fallbackClass: 'sortable-fallback', + fallbackOnBody: false, + fallbackTolerance: 0, + fallbackOffset: {x: 0, y: 0}, + supportPointer: Sortable.supportPointer !== false && ( + ('PointerEvent' in window) || + window.navigator && ('msPointerEnabled' in window.navigator) // microsoft + ), + emptyInsertThreshold: 5 + }; + + + // Set default options + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + + _prepareGroup(options); + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + + // Setup drag mode + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + + // Bind events + if (options.supportPointer) { + _on(el, 'pointerdown', this._onTapStart); + } else { + _on(el, 'mousedown', this._onTapStart); + _on(el, 'touchstart', this._onTapStart); + } + + if (this.nativeDraggable) { + _on(el, 'dragover', this); + _on(el, 'dragenter', this); + } + + sortables.push(this.el); + + // Restore sorting + options.store && options.store.get && this.sort(options.store.get(this) || []); + } + + Sortable.prototype = /** @lends Sortable.prototype */ { + constructor: Sortable, + + _computeIsAligned: function(evt) { + var target; + + if (ghostEl && !supportCssPointerEvents) { + _hideGhostForTarget(); + target = document.elementFromPoint(evt.clientX, evt.clientY); + _unhideGhostForTarget(); + } else { + target = evt.target; + } + + target = _closest(target, this.options.draggable, this.el, false); + if (_alignedSilent) return; + if (!dragEl || dragEl.parentNode !== this.el) return; + + var children = this.el.children; + for (var i = 0; i < children.length; i++) { + // Don't change for target in case it is changed to aligned before onDragOver is fired + if (_closest(children[i], this.options.draggable, this.el, false) && children[i] !== target) { + children[i].sortableMouseAligned = _isClientInRowColumn(evt.clientX, evt.clientY, children[i], this._getDirection(evt, null), this.options); + } + } + // Used for nulling last target when not in element, nothing to do with checking if aligned + if (!_closest(target, this.options.draggable, this.el, true)) { + lastTarget = null; + } + + _alignedSilent = true; + setTimeout(function() { + _alignedSilent = false; + }, 30); + + }, + + _getDirection: function(evt, target) { + return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction; + }, + + _onTapStart: function (/** Event|TouchEvent */evt) { + if (!evt.cancelable) return; + var _this = this, + el = this.el, + options = this.options, + preventOnFilter = options.preventOnFilter, + type = evt.type, + touch = evt.touches && evt.touches[0], + target = (touch || evt).target, + originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target, + filter = options.filter, + startIndex; + + _saveInputCheckedState(el); + + + // IE: Calls events in capture mode if event element is nested. This ensures only correct element's _onTapStart goes through. + // This process is also done in _onDragOver + if (IE11OrLess && !evt.artificialBubble && !_isTrueParentSortable(el, target)) { + return; + } + + // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. + if (dragEl) { + return; + } + + if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { + return; // only left button and enabled + } + + // cancel dnd if original target is content editable + if (originalTarget.isContentEditable) { + return; + } + + target = _closest(target, options.draggable, el, false); + + if (!target) { + if (IE11OrLess) { + _artificalBubble(el, evt, '_onTapStart'); + } + return; + } + + if (lastDownEl === target) { + // Ignoring duplicate `down` + return; + } + + // Get the index of the dragged element within its parent + startIndex = _index(target, options.draggable); + + // Check filter + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex); + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } + else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = _closest(originalTarget, criteria.trim(), el, false); + + if (criteria) { + _dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex); + return true; + } + }); + + if (filter) { + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } + + if (options.handle && !_closest(originalTarget, options.handle, el, false)) { + return; + } + + // Prepare `dragstart` + this._prepareDragStart(evt, touch, target, startIndex); + }, + + + _handleAutoScroll: function(evt, fallback) { + if (!dragEl || !this.options.scroll) return; + var x = evt.clientX, + y = evt.clientY, + + elem = document.elementFromPoint(x, y), + _this = this; + + // IE does not seem to have native autoscroll, + // Edge's autoscroll seems too conditional, + // Firefox and Chrome are good + if (fallback || Edge || IE11OrLess) { + _autoScroll(evt, _this.options, elem, fallback); + + // Listener for pointer element change + var ogElemScroller = _getParentAutoScrollElement(elem, true); + if ( + scrolling && + ( + !pointerElemChangedInterval || + x !== lastPointerElemX || + y !== lastPointerElemY + ) + ) { + + pointerElemChangedInterval && clearInterval(pointerElemChangedInterval); + // Detect for pointer elem change, emulating native DnD behaviour + pointerElemChangedInterval = setInterval(function() { + if (!dragEl) return; + // could also check if scroll direction on newElem changes due to parent autoscrolling + var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true); + if (newElem !== ogElemScroller) { + ogElemScroller = newElem; + _clearAutoScrolls(); + _autoScroll(evt, _this.options, ogElemScroller, fallback); + } + }, 10); + lastPointerElemX = x; + lastPointerElemY = y; + } + + } else { + // if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll + if (!_this.options.bubbleScroll || _getParentAutoScrollElement(elem, true) === window) { + _clearAutoScrolls(); + return; + } + _autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false), false); + } + }, + + _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + + if (target && !dragEl && (target.parentNode === el)) { + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + lastDownEl = target; + activeGroup = options.group; + oldIndex = startIndex; + + tapEvt = { + target: dragEl, + clientX: (touch || evt).clientX, + clientY: (touch || evt).clientY + }; + + this._lastX = (touch || evt).clientX; + this._lastY = (touch || evt).clientY; + + dragEl.style['will-change'] = 'all'; + // undo animation if needed + dragEl.style.transition = ''; + dragEl.style.transform = ''; + + dragStartFn = function () { + // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + _this._disableDelayedDrag(); + + // Make the element draggable + dragEl.draggable = _this.nativeDraggable; + + // Bind the events: dragstart/dragend + _this._triggerDragStart(evt, touch); + + // Drag start event + _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex); + + // Chosen item + _toggleClass(dragEl, options.chosenClass, true); + }; + + // Disable "draggable" + options.ignore.split(',').forEach(function (criteria) { + _find(dragEl, criteria.trim(), _disableDraggable); + }); + + if (options.supportPointer) { + _on(ownerDocument, 'pointerup', _this._onDrop); + } else { + _on(ownerDocument, 'mouseup', _this._onDrop); + _on(ownerDocument, 'touchend', _this._onDrop); + _on(ownerDocument, 'touchcancel', _this._onDrop); + } + + if (options.delay) { + // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + _on(ownerDocument, 'touchend', _this._disableDelayedDrag); + _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + _on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler); + _on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler); + options.supportPointer && _on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler); + + _this._dragStartTimer = setTimeout(dragStartFn, options.delay); + } else { + dragStartFn(); + } + } + }, + + _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) { + var touch = e.touches ? e.touches[0] : e; + if (min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) + >= this.options.touchStartThreshold + ) { + this._disableDelayedDrag(); + } + }, + + _disableDelayedDrag: function () { + var ownerDocument = this.el.ownerDocument; + + clearTimeout(this._dragStartTimer); + _off(ownerDocument, 'mouseup', this._disableDelayedDrag); + _off(ownerDocument, 'touchend', this._disableDelayedDrag); + _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + _off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler); + _off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler); + _off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler); + }, + + _triggerDragStart: function (/** Event */evt, /** Touch */touch) { + touch = touch || (evt.pointerType == 'touch' ? evt : null); + + if (!this.nativeDraggable || touch) { + if (this.options.supportPointer) { + _on(document, 'pointermove', this._onTouchMove); + } else if (touch) { + _on(document, 'touchmove', this._onTouchMove); + } else { + _on(document, 'mousemove', this._onTouchMove); + } + } else { + _on(dragEl, 'dragend', this); + _on(rootEl, 'dragstart', this._onDragStart); + } + + try { + if (document.selection) { + // Timeout neccessary for IE9 + _nextTick(function () { + document.selection.empty(); + }); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) { + } + }, + + _dragStarted: function (fallback) { + awaitingDragStarted = false; + if (rootEl && dragEl) { + if (this.nativeDraggable) { + _on(document, 'dragover', this._handleAutoScroll); + _on(document, 'dragover', _checkAlignment); + } + var options = this.options; + + // Apply effect + !fallback && _toggleClass(dragEl, options.dragClass, false); + _toggleClass(dragEl, options.ghostClass, true); + + // In case dragging an animated element + _css(dragEl, 'transform', ''); + + Sortable.active = this; + + fallback && this._appendGhost(); + + // Drag start event + _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex); + } else { + this._nulling(); + } + }, + + _emulateDragOver: function (bypassLastTouchCheck) { + if (touchEvt) { + if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY && !bypassLastTouchCheck) { + return; + } + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + + _hideGhostForTarget(); + + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + var parent = target; + + while (target && target.shadowRoot) { + target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + parent = target; + } + + if (parent) { + do { + if (parent[expando]) { + var inserted; + + inserted = parent[expando]._onDragOver({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + + if (inserted && !this.options.dragoverBubble) { + break; + } + } + + target = parent; // store last element + } + /* jshint boss:true */ + while (parent = parent.parentNode); + } + dragEl.parentNode[expando]._computeIsAligned(touchEvt); + + _unhideGhostForTarget(); + } + }, + + + _onTouchMove: function (/**TouchEvent*/evt) { + if (tapEvt) { + var options = this.options, + fallbackTolerance = options.fallbackTolerance, + fallbackOffset = options.fallbackOffset, + touch = evt.touches ? evt.touches[0] : evt, + matrix = ghostEl && _matrix(ghostEl), + scaleX = ghostEl && matrix && matrix.a, + scaleY = ghostEl && matrix && matrix.d, + dx = ((touch.clientX - tapEvt.clientX) + fallbackOffset.x) / (scaleX ? scaleX : 1), + dy = ((touch.clientY - tapEvt.clientY) + fallbackOffset.y) / (scaleY ? scaleY : 1), + translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; + + + // only set the status to dragging, when we are actually dragging + if (!Sortable.active && !awaitingDragStarted) { + if (fallbackTolerance && + min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance + ) { + return; + } + this._onDragStart(evt, true); + } + + this._handleAutoScroll(touch, true); + + + moved = true; + touchEvt = touch; + + + _css(ghostEl, 'webkitTransform', translate3d); + _css(ghostEl, 'mozTransform', translate3d); + _css(ghostEl, 'msTransform', translate3d); + _css(ghostEl, 'transform', translate3d); + + evt.cancelable && evt.preventDefault(); + } + }, + + _appendGhost: function () { + if (!ghostEl) { + var rect = _getRect(dragEl, this.options.fallbackOnBody ? document.body : rootEl, true), + css = _css(dragEl), + options = this.options; + + ghostEl = dragEl.cloneNode(true); + + _toggleClass(ghostEl, options.ghostClass, false); + _toggleClass(ghostEl, options.fallbackClass, true); + _toggleClass(ghostEl, options.dragClass, true); + + _css(ghostEl, 'box-sizing', 'border-box'); + _css(ghostEl, 'margin', 0); + _css(ghostEl, 'top', rect.top); + _css(ghostEl, 'left', rect.left); + _css(ghostEl, 'width', rect.width); + _css(ghostEl, 'height', rect.height); + _css(ghostEl, 'opacity', '0.8'); + _css(ghostEl, 'position', 'fixed'); + _css(ghostEl, 'zIndex', '100000'); + _css(ghostEl, 'pointerEvents', 'none'); + + options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); + } + }, + + _onDragStart: function (/**Event*/evt, /**boolean*/fallback) { + var _this = this; + var dataTransfer = evt.dataTransfer; + var options = _this.options; + + // Setup clone + cloneEl = _clone(dragEl); + + cloneEl.draggable = false; + cloneEl.style['will-change'] = ''; + + this._hideClone(); + + _toggleClass(cloneEl, _this.options.chosenClass, false); + + + // #1143: IFrame support workaround + _this._cloneId = _nextTick(function () { + if (!_this.options.removeCloneOnHide) { + rootEl.insertBefore(cloneEl, dragEl); + } + _dispatchEvent(_this, rootEl, 'clone', dragEl); + }); + + + !fallback && _toggleClass(dragEl, options.dragClass, true); + + // Set proper drop events + if (fallback) { + ignoreNextClick = true; + _this._loopId = setInterval(_this._emulateDragOver, 50); + } else { + // Undo what was set in _prepareDragStart before drag started + _off(document, 'mouseup', _this._onDrop); + _off(document, 'touchend', _this._onDrop); + _off(document, 'touchcancel', _this._onDrop); + + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(_this, dataTransfer, dragEl); + } + + _on(document, 'drop', _this); + + // #1276 fix: + _css(dragEl, 'transform', 'translateZ(0)'); + } + + awaitingDragStarted = true; + + _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback)); + _on(document, 'selectstart', _this); + }, + + // Returns true - if no further action is needed (either inserted or another condition) + _onDragOver: function (/**Event*/evt) { + var el = this.el, + target = evt.target, + dragRect, + targetRect, + revert, + options = this.options, + group = options.group, + activeSortable = Sortable.active, + isOwner = (activeGroup === group), + canSort = options.sort, + _this = this; + + if (_silent) return; + + // IE event order fix + if (IE11OrLess && !evt.rootEl && !evt.artificialBubble && !_isTrueParentSortable(el, target)) { + return; + } + + // Return invocation when no further action is needed in another sortable + function completed() { + if (activeSortable) { + // Set ghost class to new sortable's ghost class + _toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false); + _toggleClass(dragEl, options.ghostClass, true); + } + + if (putSortable !== _this && _this !== Sortable.active) { + putSortable = _this; + } else if (_this === Sortable.active) { + putSortable = null; + } + + + // Null lastTarget if it is not inside a previously swapped element + if ((target === dragEl && !dragEl.animated) || (target === el && !target.animated)) { + lastTarget = null; + } + // no bubbling and not fallback + if (!options.dragoverBubble && !evt.rootEl && target !== document) { + _this._handleAutoScroll(evt); + dragEl.parentNode[expando]._computeIsAligned(evt); + } + + !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation(); + + return true; + } + + // Call when dragEl has been inserted + function changed() { + _dispatchEvent(_this, rootEl, 'change', target, el, rootEl, oldIndex, _index(dragEl, options.draggable), evt); + } + + + if (evt.preventDefault !== void 0) { + evt.cancelable && evt.preventDefault(); + } + + + moved = true; + + target = _closest(target, options.draggable, el, true); + + // target is dragEl or target is animated + if (!!_closest(evt.target, null, dragEl, true) || target.animated) { + return completed(); + } + + if (target !== dragEl) { + ignoreNextClick = false; + } + + if (activeSortable && !options.disabled && + (isOwner + ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list + : ( + putSortable === this || + ( + (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && + group.checkPut(this, activeSortable, dragEl, evt) + ) + ) + ) + ) { + var axis = this._getDirection(evt, target); + + dragRect = _getRect(dragEl); + + if (revert) { + this._hideClone(); + parentEl = rootEl; // actualization + + if (nextEl) { + rootEl.insertBefore(dragEl, nextEl); + } else { + rootEl.appendChild(dragEl); + } + + return completed(); + } + + if ((el.children.length === 0) || (el.children[0] === ghostEl) || + _ghostIsLast(evt, axis, el) && !dragEl.animated + ) { + //assign target only if condition is true + if (el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target) { + target = _lastChild(el); + } + + if (target) { + targetRect = _getRect(target); + } + + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(this); + } + + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { + el.appendChild(dragEl); + parentEl = el; // actualization + realDragElRect = null; + + changed(); + this._animate(dragRect, dragEl); + target && this._animate(targetRect, target); + return completed(); + } + } + else if (target && target !== dragEl && target.parentNode === el) { + var direction = 0, + targetBeforeFirstSwap, + aligned = target.sortableMouseAligned, + differentLevel = dragEl.parentNode !== el, + scrolledPastTop = _isScrolledPast(target, axis === 'vertical' ? 'top' : 'left'); + + if (lastTarget !== target) { + lastMode = null; + targetBeforeFirstSwap = _getRect(target)[axis === 'vertical' ? 'top' : 'left']; + pastFirstInvertThresh = false; + } + + // Reference: https://www.lucidchart.com/documents/view/10fa0e93-e362-4126-aca2-b709ee56bd8b/0 + if ( + _isElInRowColumn(dragEl, target, axis) && aligned || + differentLevel || + scrolledPastTop || + options.invertSwap || + lastMode === 'insert' || + // Needed, in the case that we are inside target and inserted because not aligned... aligned will stay false while inside + // and lastMode will change to 'insert', but we must swap + lastMode === 'swap' + ) { + // New target that we will be inside + if (lastMode !== 'swap') { + isCircumstantialInvert = options.invertSwap || differentLevel || scrolling || scrolledPastTop; + } + + direction = _getSwapDirection(evt, target, axis, + options.swapThreshold, options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold, + isCircumstantialInvert, + lastTarget === target); + lastMode = 'swap'; + } else { + // Insert at position + direction = _getInsertDirection(target, options); + lastMode = 'insert'; + } + if (direction === 0) return completed(); + + realDragElRect = null; + lastTarget = target; + + lastDirection = direction; + + targetRect = _getRect(target); + + var nextSibling = target.nextElementSibling, + after = false; + + after = direction === 1; + + var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); + + if (moveVector !== false) { + if (moveVector === 1 || moveVector === -1) { + after = (moveVector === 1); + } + + _silent = true; + setTimeout(_unsilent, 30); + + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(this); + } + + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } + + parentEl = dragEl.parentNode; // actualization + + // must be done before animation + if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) { + targetMoveDistance = abs(targetBeforeFirstSwap - _getRect(target)[axis === 'vertical' ? 'top' : 'left']); + } + changed(); + !differentLevel && this._animate(targetRect, target); + this._animate(dragRect, dragEl); + + return completed(); + } + } + + if (el.contains(dragEl)) { + return completed(); + } + } + + if (IE11OrLess && !evt.rootEl) { + _artificalBubble(el, evt, '_onDragOver'); + } + + return false; + }, + + _animate: function (prevRect, target) { + var ms = this.options.animation; + + if (ms) { + var currentRect = _getRect(target); + + if (target === dragEl) { + realDragElRect = currentRect; + } + + if (prevRect.nodeType === 1) { + prevRect = _getRect(prevRect); + } + + // Check if actually moving position + if ((prevRect.left + prevRect.width / 2) !== (currentRect.left + currentRect.width / 2) + || (prevRect.top + prevRect.height / 2) !== (currentRect.top + currentRect.height / 2) + ) { + var matrix = _matrix(this.el), + scaleX = matrix && matrix.a, + scaleY = matrix && matrix.d; + + _css(target, 'transition', 'none'); + _css(target, 'transform', 'translate3d(' + + (prevRect.left - currentRect.left) / (scaleX ? scaleX : 1) + 'px,' + + (prevRect.top - currentRect.top) / (scaleY ? scaleY : 1) + 'px,0)' + ); + + forRepaintDummy = target.offsetWidth; // repaint + _css(target, 'transition', 'transform ' + ms + 'ms' + (this.options.easing ? ' ' + this.options.easing : '')); + _css(target, 'transform', 'translate3d(0,0,0)'); + } + + (typeof target.animated === 'number') && clearTimeout(target.animated); + target.animated = setTimeout(function () { + _css(target, 'transition', ''); + _css(target, 'transform', ''); + target.animated = false; + }, ms); + } + }, + + _offUpEvents: function () { + var ownerDocument = this.el.ownerDocument; + + _off(document, 'touchmove', this._onTouchMove); + _off(document, 'pointermove', this._onTouchMove); + _off(ownerDocument, 'mouseup', this._onDrop); + _off(ownerDocument, 'touchend', this._onDrop); + _off(ownerDocument, 'pointerup', this._onDrop); + _off(ownerDocument, 'touchcancel', this._onDrop); + _off(document, 'selectstart', this); + }, + + _onDrop: function (/**Event*/evt) { + var el = this.el, + options = this.options; + awaitingDragStarted = false; + scrolling = false; + isCircumstantialInvert = false; + pastFirstInvertThresh = false; + + clearInterval(this._loopId); + + clearInterval(pointerElemChangedInterval); + _clearAutoScrolls(); + _cancelThrottle(); + + clearTimeout(this._dragStartTimer); + + _cancelNextTick(this._cloneId); + _cancelNextTick(this._dragStartId); + + // Unbind events + _off(document, 'mousemove', this._onTouchMove); + + + if (this.nativeDraggable) { + _off(document, 'drop', this); + _off(el, 'dragstart', this._onDragStart); + _off(document, 'dragover', this._handleAutoScroll); + _off(document, 'dragover', _checkAlignment); + } + + this._offUpEvents(); + + if (evt) { + if (moved) { + evt.cancelable && evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + + ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); + + if (rootEl === parentEl || (putSortable && putSortable.lastPutMode !== 'clone')) { + // Remove clone + cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); + } + + if (dragEl) { + if (this.nativeDraggable) { + _off(dragEl, 'dragend', this); + } + + _disableDraggable(dragEl); + dragEl.style['will-change'] = ''; + + // Remove class's + _toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false); + _toggleClass(dragEl, this.options.chosenClass, false); + + // Drag stop event + _dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex, null, evt); + + if (rootEl !== parentEl) { + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // Add event + _dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // Remove event + _dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // drag from one list and drop into another + _dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + } + + putSortable && putSortable.save(); + } + else { + if (dragEl.nextSibling !== nextEl) { + // Get the index of the dragged element within its parent + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + } + } + } + + if (Sortable.active) { + /* jshint eqnull:true */ + if (newIndex == null || newIndex === -1) { + newIndex = oldIndex; + } + + _dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // Save sorting + this.save(); + } + } + + } + this._nulling(); + }, + + _nulling: function() { + rootEl = + dragEl = + parentEl = + ghostEl = + nextEl = + cloneEl = + lastDownEl = + + scrollEl = + scrollParentEl = + autoScrolls.length = + + pointerElemChangedInterval = + lastPointerElemX = + lastPointerElemY = + + tapEvt = + touchEvt = + + moved = + newIndex = + oldIndex = + + lastTarget = + lastDirection = + + forRepaintDummy = + realDragElRect = + + putSortable = + activeGroup = + Sortable.active = null; + + savedInputChecked.forEach(function (el) { + el.checked = true; + }); + + savedInputChecked.length = 0; + }, + + handleEvent: function (/**Event*/evt) { + switch (evt.type) { + case 'drop': + case 'dragend': + this._onDrop(evt); + break; + + case 'dragenter': + case 'dragover': + if (dragEl) { + this._onDragOver(evt); + _globalDragOver(evt); + } + break; + + case 'selectstart': + evt.preventDefault(); + break; + } + }, + + + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function () { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + + for (; i < n; i++) { + el = children[i]; + if (_closest(el, options.draggable, this.el, false)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + + return order; + }, + + + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function (order) { + var items = {}, rootEl = this.el; + + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + + if (_closest(el, this.options.draggable, rootEl, false)) { + items[id] = el; + } + }, this); + + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + }, + + + /** + * Save the current sorting + */ + save: function () { + var store = this.options.store; + store && store.set && store.set(this); + }, + + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function (el, selector) { + return _closest(el, selector || this.options.draggable, this.el, false); + }, + + + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function (name, value) { + var options = this.options; + + if (value === void 0) { + return options[name]; + } else { + options[name] = value; + + if (name === 'group') { + _prepareGroup(options); + } + } + }, + + + /** + * Destroy + */ + destroy: function () { + var el = this.el; + + el[expando] = null; + + _off(el, 'mousedown', this._onTapStart); + _off(el, 'touchstart', this._onTapStart); + _off(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + _off(el, 'dragover', this); + _off(el, 'dragenter', this); + } + // Remove draggable attributes + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + + this._onDrop(); + + sortables.splice(sortables.indexOf(this.el), 1); + + this.el = el = null; + }, + + _hideClone: function() { + if (!cloneEl.cloneHidden) { + _css(cloneEl, 'display', 'none'); + cloneEl.cloneHidden = true; + if (cloneEl.parentNode && this.options.removeCloneOnHide) { + cloneEl.parentNode.removeChild(cloneEl); + } + } + }, + + _showClone: function(putSortable) { + if (putSortable.lastPutMode !== 'clone') { + this._hideClone(); + return; + } + + if (cloneEl.cloneHidden) { + // show clone at dragEl or original position + if (rootEl.contains(dragEl) && !this.options.group.revertClone) { + rootEl.insertBefore(cloneEl, dragEl); + } else if (nextEl) { + rootEl.insertBefore(cloneEl, nextEl); + } else { + rootEl.appendChild(cloneEl); + } + + if (this.options.group.revertClone) { + this._animate(dragEl, cloneEl); + } + _css(cloneEl, 'display', ''); + cloneEl.cloneHidden = false; + } + } + }; + + function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) { + if (el) { + ctx = ctx || document; + + do { + if ( + selector != null && + ( + selector[0] === '>' && el.parentNode === ctx && _matches(el, selector.substring(1)) || + _matches(el, selector) + ) || + includeCTX && el === ctx + ) { + return el; + } + + if (el === ctx) break; + /* jshint boss:true */ + } while (el = _getParentOrHost(el)); + } + + return null; + } + + + function _getParentOrHost(el) { + return (el.host && el !== document && el.host.nodeType) + ? el.host + : el.parentNode; + } + + + function _globalDragOver(/**Event*/evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + evt.cancelable && evt.preventDefault(); + } + + + function _on(el, event, fn) { + el.addEventListener(event, fn, captureMode); + } + + + function _off(el, event, fn) { + el.removeEventListener(event, fn, captureMode); + } + + + function _toggleClass(el, name, state) { + if (el && name) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } + else { + var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); + } + } + } + + + function _css(el, prop, val) { + var style = el && el.style; + + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } + else if (el.currentStyle) { + val = el.currentStyle; + } + + return prop === void 0 ? val : val[prop]; + } + else { + if (!(prop in style) && prop.indexOf('webkit') === -1) { + prop = '-webkit-' + prop; + } + + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + + function _matrix(el) { + var appliedTransforms = ''; + do { + var transform = _css(el, 'transform'); + + if (transform && transform !== 'none') { + appliedTransforms = transform + ' ' + appliedTransforms; + } + /* jshint boss:true */ + } while (el = el.parentNode); + + if (window.DOMMatrix) { + return new DOMMatrix(appliedTransforms); + } else if (window.WebKitCSSMatrix) { + return new WebKitCSSMatrix(appliedTransforms); + } else if (window.CSSMatrix) { + return new CSSMatrix(appliedTransforms); + } + } + + + function _find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; + + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + + return list; + } + + return []; + } + + + + function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex, originalEvt) { + sortable = (sortable || rootEl[expando]); + var evt, + options = sortable.options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); + // Support for new CustomEvent feature + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent(name, { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent(name, true, true); + } + + evt.to = toEl || rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + + evt.oldIndex = startIndex; + evt.newIndex = newIndex; + + evt.originalEvent = originalEvt; + + if (rootEl) { + rootEl.dispatchEvent(evt); + } + + if (options[onName]) { + options[onName].call(sortable, evt); + } + } + + + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) { + var evt, + sortable = fromEl[expando], + onMoveFn = sortable.options.onMove, + retVal; + // Support for new CustomEvent feature + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent('move', { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + } + + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || _getRect(toEl); + evt.willInsertAfter = willInsertAfter; + + evt.originalEvent = originalEvt; + + fromEl.dispatchEvent(evt); + + if (onMoveFn) { + retVal = onMoveFn.call(sortable, evt, originalEvt); + } + + return retVal; + } + + function _disableDraggable(el) { + el.draggable = false; + } + + function _unsilent() { + _silent = false; + } + + /** + * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible) + * and non-draggable elements + * @param {HTMLElement} el The parent element + * @param {Number} childNum The index of the child + * @param {Object} options Parent Sortable's options + * @return {HTMLElement} The child at index childNum, or null if not found + */ + function _getChild(el, childNum, options) { + var currentChild = 0, + i = 0, + children = el.children; + + while (i < children.length) { + if ( + children[i].style.display !== 'none' && + children[i] !== ghostEl && + children[i] !== dragEl && + _closest(children[i], options.draggable, el, false) + ) { + if (currentChild === childNum) { + return children[i]; + } + currentChild++; + } + + i++; + } + return null; + } + + /** + * Gets the last child in the el, ignoring ghostEl or invisible elements (clones) + * @param {HTMLElement} el Parent element + * @return {HTMLElement} The last child, ignoring ghostEl + */ + function _lastChild(el) { + var last = el.lastElementChild; + + while (last === ghostEl || last.style.display === 'none') { + last = last.previousElementSibling; + + if (!last) break; + } + + return last || null; + } + + function _ghostIsLast(evt, axis, el) { + var elRect = _getRect(_lastChild(el)), + mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX, + mouseOnOppAxis = axis === 'vertical' ? evt.clientX : evt.clientY, + targetS2 = axis === 'vertical' ? elRect.bottom : elRect.right, + targetS1Opp = axis === 'vertical' ? elRect.left : elRect.top, + targetS2Opp = axis === 'vertical' ? elRect.right : elRect.bottom, + spacer = 10; + + return ( + axis === 'vertical' ? + (mouseOnOppAxis > targetS2Opp + spacer || mouseOnOppAxis <= targetS2Opp && mouseOnAxis > targetS2 && mouseOnOppAxis >= targetS1Opp) : + (mouseOnAxis > targetS2 && mouseOnOppAxis > targetS1Opp || mouseOnAxis <= targetS2 && mouseOnOppAxis > targetS2Opp + spacer) + ); + } + + function _getSwapDirection(evt, target, axis, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) { + var targetRect = _getRect(target), + mouseOnAxis = axis === 'vertical' ? evt.clientY : evt.clientX, + targetLength = axis === 'vertical' ? targetRect.height : targetRect.width, + targetS1 = axis === 'vertical' ? targetRect.top : targetRect.left, + targetS2 = axis === 'vertical' ? targetRect.bottom : targetRect.right, + dragRect = _getRect(dragEl), + invert = false; + + + if (!invertSwap) { + // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold + if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2 + // check if past first invert threshold on side opposite of lastDirection + if (!pastFirstInvertThresh && + (lastDirection === 1 ? + ( + mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2 + ) : + ( + mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2 + ) + ) + ) + { + // past first invert threshold, do not restrict inverted threshold to dragEl shadow + pastFirstInvertThresh = true; + } + + if (!pastFirstInvertThresh) { + var dragS1 = axis === 'vertical' ? dragRect.top : dragRect.left, + dragS2 = axis === 'vertical' ? dragRect.bottom : dragRect.right; + // dragEl shadow (target move distance shadow) + if ( + lastDirection === 1 ? + ( + mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow + ) : + ( + mouseOnAxis > targetS2 - targetMoveDistance + ) + ) + { + return lastDirection * -1; + } + } else { + invert = true; + } + } else { + // Regular + if ( + mouseOnAxis > targetS1 + (targetLength * (1 - swapThreshold) / 2) && + mouseOnAxis < targetS2 - (targetLength * (1 - swapThreshold) / 2) + ) { + return ((mouseOnAxis > targetS1 + targetLength / 2) ? -1 : 1); + } + } + } + + invert = invert || invertSwap; + + if (invert) { + // Invert of regular + if ( + mouseOnAxis < targetS1 + (targetLength * invertedSwapThreshold / 2) || + mouseOnAxis > targetS2 - (targetLength * invertedSwapThreshold / 2) + ) + { + return ((mouseOnAxis > targetS1 + targetLength / 2) ? 1 : -1); + } + } + + return 0; + } + + /** + * Gets the direction dragEl must be swapped relative to target in order to make it + * seem that dragEl has been "inserted" into that element's position + * @param {HTMLElement} target The target whose position dragEl is being inserted at + * @param {Object} options options of the parent sortable + * @return {Number} Direction dragEl must be swapped + */ + function _getInsertDirection(target, options) { + var dragElIndex = _index(dragEl, options.draggable), + targetIndex = _index(target, options.draggable); + + if (dragElIndex < targetIndex) { + return 1; + } else { + return -1; + } + } + + + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + + while (i--) { + sum += str.charCodeAt(i); + } + + return sum.toString(36); + } + + /** + * Returns the index of an element within its parent for a selected set of + * elements + * @param {HTMLElement} el + * @param {selector} selector + * @return {number} + */ + function _index(el, selector) { + var index = 0; + + if (!el || !el.parentNode) { + return -1; + } + + while (el && (el = el.previousElementSibling)) { + if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && el !== cloneEl) { + index++; + } + } + + return index; + } + + function _matches(/**HTMLElement*/el, /**String*/selector) { + if (el) { + try { + if (el.matches) { + return el.matches(selector); + } else if (el.msMatchesSelector) { + return el.msMatchesSelector(selector); + } else if (el.webkitMatchesSelector) { + return el.webkitMatchesSelector(selector); + } + } catch(_) { + return false; + } + } + + return false; + } + + var _throttleTimeout; + function _throttle(callback, ms) { + return function () { + if (!_throttleTimeout) { + var args = arguments, + _this = this; + + _throttleTimeout = setTimeout(function () { + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + + _throttleTimeout = void 0; + }, ms); + } + }; + } + + function _cancelThrottle() { + clearTimeout(_throttleTimeout); + _throttleTimeout = void 0; + } + + function _extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + + return dst; + } + + function _clone(el) { + if (Polymer && Polymer.dom) { + return Polymer.dom(el).cloneNode(true); + } + else if ($) { + return $(el).clone(true)[0]; + } + else { + return el.cloneNode(true); + } + } + + function _saveInputCheckedState(root) { + savedInputChecked.length = 0; + + var inputs = root.getElementsByTagName('input'); + var idx = inputs.length; + + while (idx--) { + var el = inputs[idx]; + el.checked && savedInputChecked.push(el); + } + } + + function _nextTick(fn) { + return setTimeout(fn, 0); + } + + function _cancelNextTick(id) { + return clearTimeout(id); + } + + + /** + * Returns the "bounding client rect" of given element + * @param {HTMLElement} el The element whose boundingClientRect is wanted + * @param {[HTMLElement]} container the parent the element will be placed in + * @param {[Boolean]} adjustForTransform Whether the rect should compensate for parent's transform + * (used for fixed positioning on el) + * @return {Object} The boundingClientRect of el + */ + function _getRect(el, container, adjustForTransform) { + if (!el.getBoundingClientRect && el !== win) return; + + var elRect, + top, + left, + bottom, + right, + height, + width; + + if (el !== win) { + elRect = el.getBoundingClientRect(); + top = elRect.top; + left = elRect.left; + bottom = elRect.bottom; + right = elRect.right; + height = elRect.height; + width = elRect.width; + } else { + top = 0; + left = 0; + bottom = window.innerHeight; + right = window.innerWidth; + height = window.innerHeight; + width = window.innerWidth; + } + + if (adjustForTransform && el !== win) { + // Adjust for translate() + container = container || el.parentNode; + + // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312) + // Not needed on <= IE11 + if (!IE11OrLess) { + do { + if (container && container.getBoundingClientRect && _css(container, 'transform') !== 'none') { + var containerRect = container.getBoundingClientRect(); + + // Set relative to edges of padding box of container + top -= containerRect.top + parseInt(_css(container, 'border-top-width')); + left -= containerRect.left + parseInt(_css(container, 'border-left-width')); + bottom = top + elRect.height; + right = left + elRect.width; + + break; + } + /* jshint boss:true */ + } while (container = container.parentNode); + } + + // Adjust for scale() + var matrix = _matrix(el), + scaleX = matrix && matrix.a, + scaleY = matrix && matrix.d; + + if (matrix) { + top /= scaleY; + left /= scaleX; + + width /= scaleX; + height /= scaleY; + + bottom = top + height; + right = left + width; + } + } + + return { + top: top, + left: left, + bottom: bottom, + right: right, + width: width, + height: height + }; + } + + + /** + * Checks if a side of an element is scrolled past a side of it's parents + * @param {HTMLElement} el The element who's side being scrolled out of view is in question + * @param {String} side Side of the element in question ('top', 'left', 'right', 'bottom') + * @return {Boolean} Whether the element is overflowing the viewport on the given side of it's parent + */ + function _isScrolledPast(el, side) { + var parent = _getParentAutoScrollElement(parent, true), + elSide = _getRect(el)[side]; + + /* jshint boss:true */ + while (parent) { + var parentSide = _getRect(parent)[side], + visible; + + if (side === 'top' || side === 'left') { + visible = elSide >= parentSide; + } else { + visible = elSide <= parentSide; + } + + if (!visible) return true; + + if (parent === win) break; + + parent = _getParentAutoScrollElement(parent, false); + } + + return false; + } + + // Fixed #973: + _on(document, 'touchmove', function(evt) { + if ((Sortable.active || awaitingDragStarted) && evt.cancelable) { + evt.preventDefault(); + } + }); + + + // Export utils + Sortable.utils = { + on: _on, + off: _off, + css: _css, + find: _find, + is: function (el, selector) { + return !!_closest(el, selector, el, false); + }, + extend: _extend, + throttle: _throttle, + closest: _closest, + toggleClass: _toggleClass, + clone: _clone, + index: _index, + nextTick: _nextTick, + cancelNextTick: _cancelNextTick, + detectDirection: _detectDirection, + getChild: _getChild + }; + + + /** + * Create sortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + Sortable.create = function (el, options) { + return new Sortable(el, options); + }; + + + // Export + Sortable.version = '1.8.3'; + return Sortable; +});
\ No newline at end of file diff --git a/public/js/vendor/jquery.fn.sortable.js b/public/js/vendor/jquery.fn.sortable.js new file mode 100644 index 0000000..cd5189a --- /dev/null +++ b/public/js/vendor/jquery.fn.sortable.js @@ -0,0 +1,76 @@ +(function (factory) { + "use strict"; + var sortable, + jq, + _this = this + ; + + if (typeof define === "function" && define.amd) { + try { + define(["sortablejs", "jquery"], function(Sortable, $) { + sortable = Sortable; + jq = $; + checkErrors(); + factory(Sortable, $); + }); + } catch(err) { + checkErrors(); + } + return; + } else if (typeof exports === 'object') { + try { + sortable = require('sortablejs'); + jq = require('jquery'); + } catch(err) { } + } + + if (typeof jQuery === 'function' || typeof $ === 'function') { + jq = jQuery || $; + } + + if (typeof Sortable !== 'undefined') { + sortable = Sortable; + } + + function checkErrors() { + if (!jq) { + throw new Error('jQuery is required for jquery-sortablejs'); + } + + if (!sortable) { + throw new Error('SortableJS is required for jquery-sortablejs (https://github.com/SortableJS/Sortable)'); + } + } + checkErrors(); + factory(sortable, jq); +})(function (Sortable, $) { + "use strict"; + + $.fn.sortable = function (options) { + var retVal, + args = arguments; + + this.each(function () { + var $el = $(this), + sortable = $el.data('sortable'); + + if (!sortable && (options instanceof Object || !options)) { + sortable = new Sortable(this, options); + $el.data('sortable', sortable); + } else if (sortable) { + if (options === 'destroy') { + sortable.destroy(); + $el.removeData('sortable'); + } else if (options === 'widget') { + retVal = sortable; + } else if (typeof sortable[options] === 'function') { + retVal = sortable[options].apply(sortable, [].slice.call(args, 1)); + } else if (options in sortable.options) { + retVal = sortable.option.apply(sortable, args); + } + } + }); + + return (retVal === void 0) ? this : retVal; + }; +});
\ No newline at end of file |