Why is Prop not applied to component in component library vue js? - javascript

I am busy moving my components from our projects into a component library for ease of use and maintainability purposes.
I am using this Vue/Nuxt Library template following the structure and processes outlined.
Currently I only have 2 components in there a TestButton.vue and a LoaderModal.vue .
The test button works fine when imported/called from an application when it sits in the library as it shows as expected and looks like it should as it is actually a mdb button (which I just used to test component imports). (we use MDB as our main component lib
The problem is with the Loading Modal. Normally you would set the show property of the modal to show or hide it like so.
<template>
<mdb-modal centered :show="showLoader">
<mdb-modal-body class="text-center">
<div class="d-flex justify-content-center" v-if="selectedLoader==='BALL'">
<div class="spinner-grow" role="status" style="color: #005250; width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>
</div>
<h3 class="block" style="margin-top:16px" v-if="longLoadingText!=null">{{ longLoadingText }}</h3>
<h3 class="block" style="margin-top:16px" v-else>{{ text }}</h3>
</mdb-modal-body>
</mdb-modal>
</template>
using props to show,hide and control text like so
props: {
showLoader: { default: true, type: Boolean },
text: { default: "Loading", type: String },
},
and it works fine if I run it in the component library itself using
vue serve ./src/components/LoaderModal
but when I set the showLoader prop to true from the application that imports the library it does not show. I can see the modal is in the DOM but the display is set to none.
There are no errors in the console and if I change the Display to "block" the LoadingModal Shows.
Here is the html copied from the DOM that shows it is there, but display is just set to "none"
<div data-v-bfb6b926="" data-v-6a672d6c="" class="modal" style="opacity: 1;">
<div data-v-bfb6b926="" role="document" class="modal-dialog modal-dialog-centered" style="transform: translate(0px, 0px);">
<div data-v-bfb6b926="" class="modal-content">
<div data-v-c35b1502="" data-v-6a672d6c="" class="text-center modal-body">
<div data-v-6a672d6c="" data-v-c35b1502="" class="d-flex justify-content-center">
<div data-v-6a672d6c="" data-v-c35b1502="" role="status" class="spinner-grow" style="color: rgb(0, 82, 80); width: 3rem; height: 3rem;"><span data-v-6a672d6c="" data-v-c35b1502="" class="sr-only">Loading...</span></div>
</div>
<!---->
<!---->
<h3 data-v-6a672d6c="" data-v-c35b1502="" class="block" style="margin-top: 16px;">Loading some stuff</h3>
</div>
</div>
</div>
</div>
My Library Package.json looks as follow
{
"name": "#myScope/web-commons",
"version": "0.1.23",
"private": false,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --report --target lib --name web-commons ./src/index.js",
"lint": "vue-cli-service lint",
"docs:build": "vuepress build docs",
"docs:dev": "vuepress dev docs"
},
"main": "dist/web-commons.common.js",
"files": [
"src",
"assets",https://stackoverflow.com/posts/63504989/edit#
"dist/*.{js,css}"
],
"dependencies": {
"mdbvue": "^6.7.1",
"vue": "^2.6.11"
},
"devDependencies": {
"#vue/babel-preset-app": "^4.4.6",
"#vue/cli-plugin-babel": "^4.4.6",
"#vue/cli-plugin-eslint": "^4.4.6",
"#vue/cli-service": "^4.4.6",
"babel-eslint": "^10.1.0",
"eslint": "^7.3.1",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11",
"vuepress": "^1.5.2"
}
}
and the plugin looks as follows in my main project.
import Vue from 'vue'
import * as componets from '#myScope/web-commons'
Vue.use(componets)
Then it is also added to the nuxt.config as a plugin.
Please help
EDIT
Below is the value of the props from the vue dev panel
Update
The following is the link to the codesandbox project for the component library. The library is also on Npm. Try using the LoaderModal Component in any nuxt project to see problem.
Code Sandbox Component Library
Component Implementation Example Nuxt js

I see your codesandbox. There is a small mistake using the props in your code for LoaderModal component. If you use a value directly for the props you don't need to bind (:text) it. So you can use the component as below.
<LoaderModal :showLoader="true" text="Some Loading Text"/>
if you use a data element as a props then bind it:
<LoaderModal :showLoader="true" :text="variableName"/>
Here is the working demo of your codesandbox

