Angular add toggle switch inside datepicker calendar - html

Is it possible to add the toggle switch inside the calender? I want to put it at the top of the calender when the calendar is open so the user can choose to show date details by the toggle switch
<form #uploadForm="ngForm" (keydown.enter)="$event.preventDefault()">
<div class="input-wrapper">
<mat-form-field>
<input
id="date"
name="date"
[disabled]="datePickerDisabled || uploadForm.submitted"
[matDatepicker]="datepicker"
placeholder="Expiration date"
autocomplete="off"
matInput
required />
<mat-datepicker-toggle matSuffix [for]="datepicker"></mat-datepicker-toggle>
<mat-datepicker touchUi #datepicker></mat-datepicker>
</mat-form-field>
<div class="tooltip">This field is required.</div>
<mat-slide-toggle
(change)="setMaxExpirationDate($event)">Show Date Details</mat-slide-toggle>
</div>
</form>

I think you has two aproachs
1.-Customize the header, see the docs:customizing header
From this SO answer. replicate the header:
/** Custom header component for datepicker. */
#Component({
selector: 'example-header',
template: `
<mat-slide-toggle
(change)="setMaxExpirationDate($event)">Show Date Details</mat-slide-toggle>
<div class="mat-calendar-controls">
<button mat-button type="button" class="mat-calendar-period-button"
(click)="currentPeriodClicked()" [attr.aria-label]="periodButtonLabel"
cdkAriaLive="polite">
{{periodButtonText}}
<div class="mat-calendar-arrow"
[class.mat-calendar-invert]="calendar.currentView != 'month'"></div>
</button>
<div class="mat-calendar-spacer"></div>
<ng-content></ng-content>
<button mat-icon-button type="button" class="mat-calendar-previous-button"
[disabled]="!previousEnabled()" (click)="previousClicked()"
[attr.aria-label]="prevButtonLabel">
</button>
<button mat-icon-button type="button" class="mat-calendar-next-button"
[disabled]="!nextEnabled()" (click)="nextClicked()"
[attr.aria-label]="nextButtonLabel">
</button>
</div>
</div> `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExampleHeader extends MatCalendarHeader<any> {
/** Handles user clicks on the period label. */
currentPeriodClicked(): void {
this.calendar.currentView = this.calendar.currentView == 'month' ? 'multi-year' : 'month';
}
}
(change the .html to add the controls you want)
2.-Enclose the mat-datepicker in a mat-menu, like it showed in this another SO answer
Updated Access to an element of header it's not very easy. but control the toogle in easy if we use an intermediate service
Imagine you has a service like
#Injectable({
providedIn: "root"
})
export class CalendarService {
private _event = new Subject<void>();
public onEvent = this._event as Observable<any>;
constructor() {}
command(value: any) {
this._event.next(value);
}
}
You can inject the service in the constructor of customHeader
constructor(
private _calendar: MatCalendar<D>,
private _dateAdapter: DateAdapter<D>,
#Inject(MAT_DATE_FORMATS) private _dateFormats: MatDateFormats,
cdr: ChangeDetectorRef,
private service: CalendarService
)
Then, if our toogle call a function
<mat-slide-toggle #toogle (change)="toogleChange($event)">
Show Date Details
</mat-slide-toggle>
The function becomes like
toogleChange(event: any) {
this.service.command(event);
}
Just in ngOnInit in the component subscribe to the service
ngOnInit() {
this.service.onEvent.subscribe(res => {
console.log(res.checked);
});
}
You can see in stackblitz

Related

Why does when i toggle one mat-radio button all in other cards get toggled too?

