]> BookStack Code Mirror - bookstack/blob - resources/js/services/dom.js
Removed forced initial image manager display
[bookstack] / resources / js / services / dom.js
1 /**
2  * Create a new element with the given attrs and children.
3  * Children can be a string for text nodes or other elements.
4  * @param {String} tagName
5  * @param {Object<String, String>} attrs
6  * @param {Element[]|String[]}children
7  * @return {*}
8  */
9 export function elem(tagName, attrs = {}, children = []) {
10     const el = document.createElement(tagName);
11
12     for (const [key, val] of Object.entries(attrs)) {
13         if (val === null) {
14             el.removeAttribute(key);
15         } else {
16             el.setAttribute(key, val);
17         }
18     }
19
20     for (const child of children) {
21         if (typeof child === 'string') {
22             el.append(document.createTextNode(child));
23         } else {
24             el.append(child);
25         }
26     }
27
28     return el;
29 }
30
31 /**
32  * Run the given callback against each element that matches the given selector.
33  * @param {String} selector
34  * @param {Function<Element>} callback
35  */
36 export function forEach(selector, callback) {
37     const elements = document.querySelectorAll(selector);
38     for (const element of elements) {
39         callback(element);
40     }
41 }
42
43 /**
44  * Helper to listen to multiple DOM events
45  * @param {Element} listenerElement
46  * @param {Array<String>} events
47  * @param {Function<Event>} callback
48  */
49 export function onEvents(listenerElement, events, callback) {
50     for (const eventName of events) {
51         listenerElement.addEventListener(eventName, callback);
52     }
53 }
54
55 /**
56  * Helper to run an action when an element is selected.
57  * A "select" is made to be accessible, So can be a click, space-press or enter-press.
58  * @param {HTMLElement|Array} elements
59  * @param {function} callback
60  */
61 export function onSelect(elements, callback) {
62     if (!Array.isArray(elements)) {
63         elements = [elements];
64     }
65
66     for (const listenerElement of elements) {
67         listenerElement.addEventListener('click', callback);
68         listenerElement.addEventListener('keydown', event => {
69             if (event.key === 'Enter' || event.key === ' ') {
70                 event.preventDefault();
71                 callback(event);
72             }
73         });
74     }
75 }
76
77 /**
78  * Listen to enter press on the given element(s).
79  * @param {HTMLElement|Array} elements
80  * @param {function} callback
81  */
82 export function onEnterPress(elements, callback) {
83     if (!Array.isArray(elements)) {
84         elements = [elements];
85     }
86
87     const listener = event => {
88         if (event.key === 'Enter') {
89             callback(event);
90         }
91     };
92
93     elements.forEach(e => e.addEventListener('keypress', listener));
94 }
95
96 /**
97  * Set a listener on an element for an event emitted by a child
98  * matching the given childSelector param.
99  * Used in a similar fashion to jQuery's $('listener').on('eventName', 'childSelector', callback)
100  * @param {Element} listenerElement
101  * @param {String} childSelector
102  * @param {String} eventName
103  * @param {Function} callback
104  */
105 export function onChildEvent(listenerElement, childSelector, eventName, callback) {
106     listenerElement.addEventListener(eventName, event => {
107         const matchingChild = event.target.closest(childSelector);
108         if (matchingChild) {
109             callback.call(matchingChild, event, matchingChild);
110         }
111     });
112 }
113
114 /**
115  * Look for elements that match the given selector and contain the given text.
116  * Is case insensitive and returns the first result or null if nothing is found.
117  * @param {String} selector
118  * @param {String} text
119  * @returns {Element}
120  */
121 export function findText(selector, text) {
122     const elements = document.querySelectorAll(selector);
123     text = text.toLowerCase();
124     for (const element of elements) {
125         if (element.textContent.toLowerCase().includes(text)) {
126             return element;
127         }
128     }
129     return null;
130 }
131
132 /**
133  * Show a loading indicator in the given element.
134  * This will effectively clear the element.
135  * @param {Element} element
136  */
137 export function showLoading(element) {
138     element.innerHTML = '<div class="loading-container"><div></div><div></div><div></div></div>';
139 }
140
141 /**
142  * Get a loading element indicator element.
143  * @returns {Element}
144  */
145 export function getLoading() {
146     const wrap = document.createElement('div');
147     wrap.classList.add('loading-container');
148     wrap.innerHTML = '<div></div><div></div><div></div>';
149     return wrap;
150 }
151
152 /**
153  * Remove any loading indicators within the given element.
154  * @param {Element} element
155  */
156 export function removeLoading(element) {
157     const loadingEls = element.querySelectorAll('.loading-container');
158     for (const el of loadingEls) {
159         el.remove();
160     }
161 }
162
163 /**
164  * Convert the given html data into a live DOM element.
165  * Initiates any components defined in the data.
166  * @param {String} html
167  * @returns {Element}
168  */
169 export function htmlToDom(html) {
170     const wrap = document.createElement('div');
171     wrap.innerHTML = html;
172     window.$components.init(wrap);
173     return wrap.children[0];
174 }