The actual problem seem to have been that all the required css components of the mdblibrary was not being import despite being imported in the index of the component library.
It seems that when adding a plugin to nuxt it calls the install method and nothing else. The solution was to import the css into the component itself and making it "scoped" otherwise it will affect the components in the main application.
<style
scoped
>
#import "../../node_modules/mdbvue/lib/css/mdb.min.css";
</style>

Related

"TypeError: Class constructor IF cannot be invoked without 'new'" while integrating custom CKEditor build to a VueJS 2.* application

I tried integrating CKEditor 5 to a VueJS v2.6 application . I opted for custom build option using online builder provided by CKEditor (https://ckeditor.com/ckeditor-5/online-builder/). As I need custom addition of plugins, custom build is the preferred option.
After taking a basic build , followed the exact same steps mentioned in the below link :
https://ckeditor.com/docs/ckeditor5/latest/installation/frameworks/vuejs-v2.html#integrating-a-custom-build-from-the-online-builder.
I added the editor tag in Collaboration.vue component inside App.vue .
Collaboration.vue
<script>
import Editor from 'ckeditor5-custom-build/build/ckeditor';
export default {
name: 'Collaboration',
components: {
editor: Editor,
},
data() {
return {
editor: Editor,
editorData: '<p>Content of the editor.</p>',
editorConfig: {
// The configuration of the editor.
}
};
}
};
</script>
<template>
<div>
<b-card class="mt-2 custom-header-wrapper">
<h4 slot="header">Editor</h4>
</b-card>
<div v-if="editorData" class="preview">
<editor :editor="editor" v-model="editorData" :config="editorConfig"></editor>
</div>
</div>
</template>
Below is the error I got on opening Collaboration.vue component where I added the editor tag .
How can I solve this issue?

TinyMCE not loaded in vue component

I'm trying to integrate tinymce in my vue app. I don't want to use the cloud version and I've installed it inside my app suing this commend npm i tinymce.
After the setup into my component I've imported the needed files in this way and I created a textarea into the template
<template>
<Navbar></Navbar>
<div class="container" id="editorWrapper">
<div class="row mt-5 pt-5 m-0">
<!-- -->
<div class="col-sm-12 col-md-12 col-lg-12 p-0" id="">
<h1>Write post</h1>
</div>
<!-- -->
<div class="col-sm-12 col-md-12 col-lg-12 p-0" id="programEditorWrapper">
<textarea ref="programEditor" id="programEditor"></textarea>
</div>
</div>
</div>
</template>
<script>
import tinymce from 'tinymce'
import 'tinymce/themes/silver'
import 'tinymce/skins/ui/oxide/skin.css'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/code'
import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/emoticons/js/emojis'
import 'tinymce/plugins/link'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/table'
import contentUiCss from 'tinymce/skins/ui/oxide/content.css'
import contentCss from 'tinymce/skins/content/default/content.css'
export default {
name: 'ProgramsEditor',
components: {
Navbar
},
data() {
return {
editor: null
}
},
created() {
},
mounted() {
this.initEditor()
},
methods: {
initEditor() {
this.editor = tinymce.init({
selector: '#programEditor',
plugins: 'advlist code emoticons link lists table',
toolbar: 'bold italic | bullist numlist | link emoticons',
skin: false,
content_css: false,
content_style: contentUiCss.toString() + '\n' + contentCss.toString()
})
},
}
}
</script>
Unfortunately I'm not able to see the editor and I get two errors
How I can fix this problem and let the editor work?
Based on the error message and the code snippet provided, you've missed importing some critical files that TinyMCE uses, as such its trying to load the external JS files which don't appear to exist or are being served as HTML files (potentially a 404 page).
I'd suggest reviewing the TinyMCE bundling docs further, which hold the answer here: https://www.tiny.cloud/docs/tinymce/6/introduction-to-bundling-tinymce/. So in this case, to solve the 2 errors you're getting that should be able to be resolved by importing the model and default icon pack alongside the theme:
import tinymce from 'tinymce'
import 'tinymce/icons/default';
import 'tinymce/themes/silver';
import 'tinymce/models/dom';
Basically, TinyMCE at the very minimum needs the core, theme, skin, default icon pack and model to be able to operate (Note: the model is new in TinyMCE 6 and wasn't needed before that). If any of those are missed then it will fail to initialize.
Additionally, you could use the official Vue integration guide.
After installation.
Simple input the core packages:
You need both the core tinymce package and tinymce-vue installed.
import "tinymce/tinymce";
import "tinymce/themes/silver";
import "tinymce/icons/default";
import "tinymce/skins/ui/oxide/skin.css";
import <Your component name here> from "#tinymce/tinymce-vue";
And use it as a normal Vue component.

Vue rendering elements before hiding them

I have a very simple site where I'm using the most basic Vue.
When I load the site, it renders divs which are not supposed to show, for a fraction of a second, before hiding them. It makes the site look very unprofessional.
Here is the site if you want to experience the glory of flashing divs: http://sqlforever.com/
Looking around on the web, I've tried to use v-show, v-if and even v-cloak. None of them work.
Here is an example where I'm using v-show, along with v-cloak:
<div class="row v-cloak" v-show="display_schema">
...
</div>
The definition of v-cloak:
[v-cloak] { display:none; }
Vue definition:
const app = new Vue({
el: '#app',
data: {
...
errorMsg: "",
warnMsg: "",
...
display_schema: false,
...
Else where I'm using v-if:
<div class="row">
<div id = "errorPanel" v-if="errorMsg != ''" class="alert alert-danger alert-dismissible fade show v-cloak" role="alert">
<strong>ERROR: </strong> {{errorMsg}}
<!--button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button-->
</div>
<div id = "warnPanel" v-if="warnMsg != ''" class="alert alert-warning alert-dismissible fade show v-cloak" role="alert">
<strong>WARNING: </strong> {{warnMsg}}
<!--button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button-->
</div>
</div>
Currently I'm also loading css and js directly off the web, I'll load them locally when this goes 'production' but simple experiments didn't seem to make a difference.
I'm not normally a web dev but I've done a site or two using knockoutjs and don't remember having this issue.
What am I doing wrong?
You're using v-cloak but incorrectly. From the docs:
This directive will remain on the element until the associated Vue instance finishes compilation. Combined with CSS rules such as [v-cloak] { display: none }, this directive can be used to hide un-compiled mustache bindings until the Vue instance is ready.
It's a directive, not a class name. Your CSS is correctly styling it as an attribute:
[v-cloak] { display:none; }
But you need to apply it in your template as a directive, not a class name. For example, this will remove the flash of content:
<div class="container" id="app" v-cloak>

Font Awesome icons not showing up using Bulma in Gatsby

I am building a website using Gatsby and Bulma. In my Nav.js file, where I create a general format for the buttons at the top of the website, I have a few buttons using Bulma to which I would like to add icons inside. I went off the documentation for adding Bulma buttons with Font Awesome Icons: https://bulma.io/documentation/elements/button/. My code is exactly the same, other that the fact that I have my buttons wrapped in an <a> tag to link to other pages in my website. I have the included <script> file listed in documentation to have Font Awesome Icons available, and my code looks as such:
const Nav = () => {
return (
<div style={{ margin: `3rem auto`, maxWidth: 650, padding: `0 1rem` }}>
<nav>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<p class="buttons is-outlined is-centered">
<a href="/"><button class="button is-outlined"> <span class="icon">
<i class="fas fa-home"></i>
</span>
<span>Home</span>
</button></a>
<button class="button is-outlined">Projects</button>
<button class="button is-outlined">Experience</button>
</p>
</nav>
</div>
)
}
I'm not sure if I have the script located in the correct part of the file, and I've only tried to put an icon for my Home button which looks like this:
The gap to the left of the "Home" is where I'm guessing the icon should be. I would appreciate any help as to why the icon is not showing up or is showing up blank. Thank you!
I ran into this issue myself so posting here for anyone that is looking for the answer. There are a few ways to make it work, including using the icons as components with a library such as react-fontawesome. However if you're using Bulma then chances are that you specifically don't want to do that, instead you want to use the class names.
So first install the package:
npm i #fortawesome/fontawesome-free
Then in your index.js / app.js / any styling wrapper component you have:
import '#fortawesome/fontawesome-free/css/all.min.css'
Here is a Typescript example I have in front of me. This is a wrapper component that imports all my global styles for nested child components to use:
import React from 'react';
import 'bulma/css/bulma.css';
import '#fortawesome/fontawesome-free/css/all.min.css';
import NavMenu from '../nav-menu';
import Footer from '../footer';
import './layout.css';
const Layout: React.FC<{ light: boolean }> = ({ light, children }) => {
return (
<div className="layout-wrapper">
<NavMenu light={light} />
{children}
<Footer light={light} />
</div>
);
};
export default Layout;
With the help of a friend, what solved the issue was putting the <script> tag in the public/index.html file of the project, and then making an exact copy and naming it index.html and putting it in the static folder in the project. This way, each time a Gatsby server is ran, it will create a copy of the index.html file in the public repository with the Font Awesome Icon script included.

Vue js error: Component template should contain exactly one root element

I don't know what the error is, so far I am testing through console log to check for changes after selecting a file (for uploading).
When I run $ npm run watch, i get the following error:
"Webpack is watching the files…
95% emitting
ERROR Failed to compile with 1 errors
19:42:29
error in ./resources/assets/js/components/File.vue
(Emitted value instead of an instance of Error) Vue template syntax
error:
Component template should contain exactly one root element. If you
are using v-if on multiple elements, use v-else-if to chain them
instead.
# ./resources/assets/js/components/AvatarUpload.vue 5:2-181 #
./resources/assets/js/app.js # multi ./resources/assets/js/app.js
./resources/assets/sass/app.scss"
My File.vue is
<template>
<div class="form-group">
<label for="avatar" class="control-label">Avatar</label>
<input type="file" v-on:change="fileChange" id="avatar">
<div class="help-block">
Help block here updated 4 🍸 ...
</div>
</div>
<div class="col-md-6">
<input type="hidden" name="avatar_id">
<img class="avatar" title="Current avatar">
</div>
</template>
<script>
export default{
methods: {
fileChange(){
console.log('Test of file input change')
}
}
}
</script>
Any ideas on how to solve this? What is actually the error?
Note This answer only applies to version 2.x of Vue. Version 3 has lifted this restriction.
You have two root elements in your template.
<div class="form-group">
...
</div>
<div class="col-md-6">
...
</div>
And you need one.
<div>
<div class="form-group">
...
</div>
<div class="col-md-6">
...
</div>
</div>
Essentially in Vue you must have only one root element in your templates.
For a more complete answer: http://www.compulsivecoders.com/tech/vuejs-component-template-should-contain-exactly-one-root-element/
But basically:
Currently, a VueJS template can contain only one root element (because of rendering issue)
In cases you really need to have two root elements because HTML structure does not allow you to create a wrapping parent element, you can use vue-fragment.
To install it:
npm install vue-fragment
To use it:
import Fragment from 'vue-fragment';
Vue.use(Fragment.Plugin);
// or
import { Plugin } from 'vue-fragment';
Vue.use(Plugin);
Then, in your component:
<template>
<fragment>
<tr class="hola">
...
</tr>
<tr class="hello">
...
</tr>
</fragment>
</template>
You need to wrap all the html into one single element.
<template>
<div>
<div class="form-group">
<label for="avatar" class="control-label">Avatar</label>
<input type="file" v-on:change="fileChange" id="avatar">
<div class="help-block">
Help block here updated 4 🍸 ...
</div>
</div>
<div class="col-md-6">
<input type="hidden" name="avatar_id">
<img class="avatar" title="Current avatar">
</div>
</div>
</template>
<script>
export default{
methods: {
fileChange(){
console.log('Test of file input change')
}
}
}
</script>
if, for any reasons, you don't want to add a wrapper (in my first case it was for <tr/> components), you can use a functionnal component.
Instead of having a single components/MyCompo.vue you will have few files in a components/MyCompo folder :
components/MyCompo/index.js
components/MyCompo/File.vue
components/MyCompo/Avatar.vue
With this structure, the way you call your component won't change.
components/MyCompo/index.js file content :
import File from './File';
import Avatar from './Avatar';
const commonSort=(a,b)=>b-a;
export default {
functional: true,
name: 'MyCompo',
props: [ 'someProp', 'plopProp' ],
render(createElement, context) {
return [
createElement( File, { props: Object.assign({light: true, sort: commonSort},context.props) } ),
createElement( Avatar, { props: Object.assign({light: false, sort: commonSort},context.props) } )
];
}
};
And if you have some function or data used in both templates, passed them as properties and that's it !
I let you imagine building list of components and so much features with this pattern.
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
The right approach is
<template>
<div> <!-- The root -->
<p></p>
<p></p>
</div>
</template>
The wrong approach
<template> <!-- No root Element -->
<p></p>
<p></p>
</template>
Multi Root Components
The way around to that problem is using functional components, they are components where you have to pass no reactive data means component will not be watching for any data changes as well as not updating it self when something in parent component changes.
As this is a work around it comes with a price, functional components don't have any life cycle hooks passed to it, they are instance less as well you cannot refer to this anymore and everything is passed with context.
Here is how you can create a simple functional component.
Vue.component('my-component', {
// you must set functional as true
functional: true,
// Props are optional
props: {
// ...
},
// To compensate for the lack of an instance,
// we are now provided a 2nd context argument.
render: function (createElement, context) {
// ...
}
})
Now that we have covered functional components in some detail lets cover how to create multi root components, for that I am gonna present you with a generic example.
<template>
<ul>
<NavBarRoutes :routes="persistentNavRoutes"/>
<NavBarRoutes v-if="loggedIn" :routes="loggedInNavRoutes" />
<NavBarRoutes v-else :routes="loggedOutNavRoutes" />
</ul>
</template>
Now if we take a look at NavBarRoutes template
<template>
<li
v-for="route in routes"
:key="route.name"
>
<router-link :to="route">
{{ route.title }}
</router-link>
</li>
</template>
We cant do some thing like this we will be violating single root component restriction
Solution
Make this component functional and use render
{
functional: true,
render(h, { props }) {
return props.routes.map(route =>
<li key={route.name}>
<router-link to={route}>
{route.title}
</router-link>
</li>
)
}
Here you have it you have created a multi root component, Happy coding
Reference for more details visit: https://blog.carbonteq.com/vuejs-create-multi-root-components/
In addition to Bert and blobmaster responses:
If you need to remove the root element from the DOM you can exploit css and use display: value on the root element.
Bit of a misleading error.
What fixed it on my side was the fact that I had an additional </div> without an opening <div>.
I spotted it using Find/Replace on "div" which gave an odd number.
Wrap everything in one div and it will resolve the issue.
For example,
div
----div
----/div>
----div>
----/div>
/div
It is similar concept to React.js
For vue 3 they removed this constraint in template syntax :
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
but it's still existing in JSX syntax :
Incorrect ❌
setup(props,{attrs}) {
return ()=>(
<header>...</header>
<main {..attrs}>...</main>
<footer>...</footer>
)
}
Correct ✔
setup(props,{attrs}) {
return ()=>(
<>
<header>...</header>
<main {..attrs}>...</main>
<footer>...</footer>
</>
)
}
I experienced this kind of issue and the issue was fixed by adding a main parent div tag or section if it is a section type of component.
<div class="list-of-friends">
<h3>Hello World</h3>
</div>
I was confused as I knew VueJS should only contain 1 root element and yet I was still getting this same "template syntax error Component template should contain exactly one root element..." error on an extremely simple component. Turns out I had just mispelled </template> as </tempate> and that was giving me this same error in a few files I copied and pasted. In summary, check your syntax for any mispellings in your component.
instead of using this
Vue.component('tabs', {
template: `
<div class="tabs">
<ul>
<li class="is-active"><a>Pictures</a></li>
<li><a>Music</a></li>
<li><a>Videos</a></li>
<li><a>Documents</a></li>
</ul>
</div>
<div class="tabs-content">
<slot></slot>
</div>
`,
});
you should use
Vue.component('tabs', {
template: `
<div>
<div class="tabs">
<ul>
<li class="is-active"><a>Pictures</a></li>
<li><a>Music</a></li>
<li><a>Videos</a></li>
<li><a>Documents</a></li>
</ul>
</div>
<div class="tabs-content">
<slot></slot>
</div>
</div>
`,
});
Just make sure that you have one root div and put everything inside this root
<div class="root">
<!--and put all child here --!>
<div class='child1'></div>
<div class='child2'></div>
</div>
and so on

Categories

Resources