angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.37k stars 6.75k forks source link

Month and/or year selecion in DatePicker #4853

Open julianobrasil opened 7 years ago

julianobrasil commented 7 years ago

Bug, feature request, or proposal:

Feature

What is the expected behavior?

User being able to select a month in a year, or just a full year number. The component already have a startView attribute. It could have also a view attribute so the component could be configured to show just months of the year, or years.

What is the current behavior?

The user can select a full date only (day, month and year).

What is the use-case or motivation for changing an existing behavior?

Although the design docs does mention that will be a date & time option in the future, it does not mention this feature (at least, not explicitly). It's sometimes useful to set the view of a datepicker to allow the selection of just a month or a year. Maybe the problem is related to the date object, that demands a complete date. But it can be left to the developer the control of what is being grabbed from the datepicker, for example:

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

all

Is there anything else we should know?

no

fxck commented 7 years ago

it will be added eventually https://github.com/angular/material2/issues/675#issuecomment-290670361 (and following discussion)

mmalerba commented 6 years ago

dup of #5845

mmalerba commented 6 years ago

oh whoops, misread this, not a dup

julianobrasil commented 6 years ago

@mmalerba, they are very similar, in fact 😄

This component let me choose the year (it returns an integer instead of a Date or Moment object):

image

Returning an integer is an alternative when you use a datepicker configured to work in Month or Year mode. It's better than just use a regular input with type=number

julianobrasil commented 6 years ago

This is a first approach of a wrapper to the datepicker in order to achieve some kind of pickers I need in a project of mine. Maybe it can help if someone needs something like a month picker. It's not fully tested: https://stackblitz.com/edit/angular-mulitdate-picker-demo

@mmalerba, the yearSelected and monthSelected outputs really make the picker more flexible. But you know what? In this particular project, I also need a week picker and a semester picker. I worked it around using filters. Is this kind of feature too far from the final goal planned for this component? Month and year pickers are pretty common, but week picker is kind of rare and semester picker... I think I'm the only one in the world who is using it. hahaha

mmalerba commented 6 years ago

@julianobrasil I want to try to abstract out the concept of a calendar view, have it be some interface that people can provide their component as. Then allow people to put custom views in the calendar and have the calendar interact with them via that interface. Similar idea to how the form field works with custom form field controls, haven't gotten the time to work on it yet though.

Iznogood1 commented 6 years ago

@Julino,

thanks a lot fot your month picker Excatly what I need !

krishragu12395 commented 6 years ago

@julianobrasil is that feature is added? . If yes means please elaborate that. Thanks in advance.

julianobrasil commented 6 years ago

@krishragu12395, if you are referring to the year picker and month picker, strictly speaking, no, they are not available yet. If you are asking whether it's possible to emulate these features, fortunately, the answer is yes.

However, this demands the 6-beta versions of material to be installed because you'll use the new @Outputs: yearSelected and monthSelected (see the stackblitz demo in my comment above). For now, you can read about them over here as the docs site is likely to be updated just when the team releases the final version.

krishragu12395 commented 6 years ago

Thank you @julianobrasil ...

dojchek commented 6 years ago

Month, week, quarter, semester, etc.. Would be awesome if we would have an adapter as mentioned above as it would cover any kind of custom input format. Quarters/Week/Month formatters are all supported by moment and date-fns libs. Is there any chance that something like this will get implemented?

julianobrasil commented 6 years ago

@dojchek, at some point, there will be a way to have all these custom pickers as @mmalerba mentioned in https://github.com/angular/material2/issues/4853#issuecomment-365785545

jdewell commented 6 years ago

@julianobrasil, in your demo you have the line: import { default as _rollupMoment, Moment } from 'moment';

I get an error saying: .../node_modules/moment/moment has no exported member 'default'. Ideas?

julianobrasil commented 6 years ago

Yeah... in my projects I use, instead:

import * as moment from 'moment';
type Moment = moment.Moment;
jdewell commented 6 years ago

Ahh, ok. Thanks

Iznogood1 commented 6 years ago

Onother way to avoid the "moment has no exported member 'default'" error is to set "allowSyntheticDefaultImports": true in the "compilerOptions" section of the jsconfig.json file.

Here is an example

{
  "compileOnSave": false,
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}
labyed commented 6 years ago

@julianobrasil Thank you so much !!!