My home.component.html file contains
<div class="grid grid-cols-5 gap-4 pt-10">
<div *ngFor="let card of cards" class="">
<div *ngIf="card==null;then nil else notnil"></div>
<ng-template #nil></ng-template>
<ng-template #notnil>
<mat-card class="">
<mat-card-header>
<mat-card-title>{{decode(card.question.toString())}}</mat-card-title>
<mat-card-subtitle>Type: {{card.type}} , Difficulty: {{card.difficulty}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-radio-group aria-label="Select an option" [(ngModel)]="valueFromRadio">
<mat-radio-button class="p-2" value="1">{{decode(card.correct_answer)}}</mat-radio-button>
<mat-radio-button value="0" class="p-2" *ngFor="let incorrect_answer of card.incorrect_answers">{{decode(incorrect_answer)}}</mat-radio-button>
</mat-radio-group>
</mat-card-content>
<button mat-button type="submit" (click)="getAns(valueFromRadio,card.question)">Submit</button>
</mat-card>
</ng-template>
</div>
<h1 class="w-screen text-8xl">Score: {{count}}</h1>
</div>
My home.component.ts file contains
const route:string="https://opentdb.com/api.php?amount=10"
export class Card{
constructor(public category:string,
public type:string,
public difficulty:string,
public question:string,
public correct_answer:string,
public incorrect_answers:string[]) {
}
}
export class ResponseApi{
constructor(public response_code:number,
public results:Card[]) {
}
}
export class HomeComponent implements OnInit {
valueFromRadio=0;
fetchedData: ResponseApi ={
response_code:1,
results:[]
};
count=0
cards:Card[]=[]
answer: number[]=[];
constructor(private http:HttpClient) { }
getAns(value:number,question:string){
console.log("sd",question)
if (value==1){
this.count++
}
for (let i = 0; i<this.cards.length;i++){
if (this.cards[i].question==question){
this.cards[i]==null
}
}
}
private async fetchData(){
this.http.get<any>(route).subscribe(
res=>{
this.fetchedData=res
this.fetchedData.results.map((value, index) => {
this.cards[index]=value
})
console.log("1222",this.cards)
}
);
}
ngOnInit(): void {
this.fetchData()
console.log(this.cards)
}
decode(s: string) {
return decode(s)
}
}
When I press the card.correct_answer, all the other cards correct answer gets toggled too.
Also i want to remove the card when it gets submitted but I don't know how to.
The card.question also doesn't seem to work for me. I a using the latest stable angular and also use lazy loading if that's relevant to my problems.
You're using the banana in a box syntax for your radio buttons, which is used for two way data binding.
Unless you need to select a radio button from your component, you could switch to one way data binding, e.g:
<mat-radio-group aria-label="Select an option" (change)="radioValueCatcher($event.value)">
<mat-radio-button class="p-2" value="1">{{decode(card.correct_answer)}}</mat-radio-button>
<mat-radio-button value="0" class="p-2" *ngFor="let incorrect_answer of card.incorrect_answers">{{decode(incorrect_answer)}}</mat-radio-button>
</mat-radio-group>
Then in your component:
radioValueCatcher(clickEvent) {
// Validate whatever value comes back, do something else with it, etc
this.valueFromRadio = clickEvent.value;
}
Edit: Changed to be material specific using the (change) event. If you're using material version >= 6 you'll have to use (selectionChange).

How to change content in a div dynamically via radio button control?

I am trying to build a form that will intake scheduling information that can potentially come in multiple different formats (weekly, monthly, other). I was planning on having a schedule section of the form where the user selects the type of schedule via a radio button group. If the weekly option is chosen, a group of checkboxes will appear with weekdays so you can select the days of the week. if monthly, then the day of the month can be chosen, and so on. I have tried using the *ngIf way of making things appear, and it isn't working, and I'm getting no error messages. Any ideas on how to implement this?
I am using:
-Angular material elements
-Angular 2 (8)
-Angular reactive forms
I have a portion of this implemented already, below is the code for the radio buttons and the first schedule portion I want to hide (apologies for the poor formatting of the code, still figuring out Stack Overflow):
<form [formGroup] = "SchedInfo" (ngSubmit)="onSubmit()">
<mat-radio-group formControlName= "sType" (ngSubmit)= "onSubmit()">
<mat-radio-button type="radio" [value] ="true" [checked] = "value">Weekly</mat-radio-button>
<mat-radio-button type="radio" [value] ="false" [checked] = "!value">Monthly</mat-radio-button>
</mat-radio-group>
<div class = "weeklyS" *ngIf= "sType.value">
<br>
<!-- possibly need to resturcture the section below -->
<mat-checkbox formControlName= "mo">Monday</mat-checkbox>
<mat-checkbox formControlName= "tu">Tuesday</mat-checkbox>
<mat-checkbox formControlName= "we">Wednesday</mat-checkbox>
<mat-checkbox formControlName= "th">Thursday</mat-checkbox>
<mat-checkbox formControlName= "fr">Friday</mat-checkbox>
<mat-checkbox formControlName= "sa">Saturday</mat-checkbox>
<mat-checkbox formControlName= "su">Sunday</mat-checkbox>
</div>
In the end, my goal for this is to have a schedule module that can be switched between several different input methods.
Also:
should I have one div that is repopulated with each change of selection, or should I have multiple divs that show/ hide depending on the selection?
What you actually need to do is to bind the value of your radio correctly and how to get the value back. This was the simples example I could come up with.
First our template code
<form [formGroup]="myForm" (submit)="onSubmit(myForm.value)" novalidate>
<label id="example-radio-group-label">Pick your Type</label>
<mat-radio-group class="example-radio-group" formControlName="sType">
<mat-radio-button class="example-radio-button" *ngFor="let sfType of scheduleTypes" [value]="sfType">
{{sfType}}
</mat-radio-button>
</mat-radio-group>
<ng-container [ngSwitch]="getScheduleType()">
<div *ngSwitchCase="'Weekly'">
Weekly Content
</div>
<div *ngSwitchCase="'Monthly'">
Monthly Content
</div>
</ng-container>
</form>
Now the component code:
import { Component, OnChanges, OnDestroy, OnInit, Output, ViewChild } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
export interface DataModel {
sType: string;
}
/**
* #title Radios with ngModel
*/
#Component({
selector: 'radio-ng-model-example',
templateUrl: 'radio-ng-model-example.html',
styleUrls: ['radio-ng-model-example.css'],
})
export class RadioNgModelExample {
myForm: FormGroup;
//List of the schedule types. You might want to change this to an ENUM
scheduleTypes: string[] = ['Weekly', 'Monthly'];
constructor(private formBuilder: FormBuilder) {
this.buildForm();
}
//Remember to build your form properly
public buildForm() {
this.myForm = this.formBuilder.group({
sType: [null]
});
}
public onSubmit(data: DataModel) {
//Submit Data Here
}
public getScheduleType() {
//Get the value of your stypeControl
return this.myForm.controls['sType'].value;
}
}
Live Example

