vue single file components naming; is it important? - vue.js

What's the point of the name of a single file vue component?
In this example:
<template>
<div class="inventory-section">
<draggable v-model="itemSectionProps.itemSectionCategory">
<transition-group>
<div
v-for="category in itemSectionProps.itemSectionCategory"
:key="category.itemSectionCategoryId"
>
<!-- <p>{{ category.itemSectionCategoryName }}</p> -->
<inventory-section-group :itemSectionGroupProps="category">
</inventory-section-group>
</div>
</transition-group>
</draggable>
</div>
</template>
<script>
import InventorySectionGroup from "./InventorySectionGroup";
import draggable from "vuedraggable";
export default {
name: "Inventory",
components: {
InventorySectionGroup,
draggable,
},
inventory section group is named like:
<script>
import InventoryItem from "./InventorySectionGroupItemC";
export default {
name: "Do I even have a point?",
components: {
InventoryItem,
},
props: {
itemSectionGroupData: {
type: Object,
},
},
};
</script>
so, does the name in the component matter?
After some testing, all components seem to work as long as they're translated from camel to kebap-case when imported (and used). Is this the case?

A good justification for the name is that lets say you have a naming convention to your files and for components.
For example if all components are named with what they are but not appended with comp (ie: Inventory.vue instead of InventoryComp.vue) and when you use them you want to be more explicit about what they are (components) so you want to use this component like this: <inventory-comp />. An easy way to do this is to use the name property and set it like this in your Inventory.vue:
name: 'InventoryComp` // or inventory-comp

Related

How to create a Vue Tooltip component using BootstrapVue tooltip

I'm new to Vue and I don't use Bootstrap often so please pardon my newbie question, I'm trying to create a Vue tooltip component, I've created one to behave the way I wanted using css, however, I'm running into some accessibility issues, so I decided to use the BootstrapVue tooltip instead, but I don't know how I would create this component with Bootstrap.
This is basically my Tooltip.vue component using css:
<template>
<div :class="`tooltip ${position}`">
<slot></slot>
<span class="tooltip-text">{{content}}</span>
</div>
</template>
<script>
export default {
name: 'Tooltip',
props: {
position: String,
content: String,
}
};
</script>
<style lang="scss">
.tooltip {
.......
</style>
Then I import and use my component in other places like this:
<tooltip position="right" content="Right tooltip">Hover me</tooltip>
And I have created a TooltipBootstrap.vue component wanting to have the same structure but using Bootstrap, but I don't know how that would go, here is what I started:
I npm installed bootstrap-vue
<template>
<div>
<button v-b-tooltip.hover.${position}="'${content}'"></button>
</div>
</template>
<script>
import VBTooltip from 'bootstrap-vue';
export default {
name: 'TooltipBootstrat',
components: {
VBTooltip,
},
props: {
position: String,
content: String,
}
};
</script>
I'm reading the bootstrap documentation: https://bootstrap-vue.org/docs/directives/tooltip, but I don't know if I'm using this the way it's supposed to be used, so I'm a little lost and would appreciate any help/advice, thanks!
BootstrapVue provide <b-tooltip> component and v-b-tooltip directive (preferred method from document). You can play around in the document.
In simple words, you can use v-b-tooltip directive on any element which is very convenient. but for <b-tooltip> component you have to set target to identify the target to active the tooltip.
So in your case you can do something like:
<template>
<div v-b-tooltip="{ title: content, placement: position }">
<slot></slot>
</div>
</template>
<script>
import { VBTooltip } from 'bootstrap-vue'
export default {
name: 'Tooltip',
directives: {
'b-tooltip': VBTooltip,
},
props: {
position: String,
content: String,
}
};
</script>

Can I debug the export/import of a mixin?

I'm pretty new to vue.js and I'm trying to figure out how to use mixins.
I wondered if it is possible to create components which are bare of markup/template and contain only logic. As far as I understood, this should be possible and these components are called "mixins":
https://blog.bitsrc.io/understanding-mixins-in-vue-js-bdcf9e02a7c1
I'm using router functionality.
I'm now just trying out the vary basics of this concept and created the following:
listData1.vue, where the data for a list is created and then exported:
<script>
export default {
name: "listData1",
data() {
return {
list1: {
listData1A : "listData1A",
listData1A : "listData1B",
listData1A : "listData1C"
}
}
}
}
</script>
then listBuilder.vue, which takes the data and then uses it to create a list of items.
<template>
<div>
<ul>
<li v-for="element in list1" v-text="element"></li>
</ul>
</div>
</template>
<script>
import listData1 from "#/components/complexComponent2/listData1.vue"
export default{
name: 'listBuilder'
}
</script>
And then myComplexView2.vue in my views folder:
<template>
<div>
<h1>Second Awesome List!</h1>
<listBuilder />
</div>
</template>
<script>
import listBuilder from "#/views/myComplexView2.vue"
export default{
name: 'myComplexView2',
components: {
listBuilder
}
}
</script>
Now the result I get is this:
https://imgur.com/hQit785
But it should look like this:
http://localhost:8081/myComplexView
I'm a bit clueless what to do, especially since the vue dev tools in firefox don't show me much: https://imgur.com/RHJNy47.
Am I accessing the imported incorrectly?
Should I store the data differently, with the "data : {}" syntax or should I go for props in the listData component, like this:
props:["listData1"]
And then add the actual data in the component where the list is constructed with v-for? Though this would kind of undermine my goal to accomplish separating the data from the logic injecting it into the markup.
You need to setup listData1 as mixin in listBuilder.
<template>
<div>
<ul>
<li v-for="element in list1" v-text="element"></li>
</ul>
</div>
</template>
<script>
import listData1 from "#/components/complexComponent2/listData1.vue"
export default{
name: 'listBuilder',
mixins: [listData1],
}
</script>
Otherwise the ListBuilder won't have any data.
There's a typo in the mixin data:
listData1A : "listData1A",
listData1A : "listData1B",
listData1A : "listData1C"
Should be:
listData1A : "listData1A",
listData1B : "listData1B",
listData1C : "listData1C"
Apart from this, I don't see anything at syntax level in your code that would prevent mixin and v-for for working.
However, it puzzles me that myComplexView2 is importing myComplexView2.vue as the listBuilder:
import listBuilder from "#/views/myComplexView2.vue"
I don't know if this is an error you made when pasting to SO. Otherwise, the problem is probably here, since you need to import the listBuilder component, not the complex view.

Extending Vue.js SFC components

I haven't found a good resource on extending Vue.js components. In every project I've worked on, regardless of the UI component library that's used, there are application Base components which extend the UI library components to enforce company/application defaults and standards.
I'm trying to extend Vue-Multiselect: https://vue-multiselect.js.org/ which has about 30 props and 12 slots. The component I'm extending doesn't matter -- I only mention it because ideally I don't want to have to repeat 30 props and 12 slots in my implementation.
I simply want to make two changes to the behavior of the component:
Make disabled prop a bit smarter
The Vue-Multiselect component has a standard disabled prop which works as expected:
<Multiselect :disabled="isDisabled" ...>
In our application, we have global state in Vuex which determines if the application is read-only. What I want to avoid is requiring developers to pass this state to every form field:
<Multiselect :disabled="readOnly || isDisabled" ...>
<OtherComponent :disabled="readOnly || someOtherCondition" ...>
...
So the user of my base component should only need to be concerned about their local UI state which affect the disabled status:
<BaseCombo :disabled="!emailValid" ...>
This would handle the 90% case of form fields that are locked down when the application is read-only and I can use an additional prop for cases where we want to ignore the global read-only status.
<BaseCombo :disabled="!emailValid" :ignoreReadOnly="true" ...>
Provide defaults
Secondly, I simply want to override some of the default prop values. This post addresses the question of supplying defaults:
https://stackoverflow.com/a/52592047/695318
And this works perfectly until I tried to modify the behavior of the disabled prop I mentioned previously.
My attempt to solve this was to either wrap or extend the component. I'd really want to avoid redeclaring all of the props if possible.
<template>
<Multiselect
:disabled="myCustomDisabled"
:value="value"
#input="$emit('input', $event)"
:options="options"
:label="label"
:track-by="trackBy"
:placeholder="placeholder"
... repeat for all 30 options
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
extends: Multiselect, // extend or simply wrap?
computed: {
myCustomDisabled() {
this.props.disabled || ... use disabled from Vuex state
}
},
props: {
disabled: Boolean,
placeholder: {
type: String,
default: 'My Default Value',
},
... repeat for all props
The problem I ran into is I don't know how to handle the slots. The user of this BaseCombo should still be able to use all 12 slots in the VueMultiselect component.
Is there a better solution for extending components?
You can use this.$props to access props defined in the props attribute. Similarly you can access attributes (things you haven't defined as props) with this.$attrs. Finally you can bind props with v-bind="someVariable".
If you combine this you can do something like this:
<!-- App.vue -->
<template>
<component-a msg="Hello world" :fancy="{ test: 1 }" />
</template>
<!-- ComponentA.vue -->
<template>
<component-b v-bind="$attrs" />
</template>
<script>
export default {
name: 'componentA'
}
</script>
<!-- ComponentB.vue -->
<template>
<div>
{{ msg }}
{{ fancy }}
</div>
</template>
<script>
export default {
props: {
msg: String,
fancy: Object
},
mounted () {
console.log(this.$props);
}
}
</script>
In this example, component B would be the component you try to extend.
Here's a complete example based on Sumurai8's answer and motia's comments.
<template>
<Multiselect v-bind="childProps" v-on="$listeners">
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
<slot :name="name" v-bind="slotData" />
</template>
</Multiselect>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
props: {
placeholder: {
type: String,
default: 'This is my default',
},
disabled: {
type: Boolean,
default: false,
},
},
components: {
Multiselect,
},
computed: {
childProps() {
return { ...this.$props, ...this.$attrs, disabled: this.isDisabled };
},
appReadOnly() {
return this.$store.state.appReadOnly;
},
isDisabled() {
return this.disabled || this.appReadOnly;
}
},
}
</script>

render a Vue slot in a layout from component

I'm having problems with a named slot. This seems like it should work. In the code below I'm trying to use a named slot "sidebar". I would expect my sidebar slot content to show up between the Sidebar starts and Sidebar ends text but nothing shows up in that slot. Everything renders in the main slot.
Here's my code.
route...
{
path: "/test",
name: "test",
meta: {
layout: "test-layout"
},
component: () =>
import("#/pages/Test.vue")
},
and App.vue template...
<template>
<div id="app">
<component :is="layout">
<router-view />
</component>
</div>
</template>
and test-layout...
<template>
<div>
<div>
<h1>Sidebar starts</h1>
<slot name="sidebar"/>
<h1>Sidebar ends</h1>
</div>
<div class="container">
<h1>Content starts</h1>
<slot/>
<h1>Content ends</h1>
</div>
</div>
</template>
and page Test.vue...
<template>
<test-layout>
<span slot="sidebar">sidebar slot content {{forecast.todaySummary}}</span>
<div>main slot content {{forecast.currentSummary}}</div>
</test-layout>
</template>
<script>
import api from "#/js/web-services";
export default {
data() {
return {
forecast: null
};
},
created() {
api.getDailyForecast().then(response => {
this.forecast = response.data;
});
}
};
</script>
and the import in my main.js
import TestLayout from "./layouts/test-layout.vue";
Vue.component('test-layout', TestLayout);
Why isn't my sidebar slot working?
UPDATE
If I get rid of the two lines in main.js and add
import TestLayout from "#/layouts/test-layout.vue";
and
export default {
components: { TestLayout },
data() {...
to Test.vue then it works.
In your router file you are using layout: "test-layout" this means what ever comes in your vue component will be rendered in base test-layout.
There are two ways as far as I know to render the layouts.
Do not define layout in router file and on parent component define named slots like this<slot #header></slot><slot #body></slot> then every slot will be rendered within this (test-layout) layout, and then in your each component extend like this <test-layout><template header>Header content</template><template body>Body content</template></test-layout>.
Defining layout in router file like you did, you can not further use in slots in that layout, you can just import other components e.g <template><Component1><Component2> </template>

Overriding a props type in child component?

I'm using a third party component, and it expects a prop to be a particular type.
I wish to use a different type.
Is there a way to override the type of a child component prop?
Or would I have to use a computed property and modify the prop in the parent component to the type the child component requires?
In general i think the best way would be to do as you proposed: To "use a computed property and modify the prop in the parent component to the type the child component requires". If you have to do so in several places, you could outsource the logic into a mixin that will be imported whenever the given third-party component is used.
If modifying the type of the property in the parent component isn't an option, the best way to fulfill your needs would be creating your own component that extends from the third party component and overriding the needed property. (https://v2.vuejs.org/v2/api/#extends)
Keep in mind however that the dependency containing the third-party might get updated over time. You should probably stick to a fixed version if following this approach.
I created a simple example on how to extend and override components (You can check it out as CodeSandbox here: https://codesandbox.io/s/qq9y7nm8n4
App.vue:
<template>
<div id="app">
<h2>ExtendedComponent: </h2>
<extended-component :message="1" />
<h2>BaseComponent: </h2>
<base-component message="This shall be a string" />
</div>
</template>
<script>
import ExtendedComponent from "./components/ExtendedComponent";
import BaseComponent from "./components/BaseComponent";
export default {
name: "App",
components: {
ExtendedComponent,
BaseComponent
}
};
</script>
BaseComponent.vue:
<template>
<div>
<h3>prop type: {{ typeof message }}</h3>
</div>
</template>
<script>
export default {
name: "BaseComponent",
props: {
message: {
type: String,
required: false
}
}
};
</script>
ExtendedComponent.vue:
<script>
import BaseComponent from "./BaseComponent";
export default {
name: "ExtendedComponent",
extends: BaseComponent,
props: {
message: {
type: Number,
required: false
}
}
};
</script>