Vue.JS Component Not Appearing - javascript

I'm attempting to use Vue Material on a Vue Router Dashboard page, but I'm trying to store the panel in a separate file. I'm absolutely clueless as to why this is not working, I've spent the last 2 hours googling this issue and I don't have anything. Even using the Vue chrome extension doesn't show it, which rules out styling. Putting a red background color on the component does work, yet it still does not work. And also, please forgive my bad code-- I'm about 3 days into Vue.
<template>
<div class="page-container md-layout-row">
<md-app>
<md-app-toolbar class="md-primary">
<span class="md-title">{{ usernameTitleCase }}</span>
</md-app-toolbar>
<PagePanel></PagePanel>
<md-app-content>
<div class="user">
<h1>{{ user.username }}</h1>
<h2>{{ user.customThing }}</h2>
<h3>{{ user.id }}</h3>
</div>
</md-app-content>
</md-app>
</div>
</template>
<script>
import PagePanel from '#/components/panel.vue';
export default {
name: 'Dashboard',
components: {
PagePanel
},
data() {
return {}
},
computed: {
usernameTitleCase() {
const letters = this.user.username.split('');
letters[0] = letters[0].toUpperCase();
return letters.join('')
}
},
created() {
this.user = JSON.parse(localStorage.getItem('user'));
}
}
</script>
<style>
.md-app {
min-height: 350px;
}
.md-drawer {
width: 230px;
max-width: calc(100vw - 125px);
}
</style>
Component File Here:
<template>
<md-app-drawer md-permanent="full">
<md-toolbar class="md-transparent" md-elevation="0">
Navigation
</md-toolbar>
<md-list>
<md-list-item>
<md-icon>move_to_inbox</md-icon>
<span class="md-list-item-text">Inbox</span>
</md-list-item>
<md-list-item>
<md-icon>send</md-icon>
<span class="md-list-item-text">Sent Mail</span>
</md-list-item>
<md-list-item>
<md-icon>delete</md-icon>
<span class="md-list-item-text">Trash</span>
</md-list-item>
<md-list-item>
<md-icon>error</md-icon>
<span class="md-list-item-text">Spam</span>
</md-list-item>
</md-list>
</md-app-drawer>
</template>
<script>
export default {
name: 'PagePanel'
}
</script>
I'm also NOT in production mode and am not getting any errors in console.

It's not easy to spot, but towards the end of this page of the VueMaterial docs, it says:
In these examples we have 3 distinct areas: Toolbar, Drawer and Content. You should create them using the following tags:
md-app-toolbar: ...
md-app-drawer: ...
md-app-content: ...
Any other tag passed as a direct child of the md-app tag will be ignored. The component will only look for these three tags and choose the right placement for them.
Fortunately, they added the ability to use slots (but didn't document them, you have to look at merge requests to see it). You can use them like so:
<template>
<div class="page-container md-layout-row">
<md-app>
<md-app-toolbar> ... </md-app-toolbar>
<page-panel slot="md-app-drawer"></page-panel>
<md-app-content> ... </md-app-content>
</md-app>
</div>
</template>
However, note that the slot value can only be one of the 3 values defined above.

Related

Sharing reactive localStorage state between multiple vue apps on the same page

I'm creating a dashboard with Laravel and VueJS, I created a button that allows to enlarge or reduce my sidebar in a Sidebar.vue component here is my components:
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: ref(localStorage.getItem('is_expanded') === 'true'),
}
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded
localStorage.setItem('is_expanded', this.is_expanded)
},
},
}
</script>
The problem that arises is that in another component I created a navbar with a fixed width, what I would like to do is that when my sidebar changes size I would like my navbar to also change, in the other component I just have a template with a nav and the import of my sidebar.
You say you are using laravel with vue. It's not clear how the vue components are integrated, but I'm going to assume that laravel injects individual vue components that you would like to communicate. (as opposed to a vue based SPA that communicates with laravel using API only)
The two components are not aware of each other. Even though they both have access to the same localStorage, they don't know when the values there are updated.
There are several ways you can deal with this, here's one way
create a reactive object outside of the components to manage the shared state
import { ref, computed } from 'vue'
const isExpandedRef = ref(localStorage.getItem('is_expanded') === 'true');
window.IS_EXPANDED = computed({
get(){
return isExpandedRef.value
},
set(value){
isExpandedRef.value = !!value;
localStorage.setItem('is_expanded', isExpandedRef.value);
}
})
the ref is required so that when isExpandedRef.value changes, the computed getter triggers notifications to it's listeners.
If you load this before any of the components, you will create a computed(reactive) variable available to any script on the page (frames and shadow dom aside)
then you can use in your components like this.
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="is_expanded = !is_expanded">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: window.IS_EXPANDED,
}
},
}
</script>
because is_expanded is a computed with with getters and setters, you can set the value straight from the template, or use this.is_expanded = !this.is_expanded in methods.
As stated already, this relies on using the global window object. This solution is proposed for its simplicity. There are some drawbacks to using the window object, and a more robust solution would rely on injecting such shared state instead of relying on window, but it comes with more overhead.
This code works fine
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons">
<i class="bx bx-chevrons-right"></i>click me
</span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: localStorage.getItem("is_expanded") === "true",
};
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded;
localStorage.setItem("is_expanded", this.is_expanded);
},
},
};
</script>
Here is a GIF showing the result in action: https://share.cleanshot.com/TtKsUj
Make local storage reactive by using watcher and setting its deep property to true
https://vuejs.org/guide/essentials/watchers.html#deep-watchers
or if you are using vue2 then use event bus

