How to import multiple components at once? - vue.js

How to import multiple components at once?
Then place the components on the page through
<component :is="item" v-for="item in dialogs" :key="item.name" />
<template>
<div class="container">
<component :is="item" v-for="item in dialogs" :key="item.name" />
</div>
</template>
<script>
// TODO 组件批量导入,暂时失败
import path from 'path'
const files = require.context('#/components/Menu/Dialog', true, /\.vue$/)// param1:路径;param2: 是否搜索子文件夹:param3: 文件类型
var dialogs = {}
// 组件导入
files.keys().forEach((key) => {
const name = path.basename(key, '.vue')
/**
* path.basename获取vue文件名,也可以用正则表达式匹配
* key.replace(/^\.\/(.*)\.\w+$/, '$1')
**/
dialogs[name] = files(key).default || files(key)
})
console.log(dialogs)
export default {
name: 'Panel',
components: dialogs
}
</script>

Related

ReferenceError: contenuto_translated is not defined

I'm stuck at this error that I get only if I run my laravel + vuejs in build mode.
Ihave a custom component with 3 input and I'm using it in a Quasar dialog. When I start typing inside one of these 3 inputs, it give me the error I wrote in the post title.
See code and details.
I have a custom component file (EditObjectTranslation_Singleline.vue)
<template>
<q-dialog ref="dialogRef" #hide="onDialogHide">
<q-card class="q-dialog-plugin">
<!--
...content
... use q-card-section for it?
-->
<q-card-section>
<div class="text-h6">{{ titolo }}</div>
<q-form #submit.prevent="invia_form">
<div>
<q-input outlined v-model="contenuto_campo" label="Contenuto CAMPO" class="mt-4 block w-full" :disable="true"/>
</div>
<div>
<q-input outlined v-model="contenuto_lingua_default" label="Contenuto Lingua IT" class="mt-4 block w-full" :disable="true"/>
</div>
<div>
<q-input outlined v-model="contenuto_translated" :label="label_translated" class="mt-4 block w-full" :maxlength="max_lunghezza"/>
</div>
</q-form>
</q-card-section>
<!-- buttons example -->
<q-card-actions align="right">
<q-btn color="primary" label="OK" #click="onOKClick" />
<q-btn color="primary" label="Cancel" #click="onDialogCancel" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup>
import { useDialogPluginComponent } from 'quasar'
import { computed } from '#vue/reactivity'
const props = defineProps({
contenuto_campo: '',
contenuto_lingua_default: '',
contenuto_translated: '',
lingua: '',
max_lunghezza: 0
})
const label_translated = computed (() => {
return "Contenuto tradotto " + props.lingua
})
defineEmits([
// REQUIRED; need to specify some events that your
// component will emit through useDialogPluginComponent()
...useDialogPluginComponent.emits
])
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
// dialogRef - Vue ref to be applied to QDialog
// onDialogHide - Function to be used as handler for #hide on QDialog
// onDialogOK - Function to call to settle dialog with "ok" outcome
// example: onDialogOK() - no payload
// example: onDialogOK({ /*...*/ }) - with payload
// onDialogCancel - Function to call to settle dialog with "cancel" outcome
// this is part of our example (so not required)
function onOKClick () {
// on OK, it is REQUIRED to
// call onDialogOK (with optional payload)
onDialogOK(props)
// or with payload: onDialogOK({ ... })
// ...and it will also hide the dialog automatically
}
</script>
Then, I want to use the previous component as a custom component in a quasar dialog.
So the page code where I'm using the previous custom components follows
<script setup>
import AuthenticatedLayout from '#/Layouts/AuthenticatedLayout.vue';
import { Inertia } from '#inertiajs/inertia';
import { useQuasar } from 'quasar';
import { ref } from 'vue';
import { computed } from '#vue/reactivity';
import { Link } from '#inertiajs/inertia-vue3';
import CustomComponent from '#/Components/EditObjectTranslation_Singleline.vue';
import axios from 'axios';
const $q = useQuasar()
const props = defineProps({
catalogues: Array,
lingue: Array,
field2betranslated: Number
})
const $qTranslate = useQuasar()
function onTranslateObject (pLingua) {
if (selected.value.length > 0 ) {
axios
.get('/api/getTraduzioni/'+ pLingua + '/' + selected.value[0].resource_id)
.then((response) => {
$q.dialog({
component: CustomComponent,
componentProps: {
titolo: 'Traduci contenuti:',
contenuto_campo: selected.value[0].name,
contenuto_lingua_default: response.data.originale,
contenuto_translated: response.data.tradotto,
lingua: pLingua,
max_lunghezza: 64,
resource_id: selected.value[0].resource_id
// ...more..props...
}
}).onOk((formData) => {
// console.log('>>>> OK')
translateObject(formData)
}).onOk(() => {
// console.log('>>>> second OK catcher')
}).onCancel(() => {
// console.log('>>>> Cancel')
}).onDismiss(() => {
// console.log('I am triggered on both OK and Cancel')
})
})
} else {
$q.dialog({
title: 'Attenzione',
message: 'Devi selezionare una riga'
})
}
}
</script>
<template>
<Head :title="$t('cataloghi.pagetitle') " />
<AuthenticatedLayout>
<div class="q-py-xl">
<!-- Pulsanti traduzioni -->
<div class="w-full flex justify-end space-x-2 mb-4">
<q-btn v-for="lingua in lingue" :label="lingua.codiceIso" :key="lingua.codiceIso" #click="onTranslateObject(lingua.codiceIso)" icon="language" color="whte" text-color="text-grey-7" />
</div>
</div>
</AuthenticatedLayout>
</template>
When I open the dialog and try to input in the "contenuto_translated" q-input I get this error:
"ReferenceError: contenuto_translated is not defined"
Surely I'm missing something.
Please help me.

