Why doesn't the nested component render as a slottable custom element? - custom-element

Are there limitations to compiling Svelte components as custom elements? For instance, are we able to nest components? And fill slots in those nested components?
I'm having trouble using a Svelte component as a custom element in my older Vue app.
I've got a Select and a Modal component in this simplified example: https://svelte.dev/repl/4d4ad853f8a14c6aa27f6baf33751eb8?version=3.6.4
I'm then compiling these with a standard-fare rollup.config.js:
export default {
input: "src/components.js",
output: [
// ...
{ file: "dist/index.min.js", format: "umd", name }
],
plugins: [
svelte({
dev: !production,
customElement: true,
// ...
}),
resolve(),
commonjs(),
!production && livereload("public"),
production && terser()
],
// ...
};
Then I go to use the custom elements. On click of the <conversational-select>, I get markup that looks like the following:
<conversational-select label="Domains" firstvaluelabel="All Domains">
<!-- shadow-root -->
<style>...</style>
<span class="select" >
<div class="select-value">Governance Board</div>
<div class="select-label" ></div>
</span>
<!-- The below div is the appropriate markup for Modal but the style isn't inlined and isn't slotted.
<!-- maybe because it didn't append as <sk-modal>? -->
<div ><slot></slot></div>
</conversational-select>
The "Modal" is sort-of rendering. But it doesn't fill the slot with span .chips. Doesn't inline the styles like the conversational-select does. Doesn't seem to attach its own event listeners. But does seem to create the fade transition thanks to Svelte's transition:fade directive.
I can reproduce this with a vanilla html file, so it's not Vue's fault.
Am I breaking some known rule with custom elements, butting up against the limitations of Svelte's custom element compilation, or just mistaken somewhere?

I was the author of the Svelte github issue that has been mentioned. I believe that I have a fix here. There were a few issues that existed:
slotted was never set
"nested" elements were not being added correctly
I expect the Svelte authors to make changes to my pull request, but if you want to use it, you can:
Clone my branch
Run npm && npm build && npm link
Go to your project and run npm link svelte

Related

How to properly load Bootstrap5's Masonry into Nuxt?

I am trying to use the Masonry plugin with Bootstrap5 and NuxtJS. When I follow the example here
https://getbootstrap.com/docs/5.0/examples/masonry/ and incorporate it into my own codesandbox, I notice that my demo is not in the correct masonry format. See the gaps? My sandbox
My example:
Bootstrap's example:
What do I need to do to get my demo into format shown on the Bootstrap Masonry example page?
I checked how to load the script from a CDN either globally or locally. It was working but at one condition: you needed to NOT start on the masonry page.
Meaning that if you loaded the app on a specific page, then moved to the one with the masonry it was working. But not if you started on this specific page. So, a pretty subpar solution.
This article was really helpful to understand how to wait until the CDN script is fully loaded: https://vueschool.io/articles/vuejs-tutorials/how-to-load-third-party-scripts-in-nuxt-js/
Then I realized that we are far better installing it directly as an NPM dependency. Therefore, I proceeded to the masonry repo. Found a great message on how to setup the whole thing in Nuxt.
And after a removal of some useless stuff and some modern dynamic import, here we are
<template>
<main>
<h1>Bootstrap and Masonry</h1>
<div class="row" id="masonry">
<!-- ... -->
</main>
</template>
<script>
export default {
async mounted() {
if (process.browser) {
let { default: Masonry } = await import('masonry-layout')
new Masonry('#masonry', { percentPosition: true })
}
},
}
</script>
The final solution is looking pretty well and there is not a lot of code. On top of that, the code is properly loaded. And you can load it on a click or any other event.

Nuxt not automatically importing components from nested directory

In my nuxt app, components in nested directories are not automatically importing as expected. For some of my components i have something like the following:
vue 2.6.12, nuxt 2.15.0
components\ Directory structure
TopArea\
--SomeComponent.vue
<template>
<header class="header">
<div>Hello</div>
<SomeComponent />
</header>
</template>
No other component in the application has the name SomeComponent. In the example above i get the error: Unknown custom element: <SomeComponent> - did you register the component correctly? For recursive components, make sure to provide the "name" option.. I can get around the issue by specifying the directory name before the component filename (TopAreaSomeComponent), use the prefix option in nuxt.config, or by manually importing the component. This is confusing because the docs state:
Nested Directories
If you have components in nested directories such as:
components/baseButton.vue
The component name will be based on its own filename. Therefore, the component will be:
<button />
It goes on to say "We recommend you use the directory name in the filename for clarity". But that seems like a rule than a recommendation. If i don't use the directory name as part of the filename, dynamic imports are not working for components in nested directories.
Is this an error in the docs or am I reading it wrong?
Since Nuxt 2.15.0, components changed in the way they work as stated in this github issue.
Depending of you structure and how you want to handle your organization, you may edit your configuration accordingly to the migration guide available here: https://github.com/nuxt/components#v1-to-v2
Or you could also simply set the pathPrefix option to have all your components available without any prefix at all.
nuxt.config.js/ts
components: [
{
path: '~/components', // will get any components nested in let's say /components/test too
pathPrefix: false,
},
]
PS: this solution also works with Nuxt3.
This documentation actually do need an update, indeed: https://nuxtjs.org/docs/2.x/directory-structure/components#components-discovery
This is how it works: for a given page
<template>
<div>
<yolo-swag /> <!-- no need for <nested-yolo-swag /> here -->
</div>
</template>
And with the following file tree
Update for Nuxt3
Looks like this is the new official syntax
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
components: {
global: true,
dirs: ['~/components']
},
})
This may answered already. But to illustrate the solution to comers here here's the way according to docs:
<TopAreaSomeComponent />
if your components is nested deeply:
components / TopArea / SomeComponent.vue
https://nuxtjs.org/docs/directory-structure/components/#nested-directories

