Custom Angular 2+ Stepper module - angular8

At the beginning I would like to say that I know that there is ready to use stepper from CDK or Materials but I would like to create "my own" in order to learning angular 8.
To be honest I don't know where I should search help now that is why I decided to write here. Maybe someone give me some advices and I will publish my work at the end.
So let's begin:
The index HTML code looks like that:
<stepper>
<step>
<ng-template step-label>First name</ng-template>
<input placeholder="First name" required>
<div>
<button >Next</button>
</div>
</step>
<step>
<ng-template step-label>Last name</ng-template>
<input placeholder="Last name" required>
<div>
<button >Next</button>
</div>
</step>
<step>
<ng-template step-label>Address name</ng-template>
<input placeholder="Address" required>
<div>
<button >Next</button>
</div>
</step>
</stepper>
Components + directive
#Directive({selector: '[step-label]'})
export class StepLabel{
constructor(public template: TemplateRef<any>){};
}
#Component({selector: 'step-header', templateUrl: 'step-header.html'})
export class StepHeader{
#Input() label: TemplateRef<StepLabel>;
}
#Component({
selector: 'step',
template: '<ng-template><ng-content></ng-content></ng-template>'
})
export class Step{
#ViewChild(TemplateRef, {static: true}) content: TemplateRef<Step>;
#ContentChild(StepLabel, {static: true}) label: TemplateRef<StepLabel>;
}
#Component({selector: 'stepper', templateUrl: './stepper.html'})
export class Stepper implements AfterContentInit {
stepsArray = [];
#ContentChildren(Step) steps: QueryList<Step>;
ngAfterContentInit() {
this.stepsArray = this.steps.toArray();
}
}
stepper.html
<div class="header-steps" style="bordeR: 1px solid green; padding: 10px;">
<ng-container *ngFor="let step of stepsArray; let i = index; let isLast = last">
<step-header [label]="step.label"></step-header>
</ng-container>
</div>
<div style="border: 1px solid blue; padding: 5px; margin: 5px;" class="content-steps">
<div *ngFor="let step of stepsArray; let i = index">
<ng-container *ngIf="step" [ngTemplateOutlet]="step.content"></ng-container>
</div>
</div>
step=header.html
<div class="step-label">
<ng-container *ngIf="label" [ngTemplateOutlet]="label.template"></ng-container>
</div>
I have updated whole "basic" project. It works.
I have one quesition. Why in the Directive StepLabel have to be constructor.
I tried to add somethink like that :
#ViewChild(TemplateRef, {static: true}) template: TemplateRef<StepLabel>;
but it doesn't work.

In your step component's template wrap ng-content in ng-template
template: '<ng-template><ng-content></ng-content></ng-template>'
And then get template using ViewChild
#ViewChild(TemplateRef, { static: true }) content: TemplateRef<any>;
In the end you'll get something like:
#Component({
selector: 'step',
template: '<ng-template><ng-content></ng-content></ng-template>'
})
export class Step {
#ViewChild(TemplateRef, { static: true }) content: TemplateRef<any>;
}

Related

How can I add a task to a list in my Vue 2 to-do app?

