Laravel 6.4.1 + webpack + vue + scss dynamically loaded - vue.js

I have laravel 6.4.1 with minor changes to the default webpack config.
I'm using vue components with scoped styling as well.
When I run npm run dev, everything works as it should. My Vue component is loaded and has styling.
When I run npm run production, my Vue component is not loaded.
Or well... The JS file is loaded, but the component never fires created or mounted and is not visible on screen and not visible in the DOM.
How do I know it's loaded then?
When I put console.log('test') above (or below) the export default it is displayed in the console.
When I remove the <style scoped lang="scss"> tag completely, my component is visible on screen as well.
I've already tried deleting parts of the styling, but it never works. Even an empty style tag will not render the component. It will only work when I fully remove it.
Ofcourse, I want to keep my styling in the component, so how can I fix this problem?
I've removed some JS from the Vue component to make it more readable and since I strongly suspect the issue in not in the JS I don't think it has any value for this issue.
webpack.mix.js
const mix = require('laravel-mix');
/*
|--------------------------------------------------------------------------
| Mix Asset Management
|--------------------------------------------------------------------------
|
| Mix provides a clean, fluent API for defining some Webpack build steps
| for your Laravel application. By default, we are compiling the Sass
| file for the application as well as bundling up all the JS files.
|
*/
mix
.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
if (mix.inProduction()) {
mix.version();
} else {
mix.sourceMaps();
}
// webpack.mix.js
const path = require('path'),
WebpackShellPlugin = require('webpack-shell-plugin'),
BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin,
{CleanWebpackPlugin} = require('clean-webpack-plugin');
mix.webpackConfig({
plugins: [
new WebpackShellPlugin({
onBuildStart: [
'php artisan js-localization:export --quiet',
'php artisan ziggy:generate resources/js/ziggy-routes.js --quiet'
]
}),
new BundleAnalyzerPlugin({
analyzerMode: mix.inProduction() ? 'disabled' : 'server',
openAnalyzer: false
}),
new CleanWebpackPlugin({
dry: false,
cleanStaleWebpackAssets: true,
cleanOnceBeforeBuildPatterns: [],
cleanAfterEveryBuildPatterns: [
'js/chunk/*'
],
dangerouslyAllowCleanPatternsOutsideProject: true,
}),
],
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/dist/js/route.js'),
},
},
output: {
publicPath: '/',
chunkFilename: 'js/chunk/[name].[chunkhash].js',
},
});
Vue component
<template>
<div class="position-relative my-2" :style="{backgroundColor: properties.settings.backgroundColor}">
<block-template-content-text
v-if="canDisplayBlock(block, 'content', 'text')"
:text-config="tinyMce.text"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text>
<block-template-content-text-image
v-if="canDisplayBlock(block, 'content', 'text-image')"
:text-config="tinyMce.text"
:image-config="tinyMce.image"
:block="block"
#block-change="storeBlockChange">
</block-template-content-text-image>
<div class="d-flex align-items-center justify-content-center flex-column position-absolute controls">
<i class="fas fa-2x fa-fw fa-chevron-up cursor-pointer" #click="$emit('sort-item', 'up', block)"></i>
<div>
<i class="fas fa-1x fa-fw fa-plus-circle cursor-pointer" #click="$emit('add-new-block', block)"></i>
<!-- delete section start -->
<i class="fas fa-1x fa-fw fa-trash cursor-pointer"
:id="'delete-' + block.hash"
#click="$emit('delete-block', $event, block)"></i>
<b-tooltip :target="'delete-' + block.hash"
:id="'tooltip-' + block.hash"
triggers="focus">
<button type="button"
class="btn btn-primary"
#click="$emit('cancel-delete-block', block)">
{{ Lang.get('general.buttons.cancel') }}
</button>
<button type="button"
class="btn btn-danger"
#click="$emit('delete-block', $event, block)">
<i class="fas fa-fw fa-trash"></i>
{{ Lang.get('general.buttons.delete')}}
</button>
</b-tooltip>
<!-- delete section end -->
<!-- popover start -->
<div class="popover-content" :hidden="!showPopover">
<i class="fas fa-fw fa-times close-popover cursor-pointer" #click="closePopover"></i>
<div class="form-group">
<label for="background-color">
{{ Lang.get('project.design.pages.block.settings.background-color') }}
</label>
<input type="color"
name="background-color"
id="background-color"
class="form-control"
v-model="properties.settings.backgroundColor"
value="#ffffff"
#input="setSetting('backgroundColor', $event)">
</div>
</div>
<i class="fas fa-1x fa-fw fa-cog cursor-pointer"
#click="togglePopover"></i>
<!-- popover end -->
</div>
<i class="fas fa-2x fa-fw fa-chevron-down sort-order cursor-pointer"
#click="$emit('sort-item', 'down', block)"></i>
</div>
</div>
</template>
<script>
// Bootstrap Vue
import {TooltipPlugin} from 'bootstrap-vue';
Vue.use(TooltipPlugin);
// TinyMCE editor
import 'tinymce/tinymce.min';
import 'tinymce/themes/silver/theme.min';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/link';
import 'tinymce/plugins/imagetools';
import {EventBus} from "../../vue/EventBus";
export default {
name: "display-block",
components: {
'block-template-content-text': () => import('./blocks/content/Text'),
'block-template-content-text-image': () => import('./blocks/content/TextImage'),
},
props: {
block: {
required: true,
type: Object
}
},
data() {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
}
</script>
<style scoped lang="scss">
#import "../../../sass/variables";
#import "~bootstrap/scss/mixins";
#import "~bootstrap/scss/bootstrap-grid";
#import "~bootstrap/scss/utilities/position";
#import "~bootstrap/scss/popover";
// Bootstrap Vue
#import '~bootstrap-vue/src/index.scss';
.block {
.block- {
&text {
#import '~tinymce/skins/ui/oxide/skin.min.css';
#import '~tinymce/skins/ui/oxide/content.min.css';
#import '~tinymce/skins/content/default/content.min.css';
}
}
}
.controls {
#extend .position-relative;
top: map_get($sizes, 50);
right: - map_get($spacers, 5);
transform: translateY(-50%);
&:first-child {
#extend .d-none;
}
.popover-content {
#extend .popover;
#extend .p-2;
min-width: 150px;
min-height: 150px;
.close-popover {
#extend .position-absolute;
#extend .mt-2;
#extend .mr-2;
top: 0;
right: 0;
}
input {
&[type=color] {
#extend .p-0;
width: 25px;
height: 25px;
border: none;
}
}
}
}
</style>
page.js
import {EventBus} from "../../vue/EventBus";
import {TooltipPlugin} from 'bootstrap-vue';
// import DisplayBlock from "../../components/project/DisplayBlock";
if (document.getElementById('page-editor')) {
// Bootstrap Vue
Vue.use(TooltipPlugin);
new Vue({
el: '#page-editor',
components: {
BlockModal: () => import('../../components/project/BlockModal'),
DisplayBlock: () => import('../../components/project/DisplayBlock'),
// DisplayBlock
},
data: {
[...]
},
computed: {
[...]
},
watch: {
[...]
},
methods: {
[...]
},
mounted() {
[...]
}
});
}

In your webpack file the resolver is missing for VUE.
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js'
}
}
Try by adding the above snippet.