How can I accept only one component as slot in vue

I have a button-group component and I want this component only accepts Button component that I created as slots.
So this is my ButtonGroup component:
<template>
<div class="button-group">
<slot />
</div>
</template>
<script lang="ts">
export default {
name: 'ButtonGroup',
components: {
Button,
},
};
</script>
How can I accept only Button component as slot?
use render function
<script>
import {h} from 'vue';
export default {
name: 'ButtonGroup',
render() {
const buttons = []
for (let defaultElement of this.$slots.default()) {
// case: <button />
if (defaultElement.type === 'button') {
buttons.push(defaultElement)
}
// other component
// if (defaultElement.type.name === 'Button') {
// buttons.push(defaultElement)
// }
}
return h('div', {class: 'button-group'}, buttons)
}
};
</script>
I referenced here https://vuejs.org/api/render-function.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div class="button-group">
<slot name="button">
<Button />
</slot>
</div>
</template>
<script>
export default {
name: 'ButtonGroup',
components: {
Button,
},
};
</script>
//Use of ButtonGroup component`enter code here`
<ButtonGroup>
<template #button />
</ButtonGroup>

Show on click / hide on blur wrapper component

I have several widgets that I'd like to toggle on click/blur/submit.
Let's take a simple example with an input (Vue 2 style)
Input.vue
<template>
<input
ref="input"
:value="value"
#input="input"
#blur="input"
#keyup.escape="close"
#keyup.enter="input"
/>
</template>
<script>
export default {
props: ['value'],
methods: {
input() {
this.$emit("input", this.$refs.text.value);
this.close();
},
close() {
this.$emit("close");
},
}
}
</script>
ToggleWrapper.vue
<template>
<div #click="open = true">
<div v-if="open">
<slot #close="open = false"></slot> <!-- Attempt to intercept the close event -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
}
},
}
</script>
Final usage:
<ToggleWrapper>
<Input v-model="myText" #submit="updateMyText" />
</ToggleWrapper>
So when I click on ToggleWrapper it appears, but if I close it, it doesn't disappear because it's not getting the close event.
Should I use scoped events ?
How can I intercept the close event by adding the less possible markup on the final usage ?
I think it makes sense to use a scoped slot to do this. But you can also try this kind of solution.
Input.vue
<template>
<input
ref="input"
:value="value"
#input="input"
#blur="input"
#keyup.escape="close"
#keyup.enter="input"
/>
</template>
<script>
export default {
props: ['value'],
methods: {
input() {
this.$emit("input", this.$refs.text.value);
this.close();
},
close() {
this.$parent.$emit('close-toggle')
},
}
}
</script>
ToggleWrapper.vue
<template>
<div #click="open = true">
Click
<div v-if="open">
<slot></slot> <!-- Attempt to intercept the close event -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
}
},
created() {
this.$on('close-toggle', function () {
this.open = false
})
}
}
</script>
In a Vue3 style, I would use provide and inject (dependency injection). This solution leaves the final markup very light and you still have a lot of control, see it below :
Final usage :
<script setup>
import { ref } from 'vue'
import ToggleWrapper from './ToggleWrapper.vue'
import Input from './Input.vue'
const myText = ref('hi')
const updateMyText = ($event) => {
myText.value = $event
}
</script>
<template>
<ToggleWrapper>
<Input :value="myText" #submit="updateMyText" />
</ToggleWrapper>
<p>value : {{myText}}</p>
</template>
ToggleWrapper.vue
<template>
<div #click="open = true">
<div v-if="open">
<slot></slot>
</div>
<span v-else>Open</span>
</div>
</template>
<script setup>
import { provide, inject, ref } from 'vue'
const open = ref(false)
provide('methods', {
close: () => open.value = false
})
</script>
Input.vue
<template>
<input
:value="value"
#input="input"
#blur="close"
#keyup.escape="close"
#keyup.enter="submit"
/>
</template>
<script setup>
import { inject, ref } from 'vue'
const props = defineProps(['value'])
const emit = defineEmits(['close', 'input', 'submit'])
const methods = inject('methods')
const value = ref(props.value)
const input = ($event) => {
value.value = $event.target.value
emit("input", $event.target.value);
}
const close = () => {
methods.close()
emit('close')
}
const submit = () => {
emit('submit', value.value)
close()
}
</script>
See it working here

