Scoping of HTML element id in Vue component - vue.js

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.

Related

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

Dynamic x-templates in Vue components

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.

Why v-if is not showing the heading when boolean value changes?

I am very new to vue js. I am just learning to use it from laracasts. What I want to do is communicate between root class and subclass. Here, user will put a coupon code and when he changes focus it will show a text.
My html code is like this
<body>
<div id="root">
<coupon #applied="couponApplied">
<h1 v-if="isCouponApplied">You have applied the coupon.</h1>
</div>
<script src="https://unpkg.com/vue#2.5.21/dist/vue.js"></script>
<script src="main.js"></script>
</body>
My main.js is like this,
Vue.component('coupon', {
template: '<input #blur="applied">',
methods: {
applied()
{
this.$emit('applied');
}
}
});
new Vue({
el: '#root',
data: {
isCouponApplied:false,
},
methods:{
couponApplied()
{
this.isCouponApplied = true;
}
}
});
I am checking using vue devtools extension in chrome. There is no error. The blur event is triggered. isCouponApplied also changes to true. But the h1 is not showing. Can anyone show me where I made the mistake?
The problem is that you are not closing your <coupon> tag
<div id="root">
<coupon #applied="couponApplied"></coupon>
<h1 v-if="isCouponApplied">You have applied the coupon.</h1>
</div>
Should fix your issue. If you don't close your tag, the parser will auto-close it, but it will do so at the close of its wrapping container (the root div), so the h1 content will be seen as inside the <coupon> element, and will be replaced by your component's template.

How to make a template variable non-reactive in Vue

I have an edit form with variables held in the data(). I don't want the title of the edit page to update yet I want to maintain the v-model sync of data between the input and data. What's the simplest way to make the title non-reactive in the h1 tag? Mr You has to have something up his sleeve for this..
<template>
<div>
<h1>{{ title }}</h1>
<input v-model="title">
</div>
</template>
<script>
export default {
data: {
title: 'Initial value'
}
}
</script>
The Vue docs recommend Object.freeze() on the returned object in data() to disable reactivity on properties:
data() {
return Object.freeze({ title: 'Initial value' })
}
But the caveat is it freezes all properties (it doesn't look like there's a way to freeze only some properties using this method), and using v-model with this causes console errors (Cannot assign to read only property).
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return Object.freeze({
message: 'Hello Vue.js!',
})
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<p>{{ message }}</p>
<input v-model="message"> <!-- XXX: Cannot use v-model with frozen property. This will cause a console error. -->
</div>
Alternatively, you could arbitrarily remove the reactivity from any configurable data property by redefining it with writeable: false:
methods: {
removeReactivity() {
Object.defineProperty(this, 'title', {value: null, writeable: false});
}
}
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return {
message: 'Hello Vue.js!',
}
},
methods: {
removeReactivity() {
Object.defineProperty(this, 'message', {value: null, writeable: false});
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
<div>
<button #click="removeReactivity">
Remove reactivity for <code>message</code>
</button>
</div>
</div>
You could potentially use v-once directive for your purpose if you don't want to create a separate variable for input. From the docs:
Render the element and component once only. On subsequent re-renders,
the element/component and all its children will be treated as static
content and skipped.
new Vue({
el: "#app",
data: {
title: "initial value"
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<input v-model="title">
<p>Reactive title: {{ title }}</p>
<p v-once>Static title: {{ title }}</p>
</div>
If you don't want the input to change the value of your data item, use value to bind it rather than the two-way v-model. Then it just acts as an initializer for the input.
If you want to have two values, one that doesn't change and one that does that gets initialized from the other, you need to have two data items. The non-changing one can be a prop with a default value. The other is a data member which, if you use a data function, can initialize itself to the prop value.
new Vue({
el: '#app',
props: {
initTitle: {
default: 'Initial value'
}
},
data() {
return {
title: this.initTitle
};
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<h1>{{ initTitle }}</h1>
<input v-model="title">
<div>Title is "{{title}}"</div>
</div>
You could alternatively use the little-known $options properties to define your title as a sort of internal constant rather than a prop. I am of mixed feelings about whether this is a good design approach or a step too weird.
new Vue({
el: '#app',
initTitle: 'Initial value',
data() {
return {
title: this.$options.initTitle
};
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<h1>{{ $options.initTitle }}</h1>
<input v-model="title">
<div>Title is "{{title}}"</div>
</div>
Working backwards from the contents of this blog...
It appears that when you create an object for Vue, it creates the properties with reactive getters and setters. If you then append a property to that object out-of-band, then it won't get the reactive capability, but will still be accessible as a value.
This should solve it for you:
<template>
<div>
<h1>{{ titleContainer.value }}</h1>
<input v-model="title">
</div>
</template>
<script>
export default {
data: {
titleContainer: {}
}
}
titleContainer.value = "Initial Value"
</script>
There is no easy way to solve your problem with Vue as is since Vue automatically injects reactive getters and setters for all object properties. You could use Object.freeze() on the variable to remove reactivity BUT it would apply across the whole object itself which is not what you want.
I created a fork out of vue called vue-for-babylonians to restrict reactivity and even permit some object properties to be reactive. Check it out here.
With it, you can tell Vue to not make any objects which are stored in vue or vuex from being reactive. You can also tell Vue to make certain subset of object properties reactive. You’ll find performance improves substantially and you enjoy the convenience of storing and passing large objects as you would normally in vue/vuex.

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.