Related

Add Close Button to Vue with Popper.js Modal Component

UPDATED Here is a codesandbox recreating the issue - Modal Does not hide: https://codesandbox.io/s/vue3-popperjs-modal-64762?file=/src/components/Modal.vue
I am new to Vue and cannot figure out how to Close the modal from the Hide Modal button within the component. I am using Vue.js and Popper.js for a modal/dropdown component.
I have tried emitting, rels and passing a prop but cannot get it to function correctly. I stripped it down to the shell code below.
** Main Component **
<template>
<div>
<Head title="Main" />
<div class="flex items-center">
<div class="flex">
<dropdown :auto-close="false" class="focus:z-10 px-4 hover:bg-gray-100 focus:border-white rounded-l focus:ring md:px-6" placement="auto">
<template #default>
<div class="flex">
<button id="open" class="btn-indigo" type="button">Open Modal</button>
</div>
</template>
<template #dropdown>
<div class="mt-2 px-4 py-6 w-screen bg-white rounded shadow-xl" style="maxWidth: 600px">
<h1>Modal Content</h1>
<button id="close" class="btn-indigo" type="button">Hide Modal</button>
</div>
</template>
</dropdown>
</div>
</div>
</div>
</template>
<script>
import { Head } from '#inertiajs/inertia-vue3'
import Dropdown from '#/Shared/Dropdown'
export default {
components: {
Head,
Dropdown,
},
}
</script>
** And Dropdown Component **
<template>
<button type="button" #click="show = true">
<slot />
<teleport v-if="show" to="#dropdown">
<div>
<div style="position: fixed; top: 0; right: 0; left: 0; bottom: 0; z-index: 99998; background: black; opacity: 0.2" #click="show = false" />
<div ref="dropdown" style="position: absolute; z-index: 99999" #click.stop="show = !autoClose">
<slot name="dropdown" />
</div>
</div>
</teleport>
</button>
</template>
<script>
import { createPopper } from '#popperjs/core'
export default {
props: {
placement: {
type: String,
default: 'bottom-end',
},
autoClose: {
type: Boolean,
default: true,
},
},
data() {
return {
show: false,
}
},
watch: {
show(show) {
if (show) {
this.$nextTick(() => {
this.popper = createPopper(this.$el, this.$refs.dropdown, {
placement: this.placement,
modifiers: [
{
name: 'preventOverflow',
options: {
altBoundary: true,
},
},
],
})
})
} else if (this.popper) {
setTimeout(() => this.popper.destroy(), 100)
}
},
},
mounted() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.show = false
}
})
},
}
</script>

