8

Building a Full Stack Application From Scratch with Svelte and Node (PART 4) — A...

 3 years ago
source link: https://tahazsh.com/fullstack-app-with-svelte-and-node-part-4
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

In the previous part, we displayed comments and replies in the story page. Now let's add the needed inputs for users to create them.

Creating CommentInput component

This component will be responsible for displaying the comments form and submitting it to the server to save it in the database (which we'll do later).

Create routes/story/_CommentInput.svelte, and put the following into it:

<script>
  let comment = {
    content: '',
    parent: null
  }

  function submit () {
    console.log('form is submitted')
  }
</script>

<form
  class="comment-form"
  on:submit|preventDefault="{submit}"
>
  <textarea
    class="comment-input"
    bind:value="{comment.content}"
    placeholder="Write your comment"
    required
  ></textarea>

  <button
    class="add-comment-button primary-button"
  >
    Add Comment
  </button>
</form>

As you can see above, we're binding comment.content to the textarea, and when it's submitted, we call the submit() function.

Since this input creates a comment, not a reply, we sat the comment.parent to null.

Submitting the form

When this form is submitted, we'll make a request to the server with its content so the server will create it, persist it, and return it back to the client to display it.

Since we don't have the server yet, let's mock that behavior and just return it with some test values.

Now let's update the submit function to this:

function submit () {
  // this should eventually be returned from the server
  const newComment = {
    score: 0,
    _id: Math.round((Math.random() * 99999)),
    content: comment.content,
    user: {
      username: 'test user',
    },
    createdAt: 'a few seconds ago',
    replies: []
  }

  dispatch('created', { comment: newComment })

  comment = {
    content: '',
    parent: null
  }
}

So after the server returns the comment, we should pass it back to the parent component (_CommentContainer.svelte) so it appends it to the list of displayed comments.

We're using dispatch to send it back to _CommentContainer, but for that to work, we have to import the dispatcher first.

So at the top of this file, add this:

<script>
  import { createEventDispatcher } from 'svelte'
  const dispatch = createEventDispatcher()

Displaying _CommentInput in _CommentContainer

First, import that component:

import CommentInput from './_CommentInput.svelte'

Then, display it above <div class="comment-list">.

<CommentInput
  on:created="{({ detail }) => comments = [...comments, detail.comment]}"
/>

We're here listening for the created event, which we sent from the submit function, to add that comment to the list of comments we're displaying.

Now it's time to test your work. You should see the comment input displayed like this:

1.png

Creating ReplyInput component

This component is for the reply input that we show under a specific comment.

We should show a "reply" button besides each comment. When the user clicks it, we should show that input below it.

Before we add that "reply" button, let's create the reply form.

Create routes/story/_ReplyInput.svelte with the following code:

<script>
  import { createEventDispatcher } from 'svelte'

  const dispatch = createEventDispatcher()

  export let parentCommentId

  let comment = {
    content: '',
    parent: parentCommentId
  }

  async function submit () {
    const newComment = {
      score: 0,
      _id: Math.round((Math.random() * 99999)),
      content: comment.content,
      user: {
        username: 'test user 2',
      },
      createdAt: 'a few seconds ago',
      replies: []
    }
    dispatch('created', { comment: newComment })
  }
</script>

<form
  class="reply-form"
  on:submit|preventDefault="{submit}"
>
  <textarea
    class="reply-input"
    bind:value="{comment.content}"
  ></textarea>
  <div class="reply-buttons-container">
    <button
      type="button"
      class="cancel-reply-button text-button"
      on:click="{() => dispatch('cancel')}"
    >
      Cancel
    </button>
    <button
      class="reply-button primary-button"
    >
      Reply
    </button>
  </div>
</form>

It's like the comment input but with two differences:

  • We're accepting a prop called parentCommentId which we use for the parent value for the reply. That value should be the comment id the user is replying to.
  • We dispatch a cancel event when the cancel button is clicked. This event is to close the reply form without submitting it.

Displaying the reply input

This component should be displayed inside the _Comment component. And it should only be displayed if the "reply" button is clicked.

Add the following code in _Comment.svelte above {#if comment.replies && comment.replies.length}:

{#if showReplyForm}
  <ReplyInput
    parentCommentId="{comment._id}"
    on:created="{addReply}"
    on:cancel="{() => showReplyForm = false}"
  />
{/if}

And don't forget to import it at the top:

import ReplyInput from './_ReplyInput.svelte'

Note how we're passing the parent comment id through parentCommentId.

We need to do two more changes to make this component work:

  • Create the "reply" button that toggles the showReplyForm value.
  • Add and implement the addReply function.

Let's start with the "reply" button.

Update the <div class="main"> element like this:

<div class="main">
  <div class="byline">
    <span class="author">{comment.user.username}</span>
    <span class="date">{comment.createdAt}</span>
    <span
      class="show-reply-form-button"
      on:click="{() => showReplyForm = true}"
    >reply</span>
  </div>
  <p class="comment-content">{comment.content}</p>
</div>

This element now contains the "reply" button, which if you click, showReplyForm will be set to true.

Next, let's create that variable below export let comment:

let showReplyForm = false

Our last step is to create addReply function, which we trigger on the created event on the ReplyInput component.

function addReply ({ detail }) {
  comment.replies = [...comment.replies, detail.comment]
  showReplyForm = false
}

It looks similar to how we add a new top-level comment except that we set showReplyForm = false to close it after that.

Posting a reply to any comment should now work!

2.png

What's next?

In the next part, we'll implement the story form component for creating and editing stories. We'll learn how to make that component reusable for both create and edit story pages. It's a great way to keep your code DRY and clean.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK