2

Currently I'm using Laravel 5.2 with Elixir 5 and Vue 2.0.

I'm stuck on how to access and imported component's data from the element view using something like v-if.

Here's my setup:

resources\views\vuetest.blade.php

<div id="app">
    <modal v-if="showModal"></modal>

    <button>Show Modal</button>
</div>

resources\assets\vuejs\app.js

import Vue from 'vue';
import Modal from './components/modals/show-modal.vue';
Vue.component('modal', Modal);

if (document.getElementById("app")){
    var app = new Vue({
        el: '#app',

        data: {

        },

        methods: {

        },

        computed: {

        },

        components: {
            'modal': Modal
        }

    });

    window.vue = app;
}

resources\assets\vuejs\components\modals\show-modal.vue

<template>
    <div id="myModal" class="modal fade" role="dialog">
    <div class="modal-dialog">

    <!-- Modal content-->
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal">&times;</button>
        <h4 class="modal-title">Modal Header</h4>
      </div>
      <div class="modal-body">
        <p>Some text in the modal.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
      </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                showModal: false
            }
        },

        components: {

        },
    }
</script>

Now if I just use this without v-if then it works and pulls in the template correctly.

However if I use what I had in my example it gives me an error:

app.js:967 [Vue warn]: Property or method "showModal" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.

(found in <Root>)

If I add data in main app.js it can access it but not in imported child components. I'd rather not make the main app.js messy and better to use imports and keep components in organized subfolders. How can I access these child component data properly?

4 Answers 4

2

If you want to change the state of the child component, you can set a ref on the component and access the properties directly.

In this example, a ref is set on each modal component.

<bootstrap-modal ref="modal1">

Then, to change a data value in the referenced component, simply set it like you would any other javascript value.

 <button type="button" class="btn btn-primary" @click="$refs.modal1.visible = true">Show Modal One</button>

console.clear()

const BootstrapModal = {
  template: "#modal-template",
  data() {
    return {
      visible: false
    }
  },
  watch: {
    visible(show) {
      if (show) $(this.$el).modal('show')
      else $(this.$el).modal('hide')
    }
  },
  mounted() {
    // listen for the close event
    $(this.$el).on("hidden.bs.modal", () => {
      this.visible = false
    })
  }
}

new Vue({
  el: "#app",
  components: {
    BootstrapModal
  }
})
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

<div id="app">
  <button type="button" class="btn btn-primary" @click="$refs.modal1.visible = true">Show Modal One</button>
  <bootstrap-modal ref="modal1">
    <span slot="title">Modal One</span>
    <p slot="body">Modal One Content</p>
  </bootstrap-modal>
  <button type="button" class="btn btn-success" @click="$refs.modal2.visible = true">Show Modal Two</button>
  <bootstrap-modal ref="modal2">
    <span slot="title">Modal Two</span>
    <p slot="body">Modal Two Content</p>
  </bootstrap-modal>
</div>

<template id="modal-template">
  <div class="modal fade">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"><slot name="title">Modal Title</slot></h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <slot name="body"><p>Modal Body</p></slot>
      </div>
      <div class="modal-footer" v-if="$slots.footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </div>
</div>
</template>

3
  • This worked out great and gave me some insight on how to go about different things in the future as I learn about Vue. Guess I was just going about it the wrong way.
    – dmotors
    Commented Oct 6, 2017 at 16:19
  • @dmotors We're all learning :) In many cases it's useful to do as the other answers suggest and use a property of the component to indicate how the modal should be displayed, but you are correct, essentially at that point the state of the component is not really contained in the modal.
    – Bert
    Commented Oct 6, 2017 at 16:27
  • Appreciate the help. Yeah the other examples work but my main concern from the start was having a clean app.js referencing only the components imported and nothing else while accessing child components when need to. As I stated before, if there are 100 components and at least 1 property each, that app.js would look very cluttered and large.
    – dmotors
    Commented Oct 6, 2017 at 16:48
0

You need to declare the var showModal on your main app.

data: {
    showModal: false
},

You also need to toggle that variable everytime they click the button, so add this to your button:

