How to use Select2 in Nuxt3 - vuejs3

The question is simple, how to use select2 in nuxt3?
select2 from https://select2.org/
nuxt3 from v3.nuxtjs.org

I have spent few days to do this, but failed. Finally I made my own npm package. Think might be useful for others, so here is how to do it:
1 npm install
npm install nuxt3-select2 --save
2 create a plugin
// plugins/select2.client.ts
import Select2 from 'nuxt3-select2';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component("Select2", Select2, {});
});
3 use it in template
<template>
<div>
<Select2 v-model="myValue" :options="myOptions" :settings="{ settingOption: value, settingOption: value }" #change="myChangeEvent($event)" #select="mySelectEvent($event)" />
<h4>Value: {{ myValue }}</h4>
</div>
</template>
<script setup lang="ts">
const myChangeEvent = (event) => {
console.log("myChangeEvent: ", event);
}
const mySelectEvent = (e) => {
console.log("mySelectEvent: ", event);
}
const myOptions = [
{id: 1, text: 'apple'},
{id: 2, text: 'berry'},
{id: 3, text: 'cherry'},
]
const myValue = ref();
</script>
you can read more documentation here: nuxt3-select2

Related

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'
]
}

Vue - Vee Validate 4, validate the length of an array #submit

I have some fields where the validation works fine and functionality where items get moved from an unselected array to a selected array. Im trying to validate the length of the selected array before submitting the form. It is required to select at least 1 item.
I'm trying to achieve the onSubmit() method is not fired when the "selected" array is empty. At the moment I'm trying to achieve this with vue's "watch()" method and vee-validates "validate()" method, but to no avail.
Thanks in advance!
I am using:
vue 3
vee-validate 4
Code
<template>
<Form #submit="onSubmit">
//some other fields where the validation works
//functionality that puts an array item from the unselected to the selected array
<Button>Submit</Button>
</Form>
</template>
<script setup>
import {ref, watch} from "vue/dist/vue";
import {validate, Form} from "vee-validate";
const onSubmit = () => {
//submit form if all fields are valid
}
const selected = ref([])
const unselected = ref([
{title: 'Bram janssen', id: 1, value: 1},
{title: 'Bert Bertsma', id: 2, value: 2},
{title: 'Kees Keesma', id: 3, value: 3},
{title: 'Jan Janssen', id: 4, value: 4},
{title: 'Pieter Pietersma', id: 5, value: 5},
{title: 'Lars Klaver', id: 6, value: 6},
])
watch(selected.value, () => {
validate(selected.value, {arrayLength: true})
});
</script>
//this rule lives in another file, confirmed to work
defineRule('arrayLength', (array: any) => {
if (array.length < 1) {
return 'Array is empty, add at lest 1 item';
}
return true;
});
I guess just not with a watch. You can use the validate method directly in your "add to selected". Working example: here
Code looks like this:
<template>
<form #submit="onSubmit">
<p>
<button
#click.prevent.stop="addToArray"
>
Add to Array
</button>
</p>
<p>Array Length: {{ myArray.length }} - array error: {{ arrayError }}</p>
<p>
<button type="submit">Submit</button>
</p>
</form>
</template>
<script setup lang="ts">
import { useField, useForm } from 'vee-validate';
const { handleSubmit } = useForm();
const onSubmit = handleSubmit((values) => {
alert(JSON.stringify(values, null, 2));
});
var {
value: myArray,
errorMessage: arrayError,
validate,
} = useField('myArray', (value) =>
value.length > 0 ? true : 'Please add to the array'
);
myArray.value = [];
function addToArray(){
myArray.value.push('abc');
validate();
}
</script>

Having difficulty setting up pinia stores in nuxt 3

I'm currently trying to setup a project using nuxt 3 with pinia for state management and I have bumped into the following error:
[h3] [unhandled] H3Error: defineStore is not defined
at createError (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:191:15)
at Server.nodeHandler (file:///home/johnr/Code/Personal/test/node_modules/h3/dist/index.mjs:381:21) {
statusCode: 500,
fatal: false,
unhandled: true,
statusMessage: 'Internal Server Error'
}
I initialized the project with npx nuxi init and then ran npm i, followed by npm install #pinia/nuxt. I then added pinia to nuxt.config.ts:
// nuxt.config.js
export default {
// ... other options
modules: [
// ...
'#pinia/nuxt',
],
}
and created a basic store in store/counter.js:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment };
});
and have tried to use the returned count in the app template:
<template>
<div>
<p>The count is {{ counterStore.count.value }}</p>
</div>
</template>
<script setup>
import { useCounterStore } from './store/counter.js';
const counterStore = useCounterStore();
</script>
It looks like you forgot to import defineStore in store/counter.js:
import { defineStore } from 'pinia'

How to use SSR with Stencil in a Nuxt 3 Vite project?

In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})

Adding External Script to Specific Story in Storybook

I wanted to know how I can load in external javascript into a specific story in storybook. The only documentation I can find right now is how to do it globally https://storybook.js.org/docs/react/configure/story-rendering. Doing this works, i would just like to save on performance since only one of my stories uses an external js script.
No, there isn't a standard way to do this in storybook currently (version 6.5).
However you can achieve it with a decorator.
Depending on your needs it could look something like this (this is for a React story):
import { Story, Meta } from '#storybook/react';
import { useEffect } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
useEffect(() => {
const script = document.createElement('script');
script.src = '/my-script';
document.body.appendChild(script);
}, []);
return <Story />;
},
],
};
There are some caveats here though:
The scripts will remain loaded as you navigate to other stories.
The story will render before the script has loaded.
To handle these caveats you can:
Add a cleanup handler to useEffect.
Don't render your <Story/> until the script has loaded.
For example:
import { Story, Meta } from '#storybook/react';
import { useEffect, useState } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const script = document.createElement('script');
script.onload = () => {
setIsLoaded(true);
};
script.src = '/my-script';
document.body.appendChild(script);
return () => {
// clean up effects of script here
};
}, []);
return isLoaded ? <Story /> : <div>Loading...</div>;
},
],
};
If you have multiple scripts you'll have to wrap all the onload events into a Promise.all.
This could be wrapped up in an addon similar to storybook-addon-run-script.

Resources