Vue Bootstrap - I need to click twice to open the next tab - javascript

I have this Vue JS tabs:
<b-tabs v-model="tabIndex">
<b-tab title="Settings">
<validation-observer ref="validateSetting" tag="form">
my htmlform......
<b-button variant="primary" type="submit" #click.prevent="validateSetting"> Next </b-button>
</validation-observer>
</b-tab>
<b-tab title="Edit Mapping" :disabled="mapingDisabled">
<validation-observer ref="validateEditMapping" tag="form">
my htmlform......
<b-button variant="primary" type="submit" #click.prevent="validateEditMapping"> Next Steps </b-button>
</validation-observer>
</b-tab>
<b-tab title="Safety" :disabled="safetyDisable">
<b-button variant="primary" type="submit" #click.prevent="submitXml" > Submit </b-button>
</b-tab>
</b-tabs>
Each tab contain HTML form which is validating using validation-observer means user can't go to next tab until the current tab validation is done.
Here on the second and third I have disabled it using :disabled="mapingDisabled" and :disabled="safetyDisable".
Now, If I click on the button of the first tab it's not immediately go to the second tab ! I have to click again to go to the second and so one... why?
Here is what I am using on the script part:
On the data property I have this 2 property:
mapingDisabled: true,
safetyDisable: true,
tabIndex: 1,
and the methods:
validateSetting() {
this.$refs.validateSetting.validate().then((success) => {
if (success) {
this.mapingDisabled = false;
this.tabIndex++;
let _this = this;
if (this.countHTTP == 0) {
this.callHttp();
this.countHTTP++;
}
}
});
},
callHttp() {
let importData = {
token: this.mappingData.token,
xml_file_url: this.mappingData.xml_file_url,
name: this.mappingData.mapping_name,
encoding : this.mappingData.encoding,
};
http.post("projects/import/", importData)
.then((response) => {
let res = response.data;
let _this = this;
if (res.status !== 1) {
setTimeout(function () {
_this.callHttp();
this.loadDone = true;
}, 5000);
} else if (res.status === 1) {
this.loadDone = false;
this.mappingData.id_feed = res.id_feed;
res.field_names.forEach(function (item, index) {
_this.mappingData.projectFieldOptions.push({
value: item,
text: item,
custom: false,
});
});
}
})
.catch((error) => {
console.log(error);
});
},
validateEditMapping() {
this.$refs.validateEditMapping.validate().then((success) => {
if (success) {
this.tabIndex++;
this.safetyDisable = false;
}
});
},
submitXml() {
http.post("projects/import/mapping/", this.mappingData)
.then((response) => {
for (const prop of Object.getOwnPropertyNames(this.mappingData)) {
delete this.mappingData[prop];
}
this.$bvModal.hide("import-xml-modal");
})
.catch((error) => {
console.log(error);
});
},

I think so "this.tabIndex++;" should be "this.tabIndex += 1;".But I haven't tested it,You can try it.

Related

I want handle the delete function by vuejs

This is my Indexing.vue component.
<div>
<div v-for="data in indexingList" :key="data.indexing_id">
<p>{{ data.indexing_name }}</p>
<p>{{ data.indexing_url }}</p>
</div>
<base-button type="primary" size="sm" #click="deleteIndexing(data.indexing_id)">Delete
</base-button>
</div>
export default {
data() {
return {
indexingList: [],
}
},
methods: {
getIndexingList: function() {
this.api.getDataModule("indexing/" + store.state.journalId, "journals/v1/")
.then((res) => {
console.log(res.data);
this.indexingList = res.data.indexingList;
},
(err) => {
console.log(err);
}
);
},
deleteIndexing: function(dVal) {
let sVal = {};
sVal.indexing_id = dVal;
this.api.putData("indexing/" + store.state.journalId + "/deleteindexing", sVal)
.then((res) => {
console.log(res.data);
},
(error) => {
console.log(error);
}
);
},
},
mounted: function() {
this.getIndexingList();
},
}
I'm getting data from the server using getIndexingList function in the form of the API get method. And removing the data once the user clicks the delete button by using the deleteIndexing function in the form of the put API method.
We wrote APIs in a separate file. Here in indexing.vue we are just passing the APIs.
Now I want to fix the delete(remove) function. The data was removed from the database once the user clicks the delete button but it is not removed from the webpage. Every time I need to refresh the page to see the changes.
You can use Vue.delete (this.$delete) which also ensure that the deletion triggers the view updates.
Try this-
deleteIndexing: function(dVal) {
let sVal = {};
sVal.indexing_id = dVal;
this.api
.putData("indexing/" + store.state.journalId + "/deleteindexing", sVal)
.then(
(res) => {
// find the item from indexingList and remove it
// Also make sure your response has the id of the deleted item
let index = this.indexingList.findIndex(item => item.id == res.id);
if (index != -1) {
this.$delete(this.indexingList, index);
}
},
(error) => {
console.log(error);
},
);
},

