Hello I'm working on an application made in vuejs and firebase and I use OTP authentication for account creation. I got to do this. When the phone number field and the verification code field are on the same page, the authentication passes and I am redirected to the next page.
Now I decided to separate the two fields (phone number and verification code) by creating a page to enter the phone number and a second page to enter the verification code. Unfortunately this does not work, the code verification page does not recognize the code entered.
please help me.
Page 1: (Login.vue)
<div class="row justify-content-center" style="background-color:#34B76E;">
<div class="col-lg-5 col-md-7">
<b-container>
<b-row class="vh-100" align-v="center">
<div>
<b-card no-body class="" style="max-width: 540px; background-color:#EFF3F8;
border-radius:20px 20px 20px 20px;">
<b-row no-gutters class="" style="">
<b-col md="5" style="background:#FFAB0E; width:200px; border-radius:20px 0px
0px 20px;">
<!--
<img src="../../assets/images/bg.png" height="300" width="170">
<b-card-img src="https://picsum.photos/400/400/?image=20" alt="Image"
class="rounded-0"></b-card-img>
-->
</b-col>
<b-col md="7">
<b-card-body>
<h2 style="color:black; font-weight:bold">Backoffice</h2>
<span style="font-size:0.8em; color:gray">Veuillez-vous connecter à votre
compte</span>
<div style="height:40px"></div>
<b-card-text style="">
<div style="width:250px;">
<b-form>
<b-form-text>
Indiquez votre numéro de téléphone
</b-form-text>
<b-form-group>
<b-form-input type="text" v-model="phone" aria-describedby="tel"
placeholder="+225 00 07 05 01 00" required style="border-radius:10px 10px 10px 10px;
text-align:center"></b-form-input>
</b-form-group>
<b-button id="sign-in-button" #click="sendCode" block
variant="success active" style="border-radius:10px 10px 10px 10px;">
Continuer
</b-button>
<div id="recaptcha-container"></div><br>
</b-form>
</div>
</b-card-text>
<div style="height:40px"></div>
<div class="row mt-3">
<div class="col-12 text-right">
<router-link to="/LoginCompte" class="text-light">
<small style="color:gray">Login Compte</small>
</router-link> <br>
<router-link to="/verify" class="text-light">
<small style="color:gray">Verify</small>
</router-link>
</div>
</div>
</b-card-body>
</b-col>
</b-row>
</b-card>
</div>
</b-row>
</b-container>
</div>
</div>
</template>
<script>
import firebase from "firebase";
export default {
name: 'login',
data(){
return{
phone: '',
appVerifier : '',
}
},
methods :{
sendCode(){
if(this.phone.length != 10){
alert('Numero invalide !');
}else{
//
let countryCode = '+225' //civ
let phoneNumber = countryCode + this.phone
let nextpage = this
//
let appVerifier = this.appVerifier
//
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
.then(function (confirmationResult)
{
// user in with confirmationResult.confirm(code).
window.confirmationResult = confirmationResult;
//
//alert('Continuer')
nextpage.$router.push({path:'/Verify'})
}).catch(function (error) {
// Error; SMS not sent
// ...
alert('Erreur, code non envoyer')
});
}
},
//
//
verifyCode(){
if(this.phone.length != 10 || this.otpCode.length != 6){
alert('Numero ou Format invalide !');
}else{
//
let vm = this
let code = this.otpCode
//
window.confirmationResult.confirm(code).then(function (result) {
// User signed in successfully.
var user = result.user;
// ...
//route to set password !
vm.$router.push({path:'./LoginCompte'})
}).catch(function (error) {
// User couldn't sign in (bad verification code?)
// ...
});
}
},
initReCaptcha(){
setTimeout(()=>{
let vm = this
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container',
{
'size': 'invisible',
'callback': function(response)
{
// reCAPTCHA solved, allow signInWithPhoneNumber.
// ...
},
'expired-callback': function()
{
// Response expired. Ask user to solve reCAPTCHA again.
// ...
}
});
//
this.appVerifier = window.recaptchaVerifier
},1000)
}
},
created(){
this.initReCaptcha()
}
}
</script>
<style>
</style>
Page 2: (Verify.vue)
<template>
<div class="row justify-content-center" style="background-color:#34B76E;">
<div class="col-lg-5 col-md-7">
<b-container>
<b-row class="vh-100" align-v="center">
<div>
<b-card no-body class="" style="max-width: 540px; background-color:#EFF3F8;
border-radius:20px 20px 20px 20px;">
<b-row no-gutters class="" style="">
<b-col md="5" style="background:#FFAB0E; width:200px; border-radius:20px 0px
0px 20px;">
</b-col>
<b-col md="7">
<b-card-body>
<h2 style="color:black; font-weight:bold">Backoffice</h2>
<span style="font-size:0.8em; color:gray">Veuillez-vous connecter à votre
compte</span>
<div style="height:40px"></div>
<b-card-text style="">
<div style="width:250px;">
<b-form hidden>
<b-form-text id="tel">
Indiquez votre numéro de téléphone
</b-form-text>
<b-input-group class="mt-3">
<b-form-input type="text" v-model="phone" id="tel" aria-
describedby="tel" placeholder="00 07 05 01 00" required style="border-radius:10px
0px 0px 10px; text-align:center"></b-form-input>
<b-input-group-append>
<b-button id="sign-in-button" #click="sendCode" block
variant="success active" style="height:40px; border-radius:0px 10px 10px 0px;">
<i class="fa fa-chevron-circle-right"></i>
</b-button>
</b-input-group-append>
</b-input-group>
<div id="recaptcha-container"></div><br>
</b-form>
<div>
<b-form>
<b-form-text id="tel">
Veuillez indiquer le code reçu par SMS
</b-form-text>
<b-form-group>
<b-form-input type="text" v-model="otpCode" aria-
describedby="tel" placeholder="00 00 00" required style="border-radius:10px 10px
10px 10px; text-align:center"></b-form-input>
</b-form-group>
<b-button #click="verifyCode" block variant="success active"
style="border-radius:10px 10px 10px 10px;">Se connecter</b-button>
</b-form>
</div>
</div>
</b-card-text>
<div style="height:40px"></div>
<div class="row mt-3">
<div class="col-12 text-right">
<router-link to="/" class="text-light">
<small style="color:gray">Vous n'avez pas reçu de code?</small>
</router-link>
</div>
</div>
</b-card-body>
</b-col>
</b-row>
</b-card>
</div>
</b-row>
</b-container>
</div>
</div>
</template>
<script>
import firebase from "firebase";
export default {
name: 'verify',
data(){
return{
phone: '',
appVerifier : '',
otpCode : ''
}
},
methods :{
sendCode(){
if(this.phone.length != 10){
alert('Numero invalid !');
}else{
//
let countryCode = '+225' //civ
let phoneNumber = countryCode + this.phone
//let nextpage = this
//
let appVerifier = this.appVerifier
//
firebase.auth().signInWithPhoneNumber(phoneNumber, appVerifier)
.then(function (confirmationResult) {
// user in with confirmationResult.confirm(code).
window.confirmationResult = confirmationResult;
//
alert('Continuer')
//nextpage.$router.push({path:'/Verify'})
}).catch(function (error) {
// Error; SMS not sent
// ...
alert('Erreur, code non envoyer')
});
}
},
//
verifyCode(){
if(this.phone.length != 10 || this.otpCode.length != 6){
alert('Numero ou Format invalide !');
}else{
//
let vm = this
let code = this.otpCode
//
window.confirmationResult.confirm(code).then(function (result) {
// User signed in successfully.
var user = result.user;
// ...
//route to set password !
vm.$router.push({path:'./LoginCompte'})
}).catch(function (error) {
// User couldn't sign in (bad verification code?)
// ...
});
}
},
initReCaptcha(){
setTimeout(()=>{
let vm = this
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container',
{
'size': 'invisible',
'callback': function(response) {
// reCAPTCHA solved, allow signInWithPhoneNumber.
// ...
},
'expired-callback': function() {
// Response expired. Ask user to solve reCAPTCHA again.
// ...
}
});
//
this.appVerifier = window.recaptchaVerifier
},1000)
}
},
created(){
this.initReCaptcha()
}
}
</script>
<style>
</style>
I am trying to put the value of content data from another component in this.newTutorial.content push function.
so far I get the data now I need to put it inside my v-model, like putting data from a v-model to another v-model? I tried this this.newTutorial.content = this.content; on the created function but no luck yet says type check failed for prop "rules". Expected Array, got Boolean with value true.
here is my code:
<style scoped>
img.preview {
width:200px;
}
.v-btn {
height: 50px !important;
min-width: 50px !important;
}
</style>
<template>
<div id="app">
<v-dialog v-model="dialog" width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn style="z-index:9;" color="blue lighten-1" dark rounded v-bind="attrs" v-on="on" fixed left>
<v-tooltip right >
<template v-slot:activator="{ on, attrs }">
<v-icon fab dark v-bind="attrs" v-on="on">
mdi-plus
</v-icon>
</template>
<img class="monk-ico" src="https://celfonica.s3-us-west-1.amazonaws.com/logos/monk-circle+50px.png">
<span style="display:inline;">
Add Tutorial
</span>
</v-tooltip>
</v-btn>
</template>
<div class="left">
<v-btn color="primary" #click="dialog = false" width="10px">
<v-icon>
mdi-close
</v-icon>
</v-btn>
</div>
<div class="panel-heading">
</div>
<div>
<h1>Tutorial form</h1>
<h3> create one</h3>
<form id="form" class="form-inline" v-on:submit.prevent="addTutorial">
<v-divider class="m-tb-20"></v-divider>
<h4>Author details</h4>
<div class="form-group">
<v-text-field :rules="nameRules" required label="First Name" type="text" id="tutorialFirst" class="form-control" v-model="newTutorial.first">
</v-text-field>
</div>
<div class="form-group">
<v-text-field :rules="nameRules" required label="Last Name" type="text" id="tutorialLast" class="form-control" v-model="newTutorial.last">
</v-text-field>
</div>
<div class="form-group">
<v-text-field :rules="emailRules" required label="Email" type="text" id="tutorialEmail" class="form-control" v-model="newTutorial.email">
</v-text-field>
</div>
<div class="form-goup">
<!-- Img upload input field-->
<div>
<h4 class="m-tb-20">Upload tutorial picture:</h4>
<input class="form-control" type="file" #change="previewImage" accept="image/+">
<br><v-btn class="m-tb-20" #click=" onUpload();"><v-icon>mdi-upload</v-icon></v-btn>
</div>
<div>
<p> Progress: {{uploadValue.toFixed()+"%"}}
<progress :value="uploadValue" max="100"></progress>
</p>
</div>
</div>
<v-divider class="m-tb-20"></v-divider>
<h4>Tutorial content</h4>
<div class="form-group">
<v-select required label="Language"
id="tutorialLanguage" v-model="newTutorial.language"
multiple type="text" autocomplete tags :items="languages" class="form-control">
<template slot="selection" slot-scope="data">
<v-btn>
{{ data.item }}
</v-btn>
</template>
</v-select>
</div>
<div class="form-group">
<v-text-field :rules="titleRules" required label="Tutorial Title" type="text" id="tutorialTitle" class="form-control" v-model="newTutorial.title">
</v-text-field>
</div>
<!--tiptap--> <v-card >
<div >
<editor-menu-bar v-on:submit.prevent="addTutorial" :editor="editor" v-slot="{ commands, isActive }">
<div>
<v-btn :class="{ 'is-active': isActive.bold() }" #click="commands.bold">
<v-icon class="mdi mdi-format-bold mdi-24px"> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.italic() }" #click="commands.italic">
<v-icon class="mdi mdi-format-italic mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.underline() }" #click="commands.underline">
<v-icon class="mdi mdi-format-underline mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.code() }" #click="commands.code">
<v-icon class="mdi mdi-code-tags mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.link() }" #click="commands.link">
<v-icon class="mdi mdi-link mdi-24px"> </v-icon>
</v-btn>
<v-divider></v-divider>
</div>
</editor-menu-bar>
<editor-content label="Tutorial content" :editor="editor" v-model="content" />
</div>
</v-card>
<div class="form-group">
<v-text-field required label="Date" class="form-control" type='date' v-model='newTutorial.date'>
</v-text-field>
</div>
<div class="form-group">
<v-text-field required label="Tutorial Sample Code Link" type="url" id="tutorialCode" class="form-control" v-model="newTutorial.code">
</v-text-field>
</div>
<div>
<br>
</div>
<v-divider class="m-tb-20"></v-divider>
<h4> Preview </h4>
<v-card class="m-tb-20" v-model="newTutorial">
<img class="preview " :src="picture"><br>
<v-card-title class="center">{{ newTutorial.title }} </v-card-title>
<v-card-subtitle> {{ newTutorial.first }} {{ newTutorial.last }} </v-card-subtitle>
<v-divider class="m-tb-20"></v-divider>
<v-card-text>{{ newTutorial.content }}</v-card-text>
{{ content }}
<v-card-text>
<h5>{{ newTutorial.language }}</h5>
<h5>{{ newTutorial.email }}</h5>
<h5>{{ newTutorial.date }}</h5>
</v-card-text>
</v-card>
<!-- Form push btn -->
<v-btn class="m-tb-20" #click="markcompleted();" type="submit" small color="primary" dark>
{{ displayText }}
</v-btn>
</form>
</div>
</v-dialog>
</div>
</template>
<script>
import firebase from '../plugins/firebase'
import EditorContent from "../components/EditorContent";
import toastr from 'toastr';
// to debug multiple Fire apps
//if (!firebase.apps.length) {
// firebase.initializeApp(config);
// this.newTutorial.userID= uid;
//}
import { Editor, EditorMenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
let db = firebase.database();
let messagesRef = db.ref('tutorials');
export default {
name: 'tutform',
firebase: {
tutorials: messagesRef
},
components: {
EditorMenuBar,
EditorContent,
},
data() {
return {
content: null,
editor: new Editor({
extensions: [
new Blockquote(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new BulletList(),
new OrderedList(),
new ListItem(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: '',
}),
imageData:null,
picture:null,
uploadValue: 0,
dialog: false,
displayText: 'Push me!',
newTutorial: {
first: '',
email: '',
last: '',
language: [],
title: '',
content: '',
date: '',
picture:'',
code: '',
},
languages: [
'Html', 'CSS', 'VUE', 'React', 'Ruby', 'JS', 'SASS', 'Python','PHP','C#','JAVA','Other',
],
nameRules: [
v => !!v || 'you must type something',
v => v.length <= 10 || 'hum.. this monk smelling somthing strange... must be less than 10 characters',
],
emailRules: [
v => !!v || 'E-mail is required',
v => /.+#.+/.test(v) || 'Please enter a valid email containing # ',
],
contentRules: [
v => !!v || 'Content is required amigo!'
],
titleRules: [
v => !!v || 'Tittle is required buddy!',
v => v.length <= 100 || 'Woots!, Lets try making this one shorter'
]
}
},
methods: {
previewImage(event){
this.uploadValue=0;
this.picture=null;
this.imageData=event.target.files[0];
},
onUpload() {
this.picture=null;
const storageRef=firebase.storage().ref(`tutorials/images/${this.imageData.name}`).put(this.imageData);
storageRef.on(`state_changed`, snapshot=>{
this.uploadValue=(snapshot.bytesTransferred/snapshot.totalBytes)*100;
}, error=>{console.log(error.message)},
()=>{this.uploadValue=100;
storageRef.snapshot.ref.getDownloadURL().then((url)=>{
this.picture=url;
this.newTutorial.picture = url;
console.log(this.picture);
toastr.success('Image Uploaded successfully');
})}
)
},
addTutorial: function() {
messagesRef.child(this.newTutorial.userID).push(this.newTutorial);
this.newTutorial.first = '';
this.newTutorial.last = '';
this.newTutorial.content = '';
this.newTutorial.email = '';
this.newTutorial.language = '';
this.newTutorial.title = '';
this.newTutorial.date = '',
this.newTutorial.picture= '',
this.newTutorial.code= '',
toastr.success('Horray! message sent successfully');
this.displayText = 'Nice job!';
this.nameRules = true;
this.emailRules = true;
this.contentRules = true;
this.titleRules = true;
},
markcompleted: function() {
this.displayText = 'hum.. somthing still missing';
}
},
// this functions trow in uid from user in data valu to {uid}
created: function(){
var user = firebase.auth().currentUser;
var uid;
if (user != null) {
uid = user.uid; // The user's ID, unique to the Firebase project. Do NOT use
// this value to authenticate with your backend server, if
// you have one. Use User.getToken() instead.
}
this.newTutorial.userID = uid;
this.newTutorial.content = this.content;
},
beforeDestroy() {
this.editor.destroy()
}
}
</script>
Then my component code for importing the content data :
<script>
export default {
name: 'EditorContent',
props: {
editor: {
default: null,
type: Object
},
value: {
default: "",
type: String
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (!editor || !editor.element) return;
this.editor.setContent(this.value);
this.editor.on("update", ({ getHTML }) => {
this.$emit("input", getHTML());
});
this.$nextTick(() => {
this.$el.appendChild(editor.element.firstChild);
editor.setParentComponent(this);
});
}
},
value: {
handler(value) {
this.editor.setContent(value);
}
}
},
render(createElement) {
return createElement("div");
},
beforeDestroy() {
this.editor.element = this.$el;
}
};
</script>
vue data:
console error:
I see you are trying to implement two-way binding with v-model on a custom element.
You will need to create a prop called content, then emit input events to the parent component. Check this sample code out:
Child component:
<template>
<div class="date-picker">
Month: <input type="number" ref="monthPicker" :value="value.month" #input="updateDate()"/>
Year: <input type="number" ref="yearPicker" :value="value.year" #input="updateDate()"/>
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
updateDate() {
this.$emit('input', {
month: +this.$refs.monthPicker.value,
year: +this.$refs.yearPicker.value
})
}
}
};
</script>
Parent component:
<template>
<div class="wrapper">
<date-picker v-model="date"></date-picker>
<p>
Month: {{date.month}}
Year: {{date.year}}
</p>
</div>
</template>
<script>
import DatePicker from './DatePicker.vue';
export default {
components: {
DatePicker
},
data() {
return {
date: {
month: 1,
year: 2017
}
}
}
})
</script>
For more information, check out this short article
The solution was to remove my content v-model inside the editor content tag and add the v-model I had made for sending the tutorials. here is the working code.
code:
<template>
<div>
<editor-content label="Tutorial content" :editor="editor" v-model="newTutorial.content" />
</div>
</template>
inside the script in my data :
newTutorial: {
first: '',
email: '',
last: '',
language: [],
title: '',
content: '',
date: '',
picture:'',
code: '',
},
inside the script in my send funciton :
addTutorial: function() {
messagesRef.child(this.newTutorial.userID).push(this.newTutorial);
this.newTutorial.first = '';
this.newTutorial.last = '';
this.newTutorial.content = '';
this.newTutorial.email = '';
this.newTutorial.language = '';
this.newTutorial.title = '';
this.newTutorial.date = '',
this.newTutorial.picture= '',
this.newTutorial.code= '',
toastr.success('Horray! message sent successfully');
this.displayText = 'Nice job!';
this.nameRules = true;
this.emailRules = true;
this.contentRules = true;
this.titleRules = true;
},
data in db :
form component code:
<style scoped>
img.preview {
width:200px;
}
.v-btn {
height: 50px !important;
min-width: 50px !important;
}
</style>
<template>
<div id="app">
<v-dialog v-model="dialog" width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn style="z-index:9;" color="blue lighten-1" dark rounded v-bind="attrs" v-on="on" fixed left>
<v-tooltip right >
<template v-slot:activator="{ on, attrs }">
<v-icon fab dark v-bind="attrs" v-on="on">
mdi-plus
</v-icon>
</template>
<img class="monk-ico" src="https://celfonica.s3-us-west-1.amazonaws.com/logos/monk-circle+50px.png">
<span style="display:inline;">
Add Tutorial
</span>
</v-tooltip>
</v-btn>
</template>
<div class="left">
<v-btn color="primary" #click="dialog = false" width="10px">
<v-icon>
mdi-close
</v-icon>
</v-btn>
</div>
<div class="panel-heading">
</div>
<div>
<h1>Tutorial form</h1>
<h3> create one</h3>
<form id="form" class="form-inline" v-on:submit.prevent="addTutorial">
<v-divider class="m-tb-20"></v-divider>
<h4>Author details</h4>
<div class="form-group">
<v-text-field :rules="nameRules" required label="First Name" type="text" id="tutorialFirst" class="form-control" v-model="newTutorial.first">
</v-text-field>
</div>
<div class="form-group">
<v-text-field :rules="nameRules" required label="Last Name" type="text" id="tutorialLast" class="form-control" v-model="newTutorial.last">
</v-text-field>
</div>
<div class="form-group">
<v-text-field :rules="emailRules" required label="Email" type="text" id="tutorialEmail" class="form-control" v-model="newTutorial.email">
</v-text-field>
</div>
<div class="form-goup">
<!-- Img upload input field-->
<div>
<h4 class="m-tb-20">Upload tutorial picture:</h4>
<input class="form-control" type="file" #change="previewImage" accept="image/+">
<br><v-btn class="m-tb-20" #click=" onUpload();"><v-icon>mdi-upload</v-icon></v-btn>
</div>
<div>
<p> Progress: {{uploadValue.toFixed()+"%"}}
<progress :value="uploadValue" max="100"></progress>
</p>
</div>
</div>
<v-divider class="m-tb-20"></v-divider>
<h4>Tutorial content</h4>
<div class="form-group">
<v-select required label="Language"
id="tutorialLanguage" v-model="newTutorial.language"
multiple type="text" autocomplete tags :items="languages" class="form-control">
<template slot="selection" slot-scope="data">
<v-btn>
{{ data.item }}
</v-btn>
</template>
</v-select>
</div>
<div class="form-group">
<v-text-field :rules="titleRules" required label="Tutorial Title" type="text" id="tutorialTitle" class="form-control" v-model="newTutorial.title">
</v-text-field>
</div>
<!--tiptap--> <v-card >
<div >
<editor-menu-bar v-on:submit.prevent="addTutorial" :editor="editor" v-slot="{ commands, isActive }">
<div>
<v-btn :class="{ 'is-active': isActive.bold() }" #click="commands.bold">
<v-icon class="mdi mdi-format-bold mdi-24px"> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.italic() }" #click="commands.italic">
<v-icon class="mdi mdi-format-italic mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.underline() }" #click="commands.underline">
<v-icon class="mdi mdi-format-underline mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.code() }" #click="commands.code">
<v-icon class="mdi mdi-code-tags mdi-24px "> </v-icon>
</v-btn>
<v-btn :class="{ 'is-active': isActive.link() }" #click="commands.link">
<v-icon class="mdi mdi-link mdi-24px"> </v-icon>
</v-btn>
<v-divider></v-divider>
</div>
</editor-menu-bar>
<editor-content label="Tutorial content" :editor="editor" v-model="newTutorial.content" />
</div>
</v-card>
<div class="form-group">
<v-text-field required label="Date" class="form-control" type='date' v-model='newTutorial.date'>
</v-text-field>
</div>
<div class="form-group">
<v-text-field required label="Tutorial Sample Code Link" type="url" id="tutorialCode" class="form-control" v-model="newTutorial.code">
</v-text-field>
</div>
<div>
<br>
</div>
<v-divider class="m-tb-20"></v-divider>
<h4> Preview </h4>
<v-card class="m-tb-20" v-model="newTutorial">
<img class="preview " :src="picture"><br>
<v-card-title class="center">{{ newTutorial.title }} </v-card-title>
<v-card-subtitle> {{ newTutorial.first }} {{ newTutorial.last }} </v-card-subtitle>
<v-divider class="m-tb-20"></v-divider>
<v-card-text>{{ newTutorial.content }}</v-card-text>
<v-card-text>
<h5>{{ newTutorial.language }}</h5>
<h5>{{ newTutorial.email }}</h5>
<h5>{{ newTutorial.date }}</h5>
</v-card-text>
</v-card>
<!-- Form push btn -->
<v-btn class="m-tb-20" #click="markcompleted();" type="submit" small color="primary" dark>
{{ displayText }}
</v-btn>
</form>
</div>
</v-dialog>
</div>
</template>
<script>
import firebase from '../plugins/firebase'
import EditorContent from "../components/EditorContent";
import toastr from 'toastr';
// to debug multiple Fire apps
//if (!firebase.apps.length) {
// firebase.initializeApp(config);
// this.newTutorial.userID= uid;
//}
import { Editor, EditorMenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
let db = firebase.database();
let messagesRef = db.ref('tutorials');
export default {
name: 'tutform',
firebase: {
tutorials: messagesRef
},
components: {
EditorMenuBar,
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new BulletList(),
new OrderedList(),
new ListItem(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: '',
}),
imageData:null,
picture:null,
uploadValue: 0,
dialog: false,
displayText: 'Push me!',
newTutorial: {
first: '',
email: '',
last: '',
language: [],
title: '',
content: '',
date: '',
picture:'',
code: '',
},
languages: [
'Html', 'CSS', 'VUE', 'React', 'Ruby', 'JS', 'SASS', 'Python','PHP','C#','JAVA','Other',
],
nameRules: [
v => !!v || 'you must type something',
v => v.length <= 10 || 'hum.. this monk smelling somthing strange... must be less than 10 characters',
],
emailRules: [
v => !!v || 'E-mail is required',
v => /.+#.+/.test(v) || 'Please enter a valid email containing # ',
],
contentRules: [
v => !!v || 'Content is required amigo!'
],
titleRules: [
v => !!v || 'Tittle is required buddy!',
v => v.length <= 100 || 'Woots!, Lets try making this one shorter'
]
}
},
methods: {
previewImage(event){
this.uploadValue=0;
this.picture=null;
this.imageData=event.target.files[0];
},
onUpload() {
this.picture=null;
const storageRef=firebase.storage().ref(`tutorials/images/${this.imageData.name}`).put(this.imageData);
storageRef.on(`state_changed`, snapshot=>{
this.uploadValue=(snapshot.bytesTransferred/snapshot.totalBytes)*100;
}, error=>{console.log(error.message)},
()=>{this.uploadValue=100;
storageRef.snapshot.ref.getDownloadURL().then((url)=>{
this.picture=url;
this.newTutorial.picture = url;
console.log(this.picture);
toastr.success('Image Uploaded successfully');
})}
)
},
addTutorial: function() {
messagesRef.child(this.newTutorial.userID).push(this.newTutorial);
this.newTutorial.first = '';
this.newTutorial.last = '';
this.newTutorial.content = '';
this.newTutorial.email = '';
this.newTutorial.language = '';
this.newTutorial.title = '';
this.newTutorial.date = '',
this.newTutorial.picture= '',
this.newTutorial.code= '',
toastr.success('Horray! message sent successfully');
this.displayText = 'Nice job!';
this.nameRules = true;
this.emailRules = true;
this.contentRules = true;
this.titleRules = true;
},
markcompleted: function() {
this.displayText = 'hum.. somthing still missing';
}
},
// this functions trow in uid from user in data valu to {uid}
created: function(){
var user = firebase.auth().currentUser;
var uid;
if (user != null) {
uid = user.uid; // The user's ID, unique to the Firebase project. Do NOT use
// this value to authenticate with your backend server, if
// you have one. Use User.getToken() instead.
}
this.newTutorial.userID = uid;
},
beforeDestroy() {
this.editor.destroy()
}
}
</script>
child component:
<script>
export default {
name: 'EditorContent',
props: {
editor: {
default: null,
type: Object
},
value: {
default: "",
type: String
}
},
watch: {
editor: {
immediate: true,
handler(editor) {
if (!editor || !editor.element) return;
this.editor.setContent(this.value);
this.editor.on("update", ({ getHTML }) => {
this.$emit("input", getHTML());
});
this.$nextTick(() => {
this.$el.appendChild(editor.element.firstChild);
editor.setParentComponent(this);
});
}
},
value: {
handler(value) {
this.editor.setContent(value);
}
}
},
render(createElement) {
return createElement("div");
},
beforeDestroy() {
this.editor.element = this.$el;
}
};
</script>
Hope it helps someone, I did this to add tip tap editor.
To explain the question, using the code from documentation as below:
<template>
<div>
<b-form-tags v-model="value" no-outer-focus class="mb-2">
<template v-slot="{ tags, inputAttrs, inputHandlers, addTag, removeTag }">
<b-input-group aria-controls="my-custom-tags-list">
<input
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="New tag - Press enter to add"
class="form-control">
<b-input-group-append>
<b-button #click="addTag()" variant="primary">Add</b-button>
</b-input-group-append>
</b-input-group>
<ul
id="my-custom-tags-list"
class="list-unstyled d-inline-flex flex-wrap mb-0"
aria-live="polite"
aria-atomic="false"
aria-relevant="additions removals"
>
<!-- Always use the tag value as the :key, not the index! -->
<!-- Otherwise screen readers will not read the tag
additions and removals correctly -->
<b-card
v-for="tag in tags"
:key="tag"
:id="`my-custom-tags-tag_${tag.replace(/\s/g, '_')}_`"
tag="li"
class="mt-1 mr-1"
body-class="py-1 pr-2 text-nowrap"
>
<strong>{{ tag }}</strong>
<b-button
#click="removeTag(tag)"
variant="link"
size="sm"
:aria-controls="`my-custom-tags-tag_${tag.replace(/\s/g, '_')}_`"
>remove</b-button>
</b-card>
</ul>
</template>
</b-form-tags>
</div>
</template>
<script>
export default {
data() {
return {
value: ['apple', 'orange', 'banana', 'pear', 'peach']
}
}
}
</script>
The above code gives the output as follows:
But when I have the values as an array of objects instead of short strings how to render the properties of the objects as tag elements?
for example: If I have value: [{name: 'apple', color: 'red'}, {name:'mango', color: 'yellow'}] then how to get the same output as above?
I tried something something like <strong>{{ tag.name }}</strong> and it doesn't work and gives me only empty tags to remove as follows:
Any ideas on how to achieve what I wanted to do here?
What you're trying to do is not support yet according to this issue
You'll have to map your array of objects to a string array and utilize that.
Then once you're ready to "use" your tags, you could map them back based on the original objects.
Here's a rather simple example of what could be done.
new Vue({
el: "#app",
computed: {
mappedTags() {
/* This is case sensitive */
return this.options.filter(option => this.value.includes(option.name))
}
},
data: {
value: [],
options: [{
name: 'Mango',
color: 'Orange'
},
{
name: 'Orange',
color: 'Orange'
},
{
name: 'Lemon',
color: 'Yellow'
},
{
name: 'Apple',
color: 'Red'
},
{
name: 'Banana',
color: 'Yellow'
},
]
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.4.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.2.2/dist/bootstrap-vue.js"></script>
<style>
/* Only added for better visibility on the text */
.text-stroke {
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
</style>
<div id="app" class="p-3">
Valid (<b>Casesensitive</b>) tags - [Banana, Apple, Orange, Mango]
<b-form-tags v-model="value" no-outer-focus class="mb-2">
<template v-slot="{ tags, inputAttrs, inputHandlers, addTag, removeTag }">
<b-input-group aria-controls="my-custom-tags-list">
<b-input
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="New tag - Press enter to add"></b-input>
<b-input-group-append>
<b-button #click="addTag()" variant="primary">Add</b-button>
</b-input-group-append>
</b-input-group>
<ul class="list-unstyled d-inline-flex flex-wrap mb-0">
<b-card
v-for="tag in mappedTags"
:key="tag.name"
:id="`my-custom-tags-tag_${tag.name.replace(/\s/g, '_')}_`"
tag="li"
class="mt-1 mr-1"
body-class="py-1 pr-2 text-nowrap"
>
<strong :style="`color: ${tag.color}`" class="text-stroke">
{{ tag.name }}
</strong>
<b-button
#click="removeTag(tag.name)"
variant="link"
>
Remove
</b-button>
</b-card>
</ul>
</template>
</b-form-tags>
{{ mappedTags }}
</div>
Replace {{ tag.name }} with {{ JSON.parse(tag).name }}.
That should solve the problem.
You have a similar example where I needed the input of the user and its selection and gather does two inputs values inside an array of objects.
You need to add a v-model to your inputs this way you can pass it to a custom function
<b-form-input
v-model="werkTagNombre"
<b-form-select
v-model="werkTagExp"
...
"#click="addingWerkTag({werkTagNombre, werkTagExp})"
Complete template code
<b-form-tags v-model="freelance.tags.nombre">
<template v-slot="{ inputAttrs, inputHandlers }">
<b-input-group aria-controls="tags-list">
<b-form-input
v-model="werkTagNombre"
v-bind="inputAttrs"
v-on="inputHandlers"
placeholder="Seleccione"
class="werk-input werk-shadow-input mr-2"
>
</b-form-input>
<b-input-group-append>
<b-form-select
v-model="werkTagExp"
:options="experienciaOptions"
class="werk-input werk-shadow-input ml-2 mr-2"
>
</b-form-select>
</b-input-group-append>
<b-input-group-append>
<b-button class="shadow-none ml-2" style="border-radius:6px;"#click="addingWerkTag({werkTagNombre, werkTagExp})"> ADD</b-button>
</b-input-group-append>
</b-input-group>
<span style="font-size: 10px; color: #a5a5a5;">TAGS:</span>
<ul id="tags-list" class="list-unstyled d-inline-flex flex-wrap mb-0 mt-1">
<b-badge
v-for="(werkTag, index) in freelance.tags"
:key="index"
tag="li"
class="mx-1 my-1 werk-tags"
>
{{werkTag.nombre}} {{werkTag.experiencia}}
</b-badge>
</ul>
</template>
</b-form-tags>
Script
export default {
data() {
return {
freelance: {
tags: [
{
nombre:" Test name",
experiencia:1
}
],
}
...
werkTagNombre: '',
werkTagExp: '0',
experienciaOptions: [
{ value: '0', text: 'Selecciona'},
{ value: 1, text: 'menor a 2 años'},
{ value: 2, text: '3 a 5 años'},
{ value: 3, text: 'mayor a 5 años'},
]
...
methods:{
addingWerkTag(valor){
this.freelance.tags.push({nombre: valor.werkTagNombre, exp: valor.werkTagExp});
},
...
}
If you want you can also pass the addTag default function like this:
Inside the template slot add the functions like "addtag"
<template v-slot="{ inputAttrs, inputHandlers, addTag }">
...
So you can add it to your custom function like this:
#click="addingWerkTag({ werkTagNombre, werkTagExp, addTag })"
I have a VueJS template which includes a table and a function that sends out a request to the backend API with the specified key and then receives back a JSON response.
What I am having trouble is with writing the function so it takes the output it receives and then prints into the table.
JSON Sample:
{"driver_id":1,"driver_name":"{driver_first_name}, {driver_last_name}","driver_truck":"13","driver_trailer":"83","driver_status":"driving","has_violations":false},
{"driver_id":2,"driver_name":"{driver_first_name}, {driver_last_name}","driver_truck":"58","driver_trailer":"37","driver_status":"sleeping","has_violations":true},
{"driver_id":3,"driver_name":"{driver_first_name}, {driver_last_name}","driver_truck":"80","driver_trailer":"27","driver_status":"driving","has_violations":true},
Here is my code:
<template>
<b-container fluid>
<!--Search Controls-->
<b-row>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Filter" class="mb-0">
<b-input-group>
<b-form-input v-model="filter" placeholder="Type to Search" />
<b-input-group-append>
<b-btn :disabled="!filter" #click="filter = ''">Clear</b-btn>
</b-input-group-append>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Sort" class="mb-0">
<b-input-group>
<b-form-select v-model="sortBy" :options="sortOptions">
<option slot="first" :value="null">-- none --</option>
</b-form-select>
<b-form-select :disabled="!sortBy" v-model="sortDesc" slot="append">
<option :value="false">Asc</option>
<option :value="true">Desc</option>
</b-form-select>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Sort direction" class="mb-0">
<b-input-group>
<b-form-select v-model="sortDirection" slot="append">
<option value="asc">Asc</option>
<option value="desc">Desc</option>
<option value="last">Last</option>
</b-form-select>
</b-input-group>
</b-form-group>
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Per page" class="mb-0">
<b-form-select :options="pageOptions" v-model="perPage" />
</b-form-group>
</b-col>
</b-row>
<!--Search Controls-->
<!-- Main table element -->
<b-table show-empty
stacked="md"
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-direction="sortDirection"
#filtered="onFiltered"
>
<template slot="truck_number" slot-scope="row">{{row.value.driver_truck}}</template>
<template slot="trailer_number" slot-scope="row">{{row.value.driver_trailer}}</template>
<template slot="violation_date" slot-scope="row">{{row.value.violation_date}}</template>
<!-- View Specific Violation -->
<template slot="actions" slot-scope="row">
<b-button #click="getSpecificViolation(logbook_id)" id="view_specific_violation">View Violation</b-button>
</template>
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" class="my-0" />
</b-col>
</b-row>
</b-container>
</template>
<script>
export default {
name: 'List of Violations',
data () {
return {
items: items,
fields: [
{ key: 'truck_number', label: 'Truck Number', sortable: true, 'class': 'text-center' },
{ key: 'trailer_number', label: 'Trailer Number', sortable: true, 'class': 'text-center' },
{ key: 'violation_date', label: 'Date of Violation' },
{ key: 'actions', label: 'Actions' }
],
currentPage: 1,
perPage: 5,
totalRows: items.length,
pageOptions: [ 5, 10, 15 ],
sortBy: null,
sortDesc: false,
sortDirection: 'asc',
filter: null,
}
},
},
computed: {
sortOptions () {
return this.fields
.filter(f => f.sortable)
.map(f => { return { text: f.label, value: f.key } })
}
},
created() {
this.id = this.$route.params.id;
},
methods: {
onFiltered (filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length
this.currentPage = 1
}
navigate() {
router.go(-1);
}
//Send Request all Violations for Driver_ID
axios.post('http://localhost:3030/api/allViolations/driverID',
this.driver_id, // the data to post
{ headers: {
'Content-type': 'application/x-www-form-urlencoded',
}
}).then(response => ....???); //print api json response
}
}
</script>
First of all, your code structure is disrupted. You shouldn't define computed,created and methods functions outside the export default module.
I created a sandbox for you just to rearrange the code and assign the value of response to items variable.
.then(response => (this.items = response.data));
If i assume correctly you are using Bootstrap-Vue plugin. After obtaining the items object you can follow the documentation from here
First of all, your initial value for items in data should be [] (an empty array).
After that, pretty much all you should need to do is set this.items = response.data when you get the JSON back. Although the code you pasted seems to be bad (the axios.post call is directly under methods).