How to disable rule [plugin:vite:vue] Duplicate attribute - vue.js

I have a Vue 3 app set up with Vite and Storybook.
I am trying to use two class attributes for a button, which I believe is perfectly valid:
<button
:id="id"
:type="type"
#click="onClick"
:class="classes"
:class="{
'bg-gray-100': disabled,
'cursor-not-allowed': disabled,
'inline-block': block,
'w-full': block,
}"
:disabled="disabled">{{ text }}<slot/></button>
Running the project, npm run storybook, gives me
[vite] Internal server error: Duplicate attribute.
Plugin: vite:vue
Where and how do I disable this rule?

In vue.js, you can't have two v-bind for the same attributes on one element.
In your case you are putting twice the classes elements wich results in the Duplicate attribute. error.
To solve your problem, I would recommand mergind your classes attributes using a computed properties with the other classes you are trying to add.
export default {
computed: {
mergedClasses(){
return {...this.classes,
'bg-gray-100': disabled,
'cursor-not-allowed': disabled,
'inline-block': block,
'w-full': block,
}
}
}
}
And use it in the template :
<button
:id="id"
:type="type"
#click="onClick"
:class="mergedClasses"
:class="{
'bg-gray-100': disabled,
'cursor-not-allowed': disabled,
'inline-block': block,
'w-full': block,
}"
:disabled="disabled">{{ text }}<slot/></button>

Related

Getting element (button) data attribute with vue3

I have the following html div. The {{ }} represent liquid syntax and it renders server side.
<div class="two columns">
<button
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
<span class="text">{{ value }}</span>
</button>
</div>
In a vue 3 instance I have the following method in the same page.
selectionButtonClass(el) {
console.log('checking val');
console.log(el);
}
My goal is to set a conditional class in the selectionButton method but I can't get the element to get the data attribute. The above appears in the console log as undefined. However the #click does show the event obviously it's recognize the onclick but not the class method check.
$event is only available to event handlers. #click="selectionButton($event)" defines an inline event handler, while :class="selectionButtonClass($event)" is not an event handler.
To get the element, you need to add a ref attribute to the <button>:
<button
ref="selectionButton"
data-value='{{ value }}'
class="button-selection"
:class="selectionButtonClass($event)"
#click="selectionButton($event)"
>
And access it by this.$refs.selectionButton, assuming you are using the options API. However, the ref is available only after the component is mounted. Thus you need to handle the case where the ref is null.
More on template refs: https://vuejs.org/guide/essentials/template-refs.html
Since you are using server side rendering, I think it would be better to render the value as a parameter of the selectionButton function on the server side.

VueJS - reusing prop (field name) at definition of other bindigns

I don't know how to exactly name this, I have some sort of forms in Vue which consist of different input types or more complex wrapped elements or even custom components. They all have one thing in common though - their fieldName which is used for checking various stuff such as validation, class binding etc.
Example:
<div class="field">
<div class="label">Product name</div>
<input :value="productName" #input="valueChanged" :class="{ changed: isChanged('productName') }" data-field="productName">
</div>
As you can see, productName is repeated 3 times in just a single line. I use it in dataset so valueChanged method (a global mixin) knows that field name has changed, then in the class binding to check if value has changed to style it properly, and next for the value binding itself.
It grows bigger and bigger as I want to add for example another class binding like error: hasErrors('productName')
Is there any way to define the field name once and re-use it in other bindings? It would still require some repetition, but at least changing the field name in the future would be just one change instead of 4-5. Something like this:
<input :fName="productName" :value="fName" #input="valueChanged" :class="{changed: isChanged(fName), error: hasErrors(fName)" :data-field="fName">
I know that wrapping it in some custom component would probably be one way, but that would require a lot of different conditions to render things correctly as I'm using various field types with different structures. And I would need to re-write half of my app.
Here are two potential solutions:
Use v-bind and generate an object with all of the attributes you need
<input v-bind="getAttrs('productName')" #input="valueChanged" />
...
methods: {
getAttrs(fieldName) {
return {
value: this[fieldName],
class: {
changed: this.isChanged(fieldName),
error: this.hasErrors(fieldName)
},
'data-field': fieldName
}
}
}
Store all the fields in a variable and loop through them:
<div
v-for="field in fields"
class="field"
>
<div class="label">
Product name
// Or e.g. {{ field.label }} if you have an array of field objects
</div>
<input
:value="getValue(field)"
:class="{ changed: isChanged(field), error: hasErrors(field) }"
// Still need to use v-bind to generate the data attribute on the fly
v-bind="getDataAttr(field)"
#input="valueChanged"
/>
</div>
...
data() {
return {
fields: [
'fieldName1',
'fieldName2',
'fieldName3'
],
}
},
methods: {
getValue(fieldName) {
return this[fieldName];
},
getDataAttr(fieldName) {
return {
'data-field': fieldName
}
}
}
I believe that having custom component handling input fields is the smartest way. I don't think, there would be that much conditions and even if there is some workaround, you will still need to rewrite half of your app. You may use input type as prop, so you can use component for different field types and if there are much different structures, you can use slots, to add some custom structure.

Vue.js: prevent user clicking on element while it is being transitioned with Vue <transition>

