view .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
line wrap: on
line source

document.addEventListener( "DOMContentLoaded", function( event ) {

    function _( str ) {
        return __( str, "menu.mod.php" );
    }

    function select3( selects ) {
        selects.forEach( function( select ) {
            // именно keyup чтобы не пересекалось с input
            select.querySelector( "input" ).addEventListener( "keyup", function( e ) {
                api( { fn: "get_search_pages_list", search: this.value }, function( r ) {
                    if ( r.html ) {
                        select.querySelector( ".list-search" ).innerHTML = r.html;
                        select.querySelectorAll( ".list-search li" ).forEach( function( li ) {
                            li.addEventListener( "click", select3_li );
                        } );
                    }
                } );
            } );
            // prevent close dropdown when click on input
            select.querySelector( "input" ).addEventListener( "click", function( e ) {
                e.stopPropagation();
            } );
            // show/hide dropdown list
            select.querySelector( ".field-select" ).addEventListener( "click", function ( event ) {
                event.stopPropagation();
                this.parentElement.classList.toggle( "open" );
                let list = event.currentTarget.nextElementSibling;

                // это можно убрать если переставить стили на родителя
                list.classList.toggle( "open" );
                
                if ( list.classList.contains( "open" ) ) {
                    let input = select.querySelector( "input" );
                    input.focus();
                    input.dispatchEvent( new Event( "keyup" ) );
                }
            } );
        } );
    }

    // click selected item
    function select3_li( e ) {
        let id    = this.getAttribute( "data-id" );
        let url   = this.getAttribute( "data-url" );
        let title = this.innerText;
        let input_title = this.closest( ".select-grid" ).querySelector( ".field-select" );
        let old_id = input_title.getAttribute( "data-old-id" );
        input_title.querySelector( ".value" ).innerText = title;
        input_title.setAttribute( "data-id", id );
        // поменять url в поле ввода
        let input_url = this.closest( ".menu-prop" ).querySelector( "input[name='url']" );
        input_url.value = url;
        // поменять заголовок и ссылку вверху
        let a = this.closest( ".item" ).querySelector( ":scope > a" );
        a.setAttribute( "href", cms.base_path + url );
        let replaced_title = this.closest( ".menu-prop" ).querySelector( `input[name="title"]` ).value;
        if ( replaced_title ) title = replaced_title;
        a.innerText = title;
        if ( id === "0" ) {
            input_url.removeAttribute( "disabled" );
        } else {
            input_url.setAttribute( "disabled", true );
        }
        // remove li except data-id=0
        Array.from( this.parentElement.children ).forEach( function( li ) {
            if ( li.getAttribute( "data-id" ) !== "0" ) {
                li.remove();
            }
        } );
        // close dropdown list
        input_title.click();
        // изменение
        if ( old_id != id ) {
            input_title.dispatchEvent( new Event( "input" ) );
        }
    }

    api( { fn: "get_menu_items" }, set_menu_items );

    function set_menu_items( r ) {

        document.querySelector( "#menu .menu-grid" ).innerHTML = r.list;

        // set parents for each menu item
        document.querySelectorAll( "#menu [data-parent]" ).forEach( function( el ) {
            el.nextElementSibling.insertAdjacentHTML( "beforeend", r.parents );
            let pid = el.getAttribute( "data-parent" );
            let parent = el.nextElementSibling.querySelector( `[value="${pid}"]` ).innerText.trim();
            el.querySelector( ".value" ).innerText = parent;
            // remove self
            let self   = el.closest( "[data-item]" ).getAttribute( "data-item" );
            self = el.nextElementSibling.querySelector( `[value="${self}"]` );
            if ( self ) {
                self.remove();
            }
        } );

        // selects
        let selects = document.querySelectorAll( "#menu .select-grid" );
        select3( selects );

        // Select
        document.querySelectorAll( "#menu .area-select-grid .field-select-menu, #menu .parent-select-grid .field-select" ).forEach( function( select ) {
            select.addEventListener( "click", function( e ) {
                e.stopPropagation();
                this.parentElement.classList.toggle( "open" );

                // это можно убрать если переставить стили на родителя
                select.nextElementSibling.classList.toggle( "open" );
            } );
        } );
        // Option for menu
        document.querySelectorAll( "#menu .area-select-grid .field-options .option" ).forEach( function( option ) {
            option.addEventListener( "click", function( e ) {
                let input = this.closest( ".area-select-grid" ).querySelector( ".field-select-menu" );
                let old_val = input.getAttribute( "data-menu-area" );
                let new_val = this.getAttribute( "value" );
                if ( old_val != new_val ) {
                    input.querySelector( ".value" ).innerText = this.innerText;
                    input.setAttribute( "data-menu-area", new_val );
                    // Событие изменения
                    let event = new Event( "input" );
                    input.dispatchEvent( event );
                }
                //e.stopPropagation(); убираем чтобы закрылось автоматически
            } );
        } );
        // Option for item
        document.querySelectorAll( "#menu .parent-select-grid .field-options .option" ).forEach( function( option ) {
            option.addEventListener( "click", parent_li_click );
        } );
        function parent_li_click( e ) {
            let input = this.closest( ".parent-select-grid" ).querySelector( ".field-select" );
            let old_val = input.getAttribute( "data-parent" );
            let new_val = this.getAttribute( "value" );
            if ( old_val != new_val ) {
                input.querySelector( ".value" ).innerText = this.innerText.trim();
                input.setAttribute( "data-parent", new_val );
                // Событие изменения
                let event = new Event( "input" );
                input.dispatchEvent( event );
            }
            //e.stopPropagation(); убираем чтобы закрылось автоматически
        }

        // Toggle Menu Properties
        let prop_buttons = document.querySelectorAll( "#menu .menu-buttons .prop" );
        prop_buttons.forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                e.stopPropagation();
                this.closest( "[data-item]" ).classList.toggle( "open" );
            } );
        } );

        // Отлавливать изменения полей в меню чтобы показать Сохранить
        document.querySelectorAll( "#menu .menu" ).forEach( function( menu ) {
            menu.querySelectorAll( "input, .field-select-menu" ).forEach( function( input ) {
                input.addEventListener( "input", function( event ) {
                    let menu = this.closest( ".menu" );
                    menu.classList.add( "changed" );
                } );
            } );
        } );

        // Отлавливать изменения полей в пунктах чтобы показать Сохранить
        document.querySelectorAll( "#menu .item" ).forEach( function( item ) {
            item.querySelectorAll( "input, .field-select" ).forEach( function( input ) {
                input.addEventListener( "input", function( event ) {
                    let item = this.closest( ".item" );
                    item.classList.add( "changed" );
                } );
            } );
        } );

        // Save Properties
        document.querySelectorAll( "#menu .menu-buttons .save" ).forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                e.stopPropagation();

                let item = this.closest( "[data-item]" );
                let mid  = item.getAttribute( "data-item" );

                let area = item.querySelector( "[data-menu-area]" );
                if ( area ) {
                    area = area.getAttribute( "data-menu-area" );
                }

                let tag_title = item.querySelector( "[name='tag_title']" );
                if ( tag_title ) {
                    tag_title = tag_title.value;
                }

                let url = item.querySelector( "[name='url']" );
                if ( url ) {
                    url = url.value;
                }

                let id = item.querySelector( "[name='id']" );
                if ( id ) {
                    id = id.getAttribute( "data-id" );
                }

                let pid_el = item.querySelector( "[data-parent]" );
                let pid,old_pid;
                if ( pid_el ) {
                    pid = pid_el.getAttribute( "data-parent" );
                    old_pid = pid_el.getAttribute( "data-old-parent" );
                }

                let target = item.querySelector( "[name='targetblank']" );
                if ( target ) {
                    target = target.checked;
                }

                let old_sort_el = item.querySelector( "[name='sort']" );
                let old_sort = old_sort_el.getAttribute( "data-sort" );

                let data = {
                    fn:        "save_menu_item",
                    mid:       mid,
                    title:     item.querySelector( "[name='title']" ).value,
                    tag_title: tag_title,
                    url:       url,
                    id:        id,
                    pid:       pid,
                    old_pid:   old_pid,
                    classes:   item.querySelector( "[name='classes']" ).value,
                    sort:      item.querySelector( "[name='sort']" ).value,
                    area:      area,
                    target:    target
                }
                document.querySelectorAll( `#menu [data-item]` ).forEach( function( el ) {
                    el.classList.remove( "last-edited" );
                } );
                api( data, function( r ) {
                    if ( r.ok == "false" ) {
                        notify( r.info_text, r.info_class, r.info_time );
                    }
                    if ( r.ok == "true" ) {
                        item.classList.remove( "changed" );
                        let item_with_childs = item.parentElement;
                        if ( r.list ) {
                            set_menu_items( r );
                        } else if ( old_sort != data.sort ) {
                        // пересортировка имеет смысл если не было замены всего списка
                            // обновить сортировку для сравнения
                            old_sort_el.setAttribute( "data-sort", data.sort );
                            let grid;
                            if ( data.area === null ) {
                                grid = item.closest( ".items-grid" );
                            } else {
                                grid = item.closest( ".menu-grid" );
                            }
                            let before = null;
                            for ( let n = 0; n < grid.children.length; n++ ) {
                                let node = grid.children[n];
                                if ( node != item_with_childs ) {
                                    let sort = node.querySelector( '[data-item] [name="sort"]' ).value;
                                    if ( +data.sort < +sort ) {
                                        before = node;
                                        break;
                                    }
                                }
                            };
                            grid.insertBefore( item_with_childs, before );
                        }
                        
                        // Замена title
                        let title;
                        if ( data.title != "" )
                            title = data.title;
                        else
                            title = item.querySelector( ".select-grid .value" ).innerText;
                        item.querySelector( ":scope > a" ).innerText = title;
                        
                        // обновить родителей у подпунктов
                        // поскольку мог измениться title
                        // и только у строго дочерних, которых может и не быть
                        let childs_grid = item.nextElementSibling;
                        if ( childs_grid ) {
                            let childs_selector = childs_grid.querySelectorAll( ":scope > div > .item .parent-select-grid" );
                            
                            if ( childs_selector ) childs_selector.forEach( function( grid ) {
                                let input = grid.querySelector( "[data-parent]" );
                                let pid = input.getAttribute( "data-parent" );
                                let options = grid.querySelector( ":scope > .field-options" );
                                options.innerHTML = r.parents;
                                options.querySelectorAll( ".option" ).forEach( function( option ) {
                                    option.addEventListener( "click", parent_li_click );
                                } );
                                // тайтл вычисляется выше
                                input.querySelector( ".value" ).innerText = title;
                            } );
                        }
                        
                        // Last Edited Marker
                        setTimeout( function() {
                            document.querySelector( `#menu [data-item="${data.mid}"]` ).classList.add( "last-edited" );
                        }, 200 );
                    }
                } );
            } );
        } );

        // Delete Menu or Item
        document.querySelectorAll( "#menu .menu-buttons .del" ).forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                e.stopPropagation();
                clear_highlite();
                let item = button.closest( ".item,.menu" ).parentNode;
                item.classList.add( "will-be-deleted" );
                setTimeout( function() {
                if ( ! confirm( __( "confirm_delete", "menu.mod.php" ) ) ) {
                    item.classList.remove( "will-be-deleted" );
                    return;
                }
                let data = {
                    fn: "del_menu_item",
                    mid: button.closest( "[data-item]" ).getAttribute( "data-item" )
                };
                api( data, function( r ) {
                    if ( r.info_text ) {
                        notify( r.info_text, r.info_class, r.info_time );
                        if ( r.info_class == "info-success" ) {
                            set_menu_items( r );
                        }
                    }
                } );
                }, 20 );
            } );
        } );

        // Create Item
        document.querySelectorAll( "#menu .main-main .append" ).forEach( function( button ) {
            button.addEventListener( "click", modMenuCreate );
        } );

        // Линия к родителю
        let items = document.querySelectorAll( "#menu .item" );
        items.forEach( function( el ) {
            el.addEventListener( "click", function( e ) {
                // Если опускание мыши было не на нас, то игнорим
                if ( mouse_down_el != this ) return;
                mouse_down_el = null;
                let highlited = this.classList.contains( "highlite-children" );
                clear_highlite();
                // Назначить классы
                if ( ! highlited ) {
                    this.classList.add( "highlite-children" );
                    this.closest( ".items-grid" ).classList.add( "highlite-parent" );;
                }
            } );
        } );
        // Опускание мыши
        // Возможна ситуация когда нажатие мыши было на кнопке Свойства,
        // а отпускание на плашке. В этом случае может появиться линия к родителю.
        // Поэтому нужно записывать что опускание было на плашке или панельке кнопок.
        let mouse_down_el; // элемент на котором было опускание мыши
        items.forEach( function( el ) {
            el.addEventListener( "mousedown", function( e ) {
                // чтобы проигнорировать клики по дочерним
                if ( e.target == this )
                    mouse_down_el = this;
            } );
            // див с кнопками можно тыкать
            let mb = el.querySelector( ".menu-buttons" );
            if ( mb ) mb.addEventListener( "mousedown", function( e ) {
                // чтобы проигнорировать клики по дочерним
                if ( e.target == this )
                    mouse_down_el = this.parentElement;
            } );
        } );
        // Сбросить классы
        function clear_highlite() {
            document.querySelectorAll( "#menu .items-grid.highlite-parent" ).forEach( function( el ) {
                el.classList.remove( "highlite-parent" );
            } );
            document.querySelectorAll( "#menu .item.highlite-children" ).forEach( function( el ) {
                el.classList.remove( "highlite-children" );
            } );
        }

    }

    // Select
    // Закрытие выпадающих списков при кликах вне их, а так же по ним
    document.body.addEventListener( "click", function( e ) {
        // это можно убрать если переставить стили на родителя
        document.querySelectorAll( "#menu .field-options" ).forEach( function( list ) {
            list.classList.remove( "open" );
        } );
        
        document.querySelectorAll( "#menu .area-select-grid, #menu .select-grid, #menu .parent-select-grid" ).forEach( function( list ) {
            list.classList.remove( "open" );
        } );
    } );

    // Создание меню или пункта меню
    function modMenuCreate( e ) {
        e.stopPropagation();
        let pid = this.closest( "[data-item]" ).getAttribute( "data-item" );
        api( { fn : "create_menu_item", pid : pid }, function( r ) {
            if ( r.info_text ) {
                notify( r.info_text, r.info_class, r.info_time );
                if ( r.info_class == "info-success" ) {
                    set_menu_items( r );
                    let item = document.querySelector( `#menu [data-item="${r.mid}"]` );
                    item.classList.add( "open" );
                    item.classList.add( "last-edited" );
                    // Докрутить к новому элементу
                    item.scrollIntoView( { behavior: "smooth", block: "center" } );
                }
            }
        } );
    }
    
    // Create Menu
    document.querySelector( "#menu .main-footer .create" ).addEventListener( "click", modMenuCreate );
    
    // update page used in menu
    document.body.addEventListener( "update_menu", function( e ) {
        api( { fn: "get_menu_items" }, set_menu_items );
    } );

} );