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>
Related
I've got the following problem, while reading in a File and reaching it to the Parent Component.
First I've got an FileInput on the FileReaderComponent, when File is Changed it sends an emit('change', file.content).
The top Component gets the file.content and sets it as the Input Prop of the FileViewerComponent.
As so far, it works as expected. But when I add a second FileReaderComponent, which Content should be displayed in the second FileViewerComponent.
But it always uses the #change from the first FileReaderComponent.
I'm very new to Vue. I build an minimal example to show:
Same behavior. When I use the Second FileReaderComponent it should put data into readerOutput2 but it puts data into readerOutput1 for some reason.
I can't figure out what I did wrong.
App.vue
<template>
<!-- <img alt="Vue logo" src="./assets/lpdLogo.svg" style="width: 100px; fill: green;"> -->
<FileReaderComponent #change="data.readerOutput1 = $event"/>
<FileViewerComponent v-model:input="data.readerOutput1"/>
<FileReaderComponent #change="data.readerOutput2 = $event"/>
<FileViewerComponent v-model:input="data.readerOutput2"/>
</template>
<script>
import FileReaderComponent from './components/FileReaderComponent.vue';
import FileViewerComponent from './components/FileViewerComponent.vue';
import { reactive } from 'vue';
export default {
name: 'App',
components: {
FileReaderComponent,
FileViewerComponent
},
setup () {
const data = reactive({
readerOutput1: '',
readerOutput2: ''
});
function log(toLog) {
console.log(toLog);
}
return { log, data }
}
}
</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: 60px;
}
</style>
FileReaderComponent.vue
<template>
<div id="fileUploadComponent" class="inline-flex items-center p-2 rounded border-2 border-yellow-500 m-1">
<input type='file' id="fileInput" name="fileInput" #change="fileUploadChange()" ref="fileInput" class="hidden"/>
<label for="fileInput" class="h-10 w-40 rounded text-gray-300 dark:text-gray-400 bg-gray-700 hover:bg-gray-500 text-xs" style="line-height: 2.5rem">
<span class="block text-center w-full">Datei öffnen</span></label>
<span id="filenamefield" class="inline-block m-2 text-gray-700 dark:text-gray-300">{{file.name}}</span>
</div>
</template>
<script>
import {reactive} from 'vue';
export default {
name: 'FileReaderComponent',
emits: ['inFocus', 'submit', 'change'],
props: {
input: {
type: String,
required: false,
},
},
methods : {
setContent(content) {
console.log('FileUpload, set Content', content)
this.file.content = content;
this.$emit('change', content);
},
fileUploadChange() {
console.log('fileUploadChange triggered');
this.file.input = this.$refs.fileInput.files[0];
this.file.name = this.file.input.name;
let file = this.file.input;
let parent = this;
parent.setContent('Content Loading')
function onloadevent(evt) {
parent.setContent(evt.target.result);
}
if (file) {
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (evt) {
onloadevent(evt);
}
reader.onerror = function () {
onloadevent('An Error occurred while reading File');
}
}
}
},
setup(props, {emit}) {
const file = reactive({
name: 'Keine Ausgewählt',
input: Element,
content: String,
})
return {file}
}
}
</script>
<style scoped>
.inputfile {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.inputfile+label {
font-weight: 700;
display: inline-block;
}
input:checked+svg {
display: block;
}
</style>
FileViewerComponent
<template>
<div id="fileViewerComponent" class="inline-flex items-center p-2 rounded border-2 border-yellow-500 m-1">
<h3>Input:</h3>
<div id="view">
{{ input }}
</div>
</div>
</template>
<script>
import {reactive} from 'vue';
export default {
name: 'FileViewerComponent',
emits: ['inFocus', 'submit', 'change'],
props: {
input: {
type: String,
required: false,
},
},
methods : {
},
setup(props, {emit}) {
const file = reactive({
name: 'Keine Ausgewählt',
input: Element,
content: String,
})
return {file}
}
}
</script>
<style scoped>
</style>
Finally i got an Solution.
The Problem was caused by the FileInput label pointing to id="fileInput" wich is the input. But when the input is 2 times rendered, the label accesses the first found id="fileInput".
My Solution:
FileUploadComponent.vue
I had to reach an ID to the Component an use it for the Input ID and the label for
<template>
<div id="fileUploadComponent" class="inline-flex items-center p-2 rounded border-2 border-yellow-500 m-1">
<input type='file' v-bind:id="id" name="fileInput" #change="fileUploadChange()" ref="fileInput" class="hidden"/>
<label v-bind:for="id" class="h-10 w-40 rounded text-gray-300 dark:text-gray-400 bg-gray-700 hover:bg-gray-500 text-xs" style="line-height: 2.5rem">
<span class="block text-center w-full">Datei öffnen</span></label>
<span id="filenamefield" class="inline-block m-2 text-gray-700 dark:text-gray-300">{{file.name}}</span>
</div>
</template>
<script>
import {reactive} from 'vue';
export default {
name: 'FileReaderComponent',
emits: ['inFocus', 'submit', 'change'],
props: {
input: {
type: String,
required: false,
},
id : {
type: Number,
required: false,
}
},
methods : {
setContent(content) {
console.log('FileUpload, set Content', content)
this.file.content = content;
this.$emit('change', content);
},
fileUploadChange() {
console.log('fileUploadChange triggered');
this.file.input = this.$refs.fileInput.files[0];
this.file.name = this.file.input.name;
let file = this.file.input;
let parent = this;
parent.setContent('Content Loading')
function onloadevent(evt) {
parent.setContent(evt.target.result);
}
if (file) {
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = function (evt) {
onloadevent(evt);
}
reader.onerror = function () {
onloadevent('An Error occurred while reading File');
}
}
}
},
setup(props, {emit}) {
const file = reactive({
name: 'Keine Ausgewählt',
input: Element,
content: String,
})
return {file}
}
}
</script>
<style scoped>
.inputfile {
width: 0.1px;
height: 0.1px;
opacity: 0;
overflow: hidden;
position: absolute;
z-index: -1;
}
.inputfile+label {
font-weight: 700;
display: inline-block;
}
input:checked+svg {
display: block;
}
</style>
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 ?
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
}
}
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');
}