jdewell commented 6 years ago

@julianobrasil Do you know why in your demo the chosenMonthDate in the _monthSelectedHandler is one month less than what was chosen? I put in this code: console.log("chose "+chosenMonthDate.month());

I chose July and above printed: chose 6

However, the formcontrol displays the correct month/year: 07/2017. I got into this as I was trying to figure out how to display Jul 2017

mmalerba commented 6 years ago

@jdewell JavaScript uses 0-indexed months, January = 0, February = 1, etc.

sivahanuman commented 6 years ago

How to clear the selected year? because the input box is disabled and no clear button. if the user wants to clear the year search without year option then he need to reload the page?

julianobrasil commented 6 years ago

Oh, @sivahanuman, this was not supposed to be really a component to be used out-of-the box 😃 . It's just a workaround for a temporary limitation of the Datepicker. You can take a look at the sample code and customize it to whatever you like. It's setup like this because in my case, I didn't want the users to clear the input once they have chosen one date. Some people would like also to allow the users to type in the input (I don't, in my chart application).

Anyway, adding such a button to clear out the readonly inputs can be done by adding the following code to the inputs (I've updated the stackblitz sample):

<button *ngIf="_inputCtrl.value" matSuffix  mat-icon-button (click)="_clearInput($event)">
  <mat-icon>clear</mat-icon>
</button>

and to the code:

 _clearInput($evt: MouseEvent) {
    this._inputCtrl.setValue(null);
    $evt.stopPropagation();
 }
shikha105 commented 6 years ago

@julianobrasil I have used the exact code but somehow i am not getting the similar year and month datepicker it is also going to the date after picking the month or year. Please help i used the version beta 2.0.0-beta.12 because i am using the same angular material version.

julianobrasil commented 6 years ago

@shikha105, the code works only for 6.0.0+ versions.

shikha105 commented 6 years ago

@julianobrasil Okay! Thank you for replying

pavan6cs commented 6 years ago

@julianobrasil in my project dateChange event is not firing when the month handler is added

angular material: v 6.1.0 angular: v 6.0.2

can you please suggest any solution to this issue.

julianobrasil commented 6 years ago

Hey, @pavan6cs, what exactly are you trying to do? I'm afraid you've tried to do something like <jp-multidatepicker (dateChange)="..."> in my stackblitz sample...

Well, I'm not exposing the dateChange output in that example. You'd have to take a look inside it and do it by yourself, but it's not hard to do it. Just create some @Outputs (name them after dateChange if you want), one per inner picker and a wrapper @Output for the wrapper component in order do forward the emmited date changes by the inner inputs.

pavan6cs commented 6 years ago

@julianobrasil ,what im trying to do is to emitt an event when date is changed by input or by picking from calandrer menu.

<mat-form-field class="datepicker"> <input matInput [matDatepicker]="dp" (dateChange)="addEvent('change', $event)" placeholder="Month and Year" [formControl]="date"> <mat-datepicker-toggle matSuffix [for]="dp"></mat-datepicker-toggle> <mat-datepicker #dp touchUi="true" startView="year" (yearSelected)="chosenYearHandler($event)" (monthSelected)="chosenMonthHandler($event, dp)" panelClass="example-month-picker"> </mat-datepicker> </mat-form-field>

in the above code (dateChange) will not trigger when the monthSelected handler is added.

Can you please suggest how can i emitt the event when date is changed through input and also through calender pick?

julianobrasil commented 6 years ago

Oh, I got it. It won't fire a date change in this case (it's in the docs), because the date is really changed only when the user goes through all the process and selects a day. The day selection is the only event that triggers a date change (maybe you can update the control when the year is selected to force a date change...)

pavan6cs commented 6 years ago

@julianobrasil how can i force the date change from controler ? do u have any sample . it will be lot help if u have one

thank you..

aayushwadhwa commented 5 years ago

I have tried something and might fulfil the purpose. https://stackblitz.com/edit/angular-stimns-emrki2

julianobrasil commented 5 years ago

@pavan6cs, I'm sorry, but I missed your https://github.com/angular/material2/issues/4853#issuecomment-439318879. Well, I've built up something for momentjs and you can take a look on: https://github.com/julianobrasil/multipicker

But @aayushwadhwa's comment answered exactly what you asked.

nekomamushi01 commented 5 years ago

