Dynamic x-templates in Vue components - vue.js

I have this component, and would like to pass a parameter/prop to the component saying which x-template to use. When I do it like this, it fails:
JS:
Vue.component('custom-table', {
props: ['template'],
template: '#' + this.template
})
new Vue({ el: '#app' })
HTML:
<custom-table template="my-template"></custom-table>
<script type="text/x-template" id="my-template">
<p>My Template</p>
</script>
Error:
vue.js:3 [Vue warn]: Cannot find element: #undefined
How can I use dynamic templates like this?

I'm not sure whether this is actually a good idea but it does come pretty close to what you've requested:
Vue.component('custom-table', {
props: ['template'],
template: `<component :is="{ template: '#' + template }" />`
})
new Vue({
el: '#app'
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script type="text/x-template" id="button-template">
<button>My Template</button>
</script>
<script type="text/x-template" id="em-template">
<em>My Template</em>
</script>
<div id="app">
<custom-table template="button-template"></custom-table>
<custom-table template="em-template"></custom-table>
</div>
The trick here is to use the object version of is, which allows you to pass in a component definition inline. Strictly speaking there are two components in play here, a parent and a child, and the x-template is assigned to the child. That said, the resulting DOM should be as desired as the parent doesn't add any extra elements of its own.

You can't have dynamic templates for a single component.
You could create various components, and then dynamically pick which component to render for the particular tag. For this, Vue supports dynamic component:
<component v-bind:is="currentTabComponentName"></component>
Alternatively, if you want caller to fill-in-the-blanks of your component with arbitrary HTML, then you can use slots.
Or, if it is just static HTML, then you can just pass the HTML itself as string, and render the content without escaping it:
<div v-html="task.html_content"> </div>
Maybe one of these works for you...
Other options could be to use render functions or JSX.

Related

Vue - Add CDN component without webpack

I want to add this component to my Vue.js project without using webpack.
I've tried adding this to the head:
<script src="https://cdn.jsdelivr.net/npm/vuejs-auto-complete#0.9.0/dist/build.js"></script>
And this to the body:
<autocomplete :source="[{id:1,name:'abc'},{id:2,name:'def'}]"></autocomplete>
But the following error happens:
[Vue warn]: Unknown custom element: autocomplete - did you register the component correctly? For recursive components, make sure to provide the "name" option.
What should I do?
Here's the link to the component on Github.
You need to register that component first like below
components: {
Autocomplete: window["vuejs-autocomplete"]
}
Example
new Vue({
el: '#app',
components: {
Autocomplete: window["vuejs-autocomplete"]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuejs-auto-complete#0.9.0/dist/build.js"></script>
<div id="app">
<autocomplete :source="[{id:1,name:'abc'},{id:2,name:'def'}]"></autocomplete>
</div>
Have you registered it in Vue.components(); in your main.js ?

Scoping of HTML element id in Vue component

Is there some built-in scoping mechanism for Vue component in the sense that value of id attribute of html element inside Vue component be uniquely defined without programmer's efforts to do it?
In the following code, I create two components and hope each behaves independently to each other. So, ideally if I click on each button, each is required to print out "foo" but actually not because value of ids are duplicated.
<!DOCTYPE html>
<head>
<title></title>
</head>
<body>
<div id="app">
<my-comp></my-comp>
<my-comp></my-comp>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<script>
Vue.component('my-comp', {
template: `
<div>
<button id="btn" #click="onClick">Click me</button>
<div id="pid"></div>
</div>
`,
methods: {
onClick(e) {
$('#pid').text("foo");
}
},
});
const vm = new Vue({
el: '#app',
data: () => ({}),
methods: {}
});
</script>
</body>
</html>
Don't use id in vue components unless you are passing a unique value for it using props. You should very rarely ever actually need to get a reference to an element in vue and if you do find you need to then you should be using refs.
In your case you can just use a property and template binding to handle things for you:
Vue.component('my-comp', {
template: `
<div>
<button #click="onClick">Click me</button>
<div>{{ text }}</div>
</div>
`,
data() {
text: ''
},
methods: {
onClick(e) {
this.text = 'foo'
},
},
})
It looks like the vue-uniq-ids package is what you're looking for.
It is a trend to use components. Components are cool, they are small,
obvious, easy to use and modular. Untill it comes to the id property.
Some HTML tag attributes requires using an id property, like
label[for], input[form] and many of aria-* attributes. And the problem
with the id is that it is not modular. If several id properties on the
page will has the same value they can affect each other.
VueUniqIds helps you to get rid of this problem. It provides the set
of id-related directives which value is automatically modified by
adding unique string while keeping the attrbitue easy to read.

In Vue, how can I get a css style selector from a $ref?

I'm able to use $ref to isolate the correct chuck of html, but I need a css selector for that chuck. I wish this worked...
this.$refs.myref.querySelector()
did you try:
this.$refs.myref[0].$el
it equals
document.querySelector(’.myref’)
You are, in fact, able to use querySelector on a ref if the ref in question is referring to a DOM element and not to a child Vue component. The fact that this.$refs.myref.querySelector() doesn't work for you leads me to believe that myref is a component and not an element.
If the ref is a Vue component, you are still able to reference the root DOM element of the component via its $el property. So, in your case: this.$refs.myref.$el.querySelector().
Here's a simple example snippet illustrating the difference:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
template: `
<div>
<span class="inside-child">Inside Child</span>
</div>
`
})
new Vue({
el: '#app',
mounted() {
console.log(this.$refs.child.$el.querySelector('.inside-child'));
console.log(this.$refs.div.querySelector('.inside-div'))
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child ref="child"></child>
<div ref="div">
<span class="inside-div">Inside Div</span>
</div>
</div>

Is it possible to DRY html in vuejs components without creating more components?

Lets say you have:
<template>
<div>
<!-- html for buttons -->
<!-- your form -->
<!-- html for buttons -->
</div>
</template>
<!-- rest of your component -->
Is it possible to DRY up the html for the html for buttons without using a separate component? It seems a lot of work to keep adding components just to save repeating 3-4 lines of html?
I don't know any Vue api that allows to do that properly, however there is a way.
There is v-html which would serve you for DRY html, but it would get rendered as plain HTML, so you cannot use Vue events from there -which I guess your buttons do-.
For instance:
//template
<div id="app">
<div v-html="dryContent"></div>
<p>{{content}}</p>
<div v-html="dryContent"></div>
<div v-html="computedString"></div>
</div>
//script
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p>Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
}
});
Will render the HTML properly. But you cannot setup vue event listeners in the rendered HTML.
You can still, however, setup native listeners:
dryContent: `<div>
<p onclick="console.log('foo')">Hello world!</p>
</div>`
And it will work.
And, well, there is this really obscure pattern which I totally don't suggest but that actually will fit your needs:
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p onclick="modifyContent()">Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
},
created() {
window.modifyContent = function() {
this.content = 'modified!!';
}.bind(this);
}
});
You export the component method to a window property, so you can call it from native code.
Don't know your use case, but I'm pretty sure I would just duplicate the HTML code or setup a new component instead of doing this.

vue using the components when interpolating a string

How to use the component when interpolating a string?
For example, I have a component that do something like this:
Component-A
<template>
<div>{{someProp}}</div>
</template>
When I use that component in others components I pass someProp which is a vue-component and some plain text.
Other component
<component-A someProp="Some text and <component-B/>"/>
How do I prevent the text from being escaped? And instead of the "<component-B/>" the component was substituted?
Props can only be used to pass javascript values, such as strings, numbers, objects, arrays, things like that. In your example, someProp has the string value Some text and <component-B/>, so it will render literally Some text and <component-B/> in the template.
If you want to pass a template fragment (my own terminology), then you will need to use a slot instead of a prop:
Vue.component('component-a', {
template: '<span>Component A: <slot></slot></span>',
});
Vue.component('component-b', {
template: '<strong>Component B</strong>',
});
new Vue({
el: '#app',
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<component-a>
Some text and <component-b></component-b>
</component-a>
</div>