I tried to make a chat app using Vue CLI 3 and I have finished making a real-time chat room. Then, I tried to give a citing function to it which users can cite the message before and reply to it. So, I manage to pass the cited message to the child component by props. The cited message was NULL by default. After the user clicked some buttons, I expected the value of the "cited message" would change and the new value would be passed to the child through props (automatically updated). But, in fact, it didn't.
When I was browsing the internet, I did find several questions about updating the child component when props values change. So, I tried watch:, created(), update(), but none of them worked.
I once tried to directly add an element <p> in the child component and put {{cited_message}} in it to see what was inside the variable. Then, the Vue app crashed with a white blank page left (but the console didn't show any error).
For convenience, I think the problem is around:
<CreateMessage:name="name":cited_message="this.cited_message"#interface="handleFcAfterDateBack"/>
OR
props: ["name","cited_message"],
watch: { cited_message: function (newValue){ this.c_message = newValue; } },
You can ctrl+F search for the above codes to save your time.
Parent component:
<template>
<div class="container chat">
<h2 class="text-primary text-center">Real-time chat</h2>
<h5 class="text-secondary text-center">{{ name }}</h5>
<div class="card" style="min-height: 0.8vh">
<div class="card-body">
<p class="text-secondary nomessages" v-if="messages.length == 0">
[no messages yet!]
</p>
<div class="messages" v-chat-scroll="{ always: false, smooth: false }">
<div v-for="message in messages" :key="message.id">
<div v-if="equal_name(message)">
<div class="d-flex flex-row">
<div class="text-info">
[ {{ message.name }} ] : {{ message.message }}
</div>
<div class="btn-group dropright">
<a
class="btn btn-secondary btn-sm dropdown-toggle"
href="#"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<button
#click="get_cited_message(message)"
:key="message.id"
class="dropdown-item"
href="#"
>
Cite
</button>
</div>
</div>
<div class="text-secondary time">
<sub>{{ message.timestamp }}</sub>
</div>
</div>
<!--below is for cited message-->
<div v-if="message.cited_message" class="d-flex flex-row">
Cited : {{ message.cited_message }}
</div>
</div>
<div v-else>
<div class="d-flex flex-row-reverse">
<div class="text-info">
[ {{ message.name }} ] : {{ message.message }}
</div>
<div class="text-secondary time">
<sub>{{ message.timestamp }}</sub>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-action">
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
/>
</div>
</div>
</div>
</template>
<script>
import CreateMessage from "#/components/CreateMessage";
import fb from "#/firebase/init.js";
import moment from "moment";
export default {
name: "Chat",
props: {
name: String,
},
components: {
CreateMessage,
},
methods: {
equal_name(message) {
if (message.name == this.name) {
return true;
} else {
return false;
}
},
get_cited_message(message) {
this.cited_message = message.message;
console.log(this.cited_message);
},
handleFcAfterDateBack(event) {
console.log("data after child handle: ", event);
},
},
data() {
return {
messages: [],
cited_message: null,
};
},
created() {
let ref = fb.collection("messages").orderBy("timestamp");
ref.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
change.type = "added";
if (change.type == "added") {
let doc = change.doc;
this.messages.push({
id: doc.id,
name: doc.data().name,
message: doc.data().message,
timestamp: moment(doc.data().timestamp).format(
"MMMM Do YYYY, h:mm:ss a"
),
cited_message: doc.data().cited_message,
});
}
});
});
},
};
</script>
<style>
.chat h2 {
font-size: 2.6em;
margin-bottom: 0px;
}
.chat h5 {
margin-top: 0px;
margin-bottom: 40px;
}
.chat span {
font-size: 1.2em;
}
.chat .time {
display: block;
font-size: 0.7em;
}
.messages {
max-height: 300px;
overflow: auto;
text-align: unset;
}
.d-flex div {
margin-left: 10px;
}
</style>
Child component:
<template>
<div class="container" style="margin-bottom: 30px">
<form #submit.prevent="createMessage()">
<div class="form-group">
<input
type="text"
name="message"
class="form-control"
placeholder="Enter your message"
v-model="newMessage"
/>
<p v-if="c_message" class="bg-secondary text-light">Cited: {{c_message}}</p>
<p class="text-danger" v-if="errorText">{{ errorText }}</p>
</div>
<button class="btn btn-primary" type="submit" name="action">
Submit
</button>
</form>
</div>
</template>
<script>
import fb from "#/firebase/init.js";
import moment from "moment";
export default {
name: "CreateMessage",
props: ["name","cited_message"],
watch: {
cited_message: function (newValue){
this.c_message = newValue;
}
},
data() {
return {
newMessage: "",
errorText: null,
c_message: null
};
},
methods: {
createMessage() {
if (this.newMessage) {
fb.collection("messages")
.add({
message: this.newMessage,
name: this.name,
timestamp: moment().format(),
cited_message: this.c_message
})
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch((err) => {
console.log(err);
});
this.newMessage = null;
this.errorText = null;
} else {
this.errorText = "Please enter a message!";
}
},
},
beforeMount(){
this.c_message = this.cited_message;
}
};
</script>
Side-note: In the parent component, I only made the dropdown menu for the messages on the left-hand side. If this thread solved, I would finish the right-hand side.
It is solved. I think the problem is that the child component didn't re-render when the variable in parent component updated. Only the parent component was re-rendered. So, the props' values in the child remained the initial values. In order to solve this, binding the element with v-bind:key can let the Vue app track the changes of the variable (like some kind of a reminder that reminds the app to follow the changes made on the key). When the variable(key) changes, the app will be noticed and the new value will be passed to the child.
E.g.
Original
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
/>
Solved
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
:key="this.cited_message"
/>
Even though the problem is solved, I don't know whether I understand the problem clearly. If I made any mistakes, please comment and let me know.
i want to check value in input if has any value in input i want to add class sh-is-active to a div using VUE but i don't know how... please help me
<template>
<div class="sh-wrap sh-wrap-input sh-is-active">
<label class="sh-label">First and Last Name</label>
<input type="text" class="sh-form-control sh-input" placeholder="First and Last Name" />
<span for="email" class="error">Required</span>
</div>
</template>
You can use v-model bind a value to the input and to dynamically add the classes :class="{ 'sh-is-active': name }". Read the official docs on how to bind classes and styles
new Vue({
el: '#example',
data() {
return {
name: null
}
}
})
.sh-wrap-input {
padding: 1rem;
border: 1px solid gray;
}
.sh-is-active {
background: yellow;
}
.sh-label {
display: block;
margin-bottom: .125rem;
}
.error {
display: block;
color: red;
font-size: 12px;
margin-top: .125rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="example" class="sh-wrap sh-wrap-input" :class="{ 'sh-is-active': name }">
<label class="sh-label">First and Last Name</label>
<input v-model="name" type="text" class="sh-form-control sh-input" placeholder="First and Last Name" />
<span v-if="!name" for="email" class="error">Required</span>
</div>
to check if value exists you can add :value="inputValue"
and watch for it using
watch: {inputValue: function(value){
"add your class here"
}}
I am trying to change the color of text depending on what option is chosen from a drop-down menu. This is for a TODO List project that I am working on. The drop-down menu has three options: High urgency (change text to red), Medium Urgency (change text to yellow), and Low Urgency (change text to green).
<template>
<div class="TodoList">
<input type="text" class="todo-input" v-model="newTodo" #keyup.enter="addTodo">
<div v-for="(todo, index) in todos" :key="todo.id" class="todo-item"
</div>
<ul class="urgency-column">
<li>
<label class="todo-label" >Select Urgency level:</label>
</li>
<li>
<select class="todo-drop" id="" onchange="setUrgency()">
<option value="high">High Urgency</option>
<option value="medium">Medium Urgency</option>
<option value="low">Low Urgency</option>
</select>
</li>
</ul>
</div>
</div>
</template>
<scripts>
export default {
methods: {
setUrgency(todo) {}
}
}
</script>
First, use v-model to capture the selected urgency. Make sure the model includes an urgency property when adding a new TODO item:
methods: {
addItem() {
this.todos.push({
urgency: '',
//...
})
}
}
<select class="todo-drop" v-model="todo.urgency">...</select>
Class binding
You could use a class binding to set a specific class based on the value of the item's urgency value:
<div class="item-text"
:class="{ high: todo.urgency === 'high', medium: todo.urgency === 'medium', low: todo.urgency === 'low' }">
{{todo.text}}
</div>
Then in your <style> block, style the item's text according to the corresponding urgency class:
.item-text.high {
color: red;
}
.item-text.medium {
color: yellow;
}
.item-text.low {
color: green;
}
Attribute binding
Or you could apply an attribute that could be selected in CSS. For example, this adds to the TODO item's text container an urgency attribute with a value equal to the selected urgency:
<div class="item-text" :urgency="todo.urgency">{{todo.text}}</div>
Then in your <style> block, use an attribute selector to style the item text by urgency:
.item-text[urgency="high"] {
color: red;
}
.item-text[urgency="medium"] {
color: yellow;
}
.item-text[urgency="low"] {
color: green;
}
I am using vueJs and have a radio button group. When the radio is checked how can I bind a css border attribute to a class :class="selected"?
The :checked attribute does not work as I have bound it to the v-model.
So if the radio is checked bind a class (selected) to the div.
<div class="sau-select lg center" :class="selected">
<label for="windows">
<input type="radio" id="windows" value="windows" v-model="selectedOs" :checked="checked">
<span></span>
<img src="/img/windows.gif" alt="Windows">Windows
</label>
</div>
The :class="selected" you use hasn't much effect.
To conditionally apply a class to the div, you will have to use as :class condition if the selectedOs equals the value attribute of each <input>, such as :class="{redborder: selectedOs === 'windows'}". More details in the demo below:
new Vue({
el: '#app',
data: {
selectedOs: 'windows'
}
})
.redborder { border: 1px solid red; }
<script src="https://unpkg.com/vue"></script>
<div id="app">
<label :class="{redborder: selectedOs === 'windows'}">
<input type="radio" value="windows" v-model="selectedOs"> Windows
</label>
<label :class="{redborder: selectedOs === 'linux'}">
<input type="radio" value="linux" v-model="selectedOs"> Linux
</label>
</div>
Notice that value="linux" means that that radio box will assign the string "linux" to the v-model variable (in this case selectedOs). value="linux" is equivalent to :value="'linux'", for instance.
In this carousel,
how do I show dots?
<amp-carousel layout=fixed-height height=400 type=slides autoplay controls loop arrows dots='.'>
<amp-img src="img/slaid.jpg" layout=fixed-height height=400></amp-img>
<amp-img src="img/slaid.jpg" layout=fixed-height height=400></amp-img>
</amp-carousel>
There is an example in this codelab:
https://codelabs.developers.google.com/codelabs/advanced-interactivity-in-amp/index.html
The final code is here:
<amp-carousel type="slides" layout="fixed-height" height=250 id="carousel" on="slideChange:AMP.setState({selected: {slide: event.index}})">
<amp-img width=200 height=250 src="./shirts/black.jpg" [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=300 height=375 src="./shirts/black.jpg" [src]="shirts[selected.sku].image"></amp-img>
<amp-img width=400 height=500 src="./shirts/black.jpg" [src]="shirts[selected.sku].image"></amp-img>
</amp-carousel>
<p class="dots">
<span [class]="selected.slide == 0 ? 'current' : ''" class="current"></span>
<span [class]="selected.slide == 1 ? 'current' : ''"></span>
<span [class]="selected.slide == 2 ? 'current' : ''"></span>
</p>
https://github.com/googlecodelabs/advanced-interactivity-in-amp/blob/master/static/final.html
Navigational dots can be added by indicating it in the CSS Custom Properties, <style></style>. Here's a Github sample from Styling/Theming with AMP:
<head>
<style>
amp-carousel {
--arrow-color: green;
--dots: {
opacity: 50%;
color: blue;
}
}
</style>
</head>
<body>
<amp-carousel width=500 height=500>
<div>
<amp-img width=500 height=500 src="https://placekitten.com/g/500/500">
</amp-img>
</div>
<div>
<amp-img width=500 height=500 src="https://placekitten.com/g/500/500">
</amp-img>
</div>
</amp-carousel>
</body>
You can also check this SO thread for additonal reference.