How to Open MODAL after its been added to DOM - vue 3 bootstrap 5 nodejs

I dont know much about vue/bootstrap and reading docs does not help me to understand how it all works.
How to open a modal that is created after the page was loaded. From user input. User clicks button then the modal loads into a list prop and then renders into DOM and then it opens up.
Im at the point where i created event when user clicks the button that loads the modal into the list, but how do you catch the "modal has been added to DOM" event and then you can use getElementByID to instantiate the modal and then use .show() to show it?
I can see that the card that supposed to render on the page loads/renders, but the method get null. Im guessing that the method runs before the page/DOM has been re-rendered. So how do you run another method with parameter of sorts after the custom event that added the item to list has been triggered?
The code is too big and convoluted to post. But if need be i could try to trim it down, but its a mess.
App.vue
<template>
<div class="container-center">
<AnimeList />
</div>
</template>
AnimeList.vue
<template>
<div class="containerlist">
<AnimeCardModal
v-for="anime in animeList"
:anime="anime"
#checkAnimeListForRelatedEvent="checkAnimeListForRelated"
/>
</div>
</template>
<script setup>
import { defineComponent } from "vue";
import AnimeCardModal from "./AnimeCardModal.vue";
import axios from "axios";
</script>
<script>
export default defineComponent({
name: "AnimeList",
data() {
return {
animeList: [],
limit: 30,
page: 1,
reachedEnd: false,
};
},
methods: {
async getAnimeLsit() {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
this.animeList = res.data.data;
this.page = res.data.next.page;
this.limit = res.data.next.limit;
},
async getNextBatch() {
let bottomOfWindow =
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.offsetHeight;
if (bottomOfWindow && !this.reachedEnd) {
const res = await axios.get("/api", {
params: { page: this.page, limit: this.limit },
});
res.data.data.map((item) => {
this.animeList.push(item);
});
if (!res.data.next) {
this.reachedEnd = true;
} else {
this.page = res.data.next.page;
this.limit = res.data.next.limit;
}
}
},
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
}
}
// Add the anime to the list
},
},
created() {
window.addEventListener("scroll", this.getNextBatch);
},
deactivated() {
window.removeEventListener("scroll", this.getNextBatch);
},
async mounted() {
await this.getAnimeLsit();
},
components: {
AnimeCardModal,
},
});
</script>
Here is the method that gets triggered by the user click event where it loads the Not in main list data and should render on page/DOM.
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]); <--------------------------------------
console.log("added to list");
}
}
// Add the anime to the list
},
The added item is a modal with element id. I want to instantiate this element as new Modal() and open it with .show().
But the i get error that the element does not exist = null and i cant get it, but i can see it on screen.
EDIT:1
Ok so like as per usual, once i post on SO i find an answer to my problem, but it turns into another problem.
SO to get the rendered element i used this:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => { <----------------------
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
}else{
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.show();
}
// Add the anime to the list
},
It works, but now the modals overlay each other, seems like its not working like when you add the attributes to the card element that opens modal:
:data-bs-target="'#anime-card-modal-' + anime.id"
data-bs-toggle="modal"
Is there a way to get the same effect from method as with these attributes?
I want to open a modal, by clicking an element with those attributes, then when i click another element with them attributes (different target id) it closes previously opened modal and opens the target modal.
Alright, i found a solution, works pretty good.
Instead of using myModal.show() i used myModal.toggle("anime-card-modal-" + animeID) and the else statement is not needed in the event method:
async checkAnimeListForRelated(animeID) {
if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
const res = await axios.get("/api/anime", {
params: { id: animeID },
});
if (res.data.data.length > 0) {
this.animeList.push(res.data.data[0]);
console.log("added to list");
this.$parent.$nextTick(() => {
const myModal = new Modal(
document.getElementById("anime-card-modal-" + animeID)
);
myModal.toggle("anime-card-modal-" + animeID) <---------------
}
}
// Add the anime to the list
},

Dynamically change icon variant in Vue

I have the following template which I'm trying to change dynamically when values in data has been changed:
<div class="vote-bar-content">
<button class="vote-btn" #click="upvote(news)">
<b-icon icon="chevron-up"
:variant="upvoteVariant(news)"></b-icon>
</button>
<span class="vote"> No. of vote goes here </span>
<button class="vote-btn" #click="downvote(news)" >
<b-icon icon="chevron-down"
:variant="downvoteVariant(news)"></b-icon>
</button>
</div>
What I am trying to accomplish is to change both the b-icon variants into their respective value according to upvoteVariant and downvoteVariant when the user user clicks on the respective vote-btn. Below are both functions that are bound to the b-icon elements which are listed on Vue methods:
upvoteVariant(news) {
if (this.votingList[news._id].upvoted) {
return 'warning'
}
else {
return ''
}
},
downvoteVariant(news) {
if (this.votingList[news._id].downvoted) {
return 'primary'
}
else {
return ''
}
}
When the button is clicked and the method upvote(news) is called, changes on this.votingList[news._id] takes place and the value is altered as shown below:
async upvote(news) {
axios.post(serverSide.findUserByID, {userID: this.user._id})
.then((res) => {
this.user.rep = res.data.user.rep
if (this.votingList[news._id].upvoted == true) {
alert('You have already voted!')
return
}
else {
axios.post(serverSide.castVote, {
// function params
})
.then(() => {
this.votingList[news._id].upvoted = true
alert("Vote Casted")
console.log("Voting List: ", this.votingList[news._id])
this.upvoteVariant(news)
return
})
}
})
},
So why isn't b-icon's variant changing even though the data has changed?
Assuming upvoted and downvoted were not initially in the news object (they were inserted by upvote() and downvote(), respectively), Vue 2 cannot detect addition/deletion of properties in objects.
As a workaround, you could use this.$set():
export default {
methods: {
async upvote(news) {
const item = this.votingList[news._id]
if (item.upvoted || item.downvoted) {
alert('You have already voted!')
return
}
this.$set(item, 'upvoted', true) 👈
},
}
}
demo

Remove class for another user vue.js

I have chat message system.
I have code:
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'seen',
],
computed: {
className() {
return this.seen;
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
App.js:
data:{
message: '',
convId: 1,
chat: {
message: [],
user: [],
time: [],
seen: [],
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send(){
if(this.message.length != 0 && this.message.length <= 4000) {
this.chat.message.push(this.message);
this.chat.user.push('you');
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then( response => { this.chat.seen.push(''); //remove unread class })
.catch( response => { console.log(response) } )
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.message.push(e.message);
this.chat.user.push(e.user);
this.chat.time.push(this.getTime());
this.chat.seen.push('unread'). //set class unread message for user
console.log(e);
})
.listenForWhisper('typing', (e) => {
if(e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
}
My chat.blade.php:
<message v-for="value,index in chat.message"
:key=value.index
:user=chat.user[index]
:message="chat.message[index]"
:time="chat.time[index]"
:seen="chat.seen[index]"
>
</message>
<div class="form-group">
<textarea maxlength="4000" cols="80" rows="3" class="message-input form-control" v-model='message' v-on:click="seenMessage"></textarea>
</div>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
My function seen:
public function setMessagesSeen(Conversation $conversation) {
$user = User::find(Auth::id());
$conversations = Chat::conversation($conversation->id);
//$dd = Chat::conversations($conversation)->for($user)->readAll();
dd(Chat::conversations($conversations)->for($user)->getMessages()->where('body', 'asdfsadfsd'));
//$message = Chat::messages($message)->for($user)->markRead();
broadcast(new HasSeenMessage($message));
return response('ok');
}
How I can send class "unread" to element div other user? I can paste class on current user, and I get color on element chat only for me, but how I can hide element for me and other user, when message is seen?
I want do read/unread function for users.
Example:
If user in real time send message I send class unread, when other user click on textarea, I remove class unread, and said user, that message is seen. How I can do it in real time add/remove class unread? My function is not working.
To do this you have to create an Event in your Laravel application that you will broadcast on a precise channel (you can for example give the name 'chat. {Conversation}. {User_id}') and with Laravel Echo you will listen this event!
I allowed myself to make some changes in your code -:)
I presume you have this class HasSeenEvent
<?php
namespace App\Events;
use App\Order;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class HasSeenEvent implements ShouldBroadcast
{
use SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #param Message $message
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
// I presume we can get conversation id like this : $this->message->conversation->id
return new PrivateChannel('chat.'.$this->message->conversation->id.'.'.$this->message->sender->id);
}
}
Then, in your routes/broadcast.php declare this route chat.{conversation}.{user_id}
In the function where you put the 'seen' to 1 you broadcast the event at the same time
broadcast(new HasSeenMessage($message))
Then you listen to this event in your vuejs code
components/Message.js
<template>
<li :class="className">
{{ message }}
</li>
</template>
<script>
export default {
props: [
'message',
'user',
'time',
'readed',
],
computed: {
className() {
return this.readed == 1 ? 'seen' : 'unread';
}
},
mounted() {
console.log('Component mounted.')
}
}
</script>
chat.blade.php
<message v-for="message,index in chat.messages"
:key="index"
:user="message.user"
:message="message.content"
:time="message.time"
:readed="message.readed"
>
</message>
<div class="form-group">
<button type="button" class="btn btn-lg btn-primary" v-on:click="send">Send message</button>
</div>
App.js
data: {
message: '',
convId: 1,
chat: {
messages: [],
/* message: [],
user: [],
time: [],
seen: [], */
},
typing: '',
},
....
watch: {
message() {
Echo.private('chat')
.whisper('typing', {
name: this.message
});
}
},
methods: {
send() {
if (this.message.length != 0 && this.message.length <= 4000) {
let data = {
content: this.message,
user: 'you',
time:this.getTime(),
readed: 0
}
this.chat.messages.push(data)
data = {}
axios.post('/sendMessage', {
message: this.message,
//lastName: 'Flintstone'
})
.then(response => {
console.log(response);
this.message = '';
})
.catch(error => {
console.log(error);
});
}
},
seenMessage() {
axios.post('/setMessagesSeen/' + this.convId) //this request mark messages in chat all readed for auhenticated user
.then(response => {
//This is not the best way to do that
this.chat.messages[this.messages.length -1 ].readed = 0
}).catch(response => {
console.log(response)
})
},
getTime() {
let time = new Date();
return time.getHours() + ':' + time.getMinutes();
}
},
mounted() {
Echo.private('chat')
.listen('ChatEvent', (e) => {
this.chat.messages.push({
content: e.message,
user: e.user,
time: this.getTime(),
readed: 0
})
console.log(e);
})
.listenForWhisper('typing', (e) => {
if (e.name != '')
this.typing = 'typing..';
else
this.typing = null;
});
// I presume to can access to user info
let that = this
Echo.private('chat.'+convId+'.'+user.id)
.listen('HasSeenMessage', (e) => {
let message = e.message
let lookingForMessage = that.chat.messages.find((m) => {
// I presume in your db messages table has field content and time
return m.content == message.content && m.time => message.time
})
try {
lookingForMessage.readed = 1
}catch (err){
// message not found
console.log(err)
}
})
}
Hope it helped you!

Unable to bind value return from function in v-for?

I am not able to bind function returned value with dom.
i want to display "no data - DOM" if function isNoStatuses(id,index) returns true in v-for element else it should not display.
you can refer below code. i am using vuejs2.
I just want to get status while v-for. the v-for element should show dom based on status.
Here is my sample code:
<div class="row gutter wrap justify-stretch" v-for="(row, index) in items">
<div>{{row.name}}</div> <!-- display data -->
<!-- if data not available i.e isNoStatuses(id,index) return true display below-->
<div v-if="isNoStatuses(row.id,index) === true">
<div class="card">
<div class="card-content">
<i class="material-icons">error</i> Oops! content not found!
</div>
</div>
</div>
</div>
<script>
import appService from 'service'
export default {
data () {
return {
searchedTopic: '',
items: [{id: 1, name: 'abc'}, {id: 2, name: 'xxabc'}, {id: 3, name: 'dabsc'}],
noMoreData: false
}
},
methods: {
logout () {
appService.logout()
},
isNoStatuses (idStr, i) {
appService.getstatus(idStr).then((resp) => {
var res = resp.data
console.log('get res: ' + JSON.stringify(res))
if (res.success === 1) {
return false
}
else {
return true
}
}).catch((e) => {
console.log('error while getting : ' + JSON.stringify(e))
})
},
created () {
appService.checkAuth()
},
components: {
},
mounted () {
}
}
</script>
---
i have also tried like this: the status will be taken after mounted and response status will be store in status[].
..
<div class="row gutter wrap justify-stretch" v-for="(row, index) in items">
<div>{{row.name}}</div> <!-- display data -->
<!-- if data not available i.e isNoStatuses(id,index) will set status[index] true display below-->
<div v-if="status[index] === true">
<div class="card">
<div class="card-content">
<i class="material-icons">error</i> Oops! content not found!
</div>
</div>
</div>
..
<script>
import appService from 'service'
export default {
data () {
return {
searchedTopic: '',
items: [{}, {}, {}],
noMoreData: false,
status: []
}
},
methods: {
logout () {
appService.logout()
},
isNoStatuses (idStr, i) {
appService.getstatus(idStr).then((resp) => {
var res = resp.data
console.log('get res: ' + JSON.stringify(res))
if (res.success === 1) {
this.status[i] = false
}
else {
this.status[i] = true
}
}).catch((e) => {
console.log('error while getting : ' + JSON.stringify(e))
})
},
created () {
appService.checkAuth()
},
components: {
},
mounted () {
for (var i = 0; i < this.items.length; i++) {
this.isNoStatuses(this.item[i].id_str, i)
}
}
}
</script>

Categories