Сразу скину ссылку на рабочий код:
https://github.com/yankovalenko94/React_admin_pane...
Ниже будет мой код, который я переписываю уже на функциональных компонентах а не на классовых, как по ссылке:
Проблема в функции save, где не хочет работать cloneNode. То есть даже просто банально не хочет автозаполняться, его в списке методов для virtualDom нет при автозаполнении. Но в других функциях есть такое, допустим, в функции open оно работает, т.е. я не получал ошибок когда прописывал virtualDom.cloneNode(vitrualDom) там.
import "../helpers/iframeLoader.js"; // волшебная пилюля
import axios from "axios";
import React, { useState, useEffect } from "react";
const App = () => {
const [pageList, setPageList] = useState([]); //Это наш список страниц
const [newPageName, setNewPageName] = useState(""); //А это имя страницы
const [currentPage, setCurrentPage] = useState("index.html"); //Текущая страница
//const [virtualDom, setVirtualDom] = useState(null);
var iframe; //наш фрейм
var virtualDom;
//Загружаем список страниц
const loadPageList = () => {
axios.get("./api").then(response => setPageList(response.data)); //data - то что возвращает axios, в нем нужная информация
}
//Вызываем функцию init только когда впервые отрендерится компонент
useEffect(() => {
init(currentPage);
}, []);
//Принимает название страницы, открывает ее
const init = (page) => {
iframe = document.querySelector('iframe'); //наш фрейм: определяем его селектором
open(page); //открывает страницу
//loadPageList();
}
//Здесь мы устанавливаем путь динамический к нужной странице
const open = (page) => {
setCurrentPage(`../${page}?rnd=${Math.random()}`); //Устанавливаем путь к странице + сброс кэширования
//Посылаем запрос на сервер и получаем страницу
axios.get(`../${page}`).then(res => parseStringDOM(res.data)) //Получаем чистый исходный код страницы в виде строки и превращаем в DOM структуру
.then(wrapTextNodes) //Метод оборачивает все текстовые ноды и оборачиваем в свой спец. тег чтобы редачить его
.then(dom => { //Сохраняем "чистую" копию в virtual dom. На этом моменте чистая копия === загрязненной
virtualDom = dom;
return dom;
})
.then(domToString) //Превращаем DOM дерево в строку
.then(html => axios.post("./api/saveTemplate.php", {html})) //Отправка строки dom на сервер. На сервере создается новая html страница с нужной структурой
.then(() => iframe.load("../temp.html")) //Загружаем temp.html в iframe (страницу, созданную выше)
.then(() => enabelEditing()) //iframe готов - значит врубаем функцию для доступа к редактированию элементов
}
//Сох
const save = () => {
const newDom = virtualDom.cloneNoder(virtualDom); //Копируем данные, лежащие в виртуальном dom
unwrapTextNodes(newDom);
const html = domToString(newDom)
console.log(html)
};
const unwrapTextNodes = (dom) => {
dom.body.querySelectorAll("text-editor").forEach(element => { //Перебираем все наши теги кастомные
//console.log(element)
element.parentNode.replaceChild(element.firstChild, element); //Обращаемся к род. ноде элемента и заменяем ее дочернюю ноду
})
}
//Добавляем возможность редактирования
const enabelEditing = () => {
iframe.contentDocument.body.querySelectorAll("text-editor").forEach(element => {
element.contentEditable = "true";
element.addEventListener("input", () => { //Навешиваем обработчик событий чтобы синхронизировать копии
onTextEdit(element)
})
})
};
//Запустится когда текст отредактируется
const onTextEdit = (element) => {
const id = element.getAttribute("nodeid");
//Синхронизируем то что вводит пользователь с virtual dom ( чистой версией ), помещая введенную юзером информацию в чистую копию
virtualDom.body.querySelector(`[nodeid="${id}"]`).innerHTML = element.innerHTML; //То что ввел юзер (после равно) записывается в чистую копию
}
//Разбираем код страницы в DOM дерево
const parseStringDOM = (str) => {
const parser = new DOMParser(); //DOMParser парсит html/xml, содержащиеся в строке DOM
return parser.parseFromString(str, "text/html");
}
//Найти все текстовые ноды и обернуть их в редактируемые теги
const wrapTextNodes = (dom) => {
const body = dom.body; //получаем body тег нашего iframe
let textNodes = [];//Сюда складываем каждую отдельную ноду с текстом
function recursy (element) {
element.childNodes.forEach(node => { //Получаем всех детей element и выводим в консоль
//Здесь мы проверяем, нашли ли мы текстовую ноду и пустая ли это нода. Заменяем пробелы на пустой текст если встречаются и проверяем больше нуля ли текст
if(node.nodeName === "#text" && node.nodeValue.replace(/\s+/g, "").length > 0){ // если мы зашли в дочерний элемент и наткнулись на текст, то это то что надо
textNodes.push(node); //Пушим ноды в массив
}else{ //если наткнулись не на текст - снова перебираем всю страницу
recursy(node);
}
})
};
recursy(body); //запускаем функцию на body страницы, этим самым переходя по каждому дочернему элементу
//Даем каждой текстовой ноде возможность редактирования
textNodes.forEach((node, i) => {
const wrapper = dom.createElement("text-editor"); //Создаем свой тег враппеп для текста
node.parentNode.replaceChild(wrapper, node); //заменяем тег в который обернут текст своим тегом
wrapper.appendChild(node);
wrapper.setAttribute("nodeid", i);
});
return dom;
}
//Превращение DOM структуры в строку для отправки на сервер
const domToString = (dom) => {
//XMLSerializer может быть использован для конвертации веток DOM-дерева или дерева целиком в текст
const serializer = new XMLSerializer();
return serializer.serializeToString(dom);
}
//Создание новой страницы
const createPage = () => {
axios.post("./api/createPage.php", { //Делаем post запрос, указываем что мы отправляем
"name": newPageName
})
.then(loadPageList()) //В then действия с результатом, т.е. мы загружаем снова список чтобы он обновился
.catch(() => alert("Страница уже есть!")); //Обработка ошибок
};
//Обработка ввода инпута
const inputChangeHandler = (evt) => {
setNewPageName(evt.target.value);
}
//Удаление страницы
const deletePage = (page) => {
axios.post("./api/deletePage.php", { //Делаем post запрос, указываем что мы отправляем
"name": page //Посылаем page
}).then(loadPageList())
.catch(() => alert("Страница и так не существует"));
};
return(
<>
<button onClick={() => save()}>Клик!</button>
<iframe src={currentPage} frameBorder="0"></iframe>
</>
)
}
export default App;
Да, знаю, вопрос уже типовой, я знаю что значит эта ошибка но в этом случае я даже не представляю причины ее возникновения, в этот раз не могу избавиться:(