vue set img height based on width with dynamic styling - vue.js

I have an image that should have 50% height of its width.
<img :src="post.image" ref="image" :style="{ height: imageHeight + 'px' }" />
imageHeight() {
let image = this.$refs.image
if(!image) return 0
let height = image.clientWidth * 0.5
return height
}
Unfortunately image is undefined during the evaluation of imageHeight and it does not get reevaluated when the width changes. Is there some way to make it work with a watcher or some other way?

You can use the load event to set a variable. It looks like you're using a computed, but there's no data change for it to respond to.
new Vue({
el: '#app',
data: {
url: 'http://via.placeholder.com/200x200',
imageHeight: null
},
methods: {
setheight(event) {
let image = event.target;
this.imageHeight = image.clientWidth * 0.5;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<img :src="url" #load="setheight" :style="{ height: imageHeight + 'px' }">
<div>{{imageHeight}}</div>
</div>

You could also do
<div :style={ height: myComputedHeight + '%' }></div>
data() {
return {
myCount: 10,
myTotal: 100
};
},
computed: {
myComputedHeight() {
return Math.round((this.myCount / this.myTotal) * 100);
}
}

I had to find a solution to something similar making a square div.
new Vue({
el: "#app",
data: {
initialWidth: 100,
matchedWidth: null
},
mounted() {
this.matchWidth();
},
methods: {
matchWidth() {
this.matchedWidth = this.$refs.box.offsetWidth;
}
},
computed: {
igCardStyle() {
return {
width: `${this.initialWidth}%`,
height: `${this.matchedWidth}px`
};
}
}
});
.box-wrapper {
width: 200px;
}
.box {
background-color: red;
/* border: 1px solid black; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" ref="app">
<div class="box-wrapper">
<div class="box" :style="igCardStyle" ref="box">
</div>
</div>
<hr>
<p>width: {{initialWidth}}% of .box-wrapper or {{matchedWidth}}px</p>
<p>height: {{matchedWidth}}px</p>
</div>
In this cas you have to watch out for borders present in your $refs.box that could vary the result.
If you need the height to be the half of the width try:
calc(${this.matchedWidth} * 0.5)px on the computed style property.
Hope it helps! BR

Related

How to make a proper pagination with Vuejs? Setter not defined error

Working on the pageNav for a personal app I am working on and cannot get the index to show properly.
I figure that making the pageIndexInner and itemsPerPageInner computed propetries was the best route, but when it comes to editing those, I need to also have a setter? I've looked into getters and setters, but am having a very hard time wrapping my head around it.
Without the computer properties, the click event works and I can make it all the way to the itemToal amount, but the index doesn't match up.
If you change the default pageIndex to 3,
I want to see:
but this is what I'm actually seeing:
I'm just not sure where to go with all of this and any guidance would be greatly appreciated. Thank you
Codepen Link:https://codepen.io/LovelyAndy/pen/NWbjLGz?editors=1010
Vue Component code:
<template>
<div class="_table-page-nav-wrapper">
<div #click="back" :disabled="pageIndexInner === 0" class="_arrow-btn">
<
</div>
<div class="_page-index-inner">
{{ itemsTotal }} Total Items {{ pageIndexInnerStart}} - {{ itemsPerPageInnerStart }} Shown
</div>
<div #click="forward" class="_arrow-btn">
>
</div>
</div>
</template>
<style lang="sass" scoped>
._table-page-nav-wrapper
display: flex
justify-content: center
align-items: center
div
display: flex
justify-content: center
align-items: center
._arrow-btn
width: 50px
height: 50px
border-radius: 4px
box-shadow: 0 5px 5px rgba(0,0,0,0.2)
._page-index-inner
width: 244px
height: 50px
border-radius: 4px
box-shadow: 0 5px 5px rgba(0,0,0,0.2)
margin: 0px 20px
</style>
<script>
export default {
name: 'TablePageNavigation',
props: {
/**
* passed values can be either 10 or 25 or 50
*/
itemsPerPage: {
type: Number,
default: 10,
validator: (prop) => [10, 25, 50].includes(prop),
},
pageIndex: {
type: Number,
default: 0,
},
itemsTotal: {
type: Number,
default: 100,
},
},
data() {
return {
pageIndexInner: this.pageIndex,
itemsPerPageInner: this.itemsPerPage,
}
},
computed: {
pageIndexInnerStart() {
return this.pageIndex + this.itemsPerPage
},
itemsPerPageInnerStart() {
return this.itemsPerPage + this.itemsPerPage
},
},
methods: {
back() {
if (this.itemsPerPageInner > this.itemsPerPage) {
this.itemsPerPageInner = this.itemsPerPageInner - this.itemsPerPage
this.pageIndexInner = this.pageIndexInner - this.itemsPerPage
const newIndex = this.pageIndexInner
this.$emit('update:pageIndex', newIndex)
}
return
},
forward() {
if (
this.itemsPerPageInnerStart + this.itemsPerPage > this.itemsTotal ||
this.PageIndexInnerStart + this.itemsPerPage > this.itemsTotal
) {
return
}
this.pageIndexInnerStart = this.pageIndexInnerStart + this.itemsPerPage
this.itemsPerPageInnerStart = this.itemsPerPageInnerStart + this.itemsPerPage
},
},
}
</script>
I commented on your related question earlier this morning, and decided to create an example based on my previous pagination implementation that I mentioned. I removed a lot of your calculations for a simpler approach. I didn't handle all scenarios such as if total items is not a multiple of items per page, but if you like what I did you can work that out on your own. Here is the code from my single file component that I developed in my Vue sandbox app, which uses Bootstrap 4.
<template>
<div class="table-page-navigation">
<button class="btn btn-primary" #click="back" >Back</button>
<span>
{{ itemsTotal }} Total Items {{ pageFirstItem}} - {{ pageLastItem }} Shown
</span>
<button class="btn btn-secondary" #click="forward" >Forward</button>
</div>
</template>
<script>
export default {
name: 'TablePageNavigation',
props: {
/**
* passed values can be either 10 or 25 or 50
*/
itemsPerPage: {
type: Number,
default: 10,
validator: (prop) => [10, 25, 50].includes(prop),
},
itemsTotal: {
type: Number,
default: 100,
},
},
data() {
return {
currentPage: 1,
}
},
computed: {
numPages() {
return this.itemsTotal / this.itemsPerPage;
},
pageFirstItem() {
return (this.currentPage - 1) * this.itemsPerPage + 1;
},
pageLastItem() {
return this.currentPage * this.itemsPerPage;
}
},
methods: {
back() {
if (this.currentPage > 1) {
this.currentPage--;
}
},
forward() {
if (this.currentPage < this.numPages) {
this.currentPage++;
}
},
},
}
</script>
Vuetify
Vuetify pagination Component
This might help if you're comfortable using a UI library.
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app>
<v-main>
<div class="text-center">
<v-pagination
v-model="page"
:length="6"
></v-pagination>
</div>
</v-main>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
page: 1,
}
},
})
</script>
</body>
</html>