I am trying to add a task to a tasklist in Vue based on the input and add task button, but I keep getting the error "taskList is not defined". Does anybody see how to fix this problem? The code is as following:
<template>
<div id="input">
<form>
<input v-model="task.name">
<button v-on:click="addTask" v-bind:value="task.name">+</button>
</form>
<ol>
<div v-for="task in taskList" :key="task.id">
{{ task.name }}
<div v-if="task.completed">
<h2> Done </h2>
</div>
<div v-else>
<h2> Not done</h2>
</div>
</div>
</ol>
</div>
</template>
<script>
export default {
name: 'AddTask',
data: function() {
return {
taskList: [
{
name: 'task', completed: false, id: 3
}
] }
},
methods: {
addTask: function (task) {
taskList.push(task);
alert('test');
}
}
}
</script>
Ps. any other Vue tips are welcome as well.
You need to separate out your taskList and the current task you're adding, decouple it as a new object, then add it to your taskList array.
When referring to items in your data object you need to use the this keyword – e.g this.taskList rather than taskList:
new Vue({
el: "#app",
data: {
id:1,
taskList: [],
currentTask:{
completed:false,
name:'',
id:this.id
}
},
methods: {
addTask: function() {
let newTask = {
completed:this.currentTask.completed,
name:this.currentTask.name,
id:this.currentTask.id
}
this.taskList.push(newTask);
this.id++;
//alert('test');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="input">
<ol>
<li v-for="task in taskList" :key="task.id">
{{ task.name }}
<input type="checkbox"
:checked="task.completed"
#change="task.completed = !task.completed">
<span v-if="task.completed">
Done
</span>
<span v-else>
Not Done
</span>
</li>
</ol>
<input type="text" v-model="currentTask.name">
<button v-on:click="addTask">+</button>
</div>
</div>
From what I see in your template you use tasklist but you define it as taskList You will want to make sure your names are in the same case. Usually you'll see camelCase in vue, but other popular ones are snake_case and PascalCase

Vee-Validate Showing a BlankPage when doing the configurations

I using Vue3.0 and also use Vee-validate to validate my form. But I don't know why it keep showing blank page just like in this picture
I already do what they said in the documentations, here's my code
<form class="wrap" id="signup-form col-lg-5" #submit.prevent="processForm">
<div class="row mb-5">
<router-link :to="{'name': 'Home'}">
<span class="iconify" data-icon="ion:return-up-back-outline" data-width="25" data-height="25"></span>
<button class="btn">Back</button>
</router-link>
</div>
<!-- full name -->
<div class="form-group row">
<label for="name">Full Name <span class="text-danger">*</span></label>
<ValidationProvider rules="positive|odd" v-slot="err">
<input type="text" class="form-control" v-model.trim="name">
<span>{{ err.errors[0] }}</span>
</ValidationProvider>
</div>
<!-- submit button -->
<div class="row d-flex align-items-center">
<button type="submit" class="btn btn-outline-dark col-sm-4">Submit</button>
<p style="cursor:pointer;" class="col-sm-7">
<router-link :to="{name:'Login'}">Has an account? Login</router-link>
</p>
</div>
</form>
And here's my script
<script>
import { ValidationProvider } from 'vee-validate';
import { extend } from 'vee-validate';
extend('odd', value => {
return value % 2 !== 0;
});
extend('positive', value => {
return value >= 0;
});
export default {
name: 'RegisterForm',
components: {
ValidationProvider
},
data: function(){
return{
name: '',
}
},
methods: {
processForm() {
this.$emit('form-submit',
{
'name': this.email,
'password': this.password,
})
}
}
}
</script>
What should I change from the code? By the way, Is Vee-Validator should be assigned in main.js?
Looks like you are using vee-validate v3.x, it isn't compatible with Vue 3
vee-validate v4 was released recently that supports Vue 3, but with a completely different API
https://vee-validate.logaretm.com/v4/

Vue cli 3 props (parent to child) child never update after the variable in parent has changed

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.

vue check value in textfield

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"
}}

Check box selection issue in vue.js

Please look the below image
I have two users and their allowed channels.
My issue is :
When I click one channel of a user, the same channel is also get checked of other user. For example:
When I click Commissions of user Deepu, the channel Commissions of user Midhun also get checked.
I have to avoid that, the clicked channel of the user should only selected, not other user's.
I tried this
<v-data-table
:headers="headers"
:items="UserChannels"
:loading="datatableloading"
class="elevation-1"
>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.username }}</td>
<td class="text-xs-left">
<ul class="channel-listitems">
<li v-for="item in Channels">
<input type="checkbox" class="channel-checkbox" :value="item.id" v-model="checkedChannels">
{{ item.channel_name }}
</li>
</ul>
<br>
<span>Checked names: {{ checkedChannels }}</span>
</td>
</template>
</v-data-table>
I am using Vue.js for doing this.
You can create mapping dict for each user Channels selection.
Please refer following codepen - https://codepen.io/Pratik__007/pen/oNgaXeJ?editors=1010
In data
checkedChannels:{},
Created
created () {
console.log(this.Channels)
let vm = this;
this.Users.map(function(item){
vm.$set(vm.checkedChannels,item['name'],[])
return item;
})
},
The official Vue docs states, you can use v-model on multiple checkboxes using the same array. ( Docs : https://v2.vuejs.org/v2/guide/forms.html#Checkbox )
Also working example : https://codesandbox.io/s/hungry-kepler-t7mkw
Select elements for array with checkboxes and v-model
<template>
<div id="app">
<!-- First, iterate over all users -->
<div v-for="(user, index) in users" class="user">
<p class="name">{{ user.username }} - Checked Channels {{user.channels}}</p>
<!-- Create checkbox for each available channel, and v-model it to user.channels -->
<div class="channel">
<label v-for="(channel, index) in availableChannels">
{{channel}}
<input type="checkbox" v-bind:value="channel" v-model="user.channels">
</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
users: [
{
username: "User1",
channels: ["channel1", "channel2"]
},
{
username: "User2",
channels: ["channel3"]
}
],
availableChannels: [
"channel1",
"channel2",
"channel3",
"channel4",
"channel5"
]
};
}
};
</script>
<style>
.user {
min-height: 100px;
display: flex;
align-items: center;
border: 1px solid black;
margin-bottom: 10px;
justify-items: center;
}
.name,
.channel {
flex: 1;
}
.channel {
min-height: 100px;
display: flex;
flex-direction: column;
}
</style>