comparison .cms/js/menu.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:78edf6b517a0
1 document.addEventListener( "DOMContentLoaded", function( event ) {
2
3 function _( str ) {
4 return __( str, "menu.mod.php" );
5 }
6
7 function select3( selects ) {
8 selects.forEach( function( select ) {
9 // именно keyup чтобы не пересекалось с input
10 select.querySelector( "input" ).addEventListener( "keyup", function( e ) {
11 api( { fn: "get_search_pages_list", search: this.value }, function( r ) {
12 if ( r.html ) {
13 select.querySelector( ".list-search" ).innerHTML = r.html;
14 select.querySelectorAll( ".list-search li" ).forEach( function( li ) {
15 li.addEventListener( "click", select3_li );
16 } );
17 }
18 } );
19 } );
20 // prevent close dropdown when click on input
21 select.querySelector( "input" ).addEventListener( "click", function( e ) {
22 e.stopPropagation();
23 } );
24 // show/hide dropdown list
25 select.querySelector( ".field-select" ).addEventListener( "click", function ( event ) {
26 event.stopPropagation();
27 this.parentElement.classList.toggle( "open" );
28 let list = event.currentTarget.nextElementSibling;
29
30 // это можно убрать если переставить стили на родителя
31 list.classList.toggle( "open" );
32
33 if ( list.classList.contains( "open" ) ) {
34 let input = select.querySelector( "input" );
35 input.focus();
36 input.dispatchEvent( new Event( "keyup" ) );
37 }
38 } );
39 } );
40 }
41
42 // click selected item
43 function select3_li( e ) {
44 let id = this.getAttribute( "data-id" );
45 let url = this.getAttribute( "data-url" );
46 let title = this.innerText;
47 let input_title = this.closest( ".select-grid" ).querySelector( ".field-select" );
48 let old_id = input_title.getAttribute( "data-old-id" );
49 input_title.querySelector( ".value" ).innerText = title;
50 input_title.setAttribute( "data-id", id );
51 // поменять url в поле ввода
52 let input_url = this.closest( ".menu-prop" ).querySelector( "input[name='url']" );
53 input_url.value = url;
54 // поменять заголовок и ссылку вверху
55 let a = this.closest( ".item" ).querySelector( ":scope > a" );
56 a.setAttribute( "href", cms.base_path + url );
57 let replaced_title = this.closest( ".menu-prop" ).querySelector( `input[name="title"]` ).value;
58 if ( replaced_title ) title = replaced_title;
59 a.innerText = title;
60 if ( id === "0" ) {
61 input_url.removeAttribute( "disabled" );
62 } else {
63 input_url.setAttribute( "disabled", true );
64 }
65 // remove li except data-id=0
66 Array.from( this.parentElement.children ).forEach( function( li ) {
67 if ( li.getAttribute( "data-id" ) !== "0" ) {
68 li.remove();
69 }
70 } );
71 // close dropdown list
72 input_title.click();
73 // изменение
74 if ( old_id != id ) {
75 input_title.dispatchEvent( new Event( "input" ) );
76 }
77 }
78
79 api( { fn: "get_menu_items" }, set_menu_items );
80
81 function set_menu_items( r ) {
82
83 document.querySelector( "#menu .menu-grid" ).innerHTML = r.list;
84
85 // set parents for each menu item
86 document.querySelectorAll( "#menu [data-parent]" ).forEach( function( el ) {
87 el.nextElementSibling.insertAdjacentHTML( "beforeend", r.parents );
88 let pid = el.getAttribute( "data-parent" );
89 let parent = el.nextElementSibling.querySelector( `[value="${pid}"]` ).innerText.trim();
90 el.querySelector( ".value" ).innerText = parent;
91 // remove self
92 let self = el.closest( "[data-item]" ).getAttribute( "data-item" );
93 self = el.nextElementSibling.querySelector( `[value="${self}"]` );
94 if ( self ) {
95 self.remove();
96 }
97 } );
98
99 // selects
100 let selects = document.querySelectorAll( "#menu .select-grid" );
101 select3( selects );
102
103 // Select
104 document.querySelectorAll( "#menu .area-select-grid .field-select-menu, #menu .parent-select-grid .field-select" ).forEach( function( select ) {
105 select.addEventListener( "click", function( e ) {
106 e.stopPropagation();
107 this.parentElement.classList.toggle( "open" );
108
109 // это можно убрать если переставить стили на родителя
110 select.nextElementSibling.classList.toggle( "open" );
111 } );
112 } );
113 // Option for menu
114 document.querySelectorAll( "#menu .area-select-grid .field-options .option" ).forEach( function( option ) {
115 option.addEventListener( "click", function( e ) {
116 let input = this.closest( ".area-select-grid" ).querySelector( ".field-select-menu" );
117 let old_val = input.getAttribute( "data-menu-area" );
118 let new_val = this.getAttribute( "value" );
119 if ( old_val != new_val ) {
120 input.querySelector( ".value" ).innerText = this.innerText;
121 input.setAttribute( "data-menu-area", new_val );
122 // Событие изменения
123 let event = new Event( "input" );
124 input.dispatchEvent( event );
125 }
126 //e.stopPropagation(); убираем чтобы закрылось автоматически
127 } );
128 } );
129 // Option for item
130 document.querySelectorAll( "#menu .parent-select-grid .field-options .option" ).forEach( function( option ) {
131 option.addEventListener( "click", parent_li_click );
132 } );
133 function parent_li_click( e ) {
134 let input = this.closest( ".parent-select-grid" ).querySelector( ".field-select" );
135 let old_val = input.getAttribute( "data-parent" );
136 let new_val = this.getAttribute( "value" );
137 if ( old_val != new_val ) {
138 input.querySelector( ".value" ).innerText = this.innerText.trim();
139 input.setAttribute( "data-parent", new_val );
140 // Событие изменения
141 let event = new Event( "input" );
142 input.dispatchEvent( event );
143 }
144 //e.stopPropagation(); убираем чтобы закрылось автоматически
145 }
146
147 // Toggle Menu Properties
148 let prop_buttons = document.querySelectorAll( "#menu .menu-buttons .prop" );
149 prop_buttons.forEach( function( button ) {
150 button.addEventListener( "click", function( e ) {
151 e.stopPropagation();
152 this.closest( "[data-item]" ).classList.toggle( "open" );
153 } );
154 } );
155
156 // Отлавливать изменения полей в меню чтобы показать Сохранить
157 document.querySelectorAll( "#menu .menu" ).forEach( function( menu ) {
158 menu.querySelectorAll( "input, .field-select-menu" ).forEach( function( input ) {
159 input.addEventListener( "input", function( event ) {
160 let menu = this.closest( ".menu" );
161 menu.classList.add( "changed" );
162 } );
163 } );
164 } );
165
166 // Отлавливать изменения полей в пунктах чтобы показать Сохранить
167 document.querySelectorAll( "#menu .item" ).forEach( function( item ) {
168 item.querySelectorAll( "input, .field-select" ).forEach( function( input ) {
169 input.addEventListener( "input", function( event ) {
170 let item = this.closest( ".item" );
171 item.classList.add( "changed" );
172 } );
173 } );
174 } );
175
176 // Save Properties
177 document.querySelectorAll( "#menu .menu-buttons .save" ).forEach( function( button ) {
178 button.addEventListener( "click", function( e ) {
179 e.stopPropagation();
180
181 let item = this.closest( "[data-item]" );
182 let mid = item.getAttribute( "data-item" );
183
184 let area = item.querySelector( "[data-menu-area]" );
185 if ( area ) {
186 area = area.getAttribute( "data-menu-area" );
187 }
188
189 let tag_title = item.querySelector( "[name='tag_title']" );
190 if ( tag_title ) {
191 tag_title = tag_title.value;
192 }
193
194 let url = item.querySelector( "[name='url']" );
195 if ( url ) {
196 url = url.value;
197 }
198
199 let id = item.querySelector( "[name='id']" );
200 if ( id ) {
201 id = id.getAttribute( "data-id" );
202 }
203
204 let pid_el = item.querySelector( "[data-parent]" );
205 let pid,old_pid;
206 if ( pid_el ) {
207 pid = pid_el.getAttribute( "data-parent" );
208 old_pid = pid_el.getAttribute( "data-old-parent" );
209 }
210
211 let target = item.querySelector( "[name='targetblank']" );
212 if ( target ) {
213 target = target.checked;
214 }
215
216 let old_sort_el = item.querySelector( "[name='sort']" );
217 let old_sort = old_sort_el.getAttribute( "data-sort" );
218
219 let data = {
220 fn: "save_menu_item",
221 mid: mid,
222 title: item.querySelector( "[name='title']" ).value,
223 tag_title: tag_title,
224 url: url,
225 id: id,
226 pid: pid,
227 old_pid: old_pid,
228 classes: item.querySelector( "[name='classes']" ).value,
229 sort: item.querySelector( "[name='sort']" ).value,
230 area: area,
231 target: target
232 }
233 document.querySelectorAll( `#menu [data-item]` ).forEach( function( el ) {
234 el.classList.remove( "last-edited" );
235 } );
236 api( data, function( r ) {
237 if ( r.ok == "false" ) {
238 notify( r.info_text, r.info_class, r.info_time );
239 }
240 if ( r.ok == "true" ) {
241 item.classList.remove( "changed" );
242 let item_with_childs = item.parentElement;
243 if ( r.list ) {
244 set_menu_items( r );
245 } else if ( old_sort != data.sort ) {
246 // пересортировка имеет смысл если не было замены всего списка
247 // обновить сортировку для сравнения
248 old_sort_el.setAttribute( "data-sort", data.sort );
249 let grid;
250 if ( data.area === null ) {
251 grid = item.closest( ".items-grid" );
252 } else {
253 grid = item.closest( ".menu-grid" );
254 }
255 let before = null;
256 for ( let n = 0; n < grid.children.length; n++ ) {
257 let node = grid.children[n];
258 if ( node != item_with_childs ) {
259 let sort = node.querySelector( '[data-item] [name="sort"]' ).value;
260 if ( +data.sort < +sort ) {
261 before = node;
262 break;
263 }
264 }
265 };
266 grid.insertBefore( item_with_childs, before );
267 }
268
269 // Замена title
270 let title;
271 if ( data.title != "" )
272 title = data.title;
273 else
274 title = item.querySelector( ".select-grid .value" ).innerText;
275 item.querySelector( ":scope > a" ).innerText = title;
276
277 // обновить родителей у подпунктов
278 // поскольку мог измениться title
279 // и только у строго дочерних, которых может и не быть
280 let childs_grid = item.nextElementSibling;
281 if ( childs_grid ) {
282 let childs_selector = childs_grid.querySelectorAll( ":scope > div > .item .parent-select-grid" );
283
284 if ( childs_selector ) childs_selector.forEach( function( grid ) {
285 let input = grid.querySelector( "[data-parent]" );
286 let pid = input.getAttribute( "data-parent" );
287 let options = grid.querySelector( ":scope > .field-options" );
288 options.innerHTML = r.parents;
289 options.querySelectorAll( ".option" ).forEach( function( option ) {
290 option.addEventListener( "click", parent_li_click );
291 } );
292 // тайтл вычисляется выше
293 input.querySelector( ".value" ).innerText = title;
294 } );
295 }
296
297 // Last Edited Marker
298 setTimeout( function() {
299 document.querySelector( `#menu [data-item="${data.mid}"]` ).classList.add( "last-edited" );
300 }, 200 );
301 }
302 } );
303 } );
304 } );
305
306 // Delete Menu or Item
307 document.querySelectorAll( "#menu .menu-buttons .del" ).forEach( function( button ) {
308 button.addEventListener( "click", function( e ) {
309 e.stopPropagation();
310 clear_highlite();
311 let item = button.closest( ".item,.menu" ).parentNode;
312 item.classList.add( "will-be-deleted" );
313 setTimeout( function() {
314 if ( ! confirm( __( "confirm_delete", "menu.mod.php" ) ) ) {
315 item.classList.remove( "will-be-deleted" );
316 return;
317 }
318 let data = {
319 fn: "del_menu_item",
320 mid: button.closest( "[data-item]" ).getAttribute( "data-item" )
321 };
322 api( data, function( r ) {
323 if ( r.info_text ) {
324 notify( r.info_text, r.info_class, r.info_time );
325 if ( r.info_class == "info-success" ) {
326 set_menu_items( r );
327 }
328 }
329 } );
330 }, 20 );
331 } );
332 } );
333
334 // Create Item
335 document.querySelectorAll( "#menu .main-main .append" ).forEach( function( button ) {
336 button.addEventListener( "click", modMenuCreate );
337 } );
338
339 // Линия к родителю
340 let items = document.querySelectorAll( "#menu .item" );
341 items.forEach( function( el ) {
342 el.addEventListener( "click", function( e ) {
343 // Если опускание мыши было не на нас, то игнорим
344 if ( mouse_down_el != this ) return;
345 mouse_down_el = null;
346 let highlited = this.classList.contains( "highlite-children" );
347 clear_highlite();
348 // Назначить классы
349 if ( ! highlited ) {
350 this.classList.add( "highlite-children" );
351 this.closest( ".items-grid" ).classList.add( "highlite-parent" );;
352 }
353 } );
354 } );
355 // Опускание мыши
356 // Возможна ситуация когда нажатие мыши было на кнопке Свойства,
357 // а отпускание на плашке. В этом случае может появиться линия к родителю.
358 // Поэтому нужно записывать что опускание было на плашке или панельке кнопок.
359 let mouse_down_el; // элемент на котором было опускание мыши
360 items.forEach( function( el ) {
361 el.addEventListener( "mousedown", function( e ) {
362 // чтобы проигнорировать клики по дочерним
363 if ( e.target == this )
364 mouse_down_el = this;
365 } );
366 // див с кнопками можно тыкать
367 let mb = el.querySelector( ".menu-buttons" );
368 if ( mb ) mb.addEventListener( "mousedown", function( e ) {
369 // чтобы проигнорировать клики по дочерним
370 if ( e.target == this )
371 mouse_down_el = this.parentElement;
372 } );
373 } );
374 // Сбросить классы
375 function clear_highlite() {
376 document.querySelectorAll( "#menu .items-grid.highlite-parent" ).forEach( function( el ) {
377 el.classList.remove( "highlite-parent" );
378 } );
379 document.querySelectorAll( "#menu .item.highlite-children" ).forEach( function( el ) {
380 el.classList.remove( "highlite-children" );
381 } );
382 }
383
384 }
385
386 // Select
387 // Закрытие выпадающих списков при кликах вне их, а так же по ним
388 document.body.addEventListener( "click", function( e ) {
389 // это можно убрать если переставить стили на родителя
390 document.querySelectorAll( "#menu .field-options" ).forEach( function( list ) {
391 list.classList.remove( "open" );
392 } );
393
394 document.querySelectorAll( "#menu .area-select-grid, #menu .select-grid, #menu .parent-select-grid" ).forEach( function( list ) {
395 list.classList.remove( "open" );
396 } );
397 } );
398
399 // Создание меню или пункта меню
400 function modMenuCreate( e ) {
401 e.stopPropagation();
402 let pid = this.closest( "[data-item]" ).getAttribute( "data-item" );
403 api( { fn : "create_menu_item", pid : pid }, function( r ) {
404 if ( r.info_text ) {
405 notify( r.info_text, r.info_class, r.info_time );
406 if ( r.info_class == "info-success" ) {
407 set_menu_items( r );
408 let item = document.querySelector( `#menu [data-item="${r.mid}"]` );
409 item.classList.add( "open" );
410 item.classList.add( "last-edited" );
411 // Докрутить к новому элементу
412 item.scrollIntoView( { behavior: "smooth", block: "center" } );
413 }
414 }
415 } );
416 }
417
418 // Create Menu
419 document.querySelector( "#menu .main-footer .create" ).addEventListener( "click", modMenuCreate );
420
421 // update page used in menu
422 document.body.addEventListener( "update_menu", function( e ) {
423 api( { fn: "get_menu_items" }, set_menu_items );
424 } );
425
426 } );