Hopefully this has been answered before - essentially I'm trying to append a block ("CodeBlock.vue") to an element inside App.vue from an onClick event triggered inside a sibling of CodeBlock, and a child of App.vue, ("ButtonSidebar.vue"). I'm a little confused by emitting events and/or using an eventBus Vue instance, so any pointers would be greatly appreciated:
So far I have the following. CodeBlock.vue which will be used as an instance and appended to a div inside App.vue.
CodeBlock.vue:
<template>
<div :class="type">
THIS IS A CODE BLOCK!!
</div>
</template>
<script>
export default {
name: 'CodeBlock',
props: [ 'type' ]
}
</script>
App.vue:
<template>
<div id="app" class="container">
<ButtonSidebar/>
<div id="pageBlocks" ref="container"></div>
</div>
</template>
<script>
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
// import { eventBus } from './main'
import AddTitle from './components/modules/AddTitle'
import AddSubTitle from './components/modules/AddSubTitle'
import ButtonSidebar from './components/modules/ButtonSidebar'
import CodeBlock from './components/modules/CodeBlock'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
export default {
name: 'App',
components: {
AddTitle,
AddSubTitle,
ButtonSidebar,
CodeBlock
}
}
</script>
<style>
#app {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #1f1f1f;
margin-top: 60px;
}
.no-border {
border: unset !important;
border: 0px !important;
}
</style>
ButtonSidebar.vue:
<template>
<div>
<b-button class="btn-circle absolute-float-tight text-dark" v-on:click="reveal=!reveal">
<font-awesome-icon v-if="!reveal" :icon="faPlusIcon" />
<font-awesome-icon v-if="reveal" :icon="faMinusIcon" />
</b-button>
<transition name="custom-classes-transition" enter-active-class="animated bounceInDown" leave-active-class="animated bounceOutRight">
<div v-if="reveal" class="absolute-float-reveal">
<b-button class="btn-circle text-dark" v-on:click="addCodeBlock"><font-awesome-icon :icon="faCodeIcon" /></b-button>
</div>
</transition>
</div>
</template>
<script>
import Vue from 'vue'
import FontAwesomeIcon from '#fortawesome/vue-fontawesome'
import faPlus from '#fortawesome/fontawesome-pro-regular/faPlus'
import faMinus from '#fortawesome/fontawesome-pro-regular/faMinus'
import faCode from '#fortawesome/fontawesome-pro-regular/faCode'
import CodeBlock from './CodeBlock'
export default {
name: 'ButtonSidebar',
computed: {
faPlusIcon () {
return faPlus
},
faMinusIcon () {
return faMinus
},
faCodeIcon () {
return faCode
}
},
components: {
FontAwesomeIcon,
CodeBlock
},
data () {
return {
reveal: false
}
},
props: ['codeBlocks'],
methods: {
addCodeBlock () {
var ComponentClass = Vue.extend(CodeBlock)
var instance = new ComponentClass({
propsData: { type: 'primary' }
})
instance.$mount()
this.$el.querySelector('#pageBlocks').appendChild(instance.$el)
}
}
}
</script>
<style scoped>
.absolute-float-tight {
left: 20px;
position: absolute;
}
.absolute-float-reveal {
left: 60px;
position: absolute;
}
.btn-circle {
background-color: transparent;
border-radius: 50%;
height: 34px;
padding: 0;
width: 34px;
}
</style>
It's around the this.$el.querySelector('#pageBlocks').appendChild(instance.$el) part that I start to loose the plot a bit...I'm worried that I have to strip everything down and start again perhaps?
You should avoid reaching to the DOM as much as possible. The source of truth for the data should be in your components.
refs are very useful to integrate other js library that needs a DOM element.
So in your case, assuming codeBlocks are available in your App.vue components, the SidebarButton needs to emit an event when it's clicked so that the parent App.vue can add a new Codeblock:
(I have removed some code not needed for the example. CodeBlock.vue stays the same)
App.vue
<template>
<div id="app" class="container">
<ButtonSidebar #add-block="addCodeBlock" />
<CodeBlock v-for="block in codeBlocks" :type="block.type" />
</div>
</template>
<script>
import ButtonSidebar from '../ButtonSidebar'
import CodeBlock from '../CodeBlock'
export default {
name: 'App',
components: {ButtonSidebar, CodeBlock},
data() {
return {
codeBlocks: []
}
},
methods: {
addCodeBlock() {
const newBlock = {type: 'whatever'}
this.codeBlocks.push(newBlock)
}
}
}
</script>
ButtonSideBar.vue
<template>
<div>
<b-button class="btn-circle text-dark" v-on:click="addCodeBlock</b-button>
</div>
</template>
<script>
export default {
name: 'ButtonSidebar',
data () {
return {
reveal: false
}
},
methods: {
addCodeBlock () {
this.$emit('add-block')
}
}
}
</script>
A good pattern to follow in Vue is to lift the state to the parents and passing it down as props whenever you feel like you want to share state between parents and children.
I think you could achieve it in this simple way:
App.vue (template section)
<ButtonSidebar #add="addCodeItem"/>
<div id="pageBlocks">
<codeBlock v-for="code in arrCodes" :type="code.type"/>
</div>
App.vue (script)
export default {
data() {
return {
arrCodes: []
}
},
methods: {
addCodeItem(codeType) {
this.arrCodes.push( { type: codeType } )
}
}
}
ButtonSidebar.vue (script section)
addCodeBlock () {
this.$emit('add', 'yourtype');
}
Related
Created a small project with vuejs and want to display all my icons but for some reason it does not displays my icons, Can anyone tell me where I am making the mistakes.
I want to display all my icons from that icons list in the div element.
Any help will be much appreciated, thanks!
ValidatingProps.vue
<template>
<div>
<h1>How to use Validation in props</h1>
<h3>{{ status }}</h3>
<div class="icons" v-html="icons"></div>
</div>
</template>
<script>
import icons from "./icons";
console.log(icons);
export default {
name: "validatingProps",
props: {
status: {
type: String,
default: "Validating",
validator: (value) => {
console.log("Val", value);
return ["in-progress", "complete"].includes(value);
},
},
icons: {
type: String,
required: true,
validator: (value) => {
console.log("Value >>>> ", value);
Object.keys(icons).includes(value);
console.log("Icons props: ", Object.keys(icons).includes(value));
},
},
},
mounted() {
console.log("mounted ", Object.keys(icons));
console.log("this.Icons: ", this.icons);
},
};
</script>
<style scoped>
.icons {
height: 200px;
width: 200px;
border: 2px solid red;
margin: 0 auto;
background: lightcyan;
}
App.vue
<div>
<template>
<ValidatingProps
status="progress"
:icons="Object.keys(this.icons.default)"
/>
</div>
</template>
<script>
import ValidatingProps from "./components/validatingProps.vue";
import * as icon from "./components/icons/";
export default {
name: "App",
components: {
ValidatingProps,
},
data() {
return {
icons: icon,
}
}
</script>
my icons folder structure:
My Div box shows:
I am trying to style a p tag that is slotted into a child component using the tag.
Parent Code
<template>
<BasicButton content="Test 1234" #click="SendMessage('test')" height="10" width="50" />
<TransparentTopbar />
<BasicContainer width="90">
<p class="p-blueish-gray">{{ LorumIpsum() }}</p>
</BasicContainer>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Homepage from "./components/home/Homepage.vue";
import TransparentTopbar from "./components/tools/topbar/TransparentTopbar.vue";
import BasicButton from "./components/tools/button/BasicButton.vue";
import BasicContainer from "./components/tools/container/basic_container/BasicContainer.vue"
//import func from "vue-temp/vue-editor-bridge";
export default defineComponent({
name: "App",
components: {
Homepage,
TransparentTopbar,
BasicButton,
BasicContainer
},
methods: {
SendMessage: sendMessage,
LorumIpsum: lorumIpsum
},
});
Child Code
<template>
<div class="light-rounded-container card-margins" :style="container_style">
<slot></slot>
</div>
</template>
<script lang="ts" src="./BasicContainer.ts" />
<style scoped src="./BasicContainer.css" />
import { defineComponent } from 'vue';
export default defineComponent({
name: 'BasicContainer',
props: {
width: {
type: Number,
default: 90,
required: false
}
},
data() {
return {
container_style: {
width: this.width.toString() + '%'
}
}
},
methods: {}
});
.light-rounded-container {
background-color: #242629;
border-radius: 15px;
align-self: center;
}
::v-slotted(p) {
color:red !important;
width: 1% !important;
padding-left: 5% !important;
padding-right: 5% !important;
padding-top: 25px !important;
padding-bottom: 15px !important;
}
The BasicContainer component is the one I am trying to style slotted content on. I would like to style the p tag that I pass to this component in the parent, but I would like to style it from within this child component.
#StevenB. you are corrent. I did not have my style in a separately defined NON-SCOPED .css file. After moving my styling into their and re structuring my HTML to avoid bleeding the css style into other global scope, it worked. Much appreciated.
I am really new to Vue.js and have a question.
What I want to achieve is:
-Have two input fields. If you type in something in the first input field, the value of the second input field should be updated with the value of the first input but base64 encoded.
Here is what I have tried:
main.js:
import Vue from 'vue';
import App from './App';
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
Vue.use(Antd);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
data: {
tokenInput: null,
tokenOutput: null,
},
methods: {
handleChange: function() {
this.tokenOutput = btoa(this.tokenInput);
}
}
}).$mount('#app');
App.vue:
<template>
<div id="app">
<Home/>
</div>
</template>
<script>
import Home from './components/Home.vue'
export default {
name: 'App',
components: {
Home
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
</style>
Home.vue:
<template>
<div>
<h1>helloworld</h1>
<a-tabs default-active-key="1">
<a-tab-pane key="1" tab="token converter">
<div class="content">
<a-card title="Input" class="token">
<a-textarea
v-model="tokenInput"
#change="handleChange"
allowClear
placeholder="Input the token to convert ..."
:rows="20"
>
</a-textarea>
</a-card>
<a-card title="Converted token:" class="token">
<a-textarea
:value="convertedToken"
disabled
:rows="20"
/>
</a-card>
</div>
</a-tab-pane>
<a-tab-pane key="2" tab="2nd tab"> Nothing is here. </a-tab-pane>
</a-tabs>
</div>
</template>
<style>
.content {
padding: 24px;
}
.token {
margin: 10px;
padding: 10px;
width: 100%;
}
</style>
The error message I get:
Property or method "handleChange" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property
I have also tried:
new Vue({
render: h => h(App),
data() {
return {
tokenInput: null,
tokenOutput: null,
}
},
methods: {
handleChange() {
this.tokenOutput = btoa(this.tokenInput);
}
}
}).$mount('#app');
What am I doing wrong ?
Problem: My <v-container> component doesn't always go to the height of the application. I have tried using the fill-height property, setting height: 100%;, setting height: 100vh;, even tried using max-height. I can't seem to get the results that I want!
Goal: I want my container to always span the entire height of the window. My theme on the application changes between light/dark. This changes the background color, which should always cover the entire height and width of the application view port.
Code for App.vue:
<template>
<v-app>
<main>
<v-container
fluid
fill-height
id="app"
tag="div"
style="height: 100vh; max-height: 100%;"
:class="theme"
>
<Toolbar color="primary" />
<transition
name="routerAnimation"
enter-active-class="animated faster fadeIn"
>
<router-view></router-view>
</transition>
<v-snackbar
:color="alertColor"
class="animated faster heartBeat"
:dark="isDark"
v-model="alert"
:multi-line="mode === 'multi-line'"
:timeout="alertTimeout"
top
:vertical="mode === 'vertical'"
>
<v-icon class="pr-4">{{ getAlertIcon() }}</v-icon>
{{ alertMessage }}
<v-btn :dark="isDark" icon #click="toggleAlert(false)">
<v-icon>close</v-icon>
</v-btn>
</v-snackbar>
</v-container>
</main>
</v-app>
</template>
<script>
import Toolbar from "./components/Toolbar";
import { themeMixin } from "./mixins/themeMixin.js";
import { alertMixin } from "./mixins/alertMixin";
import { authMixin } from "./mixins/authMixin";
import { socketMixin } from "./mixins/socketMixin";
import { TokenService } from "./services/tokenService";
import { ThemeService } from "./services/themeService";
import { UserService } from "./services/userService";
import { cordMixin } from "./mixins/cordMixin";
export default {
name: "app",
mixins: [alertMixin, authMixin, cordMixin, themeMixin, socketMixin],
components: { Toolbar },
created() {
this.init();
const theme = ThemeService.getTheme();
if (theme !== null) {
this.$store.commit("theme", theme);
} else {
this.$store.commit("theme", this.isDark ? "dark" : "light");
}
},
data() {
return {
color: "#0c0c0c",
y: "top",
x: null,
mode: ""
};
},
mounted() {
this.init();
}
};
</script>
<style>
#import "https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css";
#import "https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons";
html {
height: 100%;
}
body {
height: 100%;
margin: 0 auto 0;
}
#app {
height: 100%;
font-family: "Hilda-Regular", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page {
width: inherit;
}
</style>
So the solution for me was to not only remove the container in App.vue, but to also remove the styles from the <style scoped> tag for both html and body. I was setting the height: 100%; which caused some issues when content was dynamically loaded in.
The correct App.vue looks like this:
<template>
<v-app id="app" :dark="isDark">
<Toolbar color="primary" />
<transition
name="routerAnimation"
enter-active-class="animated faster fadeIn"
>
<router-view></router-view>
</transition>
<v-snackbar
:color="alertColor"
class="animated faster heartBeat"
:dark="isDark"
v-model="alert"
:multi-line="mode === 'multi-line'"
:timeout="alertTimeout"
top
:vertical="mode === 'vertical'"
>
<v-icon class="pr-4">{{ getAlertIcon() }}</v-icon>
{{ alertMessage }}
<v-btn :dark="isDark" icon #click="toggleAlert(false)">
<v-icon>close</v-icon>
</v-btn>
</v-snackbar>
</v-app>
</template>
<script>
import { themeMixin } from "./mixins/themeMixin.js";
import Toolbar from "./components/Toolbar";
import { alertMixin } from "./mixins/alertMixin";
import { authMixin } from "./mixins/authMixin";
import { socketMixin } from "./mixins/socketMixin";
import { TokenService } from "./services/tokenService";
import { ThemeService } from "./services/themeService";
import { UserService } from "./services/userService";
import { cordMixin } from "./mixins/cordMixin";
export default {
name: "app",
mixins: [alertMixin, authMixin, cordMixin, themeMixin, socketMixin],
components: { Toolbar },
created() {
this.init();
const theme = ThemeService.getTheme();
if (theme !== null) {
this.$store.commit("theme", theme);
} else {
this.$store.commit("theme", this.isDark ? "dark" : "light");
}
},
data() {
return {
color: "#0c0c0c",
y: "top",
x: null,
mode: ""
};
},
methods: {
init() {
const token = TokenService.getToken();
const user = UserService.getUser();
if (token) {
this.$store.commit("token", token);
this.setExpiry();
}
if (user) {
this.$store.commit("user", JSON.parse(user));
}
}
},
mounted() {
this.init();
},
watch: {}
};
</script>
<style>
#import "https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css";
#import "https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons";
#app {
font-family: "Hilda-Regular", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
I can nest components within the root App.vue component just fine but if I try and nest a component within a non root component nothing shows up. If I instead nest the Navbar component that wont show up in Splash.vue within App.vue it works, likewise if I move the Footer component to Splash.vue it doesn't.
App.vue (Footer component works fine, router then loads splash.vue)
<template>
<div id="app">
<Footer/>
<router-view/>
</div>
</template>
<script>
import Footer from '#/components/Footer'
export default {
name: 'App',
components: {
Footer
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 0px;
}
</style>
Splash.vue (Navbar component doesnt load, the text does load so I know the router is working correctly)
<template>
<div class="test">
<v-container fluid>
<Navbar/>
<p>splash loaded</p>
</v-container>
</div>
</template>
<script>
import Navbar from '#/components/layout/Navbar'
export default {
name: 'Splash',
data () {
return {
components: {
Navbar
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
.landing-card-style {
border-radius: 4px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.14);
}
</style>
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
Vue.use(Vuetify)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
render: h => h(App)
})
Navbar.vue
<template>
<div class="navbar">
<nav class = "deep-purple">
<div class="container">
<h1>navbar component loaded</h1>
</div>
</nav>
</div>
</template>
<script>
export default {
name: 'Navbar',
data(){
return{
}
}
}
</script>
<style>
</style>
You have your components inside data() function.
Try this instead:
export default {
name: 'Splash',
components: {
Navbar
}
}