vue multiple components in a single file - vue.js

in vue documents I saw "Namespaced Components" in "script setup" guide it writes:
You can use component tags with dots like <Foo.Bar> to refer to components nested under object properties. This is useful when you import multiple components from a single file:
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
I wanted to know in this example what will the form-component look like, and what is the correct use case for such a component, does it have anything to do with "slot" or not.

In this case, form-components refers to a .js file that seems to be exporting single-file components (.vue).
form-components.js
export { default as Label } from './form-label.vue'
export { default as Input } from './form-input.vue'
You can then access these components via:
import * as Form from './form-components'
However, I recommend using a destructuring assignment methodology, as it is better interpreted by IDEs.
import { Input, Label } from './form-components'

Related

Vue3 Reactivity in script setup for translation

I am adding some DOM elements in the script setup side but I want the messages to change when I change the language. I am using vue-i18n plugin. It's easy to do it in the template section because I can basically use the useI18n().t method but how can I do this in the script setup section. Using the useI18n().t method doesn't ensure reactivity.
Example Code:
$(".time")[0].innerHTML = `
<div>0<span>${useI18n().t("details.hour")}</span></div>
<div>0<span>${useI18n().t("details.minute")}</span></div>
<div>0<span>${useI18n().t("details.second")}</span></div>
`
Manipulating DOM directly inside the script leads to inconsistence in your app, you should drive your component by different reactive data to achieve your goal.
In your current situation try to define a computed property based on the translation then render it inside the template based on its different properties :
<script setup>
const {t} =useI18n()
const time = computed(()=>{
return {
hour:t(""details.hour"),
minute:t(""details.minute"),
second:t(""details.second"),
}
})
</script>
<template>
<div class="time">
<div>0<span>{{time.hour}}</span></div>
<div>0<span>{{time.minute}}</span></div>
<div>0<span>{{time.second}}</span></div>
</div>
</template>

Child components not rendering when referenced dynamically in composition API

I'm converting some components from vue 3's option API to the composition API. In this particular component I have two nested child components:
<script lang="ts" setup>
import ShiftOperation from "#/components/transformation-widgets/ShiftOperation.vue";
import RawJolt from "#/components/transformation-widgets/RawJolt.vue";
console.log([ShiftOperation, RawJolt])
...
From what I understand, if you're using the setup attribute in the script tag then all you have to do is import the component into a variable like I'm doing above and it should be available for the template without having to do anything else, like it's not like the old options api where you had to inject those components into the parent component.
Both components are imported successfully (confirmed by the console log:
When I'm rendering out this parent component I'm using the two child components to render out an array of data where I reference the children dynamically in the template based on information in each block of data that I'm iterating over:
<template>
<div class="renderer-wrapper">
<component
v-for="(block, index) in store.specBlocks"
v-bind:key="index"
:block="block"
:index="index"
:is="determineBlockComponent(block)"
#block-operation-updated="updateBlock"
>
</component>
</div>
</template>
// logic for determining the component to use:
export const determineBlockComponent = (block: JoltOperation) => {
switch (block.renderComponent) {
case 'shift':
return 'ShiftOperation'
default:
return 'RawJolt'
}
}
This worked fine in the options api version of it, but for some reason the components don't actually render. They show up in the elements tab:
But they don't show up in the view. I also added a created lifecycle hook into the child components that just console.log's out saying "created X", but those hooks don't fire.
Business logic wise nothing has changed, it's just been going from option api to composition api, so I'm assuming I'm missing some key detail.
Any ideas?
Your determineBlockComponent function should not return the string but the object of the component. Replace return 'ShiftOperation' with return ShiftOperation

Vuejs how to use repeated methods in single components

How would I use the same method across different components without rewriting the same method for reach component. I looked into mixins but the documentation says 'Use global mixins sparsely and carefully'. So I'm wondering if there is a more ideal way for this approach. Same with global computed.
<template>
<div class="wrapper">
...
Link on many templates
...
</div>
</template>
<script>
export default {
data() {
return {}
},
methods: {
goToPage(page) {
return this.$store.commit('page/push', {page:page});
}
}
}
</script>
Thanks
Global mixins are not the only type of mixin. See https://v2.vuejs.org/v2/guide/mixins.html
If you want to add a method or computed property to every component then you'd use a global mixin. This would affect all components, including those from third party libraries. You'd need to be careful when choosing a name to ensure you don't collide with anything else. There's also a small performance overhead from using a global mixin. As an example, Vuex uses a global mixin to ensure that the $store property is present on all components.
If you only need to add the method/property to a few components then you'd be much better off with a normal mixin. Typically that would have its own file and look something like this:
// my-mixin.js
export default {
methods: {
goToPage(page) {
return this.$store.commit('page/push', {page:page});
}
}
}
and then within your .vue files:
<script>
import myMixin from 'my-mixin'
export default {
mixins: [myMixin],
// ... all the other options
}
</script>
Given the example in the question seems to be a navigational link, an alternative to using a mixin might be to introduce a suitable component to handle those links. Rather than sharing code between components you'd just use the link component. It would depend on whether the method has uses beyond those links.
There are alternatives, such as sharing things globally across components using Vue.prototype, but for the example given in the question that doesn't seem a good fit.
I would also note that Vue 3 introduces some new alternatives to mixins via the composition API. However, it isn't immediately obvious that using composition would actually be an improvement in your specific use case. Vue 3 is also still in beta.

How to import multiple components with the same name in a Vuejs project?

I've inherited a Vue.js project that, among other things, has this:
import Multiselect from 'vue-multiselect';
<multiselect :multiple="true"
v-model="selectedTags"
:options="tagOptions"
label="title"
track-by="id"></multiselect>
Now, on the same page, we are also supposed to have a different multiselect object, imported from a different place, that looks like:
import Multiselect from './../../../../../../vendor/devcompany/scripts/vue/components/form/multiselect.vue';
<multiselect v-model="selectedTeacherIds"
:sortable="true"
:options="computedTeacherOptions">
<template slot="selected-option-value" slot-scope="{optionKey}">
{{teacherNames[optionKey]}}
</template>
Each of these works well individually, but I am supposed to somehow import both of them and use them both within the same component. Clearly this will not work without some sort of alteration to the code.
Is there some syntax to, perhaps, import as and thus change the name of one of the objects? Or do I need to go into the source code of one of them and change the naming there? The former (or some other solution not requiring changing the multiselect core files themselves) would be more desirable.
EDIT: This is what the code looks like more broadly.
import Multiselect from 'vue-multiselect';
import BbcodeEditor from './../elements/bbcode-editor';
import ApiVideoSelect from './api-video-select';
/* import other assets */
export default {
components: {ApiVideoClassDetail, ApiProgramCard, ApiUploader, Multiselect, Draggable, Datepicker, BbcodeEditor, ApiVideoSelect},
So I am wondering how to edit this syntax to add the other multiselect under a different name. I don't see how this could work:
import Multiselect from 'vue-multiselect';
import Multiselect from './../../../../../../vendor/frismedia/scripts/vue/components/form/multiselect.vue';
You do not have to change core files, just change the name while using the component:
import Multiselect from '....vue/components/form/multiselect.vue';
// your parent component
export default {
components: {
'my-custom-multiselect' : Multiselect
}
}
// in template:
<my-custom-multiselect> </my-custom-multiselect>
A simpler syntax: Change the name while importing
import MyCustomMultiselect from '....vue/components/form/multiselect.vue';
export default {
components: {
MyCustomMultiselect
}
}
// in template:
<my-custom-multiselect> </my-custom-multiselect>

How does Vue.js know this markup is the component?

How does Vue.js know <my-component> equals/is MyComponent?
<template>
<!-- Vue.js correctly inserts the component here
But how does it know my-component = MyComponent? -->
<my-component></my-component>
</template>
<script type="text/javascript">
import MyComponent from '../MyComponent.vue'
export default {
components: {
MyComponent
}
}
</script>
It is a fairly standard convention that APIs like Vue has will convert between kebab-case and PascalCase / camelCase because HTML is case insensitive.
See this info here: https://v2.vuejs.org/v2/style-guide/#Component-name-casing-in-JS-JSX-strongly-recommended
More specifically it "knows" because vue contains methods to parse camelCase, kebab-case (snake-case) in strings, props, etc.
You can see how this might be done in the util.js file found in vue/src/shared/util.js around line 157. see camelize and hyphenate
I believe PascalCase is simply handled using camelize combined with thecapitalize utility. Something like...
var camelized = camelize();
var pascalized = capitalize(camelized);