Created not being triggered in Vue js

I'm implementing an application with Vue Js and I've the following code:
<template>
<simple-page title="list-patient" folder="Patient" page="List Patient" :loading="loading">
<list-patients #patientsLoaded="onPatientsLoaded"/>
</simple-page>
</template>
Both simple-page and list-patients are custom components created by me. Inside ListPatients I've an HTTP request on Create callback, as follows:
created() {
axios.get("...").then(response => {
...
this.$emit('patientsLoaded');
})
},
Then, my objective is to handle the patientsLoaded event and uptade the loading prop on the top parent component, as follows:
data() {
return {
loading: true
}
},
methods: {
onPatientsLoaded(params) {
this.loading = false;
}
}
However, the created method is not being triggered inside the list-patients component. The only way I can make this work is by removing :loading.
Any one can help?
Edit 1
Code of simple page:
<template>
<section :id="id">
<!-- Breadcrumb-->
<breadcumb :page="page" :folder="folder"/>
<!-- Breadcrumb-->
<!-- Simple Card-->
<simple-card :title="page" :icon="icon" :loading="loading" v-slot:body>
<slot>
</slot>
</simple-card>
<!-- Simple Card-->
</section>
</template>
Code of simple card:
<b-card>
<!-- Page body-->
<slot name="body" v-if="!loading">
</slot>
<!--Is loading-->
<div class="loading-container text-center d-block">
<div v-if="loading" class="spinner sm spinner-primary"></div>
</div>
</b-card>
Your list-patients component goes in the slot with name "body". That slot has a v-if directive so basically it is not rendered and hooks are not reachable as well. Maybe changing v-if to v-show will somehow help you in that situation. Anyway, you have deeply nested slots and it is making things messy. I usually declare loading variable inside of the component, where fetching data will be rendered.
For example:
data () {
return {
loading: true;
};
},
mounted() {
axios.get('url')
.then(res => {
this.loading = false;
})
}
and in your template:
<div v-if="!loading">
<p>{{fetchedData}}</p>
</div>
<loading-spinner v-else></loading-spinner>
idk maybe that's not best practise solution
v-slot for named slots can be indicated in template tag only
I suppose you wished to place passed default slot as body slot to simple-card component? If so you should indicate v-slot not in simple-card itself but in a content you passed it it.
<simple-card :title="page" :icon="icon" :loading="loading">
<template v-slot:body>
<slot>
</slot>
</template>
</simple-card>

Template content is not being injected into slot

I'm trying to use slots to inject content from a parent component to its child, but Vue keeps rendering the default content, not parsing the content sent from its parent.
This is the code of the parent component, which in turn is a child of a global component:
let parentComponent = {
template: `
<div>
<child-component>
<template v-slot:action>Close</template>
<template v-slot:element>Modal</template>
</child-component>
</div>
`,
components: {
'child-component': childComponent
}
};
And here is its child component, where I want to pass content:
let childComponent = {
template: `
<button>
<slot name="action">Open</slot>
<slot name="element">Window</slot>
</button>
`,
};
The button is still displaying the default content: "Open Window"
What am I doing wrong?
EDIT:
This is the rest of the content, just in case it helps:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VueJS</title>
</head>
<body>
<div id="app">
<vue-directives></vue-directives>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="components/slotDirective/slotDirectiveSubcomponent.js"></script>
<script src="components/slotDirective/slotDirective.js"></script>
<script src="components/VueDirectives.js"></script>
<script>
let vue = new Vue({
el: '#app'
});
</script>
</body>
</html>
And VueDirectives.js:
Vue.component('vue-directives', {
template: `
<div>
<h3>{{ title }}</h3>
<parentComponent/>
</div>
`,
data() {
return {
title: "VueJS directives",
}
},
components: {
parentComponent
}
});
I fixed the error. I don't know the reason, but it wasn't working either if I loaded Vue.js from a CDN, or if I manually downloaded it locally, as a single file.
Then I finally tried installing it from npm, and loading Vue.js in node_modules/vue/dist/vue.js, and this way it works. I assume otherwise Vue.js does not comply with all its functionality.
I'm not sure that slots are designed for this. If you are just changing the text of a button passing props into the parentComponent is how i would go about it. E.g.
<parentComponent buttonText="some text or bind with a data value or computed prop"/>
see: https://v2.vuejs.org/v2/guide/components-props.html
Using slots... If you are experimenting try this, a reusable dialog box that you can pop in anywhere and control the content. E.g.
// myDialog
<v-dialog>
<slot>Here you can put what you want</slot>
</v-dialog>
And to use:
<myDialog>
<template>
<myContent /> Or just put content here without another component
</template>
</myDialog>

