Why do these Vue props fail outside the main component? - javascript

I am working on a Vue 3 app. I have run into a problem working with a <Navbar /> component and one of its sub-components.
In App.vue:
<template>
<Navbar />
<Content title="Home" />
<Footer />
</template>
<script>
import Navbar from './components/Navbar.vue'
import Content from './components/Content.vue'
import Footer from './components/Footer.vue'
export default {
name: 'App',
components: {
Navbar,
Content,
Footer
}
}
</script>
Within the Content.vue component, I can display the title this way:
<h1>{{ title }}</h1>
<script>
export default {
name: 'Content',
props: {
title: String
}
}
</script>
But displaying buttons with their labels by the same pattern as above does not work:
// Button.vue component
<template>
<button class="btn btn-sm btn-generate">
{{ label }}
</button>
</template>
<script>
export default {
name: 'Button',
props: {
label: String
}
}
</script>
// Navbar.vue component
<template>
<div class="navbar">
<Button label="Button 1" />
<Button label="Button 2" />
</div>
</template>
<script>
import Button from './Button'
export default {
name: 'Navbar',
components: {
Button
}
}
</script>
The problem
As a result of the code above, inside <div class="navbar"> there is only one button with no label, instead of 2 buttons labeled "Button 1" and "Button 2".
Where is my mistake?

I think that naming your component just as the already existing HTML element is not a good idea. Try changing it to MyButton and use <my-button>... in the navbar component (you still want to use <button> in MyButton, as you want to build upon it).
Most probably browser still picks just a default button instead of yours.
The first essential rule mentioned in Vue docs is Use multi-word component names.

You need to bind them so :label="button 1"

Related

Detect from component if it is within certain DOM parent

I currently have a Vue.js application in which I have used my own component. I want HTML DOM children components to be able know if they are contained in my component or just in the application itself. What would the best way to do it?
I tried it using props, but as it is all contained within another application that does not seem possible.
example: I want the si-button to be able to determine that it is contained in a si-dialog. How do I pass that information to the si-button?
<si-dialog ref="siDialogChildren" name="children">
<div>
<h1>Hello children dialog!</h1>
<p>Click outside the dialog to close this dialog.</p>
</div>
<si-button id="test" type="custom" :on-click="buttonPress">
Click
</si-button>
</si-dialog>
Yours sincerely,
Mirijam
You might want to use $parent as specified in the Vue documentation.
For example, you can get the name of the parent component by accessing to its "$options.name" property.
Example:
App.vue
<template>
<div id="app">
<ParentComponent>
<ChildComponent />
</ParentComponent>
</div>
</template>
<script>
import ParentComponent from './components/ParentComponent'
import ChildComponent from "./components/ChildComponent";
export default {
name: 'App',
components: {
ParentComponent,
ChildComponent
}
}
</script>
<style>
</style>
ParentComponent.vue
<template>
<div>
<p>I'm a parent component.</p>
<slot></slot>
</div>
</template>
<script>
export default {
name: "ParentComponent",
props: {},
computed: {}
};
</script>
<style scoped>
</style>
ChildComponent.vue
<template>
<div>
<p>Parent name: {{componentName}}</p>
<p>I'm inside a ParentComponent? {{componentName === "ParentComponent" ? "Yes!" : "No."}}</p>
</div>
</template>
<script>
export default {
name: "ChildComponent",
computed: {
componentName() {
// We get the parent name from the $options.name property of our parent
// component
return this.$parent.$options.name
}
}
};
</script>
<style scoped>
</style>
NOTE You can also access to other properties on the parent component by using the $parent property. Try it out!

How to fix blank screen when trying to render components with ' :is="component" '