Angular 6 - How to submit one area of a form (but not the entire form)

I am trying to submit single areas of a html page when a user clicks a 'Save' button. The html page consists of a number of tasks that can be edited, when the user clicks the save button I want the single task to be submitted to the server i.e. not all tasks to the server. What is the best way to do this. Below is my code:-
HTML:-
<mat-accordion multi="true">
<div class="task-element" *ngFor="let task of selectedDayTasks">
<div class="expansion-panel">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-form-field class="title-box">
<input matInput
value="{{ task.title }}">
</mat-form-field>
<!--{{ task.title }}-->
</mat-expansion-panel-header>
<mat-form-field class="description-box">
<textarea matInput value="[(task.description)]"></textarea>
</mat-form-field>
<!--<p>{{ task.description }}</p>-->
<mat-action-row>
<button mat-button color="warn" (click)="onSave(task._id, task.title, task.description, task.complete, task.day)">SAVE</button>
</mat-action-row>
</mat-expansion-panel>
</div>
<div class="done-button">
<button class="green-btn" *ngIf="task.complete" (click)="taskStateChange(task._id)" mat-fab>YES</button>
<button *ngIf="!task.complete" (click)="taskStateChange(task._id)" color="warn" mat-fab>NO</button>
</div>
</div>
</mat-accordion>
Task.component.ts:-
import { Component, Input } from '#angular/core';
import { Task} from './task.model';
import { DailyTaskService } from './daily-task.service';
#Component({
selector: 'app-daily-tasks',
templateUrl: './daily-tasks.component.html',
styleUrls: ['./daily-tasks.component.css']
})
export class DailyTasksComponent implements OnInit, OnChanges {
#Input() selectedDay: string;
tasks: Task[] = [];
selectedDayTasks: Task[] = [];
constructor(public dailyTaskService: DailyTaskService) {}
ngOnInit() {
this.dailyTaskService.getTasks()
.subscribe(taskData => {
this.tasks = taskData;
this.tasks.forEach((task)=> {
if (task.day == 'Monday') {
this.selectedDayTasks.push(task);
}
});
});
}
onSave(_id: number, title: string, description: string, complete: boolean, day: string ) {
this.dailyTaskService.updateTask(
_id,
title,
description,
complete,
day
);
}
}
I worked it out, I needed to use [(ngModel)] i.e. -
<input matInput [(ngModel)] = {{task.title}}">

Is there an onHidden event for the ngb-datepicker? If there isn't how do we add events to custom components in angular?

