How to show items in nested object one at each click of a button


#1

Hi, I have a list of chapters, pages and footnotes and want to display the footnotes one at the time whenever I click a button.
I need to start from chapter[0].page[0].footnote[0] and then change page and chapter when the footnotes in page[0] and then in chapter[0] are finished.

When I run the code I receive several errors in the child component ‘TextFootnote.vue’:

  • Property or method “footnote” is not defined on the instance but referenced during render.
  • TypeError: Cannot read property ‘text’

I can fix the errors by dispatching and getting chapter[0].page[0] from state and removing the v-for in parent component, but then I can’t display only one foot note.

Since I cannot figure out where the problem is, I will try to be as detailed as possible and hope someone can help me identifying what I am doing wrong.

The data looks like this:

[
  {
    "story_id": 2,
    "chapter": [
      {
        "id": 1,
        "page": [
          {
            "id": 1,
            "footnote": [
              {
                "id": 1,
                "text": "footnote"
              },
              {
                "id": 2,
                "text": "another Footnote"
              }
            ]
          },
          {
            "id": 2,
            "footnote": [
              {
                "id": 1,
                "text": "yet another footnote"
              }
            ]
          }
        ]
      },
      {
        "id": 2,
        "page": [
          {
            "id": 1,
            "footnote": [
              {
                "id": 1,
                "text": "final footnote"
              }
            ]
          }
        ]
      }
    ]
  }
]

which have been put in state like this:

`

this.$store.dispatch(‘setChapter’, this.storyToRead[0].chapter)

`

My parent component ‘ReadSection.vue’ is:

<template>
  <div class="story-section screen-tap" v-on:click="addFootnote()">
    <div class="story-wrapper">
      <textFootnote class="story-bubble-wrapper" v-bind:is="textFootnote" >
          <div v-for="footnote in footnotes" v-bind:footnote="footnote" :key="footnote.id"></div>
        <!-- </div> -->
      </textFootnote>
    </div>
  </div>
</template>

<script>
import TextBubble from '@/components/TextFootnote.vue'

export default {
  prop: ['readSection'],
  name: 'read-section',
  components: {
    TextFootnote
  },
  data () {
    return {
      chapters: this.$store.getters.currentChapter,
      textFootnote : this.textFootnote,
      showButton: true
    }
  },
  computed: {
    footnotes: function () {
      var bubbles = []
      for (var i = 0; i < this.chapter.length; i++) {
        for (var k = 0; k < this.chapter[i].page.length; k++) {
          footnotes.push(this.chapter[i].page[k].footnote)
        }
      }
      return footnotes
    }
  },
  methods: {
    addFootnote () {
      this.textFootnote = 'text-footnote'
    }
  }
}
</script>

And my child component ‘TextFootnote.vue’ looks like this:

<template>
  <div class="container">
  <div class="message-container">
    <div class="message">{{footnote.text}}</div>
  </div>
</div>
</template>

<script>
export default {
  props: ['textFootnote'],
  name: 'text-footnote'
}
</script>

Note that my Parent component is also a child of another component. I don’t think it is relevant, so I have not included in here. Please let me know if you need to see it as well.

I hope you can help me identifying what am I doing wrong.


#2

All in all, a bit confused about some of your logic.

textFootnote : this.textFootnote

you’re setting data with itself?

footnotes: function () {
      var bubbles = []
      for (var i = 0; i < this.chapter.length; i++) {
        for (var k = 0; k < this.chapter[i].page.length; k++) {
          footnotes.push(this.chapter[i].page[k].footnote)
        }
      }
      return footnotes
    }

You define a bubbles array, but then push to a non existent footnotes array?

<textFootnote class="story-bubble-wrapper" v-bind:is="textFootnote" >
  <div v-for="footnote in footnotes" v-bind:footnote="footnote" :key="footnote.id"></div>
</textFootnote>

This is unusual syntax, what’s your goal here? Why the is binding? And why the nested div with a prop on it?

