JabbaWook
JabbaWook

Reputation: 685

v-model on an element outside the component

Is it possible to use v-model on an element in the DOM that is outside the root element of the Vue instance?

I.e, Could you have the following:

$(function(){
  Vue.component('custom-element', {
    template: '#custom-template'
  })

  var vm = new Vue({
    el: "#app"
    data: {
      selected: ""
    }
  })
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>


<select id="SportSelector" v-model="selected">
  <option value="football"> Football </option>
  <option value="tennis"> Tennis </option>  
</select>

<div id="app">
  <custom-element> 

  </custom-element>
</div>

<script type="text/x-template" id="custom-template">
  <div>
    <p> {{selected}} </p>
  </div>
</script>

Where the select is outside the root element ("app") of the Vue. This is a simplified version of my problem, but I believe highlights the difficulties I'm having.

I saw this answer talking about centralised state management for sharing data between Vues but that seems a bit heavy to have to wrap the select in an entirely different Vue, I feel like i'm missing something major! (still very new to Vue). Does everything the Vue interacts with have to be under the root element of the instance of the Vue?

Upvotes: 1

Views: 4164

Answers (3)

MacroMan
MacroMan

Reputation: 2458

You can use portal-vue to accomplish this. It allows you to place the code from your component anywhere in the DOM.

Add it your view instance:

import PortalVue from 'portal-vue';
Vue.use(PortalVue);

In your component you define the content you want to render outside the component:

<portal to="destination">
  <p>This slot content will be rendered wherever the <portal-target> with name 'destination'
    is  located.</p>
</portal>

You can then place this anywhere in the DOM:

<portal-target name="destination">
  <!--
  This component can be located anywhere in your App.
  The slot content of the above portal component will be rendered here.
  -->
</portal-target>

Example code from https://github.com/LinusBorg/portal-vue

Upvotes: 1

Daniel S. Deboer
Daniel S. Deboer

Reputation: 1566

Unless you have some funky interaction going on with the select, add it to your Vue instance. The Vue instance should encapsulate all your templates and logic for the form.

If you're trying to mix jQuery and Vue on the same element you're probably not going to have a good time.

Upvotes: 0

Bert
Bert

Reputation: 82459

No, you cannot use v-model on elements outside the context of the Vue. The reason is Vue compiles down to a render function that renders the contents of the element it controls; it doesn't render anything outside that element, unless you involve specific code or a library like vue-portal.

That said, you can update Vue when the select changes using standard javascript set the Vue data property. I also cleaned up a few things like passing the selected data to your component (which is necessary; components cannot access their parents data directly).

$(function(){
  Vue.component('custom-element', {
    props: ["selected"],
    template: '#custom-template'
  })

  var vm = new Vue({
    el: "#app",
    data: {
      selected: ""
    }
  })
  
  //get a reference to the select
  const sports = document.querySelector("#SportSelector")
  //add a listener that updates the Vue data when the select changes
  sports.addEventListener("change", evt => vm.selected = evt.target.value)
  //initialize Vue with the current selected value
  vm.selected = sports.value
  
  
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>


<select id="SportSelector">
  <option value="football"> Football </option>
  <option value="tennis"> Tennis </option>  
</select>

<div id="app">
  <custom-element :selected="selected"> 

  </custom-element>
</div>

<script type="text/x-template" id="custom-template">
  <div>
    <p> {{selected}} </p>
  </div>
</script>

Upvotes: 3

Related Questions