<button @click="showModal = !showModal">Show Modal</button>

Also i suggest to wrap your modal inside template with a div. Since vuejs required this.

<template>
    <div>
        <div id="myModal" class="modal fade" role="dialog">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <button type="button" class="close" data-dismiss="modal">&times;</button>
                        <h4 class="modal-title">Modal Header</h4>
                    </div>
                    <div class="modal-body">
                        <p>Some text in the modal.</p>
                    </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                </div>
            </div>
        </div>
    </div>
</template>
1
  • Accessing var from main app is fine but imagine I have 100 components and each may even have data vars not hardcoded and could be an array too, this would make the main app very messy to include all vars in child components.
    – dmotors
    Commented Oct 5, 2017 at 16:46
0

Short answer: you are trying to access property of child component within the scope of parent component (or, the Root instance in your case). You should declare showModal property on the Parent component instead.

var app = new Vue({
    el: '#app',
    components: {Modal},
    data: {
        showModal: false

    },
});

and then you would have the button changing the state of your property: <button @click="showModal = true"></button>

The tricky part is close out the modal - because you can't reach your button anymore when the modal is active. You would have to change the state of your parent component from your child component. You can use custom events in this case, like so:

In the show-modal.vue

<div class="modal-footer">
    <button type="button" class="btn btn-default" @click="close">
       Close
   </button>
  </div>

    ...

 <script>
export default {
    methods: {
         close() {
             this.$emit('close');
         }
    }
}

And now, all we have to do is to listen on that custom event we just fired in the main template, like so;

<modal v-if="showModal" @close="showModal = false"></modal>
1
  • Similar to the other answer by Carlos, access vars in main/root instance is fine but if I have hundreds of components later the main file would have to declare every possible var? What if vars from child is not hardcoded and data is grabbed from api? Is there some dynamic way of achieving this?
    – dmotors
    Commented Oct 5, 2017 at 17:07
0

Not sure if best way but I figured it out with the help from this: VueJS access child component's data from parent

What I did was in my child component I put the following:

<template>
    <div>
        <div id="myModal" class="modal fade" role="dialog">
        <div class="modal-dialog">

        <!-- Modal content-->
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal">&times;</button>
            <h4 class="modal-title">Modal Header</h4>
          </div>
          <div class="modal-body">
            <p>Some text in the modal.</p>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          </div>
        </div>
    </div>
</template>

<script>
    export default {
        data: function () {
            return {
                showModal: false
            }
        },

        components: {

        },
    }
</script>

Then in my vuetest.blade.php I put this:

<div id="app">
    <modal v-if="modalData.showModal"></modal>

    <button>Show Modal</button>
</div>

Then lastly in my app.js I did this:

import Vue from 'vue';
import Modal from './components/modals/show-modal.vue';

if (document.getElementById("app")){
    var app = new Vue({
        el: '#app',

        data: {
            modalData: Modal.data()
        },

        methods: {

        },

        computed: {

        },

        components: {
            'modal': Modal
        }

    });

    window.vue = app;
}

I didn't complete the code for the show modal button obviously but at least using this way I can now access child data. I would just need to define this for every component which should account for as many vars it contains inside the data.

Also thanks Carlos for suggestion of wrapping it into a div.

3
  • You are not referencing the child components data here. The Modal components data function returns an object. When the modal component is instantiated, it calls the data function and that will return a completely different object. The code above will seemingly work because v-if is controlling whether the modal is shown or not; the child's data has nothing to do with it.
    – Bert
    Commented Oct 5, 2017 at 19:08
  • @Bert Actually it does access the child components data. I tested this theory by adding another variable called favoriteColor: 'brown' inside show-modal.vue data and then created a span with v-text="modalData.favoriteColor" and sure enough after refresh page it shows "brown"
    – dmotors
    Commented Oct 5, 2017 at 19:25
  • @Bert Ahh I see what you mean. The initial data grab from the child return is fine but once there is an action such as click to change it to true or back to false it doesn't update it. I guess trying to find a way to keep main app.js clean and only show components and no specific variables but still be reactive.
    – dmotors
    Commented Oct 5, 2017 at 23:36

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.