Writing in NativeScript-Vue for Android, I am trying to render a component based on button taps. I am using this this plugin to wrap the app in a global SideDrawer for quick navigation. I use buttons within the SideDrawer for viewing components. Here is what my App.vue looks like:
<template>
<Frame>
<Page actionBarHidden="true">
<GlobalDrawer>
<template slot="content">
<Label class="drawer-header" text="Drawer"/>
<Button class="drawer-button" text="Home" #tap="currentComponent = 'Home'" />
<Button class="drawer-button" text="Info" #tap="currentComponent = 'InfoPage'" />
<Button class="drawer-close-button" #tap="$drawer.close()">Close</Button>
</template>
<template slot="frame">
<ContentView>
<component v-for="component in pagesArray" v-show="component === currentComponent" :is="component" v-bind:key="component" />
</ContentView>
</template>
</GlobalDrawer>
</Page>
</Frame>
</template>
<script>
import Home from './pages/Home.vue';
import InfoPage from './pages/InfoPage.vue';
export default {
data () {
return {
pagesArray: ['Home', 'InfoPage'],
currentComponent: 'Home'
};
},
components: {
Home,
InfoPage
}
}
</script>
Here is the code in Home.vue, the default component I try to render:
<template>
<Page>
<navigation-bar />
<GridLayout>
<Label class="info" horizontalAlignment="center" verticalAlignment="center">
<FormattedString>
<Span class="fa" text.decode=" "/>
<Span :text="message"/>
</FormattedString>
</Label>
</GridLayout>
</Page>
</template>
<script>
import NavigationBar from '../components/NavigationBar.vue';
export default {
computed: {
message() {
return "Test Homepage";
}
},
components: {
NavigationBar
}
};
</script>
If I directly use the <home /> component within <ContentView /> it displays fine, but when trying to display it dynamically, I get a blank page; no errors. Any thoughts on this?
I run the app on a connected Samsung Galaxy S7 using the NativeScript CLI. NativeScript-vue version: ^2.4.0.
Edit [01.20.20]: Detailed explanation on how I solved it.
Read about it here: https://nativescript-vue.org/en/docs/routing/manual-routing/
In Application.vue, my GlobalDrawer looks like this:
<GlobalDrawer >
<template class="global-drawer" slot="content">
<ScrollView orientation="vertical" scrollBarIndicatorVisible="true" height="100%">
<StackLayout orientation="vertical">
<Label class="drawer-header" text="Menu"/>
<Button class="drawer-button" text="Home" ref="homeButton" #tap="homePageTap"/>
<Button class="drawer-button" text="System Oversight" #tap="infoPageTap" />
<Button class="drawer-button" text="Companies" #tap="companiesPageTap" />
<Button class="drawer-button" text="Calendar" #tap="calendarPageTap" />
<Button class="drawer-close-button" #tap="$drawer.close()">Close</Button>
<Label class="drawer-footer" text="By Bouvet" />
</StackLayout>
</ScrollView>
</template>
<template slot="frame">
<frame-hub />
</template>
</GlobalDrawer>
The methods-section contains the #tap-methods:
methods: {
homePageTap(args) {
this.$navigateTo(Home, { frame: "main-frame" });
this.$drawer.close();
},
infoPageTap(args) {
this.$navigateTo(InfoPage, { frame: "main-frame" });
this.$drawer.close();
},
companiesPageTap(args) {
this.$navigateTo(CompaniesPage, { frame: "main-frame" });
this.$drawer.close();
},
calendarPageTap(args) {
this.$navigateTo(CalendarPage, { frame: "main-frame" });
this.$drawer.close();
}
},
The < frame-hub > is a component (FrameHub.vue), looking like this:
<template>
<Page actionBarHidden="true">
<Frame id="main-frame">
<home />
</Frame>
</Page>
</template>
<script>
import Home from './../pages/Home';
import InfoPage from './../pages/InfoPage.vue';
import CompaniesPage from './../pages/CompaniesPage.vue';
import CalendarPage from './../pages/Calendar.vue';
export default {
components: {
Home,
InfoPage,
CompaniesPage,
CalendarPage
}
}
</script>
The #tap-methods in Application.vue changes the components that are renderered within the"main-frame"-element in FrameHub.vue. This way the GlobalDrawer is always on top, while the user is free to switch between pages.
I hope this helps anyone having the same issue. Also I'm sure this solution can be improved. Though I'm not currently working on this, but please let me know about improvements.
Creator of nativescript-vue-global-drawer plugin here.
The frame slot of GlobalDrawer contains a Frame element (here) to control navigation inside the drawer as stated here, which means the slot accept only Page elements.

How to use vue components in nativescript-vue popups?