I'm working on a Vue.js project, and when I click on an element, I'm using the Vue transition tag to fade it out. The problem is that as the element is in the process of being faded out, it is still clickable, which in my application can cause issues.
My question is: how can I make an element unclickable during a transition, so that users don't click it multiple times before the transition finishes?
I've already tried applying a css class with point-events: none; to the element right when the transition starts, but it didn't stop clicks during transition.
Example:
<transition name="fade">
<div v-if="shouldShow" #click="doSomeAction">Example text</div>
</transition>
(where doSomeAction sets shouldShow to false).
Vue has event modifiers that might help with that. The specific one which might be helpful to you is #click.once. If you add this to the click event the user will only be able to click it once. Documentation for it is here.
If you are using Vue.js 2.6+ you can do it with ease. In this minor realse Dynamic directive arguments was added, so you can conditionally bind desired event name, or in you case disable it (passing null).
Dynamic argument values are expected to be strings. However, it would
be convenient if we allow null as a special value that explicitly
indicates that the binding should be removed. Any other non-string
values are likely mistakes and will trigger a warning.
Reference.
// using computed property
<transition name="fade">
<div v-if="shouldShow" #[event]="doSomeAction">Example text</div>
</transition>
export default {
data() {
return {
shouldShow: true
}
},
computed: {
event() {
return this.shouldShow ? "click" : null;
}
}
}
// using object
<transition name="fade">
<div v-if="shouldShow" v-on="{ [shouldShow ? 'click' : null]: doSomeAction }">Example text</div>
</transition>
Update
If you also need to ensure that users can immediately click "through" the element that is being faded out to items behind it, you can add a class with pointer-events: none; to the element, and then do this:
this.$nextTick(() => {
this.shouldShow = false;
});
This will make sure the fade doesn't happen until the class has been added. this.$nextTick is a Vue function that waits for the dom to update (which in this case is adding the pointer-events class) before running a callback: https://v2.vuejs.org/v2/api/#Vue-nextTick
Note that pointer-events: none; doesn't work on some very old browsers (IE < 10)

Processing local components with vue

I have the following code:
<my-messages>
<message>Hello</message>
<message>World</message>
</my-messages>
For now, I did my <my-messages> component be renderized as:
<div class="Messages">
<!-- slot here -->
</div>
And I like to do the same for <message>, but the problem is that I receiving the error Unknown custom element: <message>. Except if I change my code to:
<my-messages inline-template>
<message>Hello</message>
</my-messages>
It seems a bit hack, once that I should declare the [inline-template] to all <my-messages> components, instead of it be treated directly from this component as a default rule (eg. an option as inlineTemplate: true should do the work, if it exists).
The expected render should be like:
<div class="Messages">
<div class="message">Hello</div>
<div class="message">World</div>
</div>
My component currently:
export default {
components: {
// Should process <message> sub-component.
message: require('./Messages.Message.vue'),
}
}
Edit: on reality, the inline-template seems mixing both <div>s from template, and not nesting it.
inline-template is not a hack. I think The problem is you're not registering message components at the same place where you're using my-messages component.
So the parent component that has my-messages as a child can't understand message, you need to register it in the parent too, when you use inline-template the scope changes and and whatever is inside will be treated as inner content. You can find it in the docs
EDIT
There isn't a way to have <message> to be only usable as a child of <my-messages>, you could however throw an exception if it's misused
mounted() {
if (!this.$parent.$el.classList.contains('my-message')) {
this.$destroy();
throw new Error('You must wrap the message in a my-message');
}
}
Note that this supposes that the class my-message is available in the root element, this way you can use any wrapper element.

When does binding to ref attribute become valid in Aurelia?

This is a follow up to this question: Access a DOM element in Aurelia
Is there a hook in the Screen Activation Lifecycle which allows me to run code after ref bindings have been set up? Currently it seems like there is a period of time after the activate hook is called when the ref bindings are not set up yet and then at some point they get activated. I tested this by adding a <div ref="myDiv"></div> to near the bottom of welcome.html in a cloned version of the latest (v0.13.0) skeleton-navigation repo and testing the existence of the reference in the view-model like this:
export class Welcome{
heading = 'Welcome to the Aurelia Navigation App!';
firstName = 'John';
lastName = 'Doe';
testMyDiv() {
console.log("Getting my div")
console.log(this.myDiv)
}
get fullName(){
this.testMyDiv()
return `${this.firstName} ${this.lastName}`;
}
welcome(){
alert(`Welcome, ${this.fullName}!`);
}
}
A snippet of the bottom of the template...
<button type="submit" class="btn btn-default">Submit</button>
</form>
<div ref="myDiv"></div>
</section>
</template>
This is a snapshot of what I see in the console...
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 undefined
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
welcome.js:10 Getting my div
welcome.js:11 <div ref=​"myDiv" class=​"au-target">​</div>​
(continues)
The print outs like this goes on indefinitely. You can see that fullName() is being called regularly to update the screen if the name changes (I assume this is the dirty checking)... but you can see that at the beginning there is a period when the referenced div is NOT valid as a property of the view-model, and then it IS valid. Can someone explain this? Is there a way to hook into the view-model after the ref becomes valid?
In general, bindings are processed and available after the bind callback. However, in this case since you need to access the DOM element, you will need the ViewModel to be bound and attached to the view, so use the attached callback.
class ViewModel {
bind() {
this.refItem == undefined; // true
}
attached() {
this.refItem == undefined; // false
}
}
As you noted in the comments, more information on the activator callbacks is available here: http://aurelia.io/docs.html#extending-html