How to add condition to HTML code in Vue.js? - javascript

I have the following template code:
<template v-for="(index, message) in messages">
{literal}
<div class="message_block {{message.message_type}}" v-if="message.message_type">
<div class="message">
{{message.message}}
</div>
</div>
{/literal}
</template>
What I'm trying to do is show the one line - <div class="message_block"..., if message.message_type is not equal to null, or is not undefined.
This is the data I've got:
var demo = new Vue({
el: '#testblock',
data: {
messages: [
{ message: 'Foo', message_type: "left" },
{ message: 'Bar', message_type: null }
]
}
})
I'm trying to add the wrapper <div class="message_block">, if the message_type is not equal to null.

According to your comment above:
#LinusBorg No, I want the "Bar" to show regardless of the value of message_type, it's just if message_type is null, I don't want a wrapper around it.
There is no other way than to duplicate the inner div.
<template v-for="(index, message) in messages">
{literal}
<div class="message_block {{message.message_type}}" v-if="message.message_type">
<div class="message">
{{message.message}}
</div>
</div>
<div v-if="!message.message_type" class="message">
{{message.message}}
</div>
{/literal}
</template>
If you don't want to duplicate it, you can either define it as a small component to re-use it, or use a partial.

Related

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

Vue TypeError: Cannot read property '_wrapper' of undefined when attempting to update Object property

I am trying to use a button to update a property of an Object in Vue. The Object is returned by an AJAX query to a database, and the isFetching boolean is then set to false, which attaches the containing div to the DOM. When I try and update the property, I get the following error:
TypeError: Cannot read property '_wrapper' of undefined
Below is my AJAX code:
axios.post("/api/myEndpoint", { id: router.currentRoute.query.id })
.then((response) => {
this.activities = response.data.activities;
this.isFetching = false;
})
.catch((errors) => {
console.log(errors);
router.push("/");
});
Here is my HTML:
<div v-if="isFetching">
<h2>Loading data...</h2>
</div>
<div v-else>
<div class="row no-gutters">
<div class="col">
<div class="d-flex flex-row justify-content-center">
<h4>Activities</h4>
</div>
<div class="custom-card p-2">
<div class="row no-gutters pb-4" v-for="(activity, index) in activities"
:key="activity.stage_id">
<button v-if="!activity.is_removed" class="btn custom-btn" :class="{'hov':
index == 0}" :disabled="index != 0"
#click="markRemoved(activity)">Remove</button>
</div>
</div>
</div>
</div>
</div>
Finally, here is markRemoved() as called by the button's click listener:
markRemoved(a) {
a.is_removed = true;
}
When I try and log a in markRemoved() the Object is logged to the console fine, exactly as expected. Having stepped through it in the debugger, the exception is thrown at the point I try and update the is_removed property of the Object.
Does anyone know what I'm doing wrong here?
Note: the id I pass to the AJAX query is a query parameter of Vue Router. This is also set correctly and passed as expected.
Here is an example activity Object:
{date_time_due: "2020-12-09T11:43:07.740Z"
date_time_reached_stage: "2020-12-02T11:43:07.740Z"
is_complete: true
is_removed: false
selected_option: "Some text"
stage_id: 1
stage_name: "Some text"}
The exception is only thrown on the first click of the button.
Posting in case anybody else comes across this error in the future.
Vue requires exactly one root element within a single-file component's <template> tags. I had forgotten about this and in my case had two <div> elements, shown one at a time, conditionally using v-if:
<template>
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</template>
This caused problems with Vue's reactivity, throwing the error whenever I tried to update some part of the component. After realising my mistake, I wrapped everything in a root <div>. This solved the issue for me:
<template>
<div id="fixedComponent">
<div v-if="fetchingData">
<h2>Loading data...</h2>
</div>
<div v-else>
<!-- The rest of the component -->
</div>
</div>
</template>

Vue : Accessing Nested Object Component's Values

