-
Notifications
You must be signed in to change notification settings - Fork 18
/
vanilla_nested.js
150 lines (121 loc) · 5.01 KB
/
vanilla_nested.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
(function(){
// Get the html from the data attribute and insert the new fields on the container
// "event" is the click event of the link created by the rails helper
window.addVanillaNestedFields = function(event) {
event.preventDefault();
let element = event.target;
if (!element.classList.contains('vanilla-nested-add'))
element = element.closest('.vanilla-nested-add')
const data = element.dataset;
const container = document.querySelector(data.containerSelector);
const newHtml = data.html.replace(/_idx_placeholder_/g, Date.now());
let inserted;
switch (data.methodForInsert) {
case ('append'):
container.insertAdjacentHTML('beforeend', newHtml);
inserted = container.lastElementChild;
break;
case ('prepend'):
container.insertAdjacentHTML('afterbegin', newHtml);
inserted = container.firstElementChild;
break;
}
_dispatchEvent(container, 'vanilla-nested:fields-added', element, {added: inserted})
let removeLink = inserted.querySelector('.vanilla-nested-remove');
if (removeLink)
removeLink.addEventListener('click', removeVanillaNestedFields, true);
// dispatch an event if we reached the limit configured on the model
if (data.limit) {
let nestedElements = container.children.length;
if (nestedElements >= data.limit)
_dispatchEvent(container, 'vanilla-nested:fields-limit-reached', element)
}
}
// Removes the fields or hides them until the undo timer times out
// "event" is the click event of the link created by the rails helper
window.removeVanillaNestedFields = function(event) {
event.preventDefault();
let element = event.target;
if (!element.classList.contains('vanilla-nested-remove'))
element = element.closest('.vanilla-nested-remove')
const data = element.dataset;
let wrapper = element.parentElement;
if (sel = data.fieldsWrapperSelector) wrapper = element.closest(sel);
if (data.undoTimeout) {
hideFieldsWithUndo(wrapper, element);
_dispatchEvent(wrapper, 'vanilla-nested:fields-hidden', element);
} else {
hideWrapper(wrapper);
unhideFields(wrapper);
_dispatchEvent(wrapper, 'vanilla-nested:fields-removed', element);
}
wrapper.querySelector('[name$="[_destroy]"]').value = '1';
}
// Hides an element, mainly the wrapper of a group of fields
// "wrapper" is the wrapper of the link to remove fields
function hideWrapper(wrapper) {
wrapper.style.display = 'none';
}
// Unhides the children given a fields wrapper
// "wrapper" is the wrapper of the link to remove fields
function unhideFields(wrapper) {
[...wrapper.children].forEach(child => child.style.display = 'initial');
}
// Hides an element and adds an "undo" link to unhide it
// "wrapper" is the wrapper to hide
// "element" is the link to remove the wrapper
function hideFieldsWithUndo(wrapper, element) {
[...wrapper.children].forEach(child => child.style.display = 'none');
// add the 'undo' link with it's callback
const undoLink = _createUndoWithElementsData(element.dataset);
wrapper.appendChild(undoLink);
const _onUndoClicked = function(e) {
e.preventDefault();
clearTimeout(timer);
unhideFields(wrapper);
wrapper.querySelector('[name$="[_destroy]"]').value = '0';
_dispatchEvent(wrapper, 'vanilla-nested:fields-hidden-undo', undoLink);
undoLink.remove();
}
undoLink.addEventListener('click', _onUndoClicked);
// start the timer
const _onTimerCompleted = function() {
hideWrapper(wrapper);
unhideFields(wrapper);
_dispatchEvent(wrapper, 'vanilla-nested:fields-removed', undoLink);
undoLink.remove();
}
let ms = element.dataset.undoTimeout;
let timer = setTimeout(_onTimerCompleted, ms);
}
function _dispatchEvent(element, eventName, triggeredBy, details) {
if (!details) details = {};
details.triggeredBy = triggeredBy;
let event = new CustomEvent(eventName, {bubbles: true, detail: details})
element.dispatchEvent(event);
}
function _createUndoWithElementsData(data) {
const undo = document.createElement('A');
undo.classList.add('vanilla-nested-undo');
if (classes = data.undoLinkClasses)
undo.classList.add(...classes.split(' '));
undo.innerText = data.undoText;
return undo;
}
function initVanillaNested() {
document.querySelectorAll('.vanilla-nested-add').forEach(el => {
el.addEventListener('click', addVanillaNestedFields, true);
})
document.querySelectorAll('.vanilla-nested-remove').forEach(el => {
el.addEventListener('click', removeVanillaNestedFields, true);
})
}
document.addEventListener('DOMContentLoaded', function(){
initVanillaNested();
})
// Don't run turbolinks event callback for first load, we already do it with DOMContentLoaded
const notEmpty = (obj) => Object.keys(obj).length;
document.addEventListener('turbolinks:load', function(e){
if (notEmpty(e.data.timing)) initVanillaNested();
})
})()