Функция, вызываемая слушателем события щелчка, вызывается без щелчка, но только при втором запуске - PullRequest
1 голос
/ 02 апреля 2020

Вот загадка:

Я пытаюсь создать игру для простого выбора пути, которая берет свое содержимое из локального JSON файла. При запуске он разбивает каждую сцену («Раздел» в файле JSON) на абзацы и добавляет первый абзац в DOM с прослушивателем событий, прикрепленным к окружающему div. Каждый раз, когда пользователь нажимает на «контейнер», из этого раздела добавляется еще один абзац, пока не отобразятся все абзацы.

После этого, если пользователь снова щелкает «контейнер», добавляются три варианта, каждый со своим собственным слушателем событий. При щелчке по одному из этих вариантов сцена (или «раздел») изменяется на выбранную. HTML «Контейнер» очищается, и процесс начинается снова, добавляя первый абзац новой сцены (раздела).

Проблема в том, что он отлично работает для первого раздела и вариантов, но после пользователь нажимает на выбор, он загружает два абзаца новой сцены, а не один. Он должен загрузить один абзац и ждать события нажатия, но на самом деле он загружает второй. Второй абзац загружается с помощью функции readyToUpdate, которая должна вызываться только прослушивателем событий (который не был запущен).

{  
    "Section_1": {
        "Content": "Dark corner paragraph one<>Dark corner paragraph two<>Dark corner paragraph three",
        "Choices": [
            "Go to the garden<>24",
            "Go to the terrace<>95",
            "Go to the beach<>145"
        ]

    },

    "Section_24": {
        "Content": "Garden paragraph one<>Garden paragraph two<>Garden paragraph three",
        "Choices": [
            "Go to the dark corner<>1",
            "Go to the terrace<>95",
            "Go to the beach<>145"
        ]
    },

    "Section_95": {
        "Content": "Terrace paragraph one<>Terrace paragraph two<>Terrace paragraph three",
        "Choices": [
            "Go to the dark corner<>1",
            "Go to the garden<>24",
            "Go to the beach<>145"
        ]
    },

    "Section_145": {
        "Content": "Beach paragraph one<>Beach paragraph two<>Beach paragraph three",
        "Choices": [
            "Go to the dark corner<>1",
            "Go to the garden<>24",
            "Go to the terrace<>95"
        ]
    }
}
    <div id="container"></div>
    fetch("js/contentFile.json")
    .then(response => response.json())
    .then(json => initialSetup(json));

    let narrativeText;

    let gameData = {
        section: "",
        paraCount: 0,
        choices: [],
        choiceText: [],
        choiceDest: [],
        paragraphs: "",
        paraSections: []
    };

    function updateParagraphs(){
        console.log("Enter updateParagraphs");
        let container = document.getElementById("container");

        let node = document.createElement("p");             
        let textnode = 
    document.createTextNode(gameData.paraSections[gameData.paraCount]);  
    node.appendChild(textnode);                    
    container.appendChild(node);
    container.addEventListener('click', readyToUpdate, {once: true});


        console.log(gameData.paraCount + " is less than " + 
        gameData.paraSections.length);
        console.log("Exit updateParagraphs");
        console.log(gameData);
    }

    function readyToUpdate(e) {

        console.log("Enter readyToUpdate");
        console.log(e);

        gameData.paraCount ++;
        let container = document.getElementById("container");   
        update();

        console.log("Exit readyToUpdate");
        console.log(gameData);
    }



    function choiceUpdater(e){
        console.log("Enter choiceUpdater");
        let choiceNumber = e.target.id.split("_")[1];
        gameData.section = "Section_" + gameData.choiceDest[choiceNumber];
        document.getElementById("container").innerHTML = "";
        gameData.paraCount = 0;
        update();
        console.log("Exit choiceUpdater");
        console.log(gameData);
    }

    function addChoices() {
        console.log("Enter addChoices");
        gameData.choices = narrativeText[gameData.section].Choices;
        console.log(gameData.choices);


        for (let i=0; i<gameData.choices.length; i++){
            let choice = document.createElement("h4");
            let choicesSplit = gameData.choices[i].split("<>");
            gameData.choiceText[i] = choicesSplit[0];
            gameData.choiceDest[i] = choicesSplit[1];
            let choiceTextNode = document.createTextNode(gameData.choiceText[i]);
            choice.appendChild(choiceTextNode);
            choice.setAttribute("id", "choice_" + i);
            choice.addEventListener("click", choiceUpdater, {once: true});
            document.getElementById("container").appendChild(choice);
        }
        console.log("Exit addChoices");
        console.log(gameData);
    }

function update() {
    console.log("Enter update");
    gameData.paragraphs = narrativeText[gameData.section].Content;
    gameData.paraSections = gameData.paragraphs.split("<>");

    if (gameData.paraCount < gameData.paraSections.length) {
        updateParagraphs();
    }

    else {
        addChoices();


    }
    console.log("Exit update");
    console.log(gameData);
}

function initialSetup(data) {
    console.log("Enter initial setup")
    narrativeText = data;
    gameData.section = "Section_1";
    gameData.paraCount = 0;
    update();
    console.log("Exit initial setup");
    console.log(gameData);
};

Обновление: при изменении функции updateParagraphs как ниже, это работает. Но, глядя на производительность в Chrome разработчику, количество слушателей продолжает расти. Я использую Chrome 80, поэтому он должен удалить их, используя свойство Once, но у меня были те же начальные проблемы, даже когда я вручную удалял слушателей с помощью removeEventListener ().

    function updateParagraphs(){
        console.log("Enter updateParagraphs");
        let container = document.getElementById("container");
        let node1 = document.createElement("div");
        node1.setAttribute("id", "innerContainer");
        document.getElementById("container").appendChild(node1);
        let innerContainer = document.getElementById("innerContainer");
        let node = document.createElement("p");             
        let textnode = 
        document.createTextNode(gameData.paraSections[gameData.paraCount]);  
        node.appendChild(textnode);                    
        innerContainer.appendChild(node);
        innerContainer.addEventListener('click', readyToUpdate, {once: true});
    }

Ответы [ 2 ]

2 голосов
/ 02 апреля 2020

Я верю, что когда вы щелкаете по выбору и вызываете прослушиватель щелчков выбора, вы проходите через update() и updateParagraphs() и добавляете новый прослушиватель щелчков в контейнер. В то же время, однако, событие click распространяется на контейнер и перехватывается новым добавленным слушателем контейнера.

Таким образом, у вас есть два варианта: первый, как предложено @ wth193, использовать setTimeout () для добавления слушателя контейнера на следующем тике.

function updateParagraphs(){
    // ...
    // container.addEventListener('click', readyToUpdate, {once: true});
    setTimeout(function () {
        container.addEventListener('click', readyToUpdate, {once: true});
    });
}

Более чистое (и более ясное) решение, на мой взгляд, заключалось бы в использовании Event.stopPropagation () .

function choiceUpdater(e){
  console.log("Enter choiceUpdater");
  e.stopPropagation();
  // ...
}

Таким образом, событие click захватывается только элементом выбора, а не контейнером.

2 голосов
/ 02 апреля 2020
function updateParagraphs(){
    ...
    // container.addEventListener('click', readyToUpdate, {once: true});
    setTimeout(function () {
        container.addEventListener('click', readyToUpdate, {once: true});
    });
}

Я полагаю, что setTimeout для прерывания стеков вызовов работает, но я не могу объяснить, почему.



...