This should be written as

<textFootnote class="story-bubble-wrapper" v-for="footnote in footnotes" :textFootnote="footnote.text" :key="footnote.id"></textFootnote>

In your TextFootnote component, you reference footnote.text, but this doesn’t exist. I assume you mean to be using the prop textFootnote.

I imagine if you sort out a number of these issues then it should be more apparent as to where you need to adjust your logic.

That said, based on what you say here:

I need to start from chapter[0].page[0].footnote[0] and then change page and chapter when the footnotes in page[0] and then in chapter[0] are finished.

how can you display the correct footnotes if you aren’t keeping track of the current state? i.e. current page, current chapter


#3

Thank you for your comment. The ‘bubble’ word was due to an error in the code. Now I have adjusted it everywhere.

Based on your comment I have reworked my state and parent component. I am now able to display all the footnotes in a specific page without error.
I have eliminated the computed calculation to increment chapters and pages as for now i want to concentrate on the click functionality.

Unfortunately however as soon as I hit the button, all footnotes are displayed at once and not one at each click.

I have adjusted the parent component as per below. Note that '‘currentChapter’ ‘currentPage’ and ‘currentFootnote’ now store the current position ‘storyToRead.chapter[0].page[0].footnote’ so to read the footnote in the v-for.

<template>
  <div class="story-section screen-tap" v-on:click="addFootnote()">
    <div class="story-wrapper">
      <div class="story-footnote-wrapper">
          <component v-bind:is="footnoteComponent" v-for="footnote in footnote" v-bind:footnote="footnote" :key="footnote.id"></component>;
      </div>
    </div>
  </div>
</template>

<script>
import TextFootnote from '@/components/TextFootnote.vue'

export default {
  prop: ['readSection'],
  name: 'read-section',
  components: {
    TextFootnote
  },
  data () {
    return {
      storyToRead: this.$store.getters.storyToRead,
      currentChapter: this.$store.getters.currentChapter,
      currentPage: this.$store.getters.currentPage,
      currentFootnote: this.$store.getters.currentFootnote,
      textFootnote : this.textFootnote,
      showButton: true
    }
  },
  computed: {
    footnotes: function () {
    var footnotes = this.storyToRead.chapter[this.currentChapter].page[this.currentPage].footnote
    return footnotes
    }
  },
  methods: {
    addFootnote () {
      this.textFootnote = 'text-footnote'
    }
  }
}
</script>

The v-bind:is=“footnoteComponent” allows to dynamically view the child component with name ‘text-footnote’ at the click of a button. I had this idea by from a post.

However this works only once at the first click, while I need one footnote at each click of the button.

Any suggestion on how to achieve this?


#4

But why? Unless you plan to load other types of footnote components there, why not just use v-show?

Yes, because you’ve programmed the button to just set the component to text-footnote. At no point do you increment the currentPage or currentChapter.

You need to handle the paging logic. Here’s a basic example: https://jsfiddle.net/jamesbrndwgn/g2jbdxcr/


#5

What in fiddle do not work 100% as it shows all footnotes in a page with ID=1 all at once. However I made it work by changing your displayFootnotes into:

displayFootnotes () {
    	return this.displayPage.footnote[this.currentFootnote];
    },

with currentFootnote = 0

This displays only the first footnote in each page, but I can handle it with this.currentFootnote ++

However the real problem is that if I do the same in my code, it returns an error:

footnotes: function () {
    var footnotes = this.storyToRead.chapter[this.currentChapter].page[this.currentPage].footnote[this.currentFootnote]
    return footnotes
 }

where currentFootnote being in state and = to 0, gives a:

Error in render: “TypeError: Cannot read property ‘id’ of null”

this error is returned also if i do .footnote[0]


#6

Well, somewhere you are trying to reference id but it doesn’t exist.


#7

Yes, you are right. As we are not overflowing to another problem I close this issue.
Thank you for your help!