-
Notifications
You must be signed in to change notification settings - Fork 0
/
paper-datepicker.js
613 lines (580 loc) · 18.5 KB
/
paper-datepicker.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
/**
@license
Copyright (c) 2019 elifents. All rights reserved.
*/
import { html, PolymerElement } from "@polymer/polymer/polymer-element.js";
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
import { PaperDialogBehavior } from "@polymer/paper-dialog-behavior/paper-dialog-behavior";
import "@polymer/paper-dialog-behavior/paper-dialog-shared-styles";
import "@polymer/iron-flex-layout/iron-flex-layout";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-button/paper-button";
import "@polymer/iron-icon/iron-icon";
import "@polymer/iron-icons/hardware-icons";
import "@polymer/paper-styles/color";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-ripple/paper-ripple";
import "@polymer/neon-animation/neon-animated-pages";
import "@polymer/neon-animation/neon-animatable";
import "@polymer/neon-animation/animations/fade-in-animation.js";
import "@polymer/neon-animation/animations/fade-out-animation.js";
import "web-animations-js/web-animations-next-lite.min.js";
import "@polymer/iron-list/iron-list";
/**
paper-datepicker is a polymer element which used material design theme. paper-datepicker gives a modal dialog.
Hence it can used with any other elemts like paper-button or paper-input and then read and write values using custom
element properties.
paper-datepicker uses moment.js for all the computations.
To demonstrate lets use a paper-input.
<paper-input label="Date" value="{{demoDate}}">
<paper-icon-button
slot="suffix"
icon="date-range"
on-click="openDateBox"
>
</paper-icon-button>
</paper-input>
<paper-datepicker with-backdrop opened="{{opened}}" date="{{demoDate}}"></paper-datepicker>
Realize that
* opened in paper-datepicker controlls whether the element is currently visible or not
* value of paper-input is binded with the date of paper-datepicker
* on-click event from paper-icon-button will be calling function openDateBox, with will then set opened to true
* As opened is set to true, it opens up paper-datepicker.
* Once a date is being selected it is then passed to property date and then to demoDate.
Properties
** opened
type: boolen,
default: false
Controls whether paper-datepicker is opened or not
** date
type: String,
default: today
Current date value of paper-datepicker
** format
type: String
default: YYYY-MM-DD
The format of date-value in paper-datepicker. It supports all formats supplied by moment.js
* @customElement
* @group elifent
* @polymer
* @element paper-datepicker
* @demo /demo/index.html
*/
import moment from "moment";
/**
* `paper-datepicker`
* Polymer 3 date picker element
*
* @customElement
* @polymer
* @demo demo/index.html
*/
class PaperDatepicker extends mixinBehaviors(
PaperDialogBehavior,
PolymerElement
) {
static get template() {
return html`
<style is="custom-style" include="iron-flex iron-flex-alignment"></style>
<style>
:host {
dispay: block;
min-width: 300px;
background: #fff;
user-select: none;
}
/*Class for <div> which prints year and selected date*/
.calendarHeader {
padding: 25px;
color: #ffffff;
background-color: var(--paper-indigo-900);
@apply --layout-vertical;
}
/*Class for <div> which prints year*/
.year {
font-size: 18px;
color: var(--paper-indigo-300);
}
/*Class for <div> which prints month name*/
.header {
font-size: 30px;
}
/*Class for <div> that hold naviagation icons*/
.nav {
height: 50px;
@apply --layout-horizontal;
@apply --layout-center;
padding: 10px;
}
/*For the span that prints Month and Year*/
.monthYear {
@apply --layout-flex;
text-align: center;
}
/*Class for table that prints day*/
table {
border-collapse: collapse;
border: solid thin #fff;
margin: 10px;
}
td {
text-align: center;
cursor: pointer;
height: 40px;
width: 40px;
margin: 8px;
}
.calendar tr:first-child {
border-bottom: solid thin var(--paper-blue-grey-500);
}
.calendar tr:first-child > td {
color: var(--paper-blue-grey-500);
}
td > .dateItem {
margin: auto;
height: 30px;
width: 30px;
border-radius: 15px;
@apply --layout-vertical;
@apply --layout-center-center;
}
/*Class for today in calendar*/
.today {
border: solid thin var(--paper-indigo-900);
background-color: #fff;
color: var(--paper-indigo-900);
}
/*Class for selected date in calendar*/
.highlight {
background-color: var(--paper-indigo-900);
color: #fff;
}
iron-icon {
cursor: pointer;
--iron-icon-stroke-color: var(--dark-secondary-opacity);
}
paper-button {
color: var(--paper-indigo-900);
margin-top: 10px;
}
paper-item {
color: var(--secondary-text-color);
@apply --layout-center-center;
}
paper-item.onScreenYear {
color: var(--paper-indigo-900);
font-weight: bold;
font-size: 18px;
}
neon-animatable {
position: relative;
}
/*Class for dialog dismiss button container*/
.footer {
padding: 10px;
margin-bottom: 10px;
}
</style>
<div class="layout vertical" on-iron-overlay-closed="dismissDialog">
<div class="calendarHeader">
<div class="year" on-click="_changeView" id="year">{{_year}}</div>
<div class="header">{{_header}}</div>
</div>
<div class="nav">
<iron-icon
icon="hardware:keyboard-arrow-left"
on-click="_navigate"
id="prevMonth"
></iron-icon>
<span class="monthYear" on-click="_changeView" id="month"
>{{_monthYear}}</span
>
<iron-icon
icon="hardware:keyboard-arrow-right"
on-click="_navigate"
id="nextMonth"
></iron-icon>
</div>
<neon-animated-pages
id="pages"
selected="{{_view}}"
attr-for-selected="name"
>
<neon-animatable name="calendar">
<table class="calendar">
<tr>
<td>S</td>
<td>M</td>
<td>T</td>
<td>W</td>
<td>T</td>
<td>F</td>
<td>S</td>
</tr>
<template is="dom-repeat" items="[[_monthArray]]" as="week">
<tr>
<template is="dom-repeat" items="[[week]]">
<td>
<div
style="position:relative"
on-click="_setDate"
id="[[item.day]]"
class$="dateItem {{_getBackground(item.active)}} {{_checkToday(item.day)}}"
>
[[item.day]]
<paper-ripple></paper-ripple>
</div>
</td>
</template>
</tr>
</template>
</table>
</neon-animatable>
<neon-animatable name="month">
<table>
<tr>
<td>
<paper-button id="jan" on-click="_setMonth">Jan</paper-button>
</td>
<td>
<paper-button id="feb" on-click="_setMonth">Feb</paper-button>
</td>
<td>
<paper-button id="mar" on-click="_setMonth">Mar</paper-button>
</td>
</tr>
<tr>
<td>
<paper-button id="apr" on-click="_setMonth">Apr</paper-button>
</td>
<td>
<paper-button id="may" on-click="_setMonth">May</paper-button>
</td>
<td>
<paper-button id="jun" on-click="_setMonth">Jun</paper-button>
</td>
</tr>
<tr>
<td>
<paper-button id="jul" on-click="_setMonth">Jul</paper-button>
</td>
<td>
<paper-button id="aug" on-click="_setMonth">Aug</paper-button>
</td>
<td>
<paper-button id="sep" on-click="_setMonth">Sep</paper-button>
</td>
</tr>
<tr>
<td>
<paper-button id="oct" on-click="_setMonth">Oct</paper-button>
</td>
<td>
<paper-button id="nov" on-click="_setMonth">Nov</paper-button>
</td>
<td>
<paper-button id="dec" on-click="_setMonth">Dec</paper-button>
</td>
</tr>
</table>
</neon-animatable>
<neon-animatable name="year">
<div style="height:250px;overflow-y:scroll" id="yearListContainer">
<iron-list
items="{{_years}}"
index-as="index"
scroll-target="yearListContainer"
scroll-offset="-90"
id="yearList"
>
<template>
<paper-item
class$="{{_checkYear(item.year)}}"
on-click="_setYear"
id="{{item.year}}"
>
[[item.year]]
</paper-item>
</template>
</iron-list>
</div>
</neon-animatable>
</neon-animated-pages>
<div class="buttons footer">
<paper-button dialog-dismiss>Cancel</paper-button>
<paper-button dialog-confirm on-click="_passDate">Ok</paper-button>
</div>
</div>
`;
}
static get properties() {
return {
/** Current date value of date picker. */
date: {
type: String,
value: null,
observer: "_update",
notify: true
},
/** Default date that need to set selected when date picker is iniized. Value is expected in string format.
* Passed value should also match the format that is being supplied. If format and default values foramt
* doesn't matched then date picker throws invalid date.
*/
default: {
type: String,
value: "today"
},
/** Foramt of date value. Supports all the moment date format.*/
format: {
type: String,
value: "YYYY/MM/DD"
},
/**
Dates that is currently shown in the date picker.
Basically this will be NaviagatedYear/NaviagatedMonth/CurrentDate
When navigates in the callender, we set this variable according and then plot dates in callender
*/
_screenDate: {
type: String,
value: null
},
/**
* All dates in the month based on their weekday name
*/
_monthArray: {
type: Array,
value: null,
notify: true
},
/**
* Used to store year number
*/
_year: {
type: String,
value: null
},
/**
* Used to store value printed at the header. This will be selected date in ddd, MMM DD format
*/
_header: {
type: String,
value: null
},
/**
* Month and year value printed at the navigation element.
*/
_monthYear: {
type: String,
value: null
},
/**
* Date that has been currently clicked by user. But this will not be confirmed to
* this.date until user clicks on OK. When user navigates to next month or year, this value will get reset.
*/
_selectedDate: {
type: String,
value: null
},
/**
* Current view of the element, it can be calendar, month, year
*/
_view: {
type: String,
value: "calendar"
},
/**
* Used to store years which is listed in the year view, currently storing years from 1900 to 2100.
*/
_years: {
type: Array,
value: [],
notify: true
}
};
}
ready() {
super.ready();
this._header = new moment().format("ddd, MMM DD");
for (let i = 1900, j = 0; i < 2100; i++, j++) {
this.push("_years", {
index: j,
selected: false, // true if the current item is selected
tabIndex: j, // a dynamically generated tabIndex for focus management
year: i
});
}
}
//Update is called by observer in date.
_update() {
//If passed date is null or nothing then date will be set as default date
if (this.date == null || this.date == "") {
if (this.default == "today") {
this.date = moment().format(this.format);
} else {
this.date = this.default;
}
}
//Sets screenDate and then calls plot()
this._screenDate = this.date;
this._selectedDate = this.date;
this._plot();
}
//Draws date boxed in the date picker
_plot() {
//set day based on the screenDate.
//set day as first of month and lastDay as last day of month. We then push each days to a
//weekArray first and then to monthArray.
const day = moment(this._screenDate, this.format).startOf("month");
const lastDay = moment(this._screenDate, this.format).endOf("month");
//set year and month based on day
this._year = day.format("YYYY");
this._monthYear = day.format("MMMM YYYY");
//set monthArray to null.
let monthArray = [];
//creates a new weekArray. Each item will be an object which has a day value and a flag 'active',
//active will be true if selectedDate is equal to the day.
let weekArray = ["", "", "", "", "", "", ""].map(function() {
return {
day: "",
active: false
};
});
do {
//replace items in the weekArray based with new values
weekArray[day.day()] = {
day: day.format("D"),
active: this._isSelectedDate(day)
};
//Adds a date and then check whether its the last day of a week, if it is then
//push earlier weekArray to monthArray and create a new weekArray.
day.add(1, "d");
if (day.day() == 0 && day.isSameOrBefore(lastDay)) {
monthArray.push(weekArray);
weekArray = ["", "", "", "", "", "", ""].map(function() {
return {
day: "",
active: false
};
});
}
} while (day.isSameOrBefore(lastDay));
//At the end push last weekArray created.
monthArray.push(weekArray);
//Mutate value to monthArray
this._monthArray = monthArray;
}
//replots dates based on the navigation button clicked
_navigate(e) {
//When navigates selectedDate will be set to null.
//adds/subtracts days from screenDate value based on the navigation button clicked.
const day = moment(this._screenDate, this.format).startOf("month");
switch (e.target.id) {
case "prevYear":
day.subtract(1, "y");
break;
case "nextYear":
day.add(1, "y");
break;
case "prevMonth":
day.subtract(1, "M");
break;
case "nextMonth":
day.add(1, "M");
break;
}
//Sets new date as screenDate and then calls plot()
this._screenDate = day.format(this.format);
this._plot();
}
//Sets date when user clicks on a date, this is captured based on the id of clicked item.
_setDate(e) {
//sets day as the first of day of screenDate.
let day = moment(this._screenDate, this.format).startOf("month");
let id = e.target.id;
//If id is blank then user has clicked on blank box, return;
if (id == "") return;
//Sets selected date based on the clicked id.
this._selectedDate = day.set("date", id).format(this.format);
//And now set the header of date picker
this._header = day.set("date", id).format("ddd, MMM DD");
//Get date and iterate through each values in monthArray to set active flag
let currentday = day.set("date", id).format("D");
for (let i = 0; i < this._monthArray.length; i++) {
for (let j = 0; j < this._monthArray[i].length; j++) {
if (this._monthArray[i][j].day == currentday) {
this.set("_monthArray." + i + "." + j + ".active", true);
} else {
this.set("_monthArray." + i + "." + j + ".active", false);
}
}
}
}
//Set month and then replot calendar
_setMonth(e) {
const day = moment(this._screenDate, this.format).month(e.target.id);
this._screenDate = day.format(this.format);
this._plot();
this._view = "calendar";
}
//Set month and then replot calendar
_setYear(e) {
const day = moment(this._screenDate, this.format).year(e.target.id);
this._screenDate = day.format(this.format);
this._plot();
this._view = "calendar";
}
//Returns true if passed day is equal to selectedDate
_isSelectedDate(day) {
if (day.isSame(moment(this._selectedDate, "YYYY/MM/DD"))) {
return true;
} else {
return false;
}
}
//Dynamic class computation
_getBackground(flag) {
if (flag) {
return "highlight";
} else {
return "";
}
}
_checkToday(date) {
//Today
const today = new moment().format("YYYY-MM-DD");
const inCommingDate = new moment(this._screenDate, this.format)
.date(date)
.format("YYYY-MM-DD");
if (today == inCommingDate) {
return "today";
} else {
return "";
}
}
_checkYear(year) {
//On screen year
const onScreenYear = new moment(this._screenDate, this.format).format(
"YYYY"
);
if (year == onScreenYear) {
return "onScreenYear";
} else {
return "";
}
}
//Pass date to parent via date property
_passDate() {
if (this._selectedDate != null) this.date = this._selectedDate;
}
//Managing views
_changeView(e) {
this._view = e.target.id;
if (this._view == "year") {
const onScreenYear = new moment(this._screenDate, this.format).format(
"YYYY"
);
this.$.yearList.scrollToIndex(onScreenYear - 1900);
}
}
}
window.customElements.define("paper-datepicker", PaperDatepicker);