1

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/vue@2.5.13/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?

JabbaWook
  • 677
  • 1
  • 8
  • 25
  • You could create an invisible/empty custom component inside of the root element, that would control an element ouside of it. But for reasons of maintainability alone, this should not be done. And yes the way Vue or similar libs work, everything controlled by the lib should be within a Vue instance. – t.niese Feb 15 '18 at 17:57
  • 2
    Why do you have things outside of the root of your app? Ideally, the element that you give Vue wraps your **whole** page. What is the intention behind putting the ` – zero298 Feb 15 '18 at 17:57
  • You could set a Vue data value based on the select outside the Vue, but you can't use `v-model` on the select. Also I completely disagree with the above comment that Vue needs to always wrap the whole page. One of the great strengths of Vue is that it is easy to integrate into existing pages. – Bert Feb 15 '18 at 18:32
  • @Bert can we at least agree that it should wrap whatever you want to do reactive data binding? This comes off as an XY problem. The OP said "sharing data between Vues" which, to me, implies that the OP is having one Vue context try to reach into another Vue context which seems like a bad idea. – zero298 Feb 15 '18 at 20:26
  • 2
    @zero298 It doesn't appear to me that the OP is trying to share data between Vues, but rather that was a something in favor of using a state management system, and he goes on to state he doesn't really want to put the select in another Vue. There are many cases, like working with a CMS, where some of the layout is not completely under your control, but you nevertheless may want to use Vue for certain elements of your UI. – Bert Feb 16 '18 at 00:00

3 Answers3

3

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/vue@2.5.13/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>
Bert
  • 80,741
  • 17
  • 199
  • 164
1

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

MacroMan
  • 2,335
  • 1
  • 27
  • 36
0

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.

Daniel S. Deboer
  • 1,566
  • 2
  • 10
  • 6