canvas size is different when drawing canvas size

Could you tell me how to fix this issue: canvas has 1200x700px, but the drawing is scaled to 300x150?
<template>
<div>
<input type="text" v-model="msg"></input>
<br>
<canvas v-on:mousemove="mouse" id="c"></canvas>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
msg: 'Welcome to Your Vue.js App',
vueCanvas: null,
pixel: null
}
},
methods: {
mouse: function (event) {
this.vueCanvas.putImageData(this.pixel, event.offsetX, event.offsetY)
this.msg = event.offsetX + ":" + event.offsetY
},
init: function () {
this.vueCanvas = document.getElementById("c").getContext("2d");
this.pixel = this.vueCanvas.createImageData(1, 1);
this.pixel.data[3] = 255;
}
},
mounted() {
this.init()
}
}
</script>
<style scoped>
#c {
height: 700px;
width: 1200px;
border: 1px solid gray;
}
</style>
Snippet:
new Vue({
el: "#app",
data() {
return {
msg: 'Welcome to Your Vue.js App',
vueCanvas: null,
pixel: null,
};
},
methods: {
mouse: function(event) {
this.vueCanvas.putImageData(this.pixel, event.offsetX, event.offsetY);
this.msg = event.offsetX + ':' + event.offsetY;
},
init: function() {
this.vueCanvas = document.getElementById('c').getContext('2d');
this.pixel = this.vueCanvas.createImageData(1, 1);
this.pixel.data[3] = 255;
},
},
mounted() {
this.init();
},
})
#c {
height: 700px;
width: 1200px;
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="msg" />
<br />
<canvas v-on:mousemove="mouse" id="c"></canvas>
</div>
I have recorded a video in order to show what goes wrong.
I find solution:
this.vueCanvas.canvas.width=this.vueCanvas.canvas.clientWidth //300->1200
this.vueCanvas.canvas.height=this.vueCanvas.canvas.clientHeight //150->700
but I am not sure if is it good practice.
This is how I meant in my comment:
new Vue({
el: "#app",
data() {
return {
msg: 'Welcome to Your Vue.js App',
vueCanvas: null,
pixel: null,
};
},
methods: {
mouse: function(event) {
this.vueCanvas.putImageData(this.pixel, event.offsetX, event.offsetY);
this.msg = event.offsetX + ':' + event.offsetY;
},
init: function() {
this.vueCanvas = document.getElementById('c').getContext('2d');
this.pixel = this.vueCanvas.createImageData(1, 1);
this.pixel.data[3] = 255;
},
},
mounted() {
this.init();
},
})
#c {
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="msg" />
<br />
<canvas v-on:mousemove="mouse" id="c" width="1200" height="700"></canvas>
</div>
Is this not how you want it?