how can I get the data from localStorage to show in homePosts vue.js compositions API

I'm doing a simple blog app to practice vue.js. I'm using composition API. I have stored data that get filled in in a form. This data I want to print out in another component homePosts where you can see the written blogpost with writer, headline and blogtext. I have used v-model, stored data to localStorage, in homePosts I have used v-for and {{ }} syntax to get data. But nothing shows in homePosts.
Can someone please see what im missing.
writePost.vue
<template>
<div>
<form class="form">
<label for="writer">Writer name: </label>
<input v-model="newWriter" type="text" max="500" />
<br />
<label for="img">Select image:</label>
<input type="file" id="img" name="img" accept="image/*" />
<br />
<label for="headline">Headline </label>
<input v-model="newHeadline" type="text" max="500" />
<label>Your blogtext: </label>
<textarea v-model="newNote" name="" id="" cols="30" rows="30"></textarea>
<button type="submit" #click="addNote" class="button"><router-link to="/homePosts" class="link">Post blog</router-link></button>
</form>
</div>
</template>
<script setup>
import { ref } from "vue";
const newNote = ref("");
const newWriter = ref("");
const newHeadline = ref("");
const notes = ref([]);
const addNote = () => {
notes.value.push({
id: Math.floor(Math.random() * 1000000),
text: newNote.value,
writer: newWriter.value,
headline: newHeadline.value,
});
addLocalStorage(notes)
};
const addLocalStorage = (notes) => {
localStorage.setItem("notes", JSON.stringify(notes))
JSON.parse(localStorage.getItem("notes"));
}
</script>
homePosts.vue
<template>
<div class="post-container">
<h1>Blog Posts</h1>
<div class="post-mini-container" >
<div class="post" v-for="note in notes" :key="note.id">
<!-- <img class="img-post" src="#/assets/person1.jpg"> -->
<p class="writer"> {{ note.writer }}</p>
<p class="headline"> {{ note.headline }}</p>
<p class="blog-text" > {{ note.text }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'homePosts'
}
</script>
You need to start your ref already parsing the existing items in your localStorage.
const notes = ref(JSON.parse(localStorage.getItem('notes') ?? '[]');
Or better yet, use a computed getter/setter:
const notes = computed({
get: () => JSON.parse(localStorage.getItem('notes') ?? '[]'),
set: (value) => {
localStorage.setItem('notes', JSON.stringify(value))
}
});
Or even better, take a look at vueUse/useLocalStorage 🎉
there are two approaches that you can follow, "event bus" or "pinia / vuex".
i'll explain how you can implement event bus
(you can check this post for inspiration: https://medium.com/#certosinolab/using-event-bus-in-vue-js-3-425aae8c21a6)
Add global event bus
install mit: npm install --save mitt
go to your main.ts / main.js and add the global property
import mitt from 'mitt';
const dispatcher = mitt();
const app = createApp(App);
app.config.globalProperties.dispatcher = dispatcher;
app.mount('#app');
update "script" content in writePost.vue component
<script setup>
import { ref , getCurrentInstance } from "vue";
const app = getCurrentInstance();
const dispatcher= app?.appContext.config.globalProperties.dispatcher;
const newNote = ref("");
const newWriter = ref("");
const newHeadline = ref("");
const notes = ref([]);
const addNote = () => {
notes.value.push({
id: Math.floor(Math.random() * 1000000),
text: newNote.value,
writer: newWriter.value,
headline: newHeadline.value,
});
// emit notes
dispatcher.emit("updateNotes" , notes);
addLocalStorage(notes)
};
const addLocalStorage = (notes) => {
localStorage.setItem("notes", JSON.stringify(notes))
JSON.parse(localStorage.getItem("notes"));
}
</script>
update "script" content in homePosts.vue component
<script>
export default {
name: 'homePosts',
data() {
return {notes: []}
},
mounted() {
this.notes = JSON.parse(localStorage.getItem("notes") ?? "[]");
this.dispatcher.on("updateNotes" , (notes) => {
this.notes = notes ?? [];
})
},
beforeDestroy() {
this.dispatcher.off("updateNotes");
},
}
</script>

Vue.js Passing a variable in a loop to another component

As a beginner in Vue.js,I am trying to create a Todo app, but problems seems to passing a variable to another component when looping.
i want to pass item to another embedded component.
Here what I have with all the components:
in main.js:
import Vue from "vue";
import App from "./App.vue";
import Todo from "./components/Todo";
import itemComponent from "./components/itemComponent";
Vue.config.productionTip = false;
Vue.component('todo', Todo);
Vue.component('itemcomponent', itemComponent);
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
export default {
name: "App",
components: {
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="item.id">
<itemcomponent {{item}}></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
components: {
itemcomponent,
},
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
};
</script>
in itemComponent.vue:
<template>
<div>
<input type="text" value={{item }}/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>
what is my mistake? thanks for help.
Quite a bunch of mistakes:
You should import single page components inside their parent components and register them there, not in main.js file. EDIT: Explaination to this is given in docs
Global registration often isn’t ideal. For example, if you’re using a build system like Webpack, globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download.
You have a component registration in Todo.vue, but you have misplaced it inside data so its is just a data object that is not getting used, move into components.
In your loop you have item.id, but your item is just a plain string, it does not have an id property. Either change item to object with id property, or simply use the index provided in loop (not recommended).
Pass your item as a prop, not mustache template. So in Todo.vue replace {{ item }} with :item="item"
In your ItemComponent.vue you have mustache syntax once again in the attribute. Change value={{item }} to :value="item"
So here's the final code:
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
import Todo from './components/Todo.vue';
export default {
name: "App",
components: {
Todo,
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="index">
<itemcomponent :item="item"></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
components: {
itemComponent,
}
};
</script>
itemComponent.vue:
<template>
<div>
<input type="text" :value="item"/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>