html5sortable-rails 0.9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +86 -0
- data/LICENSE.txt +21 -0
- data/README.md +32 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/html5sortable-rails.gemspec +27 -0
- data/html5sortable-test/.gitignore +30 -0
- data/html5sortable-test/.ruby-version +1 -0
- data/html5sortable-test/Gemfile +62 -0
- data/html5sortable-test/Gemfile.lock +218 -0
- data/html5sortable-test/README.md +24 -0
- data/html5sortable-test/Rakefile +6 -0
- data/html5sortable-test/app/assets/config/manifest.js +3 -0
- data/html5sortable-test/app/assets/images/.keep +0 -0
- data/html5sortable-test/app/assets/javascripts/application.js +17 -0
- data/html5sortable-test/app/assets/javascripts/cable.js +13 -0
- data/html5sortable-test/app/assets/javascripts/channels/.keep +0 -0
- data/html5sortable-test/app/assets/stylesheets/application.css +15 -0
- data/html5sortable-test/app/channels/application_cable/channel.rb +4 -0
- data/html5sortable-test/app/channels/application_cable/connection.rb +4 -0
- data/html5sortable-test/app/controllers/application_controller.rb +2 -0
- data/html5sortable-test/app/controllers/concerns/.keep +0 -0
- data/html5sortable-test/app/helpers/application_helper.rb +2 -0
- data/html5sortable-test/app/jobs/application_job.rb +2 -0
- data/html5sortable-test/app/mailers/application_mailer.rb +4 -0
- data/html5sortable-test/app/models/application_record.rb +3 -0
- data/html5sortable-test/app/models/concerns/.keep +0 -0
- data/html5sortable-test/app/views/layouts/application.html.erb +15 -0
- data/html5sortable-test/app/views/layouts/mailer.html.erb +13 -0
- data/html5sortable-test/app/views/layouts/mailer.text.erb +1 -0
- data/html5sortable-test/bin/bundle +3 -0
- data/html5sortable-test/bin/rails +9 -0
- data/html5sortable-test/bin/rake +9 -0
- data/html5sortable-test/bin/setup +36 -0
- data/html5sortable-test/bin/spring +17 -0
- data/html5sortable-test/bin/update +31 -0
- data/html5sortable-test/bin/yarn +11 -0
- data/html5sortable-test/config.ru +5 -0
- data/html5sortable-test/config/application.rb +19 -0
- data/html5sortable-test/config/boot.rb +4 -0
- data/html5sortable-test/config/cable.yml +10 -0
- data/html5sortable-test/config/credentials.yml.enc +1 -0
- data/html5sortable-test/config/database.yml +25 -0
- data/html5sortable-test/config/environment.rb +5 -0
- data/html5sortable-test/config/environments/development.rb +61 -0
- data/html5sortable-test/config/environments/production.rb +94 -0
- data/html5sortable-test/config/environments/test.rb +46 -0
- data/html5sortable-test/config/initializers/application_controller_renderer.rb +8 -0
- data/html5sortable-test/config/initializers/assets.rb +14 -0
- data/html5sortable-test/config/initializers/backtrace_silencers.rb +7 -0
- data/html5sortable-test/config/initializers/content_security_policy.rb +25 -0
- data/html5sortable-test/config/initializers/cookies_serializer.rb +5 -0
- data/html5sortable-test/config/initializers/filter_parameter_logging.rb +4 -0
- data/html5sortable-test/config/initializers/inflections.rb +16 -0
- data/html5sortable-test/config/initializers/mime_types.rb +4 -0
- data/html5sortable-test/config/initializers/wrap_parameters.rb +14 -0
- data/html5sortable-test/config/locales/en.yml +33 -0
- data/html5sortable-test/config/puma.rb +34 -0
- data/html5sortable-test/config/routes.rb +3 -0
- data/html5sortable-test/config/spring.rb +6 -0
- data/html5sortable-test/config/storage.yml +34 -0
- data/html5sortable-test/db/seeds.rb +7 -0
- data/html5sortable-test/lib/assets/.keep +0 -0
- data/html5sortable-test/lib/tasks/.keep +0 -0
- data/html5sortable-test/log/.keep +0 -0
- data/html5sortable-test/package.json +5 -0
- data/html5sortable-test/public/404.html +67 -0
- data/html5sortable-test/public/422.html +67 -0
- data/html5sortable-test/public/500.html +66 -0
- data/html5sortable-test/public/apple-touch-icon-precomposed.png +0 -0
- data/html5sortable-test/public/apple-touch-icon.png +0 -0
- data/html5sortable-test/public/favicon.ico +0 -0
- data/html5sortable-test/public/robots.txt +1 -0
- data/html5sortable-test/test/application_system_test_case.rb +5 -0
- data/html5sortable-test/test/controllers/.keep +0 -0
- data/html5sortable-test/test/fixtures/.keep +0 -0
- data/html5sortable-test/test/fixtures/files/.keep +0 -0
- data/html5sortable-test/test/helpers/.keep +0 -0
- data/html5sortable-test/test/integration/.keep +0 -0
- data/html5sortable-test/test/mailers/.keep +0 -0
- data/html5sortable-test/test/models/.keep +0 -0
- data/html5sortable-test/test/system/.keep +0 -0
- data/html5sortable-test/test/test_helper.rb +10 -0
- data/html5sortable-test/tmp/.keep +0 -0
- data/html5sortable-test/vendor/.keep +0 -0
- data/lib/html5sortable/rails.rb +8 -0
- data/lib/html5sortable/rails/version.rb +6 -0
- data/vendor/assets/javascripts/html5sortable.js +1094 -0
- metadata +194 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
.rails-default-error-page {
|
|
8
|
+
background-color: #EFEFEF;
|
|
9
|
+
color: #2E2F30;
|
|
10
|
+
text-align: center;
|
|
11
|
+
font-family: arial, sans-serif;
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.rails-default-error-page div.dialog {
|
|
16
|
+
width: 95%;
|
|
17
|
+
max-width: 33em;
|
|
18
|
+
margin: 4em auto 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rails-default-error-page div.dialog > div {
|
|
22
|
+
border: 1px solid #CCC;
|
|
23
|
+
border-right-color: #999;
|
|
24
|
+
border-left-color: #999;
|
|
25
|
+
border-bottom-color: #BBB;
|
|
26
|
+
border-top: #B00100 solid 4px;
|
|
27
|
+
border-top-left-radius: 9px;
|
|
28
|
+
border-top-right-radius: 9px;
|
|
29
|
+
background-color: white;
|
|
30
|
+
padding: 7px 12% 0;
|
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.rails-default-error-page h1 {
|
|
35
|
+
font-size: 100%;
|
|
36
|
+
color: #730E15;
|
|
37
|
+
line-height: 1.5em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.rails-default-error-page div.dialog > p {
|
|
41
|
+
margin: 0 0 1em;
|
|
42
|
+
padding: 1em;
|
|
43
|
+
background-color: #F7F7F7;
|
|
44
|
+
border: 1px solid #CCC;
|
|
45
|
+
border-right-color: #999;
|
|
46
|
+
border-left-color: #999;
|
|
47
|
+
border-bottom-color: #999;
|
|
48
|
+
border-bottom-left-radius: 4px;
|
|
49
|
+
border-bottom-right-radius: 4px;
|
|
50
|
+
border-top-color: #DADADA;
|
|
51
|
+
color: #666;
|
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
|
|
57
|
+
<body class="rails-default-error-page">
|
|
58
|
+
<!-- This file lives in public/422.html -->
|
|
59
|
+
<div class="dialog">
|
|
60
|
+
<div>
|
|
61
|
+
<h1>The change you wanted was rejected.</h1>
|
|
62
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
|
63
|
+
</div>
|
|
64
|
+
<p>If you are the application owner check the logs for more information.</p>
|
|
65
|
+
</div>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
.rails-default-error-page {
|
|
8
|
+
background-color: #EFEFEF;
|
|
9
|
+
color: #2E2F30;
|
|
10
|
+
text-align: center;
|
|
11
|
+
font-family: arial, sans-serif;
|
|
12
|
+
margin: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.rails-default-error-page div.dialog {
|
|
16
|
+
width: 95%;
|
|
17
|
+
max-width: 33em;
|
|
18
|
+
margin: 4em auto 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rails-default-error-page div.dialog > div {
|
|
22
|
+
border: 1px solid #CCC;
|
|
23
|
+
border-right-color: #999;
|
|
24
|
+
border-left-color: #999;
|
|
25
|
+
border-bottom-color: #BBB;
|
|
26
|
+
border-top: #B00100 solid 4px;
|
|
27
|
+
border-top-left-radius: 9px;
|
|
28
|
+
border-top-right-radius: 9px;
|
|
29
|
+
background-color: white;
|
|
30
|
+
padding: 7px 12% 0;
|
|
31
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.rails-default-error-page h1 {
|
|
35
|
+
font-size: 100%;
|
|
36
|
+
color: #730E15;
|
|
37
|
+
line-height: 1.5em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.rails-default-error-page div.dialog > p {
|
|
41
|
+
margin: 0 0 1em;
|
|
42
|
+
padding: 1em;
|
|
43
|
+
background-color: #F7F7F7;
|
|
44
|
+
border: 1px solid #CCC;
|
|
45
|
+
border-right-color: #999;
|
|
46
|
+
border-left-color: #999;
|
|
47
|
+
border-bottom-color: #999;
|
|
48
|
+
border-bottom-left-radius: 4px;
|
|
49
|
+
border-bottom-right-radius: 4px;
|
|
50
|
+
border-top-color: #DADADA;
|
|
51
|
+
color: #666;
|
|
52
|
+
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
|
|
57
|
+
<body class="rails-default-error-page">
|
|
58
|
+
<!-- This file lives in public/500.html -->
|
|
59
|
+
<div class="dialog">
|
|
60
|
+
<div>
|
|
61
|
+
<h1>We're sorry, but something went wrong.</h1>
|
|
62
|
+
</div>
|
|
63
|
+
<p>If you are the application owner check the logs for more information.</p>
|
|
64
|
+
</div>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
|
2
|
+
require_relative '../config/environment'
|
|
3
|
+
require 'rails/test_help'
|
|
4
|
+
|
|
5
|
+
class ActiveSupport::TestCase
|
|
6
|
+
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
|
7
|
+
fixtures :all
|
|
8
|
+
|
|
9
|
+
# Add more helper methods to be used by all tests here...
|
|
10
|
+
end
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* HTML5Sortable package
|
|
3
|
+
* https://github.com/lukasoppermann/html5sortable
|
|
4
|
+
*
|
|
5
|
+
* Maintained by Lukas Oppermann <lukas@vea.re>
|
|
6
|
+
*
|
|
7
|
+
* Released under the MIT license.
|
|
8
|
+
*/
|
|
9
|
+
var sortable = (function () {
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get or set data on element
|
|
14
|
+
* @param {HTMLElement} element
|
|
15
|
+
* @param {string} key
|
|
16
|
+
* @param {any} value
|
|
17
|
+
* @return {*}
|
|
18
|
+
*/
|
|
19
|
+
function addData(element, key, value) {
|
|
20
|
+
if (value === undefined) {
|
|
21
|
+
return element && element.h5s && element.h5s.data && element.h5s.data[key];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
element.h5s = element.h5s || {};
|
|
25
|
+
element.h5s.data = element.h5s.data || {};
|
|
26
|
+
element.h5s.data[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove data from element
|
|
31
|
+
* @param {HTMLElement} element
|
|
32
|
+
*/
|
|
33
|
+
function removeData(element) {
|
|
34
|
+
if (element.h5s) {
|
|
35
|
+
delete element.h5s.data;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _filter (nodes, selector) {
|
|
40
|
+
if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) {
|
|
41
|
+
throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.');
|
|
42
|
+
}
|
|
43
|
+
if (typeof selector !== 'string') {
|
|
44
|
+
return Array.from(nodes);
|
|
45
|
+
}
|
|
46
|
+
return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* eslint-env browser */
|
|
50
|
+
var stores = new Map();
|
|
51
|
+
/**
|
|
52
|
+
* Stores data & configurations per Sortable
|
|
53
|
+
* @param {Object} config
|
|
54
|
+
*/
|
|
55
|
+
var Store = (function () {
|
|
56
|
+
function Store() {
|
|
57
|
+
this._config = new Map(); // eslint-disable-line no-undef
|
|
58
|
+
this._placeholder = undefined; // eslint-disable-line no-undef
|
|
59
|
+
this._data = new Map(); // eslint-disable-line no-undef
|
|
60
|
+
}
|
|
61
|
+
Object.defineProperty(Store.prototype, "config", {
|
|
62
|
+
/**
|
|
63
|
+
* get the configuration map of a class instance
|
|
64
|
+
* @method config
|
|
65
|
+
* @return {object}
|
|
66
|
+
*/
|
|
67
|
+
get: function () {
|
|
68
|
+
// transform Map to object
|
|
69
|
+
var config = {};
|
|
70
|
+
this._config.forEach(function (value, key) {
|
|
71
|
+
config[key] = value;
|
|
72
|
+
});
|
|
73
|
+
// return object
|
|
74
|
+
return config;
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* set the configuration of a class instance
|
|
78
|
+
* @method config
|
|
79
|
+
* @param {object} config object of configurations
|
|
80
|
+
*/
|
|
81
|
+
set: function (config) {
|
|
82
|
+
if (typeof config !== 'object') {
|
|
83
|
+
throw new Error('You must provide a valid configuration object to the config setter.');
|
|
84
|
+
}
|
|
85
|
+
// combine config with default
|
|
86
|
+
var mergedConfig = Object.assign({}, config);
|
|
87
|
+
// add config to map
|
|
88
|
+
this._config = new Map(Object.entries(mergedConfig));
|
|
89
|
+
},
|
|
90
|
+
enumerable: true,
|
|
91
|
+
configurable: true
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* set individual configuration of a class instance
|
|
95
|
+
* @method setConfig
|
|
96
|
+
* @param key valid configuration key
|
|
97
|
+
* @param value any value
|
|
98
|
+
* @return void
|
|
99
|
+
*/
|
|
100
|
+
Store.prototype.setConfig = function (key, value) {
|
|
101
|
+
if (!this._config.has(key)) {
|
|
102
|
+
throw new Error("Trying to set invalid configuration item: " + key);
|
|
103
|
+
}
|
|
104
|
+
// set config
|
|
105
|
+
this._config.set(key, value);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* get an individual configuration of a class instance
|
|
109
|
+
* @method getConfig
|
|
110
|
+
* @param key valid configuration key
|
|
111
|
+
* @return any configuration value
|
|
112
|
+
*/
|
|
113
|
+
Store.prototype.getConfig = function (key) {
|
|
114
|
+
if (!this._config.has(key)) {
|
|
115
|
+
throw new Error("Invalid configuration item requested: " + key);
|
|
116
|
+
}
|
|
117
|
+
return this._config.get(key);
|
|
118
|
+
};
|
|
119
|
+
Object.defineProperty(Store.prototype, "placeholder", {
|
|
120
|
+
/**
|
|
121
|
+
* get the placeholder for a class instance
|
|
122
|
+
* @method placeholder
|
|
123
|
+
* @return {HTMLElement|null}
|
|
124
|
+
*/
|
|
125
|
+
get: function () {
|
|
126
|
+
return this._placeholder;
|
|
127
|
+
},
|
|
128
|
+
/**
|
|
129
|
+
* set the placeholder for a class instance
|
|
130
|
+
* @method placeholder
|
|
131
|
+
* @param {HTMLElement} placeholder
|
|
132
|
+
* @return {void}
|
|
133
|
+
*/
|
|
134
|
+
set: function (placeholder) {
|
|
135
|
+
if (!(placeholder instanceof HTMLElement) && placeholder !== null) {
|
|
136
|
+
throw new Error('A placeholder must be an html element or null.');
|
|
137
|
+
}
|
|
138
|
+
this._placeholder = placeholder;
|
|
139
|
+
},
|
|
140
|
+
enumerable: true,
|
|
141
|
+
configurable: true
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* set an data entry
|
|
145
|
+
* @method setData
|
|
146
|
+
* @param {string} key
|
|
147
|
+
* @param {any} value
|
|
148
|
+
* @return {void}
|
|
149
|
+
*/
|
|
150
|
+
Store.prototype.setData = function (key, value) {
|
|
151
|
+
if (typeof key !== 'string') {
|
|
152
|
+
throw new Error("The key must be a string.");
|
|
153
|
+
}
|
|
154
|
+
this._data.set(key, value);
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* get an data entry
|
|
158
|
+
* @method getData
|
|
159
|
+
* @param {string} key an existing key
|
|
160
|
+
* @return {any}
|
|
161
|
+
*/
|
|
162
|
+
Store.prototype.getData = function (key) {
|
|
163
|
+
if (typeof key !== 'string') {
|
|
164
|
+
throw new Error("The key must be a string.");
|
|
165
|
+
}
|
|
166
|
+
return this._data.get(key);
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* delete an data entry
|
|
170
|
+
* @method deleteData
|
|
171
|
+
* @param {string} key an existing key
|
|
172
|
+
* @return {boolean}
|
|
173
|
+
*/
|
|
174
|
+
Store.prototype.deleteData = function (key) {
|
|
175
|
+
if (typeof key !== 'string') {
|
|
176
|
+
throw new Error("The key must be a string.");
|
|
177
|
+
}
|
|
178
|
+
return this._data.delete(key);
|
|
179
|
+
};
|
|
180
|
+
return Store;
|
|
181
|
+
}());
|
|
182
|
+
function store (sortableElement) {
|
|
183
|
+
// if sortableElement is wrong type
|
|
184
|
+
if (!(sortableElement instanceof HTMLElement)) {
|
|
185
|
+
throw new Error('Please provide a sortable to the store function.');
|
|
186
|
+
}
|
|
187
|
+
// create new instance if not avilable
|
|
188
|
+
if (!stores.has(sortableElement)) {
|
|
189
|
+
stores.set(sortableElement, new Store());
|
|
190
|
+
}
|
|
191
|
+
// return instance
|
|
192
|
+
return stores.get(sortableElement);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* @param {Array|HTMLElement} element
|
|
197
|
+
* @param {Function} callback
|
|
198
|
+
* @param {string} event
|
|
199
|
+
*/
|
|
200
|
+
function addEventListener(element, eventName, callback) {
|
|
201
|
+
if (element instanceof Array) {
|
|
202
|
+
for (var i = 0; i < element.length; ++i) {
|
|
203
|
+
addEventListener(element[i], eventName, callback);
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
element.addEventListener(eventName, callback);
|
|
208
|
+
store(element).setData("event" + eventName, callback);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* @param {Array<HTMLElement>|HTMLElement} element
|
|
212
|
+
* @param {string} eventName
|
|
213
|
+
*/
|
|
214
|
+
function removeEventListener(element, eventName) {
|
|
215
|
+
if (element instanceof Array) {
|
|
216
|
+
for (var i = 0; i < element.length; ++i) {
|
|
217
|
+
removeEventListener(element[i], eventName);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
element.removeEventListener(eventName, store(element).getData("event" + eventName));
|
|
222
|
+
store(element).deleteData("event" + eventName);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {Array<HTMLElement>|HTMLElement} element
|
|
227
|
+
* @param {string} attribute
|
|
228
|
+
* @param {string} value
|
|
229
|
+
*/
|
|
230
|
+
function addAttribute(element, attribute, value) {
|
|
231
|
+
if (element instanceof Array) {
|
|
232
|
+
for (var i = 0; i < element.length; ++i) {
|
|
233
|
+
addAttribute(element[i], attribute, value);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
element.setAttribute(attribute, value);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* @param {Array|HTMLElement} element
|
|
241
|
+
* @param {string} attribute
|
|
242
|
+
*/
|
|
243
|
+
function removeAttribute(element, attribute) {
|
|
244
|
+
if (element instanceof Array) {
|
|
245
|
+
for (var i = 0; i < element.length; ++i) {
|
|
246
|
+
removeAttribute(element[i], attribute);
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
element.removeAttribute(attribute);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function offset (element) {
|
|
254
|
+
if (!element.parentElement || element.getClientRects().length === 0) {
|
|
255
|
+
throw new Error('target element must be part of the dom');
|
|
256
|
+
}
|
|
257
|
+
var rect = element.getClientRects()[0];
|
|
258
|
+
return {
|
|
259
|
+
left: rect.left + window.scrollX,
|
|
260
|
+
right: rect.right + window.scrollX,
|
|
261
|
+
top: rect.top + window.scrollY,
|
|
262
|
+
bottom: rect.bottom + window.scrollY
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function _debounce (func, wait) {
|
|
267
|
+
if (wait === void 0) { wait = 0; }
|
|
268
|
+
var timeout;
|
|
269
|
+
return function () {
|
|
270
|
+
var args = [];
|
|
271
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
272
|
+
args[_i - 0] = arguments[_i];
|
|
273
|
+
}
|
|
274
|
+
clearTimeout(timeout);
|
|
275
|
+
timeout = setTimeout(function () {
|
|
276
|
+
func.apply(void 0, args);
|
|
277
|
+
}, wait);
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function index (element, elementList) {
|
|
282
|
+
if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) {
|
|
283
|
+
throw new Error('You must provide an element and a list of elements.');
|
|
284
|
+
}
|
|
285
|
+
return Array.from(elementList).indexOf(element);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function isInDom (element) {
|
|
289
|
+
if (!(element instanceof HTMLElement)) {
|
|
290
|
+
throw new Error('Element is not a node element.');
|
|
291
|
+
}
|
|
292
|
+
return element.parentNode !== null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* eslint-env browser */
|
|
296
|
+
/**
|
|
297
|
+
* Insert node before or after target
|
|
298
|
+
* @param {HTMLElement} referenceNode - reference element
|
|
299
|
+
* @param {HTMLElement} newElement - element to be inserted
|
|
300
|
+
* @param {String} position - insert before or after reference element
|
|
301
|
+
*/
|
|
302
|
+
var insertNode = function (referenceNode, newElement, position) {
|
|
303
|
+
if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) {
|
|
304
|
+
throw new Error('target and element must be a node');
|
|
305
|
+
}
|
|
306
|
+
referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling));
|
|
307
|
+
};
|
|
308
|
+
/**
|
|
309
|
+
* Insert before target
|
|
310
|
+
* @param {HTMLElement} target
|
|
311
|
+
* @param {HTMLElement} element
|
|
312
|
+
*/
|
|
313
|
+
var insertBefore = function (target, element) { return insertNode(target, element, 'before'); };
|
|
314
|
+
/**
|
|
315
|
+
* Insert after target
|
|
316
|
+
* @param {HTMLElement} target
|
|
317
|
+
* @param {HTMLElement} element
|
|
318
|
+
*/
|
|
319
|
+
var insertAfter = function (target, element) { return insertNode(target, element, 'after'); };
|
|
320
|
+
|
|
321
|
+
function _serialize (sortableContainer, customItemSerializer, customContainerSerializer) {
|
|
322
|
+
if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; }
|
|
323
|
+
if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; }
|
|
324
|
+
// check for valid sortableContainer
|
|
325
|
+
if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) {
|
|
326
|
+
throw new Error('You need to provide a sortableContainer to be serialized.');
|
|
327
|
+
}
|
|
328
|
+
// check for valid serializers
|
|
329
|
+
if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') {
|
|
330
|
+
throw new Error('You need to provide a valid serializer for items and the container.');
|
|
331
|
+
}
|
|
332
|
+
// get options
|
|
333
|
+
var options = addData(sortableContainer, 'opts');
|
|
334
|
+
var item = options.items;
|
|
335
|
+
// serialize container
|
|
336
|
+
var items = _filter(sortableContainer.children, item);
|
|
337
|
+
var serializedItems = items.map(function (item) {
|
|
338
|
+
return {
|
|
339
|
+
parent: sortableContainer,
|
|
340
|
+
node: item,
|
|
341
|
+
html: item.outerHTML,
|
|
342
|
+
index: index(item, items)
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
// serialize container
|
|
346
|
+
var container = {
|
|
347
|
+
node: sortableContainer,
|
|
348
|
+
itemCount: serializedItems.length
|
|
349
|
+
};
|
|
350
|
+
return {
|
|
351
|
+
container: customContainerSerializer(container),
|
|
352
|
+
items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); })
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function _makePlaceholder (sortableElement, placeholder, placeholderClass) {
|
|
357
|
+
if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; }
|
|
358
|
+
if (!(sortableElement instanceof HTMLElement)) {
|
|
359
|
+
throw new Error('You must provide a valid element as a sortable.');
|
|
360
|
+
}
|
|
361
|
+
// if placeholder is not an element
|
|
362
|
+
if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) {
|
|
363
|
+
throw new Error('You must provide a valid element as a placeholder or set ot to undefined.');
|
|
364
|
+
}
|
|
365
|
+
// if no placeholder element is given
|
|
366
|
+
if (placeholder === undefined) {
|
|
367
|
+
if (['UL', 'OL'].includes(sortableElement.tagName)) {
|
|
368
|
+
placeholder = document.createElement('li');
|
|
369
|
+
}
|
|
370
|
+
else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) {
|
|
371
|
+
placeholder = document.createElement('tr');
|
|
372
|
+
// set colspan to always all rows, otherwise the item can only be dropped in first column
|
|
373
|
+
placeholder.innerHTML = '<td colspan="100"></td>';
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
placeholder = document.createElement('div');
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// add classes to placeholder
|
|
380
|
+
if (typeof placeholderClass === 'string') {
|
|
381
|
+
(_a = placeholder.classList).add.apply(_a, placeholderClass.split(' '));
|
|
382
|
+
}
|
|
383
|
+
return placeholder;
|
|
384
|
+
var _a;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function _getElementHeight (element) {
|
|
388
|
+
if (!(element instanceof HTMLElement)) {
|
|
389
|
+
throw new Error('You must provide a valid dom element');
|
|
390
|
+
}
|
|
391
|
+
// get calculated style of element
|
|
392
|
+
var style = window.getComputedStyle(element);
|
|
393
|
+
// pick applicable properties, convert to int and reduce by adding
|
|
394
|
+
return ['height', 'padding-top', 'padding-bottom']
|
|
395
|
+
.map(function (key) {
|
|
396
|
+
var int = parseInt(style.getPropertyValue(key), 10);
|
|
397
|
+
return isNaN(int) ? 0 : int;
|
|
398
|
+
})
|
|
399
|
+
.reduce(function (sum, value) { return sum + value; });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function _getHandles (items, selector) {
|
|
403
|
+
if (!(items instanceof Array)) {
|
|
404
|
+
throw new Error('You must provide a Array of HTMLElements to be filtered.');
|
|
405
|
+
}
|
|
406
|
+
if (typeof selector !== 'string') {
|
|
407
|
+
return items;
|
|
408
|
+
}
|
|
409
|
+
return items
|
|
410
|
+
.filter(function (item) {
|
|
411
|
+
return item.querySelector(selector) instanceof HTMLElement;
|
|
412
|
+
})
|
|
413
|
+
.map(function (item) {
|
|
414
|
+
return item.querySelector(selector);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* defaultDragImage returns the current item as dragged image
|
|
420
|
+
* @param {HTMLElement} draggedElement - the item that the user drags
|
|
421
|
+
* @param {object} elementOffset - an object with the offsets top, left, right & bottom
|
|
422
|
+
* @param {Event} event - the original drag event object
|
|
423
|
+
* @return {object} with element, posX and posY properties
|
|
424
|
+
*/
|
|
425
|
+
var defaultDragImage = function (draggedElement, elementOffset, event) {
|
|
426
|
+
return {
|
|
427
|
+
element: draggedElement,
|
|
428
|
+
posX: event.pageX - elementOffset.left,
|
|
429
|
+
posY: event.pageY - elementOffset.top
|
|
430
|
+
};
|
|
431
|
+
};
|
|
432
|
+
function setDragImage (event, draggedElement, customDragImage) {
|
|
433
|
+
// check if event is provided
|
|
434
|
+
if (!(event instanceof Event)) {
|
|
435
|
+
throw new Error('setDragImage requires a DragEvent as the first argument.');
|
|
436
|
+
}
|
|
437
|
+
// check if draggedElement is provided
|
|
438
|
+
if (!(draggedElement instanceof HTMLElement)) {
|
|
439
|
+
throw new Error('setDragImage requires the dragged element as the second argument.');
|
|
440
|
+
}
|
|
441
|
+
// set default function of none provided
|
|
442
|
+
if (!customDragImage) {
|
|
443
|
+
customDragImage = defaultDragImage;
|
|
444
|
+
}
|
|
445
|
+
// check if setDragImage method is available
|
|
446
|
+
if (event.dataTransfer && event.dataTransfer.setDragImage) {
|
|
447
|
+
// get the elements offset
|
|
448
|
+
var elementOffset = offset(draggedElement);
|
|
449
|
+
// get the dragImage
|
|
450
|
+
var dragImage = customDragImage(draggedElement, elementOffset, event);
|
|
451
|
+
// check if custom function returns correct values
|
|
452
|
+
if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') {
|
|
453
|
+
throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].');
|
|
454
|
+
}
|
|
455
|
+
// needs to be set for HTML5 drag & drop to work
|
|
456
|
+
event.dataTransfer.effectAllowed = 'copyMove';
|
|
457
|
+
// Firefox requires arbitrary content in setData for the drag & drop functionality to work
|
|
458
|
+
event.dataTransfer.setData('text/plain', 'arbitrary');
|
|
459
|
+
// set the drag image on the event
|
|
460
|
+
event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function _listsConnected (destination, origin) {
|
|
465
|
+
// check if valid sortable
|
|
466
|
+
if (destination.isSortable === true) {
|
|
467
|
+
var acceptFrom = store(destination).getConfig('acceptFrom');
|
|
468
|
+
// check if acceptFrom is valid
|
|
469
|
+
if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') {
|
|
470
|
+
throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.');
|
|
471
|
+
}
|
|
472
|
+
if (acceptFrom !== null) {
|
|
473
|
+
return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) {
|
|
474
|
+
return sel.length > 0 && origin.matches(sel);
|
|
475
|
+
}).length > 0;
|
|
476
|
+
}
|
|
477
|
+
// drop in same list
|
|
478
|
+
if (destination === origin) {
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
// check if lists are connected with connectWith
|
|
482
|
+
if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) {
|
|
483
|
+
return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith');
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
var defaultConfiguration = {
|
|
490
|
+
items: null,
|
|
491
|
+
// deprecated
|
|
492
|
+
connectWith: null,
|
|
493
|
+
// deprecated
|
|
494
|
+
disableIEFix: null,
|
|
495
|
+
acceptFrom: null,
|
|
496
|
+
copy: false,
|
|
497
|
+
placeholder: null,
|
|
498
|
+
placeholderClass: 'sortable-placeholder',
|
|
499
|
+
draggingClass: 'sortable-dragging',
|
|
500
|
+
hoverClass: false,
|
|
501
|
+
debounce: 0,
|
|
502
|
+
throttleTime: 100,
|
|
503
|
+
maxItems: 0,
|
|
504
|
+
itemSerializer: undefined,
|
|
505
|
+
containerSerializer: undefined,
|
|
506
|
+
customDragImage: null
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* make sure a function is only called once within the given amount of time
|
|
511
|
+
* @param {Function} fn the function to throttle
|
|
512
|
+
* @param {number} threshold time limit for throttling
|
|
513
|
+
*/
|
|
514
|
+
// must use function to keep this context
|
|
515
|
+
function _throttle (fn, threshold) {
|
|
516
|
+
var _this = this;
|
|
517
|
+
if (threshold === void 0) { threshold = 250; }
|
|
518
|
+
// check function
|
|
519
|
+
if (typeof fn !== 'function') {
|
|
520
|
+
throw new Error('You must provide a function as the first argument for throttle.');
|
|
521
|
+
}
|
|
522
|
+
// check threshold
|
|
523
|
+
if (typeof threshold !== 'number') {
|
|
524
|
+
throw new Error('You must provide a number as the second argument for throttle.');
|
|
525
|
+
}
|
|
526
|
+
var lastEventTimestamp = null;
|
|
527
|
+
return function () {
|
|
528
|
+
var args = [];
|
|
529
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
530
|
+
args[_i - 0] = arguments[_i];
|
|
531
|
+
}
|
|
532
|
+
var now = Date.now();
|
|
533
|
+
if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) {
|
|
534
|
+
lastEventTimestamp = now;
|
|
535
|
+
fn.apply(_this, args);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function enableHoverClass (sortableContainer, enable) {
|
|
541
|
+
if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {
|
|
542
|
+
var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' ');
|
|
543
|
+
// add class on hover
|
|
544
|
+
if (enable === true) {
|
|
545
|
+
addEventListener(sortableContainer, 'mousemove', _throttle(function (event) {
|
|
546
|
+
// check of no mouse button was pressed when mousemove started == no drag
|
|
547
|
+
if (event.buttons === 0) {
|
|
548
|
+
_filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
|
|
549
|
+
if (item !== event.target) {
|
|
550
|
+
(_a = item.classList).remove.apply(_a, hoverClasses_1);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
(_b = item.classList).add.apply(_b, hoverClasses_1);
|
|
554
|
+
}
|
|
555
|
+
var _a, _b;
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}, store(sortableContainer).getConfig('throttleTime')));
|
|
559
|
+
// remove class on leave
|
|
560
|
+
addEventListener(sortableContainer, 'mouseleave', function () {
|
|
561
|
+
_filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
|
|
562
|
+
(_a = item.classList).remove.apply(_a, hoverClasses_1);
|
|
563
|
+
var _a;
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
removeEventListener(sortableContainer, 'mousemove');
|
|
569
|
+
removeEventListener(sortableContainer, 'mouseleave');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* eslint-env browser */
|
|
575
|
+
/*
|
|
576
|
+
* variables global to the plugin
|
|
577
|
+
*/
|
|
578
|
+
var dragging;
|
|
579
|
+
var draggingHeight;
|
|
580
|
+
/*
|
|
581
|
+
* Keeps track of the initialy selected list, where 'dragstart' event was triggered
|
|
582
|
+
* It allows us to move the data in between individual Sortable List instances
|
|
583
|
+
*/
|
|
584
|
+
// Origin List - data from before any item was changed
|
|
585
|
+
var originContainer;
|
|
586
|
+
var originIndex;
|
|
587
|
+
var originElementIndex;
|
|
588
|
+
var originItemsBeforeUpdate;
|
|
589
|
+
// Destination List - data from before any item was changed
|
|
590
|
+
var destinationItemsBeforeUpdate;
|
|
591
|
+
/**
|
|
592
|
+
* remove event handlers from items
|
|
593
|
+
* @param {Array|NodeList} items
|
|
594
|
+
*/
|
|
595
|
+
var _removeItemEvents = function (items) {
|
|
596
|
+
removeEventListener(items, 'dragstart');
|
|
597
|
+
removeEventListener(items, 'dragend');
|
|
598
|
+
removeEventListener(items, 'dragover');
|
|
599
|
+
removeEventListener(items, 'dragenter');
|
|
600
|
+
removeEventListener(items, 'drop');
|
|
601
|
+
removeEventListener(items, 'mouseenter');
|
|
602
|
+
removeEventListener(items, 'mouseleave');
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* _getDragging returns the current element to drag or
|
|
606
|
+
* a copy of the element.
|
|
607
|
+
* Is Copy Active for sortable
|
|
608
|
+
* @param {HTMLElement} draggedItem - the item that the user drags
|
|
609
|
+
* @param {HTMLElement} sortable a single sortable
|
|
610
|
+
*/
|
|
611
|
+
var _getDragging = function (draggedItem, sortable) {
|
|
612
|
+
var ditem = draggedItem;
|
|
613
|
+
if (store(sortable).getConfig('copy') === true) {
|
|
614
|
+
ditem = draggedItem.cloneNode(true);
|
|
615
|
+
addAttribute(ditem, 'aria-copied', 'true');
|
|
616
|
+
draggedItem.parentElement.appendChild(ditem);
|
|
617
|
+
ditem.style.display = 'none';
|
|
618
|
+
ditem.oldDisplay = draggedItem.style.display;
|
|
619
|
+
}
|
|
620
|
+
return ditem;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* Remove data from sortable
|
|
624
|
+
* @param {HTMLElement} sortable a single sortable
|
|
625
|
+
*/
|
|
626
|
+
var _removeSortableData = function (sortable) {
|
|
627
|
+
removeData(sortable);
|
|
628
|
+
removeAttribute(sortable, 'aria-dropeffect');
|
|
629
|
+
};
|
|
630
|
+
/**
|
|
631
|
+
* Remove data from items
|
|
632
|
+
* @param {Array<HTMLElement>|HTMLElement} items
|
|
633
|
+
*/
|
|
634
|
+
var _removeItemData = function (items) {
|
|
635
|
+
removeAttribute(items, 'aria-grabbed');
|
|
636
|
+
removeAttribute(items, 'aria-copied');
|
|
637
|
+
removeAttribute(items, 'draggable');
|
|
638
|
+
removeAttribute(items, 'role');
|
|
639
|
+
};
|
|
640
|
+
/**
|
|
641
|
+
* find sortable from element. travels up parent element until found or null.
|
|
642
|
+
* @param {HTMLElement} element a single sortable
|
|
643
|
+
*/
|
|
644
|
+
function findSortable(element) {
|
|
645
|
+
while (element.isSortable !== true) {
|
|
646
|
+
element = element.parentElement;
|
|
647
|
+
}
|
|
648
|
+
return element;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Dragging event is on the sortable element. finds the top child that
|
|
652
|
+
* contains the element.
|
|
653
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
654
|
+
* @param {HTMLElement} element is that being dragged
|
|
655
|
+
*/
|
|
656
|
+
function findDragElement(sortableElement, element) {
|
|
657
|
+
var options = addData(sortableElement, 'opts');
|
|
658
|
+
var items = _filter(sortableElement.children, options.items);
|
|
659
|
+
var itemlist = items.filter(function (ele) {
|
|
660
|
+
return ele.contains(element);
|
|
661
|
+
});
|
|
662
|
+
return itemlist.length > 0 ? itemlist[0] : element;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Destroy the sortable
|
|
666
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
667
|
+
*/
|
|
668
|
+
var _destroySortable = function (sortableElement) {
|
|
669
|
+
var opts = addData(sortableElement, 'opts') || {};
|
|
670
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
671
|
+
var handles = _getHandles(items, opts.handle);
|
|
672
|
+
// remove event handlers & data from sortable
|
|
673
|
+
removeEventListener(sortableElement, 'dragover');
|
|
674
|
+
removeEventListener(sortableElement, 'dragenter');
|
|
675
|
+
removeEventListener(sortableElement, 'drop');
|
|
676
|
+
// remove event data from sortable
|
|
677
|
+
_removeSortableData(sortableElement);
|
|
678
|
+
// remove event handlers & data from items
|
|
679
|
+
removeEventListener(handles, 'mousedown');
|
|
680
|
+
_removeItemEvents(items);
|
|
681
|
+
_removeItemData(items);
|
|
682
|
+
};
|
|
683
|
+
/**
|
|
684
|
+
* Enable the sortable
|
|
685
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
686
|
+
*/
|
|
687
|
+
var _enableSortable = function (sortableElement) {
|
|
688
|
+
var opts = addData(sortableElement, 'opts');
|
|
689
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
690
|
+
var handles = _getHandles(items, opts.handle);
|
|
691
|
+
addAttribute(sortableElement, 'aria-dropeffect', 'move');
|
|
692
|
+
addData(sortableElement, '_disabled', 'false');
|
|
693
|
+
addAttribute(handles, 'draggable', 'true');
|
|
694
|
+
// @todo: remove this fix
|
|
695
|
+
// IE FIX for ghost
|
|
696
|
+
// can be disabled as it has the side effect that other events
|
|
697
|
+
// (e.g. click) will be ignored
|
|
698
|
+
if (opts.disableIEFix === false) {
|
|
699
|
+
var spanEl = (document || window.document).createElement('span');
|
|
700
|
+
if (typeof spanEl.dragDrop === 'function') {
|
|
701
|
+
addEventListener(handles, 'mousedown', function () {
|
|
702
|
+
if (items.indexOf(this) !== -1) {
|
|
703
|
+
this.dragDrop();
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
var parent = this.parentElement;
|
|
707
|
+
while (items.indexOf(parent) === -1) {
|
|
708
|
+
parent = parent.parentElement;
|
|
709
|
+
}
|
|
710
|
+
parent.dragDrop();
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
/**
|
|
717
|
+
* Disable the sortable
|
|
718
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
719
|
+
*/
|
|
720
|
+
var _disableSortable = function (sortableElement) {
|
|
721
|
+
var opts = addData(sortableElement, 'opts');
|
|
722
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
723
|
+
var handles = _getHandles(items, opts.handle);
|
|
724
|
+
addAttribute(sortableElement, 'aria-dropeffect', 'none');
|
|
725
|
+
addData(sortableElement, '_disabled', 'true');
|
|
726
|
+
addAttribute(handles, 'draggable', 'false');
|
|
727
|
+
removeEventListener(handles, 'mousedown');
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
* Reload the sortable
|
|
731
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
732
|
+
* @description events need to be removed to not be double bound
|
|
733
|
+
*/
|
|
734
|
+
var _reloadSortable = function (sortableElement) {
|
|
735
|
+
var opts = addData(sortableElement, 'opts');
|
|
736
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
737
|
+
var handles = _getHandles(items, opts.handle);
|
|
738
|
+
addData(sortableElement, '_disabled', 'false');
|
|
739
|
+
// remove event handlers from items
|
|
740
|
+
_removeItemEvents(items);
|
|
741
|
+
removeEventListener(handles, 'mousedown');
|
|
742
|
+
// remove event handlers from sortable
|
|
743
|
+
removeEventListener(sortableElement, 'dragover');
|
|
744
|
+
removeEventListener(sortableElement, 'dragenter');
|
|
745
|
+
removeEventListener(sortableElement, 'drop');
|
|
746
|
+
};
|
|
747
|
+
/**
|
|
748
|
+
* Public sortable object
|
|
749
|
+
* @param {Array|NodeList} sortableElements
|
|
750
|
+
* @param {object|string} options|method
|
|
751
|
+
*/
|
|
752
|
+
function sortable(sortableElements, options) {
|
|
753
|
+
// get method string to see if a method is called
|
|
754
|
+
var method = String(options);
|
|
755
|
+
// merge user options with defaultss
|
|
756
|
+
options = Object.assign({
|
|
757
|
+
connectWith: null,
|
|
758
|
+
acceptFrom: null,
|
|
759
|
+
copy: false,
|
|
760
|
+
placeholder: null,
|
|
761
|
+
disableIEFix: null,
|
|
762
|
+
placeholderClass: 'sortable-placeholder',
|
|
763
|
+
draggingClass: 'sortable-dragging',
|
|
764
|
+
hoverClass: false,
|
|
765
|
+
debounce: 0,
|
|
766
|
+
maxItems: 0,
|
|
767
|
+
itemSerializer: undefined,
|
|
768
|
+
containerSerializer: undefined,
|
|
769
|
+
customDragImage: null,
|
|
770
|
+
items: null
|
|
771
|
+
}, (typeof options === 'object') ? options : {});
|
|
772
|
+
// check if the user provided a selector instead of an element
|
|
773
|
+
if (typeof sortableElements === 'string') {
|
|
774
|
+
sortableElements = document.querySelectorAll(sortableElements);
|
|
775
|
+
}
|
|
776
|
+
// if the user provided an element, return it in an array to keep the return value consistant
|
|
777
|
+
if (sortableElements instanceof HTMLElement) {
|
|
778
|
+
sortableElements = [sortableElements];
|
|
779
|
+
}
|
|
780
|
+
sortableElements = Array.prototype.slice.call(sortableElements);
|
|
781
|
+
if (/serialize/.test(method)) {
|
|
782
|
+
return sortableElements.map(function (sortableContainer) {
|
|
783
|
+
var opts = addData(sortableContainer, 'opts');
|
|
784
|
+
return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
sortableElements.forEach(function (sortableElement) {
|
|
788
|
+
if (/enable|disable|destroy/.test(method)) {
|
|
789
|
+
return sortable[method](sortableElement);
|
|
790
|
+
}
|
|
791
|
+
// log deprecation
|
|
792
|
+
['connectWith', 'disableIEFix'].forEach(function (configKey) {
|
|
793
|
+
if (options.hasOwnProperty(configKey) && options[configKey] !== null) {
|
|
794
|
+
console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating.");
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
// merge options with default options
|
|
798
|
+
options = Object.assign({}, defaultConfiguration, options);
|
|
799
|
+
// init data store for sortable
|
|
800
|
+
store(sortableElement).config = options;
|
|
801
|
+
// get options & set options on sortable
|
|
802
|
+
options = addData(sortableElement, 'opts') || options;
|
|
803
|
+
addData(sortableElement, 'opts', options);
|
|
804
|
+
// property to define as sortable
|
|
805
|
+
sortableElement.isSortable = true;
|
|
806
|
+
// reset sortable
|
|
807
|
+
_reloadSortable(sortableElement);
|
|
808
|
+
// initialize
|
|
809
|
+
var listItems = _filter(sortableElement.children, options.items);
|
|
810
|
+
// create element if user defined a placeholder element as a string
|
|
811
|
+
var customPlaceholder;
|
|
812
|
+
if (options.placeholder !== null && options.placeholder !== undefined) {
|
|
813
|
+
var tempContainer = document.createElement(sortableElement.tagName);
|
|
814
|
+
tempContainer.innerHTML = options.placeholder;
|
|
815
|
+
customPlaceholder = tempContainer.children[0];
|
|
816
|
+
}
|
|
817
|
+
// add placeholder
|
|
818
|
+
store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass);
|
|
819
|
+
addData(sortableElement, 'items', options.items);
|
|
820
|
+
if (options.acceptFrom) {
|
|
821
|
+
addData(sortableElement, 'acceptFrom', options.acceptFrom);
|
|
822
|
+
}
|
|
823
|
+
else if (options.connectWith) {
|
|
824
|
+
addData(sortableElement, 'connectWith', options.connectWith);
|
|
825
|
+
}
|
|
826
|
+
_enableSortable(sortableElement);
|
|
827
|
+
addAttribute(listItems, 'role', 'option');
|
|
828
|
+
addAttribute(listItems, 'aria-grabbed', 'false');
|
|
829
|
+
// enable hover class
|
|
830
|
+
enableHoverClass(sortableElement, true);
|
|
831
|
+
/*
|
|
832
|
+
Handle drag events on draggable items
|
|
833
|
+
Handle is set at the sortableElement level as it will bubble up
|
|
834
|
+
from the item
|
|
835
|
+
*/
|
|
836
|
+
addEventListener(sortableElement, 'dragstart', function (e) {
|
|
837
|
+
// ignore dragstart events
|
|
838
|
+
if (e.target.isSortable === true) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
e.stopImmediatePropagation();
|
|
842
|
+
if ((options.handle && !e.target.matches(options.handle)) || e.target.getAttribute('draggable') === 'false') {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
var sortableContainer = findSortable(e.target);
|
|
846
|
+
var dragItem = findDragElement(sortableContainer, e.target);
|
|
847
|
+
// grab values
|
|
848
|
+
originItemsBeforeUpdate = _filter(sortableContainer.children, options.items);
|
|
849
|
+
originIndex = originItemsBeforeUpdate.indexOf(dragItem);
|
|
850
|
+
originElementIndex = index(dragItem, sortableContainer.children);
|
|
851
|
+
originContainer = sortableContainer;
|
|
852
|
+
// add transparent clone or other ghost to cursor
|
|
853
|
+
setDragImage(e, dragItem, options.customDragImage);
|
|
854
|
+
// cache selsection & add attr for dragging
|
|
855
|
+
draggingHeight = _getElementHeight(dragItem);
|
|
856
|
+
dragItem.classList.add(options.draggingClass);
|
|
857
|
+
dragging = _getDragging(dragItem, sortableContainer);
|
|
858
|
+
addAttribute(dragging, 'aria-grabbed', 'true');
|
|
859
|
+
// dispatch sortstart event on each element in group
|
|
860
|
+
sortableContainer.dispatchEvent(new CustomEvent('sortstart', {
|
|
861
|
+
detail: {
|
|
862
|
+
origin: {
|
|
863
|
+
elementIndex: originElementIndex,
|
|
864
|
+
index: originIndex,
|
|
865
|
+
container: originContainer
|
|
866
|
+
},
|
|
867
|
+
item: dragging
|
|
868
|
+
}
|
|
869
|
+
}));
|
|
870
|
+
});
|
|
871
|
+
/*
|
|
872
|
+
We are capturing targetSortable before modifications with 'dragenter' event
|
|
873
|
+
*/
|
|
874
|
+
addEventListener(sortableElement, 'dragenter', function (e) {
|
|
875
|
+
if (e.target.isSortable === true) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
var sortableContainer = findSortable(e.target);
|
|
879
|
+
destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items'))
|
|
880
|
+
.filter(function (item) { return item !== store(sortableElement).placeholder; });
|
|
881
|
+
});
|
|
882
|
+
/*
|
|
883
|
+
* Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend
|
|
884
|
+
* Fires each time dragEvent end, or ESC pressed
|
|
885
|
+
* We are using it to clean up any draggable elements and placeholders
|
|
886
|
+
*/
|
|
887
|
+
addEventListener(sortableElement, 'dragend', function (e) {
|
|
888
|
+
if (!dragging) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
dragging.classList.remove(options.draggingClass);
|
|
892
|
+
addAttribute(dragging, 'aria-grabbed', 'false');
|
|
893
|
+
if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') {
|
|
894
|
+
dragging.remove();
|
|
895
|
+
}
|
|
896
|
+
dragging.style.display = dragging.oldDisplay;
|
|
897
|
+
delete dragging.oldDisplay;
|
|
898
|
+
var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; })
|
|
899
|
+
.filter(function (placeholder) { return placeholder instanceof HTMLElement; })
|
|
900
|
+
.filter(isInDom)[0];
|
|
901
|
+
if (visiblePlaceholder) {
|
|
902
|
+
visiblePlaceholder.remove();
|
|
903
|
+
}
|
|
904
|
+
// dispatch sortstart event on each element in group
|
|
905
|
+
sortableElement.dispatchEvent(new CustomEvent('sortstop', {
|
|
906
|
+
detail: {
|
|
907
|
+
origin: {
|
|
908
|
+
elementIndex: originElementIndex,
|
|
909
|
+
index: originIndex,
|
|
910
|
+
container: originContainer
|
|
911
|
+
},
|
|
912
|
+
item: dragging
|
|
913
|
+
}
|
|
914
|
+
}));
|
|
915
|
+
dragging = null;
|
|
916
|
+
draggingHeight = null;
|
|
917
|
+
});
|
|
918
|
+
/*
|
|
919
|
+
* Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop
|
|
920
|
+
* Fires when valid drop target area is hit
|
|
921
|
+
*/
|
|
922
|
+
addEventListener(sortableElement, 'drop', function (e) {
|
|
923
|
+
if (!_listsConnected(sortableElement, dragging.parentElement)) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
e.preventDefault();
|
|
927
|
+
e.stopPropagation();
|
|
928
|
+
addData(dragging, 'dropped', 'true');
|
|
929
|
+
// get the one placeholder that is currently visible
|
|
930
|
+
var visiblePlaceholder = Array.from(stores.values()).map(function (data) {
|
|
931
|
+
return data.placeholder;
|
|
932
|
+
})
|
|
933
|
+
.filter(function (placeholder) { return placeholder instanceof HTMLElement; })
|
|
934
|
+
.filter(isInDom)[0];
|
|
935
|
+
// attach element after placeholder
|
|
936
|
+
insertAfter(visiblePlaceholder, dragging);
|
|
937
|
+
// remove placeholder from dom
|
|
938
|
+
visiblePlaceholder.remove();
|
|
939
|
+
/*
|
|
940
|
+
* Fires Custom Event - 'sortstop'
|
|
941
|
+
*/
|
|
942
|
+
sortableElement.dispatchEvent(new CustomEvent('sortstop', {
|
|
943
|
+
detail: {
|
|
944
|
+
origin: {
|
|
945
|
+
elementIndex: originElementIndex,
|
|
946
|
+
index: originIndex,
|
|
947
|
+
container: originContainer
|
|
948
|
+
},
|
|
949
|
+
item: dragging
|
|
950
|
+
}
|
|
951
|
+
}));
|
|
952
|
+
var placeholder = store(sortableElement).placeholder;
|
|
953
|
+
var originItems = _filter(originContainer.children, options.items)
|
|
954
|
+
.filter(function (item) { return item !== placeholder; });
|
|
955
|
+
var destinationContainer = this.isSortable === true ? this : this.parentElement;
|
|
956
|
+
var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items'))
|
|
957
|
+
.filter(function (item) { return item !== placeholder; });
|
|
958
|
+
var destinationElementIndex = index(dragging, Array.from(dragging.parentElement.children)
|
|
959
|
+
.filter(function (item) { return item !== placeholder; }));
|
|
960
|
+
var destinationIndex = index(dragging, destinationItems);
|
|
961
|
+
/*
|
|
962
|
+
* When a list item changed container lists or index within a list
|
|
963
|
+
* Fires Custom Event - 'sortupdate'
|
|
964
|
+
*/
|
|
965
|
+
if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) {
|
|
966
|
+
sortableElement.dispatchEvent(new CustomEvent('sortupdate', {
|
|
967
|
+
detail: {
|
|
968
|
+
origin: {
|
|
969
|
+
elementIndex: originElementIndex,
|
|
970
|
+
index: originIndex,
|
|
971
|
+
container: originContainer,
|
|
972
|
+
itemsBeforeUpdate: originItemsBeforeUpdate,
|
|
973
|
+
items: originItems
|
|
974
|
+
},
|
|
975
|
+
destination: {
|
|
976
|
+
index: destinationIndex,
|
|
977
|
+
elementIndex: destinationElementIndex,
|
|
978
|
+
container: destinationContainer,
|
|
979
|
+
itemsBeforeUpdate: destinationItemsBeforeUpdate,
|
|
980
|
+
items: destinationItems
|
|
981
|
+
},
|
|
982
|
+
item: dragging
|
|
983
|
+
}
|
|
984
|
+
}));
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) {
|
|
988
|
+
if (!dragging) {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
// set placeholder height if forcePlaceholderSize option is set
|
|
992
|
+
if (options.forcePlaceholderSize) {
|
|
993
|
+
store(sortableElement).placeholder.style.height = draggingHeight + 'px';
|
|
994
|
+
}
|
|
995
|
+
// if element the draggedItem is dragged onto is within the array of all elements in list
|
|
996
|
+
// (not only items, but also disabled, etc.)
|
|
997
|
+
if (Array.from(sortableElement.children).indexOf(element) > -1) {
|
|
998
|
+
var thisHeight = _getElementHeight(element);
|
|
999
|
+
var placeholderIndex = index(store(sortableElement).placeholder, element.parentElement.children);
|
|
1000
|
+
var thisIndex = index(element, element.parentElement.children);
|
|
1001
|
+
// Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering
|
|
1002
|
+
if (thisHeight > draggingHeight) {
|
|
1003
|
+
// Dead zone?
|
|
1004
|
+
var deadZone = thisHeight - draggingHeight;
|
|
1005
|
+
var offsetTop = offset(element).top;
|
|
1006
|
+
if (placeholderIndex < thisIndex && pageY < offsetTop) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if (placeholderIndex > thisIndex &&
|
|
1010
|
+
pageY > offsetTop + thisHeight - deadZone) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (dragging.oldDisplay === undefined) {
|
|
1015
|
+
dragging.oldDisplay = dragging.style.display;
|
|
1016
|
+
}
|
|
1017
|
+
if (dragging.style.display !== 'none') {
|
|
1018
|
+
dragging.style.display = 'none';
|
|
1019
|
+
}
|
|
1020
|
+
// To avoid flicker, determine where to position the placeholder
|
|
1021
|
+
// based on where the mouse pointer is relative to the elements
|
|
1022
|
+
// vertical center.
|
|
1023
|
+
var placeAfter = false;
|
|
1024
|
+
try {
|
|
1025
|
+
var elementMiddle = offset(element).top + element.offsetHeight / 2;
|
|
1026
|
+
placeAfter = pageY >= elementMiddle;
|
|
1027
|
+
}
|
|
1028
|
+
catch (e) {
|
|
1029
|
+
placeAfter = placeholderIndex < thisIndex;
|
|
1030
|
+
}
|
|
1031
|
+
if (placeAfter) {
|
|
1032
|
+
insertAfter(element, store(sortableElement).placeholder);
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
insertBefore(element, store(sortableElement).placeholder);
|
|
1036
|
+
}
|
|
1037
|
+
// get placeholders from all stores & remove all but current one
|
|
1038
|
+
Array.from(stores.values())
|
|
1039
|
+
.filter(function (data) { return data.placeholder !== undefined; })
|
|
1040
|
+
.forEach(function (data) {
|
|
1041
|
+
if (data.placeholder !== store(sortableElement).placeholder) {
|
|
1042
|
+
data.placeholder.remove();
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
// get all placeholders from store
|
|
1048
|
+
var placeholders = Array.from(stores.values())
|
|
1049
|
+
.filter(function (data) { return data.placeholder !== undefined; })
|
|
1050
|
+
.map(function (data) {
|
|
1051
|
+
return data.placeholder;
|
|
1052
|
+
});
|
|
1053
|
+
// check if element is not in placeholders
|
|
1054
|
+
if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) {
|
|
1055
|
+
placeholders.forEach(function (element) { return element.remove(); });
|
|
1056
|
+
element.appendChild(store(sortableElement).placeholder);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}, options.debounce);
|
|
1060
|
+
// Handle dragover and dragenter events on draggable items
|
|
1061
|
+
var onDragOverEnter = function (e) {
|
|
1062
|
+
var element = e.target;
|
|
1063
|
+
var sortableElement = element.isSortable === true ? element : findSortable(element);
|
|
1064
|
+
element = findDragElement(sortableElement, element);
|
|
1065
|
+
if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
var options = addData(sortableElement, 'opts');
|
|
1069
|
+
if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
e.preventDefault();
|
|
1073
|
+
e.stopPropagation();
|
|
1074
|
+
e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move';
|
|
1075
|
+
debouncedDragOverEnter(sortableElement, element, e.pageY);
|
|
1076
|
+
};
|
|
1077
|
+
addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter);
|
|
1078
|
+
addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter);
|
|
1079
|
+
});
|
|
1080
|
+
return sortableElements;
|
|
1081
|
+
}
|
|
1082
|
+
sortable.destroy = function (sortableElement) {
|
|
1083
|
+
_destroySortable(sortableElement);
|
|
1084
|
+
};
|
|
1085
|
+
sortable.enable = function (sortableElement) {
|
|
1086
|
+
_enableSortable(sortableElement);
|
|
1087
|
+
};
|
|
1088
|
+
sortable.disable = function (sortableElement) {
|
|
1089
|
+
_disableSortable(sortableElement);
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
return sortable;
|
|
1093
|
+
|
|
1094
|
+
}());
|