Are there any "webcomponent" version of materialize-css?
Has anyone tried to build a webcomponent using materialize-css? I'm able to get the style but haven't figured out how to get the underlying javascript/icons to appear:
import {html, LitElement, css, unsafeCSS} from 'lit-element';
// import 'materialize-css';
import MaterializeCSS from 'materialize-css/dist/css/materialize.min.css';
// import IconFont from 'https://fonts.googleapis.com/icon?family=Material+Icons';
class MaterializeDemo extends LitElement {
static get styles() {
return [
css`
:host {
display: block;
}
`,
unsafeCSS(MaterializeCSS),
// unsafeCSS(IconFont),
];
}
render() {
return html`
<a class="waves-effect waves-light btn">button</a>
<a class="waves-effect waves-light btn"><i class="material-icons left">cloud</i>button</a>
<a class="waves-effect waves-light btn"><i class="material-icons right">cloud</i>button</a>
<a class="btn-floating pulse"><i class="material-icons">menu</i></a>
<a class="btn-floating btn-large pulse"><i class="material-icons">cloud</i></a>
<a class="btn-floating btn-large cyan pulse"><i class="material-icons">edit</i></a>
`;
}
}
customElements.define('materialize-demo', MaterializeDemo);
Most likely materialize-css doesn't handle well the ShadowRoot (on the javascript part), if you don't care about shadow-root you could disable it by overriding the method createRenderRoot
class MaterializeDemo extends LitElement {
[...]
createRenderRoot() {
return this;
}
}
However the "styles" method won't work anymore and you will have to declare your css globally.
Related
I'm trying to do some testings for my components which mostly contain element-plus elements. However, when running tests, I'm not able to access to elements in the <template> tag .
From the example below, I'm not able to render the <template #dropdown> in the snapshot:
example.spec.js
import { ElementPlus } from 'element-plus'
import { createI18n } from 'vue-i18n'
import { mount } from '#vue/test-utils'
import store from '#/store/index'
import TTT from '../../../TTT.vue'
const i18n = createI18n({
// vue-i18n options here ...
})
describe('test', () => {
const wrapper = mount(TTT, {
global: {
plugins: [ElementPlus, i18n, store],
},
})
expect(wrapper)
test('snapShot', () => {
expect(wrapper.element).toMatchSnapshot()
})
})
TTT.vue
<template>
<el-dropdown class="me-3" :hide-timeout="0" :show-timeout="0">
<span class="el-dropdown-link h-100">
<a href="#" class="px-4 py-3 text-white font-18" #click.prevent><font-awesome-icon class="font1R6" icon="earth-americas" /></a>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item #click.prevent="handleChangeLanguage(item.value)"> 123 </el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
snapshot
exports[`test snapShot 1`] = `
<el-dropdown
class="me-3"
hide-timeout="0"
show-timeout="0"
>
<span
class="el-dropdown-link h-100"
>
<a
class="px-4 py-3 text-white font-18"
href="#"
>
<font-awesome-icon
class="font1R6"
icon="earth-americas"
/>
</a>
</span>
</el-dropdown>
`;
I tried to change the template tag to slot, i.e. <template #dropdown> to <slot name="dropdown">. The content did reflect in snapshot, but some errors showed up on the website.
If anyone knows the solution, please let me know. I'm stuck for days....
The following code fixed the issue:
const wrapper = mount(Component, {
global: {
stubs: {
teleport: { template: '<div />' },
},
},
})
I'm just starting to use VueJS & Tailwind, having never really used anything related to npm before.
I have the below code, making use of Tailwind & Headless UI which through debugging, I know I'm like 99% of the way there... except for the continuous error message
Uncaught ReferenceError: posts is not defined
I know this should be straight forward, but everything I've found either here or with Google hasn't worked. Where am I going wrong?
<template>
<Listbox as="div" v-model="selected">
<ListboxLabel class="">
Country
</ListboxLabel>
<div class="mt-1 relative">
<ListboxButton class="">
<span class="">
<img :src="selected.flag" alt="" class="" />
<span class="">{{ selected.name }}</span>
</span>
<span class="">
<SelectorIcon class="" aria-hidden="true" />
</span>
</ListboxButton>
<transition leave-active-class="" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions class="">
<ListboxOption as="template" v-for="country in posts" :key="country" :value="country" v-slot="{ active, selected }">
<li :class="">
<div class="">
<img :src="country.flag" alt="" class="" />
<span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']">
{{ country.latin }}
</span>
</div>
<span v-if="selected" :class="">
<CheckIcon class="" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
<script>
import { ref } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
data() {
return {
response: null,
posts: undefined,
};
},
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},
setup() {
const selected = ref(posts[30])
return {
selected,
}
},
}
</script>
The offending line is const selected = ref(posts[30]) which I know I need to somehow define posts, but I don't get how?
CAUSE OF YOUR ERROR:
You are trying to access an array element before the array is populated. Thus the undefined error.
EXPLANATION
You are using a mix of composition api and options api. Stick to one.
I am writing this answer assuming you will pick the composition api.
Follow the comments in the below snippet;
<script>
// IMPORT ONMOUNTED HOOK
import { ref, onMounted } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
// YOU DO NOT NEED TO DEFINE THE DATA PROPERTY WHEN USING COMPOSITION API
/*data() {
return {
response: null,
posts: undefined,
};
},*/
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
// YOU DO NOT NEED THESE LIFE CYCLE HOOKS; COMPOSITION API PROVIDES ITS OWN LIFECYCLE HOOKS
/*mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},*/
setup() {
// YOU ARE TRYING TO ACCESS AN ELEMENT BEFORE THE ARRAY IS POPULATED; THUS THE ERROR
//const selected = ref(posts[30])
const posts = ref(undefined);
const selected = ref(undefined);
onMounted(()=>{
// CALL THE AXIOS METHOD FROM WITHIN THE LIFECYCLE HOOK AND HANDLE THE PROMISE LIKE A BOSS
axios.get('http://localhost')
.then((res) => {
selected.value = res[30];
});
});
return {
selected,
}
},
}
</script>
According to your comment; you should first check if the “selected != null” before using ‘selected’ inside the template. You can use a shorthand version like this
<img :src=“selected?.flag” />
I'm noticing that the v-for I'm using to render some images inside a component, when data are updated using the event bus,I will have a little delay in DOM content replacing. Is there a way to solve this little problem?
NB: I can't replicate the data passed because it's a 50 elements list of images urls and are provided by an external api service.
<template>
<div class="row m-0 pl-5 pr-5">
<div class="col-4 col-img hide p-0" v-for="(img, idx) in feed" :key="idx" v-observe-visibility="visibilityChanged">
<img class="img-fluid w-100 h-100 ig-image" :src="img.url">
<div class="card-img-overlay text-center">
<p class="text-white">{{ img.likes }} <i class="fas fa-heart"></i> {{ img.comments }} <i class="fas fa-comment"></i></p>
</div>
<a class="stretched-link" href="#image-modal" data-toggle="modal" v-on:click.prevent="zoomImage(img.url)"></a>
</div>
</div>
</template>
<script>
import { EventBus } from '#/standalone/event-bus'
export default {
name: 'UserMedia',
data() {
return {
feed: null,
imgUrl: null
}
},
mounted() {
EventBus.$on('profile-media', (media) => {
this.$set(this, 'feed', media)
})
},
methods: {
zoomImage(url) {
this.$set(this, 'imgUrl', url)
},
visibilityChanged(isVisible, el) {
if(isVisible){
el.target.classList.remove('hide')
el.target.classList.add('show')
}
}
}
}
</script>
<style scoped>
.col-img {
height: 420px;
}
</style>
Since if you change the data 'feed', it will take time to load the images but inorder to load small size images prior to heavy size for good user experience you can use some very good npm packages:
npm i vue-lazyload (I have used it and recommend this one)
npm i v-lazy-image (I haven't used yet but you can explore this as well)
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.
## Note: ##
Onclick of div I am trying to enable the class name which was clicked.
like eg: $('div[data-index='0.0']').addClass('selected'); in Jquery // addClass only to specified div which has data-index =0.0.
I dont want want it to enable all className on click.
Unique way of enabling specific class name
I want the answer specifically in angular2
## Template: ##
<div class="board">
<div class="matrix selected" data-index="0-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-0)">
</div>
<div class="matrix selected" data-index="0-1" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-1)">
</div>
<div class="matrix selected" data-index="1-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(1-0)">
</div>
<div class="matrix selected" data-index="1-1" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(1-1)">
</div>
</div>
## component ##
import { Component, OnInit} from '#angular/core';
#Component({
selector: 'app'
})
export class displayComponent implements OnInit {
isCellSelected :boolean ;
constructor() {
}
ngOnInit() {
}
fireClickEvent(clickedCell) {
const selectedCellIndex = clickedCell;
this.isCellSelected = true; // enabling className for all three
// I need only clicked cell to activate the className Not all.
}
}
Thanks in Advance .!!
#Directive({
selector: '[selectable]'
})
export class MatrixDirective {
#HostBinding('class')
classes = '';
#Input('selectableClass')
toggleClass = 'selected';
#HostListener('click')
fireClickEvent() {
if(this.classes.indexOf(this.toggleClass) > -1) {
this.classes = this.classes.replace(this.toggleClass, '');
} else {
this.classes = `${this.classes} ${this.toggleClass}`;
}
};
}
This directive here will accomplish what you're looking for, a bit of overkill, but will help shift you into the "angular way" of doing things.
To use the above directive, just adjust your elements above with this directive.
<!-- OLD -->
<div class="matrix selected" data-index="0-0" [ngClass]="{selected:isCellSelected}" (click)="fireClickEvent(0-0)">
</div>
<!-- Adjusted -->
<div class="matrix" data-index="0-0" selectable>
</div>