I am using the datepicker from this source: https://ng-bootstrap.github.io/#/components/datepicker/api
And I would like to check if the ngb-datepicker is closed because I need to change the text of the button where it is triggered.
template:
<button (click)="dp.open(); changeText();">{{buttonText}}</button>
<ngb-datepicker #dp
[(ngModel)]="model"
(onHidden)="changeButtonText2()" <<---is this possible? >
/>
ts:
import {Component} from '#angular/core';
import {NgbCalendar} from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'ngbd-datepicker-basic',
templateUrl: './datepicker-basic.html'
})
export class NgbdDatepickerBasic {
buttonText: string = 'Open Calendar'
constructor(private calendar: NgbCalendar) {
}
changeText() {
this.buttonText = 'The Calendar is Open';
}
changeButtonText2() {
this.buttonText = 'Open Calendar'
}
}
The ngx-bootstrap datepick have this but it seems the ngb-datepicker does not implement this feature. Can someone help me create a workaround so I don't have to use the ngx-bootstrap just for this? I already added some styling so...
Thanks :)
If you are using popup datepicker and only want to change text button
<form class="form-inline">
<button (click)="dpk.toggle()">{{ dpk.isOpen() ? 'The Calendar is Open' : 'Open Calendar' }}</button>
<div class="form-group">
<input class="form-control" placeholder="yyyy-mm-dd"
name="dp" [(ngModel)]="model" ngbDatepicker #dpk="ngbDatepicker">
</div>
</form>
if you want to custom more code, use this:
#ViewChild('dpk') dpk: NgbDatepicker;
ngAfterViewInit() {
this.dpk['close'] = () => {
if (this.dpk['isOpen']) {
// super origin event
this.dpk['_vcRef'].remove(this.dpk['_vcRef'].indexOf(this.dpk['_cRef'].hostView));
this.dpk['_cRef'] = null;
this.dpk['_closed$'].next();
// custom code
console.log('closed');
}
};
}

Angular 2+ initialize value of input

I am trying to implement a Component which corresponds to a Bootstrap modal including an input. The input is hooked to a variable in the Component class via [(ngModel)]="..." This works if I enter text inside the input (the variable's value gets updated).
What I want to do is that when this component's show() method gets called the input should be populated with text passed in as a parameter. This does not seem to work and I can't figure out how I can set the initial text passed in as a parameter (without using jQuery).
Here's the relevant code:
editdialog.component.html
<div id="edit_todo_modal" class="modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit todo</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Editing todo: {{currentText}}</p>
<div class="row">
<div class="col-md-12">
<!-- THIS IS WHERE I WANT THE INITAL TEXT -->
<input id="edit-todo-modal-input" type="text" class="form-control" [(ngModel)]="currentText">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">Save changes</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
editdialog.component.ts
import { Component } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { ListComponent } from './list.component';
import { Injectable } from '#angular/core';
declare var jQuery : any;
#Injectable()
#Component({
selector: 'edit-todo-dialog',
templateUrl: './editdialog.component.html',
styleUrls: ['./editdialog.component.css']
})
export class EditTodoDialogComponent{
currentText: string = "";
index: number;
/* I want to use this method to set the initial value */
show(index: number, text: string): void {
this.currentText = text;
this.index = index;
jQuery("#edit-todo-modal-input").val(this.currentText); // I don't want to use jQuery for showing the initial value, however this works
jQuery("#edit_todo_modal").modal(); // show bootstrap modal
}
}
Thanks in advance.
UPDATE
The show()method gets called from this component
import { Component } from '#angular/core';
import { ListService } from './list.service';
import { OnInit } from '#angular/core';
import { EditTodoDialogComponent } from './editdialog.component';
/**
* The main todo list component
*/
#Component({
selector: 'list-component',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css'],
providers: [ListService, EditTodoDialogComponent]
})
export class ListComponent implements OnInit {
private listService: ListService;
private editTodoDialog: EditTodoDialogComponent;
/* ... */
constructor(listService: ListService, editTodoDialog: EditTodoDialogComponent) {
this.listService = listService;
this.editTodoDialog = editTodoDialog;
}
ngOnInit(): void {
this.getTodos();
}
/* ... */
// TO BE IMPLEMENTED
showEditTodoDialog(index: number) : void {
this.editTodoDialog.show(index, this.todos[index]);
}
}
The event is hooked like this:
<li class="list-group-item" *ngFor="let todo of todos; let i = index">
<div class="todo-content">
<p class="todo-text" (dblclick)="showEditTodoDialog(i)">
{{todo}}
</p>
</div>
<div class="todo-close">
<button (click)="removeTodo(i)" class="btn btn-danger btn-sm">
<i class="fa fa-remove"></i>
</button>
</div>
</li>
The problem is that you are calling the show from ListComponent by using the componentReference.
You should not do that to pass information between components .
You should either use a #Input and #Output i:e Event Emitters if these component have Parent child relationship else the best way is to go for Shared Services where once you load he data to the service the other component is notified of the change and subscribes to the new data.
More info on how to use parent child link
More info on how to use shared serviceslink
Have you tried value?:
<input id="edit-todo-modal-input" type="text" class="form-control" [value]="currentText" ngModel>
For objects, use ngValue:
<input id="edit-todo-modal-input" type="text" class="form-control" [ngValue]="currentText" ngModel>