just found a solution about this problem

//for month// //in your template <input [matDatepicker]="datepicker"> <mat-datepicker #picker (monthSelected)="close($event)" startView="year">

//in your controller import { MatDatepicker } from '@angular/material';

@ViewChild('picker') datePicker: MatDatepicker; //

close($event) { this.datePicker.close(); }

//for year// //in your template <input [matDatepicker]="datepicker"> <mat-datepicker #picker (yearSeleceted)="close($event)" startView="multi-year">

//in your controller import { MatDatepicker } from '@angular/material';

@ViewChild('picker') datePicker: MatDatepicker; //

close($event) { this.datePicker.close(); }

nekomamushi01 commented 5 years ago

ohh my bad @aayushwadhwa already answered it. but additional, if you want month picker only, change startView to year :)

igornowosad commented 4 years ago

you can do this also without using @ViewChild I had two date pickers and I only passed ref to them as a argument of close function

  <div>
    <mat-form-field>
      <input matInput [matDatepicker]="fromDatePicker" placeholder="From">
      <mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
      <mat-datepicker startView="multi-year" (yearSelected)="closeDatePicker(fromDatePicker)" #fromDatePicker></mat-datepicker>
    </mat-form-field>
    <mat-form-field>
      <input matInput [matDatepicker]="toDatePicker" placeholder="To">
      <mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
      <mat-datepicker startView="multi-year" (yearSelected)="closeDatePicker(toDatePicker)" #toDatePicker></mat-datepicker>
    </mat-form-field>
  </div>
  closeDatePicker(elem: MatDatepicker<any>) {
    elem.close();
  }
stephendavid commented 4 years ago

Yes We can using moment,

add below lines above @component

