this.$refs.canvas undefined in vuejs - vue.js

I'm setting up a new project using vuejs3 and canvas, but I'm trapped in canvas element. Could anyone give me some advice or ideas?
In my project I got undefined with this.$refs.canvas. Also when I use document.getElementById('canvas'), I get null.
<template>
<div id="app">
<canvas
ref="canvas"
id="canvas"
class="bg"
width="400"
height="400"></canvas>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
}
},
created() {
// const canvas = this.$refs.canvas.getContext('2d')
const canvas = document.getElementById('canvas')
console.log(canvas)
},
}
</script>
It will be appreciate if anyone can give me some help.

The elements won't yet be rendered in created. Try mounted.
<template>
<div id="app">
<canvas
ref="canvas"
id="canvas"
class="bg"
width="400"
height="400"></canvas>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
}
},
mounted() {
const canvas = this.$refs.canvas.getContext('2d')
console.log(canvas)
},
}
</script>
View the Lifecycle Diagram here: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram

Related

if-emoji lookup table with Vue

I'm using the npm module if-emoji to detect whether a user can view emojis or not. There is a similar tool in Modernizr.
If the user can't view emojis, I'm displaying an image of the emoji instead. So my Vue HTML looks like this:
<h2 v-if="this.emojis">πŸ˜„</h2>
<h2 v-if="!this.emojis"><img src="https://example.com/emoji.png">/h2>
Does this still download the image for users who can view emojis, therefore using bandwidth unecessarily?
And is there a more efficient way of doing this, so that I don't need to go and add v-if every time I use an emoji? Can there be some sort of lookup table, if there's an emoji and !this.emojis?
You can solve this also by creating your own vue.component
<emojiorimage usesmily="false" smily="πŸ˜„" imgpath="https://i.stack.imgur.com/QdqVi.png"></emojiorimage>
that capsulates the decision if image or smily inside itself.
var app = new Vue({
el: "#app",
data: function(){
return {
extendedCost: 0,
}
},
components: { "emojiorimage" : {
template : `
<h2>
usesmily is {{usesmily}}<br/>
<div v-if="usesmily === 'true'">{{ smily }}</div>
<div v-else><img :scr="imgpath" width="50" height="50" :alt="imgpath" /></div>
</h2>`,
props: ["usesmily", "smily", "imgpath"],
}}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Emoji or Image</h1>
<emojiorimage
usesmily="false"
smily="πŸ˜„"
imgpath="https://i.stack.imgur.com/QdqVi.png"
></emojiorimage>
<emojiorimage
usesmily="true"
smily="😎"
imgpath="https://i.stack.imgur.com/QdqVi.png"
></emojiorimage>
</div>
You can then feed it the isemoji from your npm package that you query once and store somewhere.
For seperate vue files it would look kind of like this:
emojiorimage.vue
<template>
<h2>
<div v-if="usesmily === 'true'">{{ smily }}</div>
<div v-else><img :scr="imgpath" width="50" height="50"
:alt="imgpath" /></div>
</h2>
</template>
<script>
export default {
props: ["usesmily", "smily", "imgpath"],
};
</script>
App.vue
<template>
<div id="app">
<h1>Emoji or Image</h1>
<emojiorimage
usesmily="false"
smily="πŸ˜„"
imgpath="https://i.stack.imgur.com/QdqVi.png"
/>
<emojiorimage
usesmily="true"
smily="😎"
imgpath="https://i.stack.imgur.com/QdqVi.png"
/>
</div>
</template>
<script>
import emojiorimage from "./components/emojiorimage.vue";
export default {
components: {
emojiorimage,
},
};
</script>
index.html
<div id="app"></div>
index.js
import Vue from "vue";
import App from "./App";
Vue.config.productionTip = false;
new Vue({
el: "#app",
template: "<App/>",
components: { App }
});
to get:
Learning resources:
https://v2.vuejs.org/v2/guide/components.html
https://v2.vuejs.org/v2/guide/single-file-components.html
This should work as well:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<h2 v-if="true">πŸ˜„</h2>
<h2 v-else><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/SNice.svg/1200px-SNice.svg.png" width=15 height=15></h2>
<h2 v-if="false">πŸ˜„</h2>
<h2 v-else><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/SNice.svg/1200px-SNice.svg.png" width=15 height=15></h2>
The v-if part only is part of the sourcecode if the statement is true - else the else statement is in instead. If the img tag is not part of the loaded source the image wont be loaded.
<h2 v-if="emojis">πŸ˜„</h2>
<h2 v-else><img src="https://example.com/emoji.png"></h2>
Is probably the only way to improve your code as #Patrick Artner pointed out.
You do not need this. in the template
To the question if the image is loaded if it is not shown the simple answer is no. Vue only renders v-ifs when they are needed and does not render it - the image is loaded only if !emojis.
Thanks to #PatrickArtner who posted the idea of using a Vue component to render the emoji / image. Here is my final solution, which resizes the emoji / image to fit the surrounding text β€” it uses fitty to resize the emojis, and sets the images to height:1em;.
Emoji.vue
<template>
<div class="emojiContainer">
<span id="fittyEmoji" v-if="emojis">{{ insert }}</span>
<img v-if="!emojis" :src="insert" class="emojiImage">
</div>
</template>
<style scoped>
.emojiContainer {
display: inline;
}
.emojiImage {
height:1em;
}
</style>
<script src="fitty.min.js"></script>
<script>
import fitty from 'fitty';
import ifEmoji from 'if-emoji'
export default {
name: 'Emoji',
props: ['name'],
data: function() {
return {
insert: "",
emojis: false,
}
},
methods: {
insertEmoji(){
var names = ['frog', 'fire']
var emojis = ['🐸','πŸ”₯']
var urls = ['https://example.com/frog.png',
'https://example.com/fire.png']
if (this.emojis) {
this.insert = emojis[names.indexOf(this.name)];
} else {
this.insert = urls[names.indexOf(this.name)];
}
fitty('#fittyEmoji')
}
},
beforeMount() {
if (ifEmoji('🐸')) {
this.emojis = true;
} else {
this.emojis = false;
}
this.insertEmoji();
}
}
</script>
Then in your parent components you can just insert like this:
<template>
<div id="app">
<h1><Emoji name='frog'/> Frog Facts</h1>
<p>Lorem ipsum <Emoji name='fire'/><p>
</div>
</template>
<script>
import Emoji from '#/components/Emoji.vue'
export default {
components: {
Emoji,
},
};
</script>

Vue.js Passing a variable in a loop to another component

As a beginner in Vue.js,I am trying to create a Todo app, but problems seems to passing a variable to another component when looping.
i want to pass item to another embedded component.
Here what I have with all the components:
in main.js:
import Vue from "vue";
import App from "./App.vue";
import Todo from "./components/Todo";
import itemComponent from "./components/itemComponent";
Vue.config.productionTip = false;
Vue.component('todo', Todo);
Vue.component('itemcomponent', itemComponent);
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
export default {
name: "App",
components: {
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="item.id">
<itemcomponent {{item}}></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
components: {
itemcomponent,
},
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
};
</script>
in itemComponent.vue:
<template>
<div>
<input type="text" value={{item }}/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>
what is my mistake? thanks for help.
Quite a bunch of mistakes:
You should import single page components inside their parent components and register them there, not in main.js file. EDIT: Explaination to this is given in docs
Global registration often isn’t ideal. For example, if you’re using a build system like Webpack, globally registering all components means that even if you stop using a component, it could still be included in your final build. This unnecessarily increases the amount of JavaScript your users have to download.
You have a component registration in Todo.vue, but you have misplaced it inside data so its is just a data object that is not getting used, move into components.
In your loop you have item.id, but your item is just a plain string, it does not have an id property. Either change item to object with id property, or simply use the index provided in loop (not recommended).
Pass your item as a prop, not mustache template. So in Todo.vue replace {{ item }} with :item="item"
In your ItemComponent.vue you have mustache syntax once again in the attribute. Change value={{item }} to :value="item"
So here's the final code:
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
in App.vue:
<template>
<div id="app">
<todo></todo>
<img alt="Vue logo" src="./assets/logo.png" width="25%" />
<!-- <HelloWorld msg="Hello Vue in CodeSandbox!" /> -->
</div>
</template>
<script>
import Todo from './components/Todo.vue';
export default {
name: "App",
components: {
Todo,
},
};
</script>
in Todo.vue:
<template>
<div>
<input type="text" v-model="nameme" />
<button type="click" #click="assignName">Submit</button>
<div v-for="(item, index) in result" :key="index">
<itemcomponent :item="item"></itemcomponent>
</div>
</div>
</template>
<script>
import { itemcomponent } from "./itemComponent";
export default {
props:['item'],
data() {
return {
nameme: "",
upernameme: "",
result: [],
};
},
methods: {
assignName: function () {
this.upernameme = this.nameme.toUpperCase();
this.result.push(this.nameme);
this.nameme = "";
},
},
components: {
itemComponent,
}
};
</script>
itemComponent.vue:
<template>
<div>
<input type="text" :value="item"/>
</div>
</template>
<script>
export default {
props: {
item: String,
},
data() {
return {};
},
};
</script>

How can I emit from component and listen from another one?

I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>

Vue.js component is not showing visually on the page

I have the following Vue component called TeamCard:
<template>
<div class="m-8 max-w-sm rounded overflow-hidden shadow-lg">
<!-- removed fro brevety -->
div class="text-gray-900 font-bold text-xl mb-2">{{ name }}</div>
<p class="text-gray-700 text-base"> {{ description }} </p>
<!-- removed fro brevety -->
</div>
</div>
</template>
<script>
export default {
name: "TeamCard",
props: {
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
}
};
</script>
<style scoped>
#import "https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css";
</style>
I call the component in an HTML page (Spring boot template) the following way:
<teamCard v-bind:name="'Hawks'" v-bind:description="'Best team'" ></teamCard>
<script src="https://unpkg.com/vue"></script>
<script th:src="#{/TeamCard/TeamCard.umd.min.js}"></script>
<script>
(function() {
new Vue({
components: {
teamCard: TeamCard
}
})
})();
</script>
When I go to the page in the browser that has the second snippet, nothing is shown. There are no errors in the console. What am I doing wrong? How can I make the component be shown?
I've never seen Vue used this way but it looks like there is no mounting element for the app. Try this:
<div id="app">
<team-card v-bind:name="'Hawks'" v-bind:description="'Best team'"></team-card>
</div>
and
(function() {
new Vue({
el: '#app',
components: {
teamCard: TeamCard
}
})
})();

NuxtJS & Bootstrap Vue - Best way to change image from Card Component

Wanted outcome : create a card component that I can use through several different pages. I want to define directly on each page the top image that I want to use within the component.
The Card Component :
<template>
<div>
<b-card img-alt="Card image" img-top>
<b-card-img :src="imageUrl" alt="Image" bottom></b-card-img>
<b-card-text>
Some quick example text to build on the card and make up the bulk of the
card's content.
</b-card-text>
</b-card>
</div>
</template>
<script>
export default {
props: {
imageUrl: String,
required: true
}
};
</script>
The index page :
<template>
<div>
<Card ></Card>
</div>
<
</template>
<script>
import Card from "~/components/Card.vue";
export default {
components: {
Card
},
data: function() {
return {
imageUrl: require("../assets/imgs/logo.png")
};
},
};
</script>
<style scoped>
}
</style>
Can you tell me what's wrong? If I create an about-us page reusing the card component, I would like to change imageUrl prop.
You have the imageUrl prop in your Card component, but aren't using it on your index page. You should be able to do <Card :image-url="imageUrl"> on your index page.
One thing that is wrong however, is how you define your prop in your component.
Change it to this:
props: {
imageUrl: {
type: String,
required: true
}
}
Vue.component('card', {
el: '#card-template',
props: {
imageUrl: {
type: String,
required: true
}
}
})
new Vue({
el: '#app',
data() {
return {
imageUrl: 'https://via.placeholder.com/1920x720'
}
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.13.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.13.0/dist/bootstrap-vue.js"></script>
<div id="app">
<card :image-url="imageUrl"></card>
</div>
<template id="card-template">
<b-card img-alt="Card image" img-top>
<b-card-img :src="imageUrl" alt="Image" bottom></b-card-img>
<b-card-text>
Some quick example text to build on the card and make up the bulk of the
card's content.
</b-card-text>
</b-card>
</template>