I have problems accessing this "name" property on the component. I can only access it statically.
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
{{ channel.users[0].name }}
</p>
</div>
</template>
Here is an Image of my Vue Devtools
So I have an v-for loop over channels, and I want to: Access the Usernames for each channel (if it is not my own preferably as "username" is set on my own i think its easy to exclude it right?) So that in the end In Channel 1 when there are 2 Users , I want to show the corresponding username, so the "other username", the one i am chatting with, and he should see my name that is the initial goal.
I thought of doing something like this:
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<!-- {{ channel.users[0].name }} -->
<span v-for="user,key in channel">{{key}}</span>
</p>
</div>
it at least displays the content of the channels object for each channel, but something like this isnt gonna work: key.user.name , unfortunately im stuck here. please help :)
edit: here is a dd() of the view
click
EDIT 2: Parent Data Provided:
//chat-app.blade.php
<div id="app">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Chats</div>
<vue-chat :channels="{{ $channels }}" ></vue-chat>
</div>
</div>
</div>
</div>
</div>
</div>
//<vue-chat> component
<template>
<div class="chat">
<div class="container">
<div class="row">
<div class="col-md-3">
<vue-chat-channels
:channels="channels"
:active-channel="activeChannel"
#channelChanged="onChannelChanged"
:username="sername"
></vue-chat-channels>
</div>
<div class="col-md-3">
<vue-chat-messages :messages="messages"></vue-chat-messages>
</div>
<div class="col-md-3">participants</div>
</div>
<div class="message-input-wrapper col-md-12"><vue-chat-new-message :active-channel="activeChannel"
:username="username"></vue-chat-new-message></div>
</div>
</div>
</template>
<script>
export default {
props: ["channels"],
data() {
return {
activeChannel: this.channels[0].id,
messages: [],
username: ''
};
},
methods: {
fetchMessages() {
let endpoint = `/channels/${this.activeChannel}/messages`;
axios.get(endpoint).then(({ data }) => {
this.messages = data;
});
},
onChannelChanged(id) {
this.activeChannel = id;
this.fetchMessages();
}
},
created() {
this.fetchMessages();
axios.get('/userfetch').then( ({data}) => {
console.log("Current User: "+data.name);
this.username = data.name;
});
console.log(this.channels[0].name);
// for (let channel of this.channels) {
this.channels.forEach(channel => {
// Channelname
window.Echo.channel('presence-'+channel.name)
.listen('MessageSent', (channel) => {
console.log(channel.data.message);
this.messages.push({ message: channel.data.message, author_username: channel.data.author_username});
if (this.activeChannel == channel.id) {
console.log("received message");
}
});
});
}
};
</script>
<style>
</style>
//ChatController.php
public function index()
{
$channels = Channel::with('users')->whereHas('users', function($q) {
$q->where('user_id',Auth::id());
})->get();
$user = Auth::user()->name;
return view('chat-app' , compact('channels','user'));
}
Short Explanation: ChatController returns the blade view, which has the data channels and user (my username) , and then vue comes into play which should pass down the prop of my username but i couldnt get it to work just yet
So you need to access users in every channel.
You can try like this:
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<span v-for="user in channel.users">
{{ user.name }}
</span>
</p>
</div>
This should work. If you have errors provide it here.
If you need to compare every user you can do it simply with v-if:
<span v-for="user in channel.users">
<span v-if="user.name === parentdata">
{{ user.name }}
</span>
</span>

Create unknown in advance Vue instances count

In the question Are multiple Vuejs instances possible?, below solution has been suggested:
new Vue({
el: '#app1',
data () {
return {
message: 'Hello'
}
},
})
new Vue({
el: '#app2',
data () {
return {
message: 'Helloa'
}
}
})
<div id="app1">
{{ message }}
</div>
<div id="app2">
{{ message }}
</div>
Well, it works for two instances, but what if we does not know in advance, how many <div id="appN"> will be? Also, it may not has id.
<!-- we don't know in advance, how many it will be -->
<div class="target">
{{ message }}
</div>
<div class="target">
{{ message }}
</div>
Is Vue's flexibility ends here?

On hover change multiple elements data in vue framework

I'm new to vue! I have 3 divs, All divs have a message data (One source)! I've bind a method, that when hover on one div it will change the message! The thing is working fine, but it change all div's message! And I know it's logical! But How can I only changed on that hovered element data! Not affecting others!
Here is my JSFiddle.
Html Is :
<div id="app">
<div class="one" v-on:mouseover="change">
{{ message }}
</div>
<div class="one" v-on:mouseover="change">
{{ message }}
</div>
<div class="one" v-on:mouseover="change">
{{ message }}
</div>
</div>
Vue :
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
methods : {
change : function() {
this.message = "Changed"
}
}
}
)
You need to create separate data bindings, one for each message div. There are a number of ways to solve this. The best solution depends on your use case. But, here's a simple solution:
Template:
<div id="app">
<div class="one" v-on:mouseover="change('foo', 'this is a message')">
{{ messages.foo }}
</div>
<div class="one" v-on:mouseover="change('bar', 'here is a different message')">
{{ messages.bar }}
</div>
<div class="one" v-on:mouseover="change('baz', 'message for baz')">
{{ messages.baz }}
</div>
</div>
Vue component:
new Vue({
el: '#app',
data: function () {
return { messages: {} }
},
methods : {
change : function(key, message) {
this.$set(this.messages, key, message)
}
}
})

Categories

Resources