Vue won't render anything if there are *any* errors

I'm learning Vue, and I notice that when other people view their webpages, it mostly displays alright even if they haven't finished the code. This is the behavior I'm used to from vanilla html/css/javascript.
But every time I have any problem with a component, e.g. undefined variable, it breaks the whole component and nothing gets displayed. For example, in a Single File Component if I have
<template>
<form>
<label>Write the title:</label>
<input v-model="title"></input>
/* Bunch of other stuff */
<form>
</template>
<script>
export default {
data() {
return {
/* I forget to write title here */
}
}
}
</script>
<style>
</style>
The whole template won't get displayed. Why is this happening and how can I fix it? It makes development much harder.
Btw I'm using Vue CLI 3 and used Vue UI to create the project, and "npm run serve" to view the site.

Can't select element generated by third-party Vue plugin

I'm using Owl Carousel for Vue. It doesn't seem to work properly since all carousel items are visible in their global container, which is several screens wide (there's no overflow: hidden or any max-width to make only x items visible at a time).
Anyway I find myself forced to apply some container class to a wrapper that the plugin generates dynamically. To that end I do:
mounted () {
this.$nextTick(() => {
document.querySelector('.owl-carousel').classList.add('container')
})
}
But, querySelector('.owl-carousel') is null although I see it in the DOM.
How can I successfully select it?
wow a jquery plugin wrapped into vue ... with like 200 lines of props ...
props start here: L23
props end here : L220
but honestly just add your class here:
<div :id="elementHandle" :class="['owl-carousel', 'owl-theme', 'your-class-here']">
fork it
customize it
npm install <git repo url>

How to trigger $('.button-collapse').sideNav('hide'); in vuejs 2 without jQuery?

I have successfully added the following code which provides me with the sideNav from materialise:
<v-btn-link v-side-nav:side-nav="nav" class="button-collapse btn-flat" id="btn-side-menu"><i class="material-icons">menu</i></v-btn-link>
<v-side-nav id="side-nav" class="hide-on-small">
<a v-on:click="handleNavDashboard()">Dashboard</a>
<a v-on:click="handleLogout()">Logout</a>
</v-side-nav>
and I use the following methods:
methods: {
handleLogout () {
console.log('LOGGED OUT')
this.$store.dispatch('clearAuthUser')
window.localStorage.removeItem('authUser')
this.$router.push({name: 'login'})
},
handleNavDashboard () {
console.log('GOING DASHBOARD')
console.log(this)
this.$router.push({name: 'dashboard'})
}
}
so when I am on the home page and i click Dashboard, I get the dashboard contents on the screen but the sideNav menu and the darkened background are still there. Materialise-css says you can use this function
$('.button-collapse').sideNav('hide');
to hide it progmatically but I don't have jQuery installed. How to I reset the sideNav after a nav click?
CDN
From Materialize docs:
One last thing to note is that you have to import jQuery before
importing materialize.js!
<body>
<!--Import jQuery before materialize.js-->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/materialize.min.js"></script>
</body>
NPM
Much better way, install jQuery via npm :
npm install jquery
and use webpack ProvidePlugin to make jQuery global module available in all of your files
here is sample of webpack.config.js file
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery"
})
In Vue.js DOM manipulations are encapsulated inside directives, you can use conditional rendering directives v-if or v-show to make this work without using jquery:
jsFiddle example
Also check component framework Vuetify.js that provide clean, semantic and reusable components.
If you want to include jQuery in to a project which is using requires or imports, then you need to make sure it's required and not included using script tags, because it will be outside the scope of the compiled code (unless it was shimmed), so add the following to your project:
ES6 syntax:
import jQuery from 'jquery';
window.$ = window.jQuery = jQuery
ES5 Syntax:
window.$ = window.jQuery = require('jquery');
And make sure you have installed jQuery:
npm install jquery --save-dev
This puts jQuery into the global scope so it can be used site wide. The docs for that package don't really make that clear, and for some reason they don't mention that jQuery is a dependency, but looking at the code it clearly is for some of the components.
If you don't want to use JQuery and Materialize, you can use the directive :v-show="showAside" or :v-if="showAside" with a property like showAside (in data) and handle the value with a click.
There is a very quick and cheap example: https://jsfiddle.net/nosferatu79/p85rw6xz/