1

I am trying to implement nested comments in vue.js and nuxt.js.

  • Each comment can have one or more children comments.
  • Each child comment, can again, have one or more children comments.
  • Unlimited levels of nested comments is possible.

As you can see in the diagram I have attached, I would like each comment to "know" (for the sake of simplicity, to display) the following information:

  1. The depth of the comment (I have this working already). Example, all of the "top-level" comments are at depth=0, all their children are at depth=1, and so on.
  2. The number of direct children
  3. the number of children (including nested children, unlimited levels deep)

enter image description here

I came across this question on StackOverflow but it doesn't quite do the trick. Or maybe I am doing something wrong.

In case you want to take a look at my (very messy) code, here it is. However, I'm willing to start over, so appreciate any pointers on how to pass the data up / down the chain of nested comments (vue components). Some sample code would be great.

components/PostComment.vue:

<template>
<div>


  <div class="tw-flex tw-flex-wrap tw-justify-end">
    <div :class="indent" class="tw-w-full tw-flex">

      <div class="tw-font-bold tw-p-4 tw-border-gray-400 tw-border tw-rounded tw-text-right">
        <div class="kb-card-section">
          <div class="kb-card-section-content tw-flex tw-flex-wrap tw-items-center tw-text-left">

            <div class="tw-flex tw-w-full">
              <div class="tw-hidden md:tw-block md:tw-w-2/12 tw-text-right tw-my-auto">
                <div class="tw-flex">
                  <p class="tw-w-full tw-text-xs tw-text-gray-600 tw-text-right">children: {{ numNestedChildComments }}, depth: {{depth}}</p>
                </div>
              </div>
            </div>

          </div>

        </div>

      </div>

    </div>

    <div class="tw-w-full" v-if="commentData.nested_comments" v-for="nestedComment in commentData.nested_comments">
      <post-comment 
        :commentData="nestedComment"
        :depth="depth + 1"
        :numChildCommentsOfParent=numNestedChildComments
      />
    </div>

  </div>


</div>
</template>

<script>

export default {
  name: 'post-comment', // necessary for recursive components / nested comments to work
  props: {
    depth: {
      type: Number,
      required: true
    },
    postAuthorData: {
      type: Object,
      required: true
    },
    commentAuthorData: {
      type: Object,
      required: true
    },
    commentData: {
      type: Object,
      required: true
    },
    numChildCommentsOfParent: {
      type: Number,
      required: true
    },
  },
  data() {
    return {
      numNestedChildComments: this.numChildCommentsOfParent,
    }
  },
  mounted() {
    this.incrementNumParentComments();
  },
  methods: {
    incrementNumParentComments() {
      this.numNestedChildComments++;
      this.$emit('incrementNumParentComments');
    },
  },
  computed: {
    indent() {
      switch (this.depth) {
        case 0:
          return "tw-ml-0 tw-mt-1";
        case 1:
          return "tw-ml-4 tw-mt-1";
        case 2:
          return "tw-ml-8 tw-mt-1";
        case 3:
        default:
          return "tw-ml-12 tw-mt-1";
      }
    },
  },
}

</script>
kp123
  • 1,250
  • 1
  • 14
  • 24
  • All of this information can be calculated upfront. Work with the data, not the components. – Decade Moon Nov 21 '19 at 20:34
  • I know it can be done up-front. I'm trying to do it when the components are initialized. The question is how? – kp123 Nov 21 '19 at 23:34

1 Answers1

1

Figured it out with some help from Rodrigo Pedra from the Laracasts community.

Here as a parent component calling the tree roots:

<template>
    <div>
        <MyTree v-for="item in records" :key="item.id" :item="item" />
    </div>
</template>

<script>
import MyTree from './MyTree';

const FIXTURE = [
    {
        id: 1,
        children: [
            {
                id: 2,
                children: [{id: 3}, {id: 4}, {id: 5}],
            },
            {
                id: 6,
                children: [
                    {id: 7},
                    {id: 8, children: [{id: 9}, {id: 10}]},
                ],
            },
        ],
    },
    {
        id: 11,
        children: [
            {id: 12, children: [{id: 13}, {id: 14}, {id: 15}]},
            {id: 16, children: [{id: 17}]},
            {id: 18},
        ],
    },
];

export default {
    components: {MyTree},

    data() {
        return {
            records: FIXTURE,
        };
    },
};
</script>
And here is the tree component:

<template>
    <div>
        <div style="border: 1px solid black; padding: 5px;" :style="offset">
            id: {{ item.id }}
            // depth: {{ depth }}
            // direct: {{ direct }}
            // children: {{ childrenCount }}
        </div>

        <template v-if="item.children">
            <MyTree
                v-for="record in item.children"
                :key="record.id"
                :item="record"
                :depth="depth + 1"
                @born="handleBorn()" />
        </template>
    </div>
</template>

<script>
const COLORS = [
    'white',
    'lightgray',
    'lightblue',
    'lightcyan',
    'lightskyblue',
    'lightpink',
];

export default {
    // MUST give a name in recursive components
    // https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components
    name: 'MyTree',

    props: {
        item: {type: Object, required: true},
        depth: {type: Number, default: 0},
    },

    data() {
        return {
            childrenCount: 0,
        };
    },

    computed: {
        direct() {
            if (Array.isArray(this.item.children)) {
                return this.item.children.length;
            }

            return 0;
        },

        offset() {
            return {
                'margin-left': (this.depth * 20) + 'px',
                'background-color': COLORS[this.depth % COLORS.length],
            };
        },
    },

    mounted() {
        this.$emit('born');
    },

    methods: {
        handleBorn() {
            this.childrenCount++;
            this.$emit('born');
        },
    },
};
</script>

Screenshot:

enter image description here

kp123
  • 1,250
  • 1
  • 14
  • 24