Search code examples
javascriptsvelteinnerhtml

Calling a javascript function with parameters in innerHTML


I'm trying to build my own comment system.

Here's the code that show a comment box when clicking on the "Reply" button:

function postComment(i, parentId) {
    let content;
    if (parentId === undefined) {
        content = commentBox.value
    } else {
        content = eval("subCommentBox"+i).value
    }
    const body = JSON.stringify({
        "post_slug": location.pathname.slice(1),
        "username": "quantong",
        "parent_id": parentId,
        "content": content,
    })
    fetch("http://localhost:8080/comments", {
        method: "post",
        headers: { "Content-Type": "application/json" },
        body: body
    })
        .then(resp => {
            if (resp.status === 200) {
                return resp.json()
            } else {
                console.log("Status: " + resp.status)
            }
        })
    commentBox.value = "";
    window.location.reload();
}

let allElements = document.body.getElementsByClassName("replybtn");

let addCommentField = function () {
    for (let i = 0; i < allElements.length; i++) {
        if (allElements[i] === this) {
            if (document.getElementsByClassName("replyform")[i].innerHTML.length === 0) {
                document.getElementsByClassName("replyform")[i].innerHTML = `
                    <div class="form-group">
                        <textarea class="form-control" id="subCommentBox`+i+`" rows="3"></textarea>
                    </div>
                    <div class="d-flex flex-row-reverse">
                        <button type="button" class="btn btn-success" onclick="postComment(` + i + `, ` + allElements[i].id + `)">Comment</button>
                    </div>
                `
            }
        }
    }
};

window.onload = function() {
    for (let i = 0; i < allElements.length; i++) {
        allElements[i].addEventListener('click', addCommentField, false)
    }
}

It worked fine if I put in a .js file.

The thing is after user is logged in, I want to pass username and profile picture as a body to the backend side, so I moved it to App.svelte file:

    let commentBox;
    function postComment(i, parentId) {
        let content;
        if (parentId === undefined) {
            content = commentBox
        } else {
            content = eval("subCommentBox"+i).value
        }
        const body = JSON.stringify({
            "post_slug": location.pathname.slice(1),
            "image_url": responsePayload.picture,
            "username": responsePayload.name,
            "parent_id": parentId,
            "content": content},
        )
        fetch("http://localhost:8090/comments", {
            method: "post",
            headers: { "Content-Type": "application/json" },
            body: body
        })
            .then(resp => {
                if (resp.status === 200) {
                    return resp.json()
                } else {
                    console.log("Status: " + resp.status)
                }
            })
        commentBox = "";
        window.location.reload();
    }

If I leave the innerHTML text as it is, it caused:

Uncaught ReferenceError: postComment is not defined

If I change it from:

<button type="button" class="btn btn-success" onclick="postComment(` + i + `, ` + allElements[i].id + `)">Comment</button>

to:

<button type="button" class="btn btn-success" on:click={() => postComment(i, allElements[i].id)}>Comment</button>

it will be rendered as:

enter image description here

So, in a .svelte file, how can I call a javascript function with parameters in innerHTML?


Solution

  • If you want to make this in Svelte you should try doing it the 'Svelte' way as well, that means dropping all this constructing html in javascript and injecting it.

    Instead consider your markup as a reflection of 'state' and also use components to make your life easier. Your state would be for example an array of comments and if a user clicked 'reply'.

    <!-- This will render a 'Comment' component for each item in the comments -->
    {#each comment as comment}
      <Comment {...comment} />
    {/each}
    
    <!-- Comment.svelte -->
    <script>
      export let id = "";
      export let children = [];
      export let text = "";
    
      let isReplying = false;
      let reply = ""
    
      async function postComment() {
        // Hide comment box
        isReplying = false;
        // Send to server, the inputted text is available in 'reply'
        const res =  await fetch(...).then(res => res.json();
        // Append to children so it appears 'live'
        children = [...children, res]
      }
    </script>
    
    <!-- The post itself -->
    <div>{text}</div>
    
    <!-- Each child is a comment of it's own so render it with this component -->
    {#each children as child}
      <svelte:self {...child} />
    {/each}
    
    <!-- The reply button simply toggles a state -->
    <button on:click={() => isReplying = !isReplying}>Reply</button>
    
    <!-- Show this based on state -->
    {#if isReplying}
      <textarea bind:value={reply}>Blabla</textarea>
      <button on:click={postComment}>Send</button>
    {/if isReplying}
    

    This should give you a fair idea of what direction to go in.

    Remember that your UI is a reflection of your state. So just changed the state and Svelte will take care of the rest (inserting, removing, updating domelements)