vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.9k stars 33.69k forks source link

root opts attributes support #7924

Open SassNinja opened 6 years ago

SassNinja commented 6 years ago

What problem does this feature solve?

The desired opts feature should offer the possibility to provide data from the markup (mostly from backend) to the vue instance. So you can pass options/params from outside the vue scope. Example use case: https://forum.vuejs.org/t/passing-props-to-root-instances-in-2-0/244

Right now this requires custom helpers/code to pipe the data through to the component. Much better would be a more comfortable way similar the way riot does it with opts http://riotjs.com/api/#mounting

What does the proposed API look like?

Since the instance root is not a custom tag (compared to riot) I can imagine using prefixed attributes e.g. data-opt-[NAME] So the root may look like this:

<div id="myRoot" data-opt-firstname="John" data-opt-lastname="Smith"></div>

and within the template you can access it as

<template>
    <span>Hello {{ opts.firstname }} {{ opts.lastname }}</span>
</template>
LinusBorg commented 6 years ago

Much better would be a more comfortable way similar the way riot does it with opts http://riotjs.com/api/#mounting

That doesn't look at all like the API you propose further down:

<div id="myRoot" data-opt-firstname="John" data-opt-lastname="Smith"></div>

It looks much more like simply passing propsData when creating the instance, which looks like this:

new Vue ({
  el: '#app',
  propsData: {
    firstname: 'John'
  }
})

Adding all data- properties as props could be as imply as:

const el = document.getElementById('app')
new Vue ({
  el: el,
  propsData: {
    ...el.dataset
  }
})
SassNinja commented 6 years ago

Thanks @LinusBorg for the quick response!

I've tried to implement your example with the propsData like this, but it doesn't work: nothing appears in the template and the vue browser extension tells me the same (undefined)

<div id="myRoot" data-firstname="John"></div>
import Vue from 'vue'
import myRoot from './myRoot.vue')
const el = document.getElementById('myRoot')
new Vue({
    el: el,
    propsData: {
        ...el.dataset
    },
    render: h => h(myRoot)
})
<template>
    <span>Hello {{ firstname }}</span>
</template>
<script>
    export default {
        props: ['firstname']
    }
</script>

However even if this would work it has several downsides:

Maybe I'm wrong and just overseeing something but I haven't seen a way yet to pipe through data from markup (non vue origin) to the template. At least no way to do it as easy as in riot where it's really that simple:

For me the propsData solution doesn't meet that demand, does it?

SassNinja commented 6 years ago

- Update -

I've found a way that works for me although it somehow doesn't feel straight forward. Looks like this (this time I'm using pagination to have a more 'realistic' example):

home.html

<body>
    <div class="pagination" data-page="1"></div>
    <p>Lorem ipsum</p>
    <div class="pagination" data-page="1"></div>
    <script src="/assets/dist/pagination.js"></script>
</body>

src/pagination.js

import Vue from 'vue';
import Pagination from './pagination.vue';
document.querySelectorAll('.pagination').forEach(function(elem){
    new Vue({
        el: elem,
        render: h => h(Pagination),
        data: { opts: {} },
        beforeMount: function() {
            this.opts = this.$el.dataset;
        }
    });
});

src/pagination.vue

<template>
    <div>
        <span>Current page: {{ opts.page }}</span>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                opts: this.$root.opts
            }
        }
    }
</script>

If there's no easier way to do this and you don't think there's the need to simplify this via vue, you may close this feature request.

ejweiler commented 6 years ago

From the previous two comments I have two suggestions:

You won't be able to drop this right in, but might give you something to consider and try.

If you had a vue instance that wrapped your html (closer to the level of the body) you could pass in variables just like you do with your pagination.vue template:

pagination.js

...
import Pagination from './Pagination.vue'
...
components: { Pagination },
...

template

<body>
    <div id="app">
        <pagination :data="opts.data1" data-page="1"></div>
       <p>Lorem ipsum</p>
        <pagination :data="opts.data2" data-page="1"></div>
    </div>
</body>

Alternatively, is it possible to solve this with a plugin? Inspired by the way using Vuex gives the stores to all children, could you make a plugin which provides these props to all components?

const userOptionsFromServer = {...}

const UserDataPlugin = {
  install (Vue) {
    Vue.mixin({
      created() {
        this.opts = userOptionsFromServer
      }
    })
  }
}

Vue.use(UserDataPlugin)
SassNinja commented 6 years ago

Thank you @ejweiler for your help!

Correct me if I'm wrong but the template syntax of your example requires the vue compiler in client, right? Or can this be interpreted by vue with runtime only?

<pagination :data="opts.data1" ...

The reason I'm breaking down all my components (pagination, search etc.) into independent files instead of one big bundled app.js is that I want load as less code on each page as possible. Thus I'm using vue without compiler.

Since the HTML gets build in the backend I can't precompile it (SSR of vue is not an option) and thus can only precompile the vue files.

The way I've posted above is the only one I've found so far that let's my include the same component on the page with different data/options from the backend

<body>
    <div class="pagination" data-title="header pagination 123"></div>
    <p>Lorem ipsum</p>
    <div class="pagination" data-title="footer pagination 456"></div>
    <script src="/assets/dist/pagination.js"></script>
</body>
ejweiler commented 6 years ago

@SassNinja I didn't realize you had those constraints, how about the option of trying a plugin?

duprasa commented 5 years ago

So I also found a solution that is pretty easy to use, but I'm not sure if it violates some of vue's rules since we will have the root component and template component bound to the same DOM element.

ex: https://codepen.io/duprasa/pen/mvZQrB?editors=1010

code:

<component id="component" prop-a ="Value A"></component>
let el = document.getElementById("component")
let component_instance = (new Vue({el: el, components: {component: component_options}})).$children[0];