Thanks for browsing!
I am creating a slider with vue-image-lightbox.
However, now that I have a static src listed in the sample and the image is a slider, I would like to change this src to a dynamic one and display the image accordingly.
So I wrote the code as shown below, but the img src is set to undefind and the image is not displayed.
I thought I stored /uploads/1 here, but it is undefind.
So I'm hoping you can tell me how to store /uploads/1 in src and thumb.
I would like to ask for your wisdom.
<template>
<div>
<figure class="mr-3">
<a class="img-box" #click.prevent.stop="show(0)">
<img
:src="post.main_image_url"
:alt="post.main_image_url"
>
</a>
</figure>
<ImageLightBox :media="media" ref="lightbox" :show-light-box="false" :show-caption="true"></ImageLightBox>
</div>
</template>
<script>
import ImageLightBox from 'vue-image-lightbox';
require("vue-image-lightbox/dist/vue-image-lightbox.min.css");
export default {
name: 'lightbox',
components: {
ImageLightBox,
},
data() {
return {
post: {},
post_main_image_url: '',
media: [
{
// src: "/uploads/1" will work.
thumb: this.post_main_image_url,
src: this.post_main_image_url,
title: '朝日が昇る摩周湖(北海道川上郡弟子屈町)',
},
],
}
},
methods: {
show: function(index){
this.$refs.lightbox.showImage(index)
},
// data acquisition process
getData() {
this.$loading.load(
this.$auth.api.get(
'posts/' + this.post_id, {
params: {}
}
).then(res => {
this.post = res.data.post;
let mainImageUrl = res.data.post.main_image_url;
console.log(mainImageUrl)
// =>/uploads/1
let stringMainImageUrl = String(mainImageUrl)
console.log(stringMainImageUrl)
// =>/uploads/1
this.post_main_image_url = stringMainImageUrl
console.log(String(this.post_main_image_url))
// =>/uploads/1
}).catch(err => {
this.$errorHandlers.initial(err);
})
)
}
}
created() {
this.getData();
},
}
</script>
Hey just looked at your code, here are a couple of things:
// I couldn't find `show` method, is it declared?
<a class="img-box" #click.prevent.stop="show(0)">
<!-- You are initializing 'post' as empty string and I don't see a mounted hook
for initializing this property 'main_image_url' -->
<img :src="post.main_image_url" :alt="post.main_image_url" />
data() {
return {
post: {},
post_main_image_url: '',
media: [
{
/* When the component is mounted both properties will get initialized with an empty string.
You might need to initialize properties within the "mounted" hook. */
thumb: this.post_main_image_url,
src: this.post_main_image_url,
// I don't see this method being called anywhere in the code
getData() {
this.$loading.load(
this.$auth.api
.get('posts/' + this.post_id, {
params: {}
})
I have a page in my Vue application which does an animation on route enter. However because I embedded a Vimeo video in this page, the frames drop from 60 to 20fps during the animation. I think it's because the request to the Vimeo server has to be made and is not finished when the animation starts.
I would like to preload the embedded Vimeo video on the first site load or somehow wait until the request has finished and then start the animation. How do I do that?
The video is embedded like this in the parent component and the animation is in the projecttop child component:
<div>
<projecttop></projecttop>
<div class="vimeo-container">
<iframe src="https://player.vimeo.com/video/397648215" frameborder="0" allow="fullscreen"allowfullscreen></iframe>
</div>
</div>
export default {
name: 'parentcomponent',
components: {
projecttop
}
}
This is the child component with the animation:
<script>
import { gsap } from "gsap";
export default {
name: "projecttop",
methods: {
slideIn() {
var tl = gsap.timeline()
tl.from('#project-title', {
delay: 0.5,
duration: 0.8,
y: 100,
opacity: 0,
ease: "circ.out",
})
}
},
mounted () {
if(document.readyState === "complete") {
this.slideIn()
}
}
};
</script>
You could try something like this in your parent component:
<projecttop :start-animation="videoLoaded"></projecttop>
<div class="vimeo-container">
<iframe
src="https://player.vimeo.com/video/397648215"
frameborder="0"
allow="fullscreen"
allowfullscreen
#load="videoLoaded = true
>
</iframe>
</div>
...
data() {
return {
videoLoaded: false,
}
},
Then, in your child component, pass in the value of videoLoaded and add a watcher to monitor when this is updated to true:
props: {
startAnimation: {
type: Boolean,
required: true
}
},
watch: {
startAnimation(val) {
if (val) this.slideIn();
}
}
How should I bind to an iframe src with something received back via the YouTube data api?
Can I set a string inside :src and concatenate onto the end of it?
/embed/'this.param.videoId'
The response returns an Id {{ video.snippet.resourceId.videoId }}, but the url contains other parts src=“https://www.youtube.com/embed/xxx”
Any help greatly appreciated!
Thanks
This worked:
:src="ytEmbedUrl + video.snippet.resourceId.videoId"
iframe resolving only when there's a videoId:
<iframe v-if="videoId" :src="mySrc"></iframe>
computed property:
computed: {
mySrc: {
get: function() {
//concat using template literal
return `https://www.youtube.com/embed/${this.videoId}`
}
}
}
data property:
{
videoId: false
}
Your method to assign to it:
methods: {
getMyVideo() {
video = // get my video code
this.videoId = video.snippet.resourceId.videoId
}
}
Or just inline it if you're super lazy:
<iframe src="`https://www.youtube.com/embed/${videoId}`"></iframe>
Here is what is working for me with Bootstrap V5 css
<template>
<section class="video-player-container">
<div class="video-player-row">
<div
class="video-player ratio"
:class="`ratio-${group.videoAspectRatio}`"
>
<iframe
loading="lazy"
:src="`https://www.youtube.com/embed/${getIdFromURL(group.videoEmbedLink)}?rel=0`"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
/>
</div>
</div>
</section>
</template>
<script>
export default {
name: 'VideoPlayer',
props: {
group: {
type: Object,
required: true
}
},
methods: {
getIdFromURL (url) {
const youtubeRegexp = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\S*[^\w\s-])([\w-]{11})(?=[^\w-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig
let id = url.replace(youtubeRegexp, '$1')
if (id.includes(';')) {
const pieces = id.split(';')
if (pieces[1].includes('%')) {
const uriComponent = decodeURIComponent(pieces[1])
id = `http://youtube.com${uriComponent}`.replace(youtubeRegexp, '$1')
} else {
id = pieces[0]
}
} else if (id.includes('#')) {
id = id.split('#')[0]
}
return id
}
}
}
</script>
<style scoped lang="scss">
.video-player {
&-container {
#include make-container();
#include default-max-widths();
}
&-row {
#include make-row();
}
}
</style>
I have an area where people can upload their own user-image. But if they do not, I would like to display a default one.
After some googling, I found I can do so by doing something like -
<img :src="creatorImage" #error="defaultAvatar">
But, I am not sure how to then created a method to pass the correct (default) image into it.
I did it with a computed property, like this:
<template>
<img :src="creatorImage" #error="imageError = true"/>
</template>
<script>
...
data() {
return {
imageError: false,
defaultImage: require("#/assets/imgs/default.jpg")
};
},
computed: {
creatorImage() {
return this.imageError ? this.defaultImage : "creator-image.jpg";
}
}
...
</script>
I would suggest creating a component for this, as it sounds like something that will be used on more places.
JsFiddle example
Component
Vue.component('img-with-default', {
props: ['defaultImg'],
data: function () {
return {
defaultAvatar: this.defaultImg || 'https://cdn0.iconfinder.com/data/icons/crime-protection-people/110/Ninja-128.png'
}
},
computed:{
userImage: function() {
if(this.uploadedImg != null) {
return this.uploadedImg;
} else {
return this.defaultAvatar;
}
}
},
template: '<img :src="userImage">'
})
And using the commponent would be
HTML
<div id="editor">
<img-with-default></img-with-default>
<img-with-default default-img="https://cdn3.iconfinder.com/data/icons/avatars-15/64/_Ninja-2-128.png" ></img-with-default>
</div>
JS
new Vue({
el: '#editor'
})
With this you have the default image.
If you want you can create a component that would display passed img src or the default one.
Component
Vue.component('img-with-default', {
props: ['imgSrc'],
data: function () {
return {
imageSource: this.imgSrc || 'https://cdn0.iconfinder.com/data/icons/crime-protection-people/110/Ninja-128.png'
}
},
template: '<img :src="imageSource">'
})
and to use it
HTML
<div id="editor">
<img-with-default></img-with-default>
<img-with-default img-src="https://cdn3.iconfinder.com/data/icons/avatars-15/64/_Ninja-2-128.png" ></img-with-default>
</div>
<textarea name="" id="" cols="30" rows="10" v-model="$store.state.user.giftMessage | truncate 150"></textarea>
I tried creating a custom filter :
filters: {
truncate(text, stop, clamp) {
return text.slice(0, stop) + (stop < text.length ? clamp || '...' : '')
}
}
but that didn't broke the build when I put it on the v-model for the input...
Any advice?
This is one of those cases, where you really want to use a component.
Here is an example component that renders a textarea and limits the amount of text.
Please note: this is not a production ready, handle all the corner cases component. It is intended as an example.
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
This component implements v-model and only emits a change to the data if the length of the text is less than the specified max. It does this by listening to keydown and preventing the default action (typing a character) if the length of the text is equal to or more than the allowed max.
console.clear()
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
new Vue({
el: "#app",
data:{
text: ""
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<limited-textarea v-model="text"
:max="10"
cols="30"
rows="10">
</limited-textarea>
</div>
Another issue with the code in the question is Vuex will not allow you set a state value directly; you have to do it through a mutation. That said, there should be a Vuex mutation that accepts the new value and sets it, and the code should commit the mutation.
mutations: {
setGiftMessage(state, message) {
state.user.giftMessage = message
}
}
And in your Vue:
computed:{
giftMessage:{
get(){return this.$store.state.user.giftMessage},
set(v) {this.$store.commit("setGiftMessage", v)}
}
}
Technically the code should be using a getter to get the user (and it's giftMessage), but this should work. In the template you would use:
<limited-textarea cols="30" rows="10" v-model="giftMessage"></limited-textarea>
Here is a complete example using Vuex.
console.clear()
const store = new Vuex.Store({
state:{
user:{
giftMessage: "test"
}
},
getters:{
giftMessage(state){
return state.user.giftMessage
}
},
mutations:{
setGiftMessage(state, message){
state.user.giftMessage = message
}
}
})
Vue.component("limited-textarea", {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
template: `
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
`,
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
if (evt.keyCode >= 48 && evt.keyCode <= 90) {
evt.preventDefault()
return
}
}
}
}
})
new Vue({
el: "#app",
store,
computed:{
giftMessage:{
get(){ return this.$store.getters.giftMessage},
set(v){ this.$store.commit("setGiftMessage", v)}
}
}
})
<script src="https://unpkg.com/vue#2.4.2"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.4.0/vuex.js"></script>
<div id="app">
<limited-textarea v-model="giftMessage"
:max="10"
cols="30"
rows="10">
</limited-textarea>
Message: {{giftMessage}}
</div>
Sorry to break in. Was looking for a solution. Looked at all of them.
For me they look too complicated. I'm always looking for symplicity.
Therefor I like the answer of #Даниил Пронин. But it has the by #J. Rambo noted potential problem.
To stay as close as possible to the native html textelement. The solution I came up with is:
Vue Template
<textarea v-model="value" #input="assertMaxChars()">
JavaScript
let app = new Vue({
el: '#app',
data: {
value: 'Vue is working!',
maxLengthInCars: 25
},
methods: {
assertMaxChars: function () {
if (this.value.length >= this.maxLengthInCars) {
this.value = this.value.substring(0,this.maxLengthInCars);
}
}
}
})
Here is a REPL link: https://repl.it/#tsboh/LimitedCharsInTextarea
The upside I see is:
the element is as close as possible to the native element
simple code
textarea keeps focus
delete still works
works with pasting text as well
Anyway
happy coding
While I agree with the selected answer. You can also easily prevent the length using a keydown event handler.
Vue Template
<input type="text" #keydown="limit( $event, 'myModel', 3)" v-model="myModel" />
JavaScript
export default {
name: 'SomeComponent',
data () {
return {
myModel: ''
};
},
methods: {
limit( event, dataProp, limit ) {
if ( this[dataProp].length >= limit ) {
event.preventDefault();
}
}
}
}
Doing this way, you can also use regular expression to event prevent the type of keys accepted. For instance, if you only wanted to accept numeric values you can do the following.
methods: {
numeric( event, dataProp, limit ) {
if ( !/[0-9]/.test( event.key ) ) {
event.preventDefault();
}
}
}
I have improved on #J Ws answer. The resulting code does not have to define how to react on which keypress, which is why it can be used with any character in contrast to the accepted answer. It only cares about the string-length of the result. It also can handle Copy-Paste-actions and cuts overlong pastes to size:
Vue.component("limitedTextarea", {
props: {
value: {
type: String,
default: ""
},
max: {
type: Number,
default: 25
}
},
computed: {
internalValue: {
get: function () {
return this.value;
},
set: function (aModifiedValue) {
this.$emit("input", aModifiedValue.substring(0, this.max));
}
}
},
template: '<textarea v-model="internalValue" #keydown="$forceUpdate()" #paste="$forceUpdate()"></textarea>'
});
The magic lies in the #keydown and #paste-events, which force an update. As the value is already cut to size correctly, it assures that the internalValue is acting accordingly.
If you also want to protect the value from unchecked script-changes, you can add the following watcher:
watch: {
value: function(aOldValue){
if(this.value.length > this.max){
this.$emit("input", this.value.substring(0, this.max));
}
}
}
I just found a problem with this easy solution: If you set the cursor somewhere in the middle and type, transgressing the maximum, the last character is removed and the cursor set to the end of the text. So there is still some room for improvement...
My custom directive version. Simple to use.
<textarea v-model="input.textarea" v-max-length="10"></textarea>
Vue.directive('maxlength',{
bind: function(el, binding, vnode) {
el.dataset.maxLength = Number(binding.value);
var handler = function(e) {
if (e.target.value.length > el.dataset.maxLength) {
e.target.value = e.target.value.substring(0, el.dataset.maxLength);
var event = new Event('input', {
'bubbles': true,
'cancelable': true
});
this.dispatchEvent(event);
return;
}
};
el.addEventListener('input', handler);
},
update: function(el, binding, vnode) {
el.dataset.maxLength = Number(binding.value);
}
})
Event() has browser compatibility issue.
Unfortunately for me, keydown approach seems not working good with CJK.
there can be side effects since this method fires input event double time.
Best way is to use watch to string length and set old value if string is longer than you want:
watch: {
'inputModel': function(val, oldVal) {
if (val.length > 250) {
this.inputModel = oldVal
}
},
},
Simply use maxlength attribute like this:
<textarea v-model="value" maxlength="50" />
I used your code and broke it out into a .Vue component, thanks!
<template>
<textarea v-model="internalValue" #keydown="onKeyDown"></textarea>
</template>
<script>
export default {
props:{
value:{ type: String, default: ""},
max:{type: Number, default: 250}
},
computed:{
internalValue: {
get() {return this.value},
set(v){ this.$emit("input", v)}
}
},
methods:{
onKeyDown(evt){
if (this.value.length >= this.max) {
evt.preventDefault();
console.log('keydown');
return
}
}
}
}
I did this using tailwind 1.9.6 and vue 2:
<div class="relative py-4">
<textarea
v-model="comment"
class="w-full border border-gray-400 h-16 bg-gray-300 p-2 rounded-lg text-xs"
placeholder="Write a comment"
:maxlength="100"
/>
<span class="absolute bottom-0 right-0 text-xs">
{{ comment.length }}/100
</span>
</div>
// script
data() {
return {
comment: ''
}
}