Vue.js referenced template components stop after first sub component

I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>

How to update a slot in Vue.JS

I have a Vue component simplified below.
Here is the template
<template>
<slot></slot>
</template>
The slot may contain HTML, which is why I decided to use a slot rather than a prop which I would simply bind to. I'd like to keep it that way.
I have a method that gets new HTML from the server. I'd like to use this new HTML to update the slot. I'm not sure if slots are reactive and how I can accomplish this.
I can view the default slot using this.$slots.default[0], but I don't know how to update it with a string of HTML content. Simply assigning the string to the element is obviously incorrect, to .innerHtml does not work because it isn't an available function, and to .text doesn't work. I assume that even though the text element exists on the slot object, the element properties take precedence.
Per suggestion in comments, I've tried this along with a computer property.
<span v-html="messageContent"><slot></slot></span>
But now the problem is that it overwrites the slot passed to me.
How can I reactively update a slot with new HTML in Vue.JS?
I think your issue comes from a misunderstanding of how <slot> inherently works in VueJS. Slots are used to interweave content from a consuming parent component into a child component. See it as a HTML equivalent of v-bind:prop. When you use v-bind:prop on a component, you are effectively passing data into a child component. This is the same as slots.
Without any concrete example or code from your end, this answer is at best just guess-work. I assume that your parent component is a VueJS app itself, and the child component is the one that holds the <slot> element.
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- content here -->
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
In this case, the app has a default ground state where it passes static HTML to the child component:
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- Markup to be interweaved into custom component -->
<p>Lorem ipsum dolor sit amet.</p>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Then, when an event is fired, you want to replace that ground-state markup with new incoming markup. This can be done by storing the incoming HTML in the data attribute, and simply using v-html to conditionally render it. Let's say we want to store the incoming markup in app's vm.$data.customHTML:
data: {
customHTML: null
}
Then your template will look like this:
<!-- Parent template -->
<div id="app">
<custom-component>
<div v-if="customHTML" v-html="customHTML"></div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Note that in contrast to the code you have tried, the differences are that:
It is the parent component (i.e. the consuming component) that is responsible for dictating what kind of markup to pass to the child
The child component is as dumb as it gets: it simply receives markup and renders it in the <slot> element
See proof-of-concept below:
var customComponent = Vue.component('custom-component', {
template: '#custom-component-template'
});
new Vue({
el: '#app',
data: {
customHTML: null
},
components: {
customComponent: customComponent
},
methods: {
updateSlot: function() {
this.customHTML = '<p>Foo bar baz</p>';
}
}
});
.custom-component {
background-color: yellow;
border: 1px solid #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h1>I am the app</h1>
<button type="button" #click="updateSlot">Click me to update slot content</button>
<custom-component>
<div v-if="customHTML" v-html="customHTML">
</div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- custom-component template -->
<script type="text/template" id="custom-component-template">
<div class="custom-component">
<h2>I am a custom component</h2>
<!-- slot receives markup set in <custom-component> -->
<slot></slot>
</div>
</script>
Below is my solution though I don't like this opinion (load html into slot directly in current component level) because it breaks the rules for the slot. And I think you should do like this way (<component><template v-html="yourHtml"></template></component>), it will be better because Slot will focus on its job as Vue designed.
The key is this.$slots.default must be one VNode, so I used extend() and $mount() to get the _vnode.
Vue.config.productionTip = false
Vue.component('child', {
template: '<div><slot></slot><a style="color:green">Child</a></div>',
mounted: function(){
setTimeout(()=>{
let slotBuilder = Vue.extend({
// use your html instead
template: '<div><a style="color:red">slot in child</a></div>',
})
let slotInstance = new slotBuilder()
this.$slots.default = slotInstance.$mount()._vnode
this.$forceUpdate()
}, 2000)
}
})
new Vue({
el: '#app',
data() {
return {
test: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<child><h1>Test</h1></child>
</div>

Categories