Vue 3 component data binding doesn't work - vue-component

In the following example, {{test}} doesn't get updated according to the input of the component. What am I doing wrong?
<html>
<body>
<Component v-model="test"></Component>
{{test}}
<script type="module">
import {createApp} from './node_modules/vue/dist/vue.esm-browser.prod.js';
const Component = {
props: {
modelValue: String,
},
emits: [
'update:modelValue',
],
template: `<input #keyup="updateValue">`,
methods: {
updateValue(event) {
this.$emit('update:modelValue', event.target.value);
},
},
};
const app = createApp({});
app.component('Component', Component);
app.mount('body');
</script>
</body>
</html>

You forget to declare test variable in data options.
const app = createApp({
data: () => {
return {
test: ''
}
}
});

Related

Can I load the component template dynamically

Is it possible to load the template from server (include components) dynamically? Or can I change the template before it rendered?
I would like to let user to store their own form template into a database and generate the form according to the template-id.
I tried to change the this.$options.template, but it seems like only work for vue2.
<!-- static/myproj/js/my-field.vue -->
<template>
<label :for="name+'Fld'" v-html="title"></label>
<input :name="name" :type="text" :value="value" :id="name+'Fld'"/>
</template>
<script>
export default{
props: {
name: {type:String, required:true},
value: {type:String, required:false, default:''},
type: {type:String, required:true},
title: {type:String, required:false, default:'Field: '},
},
data: function(){ return {}; },
}
</script>
// index.vue
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
template: '<div>I\'m here to be replaced.</div>',
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
}
<!-- server response for /api/template/abcdefg -->
<form action="POST">
<my-field name="name" title="Your Name: " type="text"/>
<my-field name="email" title="Email: " type="email"/>
<input type="submit"/><input type="reset"/>
</form>
Thanks.
Finally, I got the solution. According to Vue3: How to use Vue.compile in Vue3, we can render the template directly by Vue3 like this:
// index.vue
import { h, compile } from 'vue';
const loadVueModuleOpts= {
moduleCache: {vue: Vue},
async getFile(url) {
const res = await fetch(url);
if ( !res.ok )
throw Object.assign(new Error(res.statusText + ' ' + url), { res });
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
}
},
};
export default{
props: {
id: {required:true, type:String, default:'abcdefg'},
},
data: function(){
this.loadSource();
return {
source: null,
target: null,
};
},
// Magic here
render: function(){
if(this.target)
return h(compile(this.target).bind(this));
return h('div', 'Loading...');
},
created: async function(){
this.$options.template=await axios.get(`/api/template/${id}`).then(resp=>resp.data);
},
components: {
'my-field': Vue.defineAsyncComponent( ()=>loadModule('/static/myproj/js/my-field.vue', loadVueModuleOpts)),
},
}

Storybook Vue3 - Work with v-model in stories

I have a question regarding Storybook and Vue components with v-models. When writing a story for let's say an input component with a v-model i want a control reflecting the value of this v-model. Setting the modelValue from the control is no problem, but when using the component itself the control value stays the same. I am searching the web for a while now but i can't seem to find a solution for this.
A small example:
// InputComponent.vue
<template>
<input
type="text"
:value="modelValue"
#input="updateValue"
:class="`form-control${readonly ? '-plaintext' : ''}`"
:readonly="readonly"
/>
</template>
<script lang="ts">
export default {
name: "GcInputText"
}
</script>
<script lang="ts" setup>
defineProps({
modelValue: {
type: String,
default: null
},
readonly: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
</script>
In Storybook:
Does anyone have a solution to make this working?
Thanks in advance!
In my case, I have a custom select input that uses a modelValue prop.
I tried this and worked for me:
at my-component.stories.js:
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
export default {
title: 'Core/MyComponent',
component: MyComponent,
argTypes: { }
}
const Template = (args) => ({
components: { MyComponent },
setup() {
let model = ref('Javascript')
const updateModel = (event) => model.value = event
return { args, model, updateModel }
},
template: '<my-component v-bind="args" :modelValue="model" #update:modelValue="updateModel" />'
})
export const Default = Template.bind({})
Default.args = {
options: [
'Javascript',
'PHP',
'Java'
]
}

Convert options api to composition api for vue3 - v-model binding and watch

I have the following working code for a search input using options API for component data, watch and methods, I am trying to convert that to the composition api.
I am defining props in <script setup> and also a onMounted function.
<template>
<label for="search" class="hidden">Search</label>
<input
id="search"
ref="search"
v-model="search"
class="border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm h-9 w-1/2"
:class="{ 'transition-border': search }"
autocomplete="off"
name="search"
placeholder="Search"
type="search"
#keyup.esc="search = null"
/>
</template>
<script setup>
import {onMounted} from "vue";
const props = defineProps({
routeName: String
});
onMounted(() => {
document.getElementById('search').focus()
});
</script>
<!--TODO convert to composition api-->
<script>
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {
// page.props.search will come from the backend after search has returned.
search: this.$inertia.page.props.search || null,
};
},
watch: {
search() {
if (this.search) {
// if you type something in the search input
this.searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
this.$inertia.get(route(this.routeName));
}
},
},
methods: {
searchMethod: _.debounce(function () {
this.$inertia.get(
route(this.routeName),
{ search: this.search }
);
}, 500),
},
});
</script>
What I am trying to do is convert it to the composition api. I have tried the following but I can't get it to work at all.
let search = ref(usePage().props.value.search || null);
watch(search, () => {
if (search.value) {
// if you type something in the search input
searchMethod();
} else {
// else just give us the plain ol' paginated list - route('stories.index')
Inertia.get(route(props.routeName));
}
});
function searchMethod() {
_.debounce(function () {
Inertia.get(
route(props.routeName),
{search: search}
);
}, 500)
}
Any help or pointers in how to convert what is currently in <script> into <script setup> would be greatly appreciated thanks.
I managed to get this working with the below!
<script setup>
import {onMounted, ref} from "vue";
import {Inertia} from "#inertiajs/inertia";
const props = defineProps({
route_name: {
type: String,
required: true
},
search: {
type: String,
default: null
}
});
const search = ref(props.search);
onMounted(() => {
search.value.focus();
search.value.addEventListener('input', () => {
if (search.value.value) {
searching();
} else {
Inertia.get(route(props.route_name));
}
});
});
const searching = _.debounce(function() {
Inertia.get(route(props.route_name), {search: search.value.value});
}, 500);
</script>