Can I execute a method when an input box gets to a certain length?

I have an input box that takes a string. Can I execute a method (in vue.js) when the length of the string gets to a certain number?
something like
<input v-if="inputBox.length == 6 THEN runme()"...>
You can use watch option, you'll be able to react to data changes :
new Vue({
el: '#root',
data: {
message: '',
inputLength : undefined
},
methods : {
doSomething(){
console.log('I did it !')
}
},
watch :{
message : function(val) {
if(val.length>=5){
this.inputLength = val.length
this.doSomething();
}
}
}
})
.container {
padding-top: 2em;
}
.intro {
font-size: 1.5em;
margin-bottom: 1.5em;
}
.input-value {
margin-top: 1em;
font-size: 1.25em;
}
.highlight {
color: #00d1b2;
font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div class="container">
<h1 class="intro">Binding with Vue</h1>
<div id='root' class="box">
<label class="label">Enter text here</label>
<input class="input is-medium" type='text' id='input' v-model='message'>
<p class="input-value">The value of the input is: <span class="highlight">{{ inputLength }}</span></p>
</div>
</div>
In this example, if input length is >= 5 then it will change the inputLenght value in data and execute a method.
For more informations about this, go see :
https://v2.vuejs.org/v2/guide/computed.html#Watchers
You can use a watcher to trigger a method when the string exceeds the length:
new Vue({
data () {
return {
model: ''
}
},
watch: {
model: {
handler: function (value) {
if (value.length >= 6) {
this.trigger()
}
}
}
},
el: '#app',
methods: {
trigger () {
alert('hi there')
}
},
template: `<input v-model="model">`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Vue: Change class with the value of a variable in setInterval

I am learning Vue JS. I want to change class using setInterval. But can’t pass the changing value of Method to Computed Property. After two seconds class will change automatically with the changed value of "changeColor"
My Code:
HTML:
<div>
<button #click="startEffect">Start Effect</button>
<div id="effect" :class="myClass"></div>
</div>
CSS:
.highlight {
background-color: red;
width: 200px !important;
}
.shrink {
background-color: gray;
width: 50px !important;
}
Vue JS:
new Vue({
el: '#exercise',
data: {
changeColor: false
},
methods : {
startEffect: function() {
setInterval(function(){
this.changeColor = !this.changeColor;
//alert(this.changeColor);
}, 2000);
//alert(this.changeColor);
}
},
computed: {
myClass: function() {
return {
highlight: this.changeColor,
shrink: !this.changeColor
}
}
}
})
bind your function to the component...
setInterval(function(){
this.changeColor = !this.changeColor;
}.bind(this), 2000);
and then you can do ...
<div id="effect" :class="[changeColor?'highlight':'shrink']"></div>

Vuejs: autofit div according to remaining space in window

Within vueJs (2.5.16) I'm trying to set the style property of a div so it auto fit the remaining space of a window depending of its size:
I intended to use a computed value which would give me live the correct height and bind it into the style property of the wanted div:
Vue.component('menus', {
template: '<div id="menu-div">MY MENU</div>'
});
Vue.component('expandable', {
template: '<div id="expandable-div" :style="{height:expandableHeight}">EXPANDABLE<div>{{expandableHeight}}</div></div>',
computed: {
expandableHeight() {
return ($(window).height() - $("#expandable-div").position().top) + 'px'
}
}
});
var vm = new Vue({
el: '#app'
});
fiddle
Using $(window).height() and $("#expandable-div").position().top from jQuery I thought I could achieve a result since it works in my console.
Unfortunately I have an error:
TypeError: Cannot read property 'top' of undefined
Why not use CSS's flexbox to achieve that?
Vue.component('menus', {
template: '<div id="menu-div">MY MENU</div>'
});
Vue.component('expandable', {
template: '<div id="expandable-div">EXPANDABLE<div>foo</div></div>',
computed: {}
});
var vm = new Vue({
el: '#app'
});
body {
margin: 0;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
}
#menu-div {
height: 50px;
background: #768ffb;
margin-bottom: 5px;
}
#expandable-div {
background: #76fbc1;
flex-grow: 1;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<menus></menus>
<expandable></expandable>
</div>