Nuxt - Using router.push inside a component not changing pages correctly

index.vue -
<template>
<div>
<Header />
<div class="container">
<SearchForm />
</div>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
data() {
return {
form: {
email: '',
password: ''
},
show: true
}
},
methods: {
logout() {
// Code will also be required to invalidate the JWT Cookie on external API
Cookie.remove('auth')
this.$store.commit('setAuth', {
auth: null,
user_type: null
})
}
}
}
</script>
<style>
.container {
/* margin: 0 auto; */
/* min-height: 100vh; */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
jobs.vue -
<template>
<div>
<Header />
<SearchForm />
<b-container class="main_container">
<b-row>
<h1> Results for "{{q}}"</h1>
</b-row>
<b-row>
<ul id="array-rendering">
<li v-for="item in results" :key="item.job_id">
{{ item.job_title }}
{{ item.job_city }}
{{ item.job_state }}
{{ item.job_work_remote }}
</li>
</ul>
</b-row>
</b-container>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
// middleware: 'notAuthenticated',
watchQuery: ['q'],
data() {
return {
q: null,
results: []
}
},
async fetch() {
this.q = this.$route.query.q
this.results = await this.$axios.$get('/api/job/search', {
params: {
keyword: this.q,
}
})
},
methods: {
}
}
</script>
<style>
.container {
align-items: center;
text-align: center;
}
</style>
SearchForm.vue component -
<template>
<div id='searchFormDiv'>
<b-form inline #submit.prevent="onSubmit">
<label class="sr-only" for="inline-form-input-name"> keyword</label>
<b-form-input v-model="form.keyword" id="inline-form-input-name" class="mb-2 mr-sm-2 mb-sm-0" placeholder="Job title or keyword" size="lg"></b-form-input>
<label class="sr-only" for="inline-form-input-username">location</label>
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
<b-form-input v-model="form.location" id="inline-form-input-username" size="lg" placeholder="City, state or zip"></b-form-input>
</b-input-group>
<b-button type="submit" variant="primary">Find Jobs</b-button>
</b-form>
</div>
</template>
<script>
import {
BIconSearch,
BIconGeoAlt
} from 'bootstrap-vue'
export default {
data() {
return {
form: {
keyword: '',
location: ''
}
}
},
created () {
this.form.keyword = this.$route.query.q
},
methods: {
onSubmit() {
this.$router.push({
path: 'jobs',
query: {
q: this.form.keyword
}
});
}
},
components: {
BIconSearch,
BIconGeoAlt
},
}
</script>
<style>
#searchFormDiv {
margin-top: 50px
}
</style>
The route for "http://localhost:3000/" returns the index.vue page.
In this vue page, I have a component with a search form. Once you complete these form and hit the seach button, it will re-direct to a results page
if this.form.keyword = "Data", the next URL will be "http://localhost:3000/jobs?q=Data" and it will be using the jobs.vue page.
The issue I'm running into is the CSS is not being loaded from the jobs.vue page. It's still coming from the index.vue page for some reason. If I refresh the page, then the CSS from jobs.vue is loading. I need the CSS to load from jobs.vue on the initial redirect. All of the query data is working as expected so thats a plus.
However, the following CSS is being applied from index.vue for some reason instead of the CSS from the jobs.vue page -
display: flex;
justify-content: center;
Does anyone know whats going on here? This app is SSR and not SPA.
You have to scope your css from the index.vue page to the other pages with the scoped directive (see docs https://vue-loader.vuejs.org/guide/scoped-css.html)
<style scoped>
/* local styles */
</style>
<style>
/* global styles */
</style>
You can add your global CSS in your layouts/default.vue file.
This solved the issue -
methods: {
onSubmit() {
window.location = 'http://localhost:3000/jobs?q=' + this.form.keyword;
}
},

Use more than one directive to add data attributes to components

I have two directives which are supposed to add data attributes to components for testing, however, only one of the directives actually gets added. The two components are Bootstrap-Vue's BFormInput and BButton.
I tried removing everything but one of the buttons and the directive is still not added i.e
<b-input-group class="sm-2 mb-2 mt-2">
<b-button
variant="primary"
#click="searchJobs"
class="rounded-0"
v-jobs-search-button-directive="{ id: 'search-button' }"
>
Search
</b-button>
</b-input-group>
wrapper.html() output is:
<b-input-group-stub tag="div" class="sm-2 mb-2 mt-2"><b-button-stub target="_self" event="click" routertag="a" variant="secondary" type="button" tag="button" class="rounded-0">
Search
</b-button-stub></b-input-group-stub>
However, it is added when instead of a button I leave in place the input form i.e.
<b-input-group class="sm-2 mb-2 mt-2">
<b-form-input
v-jobs-search-input-directive="{ id: 'input-keyword' }"
class="mr-2 rounded-0"
placeholder="Enter Search term..."
:value="this.searchConfig.Keyword"
#input="this.updateJobsSearchConfig"
/>
</b-input-group>
wrapper.html() output is:
<b-input-group-stub tag="div" class="sm-2 mb-2 mt-2"><b-form-input-stub value="" placeholder="Enter Search term..." type="text" class="mr-2 rounded-0" data-jobs-search-input-id="input-keyword"></b-form-input>
This is how I add the directives
<template>
<b-input-group class="sm-2 mb-2 mt-2">
<b-form-input
v-jobs-search-input-directive="{ id: 'input-keyword' }"
class="mr-2 rounded-0"
placeholder="Enter Search term..."
:value="this.searchConfig.Keyword"
#input="this.updateJobsSearchConfig"
/>
<b-button
variant="primary"
#click="searchJobs"
class="rounded-0"
v-jobs-search-button-directive="{ id: 'search-button' }"
>
Search
</b-button>
</b-input-group>
</template>
<script>
import { mapActions, mapState } from 'vuex'
import JobService from '#/api-services/job.service'
import JobsSearchInputDirective from '#/directives/components/jobs/JobsSearchInputDirective'
import JobsSearchButtonDirective from '#/directives/components/jobs/JobsSearchButtonDirective'
export default {
name: 'jobs-search',
directives: { JobsSearchInputDirective, JobsSearchButtonDirective },
data () {
return {
jobs: [],
pages: 0
}
},
computed: {
...mapState({
pagedConfig: state => state.jobs.paged,
searchConfig: state => state.jobs.search
})
},
methods: {
// Methods go here
}
}
jobs-search-input-directive is
export default (el, binding) => {
if (process.env.NODE_ENV === 'test') {
Object.keys(binding.value).forEach(value => {
el.setAttribute(`data-jobs-search-input-${value}`, binding.value[value])
})
}
}
jobs-search-button-directive is
export default (el, binding) => {
if (process.env.NODE_ENV === 'test') {
Object.keys(binding.value).forEach(value => {
el.setAttribute(`data-jobs-search-button-${value}`, binding.value[value])
})
}
}
This is the test I run, mounting with shallowMount
it('should call jobsSearch method on search button click event', () => {
wrapper.find('[data-jobs-search-button-id="search-button"]').trigger('click')
expect(searchJobs).toHaveBeenCalled()
})
which comes back with
Error: [vue-test-utils]: find did not return [data-jobs-search-button-id="search-button"], cannot call trigger() on empty Wrapper
However wrapper.find('[data-jobs-search-input-id="input-keyword"]') DOES find the input-form
The two directives are registered in the JobsSearch.vue component and they definitely get rendered if I remove the process.env part
I expect the attribute to be added to both components but it only gets added to the BFormInput when testing. Any help will be greatly appreciated.
I believe that the problem occurs when...
... trying to use a directive...
... on a functional child component...
... with shallowMount.
b-button is a functional component.
I've put together the demo below to illustrate the problem. It mounts the same component in 3 different ways and it only fails in the specific case outlined above.
MyComponent = {
template: `
<div>
<my-normal v-my-directive></my-normal>
<my-functional v-my-directive></my-functional>
</div>
`,
components: {
MyNormal: {
render: h => h('span', 'Normal')
},
MyFunctional: {
functional: true,
render: (h, context) => h('span', context.data, 'Functional')
}
},
directives: {
myDirective (el) {
el.setAttribute('name', 'Lisa')
}
}
}
const v = new Vue({
el: '#app',
components: {
MyComponent
}
})
document.getElementById('markup1').innerText = v.$el.innerHTML
const cmp1 = VueTestUtils.mount(MyComponent)
document.getElementById('markup2').innerText = cmp1.html()
const cmp2 = VueTestUtils.shallowMount(MyComponent)
document.getElementById('markup3').innerText = cmp2.html()
#markup1, #markup2, #markup3 {
border: 1px solid #777;
margin: 10px;
padding: 10px;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-template-compiler#2.6.10/browser.js"></script>
<script src="https://unpkg.com/#vue/test-utils#1.0.0-beta.29/dist/vue-test-utils.iife.js"></script>
<div id="app">
<my-component></my-component>
</div>
<div id="markup1"></div>
<div id="markup2"></div>
<div id="markup3"></div>
I haven't really looked at the code for vue-test-utils before but stepping through in the debugger makes me suspicious of this line:
https://github.com/vuejs/vue-test-utils/blob/9dc90a3fd4818ff70e270568a2294b1d8aa2c3af/packages/create-instance/create-component-stubs.js#L99
This is the render function for the stubbed child component. It would appear that context.data.directives does contain the correct directive but they aren't being passed on in the call to h.
Contrast that with the render function in my example component MyFunctional, which passes on all of data. That's required for directives to work with a functional component but when MyFunctional gets replaced with a stub the new render function seems to drop the directives property.
The only workaround I've been able to come up with is to provide your own stub:
VueTestUtils.shallowMount(MyComponent, {
stubs: {
BButton: { render: h => h('div')}
}
})
By using a non-functional stub the directive works fine. Not sure how much value this would take away from the test though.

Nuxt Failed to execute ‘appendChild’ on ‘Node’ when trying to get window size

The problem is that the project must be transferred to Nuxt and some of the code does not work. Namely, the size of the screen must perform actions with the text. Since Nuxt is an SSR, the code cannot be executed on the server side because it does not know the size of the window.
Can I somehow fulfill this idea so that everything works?
I have a project with nuxt and i18n
[nuxt] Error while initializing app DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
at Object.Je [as appendChild]
this my component vue
This code is an example of what causes an error.
<template>
<section>
<div>
<h2 class="subtitle" v-html="filterHeadSlogan"></h2>
</div>
</section>
</template>
<script>
export default {
name: 'testapp',
data() {
return {
filterHeadSlogan: '',
windowWidth: 0
}
},
methods: {
getWindowWidth(event) {
this.windowWidth = document.documentElement.clientWidth
var str = "<i>HELLO WORLD</i>"
if (this.windowWidth >= 960) {
this.filterHeadSlogan = str
} else {
this.filterHeadSlogan = str.replace(/<\/?[^>]+(>|$)/g, '')
}
}
},
mounted() {
this.$nextTick(function () {
window.addEventListener('resize', this.getWindowWidth);
//Init
this.getWindowWidth()
})
}
}
</script>
An error occurred because there was no data in the variable. The village appeared, but there was no data and there was a conflict. I created asyncData
async asyncData(){
return {
headSlogan: ""
}
},
Full code
<template>
<div class="westartslogan">
<div class="head-slogan">
<h2 v-html="headSlogan"></h2>
</div>
<h3>{{$t('page.home.wellcom_block_subtitle_left')}}</h3>
<ul>
<li><i class="icon"></i>
<div v-html="$t('page.home.wellcom_block_option_1_left')"></div></li>
<li><i class="icon"></i>
<div v-html="$t('page.home.wellcom_block_option_2_left')"></div></li>
<li><i class="icon"></i>
<div v-html="$t('page.home.wellcom_block_option_3_left')"></div></li>
<li><i class="icon"></i>
<div v-html="$t('page.home.wellcom_block_option_4_left')"></div></li>
<li><i class="icon"></i>
<div v-html="$t('page.home.wellcom_block_option_5_left')"></div></li>
</ul>
<div class="startcalc-btn button-container">
<nuxt-link :to="getLocalizedRoute({ name: 'calculator' })" class="uk-button uk-button-default">{{
$t('page.home.wellcom_button_calculator') }}
</nuxt-link >
</div>
<div class="ourproject-btn uk-hidden#s">
<div class="button-container">
<nuxt-link :to="getLocalizedRoute({ name: 'portfolio' })" class="uk-button uk-button-default">
{{ $t('page.home.wellcom_button_portfolio') }}
</nuxt-link>
</div>
</div>
</div>
</template>
<script>
export default {
async asyncData(){
return {
headSlogan: ""
}
},
name: 'we_can',
data () {
return {
filterHeadSlogan: '',
headSlogan: this.$i18n.t('page.home.wellcom_block_title_left'),
windowWidth: 0
}
},
methods: {
getWindowWidth (event) {
this.windowWidth = document.documentElement.clientWidth
if (this.windowWidth >= 960) {
this.headSlogan = this.headSlogan
} else {
var str = this.headSlogan
this.headSlogan = str.replace(/<\/?[^>]+(>|$)/g, '')
}
}
},
mounted() {
this.$nextTick(function () {
window.addEventListener('resize', this.getWindowWidth);
//Init
this.getWindowWidth()
})
}
}
</script>
<style scoped>
</style>
I was dealing with the same problem.
Do these steps:
Run your project (yarn start).
Open http://localhost:3000/ in Chrome.
In Chrome devtools clear site data in application tab.
Hard reload the page.

How to use x-template to separate out template from Vue Component

Tried to separate out template from Vue Component as below but it does not work.
Referencing only vue.js file and not browsify.
Vue.component('my-checkbox', {
template: '#checkbox-template',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
<script type="text/x-template" id="checkbox-template">
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title"></div>
</div>
</script>
Or any alternate way to separate out template from vue component.
You define X-Templates in your HTML file. See below for a brief demo
// this is the JS file, eg app.js
Vue.component('my-checkbox', {
template: '#checkbox-template',
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
});
new Vue({el:'#app'})
/* CSS file */
.checkbox-wrapper {
border: 1px solid;
display: flex;
}
.checkbox {
width: 50px;
height: 50px;
background: red;
}
.checkbox.checked {
background: green;
}
<!-- HTML file -->
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<script type="text/x-template" id="checkbox-template">
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title">{{ title }}</div>
</div>
</script>
<div id="app"> <!-- the root Vue element -->
<my-checkbox></my-checkbox> <!-- your component -->
</div>
Sorry for my bad english it's not my main language!
Try it!
You need generate two file in same directory:
path/to/checkboxComponent.vue
path/to/checkboxComponent.html
In checkboxComponent.vue file
<script>
// Add imports here eg:
// import Something from 'something';
export default {
template: require('./checkboxComponent.html'),
data() {
return { checked: false, title: 'Check me' }
},
methods: {
check() { this.checked = !this.checked; }
}
}
</script>
In checkboxComponent.html file
<template>
<div class="checkbox-wrapper" #click="check">
<div :class="{ checkbox: true, checked: checked }"></div>
<div class="title"></div>
</div>
</template>
Now you need to declare this Component in same file you declare Vue app, as the following:
Vue.component('my-checkbox', require('path/to/checkboxComponent.vue').default);
In my case
I have three files with these directories structure:
js/app.js
js/components/checkboxComponent.vue
js/components/checkboxComponent.html
In app.js, i'm declare the Vue app, so the require method path need to start with a dot, like this:
Vue.component('my-checkbox', require('./components/checkboxComponent.vue').default);