I open the popup in some root component like this:
import parentt from "./parentt.vue";
.
.
.
this.$showModal(parentt, {
fullscreen: true,
});
This is the content of parentt.vue:
<template>
<StackLayout>
<label text="parent" />
<!-- <child /> -->
</StackLayout>
</template>
<script>
import child from "./child.vue";
export default {
components: [child],
};
</script>
<style scoped>
</style>
This is the content of child.vue:
<template>
<StackLayout>
<label text="child" />
</StackLayout>
</template>
<script>
export default {};
</script>
<style scoped></style>
Whith <child /> commented out I get a popup with text parent in it.
with <child /> being there I get a white screen.
I'm using many components in different places in my code, it's only here in a popup that it doesn't work.
You have wrong bracket in components object in parentt.vue. Components is an object, thus use braces instead of the square brackets.
So, the correct script section looks like in parentt.vue:
<script>
import child from "./child.vue";
export default {
components: {
child
},
};
</script>
I recommend for detailed informations the official vue documentation

Variable is linked in two components vue js

I just want to use an independant component many times like this :
<main-component>
<other-component />
<other-component />
</main-component>
I have a problem with variable in my other-component.
In the code bellow I use show var to display or not content according button click. I want to display only the paragraph of the current component clicked
import otherComponent from '../../otherComponent'
// main component
export default {
name: 'main-component',
components: {
otherComponent
},
data () {
return {
}
},
}
<template>
<div>
<other-component />
<other-component />
</div>
</template>
// other component
export default {
name: 'other-component',
data () {
return {
show: false
}
},
methods: {
toggle(){
this.show = !this.show
}
}
<template>
<div>
<button #click="toggle">Toggle</button>
<p v-show="show">1</p>
</div>
</template
When I click in button the two p are displayed. Why ?
If you nest components inside one another, you need to use slots: https://v2.vuejs.org/v2/guide/components-slots.html#ad.
For the following code to render correctly:
<main-component>
<other-component />
<other-component />
</main-component>
Your main-component component needs to have <slot /> in it's template:
<template>
<div>
<slot />
</div>
</template>
Then <slot /> will be replaced by ...
<other-component />
<other-component />
...when it renders. Which means main-component does not need to import or have any reference to otherComponent.
I have an example on CodeSandbox of this working: https://codesandbox.io/embed/vue-template-gblx0.

Vuejs toggle class to the body on button click in a components

I want to toggle a class to the body or to the root element("#app") if I click on the button inside the header component
Header.vue :
<template lang="html">
<header>
<button class="navbar-toggler navbar-toggler align-self-center" type="button" #click="collapedSidebar">
<span class="mdi mdi-menu"></span>
</button>
</header>
</template>
<script>
export default {
name: 'app-header',
data: {
isActive: false
},
methods: {
collapedSidebar() {
}
}
}
</script>
App.vue :
<template lang="html">
<div id="app" :class="{ active: isActive }">
...
</div>
</template>
! Please correct me if I'm in the wrong direction. I'm new in Vuejs :)
You can emit an event inside your header and maybe catch it in the mounted of app component like this:
In your sidebar or other component:
on_some_btn_click: function (){
this.$emit('toggle_root_class');
}
In your app component:
mounted: function(){
var _this = this;
this.$on('toggle_root_class', function(){
_this.active = !_this.active;
});
}
Vue may restrict event from being observed in sibling components. So I use EventBus in my projects to handle sending events easily.
the problem lies in your component scope. You tried to access data in Header.vue in App.vue which is impossible by the structure in showed in your code. Consider moving isActive data to App.vue or use Vuex.
You can use jquery to toggle class for an element which is not inside the Vue template.
You can call a function on click of a button and inside it, you can toggle class in body tag using jquery.
<template lang="html">
<header>
<button class="navbar-toggler navbar-toggler align-self-center" type="button" :class="{ active: isActive }" #click="activeLink">
<span class="mdi mdi-menu"></span>
</button>
</header>
</template>
<script>
export default {
name: 'app-header',
data: {
isActive: false
},
methods: {
activeLink() {
$('body').toggleClass('.class-name');
}
}
}
</script>

Categories

Resources