Nuxt3 can't import component in tests

I'm trying to run a component unit test on Nuxt 3 but I get an error telling me that the component cannot be found..
FAIL test/components/button.test.ts [ test/components/button.test.ts ]
Error: Failed to resolve import "#/components/Texts/Button/ButtonText.vue" from "components\Button\Button.vue". Does the file exist?
button.spec.ts
import {test, expect} from 'vitest';
import {mount} from '#vue/test-utils';
import Button from "../../components/Button/Button.vue";
test('Button Component', async () => {
const button = mount(Button, {
props: {
text: 'My Test Button'
}
});
expect(button.text()).toEqual('My Test Button');
});
Button.vue
<template>
<div class="Button">
<slot name="left" />
<ButtonText :text="text" />
<slot name="right" />
</div>
</template>
<script lang="ts">
export default {
name: 'Button',
components: {ButtonText},
props: {
// Text to display in the button
text: {
type: String as () => string,
default: 'Button',
required: true,
},
}
}
</script>
any ideas ?
Assuming, that #/components/Texts/Button/ButtonText.vue actually exists, a solution to your problem might be adding aliases to your ./vitest.config.ts like that:
// vitest.config.ts
import { defineConfig } from 'vite'
import { aliases } from './aliases'
export default defineConfig({
resolve: { aliases },
// ... further settings
})
// aliases.ts
import { resolve } from 'path';
const r = (p: string) => resolve(__dirname, p);
export const alias: Record<string, string> = {
'~~': r('.'),
'~~/': r('./'),
'##': r('.'),
'##/': r('./'),
// ... other aliases
};

how to configure rollup vue3 environment

when I use rollup configure vue3 environment ,i run it then use the component in the html file,it happen error
the code below
rollup.config.js this is rollup config file
const path = require('path')
const resolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')
const json = require('rollup-plugin-json')
const vue = require('rollup-plugin-vue')
const postcss = require('rollup-plugin-postcss')
const inputPath = path.resolve(__dirname,'./src/index.js')
const outputUmdPath = path.resolve(__dirname,'./dist/imooc.datav.js')
const outputEsPath = path.resolve(__dirname,'./dist/imooc.datav.es.js')
module.exports = {
input:inputPath,
output:[{
file:outputUmdPath,
format:'umd',
name:'imoocDatav',
globals: {
vue: 'Vue'
}
},{
file:outputEsPath,
format:'es',
globals: {
vue: 'Vue'
}
}],
plugins:[
vue(),
babel({
exclude:'node_modules/**',
presets: ["#vue/babel-preset-jsx"]
}),
resolve(),
commonjs(),
json(),
// vue(),
postcss({
plugins:[]
})
],
external:['vue']
}
Test.vue this is vue3 component
<template>
<div class="text">{{ aa }}</div>
</template>
<script>
export default {
name: 'Test',
setup() {
const aa = 'hello';
return {
aa,
};
},
};
</script>
<style lang="scss" scoped>
.text {
color: red;
}
</style>
index.js the code make an global component
import Test from './Test.vue'
export default function(Vue){
Vue.component(Test.name,Test);
}
index.html this is html file
<!DOCTYPE html>
<html>
<head>
<title>imooc datav libs example</title>
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.0-beta.6/dist/vue.global.js"></script>
<script src="../dist/imooc.datav.js"></script>
</head>
<body>
<div id="app">
{{message}}
<test></test>
</div>
<script>
Vue.createApp({
setup(){
var message = 'hello world';
return {
message
}
}
}).use(imoocDatav).mount('#app');
</script>
</body>
</html>
vue.global.js:4877 TypeError: Cannot read property 'aa' of undefined
package.json dependencies:
"vue": "^3.0.0"
Change:
external:['vue']
to
external:['#vue']
This works for me

Resources