import { default as _rollupMoment, Moment } from 'moment'; import { MomentDateAdapter, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter';

const moment = _rollupMoment || _moment;

// See the Moment.js docs for the meaning of these formats: // https://momentjs.com/docs/#/displaying/format/ export const MY_FORMATS = { parse: { dateInput: 'YYYY', }, display: { dateInput: 'YYYY', monthYearLabel: 'YYYY', dateA11yLabel: 'LL', monthYearA11yLabel: 'YYYY', }, };

Then Add below code in component @Component({ selector: 'xxx', templateUrl: './xxx.tml', styleUrls: ['./xxx.scss'], providers: [ // MomentDateAdapter can be automatically provided by importing MomentDateModule in your // application's root module. We provide it at the component level here, due to limitations of // our example generation script. { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] },

{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },

], }) export class BrandDialogComponent implements OnInit { date = new FormControl(moment());

constructor() {}

ngOnInit() {}

chosenYearHandler(normalizedYear: Moment, datepicker: MatDatepicker) { const ctrlValue = this.date.value; ctrlValue.year(normalizedYear.year()); this.date.setValue(ctrlValue); datepicker.close(); }

}

in html add below code =>

Which is works perfectly, and don't forgot to add => "allowSyntheticDefaultImports": true - in tsconfig.json

castor-dev3 commented 4 years ago

you can do this also without using @ViewChild I had two date pickers and I only passed ref to them as a argument of close function

  <div>
    <mat-form-field>
      <input matInput [matDatepicker]="fromDatePicker" placeholder="From">
      <mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
      <mat-datepicker startView="multi-year" (yearSelected)="closeDatePicker(fromDatePicker)" #fromDatePicker></mat-datepicker>
    </mat-form-field>
    <mat-form-field>
      <input matInput [matDatepicker]="toDatePicker" placeholder="To">
      <mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
      <mat-datepicker startView="multi-year" (yearSelected)="closeDatePicker(toDatePicker)" #toDatePicker></mat-datepicker>
    </mat-form-field>
  </div>
  closeDatePicker(elem: MatDatepicker<any>) {
    elem.close();
  }

The only trouble with this was the form control isn't updating his value, so for me the input keeps empty, besides you can get the value of the year adding the envent as input in closeDatapicker

closeDatePicker(elem: MatDatepicker<any>, event: MatDatepickerInputEvent<Date>) {
        elem.close();
        // This setting value also can't refresh the value in the html input
        this.formGroup.get('year').setValue(String(event).split(' ')[3]);
    }
tobiasschweizer commented 4 years ago

Is there a way for the user to dynamically decide wether she wants to select a day, month, or year?

If I understand correctly, the date picker is closed when a year has been selected, so there is no way to pick a month or day with that config.

igornowosad commented 4 years ago

@tobiasschweizer you can try to make a function for both monthSelected and yearSelected output, something like

monthSelected() {
  if (user checked to select only month) {
    return close calendar
  }
  return
}
yearSelected() {
  if (user checked to select only year) {
    return close calendar
  }
  return
}
tobiasschweizer commented 4 years ago

Ideally, the DatePicker would allow for only selecting a month if this is what the user wants and then close itself.

Would you suggest to set up different configuration options elsewhere in the template (to find out if "user checked to select only month" etc.) or something similar and then react to to this?

There could be a selection for precision the user could choose from: "year" | "month" | "day".

tobiasschweizer commented 4 years ago

I had a look at the date-range branch. As @mmalerba has pointed out to me, there is a new component that lets the user select a date range with day precision. Internally, this is represented as a DateRange with a start and an end date:

https://github.com/angular/components/blob/9f019124a7a64a93b67114f1cc05955a1ccfd7ba/src/material/datepicker/date-selection-model.ts#L12-L26

In the template, the start and end dates can be bound to an input via the form control name. So it's like in the case of picking a single date, only that for a range you get two dates.

I think from the logical point of view this is all that's needed to represent dates with month and year precision. Internally, these can be treated as ranges from the first to the last day of a month or a year. In the GUI, however, these need to be displayed differently. Clicking on a month or year should be enough to create a date range for a date with month or year precision, the same applies when initializing an existing date range.

I think DateRange needs an additional member precision which could be day, month, or year. Given a range like start: 04/01/2020, end: 04/30/2020, this information would be displayed differently depending on precision: either as an actual selection from the first to the last day of the month (day precision) or just as an active month in month view (month precision). The same applies when the user just wants to define a year like 2020 (internally represented as a date range from the first day to last day of the year).

I am also thinking of periods, but they can be represented by two date ranges. With imprecise start and end dates of a period you then have four dates, but logically you just have to worry about the start date of the period's beginning end the end date of the period's end.

@mmalerba I am not sure if this belongs here. Shall I try to describe this in a doc? Where would that go?

mmalerba commented 4 years ago

@tobiasschweizer we have a design doc template, though I'm not sure all of the sections really apply in this case, since we're talking about adding a feature to an existing component, not making a new component. I think just to give us a starting point to discuss you can copy the above into the "Proposed implementation" section.

Though it might be possible to represent the precision as a DateRange, I'm not sure if that's the right thing to do here. There are a number of places where we use the DateRange as a cue to show a little bit different UI. The logic in these areas could get event more confusing if a DateRange is sometimes not really a range and just a different precision date. I think if we wanted to represent precision like that we would probably create a separate class called Period or something.

I think the question of how we represent precision is important, but probably more important is how we work it into the existing pieces that are in play in the datepicker. Some of the pieces are definitely hardcoded to a specific precision (e.g. MonthView), and we might need to come up with a more generic framework that allows for more flexibility in terms of precision

tobiasschweizer commented 4 years ago

@mmalerba Ok, thanks for the input. I made a copy of the design doc template and will proceed as you proposed. I will then share the doc with you.

tobiasschweizer commented 4 years ago

@mmalerba I shared the design doc with you that I created. Are you able to access it?

mmalerba commented 4 years ago

Yep, its on my list of things to take a look at

andreElrico commented 3 years ago

Here is how I solved it:

https://stackblitz.com/edit/angular-pyeuls?file=app%2Fdatepicker-views-selection-example.html

infacto commented 3 years ago

Please remove moment js. This should not be a dependency to achieve things like that. I see that Angular Material officially supports moment js. There is an adapter for the mat-datepicker etc. This is really annoying. Instead of that, allow date processor, a middleware or something like this. ... This framework is too complicated. You should get inspired by other Frameworks like Vuetify etc. And note that there are better alternatives to momentjs. Like dayjs or date-fns, etc. ...

I just wonder: Why does the mat-datepicker input displays the correct local date, but fails on user input with exactly the same date format? And why does this work in other Frameworks? Sorry, I'm pissed a bit because this is very time wasting stuff. ... Argh...

axell9641 commented 3 years ago

https://material.angular.io/components/datepicker/examples#datepicker-views-selection