Creating a vue.js component [duplicate] - vue.js

This question already has answers here:
Creating a simple VUE.JS application
(2 answers)
Closed 2 years ago.
I have created a vue.js application by
vue init webpack myproject
I am then following a link, to create simple copper component, I have put that under src/components directory as follow:
<template>
<div id="app">
<polygon-crop :imageSource = "'src/assets/logo.png'"
ref="canvas"> </polygon-crop>
<button #click.prevent="crop"> Crop </button>
<button #click.prevent="undo"> Undo </button>
<button #click.prevent="redo"> Redo </button>
<button #click.prevent="reset"> Reset </button>
</div>
</template>
<script>
import Vue from 'vue';
import VuePolygonCropper from 'vue-polygon-cropper';
Vue.component(VuePolygonCropper);
export
default
{
name: 'App',
methods: {
crop: function() {
this.$refs.canvas.crop();
},
undo: function()
{
this.$refs.canvas.undo();
},
redo: function()
{
this.$refs.canvas.redo();
},
reset: function()
{
this.$refs.canvas.reset();
}
}
};
</script>
But actually, it doesn't render properly and my component doesn't show up properly. I am new to vue.js and any help would be appreciated!

You won't be able to get this component running with just this code snippet, there's a couple of things that you would need to do to fix this up.
Before we go any deeper, I would like you to make sure if you have installed this vue-polygon-cropper component. If you navigated to the package.json that is located in the same level as your "src" folder, you would see a mention of vue-polygon-cropper there, if not please install it by npm install vue-polygon-croper .
Let's take a look at your <template> section first:
1- In the template, you call a component <polygon-crop> but, there is no component registered by that name in your script (What you are attempting to register is 'VuePolygonCropper' so you should try using <VuePolygonCropper> component instead.
2-I see there you copied and pasted the logo image in assets, that's a great way to test it! However, Digging through the creator's example that they put up on github, It seems like this component requires a full path to your image file instead of the relative path. so instead of /src/assets/logo.png try doing :imageSource="require('../assets/logo.png')"
I'm assuming the assets logo is on a folder that is one level above your current component.
So your template should look like this:
<template>
<div id="app">
<VuePolygonCropper :imageSource = "require('../assets/logo.png')"
ref="canvas"> </VuePolygonCropper>
<button #click.prevent="crop"> Crop </button>
<button #click.prevent="undo"> Undo </button>
<button #click.prevent="redo"> Redo </button>
<button #click.prevent="reset"> Reset </button>
</div>
</template>
Now on to your script!
just import the VuePolygonCropper and mention it as a component in the components section.
You don't need to import vue and do Vue.component(VuePolygonCropper). The correct way to register this component would be like this
<script>
import VuePolygonCropper from 'vue-polygon-cropper';
export
default
{
name: 'App',
components:{VuePolygonCropper},
methods: {
crop: function() {
this.$refs.canvas.crop();
},
undo: function()
{
this.$refs.canvas.undo();
},
redo: function()
{
this.$refs.canvas.redo();
},
reset: function()
{
this.$refs.canvas.reset();
}
}
};
</script>
For the heck of it, I have created a codesandbox that you can play around with . You can try to play around with the App.vue file and see how it was created.
Happy coding!

Related

HowTo: Toggle dark mode with TailwindCSS + Vue3 + Vite

I'm a beginner regarding Vite/Vue3 and currently I am facing an issue where I need the combined knowledge of the community.
I've created a Vite/Vue3 app and installed TailwindCSS to it:
npm create vite#latest my-vite-vue-app -- --template vue
cd my-vite-vue-app
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Then I followed the instructions on Tailwind's homepage:
Add the paths to all of your template files in your tailwind.config.js file.
Import the newly-created ./src/index.css file in your ./src/main.js file. Create a ./src/index.css file and add the #tailwind directives for each of Tailwind’s layers.
Now I have a working Vite/Vue3/TailwindCSS app and want to add the feature to toggle dark mode to it.
The Tailwind documentation says this can be archived by adding darkMode: 'class' to tailwind.config.js and then toggle the class dark for the <html> tag.
I made this work by using this code:
Inside index.html
<html lang="en" id="html-root">
(...)
<body class="antialiased text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-900">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Inside About.vue
<template>
<div>
<h1>This is an about page</h1>
<button #click="toggleDarkMode">Toggle</botton>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
const element = document.getElementById('html-root')
if (element.classList.contains('dark')) {
element.classList.remove('dark')
} else {
element.classList.add('dark')
}
},
},
};
</script>
Yes, I know that this isn't Vue3-style code. And, yes, I know that one could do element.classList.toggle() instead of .remove() and .add(). But maybe some other beginners like me will look at this in the future and will be grateful for some low-sophisticated code to start with. So please have mercy...
Now I'll finally come to the question I want to ask the community:
I know that manipulating the DOM like this is not the Vue-way of doing things. And, of course, I want to archive my goal the correct way. But how do I do this?
Believe me I googled quite a few hours but I didn't find a solution that's working without installing this and this and this additional npm module.
But I want to have a minimalist approach. As few dependancies as possbile in order not to overwhelm me and others that want to start learning.
Having that as a background - do you guys and gals have a solution for me and other newbies? :-)
The target element of your event is outside of your application. This means there is no other way to interact with it other than by querying it via the DOM available methods.
In other words, you're doing it right.
If the element was within the application, than you'd simply link class to your property and let Vue handle the specifics of DOM manipulation:
:class="{ dark: darkMode }"
But it's not.
As a side note, it is really important your toggle method doesn't rely on whether the <body> element has the class or not, in order to decide if it should be applied/removed. You should keep the value saved in your app's state and that should be your only source of truth.
That's the Vue principle you don't want break: let data drive the DOM state, not the other way around.
It's ok to get the value (on mount) from current state of <body>, but from that point on, changes to your app's state will determine whether or not the class is present on the element.
vue2 example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
darkMode: document.body.classList.contains('dark')
}),
methods: {
applyDarkMode() {
document.body.classList[
this.darkMode ? 'add' : 'remove'
]('dark')
}
},
watch: {
darkMode: 'applyDarkMode'
}
})
body.dark {
background-color: #191919;
color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>
vue3 example:
const {
createApp,
ref,
watchEffect
} = Vue;
createApp({
setup() {
const darkMode = ref(document.body.classList.contains('dark'));
const applyDarkMode = () => document.body.classList[
darkMode.value ? 'add' : 'remove'
]('dark');
watchEffect(applyDarkMode);
return { darkMode };
}
}).mount('#app')
body.dark {
background-color: #191919;
color: white;
}
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app">
<label>
<input type="checkbox" v-model="darkMode">
dark mode
</label>
</div>
Obviously, you might want to keep the state of darkMode in some external store, not locally, in data (and provide it in your component via computed), if you use it in more than one component.
What you're looking for is Binding Classes, but where you're getting stuck is trying to manipulate the <body> which is outside of the <div> your main Vue instance is mounted in.
Now your problem is your button is probably in a different file to your root <div id="app"> which starts in your App.vue from boilerplate code. Your two solutions are looking into state management (better for scalability), or doing some simple variable passing between parents and children. I'll show the latter:
Start with your switch component:
// DarkButton.vue
<template>
<div>
<h1>This is an about page</h1>
<button #click="toggleDarkMode">Toggle</button>
</div>
</template>
<script>
export default {
methods: {
toggleDarkMode() {
this.$emit('dark-switch');
},
},
};
</script>
This uses component events ($emit)
Then your parent/root App.vue will listen to that toggle event and update its class in a Vue way:
<template>
<div id="app" :class="{ dark: darkmode }">
<p>Darkmode: {{ darkmode }}</p>
<DarkButton #dark-switch="onDarkSwitch" />
</div>
</template>
<script>
import DarkButton from './components/DarkButton.vue';
export default {
name: 'App',
components: {
DarkButton,
},
data: () => ({
darkmode: false,
}),
methods: {
onDarkSwitch() {
this.darkmode = !this.darkmode;
},
},
};
</script>
While tailwind say for Vanilla JS to add it into your <body>, you generally shouldn't manipulate that from that point on. Instead, don't manipulate your <body>, only go as high as your <div id="app"> with things you want to be within reach of Vue.

How can i just have one Component instead of different components in the context of Vuejs

I am new to Vuejs, I am using 4 different components, where in every component i have called the API. Here 4 different components are all same except their contents.
Now, i want to make my code effective, so i want to have a single component, the reason why i created different components is my another App.vue component has 4 different buttons, so whenever you click any of it, it will open the respective component.
But now i want to have only one component instead of four different components, and whenever the buttons in App.vue component is clicked it should open the exact content in single component(instead of 4 components).
Please do help me with this, by sharing your ideas and if any examples.
In this context, you can use props, which is a way of passing data to "child" components.
https://v2.vuejs.org/v2/guide/components-props.html
Example
App.vue
<template>
<div id="app">
<SingleComponent :button="button" />
</div>
</template>
<script>
import SingleComponent from "#/components/SingleComponent.vue";
// #/ means src/
export default {
name: "App",
components: {
SingleComponent,
},
data: () => ({
button: 2,
}),
};
</script>
SingleComponent.vue
<template>
<div>
<button v-if="button === 0">...</button>
<button v-else-if="button === 1">...</button>
<button v-else-if="button === 2">...</button>
<button v-else-if="button === 3">...</button>
</div>
</template>
<script>
export default {
name: "SingleComponent",
props: {
button: {
type: Number,
required: true,
},
},
};
</script>
You should also take a look at slots, it is very important in Vue.js and that could also solve your problem.
https://v2.vuejs.org/v2/guide/components-slots.html

Creating a simple VUE.JS application

I am trying to use a simple polygon cropper from Vue within an application by following the steps in this article.
I created my app using:
vue init webpack myproject
Now, I need to add the sample template to my app (it has a src folder), but I am not sure how to amend or add this piece of code to my application. The template should be as follows per the linked article:
// Global
import Vue from 'vue';
import VuePolygonCropper from 'vue-polygon-cropper';
Vue.component(VuePolygonCropper);
// Local
import VueCropper from 'vue-polygon-cropper';
export default {
components: { VueCropper}
}
<template>
<div id="app">
<polygon-crop :imageSource="'/demo.png'" ref="canvas"></polygon-crop>
<button #click.prevent="crop">Crop</button>
<button #click.prevent="undo">Undo</button>
<button #click.prevent="redo">Redo</button>
<button #click.prevent="reset">Reset</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
crop: function () {
this.$refs.canvas.crop();
},
undo: function () {
this.$refs.canvas.undo();
},
redo: function () {
this.$refs.canvas.redo();
},
reset: function () {
this.$refs.canvas.reset();
}
}
};
</script>
I am not sure what the meaning of global or local is there. My src folder structure is as follows:
Directory of C:\ThermoAnalyser\vue_js\myproject\src
27/12/2020 11:37 AM <DIR> .
27/12/2020 11:37 AM <DIR> ..
27/12/2020 11:37 AM 374 App.vue
27/12/2020 11:37 AM <DIR> assets
27/12/2020 11:37 AM <DIR> components
27/12/2020 11:37 AM 360 main.js
27/12/2020 11:37 AM <DIR> router
2 File(s) 734 bytes
5 Dir(s) 301,183,393,792 bytes free
You won't be able to get this component running with just this code snippet, there's a couple of things that you would need to do to fix this up.
Before we go any deeper, I would like you to make sure if you have installed this vue-polygon-cropper component. If you navigated to the package.json that is located in the same level as your "src" folder, you would see a mention of vue-polygon-cropper there, if not please install it by npm install vue-polygon-croper .
Let's take a look at your <template> section first:
1- In the template, you call a component <polygon-crop> but, there is no component registered by that name in your script (What you are attempting to register is 'VuePolygonCropper' so you should try using <VuePolygonCropper> component instead.
2-I see there you copied and pasted the logo image in assets, that's a great way to test it! However, Digging through the creator's example that they put up on github, It seems like this component requires a full path to your image file instead of the relative path. so instead of /src/assets/logo.png try doing :imageSource="require('../assets/logo.png')"
I'm assuming the assets logo is on a folder that is one level above your current component.
So your template should look like this:
<template>
<div id="app">
<VuePolygonCropper :imageSource = "require('../assets/logo.png')"
ref="canvas"> </VuePolygonCropper>
<button #click.prevent="crop"> Crop </button>
<button #click.prevent="undo"> Undo </button>
<button #click.prevent="redo"> Redo </button>
<button #click.prevent="reset"> Reset </button>
</div>
</template>
Now on to your script!
just import the VuePolygonCropper and mention it as a component in the components section.
You don't need to import vue and do Vue.component(VuePolygonCropper). The correct way to register this component would be like this
<script>
import VuePolygonCropper from 'vue-polygon-cropper';
export
default
{
name: 'App',
components:{VuePolygonCropper},
methods: {
crop: function() {
this.$refs.canvas.crop();
},
undo: function()
{
this.$refs.canvas.undo();
},
redo: function()
{
this.$refs.canvas.redo();
},
reset: function()
{
this.$refs.canvas.reset();
}
}
};
</script>
For the heck of it, I have created a codesandbox that you can play around with . You can try to play around with the App.vue file and see how it was created.
Happy coding!
"Global" vs "Local"
The "global" and "local" comments refer to global component registration and local component registration. The article shows both ways of registering the vue-polygon-cropper component probably to make it easier to copy-paste into your own code.
Global component registration
You can register a component globally so that it could be used in another component without the consuming component having to register it locally. This is normally used for commonly used components that are frequently found in several components (e.g., a button).
Below is an example of global component registration that allows MyButton to be used in MyForm. Notice how MyForm's <template> uses MyButton without any component registration for MyButton in its <script>.
// main.js
import Vue from 'vue'
Vue.component('MyButton', { /*...*/ })
// MyForm.vue
<template>
<MyButton #click="onClick" />
</template>
<script>
export default {
methods: {
onClick() { /*...*/ }
}
}
</script>
Local component registration
For seldom used components (ones that are only found in a few components of your app), register the components locally instead to help minimize the bundle size if needed. If your component is never used (e.g., from a refactoring down the road), local registration allows the bundler to crop out your component from the final output.
Here's the previous example with local registration instead:
// MyButton.vue
<template>
<button />
</template>
// MyForm.vue
<template>
<MyButton #click="onClick" />
</template>
<script>
import MyButton from './MyButton.vue'
export default {
components: {
MyButton 👈
},
methods: {
onClick() { /*...*/ }
}
}
</script>
Getting started with the sample code
To quickly get the sample code working in your project:
Copy the <template> and <script> parts of the sample code into your src/App.vue, replacing everything.
In the App.vue's component definition, locally register vue-polygon-cropper as polygon-crop:
<script>
import VuePolygonCropper from 'vue-polygon-cropper'
export default {
components: {
'polygon-crop': VuePolygonCropper
}
}
<script>
The sample code refers to an image at /demo.png, but your sample app only has src/assets/logo.png, so edit src/App.vue's <template> so that polygon-crop uses src/assets/logo.png. We have to require the asset's path so that Webpack properly resolves the path from source:
<polygon-crop :imageSource="require('#/assets/logo.png')">
sample GitHub repo
Update to Vue CLI
Consider using Vue CLI's default generated templates (from vue create) instead of that outdated webpack template. The newly created project would still use Webpack, but most of the config for developer ergonomics are abstracted away, which can be helpful for beginners.
vue create myproject

Vue js loading js file in mounted() hook

I have the following Vue component:
<template>
<div id="wrapper">
<div class="main-container">
<Header />
<router-view/>
<Footer/>
</div>
</div>
</template>
<script>
import './assets/js/popper.min.js';
// other imports
// ....
export default {
name: 'App',
components : {
Header,
Footer
},
mounted(){
// this is syntax error
import './assets/js/otherjsfile.js'
}
}
</script>
As is clear from the code snippet, I want to have the otherjsfile.js loaded in mounted() hook. That script file has certain IIFEs which expects the html of the web page to be fully loaded.
So how do I invoke that js file in a lifecycle hook?
This is the pattern I use. The example is importing a js file which contains an IIFY, which instantiates an object on window.
The only problem with this would occur if you want to use SSR, in which case you need Vue's <ClientOnly> component, see Browser API Access Restrictions
mounted() {
import('../public/myLibrary.js').then(m => {
// use my library here or call a method that uses it
});
},
Note it also works with npm installed libraries, with the same path conventions i.e non-relative path indicates the library is under node_modules.
I'm a little unsure of what your asking. But if you are just trying to include an external js file in your page, you can just use the script tag in your template and not have to put anything in your mounted function, like this:
<template>
<div id="wrapper">
<div class="main-container">
<Header />
<router-view/>
<Footer/>
</div>
<script src="./assets/js/otherjsfile.js"></script>
</div>
</template>
<script>
import './assets/js/popper.min.js';
// other imports
// ....
export default {
name: 'App',
components : {
Header,
Footer
},
}
</script>
Does this solve your issue?

How to use Onsen UI tabbar with Vue single file components

I'm using Vue Onsen UI and trying to render a Vue single file component for each tab.
In the documentation here, they make use of template in a single page. Which is not very reusable. I want to be able to import custom component and render that.
Here is something that I'm trying to do which doesn't seem to work.
<template lang="html">
<v-ons-page>
<!-- top tab bar -->
<v-ons-tabbar position="top" :index="0">
<v-ons-tab label="Browse" page="TaskList">
</v-ons-tab>
<v-ons-tab label="Second">
</v-ons-tab>
</v-ons-tabbar>
</v-ons-page>
</template>
<script>
import TaskList from './TaskList';
export default {
template: '#main',
components: {
'task-list': TaskList,
},
};
</script>
<style lang="scss">
</style>
Can you suggest anything that I should try?
Instead of using tab objects that reference the components directly, use the :tabs property of the tabbar to set up the pages:
<template lang="html">
<v-ons-page>
<v-ons-tabbar position="top" :index="0" :tabs="tabs">
</v-ons-tabbar>
</v-ons-page>
</template>
<script>
import TaskList from './TaskList';
import SecondPage from './SecondPage';
export default {
template: '#main',
data: function () {
return {
tabs: [
{label: 'Browse', page: TaskList},
{label: 'Second', page: SecondPage}
]
}
}
};
</script>
Also, make sure the root element of the components you reference in the page property are <v-ons-page> elements.
I was having the same difficulty with the following syptoms:
Tabs were not appearing at all
No errors in CLI or in console
Note that I was also using the "Hello World" app that is generated from the CLI (vue init OnsenUI/vue-pwa-webpack hello-world)
Resolution
It was pretty simple in the end: there is a file in the root of the folder called vue-onsen-components.js which has all of the components and some of them are commented out. I had to uncomment the following lines and then the tabs appeared:
export { default as VOnsTab } from 'vue-onsenui/esm/components/VOnsTab'
export { default as VOnsTabbar } from 'vue-onsenui/esm/components/VOnsTabbar'