spina-admin-journal 0.1.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/MIT-LICENSE +20 -0
- data/README.md +99 -0
- data/Rakefile +36 -0
- data/app/assets/config/spina_admin_journal_manifest.js +3 -0
- data/app/assets/javascripts/spina/admin/journal/application.es6 +72 -0
- data/app/assets/stylesheets/spina/admin/journal/application.css +27 -0
- data/app/controllers/spina/admin/journal/application_controller.rb +26 -0
- data/app/controllers/spina/admin/journal/articles_controller.rb +125 -0
- data/app/controllers/spina/admin/journal/authors_controller.rb +83 -0
- data/app/controllers/spina/admin/journal/institutions_controller.rb +71 -0
- data/app/controllers/spina/admin/journal/issues_controller.rb +129 -0
- data/app/controllers/spina/admin/journal/journals_controller.rb +73 -0
- data/app/controllers/spina/admin/journal/volumes_controller.rb +79 -0
- data/app/models/spina/admin/journal.rb +11 -0
- data/app/models/spina/admin/journal/affiliation.rb +62 -0
- data/app/models/spina/admin/journal/article.rb +55 -0
- data/app/models/spina/admin/journal/author.rb +47 -0
- data/app/models/spina/admin/journal/authorship.rb +19 -0
- data/app/models/spina/admin/journal/institution.rb +32 -0
- data/app/models/spina/admin/journal/issue.rb +49 -0
- data/app/models/spina/admin/journal/journal.rb +50 -0
- data/app/models/spina/admin/journal/volume.rb +37 -0
- data/app/validators/spina/admin/journal/uri_validator.rb +24 -0
- data/app/views/layouts/spina/admin/journal/articles.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/authors.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/institutions.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/issues.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/journals.html.haml +10 -0
- data/app/views/layouts/spina/admin/journal/volumes.html.haml +10 -0
- data/app/views/spina/admin/hooks/journal/_head.html.haml +2 -0
- data/app/views/spina/admin/hooks/journal/_primary_navigation.html.haml +27 -0
- data/app/views/spina/admin/hooks/journal/_website_secondary_navigation.html.haml +0 -0
- data/app/views/spina/admin/journal/affiliations/_affiliation.html.haml +8 -0
- data/app/views/spina/admin/journal/application/_empty_list.html.haml +3 -0
- data/app/views/spina/admin/journal/articles/_article.html.haml +10 -0
- data/app/views/spina/admin/journal/articles/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/articles/_form_authors.html.haml +10 -0
- data/app/views/spina/admin/journal/articles/_form_details.html.haml +52 -0
- data/app/views/spina/admin/journal/articles/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/articles/index.html.haml +19 -0
- data/app/views/spina/admin/journal/articles/new.html.haml +1 -0
- data/app/views/spina/admin/journal/authors/_author.html.haml +8 -0
- data/app/views/spina/admin/journal/authors/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/authors/_form_affiliation.html.haml +20 -0
- data/app/views/spina/admin/journal/authors/_form_articles.html.haml +18 -0
- data/app/views/spina/admin/journal/authors/_form_details.html.haml +6 -0
- data/app/views/spina/admin/journal/authors/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/authors/index.html.haml +17 -0
- data/app/views/spina/admin/journal/authors/new.html.haml +1 -0
- data/app/views/spina/admin/journal/institutions/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/institutions/_form_details.html.haml +9 -0
- data/app/views/spina/admin/journal/institutions/_form_view_affiliations.html.haml +10 -0
- data/app/views/spina/admin/journal/institutions/_institution.html.haml +9 -0
- data/app/views/spina/admin/journal/institutions/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/institutions/index.html.haml +16 -0
- data/app/views/spina/admin/journal/institutions/new.html.haml +1 -0
- data/app/views/spina/admin/journal/issues/_form.html.haml +29 -0
- data/app/views/spina/admin/journal/issues/_form_articles.html.haml +14 -0
- data/app/views/spina/admin/journal/issues/_form_details.html.haml +32 -0
- data/app/views/spina/admin/journal/issues/_issue.html.haml +9 -0
- data/app/views/spina/admin/journal/issues/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/issues/index.html.haml +18 -0
- data/app/views/spina/admin/journal/issues/new.html.haml +1 -0
- data/app/views/spina/admin/journal/journals/_form.html.haml +35 -0
- data/app/views/spina/admin/journal/journals/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/journals/index.html.haml +27 -0
- data/app/views/spina/admin/journal/journals/new.html.haml +1 -0
- data/app/views/spina/admin/journal/volumes/_form.html.haml +15 -0
- data/app/views/spina/admin/journal/volumes/_form_details.html.haml +10 -0
- data/app/views/spina/admin/journal/volumes/_form_issues.html.haml +13 -0
- data/app/views/spina/admin/journal/volumes/_volume.html.haml +7 -0
- data/app/views/spina/admin/journal/volumes/edit.html.haml +1 -0
- data/app/views/spina/admin/journal/volumes/index.html.haml +17 -0
- data/app/views/spina/admin/journal/volumes/new.html.haml +1 -0
- data/config/locales/en.yml +184 -0
- data/config/routes.rb +21 -0
- data/db/migrate/20201216152147_create_spina_admin_journal_journals.rb +13 -0
- data/db/migrate/20201216153822_create_spina_admin_journal_volumes.rb +13 -0
- data/db/migrate/20201216155113_create_spina_admin_journal_issues.rb +15 -0
- data/db/migrate/20201216161122_create_spina_admin_journal_articles.rb +16 -0
- data/db/migrate/20201216205633_create_spina_admin_journal_institutions.rb +11 -0
- data/db/migrate/20201216211224_create_spina_admin_journal_authors.rb +7 -0
- data/db/migrate/20201216221146_create_spina_admin_journal_affiliations.rb +15 -0
- data/db/migrate/20201216230816_create_spina_admin_journal_authorships.rb +14 -0
- data/db/migrate/20201231165231_create_spina_admin_journal_parts.rb +12 -0
- data/db/migrate/20210424123450_add_json_attributes_to_spina_admin_journal_journals.rb +7 -0
- data/db/migrate/20210424123521_add_json_attributes_to_spina_admin_journal_issues.rb +7 -0
- data/db/migrate/20210424123555_add_json_attributes_to_spina_admin_journal_articles.rb +7 -0
- data/lib/spina/admin/journal.rb +18 -0
- data/lib/spina/admin/journal/engine.rb +17 -0
- data/lib/spina/admin/journal/version.rb +9 -0
- data/lib/tasks/spina/admin/journal_tasks.rake +5 -0
- data/vendor/assets/javascripts/spina/admin/journal/html5sortable.js +1295 -0
- metadata +388 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spina'
|
|
4
|
+
require 'spina/admin/journal/engine'
|
|
5
|
+
require 'rails-i18n'
|
|
6
|
+
|
|
7
|
+
# Spina CMS.
|
|
8
|
+
# @see https://www.spinacms.com Spina website
|
|
9
|
+
module Spina
|
|
10
|
+
module Admin
|
|
11
|
+
# Journal management plugin for Spina.
|
|
12
|
+
module Journal
|
|
13
|
+
def self.table_name_prefix
|
|
14
|
+
'spina_admin_journal_'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spina
|
|
4
|
+
module Admin
|
|
5
|
+
module Journal
|
|
6
|
+
# Registers the plugin with Spina.
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
config.before_initialize do
|
|
9
|
+
::Spina::Plugin.register do |plugin|
|
|
10
|
+
plugin.name = 'journal'
|
|
11
|
+
plugin.namespace = 'journal'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,1295 @@
|
|
|
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
|
+
/* eslint-env browser */
|
|
40
|
+
/**
|
|
41
|
+
* Filter only wanted nodes
|
|
42
|
+
* @param {NodeList|HTMLCollection|Array} nodes
|
|
43
|
+
* @param {String} selector
|
|
44
|
+
* @returns {Array}
|
|
45
|
+
*/
|
|
46
|
+
var _filter = (function (nodes, selector) {
|
|
47
|
+
if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) {
|
|
48
|
+
throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.');
|
|
49
|
+
}
|
|
50
|
+
if (typeof selector !== 'string') {
|
|
51
|
+
return Array.from(nodes);
|
|
52
|
+
}
|
|
53
|
+
return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/* eslint-env browser */
|
|
57
|
+
var stores = new Map();
|
|
58
|
+
/**
|
|
59
|
+
* Stores data & configurations per Sortable
|
|
60
|
+
* @param {Object} config
|
|
61
|
+
*/
|
|
62
|
+
var Store = /** @class */ (function () {
|
|
63
|
+
function Store() {
|
|
64
|
+
this._config = new Map(); // eslint-disable-line no-undef
|
|
65
|
+
this._placeholder = undefined; // eslint-disable-line no-undef
|
|
66
|
+
this._data = new Map(); // eslint-disable-line no-undef
|
|
67
|
+
}
|
|
68
|
+
Object.defineProperty(Store.prototype, "config", {
|
|
69
|
+
/**
|
|
70
|
+
* get the configuration map of a class instance
|
|
71
|
+
* @method config
|
|
72
|
+
* @return {object}
|
|
73
|
+
*/
|
|
74
|
+
get: function () {
|
|
75
|
+
// transform Map to object
|
|
76
|
+
var config = {};
|
|
77
|
+
this._config.forEach(function (value, key) {
|
|
78
|
+
config[key] = value;
|
|
79
|
+
});
|
|
80
|
+
// return object
|
|
81
|
+
return config;
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* set the configuration of a class instance
|
|
85
|
+
* @method config
|
|
86
|
+
* @param {object} config object of configurations
|
|
87
|
+
*/
|
|
88
|
+
set: function (config) {
|
|
89
|
+
if (typeof config !== 'object') {
|
|
90
|
+
throw new Error('You must provide a valid configuration object to the config setter.');
|
|
91
|
+
}
|
|
92
|
+
// combine config with default
|
|
93
|
+
var mergedConfig = Object.assign({}, config);
|
|
94
|
+
// add config to map
|
|
95
|
+
this._config = new Map(Object.entries(mergedConfig));
|
|
96
|
+
},
|
|
97
|
+
enumerable: false,
|
|
98
|
+
configurable: true
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* set individual configuration of a class instance
|
|
102
|
+
* @method setConfig
|
|
103
|
+
* @param key valid configuration key
|
|
104
|
+
* @param value any value
|
|
105
|
+
* @return void
|
|
106
|
+
*/
|
|
107
|
+
Store.prototype.setConfig = function (key, value) {
|
|
108
|
+
if (!this._config.has(key)) {
|
|
109
|
+
throw new Error("Trying to set invalid configuration item: " + key);
|
|
110
|
+
}
|
|
111
|
+
// set config
|
|
112
|
+
this._config.set(key, value);
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* get an individual configuration of a class instance
|
|
116
|
+
* @method getConfig
|
|
117
|
+
* @param key valid configuration key
|
|
118
|
+
* @return any configuration value
|
|
119
|
+
*/
|
|
120
|
+
Store.prototype.getConfig = function (key) {
|
|
121
|
+
if (!this._config.has(key)) {
|
|
122
|
+
throw new Error("Invalid configuration item requested: " + key);
|
|
123
|
+
}
|
|
124
|
+
return this._config.get(key);
|
|
125
|
+
};
|
|
126
|
+
Object.defineProperty(Store.prototype, "placeholder", {
|
|
127
|
+
/**
|
|
128
|
+
* get the placeholder for a class instance
|
|
129
|
+
* @method placeholder
|
|
130
|
+
* @return {HTMLElement|null}
|
|
131
|
+
*/
|
|
132
|
+
get: function () {
|
|
133
|
+
return this._placeholder;
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* set the placeholder for a class instance
|
|
137
|
+
* @method placeholder
|
|
138
|
+
* @param {HTMLElement} placeholder
|
|
139
|
+
* @return {void}
|
|
140
|
+
*/
|
|
141
|
+
set: function (placeholder) {
|
|
142
|
+
if (!(placeholder instanceof HTMLElement) && placeholder !== null) {
|
|
143
|
+
throw new Error('A placeholder must be an html element or null.');
|
|
144
|
+
}
|
|
145
|
+
this._placeholder = placeholder;
|
|
146
|
+
},
|
|
147
|
+
enumerable: false,
|
|
148
|
+
configurable: true
|
|
149
|
+
});
|
|
150
|
+
/**
|
|
151
|
+
* set an data entry
|
|
152
|
+
* @method setData
|
|
153
|
+
* @param {string} key
|
|
154
|
+
* @param {any} value
|
|
155
|
+
* @return {void}
|
|
156
|
+
*/
|
|
157
|
+
Store.prototype.setData = function (key, value) {
|
|
158
|
+
if (typeof key !== 'string') {
|
|
159
|
+
throw new Error('The key must be a string.');
|
|
160
|
+
}
|
|
161
|
+
this._data.set(key, value);
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* get an data entry
|
|
165
|
+
* @method getData
|
|
166
|
+
* @param {string} key an existing key
|
|
167
|
+
* @return {any}
|
|
168
|
+
*/
|
|
169
|
+
Store.prototype.getData = function (key) {
|
|
170
|
+
if (typeof key !== 'string') {
|
|
171
|
+
throw new Error('The key must be a string.');
|
|
172
|
+
}
|
|
173
|
+
return this._data.get(key);
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* delete an data entry
|
|
177
|
+
* @method deleteData
|
|
178
|
+
* @param {string} key an existing key
|
|
179
|
+
* @return {boolean}
|
|
180
|
+
*/
|
|
181
|
+
Store.prototype.deleteData = function (key) {
|
|
182
|
+
if (typeof key !== 'string') {
|
|
183
|
+
throw new Error('The key must be a string.');
|
|
184
|
+
}
|
|
185
|
+
return this._data.delete(key);
|
|
186
|
+
};
|
|
187
|
+
return Store;
|
|
188
|
+
}());
|
|
189
|
+
/**
|
|
190
|
+
* @param {HTMLElement} sortableElement
|
|
191
|
+
* @returns {Class: Store}
|
|
192
|
+
*/
|
|
193
|
+
var store = (function (sortableElement) {
|
|
194
|
+
// if sortableElement is wrong type
|
|
195
|
+
if (!(sortableElement instanceof HTMLElement)) {
|
|
196
|
+
throw new Error('Please provide a sortable to the store function.');
|
|
197
|
+
}
|
|
198
|
+
// create new instance if not avilable
|
|
199
|
+
if (!stores.has(sortableElement)) {
|
|
200
|
+
stores.set(sortableElement, new Store());
|
|
201
|
+
}
|
|
202
|
+
// return instance
|
|
203
|
+
return stores.get(sortableElement);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {Array|HTMLElement} element
|
|
208
|
+
* @param {Function} callback
|
|
209
|
+
* @param {string} event
|
|
210
|
+
*/
|
|
211
|
+
function addEventListener(element, eventName, callback) {
|
|
212
|
+
if (element instanceof Array) {
|
|
213
|
+
for (var i = 0; i < element.length; ++i) {
|
|
214
|
+
addEventListener(element[i], eventName, callback);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
element.addEventListener(eventName, callback);
|
|
219
|
+
store(element).setData("event" + eventName, callback);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* @param {Array<HTMLElement>|HTMLElement} element
|
|
223
|
+
* @param {string} eventName
|
|
224
|
+
*/
|
|
225
|
+
function removeEventListener(element, eventName) {
|
|
226
|
+
if (element instanceof Array) {
|
|
227
|
+
for (var i = 0; i < element.length; ++i) {
|
|
228
|
+
removeEventListener(element[i], eventName);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
element.removeEventListener(eventName, store(element).getData("event" + eventName));
|
|
233
|
+
store(element).deleteData("event" + eventName);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @param {Array<HTMLElement>|HTMLElement} element
|
|
238
|
+
* @param {string} attribute
|
|
239
|
+
* @param {string} value
|
|
240
|
+
*/
|
|
241
|
+
function addAttribute(element, attribute, value) {
|
|
242
|
+
if (element instanceof Array) {
|
|
243
|
+
for (var i = 0; i < element.length; ++i) {
|
|
244
|
+
addAttribute(element[i], attribute, value);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
element.setAttribute(attribute, value);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* @param {Array|HTMLElement} element
|
|
252
|
+
* @param {string} attribute
|
|
253
|
+
*/
|
|
254
|
+
function removeAttribute(element, attribute) {
|
|
255
|
+
if (element instanceof Array) {
|
|
256
|
+
for (var i = 0; i < element.length; ++i) {
|
|
257
|
+
removeAttribute(element[i], attribute);
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
element.removeAttribute(attribute);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @param {HTMLElement} element
|
|
266
|
+
* @returns {Object}
|
|
267
|
+
*/
|
|
268
|
+
var _offset = (function (element) {
|
|
269
|
+
if (!element.parentElement || element.getClientRects().length === 0) {
|
|
270
|
+
throw new Error('target element must be part of the dom');
|
|
271
|
+
}
|
|
272
|
+
var rect = element.getClientRects()[0];
|
|
273
|
+
return {
|
|
274
|
+
left: rect.left + window.pageXOffset,
|
|
275
|
+
right: rect.right + window.pageXOffset,
|
|
276
|
+
top: rect.top + window.pageYOffset,
|
|
277
|
+
bottom: rect.bottom + window.pageYOffset
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed
|
|
283
|
+
* @param {Function} func to debounce
|
|
284
|
+
* @param {number} time to wait before calling function with latest arguments, 0 - no debounce
|
|
285
|
+
* @returns {function} - debounced function
|
|
286
|
+
*/
|
|
287
|
+
var _debounce = (function (func, wait) {
|
|
288
|
+
if (wait === void 0) { wait = 0; }
|
|
289
|
+
var timeout;
|
|
290
|
+
return function () {
|
|
291
|
+
var args = [];
|
|
292
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
293
|
+
args[_i] = arguments[_i];
|
|
294
|
+
}
|
|
295
|
+
clearTimeout(timeout);
|
|
296
|
+
timeout = setTimeout(function () {
|
|
297
|
+
func.apply(void 0, args);
|
|
298
|
+
}, wait);
|
|
299
|
+
};
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
/* eslint-env browser */
|
|
303
|
+
/**
|
|
304
|
+
* Get position of the element relatively to its sibling elements
|
|
305
|
+
* @param {HTMLElement} element
|
|
306
|
+
* @returns {number}
|
|
307
|
+
*/
|
|
308
|
+
var _index = (function (element, elementList) {
|
|
309
|
+
if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) {
|
|
310
|
+
throw new Error('You must provide an element and a list of elements.');
|
|
311
|
+
}
|
|
312
|
+
return Array.from(elementList).indexOf(element);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
/* eslint-env browser */
|
|
316
|
+
/**
|
|
317
|
+
* Test whether element is in DOM
|
|
318
|
+
* @param {HTMLElement} element
|
|
319
|
+
* @returns {boolean}
|
|
320
|
+
*/
|
|
321
|
+
var isInDom = (function (element) {
|
|
322
|
+
if (!(element instanceof HTMLElement)) {
|
|
323
|
+
throw new Error('Element is not a node element.');
|
|
324
|
+
}
|
|
325
|
+
return element.parentNode !== null;
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
/* eslint-env browser */
|
|
329
|
+
/**
|
|
330
|
+
* Insert node before or after target
|
|
331
|
+
* @param {HTMLElement} referenceNode - reference element
|
|
332
|
+
* @param {HTMLElement} newElement - element to be inserted
|
|
333
|
+
* @param {String} position - insert before or after reference element
|
|
334
|
+
*/
|
|
335
|
+
var insertNode = function (referenceNode, newElement, position) {
|
|
336
|
+
if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) {
|
|
337
|
+
throw new Error('target and element must be a node');
|
|
338
|
+
}
|
|
339
|
+
referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling));
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Insert before target
|
|
343
|
+
* @param {HTMLElement} target
|
|
344
|
+
* @param {HTMLElement} element
|
|
345
|
+
*/
|
|
346
|
+
var insertBefore = function (target, element) { return insertNode(target, element, 'before'); };
|
|
347
|
+
/**
|
|
348
|
+
* Insert after target
|
|
349
|
+
* @param {HTMLElement} target
|
|
350
|
+
* @param {HTMLElement} element
|
|
351
|
+
*/
|
|
352
|
+
var insertAfter = function (target, element) { return insertNode(target, element, 'after'); };
|
|
353
|
+
|
|
354
|
+
/* eslint-env browser */
|
|
355
|
+
/**
|
|
356
|
+
* Filter only wanted nodes
|
|
357
|
+
* @param {HTMLElement} sortableContainer
|
|
358
|
+
* @param {Function} customSerializer
|
|
359
|
+
* @returns {Array}
|
|
360
|
+
*/
|
|
361
|
+
var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) {
|
|
362
|
+
if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; }
|
|
363
|
+
if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; }
|
|
364
|
+
// check for valid sortableContainer
|
|
365
|
+
if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) {
|
|
366
|
+
throw new Error('You need to provide a sortableContainer to be serialized.');
|
|
367
|
+
}
|
|
368
|
+
// check for valid serializers
|
|
369
|
+
if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') {
|
|
370
|
+
throw new Error('You need to provide a valid serializer for items and the container.');
|
|
371
|
+
}
|
|
372
|
+
// get options
|
|
373
|
+
var options = addData(sortableContainer, 'opts');
|
|
374
|
+
var item = options.items;
|
|
375
|
+
// serialize container
|
|
376
|
+
var items = _filter(sortableContainer.children, item);
|
|
377
|
+
var serializedItems = items.map(function (item) {
|
|
378
|
+
return {
|
|
379
|
+
parent: sortableContainer,
|
|
380
|
+
node: item,
|
|
381
|
+
html: item.outerHTML,
|
|
382
|
+
index: _index(item, items)
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
// serialize container
|
|
386
|
+
var container = {
|
|
387
|
+
node: sortableContainer,
|
|
388
|
+
itemCount: serializedItems.length
|
|
389
|
+
};
|
|
390
|
+
return {
|
|
391
|
+
container: customContainerSerializer(container),
|
|
392
|
+
items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); })
|
|
393
|
+
};
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/* eslint-env browser */
|
|
397
|
+
/**
|
|
398
|
+
* create a placeholder element
|
|
399
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
400
|
+
* @param {string|undefined} placeholder a string representing an html element
|
|
401
|
+
* @param {string} placeholderClasses a string representing the classes that should be added to the placeholder
|
|
402
|
+
*/
|
|
403
|
+
var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) {
|
|
404
|
+
var _a;
|
|
405
|
+
if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; }
|
|
406
|
+
if (!(sortableElement instanceof HTMLElement)) {
|
|
407
|
+
throw new Error('You must provide a valid element as a sortable.');
|
|
408
|
+
}
|
|
409
|
+
// if placeholder is not an element
|
|
410
|
+
if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) {
|
|
411
|
+
throw new Error('You must provide a valid element as a placeholder or set ot to undefined.');
|
|
412
|
+
}
|
|
413
|
+
// if no placeholder element is given
|
|
414
|
+
if (placeholder === undefined) {
|
|
415
|
+
if (['UL', 'OL'].includes(sortableElement.tagName)) {
|
|
416
|
+
placeholder = document.createElement('li');
|
|
417
|
+
}
|
|
418
|
+
else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) {
|
|
419
|
+
placeholder = document.createElement('tr');
|
|
420
|
+
// set colspan to always all rows, otherwise the item can only be dropped in first column
|
|
421
|
+
placeholder.innerHTML = '<td colspan="100"></td>';
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
placeholder = document.createElement('div');
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// add classes to placeholder
|
|
428
|
+
if (typeof placeholderClass === 'string') {
|
|
429
|
+
(_a = placeholder.classList).add.apply(_a, placeholderClass.split(' '));
|
|
430
|
+
}
|
|
431
|
+
return placeholder;
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
/* eslint-env browser */
|
|
435
|
+
/**
|
|
436
|
+
* Get height of an element including padding
|
|
437
|
+
* @param {HTMLElement} element an dom element
|
|
438
|
+
*/
|
|
439
|
+
var _getElementHeight = (function (element) {
|
|
440
|
+
if (!(element instanceof HTMLElement)) {
|
|
441
|
+
throw new Error('You must provide a valid dom element');
|
|
442
|
+
}
|
|
443
|
+
// get calculated style of element
|
|
444
|
+
var style = window.getComputedStyle(element);
|
|
445
|
+
// get only height if element has box-sizing: border-box specified
|
|
446
|
+
if (style.getPropertyValue('box-sizing') === 'border-box') {
|
|
447
|
+
return parseInt(style.getPropertyValue('height'), 10);
|
|
448
|
+
}
|
|
449
|
+
// pick applicable properties, convert to int and reduce by adding
|
|
450
|
+
return ['height', 'padding-top', 'padding-bottom']
|
|
451
|
+
.map(function (key) {
|
|
452
|
+
var int = parseInt(style.getPropertyValue(key), 10);
|
|
453
|
+
return isNaN(int) ? 0 : int;
|
|
454
|
+
})
|
|
455
|
+
.reduce(function (sum, value) { return sum + value; });
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
/* eslint-env browser */
|
|
459
|
+
/**
|
|
460
|
+
* Get width of an element including padding
|
|
461
|
+
* @param {HTMLElement} element an dom element
|
|
462
|
+
*/
|
|
463
|
+
var _getElementWidth = (function (element) {
|
|
464
|
+
if (!(element instanceof HTMLElement)) {
|
|
465
|
+
throw new Error('You must provide a valid dom element');
|
|
466
|
+
}
|
|
467
|
+
// get calculated style of element
|
|
468
|
+
var style = window.getComputedStyle(element);
|
|
469
|
+
// pick applicable properties, convert to int and reduce by adding
|
|
470
|
+
return ['width', 'padding-left', 'padding-right']
|
|
471
|
+
.map(function (key) {
|
|
472
|
+
var int = parseInt(style.getPropertyValue(key), 10);
|
|
473
|
+
return isNaN(int) ? 0 : int;
|
|
474
|
+
})
|
|
475
|
+
.reduce(function (sum, value) { return sum + value; });
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
/* eslint-env browser */
|
|
479
|
+
/**
|
|
480
|
+
* get handle or return item
|
|
481
|
+
* @param {Array<HTMLElement>} items
|
|
482
|
+
* @param {string} selector
|
|
483
|
+
*/
|
|
484
|
+
var _getHandles = (function (items, selector) {
|
|
485
|
+
if (!(items instanceof Array)) {
|
|
486
|
+
throw new Error('You must provide a Array of HTMLElements to be filtered.');
|
|
487
|
+
}
|
|
488
|
+
if (typeof selector !== 'string') {
|
|
489
|
+
return items;
|
|
490
|
+
}
|
|
491
|
+
return items
|
|
492
|
+
// remove items without handle from array
|
|
493
|
+
.filter(function (item) {
|
|
494
|
+
return item.querySelector(selector) instanceof HTMLElement ||
|
|
495
|
+
(item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement);
|
|
496
|
+
})
|
|
497
|
+
// replace item with handle in array
|
|
498
|
+
.map(function (item) {
|
|
499
|
+
return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector));
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* @param {Event} event
|
|
505
|
+
* @returns {HTMLElement}
|
|
506
|
+
*/
|
|
507
|
+
var getEventTarget = (function (event) {
|
|
508
|
+
return (event.composedPath && event.composedPath()[0]) || event.target;
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
/* eslint-env browser */
|
|
512
|
+
/**
|
|
513
|
+
* defaultDragImage returns the current item as dragged image
|
|
514
|
+
* @param {HTMLElement} draggedElement - the item that the user drags
|
|
515
|
+
* @param {object} elementOffset - an object with the offsets top, left, right & bottom
|
|
516
|
+
* @param {Event} event - the original drag event object
|
|
517
|
+
* @return {object} with element, posX and posY properties
|
|
518
|
+
*/
|
|
519
|
+
var defaultDragImage = function (draggedElement, elementOffset, event) {
|
|
520
|
+
return {
|
|
521
|
+
element: draggedElement,
|
|
522
|
+
posX: event.pageX - elementOffset.left,
|
|
523
|
+
posY: event.pageY - elementOffset.top
|
|
524
|
+
};
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* attaches an element as the drag image to an event
|
|
528
|
+
* @param {Event} event - the original drag event object
|
|
529
|
+
* @param {HTMLElement} draggedElement - the item that the user drags
|
|
530
|
+
* @param {Function} customDragImage - function to create a custom dragImage
|
|
531
|
+
* @return void
|
|
532
|
+
*/
|
|
533
|
+
var setDragImage = (function (event, draggedElement, customDragImage) {
|
|
534
|
+
// check if event is provided
|
|
535
|
+
if (!(event instanceof Event)) {
|
|
536
|
+
throw new Error('setDragImage requires a DragEvent as the first argument.');
|
|
537
|
+
}
|
|
538
|
+
// check if draggedElement is provided
|
|
539
|
+
if (!(draggedElement instanceof HTMLElement)) {
|
|
540
|
+
throw new Error('setDragImage requires the dragged element as the second argument.');
|
|
541
|
+
}
|
|
542
|
+
// set default function of none provided
|
|
543
|
+
if (!customDragImage) {
|
|
544
|
+
customDragImage = defaultDragImage;
|
|
545
|
+
}
|
|
546
|
+
// check if setDragImage method is available
|
|
547
|
+
if (event.dataTransfer && event.dataTransfer.setDragImage) {
|
|
548
|
+
// get the elements offset
|
|
549
|
+
var elementOffset = _offset(draggedElement);
|
|
550
|
+
// get the dragImage
|
|
551
|
+
var dragImage = customDragImage(draggedElement, elementOffset, event);
|
|
552
|
+
// check if custom function returns correct values
|
|
553
|
+
if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') {
|
|
554
|
+
throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].');
|
|
555
|
+
}
|
|
556
|
+
// needs to be set for HTML5 drag & drop to work
|
|
557
|
+
event.dataTransfer.effectAllowed = 'copyMove';
|
|
558
|
+
// Firefox requires it to use the event target's id for the data
|
|
559
|
+
event.dataTransfer.setData('text/plain', getEventTarget(event).id);
|
|
560
|
+
// set the drag image on the event
|
|
561
|
+
event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Check if curList accepts items from destList
|
|
567
|
+
* @param {sortable} destination the container an item is move to
|
|
568
|
+
* @param {sortable} origin the container an item comes from
|
|
569
|
+
*/
|
|
570
|
+
var _listsConnected = (function (destination, origin) {
|
|
571
|
+
// check if valid sortable
|
|
572
|
+
if (destination.isSortable === true) {
|
|
573
|
+
var acceptFrom = store(destination).getConfig('acceptFrom');
|
|
574
|
+
// check if acceptFrom is valid
|
|
575
|
+
if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') {
|
|
576
|
+
throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.');
|
|
577
|
+
}
|
|
578
|
+
if (acceptFrom !== null) {
|
|
579
|
+
return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) {
|
|
580
|
+
return sel.length > 0 && origin.matches(sel);
|
|
581
|
+
}).length > 0;
|
|
582
|
+
}
|
|
583
|
+
// drop in same list
|
|
584
|
+
if (destination === origin) {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
// check if lists are connected with connectWith
|
|
588
|
+
if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) {
|
|
589
|
+
return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith');
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return false;
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* default configurations
|
|
597
|
+
*/
|
|
598
|
+
var defaultConfiguration = {
|
|
599
|
+
items: null,
|
|
600
|
+
// deprecated
|
|
601
|
+
connectWith: null,
|
|
602
|
+
// deprecated
|
|
603
|
+
disableIEFix: null,
|
|
604
|
+
acceptFrom: null,
|
|
605
|
+
copy: false,
|
|
606
|
+
placeholder: null,
|
|
607
|
+
placeholderClass: 'sortable-placeholder',
|
|
608
|
+
draggingClass: 'sortable-dragging',
|
|
609
|
+
hoverClass: false,
|
|
610
|
+
dropTargetContainerClass: false,
|
|
611
|
+
debounce: 0,
|
|
612
|
+
throttleTime: 100,
|
|
613
|
+
maxItems: 0,
|
|
614
|
+
itemSerializer: undefined,
|
|
615
|
+
containerSerializer: undefined,
|
|
616
|
+
customDragImage: null,
|
|
617
|
+
orientation: 'vertical'
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* make sure a function is only called once within the given amount of time
|
|
622
|
+
* @param {Function} fn the function to throttle
|
|
623
|
+
* @param {number} threshold time limit for throttling
|
|
624
|
+
*/
|
|
625
|
+
// must use function to keep this context
|
|
626
|
+
function _throttle (fn, threshold) {
|
|
627
|
+
var _this = this;
|
|
628
|
+
if (threshold === void 0) { threshold = 250; }
|
|
629
|
+
// check function
|
|
630
|
+
if (typeof fn !== 'function') {
|
|
631
|
+
throw new Error('You must provide a function as the first argument for throttle.');
|
|
632
|
+
}
|
|
633
|
+
// check threshold
|
|
634
|
+
if (typeof threshold !== 'number') {
|
|
635
|
+
throw new Error('You must provide a number as the second argument for throttle.');
|
|
636
|
+
}
|
|
637
|
+
var lastEventTimestamp = null;
|
|
638
|
+
return function () {
|
|
639
|
+
var args = [];
|
|
640
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
641
|
+
args[_i] = arguments[_i];
|
|
642
|
+
}
|
|
643
|
+
var now = Date.now();
|
|
644
|
+
if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) {
|
|
645
|
+
lastEventTimestamp = now;
|
|
646
|
+
fn.apply(_this, args);
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* eslint-env browser */
|
|
652
|
+
/**
|
|
653
|
+
* enable or disable hoverClass on mouseenter/leave if container Items
|
|
654
|
+
* @param {sortable} sortableContainer a valid sortableContainer
|
|
655
|
+
* @param {boolean} enable enable or disable event
|
|
656
|
+
*/
|
|
657
|
+
// export default (sortableContainer: sortable, enable: boolean) => {
|
|
658
|
+
var enableHoverClass = (function (sortableContainer, enable) {
|
|
659
|
+
if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {
|
|
660
|
+
var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' ');
|
|
661
|
+
// add class on hover
|
|
662
|
+
if (enable === true) {
|
|
663
|
+
addEventListener(sortableContainer, 'mousemove', _throttle(function (event) {
|
|
664
|
+
// check of no mouse button was pressed when mousemove started == no drag
|
|
665
|
+
if (event.buttons === 0) {
|
|
666
|
+
_filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
|
|
667
|
+
var _a, _b;
|
|
668
|
+
if (item !== event.target) {
|
|
669
|
+
(_a = item.classList).remove.apply(_a, hoverClasses_1);
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
(_b = item.classList).add.apply(_b, hoverClasses_1);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}, store(sortableContainer).getConfig('throttleTime')));
|
|
677
|
+
// remove class on leave
|
|
678
|
+
addEventListener(sortableContainer, 'mouseleave', function () {
|
|
679
|
+
_filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
|
|
680
|
+
var _a;
|
|
681
|
+
(_a = item.classList).remove.apply(_a, hoverClasses_1);
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
// remove events
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
removeEventListener(sortableContainer, 'mousemove');
|
|
688
|
+
removeEventListener(sortableContainer, 'mouseleave');
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
/* eslint-env browser */
|
|
694
|
+
/*
|
|
695
|
+
* variables global to the plugin
|
|
696
|
+
*/
|
|
697
|
+
var dragging;
|
|
698
|
+
var draggingHeight;
|
|
699
|
+
var draggingWidth;
|
|
700
|
+
/*
|
|
701
|
+
* Keeps track of the initialy selected list, where 'dragstart' event was triggered
|
|
702
|
+
* It allows us to move the data in between individual Sortable List instances
|
|
703
|
+
*/
|
|
704
|
+
// Origin List - data from before any item was changed
|
|
705
|
+
var originContainer;
|
|
706
|
+
var originIndex;
|
|
707
|
+
var originElementIndex;
|
|
708
|
+
var originItemsBeforeUpdate;
|
|
709
|
+
// Previous Sortable Container - we dispatch as sortenter event when a
|
|
710
|
+
// dragged item enters a sortableContainer for the first time
|
|
711
|
+
var previousContainer;
|
|
712
|
+
// Destination List - data from before any item was changed
|
|
713
|
+
var destinationItemsBeforeUpdate;
|
|
714
|
+
/**
|
|
715
|
+
* remove event handlers from items
|
|
716
|
+
* @param {Array|NodeList} items
|
|
717
|
+
*/
|
|
718
|
+
var _removeItemEvents = function (items) {
|
|
719
|
+
removeEventListener(items, 'dragstart');
|
|
720
|
+
removeEventListener(items, 'dragend');
|
|
721
|
+
removeEventListener(items, 'dragover');
|
|
722
|
+
removeEventListener(items, 'dragenter');
|
|
723
|
+
removeEventListener(items, 'drop');
|
|
724
|
+
removeEventListener(items, 'mouseenter');
|
|
725
|
+
removeEventListener(items, 'mouseleave');
|
|
726
|
+
};
|
|
727
|
+
// Remove container events
|
|
728
|
+
var _removeContainerEvents = function (originContainer, previousContainer) {
|
|
729
|
+
if (originContainer) {
|
|
730
|
+
removeEventListener(originContainer, 'dragleave');
|
|
731
|
+
}
|
|
732
|
+
if (previousContainer && (previousContainer !== originContainer)) {
|
|
733
|
+
removeEventListener(previousContainer, 'dragleave');
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* _getDragging returns the current element to drag or
|
|
738
|
+
* a copy of the element.
|
|
739
|
+
* Is Copy Active for sortable
|
|
740
|
+
* @param {HTMLElement} draggedItem - the item that the user drags
|
|
741
|
+
* @param {HTMLElement} sortable a single sortable
|
|
742
|
+
*/
|
|
743
|
+
var _getDragging = function (draggedItem, sortable) {
|
|
744
|
+
var ditem = draggedItem;
|
|
745
|
+
if (store(sortable).getConfig('copy') === true) {
|
|
746
|
+
ditem = draggedItem.cloneNode(true);
|
|
747
|
+
addAttribute(ditem, 'aria-copied', 'true');
|
|
748
|
+
draggedItem.parentElement.appendChild(ditem);
|
|
749
|
+
ditem.style.display = 'none';
|
|
750
|
+
ditem.oldDisplay = draggedItem.style.display;
|
|
751
|
+
}
|
|
752
|
+
return ditem;
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* Remove data from sortable
|
|
756
|
+
* @param {HTMLElement} sortable a single sortable
|
|
757
|
+
*/
|
|
758
|
+
var _removeSortableData = function (sortable) {
|
|
759
|
+
removeData(sortable);
|
|
760
|
+
removeAttribute(sortable, 'aria-dropeffect');
|
|
761
|
+
};
|
|
762
|
+
/**
|
|
763
|
+
* Remove data from items
|
|
764
|
+
* @param {Array<HTMLElement>|HTMLElement} items
|
|
765
|
+
*/
|
|
766
|
+
var _removeItemData = function (items) {
|
|
767
|
+
removeAttribute(items, 'aria-grabbed');
|
|
768
|
+
removeAttribute(items, 'aria-copied');
|
|
769
|
+
removeAttribute(items, 'draggable');
|
|
770
|
+
removeAttribute(items, 'role');
|
|
771
|
+
};
|
|
772
|
+
/**
|
|
773
|
+
* find sortable from element. travels up parent element until found or null.
|
|
774
|
+
* @param {HTMLElement} element a single sortable
|
|
775
|
+
* @param {Event} event - the current event. We need to pass it to be able to
|
|
776
|
+
* find Sortable whith shadowRoot (document fragment has no parent)
|
|
777
|
+
*/
|
|
778
|
+
function findSortable(element, event) {
|
|
779
|
+
if (event.composedPath) {
|
|
780
|
+
return event.composedPath().find(function (el) { return el.isSortable; });
|
|
781
|
+
}
|
|
782
|
+
while (element.isSortable !== true) {
|
|
783
|
+
element = element.parentElement;
|
|
784
|
+
}
|
|
785
|
+
return element;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Dragging event is on the sortable element. finds the top child that
|
|
789
|
+
* contains the element.
|
|
790
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
791
|
+
* @param {HTMLElement} element is that being dragged
|
|
792
|
+
*/
|
|
793
|
+
function findDragElement(sortableElement, element) {
|
|
794
|
+
var options = addData(sortableElement, 'opts');
|
|
795
|
+
var items = _filter(sortableElement.children, options.items);
|
|
796
|
+
var itemlist = items.filter(function (ele) {
|
|
797
|
+
return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element));
|
|
798
|
+
});
|
|
799
|
+
return itemlist.length > 0 ? itemlist[0] : element;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Destroy the sortable
|
|
803
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
804
|
+
*/
|
|
805
|
+
var _destroySortable = function (sortableElement) {
|
|
806
|
+
var opts = addData(sortableElement, 'opts') || {};
|
|
807
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
808
|
+
var handles = _getHandles(items, opts.handle);
|
|
809
|
+
// remove event handlers & data from sortable
|
|
810
|
+
removeEventListener(sortableElement, 'dragover');
|
|
811
|
+
removeEventListener(sortableElement, 'dragenter');
|
|
812
|
+
removeEventListener(sortableElement, 'dragstart');
|
|
813
|
+
removeEventListener(sortableElement, 'dragend');
|
|
814
|
+
removeEventListener(sortableElement, 'drop');
|
|
815
|
+
// remove event data from sortable
|
|
816
|
+
_removeSortableData(sortableElement);
|
|
817
|
+
// remove event handlers & data from items
|
|
818
|
+
removeEventListener(handles, 'mousedown');
|
|
819
|
+
_removeItemEvents(items);
|
|
820
|
+
_removeItemData(items);
|
|
821
|
+
_removeContainerEvents(originContainer, previousContainer);
|
|
822
|
+
// clear sortable flag
|
|
823
|
+
sortableElement.isSortable = false;
|
|
824
|
+
};
|
|
825
|
+
/**
|
|
826
|
+
* Enable the sortable
|
|
827
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
828
|
+
*/
|
|
829
|
+
var _enableSortable = function (sortableElement) {
|
|
830
|
+
var opts = addData(sortableElement, 'opts');
|
|
831
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
832
|
+
var handles = _getHandles(items, opts.handle);
|
|
833
|
+
addAttribute(sortableElement, 'aria-dropeffect', 'move');
|
|
834
|
+
addData(sortableElement, '_disabled', 'false');
|
|
835
|
+
addAttribute(handles, 'draggable', 'true');
|
|
836
|
+
// @todo: remove this fix
|
|
837
|
+
// IE FIX for ghost
|
|
838
|
+
// can be disabled as it has the side effect that other events
|
|
839
|
+
// (e.g. click) will be ignored
|
|
840
|
+
if (opts.disableIEFix === false) {
|
|
841
|
+
var spanEl = (document || window.document).createElement('span');
|
|
842
|
+
if (typeof spanEl.dragDrop === 'function') {
|
|
843
|
+
addEventListener(handles, 'mousedown', function () {
|
|
844
|
+
if (items.indexOf(this) !== -1) {
|
|
845
|
+
this.dragDrop();
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
var parent = this.parentElement;
|
|
849
|
+
while (items.indexOf(parent) === -1) {
|
|
850
|
+
parent = parent.parentElement;
|
|
851
|
+
}
|
|
852
|
+
parent.dragDrop();
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* Disable the sortable
|
|
860
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
861
|
+
*/
|
|
862
|
+
var _disableSortable = function (sortableElement) {
|
|
863
|
+
var opts = addData(sortableElement, 'opts');
|
|
864
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
865
|
+
var handles = _getHandles(items, opts.handle);
|
|
866
|
+
addAttribute(sortableElement, 'aria-dropeffect', 'none');
|
|
867
|
+
addData(sortableElement, '_disabled', 'true');
|
|
868
|
+
addAttribute(handles, 'draggable', 'false');
|
|
869
|
+
removeEventListener(handles, 'mousedown');
|
|
870
|
+
};
|
|
871
|
+
/**
|
|
872
|
+
* Reload the sortable
|
|
873
|
+
* @param {HTMLElement} sortableElement a single sortable
|
|
874
|
+
* @description events need to be removed to not be double bound
|
|
875
|
+
*/
|
|
876
|
+
var _reloadSortable = function (sortableElement) {
|
|
877
|
+
var opts = addData(sortableElement, 'opts');
|
|
878
|
+
var items = _filter(sortableElement.children, opts.items);
|
|
879
|
+
var handles = _getHandles(items, opts.handle);
|
|
880
|
+
addData(sortableElement, '_disabled', 'false');
|
|
881
|
+
// remove event handlers from items
|
|
882
|
+
_removeItemEvents(items);
|
|
883
|
+
_removeContainerEvents();
|
|
884
|
+
removeEventListener(handles, 'mousedown');
|
|
885
|
+
// remove event handlers from sortable
|
|
886
|
+
removeEventListener(sortableElement, 'dragover');
|
|
887
|
+
removeEventListener(sortableElement, 'dragenter');
|
|
888
|
+
removeEventListener(sortableElement, 'drop');
|
|
889
|
+
};
|
|
890
|
+
/**
|
|
891
|
+
* Public sortable object
|
|
892
|
+
* @param {Array|NodeList} sortableElements
|
|
893
|
+
* @param {object|string} options|method
|
|
894
|
+
*/
|
|
895
|
+
function sortable(sortableElements, options) {
|
|
896
|
+
// get method string to see if a method is called
|
|
897
|
+
var method = String(options);
|
|
898
|
+
options = options || {};
|
|
899
|
+
// check if the user provided a selector instead of an element
|
|
900
|
+
if (typeof sortableElements === 'string') {
|
|
901
|
+
sortableElements = document.querySelectorAll(sortableElements);
|
|
902
|
+
}
|
|
903
|
+
// if the user provided an element, return it in an array to keep the return value consistant
|
|
904
|
+
if (sortableElements instanceof HTMLElement) {
|
|
905
|
+
sortableElements = [sortableElements];
|
|
906
|
+
}
|
|
907
|
+
sortableElements = Array.prototype.slice.call(sortableElements);
|
|
908
|
+
if (/serialize/.test(method)) {
|
|
909
|
+
return sortableElements.map(function (sortableContainer) {
|
|
910
|
+
var opts = addData(sortableContainer, 'opts');
|
|
911
|
+
return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer);
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
sortableElements.forEach(function (sortableElement) {
|
|
915
|
+
if (/enable|disable|destroy/.test(method)) {
|
|
916
|
+
return sortable[method](sortableElement);
|
|
917
|
+
}
|
|
918
|
+
// log deprecation
|
|
919
|
+
['connectWith', 'disableIEFix'].forEach(function (configKey) {
|
|
920
|
+
if (Object.prototype.hasOwnProperty.call(options, configKey) && options[configKey] !== null) {
|
|
921
|
+
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.");
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
// merge options with default options
|
|
925
|
+
options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options);
|
|
926
|
+
// init data store for sortable
|
|
927
|
+
store(sortableElement).config = options;
|
|
928
|
+
// set options on sortable
|
|
929
|
+
addData(sortableElement, 'opts', options);
|
|
930
|
+
// property to define as sortable
|
|
931
|
+
sortableElement.isSortable = true;
|
|
932
|
+
// reset sortable
|
|
933
|
+
_reloadSortable(sortableElement);
|
|
934
|
+
// initialize
|
|
935
|
+
var listItems = _filter(sortableElement.children, options.items);
|
|
936
|
+
// create element if user defined a placeholder element as a string
|
|
937
|
+
var customPlaceholder;
|
|
938
|
+
if (options.placeholder !== null && options.placeholder !== undefined) {
|
|
939
|
+
var tempContainer = document.createElement(sortableElement.tagName);
|
|
940
|
+
if (options.placeholder instanceof HTMLElement) {
|
|
941
|
+
tempContainer.appendChild(options.placeholder);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
tempContainer.innerHTML = options.placeholder;
|
|
945
|
+
}
|
|
946
|
+
customPlaceholder = tempContainer.children[0];
|
|
947
|
+
}
|
|
948
|
+
// add placeholder
|
|
949
|
+
store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass);
|
|
950
|
+
addData(sortableElement, 'items', options.items);
|
|
951
|
+
if (options.acceptFrom) {
|
|
952
|
+
addData(sortableElement, 'acceptFrom', options.acceptFrom);
|
|
953
|
+
}
|
|
954
|
+
else if (options.connectWith) {
|
|
955
|
+
addData(sortableElement, 'connectWith', options.connectWith);
|
|
956
|
+
}
|
|
957
|
+
_enableSortable(sortableElement);
|
|
958
|
+
addAttribute(listItems, 'role', 'option');
|
|
959
|
+
addAttribute(listItems, 'aria-grabbed', 'false');
|
|
960
|
+
// enable hover class
|
|
961
|
+
enableHoverClass(sortableElement, true);
|
|
962
|
+
/*
|
|
963
|
+
Handle drag events on draggable items
|
|
964
|
+
Handle is set at the sortableElement level as it will bubble up
|
|
965
|
+
from the item
|
|
966
|
+
*/
|
|
967
|
+
addEventListener(sortableElement, 'dragstart', function (e) {
|
|
968
|
+
// ignore dragstart events
|
|
969
|
+
var target = getEventTarget(e);
|
|
970
|
+
if (target.isSortable === true) {
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
e.stopImmediatePropagation();
|
|
974
|
+
if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
var sortableContainer = findSortable(target, e);
|
|
978
|
+
var dragItem = findDragElement(sortableContainer, target);
|
|
979
|
+
// grab values
|
|
980
|
+
originItemsBeforeUpdate = _filter(sortableContainer.children, options.items);
|
|
981
|
+
originIndex = originItemsBeforeUpdate.indexOf(dragItem);
|
|
982
|
+
originElementIndex = _index(dragItem, sortableContainer.children);
|
|
983
|
+
originContainer = sortableContainer;
|
|
984
|
+
// add transparent clone or other ghost to cursor
|
|
985
|
+
setDragImage(e, dragItem, options.customDragImage);
|
|
986
|
+
// cache selsection & add attr for dragging
|
|
987
|
+
draggingHeight = _getElementHeight(dragItem);
|
|
988
|
+
draggingWidth = _getElementWidth(dragItem);
|
|
989
|
+
dragItem.classList.add(options.draggingClass);
|
|
990
|
+
dragging = _getDragging(dragItem, sortableContainer);
|
|
991
|
+
addAttribute(dragging, 'aria-grabbed', 'true');
|
|
992
|
+
// dispatch sortstart event on each element in group
|
|
993
|
+
sortableContainer.dispatchEvent(new CustomEvent('sortstart', {
|
|
994
|
+
detail: {
|
|
995
|
+
origin: {
|
|
996
|
+
elementIndex: originElementIndex,
|
|
997
|
+
index: originIndex,
|
|
998
|
+
container: originContainer
|
|
999
|
+
},
|
|
1000
|
+
item: dragging,
|
|
1001
|
+
originalTarget: target
|
|
1002
|
+
}
|
|
1003
|
+
}));
|
|
1004
|
+
});
|
|
1005
|
+
/*
|
|
1006
|
+
We are capturing targetSortable before modifications with 'dragenter' event
|
|
1007
|
+
*/
|
|
1008
|
+
addEventListener(sortableElement, 'dragenter', function (e) {
|
|
1009
|
+
var target = getEventTarget(e);
|
|
1010
|
+
var sortableContainer = findSortable(target, e);
|
|
1011
|
+
if (sortableContainer && sortableContainer !== previousContainer) {
|
|
1012
|
+
destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items'))
|
|
1013
|
+
.filter(function (item) { return item !== store(sortableElement).placeholder; });
|
|
1014
|
+
if (options.dropTargetContainerClass) {
|
|
1015
|
+
sortableContainer.classList.add(options.dropTargetContainerClass);
|
|
1016
|
+
}
|
|
1017
|
+
sortableContainer.dispatchEvent(new CustomEvent('sortenter', {
|
|
1018
|
+
detail: {
|
|
1019
|
+
origin: {
|
|
1020
|
+
elementIndex: originElementIndex,
|
|
1021
|
+
index: originIndex,
|
|
1022
|
+
container: originContainer
|
|
1023
|
+
},
|
|
1024
|
+
destination: {
|
|
1025
|
+
container: sortableContainer,
|
|
1026
|
+
itemsBeforeUpdate: destinationItemsBeforeUpdate
|
|
1027
|
+
},
|
|
1028
|
+
item: dragging,
|
|
1029
|
+
originalTarget: target
|
|
1030
|
+
}
|
|
1031
|
+
}));
|
|
1032
|
+
addEventListener(sortableContainer, 'dragleave', function (e) {
|
|
1033
|
+
// TODO: rename outTarget to be more self-explanatory
|
|
1034
|
+
// e.fromElement for very old browsers, similar to relatedTarget
|
|
1035
|
+
var outTarget = e.relatedTarget || e.fromElement;
|
|
1036
|
+
if (!e.currentTarget.contains(outTarget)) {
|
|
1037
|
+
if (options.dropTargetContainerClass) {
|
|
1038
|
+
sortableContainer.classList.remove(options.dropTargetContainerClass);
|
|
1039
|
+
}
|
|
1040
|
+
sortableContainer.dispatchEvent(new CustomEvent('sortleave', {
|
|
1041
|
+
detail: {
|
|
1042
|
+
origin: {
|
|
1043
|
+
elementIndex: originElementIndex,
|
|
1044
|
+
index: originIndex,
|
|
1045
|
+
container: sortableContainer
|
|
1046
|
+
},
|
|
1047
|
+
item: dragging,
|
|
1048
|
+
originalTarget: target
|
|
1049
|
+
}
|
|
1050
|
+
}));
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
previousContainer = sortableContainer;
|
|
1055
|
+
});
|
|
1056
|
+
/*
|
|
1057
|
+
* Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend
|
|
1058
|
+
* Fires each time dragEvent end, or ESC pressed
|
|
1059
|
+
* We are using it to clean up any draggable elements and placeholders
|
|
1060
|
+
*/
|
|
1061
|
+
addEventListener(sortableElement, 'dragend', function (e) {
|
|
1062
|
+
if (!dragging) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
dragging.classList.remove(options.draggingClass);
|
|
1066
|
+
addAttribute(dragging, 'aria-grabbed', 'false');
|
|
1067
|
+
if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') {
|
|
1068
|
+
dragging.remove();
|
|
1069
|
+
}
|
|
1070
|
+
dragging.style.display = dragging.oldDisplay;
|
|
1071
|
+
delete dragging.oldDisplay;
|
|
1072
|
+
var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; })
|
|
1073
|
+
.filter(function (placeholder) { return placeholder instanceof HTMLElement; })
|
|
1074
|
+
.filter(isInDom)[0];
|
|
1075
|
+
if (visiblePlaceholder) {
|
|
1076
|
+
visiblePlaceholder.remove();
|
|
1077
|
+
}
|
|
1078
|
+
// dispatch sortstart event on each element in group
|
|
1079
|
+
sortableElement.dispatchEvent(new CustomEvent('sortstop', {
|
|
1080
|
+
detail: {
|
|
1081
|
+
origin: {
|
|
1082
|
+
elementIndex: originElementIndex,
|
|
1083
|
+
index: originIndex,
|
|
1084
|
+
container: originContainer
|
|
1085
|
+
},
|
|
1086
|
+
item: dragging
|
|
1087
|
+
}
|
|
1088
|
+
}));
|
|
1089
|
+
previousContainer = null;
|
|
1090
|
+
dragging = null;
|
|
1091
|
+
draggingHeight = null;
|
|
1092
|
+
draggingWidth = null;
|
|
1093
|
+
});
|
|
1094
|
+
/*
|
|
1095
|
+
* Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop
|
|
1096
|
+
* Fires when valid drop target area is hit
|
|
1097
|
+
*/
|
|
1098
|
+
addEventListener(sortableElement, 'drop', function (e) {
|
|
1099
|
+
if (!_listsConnected(sortableElement, dragging.parentElement)) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
e.preventDefault();
|
|
1103
|
+
e.stopPropagation();
|
|
1104
|
+
addData(dragging, 'dropped', 'true');
|
|
1105
|
+
// get the one placeholder that is currently visible
|
|
1106
|
+
var visiblePlaceholder = Array.from(stores.values()).map(function (data) {
|
|
1107
|
+
return data.placeholder;
|
|
1108
|
+
})
|
|
1109
|
+
// filter only HTMLElements
|
|
1110
|
+
.filter(function (placeholder) { return placeholder instanceof HTMLElement; })
|
|
1111
|
+
// filter only elements in DOM
|
|
1112
|
+
.filter(isInDom)[0];
|
|
1113
|
+
// attach element after placeholder
|
|
1114
|
+
insertAfter(visiblePlaceholder, dragging);
|
|
1115
|
+
// remove placeholder from dom
|
|
1116
|
+
visiblePlaceholder.remove();
|
|
1117
|
+
/*
|
|
1118
|
+
* Fires Custom Event - 'sortstop'
|
|
1119
|
+
*/
|
|
1120
|
+
sortableElement.dispatchEvent(new CustomEvent('sortstop', {
|
|
1121
|
+
detail: {
|
|
1122
|
+
origin: {
|
|
1123
|
+
elementIndex: originElementIndex,
|
|
1124
|
+
index: originIndex,
|
|
1125
|
+
container: originContainer
|
|
1126
|
+
},
|
|
1127
|
+
item: dragging
|
|
1128
|
+
}
|
|
1129
|
+
}));
|
|
1130
|
+
var placeholder = store(sortableElement).placeholder;
|
|
1131
|
+
var originItems = _filter(originContainer.children, options.items)
|
|
1132
|
+
.filter(function (item) { return item !== placeholder; });
|
|
1133
|
+
var destinationContainer = this.isSortable === true ? this : this.parentElement;
|
|
1134
|
+
var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items'))
|
|
1135
|
+
.filter(function (item) { return item !== placeholder; });
|
|
1136
|
+
var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children)
|
|
1137
|
+
.filter(function (item) { return item !== placeholder; }));
|
|
1138
|
+
var destinationIndex = _index(dragging, destinationItems);
|
|
1139
|
+
if (options.dropTargetContainerClass) {
|
|
1140
|
+
destinationContainer.classList.remove(options.dropTargetContainerClass);
|
|
1141
|
+
}
|
|
1142
|
+
/*
|
|
1143
|
+
* When a list item changed container lists or index within a list
|
|
1144
|
+
* Fires Custom Event - 'sortupdate'
|
|
1145
|
+
*/
|
|
1146
|
+
if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) {
|
|
1147
|
+
sortableElement.dispatchEvent(new CustomEvent('sortupdate', {
|
|
1148
|
+
detail: {
|
|
1149
|
+
origin: {
|
|
1150
|
+
elementIndex: originElementIndex,
|
|
1151
|
+
index: originIndex,
|
|
1152
|
+
container: originContainer,
|
|
1153
|
+
itemsBeforeUpdate: originItemsBeforeUpdate,
|
|
1154
|
+
items: originItems
|
|
1155
|
+
},
|
|
1156
|
+
destination: {
|
|
1157
|
+
index: destinationIndex,
|
|
1158
|
+
elementIndex: destinationElementIndex,
|
|
1159
|
+
container: destinationContainer,
|
|
1160
|
+
itemsBeforeUpdate: destinationItemsBeforeUpdate,
|
|
1161
|
+
items: destinationItems
|
|
1162
|
+
},
|
|
1163
|
+
item: dragging
|
|
1164
|
+
}
|
|
1165
|
+
}));
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageX, pageY) {
|
|
1169
|
+
if (!dragging) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
// set placeholder height if forcePlaceholderSize option is set
|
|
1173
|
+
if (options.forcePlaceholderSize) {
|
|
1174
|
+
store(sortableElement).placeholder.style.height = draggingHeight + 'px';
|
|
1175
|
+
store(sortableElement).placeholder.style.width = draggingWidth + 'px';
|
|
1176
|
+
}
|
|
1177
|
+
// if element the draggedItem is dragged onto is within the array of all elements in list
|
|
1178
|
+
// (not only items, but also disabled, etc.)
|
|
1179
|
+
if (Array.from(sortableElement.children).indexOf(element) > -1) {
|
|
1180
|
+
var thisHeight = _getElementHeight(element);
|
|
1181
|
+
var thisWidth = _getElementWidth(element);
|
|
1182
|
+
var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children);
|
|
1183
|
+
var thisIndex = _index(element, element.parentElement.children);
|
|
1184
|
+
// Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering
|
|
1185
|
+
if (thisHeight > draggingHeight || thisWidth > draggingWidth) {
|
|
1186
|
+
// Dead zone?
|
|
1187
|
+
var deadZoneVertical = thisHeight - draggingHeight;
|
|
1188
|
+
var deadZoneHorizontal = thisWidth - draggingWidth;
|
|
1189
|
+
var offsetTop = _offset(element).top;
|
|
1190
|
+
var offsetLeft = _offset(element).left;
|
|
1191
|
+
if (placeholderIndex < thisIndex &&
|
|
1192
|
+
((options.orientation === 'vertical' && pageY < offsetTop) ||
|
|
1193
|
+
(options.orientation === 'horizontal' && pageX < offsetLeft))) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (placeholderIndex > thisIndex &&
|
|
1197
|
+
((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) ||
|
|
1198
|
+
(options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (dragging.oldDisplay === undefined) {
|
|
1203
|
+
dragging.oldDisplay = dragging.style.display;
|
|
1204
|
+
}
|
|
1205
|
+
if (dragging.style.display !== 'none') {
|
|
1206
|
+
dragging.style.display = 'none';
|
|
1207
|
+
}
|
|
1208
|
+
// To avoid flicker, determine where to position the placeholder
|
|
1209
|
+
// based on where the mouse pointer is relative to the elements
|
|
1210
|
+
// vertical center.
|
|
1211
|
+
var placeAfter = false;
|
|
1212
|
+
try {
|
|
1213
|
+
var elementMiddleVertical = _offset(element).top + element.offsetHeight / 2;
|
|
1214
|
+
var elementMiddleHorizontal = _offset(element).left + element.offsetWidth / 2;
|
|
1215
|
+
placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) ||
|
|
1216
|
+
(options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal));
|
|
1217
|
+
}
|
|
1218
|
+
catch (e) {
|
|
1219
|
+
placeAfter = placeholderIndex < thisIndex;
|
|
1220
|
+
}
|
|
1221
|
+
if (placeAfter) {
|
|
1222
|
+
insertAfter(element, store(sortableElement).placeholder);
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
insertBefore(element, store(sortableElement).placeholder);
|
|
1226
|
+
}
|
|
1227
|
+
// get placeholders from all stores & remove all but current one
|
|
1228
|
+
Array.from(stores.values())
|
|
1229
|
+
// remove empty values
|
|
1230
|
+
.filter(function (data) { return data.placeholder !== undefined; })
|
|
1231
|
+
// foreach placeholder in array if outside of current sorableContainer -> remove from DOM
|
|
1232
|
+
.forEach(function (data) {
|
|
1233
|
+
if (data.placeholder !== store(sortableElement).placeholder) {
|
|
1234
|
+
data.placeholder.remove();
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
// get all placeholders from store
|
|
1240
|
+
var placeholders = Array.from(stores.values())
|
|
1241
|
+
.filter(function (data) { return data.placeholder !== undefined; })
|
|
1242
|
+
.map(function (data) {
|
|
1243
|
+
return data.placeholder;
|
|
1244
|
+
});
|
|
1245
|
+
// check if element is not in placeholders
|
|
1246
|
+
if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) {
|
|
1247
|
+
placeholders.forEach(function (element) { return element.remove(); });
|
|
1248
|
+
element.appendChild(store(sortableElement).placeholder);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}, options.debounce);
|
|
1252
|
+
// Handle dragover and dragenter events on draggable items
|
|
1253
|
+
var onDragOverEnter = function (e) {
|
|
1254
|
+
var element = e.target;
|
|
1255
|
+
var sortableElement = element.isSortable === true ? element : findSortable(element, e);
|
|
1256
|
+
element = findDragElement(sortableElement, element);
|
|
1257
|
+
if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
var options = addData(sortableElement, 'opts');
|
|
1261
|
+
if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) {
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
e.preventDefault();
|
|
1265
|
+
e.stopPropagation();
|
|
1266
|
+
e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move';
|
|
1267
|
+
debouncedDragOverEnter(sortableElement, element, e.pageX, e.pageY);
|
|
1268
|
+
};
|
|
1269
|
+
addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter);
|
|
1270
|
+
addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter);
|
|
1271
|
+
});
|
|
1272
|
+
return sortableElements;
|
|
1273
|
+
}
|
|
1274
|
+
sortable.destroy = function (sortableElement) {
|
|
1275
|
+
_destroySortable(sortableElement);
|
|
1276
|
+
};
|
|
1277
|
+
sortable.enable = function (sortableElement) {
|
|
1278
|
+
_enableSortable(sortableElement);
|
|
1279
|
+
};
|
|
1280
|
+
sortable.disable = function (sortableElement) {
|
|
1281
|
+
_disableSortable(sortableElement);
|
|
1282
|
+
};
|
|
1283
|
+
/* START.TESTS_ONLY */
|
|
1284
|
+
sortable.__testing = {
|
|
1285
|
+
// add internal methods here for testing purposes
|
|
1286
|
+
_data: addData,
|
|
1287
|
+
_removeItemEvents: _removeItemEvents,
|
|
1288
|
+
_removeItemData: _removeItemData,
|
|
1289
|
+
_removeSortableData: _removeSortableData,
|
|
1290
|
+
_removeContainerEvents: _removeContainerEvents
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
return sortable;
|
|
1294
|
+
|
|
1295
|
+
}());
|