קודקודייל
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
קודקודייל
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
  • קודקודייל
  • מי אתם קודקודייל?
  • קורסים בחינם
  • צרו קשר
  • בניית אתרים
    • וורדפרס
  • נגישות אתרים
  • כל הקטגוריות
    • אנגולר
    • HTML
    • CSS
    • Javascript
    • Typescript
    • NodeJs
    • בלוקציין
ראשי ♦ אנגולר ♦ רכיבים: בניה של רכיב לוח שנה נגיש באנגולר

רכיבים: בניה של רכיב לוח שנה נגיש באנגולר

עידן יצחקי 5 בנובמבר 2021 אין תגובות

לוח שנה / תאריכון / calendar , כולם אומרים דבר אחד, להתעסק עם תאריכים ולהבין מה אפשר להוציא מהאובייקט Date.

נתחיל קודם בקוד ואחרי זה נראה מה בדיוק קורה שם.

לוח השנה יהיה אין סופי ולא משנה לאיזה כיוון נדפדף, הוא יהיה נגיש כך שבעזרת מקלדת ניתן לתפעל אותו והוא יהיה בסיסי כך שלא יהיה "רעש" בקוד של דברים מיותרים.

אתם מוזמים להוסיף ולשפר אותו 😎

<div class="calendar {{calendarSize}}">
    <div class="controls-wrapper">
        <h3 aria-live="polite" class="current-date-title">
            <span class="current-month">{{date|date:calendarSize==="small"?"MMM":"MMMM"}}</span>
            <span class="current-year">({{year}})</span>
        </h3>
        <div class="controller">
            <button class="today-btn" (click)="todayFn()">Today</button>
            <button (click)="moveOneMonthRev()" class="oneMonthRev" title="Prev"><img src="assets/icons/Arrow_Right.svg" alt="prev"></button>
            <button (click)="moveOneMonthFwd()" class="oneMonthFwd" title="Next"><img src="assets/icons/Arrow_Right.svg" alt="next"></button>
        </div>


    </div>
    <table class="main-table">
        <thead>
            <tr>
                <th *ngFor="let dayName of daysInWeekNames" class="main-top-th" scope="col"><span>{{dayName}}</span></th>
            </tr>
        </thead>
        <tbody role="radiogroup">
            <tr *ngFor="let week of calendarArr; trackBy:trackByFn">
                <td *ngFor="let day of week;let last=last;let idx=index;" class="main-td">
                    <div class="day-wrapper" [attr.role]="day.dayNumber!==0 ? 'radio' : null" [ngClass]="{'weekend':last,'weekend-partial':(idx===week.length-2),'selected-cell':day.dayNumber+'/'+(month+1)+'/'+year===colorSelectedCell}" [attr.tabindex]="day.dayNumber!==0 ? 0 : null"
                        [attr.title]="day.dayNumber!==0 ? day.dayNumber+'/'+(month+1)+'/'+year : null" (click)="onDateSelectedFn($event,day.dayNumber,month,year)" (keyup)="onDateSelectedFn($event,day.dayNumber,month,year)" [attr.aria-checked]="day.dayNumber+'/'+(month+1)+'/'+year===colorSelectedCell">
                        <span [ngClass]="{'current-day':currentDate.day===day.dayNumber && currentDate.month===month && currentDate.year===year}" *ngIf="day.dayNumber!==0" class="day-in-calendar">{{day.dayNumber}}</span>
                        <div class="day-in-calendar-data" *ngIf="day.events.length>0">
                            <span *ngFor="let ev of day.events" class="day-event {{ev.eventClass}}">{{ev.eventText}}</span>
                        </div>
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
</div>
.calendar {
    height: 100%;
}

.calendar .main-table {
    table-layout: fixed;
    border-collapse: collapse;
    border-spacing: 0;
    width: 100%;
    height: 100%;
}

.calendar .main-table .main-top-th,
.calendar .main-table .main-td {
    border: 1px solid rgb(170, 201, 230);
    text-align: center;
}

.calendar .main-table .main-top-th {
    padding: 5px;
}

.calendar.small .main-table .main-top-th {
    padding: 0;
}

.calendar .main-table .current-day {
    color: blueviolet;
    font-weight: bold;
}

.calendar .main-table .main-td .day-wrapper {
    position: relative;
    height: 100%;
    cursor: pointer;
}

.calendar.large .main-table .main-td .day-wrapper {
    min-height: 100px;
    min-width: 100px;
}

.calendar.large .main-table .main-td .day-wrapper .day-in-calendar {
    position: absolute;
    left: 0;
    top: 0;
    font-size: 24px;
    padding: 10px;
}

.calendar.medium .main-table .main-td .day-wrapper .day-in-calendar {
    position: absolute;
    left: 0;
    top: 0;
    font-size: 18px;
    padding: 2px;
}

.calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data {
    max-height: 100px;
    min-width: 100px;
    overflow: auto;
    padding-left: 45px;
    box-sizing: border-box;
}

.calendar.medium .main-table .main-td .day-wrapper .day-in-calendar-data {
    max-height: 40px;
    min-width: 40px;
    overflow: auto;
    padding-left: 24px;
    box-sizing: border-box;
}

.calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data .day-event {
    display: block;
    background-color: rgba(100, 180, 255, 0.5);
    text-align: left;
    margin: 4px;
    padding: 4px;
}

.calendar.medium .main-table .main-td .day-wrapper .day-in-calendar-data .day-event {
    display: block;
    background-color: rgba(100, 180, 255, 0.5);
    text-align: left;
    margin: 2px;
    padding: 2px;
    overflow: hidden;
    white-space: nowrap;
    font-size: 16px;
}

.calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data .day-event.event-start {
    border-top-left-radius: 12px;
    border-bottom-left-radius: 12px;
}

.calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data .day-event.event-end {
    border-top-right-radius: 12px;
    border-bottom-right-radius: 12px;
}

.calendar.medium .main-table .main-td .day-wrapper .day-in-calendar-data .day-event.event-start {
    border-top-left-radius: 6px;
    border-bottom-left-radius: 6px;
}

.calendar.medium .main-table .main-td .day-wrapper .day-in-calendar-data .day-event.event-end {
    border-top-right-radius: 6px;
    border-bottom-right-radius: 6px;
}

.calendar.small .main-table .main-td .day-wrapper .day-in-calendar-data {
    display: none;
}

.calendar.medium .main-table .main-td .day-wrapper {
    min-height: 40px;
    min-width: 40px;
}

.calendar.small .main-table .main-td .day-wrapper {
    min-height: 20px;
    min-width: 20px;
}

.calendar.large .current-month {
    font-size: 36px;
}

.calendar.medium .current-month {
    font-size: 24px;
}

.calendar.small .current-month {
    font-size: 18px;
}

.calendar .current-year {
    vertical-align: top;
}

.calendar.large .current-year {
    font-size: 24px;
}

.calendar.medium .current-year {
    font-size: 18px;
}

.calendar.small .current-year {
    font-size: 16px;
}

.calendar .current-date-title {
    text-align: left;
    margin: 0;
}

.calendar .oneMonthRev {
    left: 20px;
    border: 0;
    background-color: transparent;
    cursor: pointer;
    transform: rotate(180deg);
    -webkit-transform: rotate(180deg);
    -moz-transform: rotate(180deg);
    -ms-transform: rotate(180deg);
    -o-transform: rotate(180deg);
    padding: 0;
    margin-left: 20px;
}

.calendar .oneMonthFwd {
    right: 20px;
    border: 0;
    background-color: transparent;
    cursor: pointer;
    margin-left: 20px;
    padding: 0;
}

.calendar .oneMonthFwd:hover,
.calendar .oneMonthRev:hover,
.calendar .oneMonthFwd:focus,
.calendar .oneMonthRev:focus,
.calendar .today-btn:hover,
.calendar .today-btn:focus {
    background-color: rgba(255, 255, 255, 0.5);
    border-radius: 8px;
    -webkit-border-radius: 8px;
    -moz-border-radius: 8px;
    -ms-border-radius: 8px;
    -o-border-radius: 8px;
}

.calendar .oneMonthFwd,
.calendar .oneMonthRev {
    vertical-align: middle;
    height: 100%;
}

.calendar .oneMonthFwd img,
.calendar .oneMonthRev img {
    height: 100%;
}

.calendar .controls-wrapper {
    position: relative;
    background-color: rgb(124, 158, 202);
    padding: 5px;
}

.calendar.large .controls-wrapper {
    min-height: 40px;
}

.calendar.medium .controls-wrapper {
    min-height: 30px;
}

.calendar.small .controls-wrapper {
    min-height: 22px;
}

.calendar .controls-wrapper .controller {
    position: absolute;
    top: 3px;
    right: 3px;
}

.calendar.large .controls-wrapper .controller {
    height: 44px;
}

.calendar.medium .controls-wrapper .controller {
    height: 34px;
}

.calendar.small .controls-wrapper .controller {
    height: 24px;
}

.calendar .today-btn {
    background-color: transparent;
    border: 0;
    vertical-align: middle;
    font-weight: bold;
    cursor: pointer;
}

.calendar.large .today-btn {
    height: 40px;
}

.calendar.medium .today-btn {
    height: 34px;
}

.calendar.small .today-btn {
    height: 28px;
}

.calendar .weekend {
    background-color: rgb(170, 201, 230);
}

.calendar .weekend-partial {
    background-image: linear-gradient(to right, rgba(170, 201, 230, 0), rgb(170, 201, 230));
}

.calendar .selected-cell {
    background-color: rgb(255, 240, 191);
    background-image: none;
}

@media screen and (max-width:1023px) and (min-width:769px) {
    .calendar.large .main-table .main-td .day-wrapper .day-in-calendar {
        font-size: 18px;
        padding: 5px;
    }
    .calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data {
        padding-left: 30px;
    }
}

@media screen and (max-width:768px) {
    .calendar.large .main-table .main-td .day-wrapper .day-in-calendar {
        font-size: 16px;
        padding: 2px;
    }
    .calendar.large .main-table .main-td .day-wrapper .day-in-calendar-data {
        padding-left: 0px;
        padding-top: 20px;
    }
}
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { IDayDate, IEventsData, ISingleDayData } from './calendar.interface';

@Component({
  selector: 'app-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit, OnChanges {
  @Input() calendarSize: "large" | "medium" | "small" = "large";
  @Input() eventsData: IEventsData[] = [];
  @Output() selectedDateEmiter: EventEmitter<Date> = new EventEmitter<Date>();
  @Input() setSelectedDate: string = '';
  daysInWeekNames: string[] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  calendarArr: Array<ISingleDayData[]> = [];
  date: Date = new Date();
  year: number = this.date.getFullYear();
  month: number = this.date.getMonth();
  firstDay: number = (new Date(this.year, this.month, 1)).getDay();
  lastDay: number = (new Date(this.year, this.month + 1, 0)).getDate();
  currentDay: number = this.date.getDate();
  monthCorrection: number = 0;
  currentDate: IDayDate = { day: this.currentDay, month: this.month, year: this.year };
  selectedDate: Date;
  dayDataArr: ISingleDayData[] = [];
  emptyCells: number = 0;
  colorSelectedCell: string = '';
  constructor() {

  }
  ngOnInit() {
    this.generateDateFn();

  }
  ngOnChanges(changes: SimpleChanges) {
    let dateArr = (this.setSelectedDate).split('/');
    if (dateArr.length === 3) {
      this.setSelectedDateFn(parseInt(dateArr[0]), parseInt(dateArr[1]) - 1, parseInt(dateArr[2]));

    }
    if (changes.eventsData !== undefined) {
      this.eventsData = changes.eventsData.currentValue;
      this.generateDateFn();

    }
  }
  generateDateFn() {
    this.date = new Date(this.currentDate.year, this.currentDate.month + this.monthCorrection)
    this.month = this.date.getMonth();
    this.year = this.date.getFullYear();
    this.firstDay = (new Date(this.year, this.month, 1)).getDay();
    this.lastDay = (new Date(this.year, this.month + 1, 0)).getDate();
    this.calcDaysFn();
  }
  calcDaysFn() {
    let sumOfDaysInMonth = [];
    for (this.emptyCells = 0; this.emptyCells < this.firstDay; this.emptyCells++) {
      let emptyDay: ISingleDayData = { dayNumber: 0, events: [] };
      sumOfDaysInMonth.push(emptyDay);
    }
    for (let arrCnt = 0; arrCnt < this.lastDay; arrCnt++) {
      let itemDay: ISingleDayData = { dayNumber: arrCnt + 1, events: [] }
      sumOfDaysInMonth.push(itemDay);
    }
    let weekArr: ISingleDayData[] = [];
    this.calendarArr = [];
    while (sumOfDaysInMonth.length > 7) {
      weekArr = sumOfDaysInMonth.splice(0, 7);
      this.calendarArr.push(weekArr);
    }

    if (sumOfDaysInMonth.length < 7) {
      let arrLength = 7 - (sumOfDaysInMonth.length)
      for (let addEmpty = 0; addEmpty < arrLength; addEmpty++) {
        let emptyDay: ISingleDayData = { dayNumber: 0, events: [] };
        sumOfDaysInMonth.push(emptyDay);
      }
    }
    this.calendarArr.push(sumOfDaysInMonth);
    this.eventsFn();

  }
  trackByFn(index, item) {
    if (!item) return null;
    if (item.id) {
      return item.id;
    }
    if (item.itemValue) {
      return item.itemValue;
    }
    return index;
  }
  moveOneMonthFwd() {
    this.monthCorrection++;
    this.generateDateFn();
  }
  moveOneMonthRev() {
    this.monthCorrection--;
    this.generateDateFn();
  }
  todayFn() {
    this.monthCorrection = 0;
    this.generateDateFn();
  }
  onDateSelectedFn(e, day: number, month: number, year: number) {
    if (day !== 0 && (e.which === 32 || e.which === 13 || e.type === 'click')) {
      this.setSelectedDateFn(day, month, year);
    }
  }
  setSelectedDateFn(day: number, month: number, year: number) {
    this.colorSelectedCell = day + '/' + (month + 1) + '/' + year;
    this.selectedDate = new Date(year, month, day);
    this.selectedDateEmiter.emit(this.selectedDate);
  }
  eventsFn() {
    for (let ev = 0; ev < this.eventsData.length; ev++) {
      let _event = this.eventsData[ev];
      let startDay = _event.startDate.getDate();
      let lengthEvent = Math.ceil((_event.endDate.getTime() - _event.startDate.getTime()) / (1000 * 60 * 60 * 24));
      for (let le = 0; le < lengthEvent + 1; le++) {
        let weekloc = Math.ceil((startDay + le + this.emptyCells) / 7);
        let dayloc = ((startDay + le + this.emptyCells) % 7);
        if (dayloc === 0) {
          dayloc = 7;
        }
        let eventClass: string = '';
        if (le === 0) {
          eventClass = 'event-start';
        }
        if (le === lengthEvent) {
          eventClass += ' event-end';
        }

        let selectedDay: ISingleDayData = this.calendarArr[weekloc - 1][dayloc - 1];
        selectedDay.events.push({ eventText: this.eventsData[ev].eventText, eventIcon: this.eventsData[ev].eventIcon, eventClass: eventClass });
        if (selectedDay.dayNumber === this.lastDay) {
          break;
        }
      }

    }
  }
}
export interface IDayDate{
    day:number,
    month:number,
    year:number
}
export interface IEventsData{
    startDate:Date,
    endDate:Date,
    eventText:string,
    eventIcon:string
}
export interface ISingleDayData{
    dayNumber:number,
    events:ISingleEvent[]
}
export interface ISingleEvent{
    eventText:string,
    eventIcon:string,
    eventClass?:string
}

נעבור על הדברים העקריים בקוד (אני ממליץ לעצור את הקוד בזמן פעולה ולראות מה כל פונקציה עושה).

נתחיל עם ה-HTML

שורה 1

המעטפת של הרכיב, מקבלת גם משתנה שהוא CLASS CSS נוסף ועל פי זה נקבע גודל הרכיב בדף, שימו לב שהרכיב רספונסיבי אבל על פי הגודל שבמשתנה יקבע לו גודל מקסימאלי.

המטרה היא בשלב מאוחר יותר לתת את האפשרות לשים אותו על הדף או לשדך אותו לרכיב מותאם טופס וכך ליצר datepicker.

שורה 2 – 14

מעטפת של הכותרת/ פס עליון של הלוח שנה, שם יהיו החצים קדימה ואחורה בחודשים, כפתור קפיצה להיום, מידע שאנחנו רוצים לתת למשתמש, כמו החודש והשנה.

שורה 4

יש שימוש ב-PIPE של אנגולר אשר משנה את תצוגת החודש בהתאם לגודל הלוח שנה שנקבע.

שורה 15 – 34

זה הלוח שנה שלנו, הוא מבוסס על TABLE, מאוד נוח לנגישות ובכלל לוח שנה הוא מידע טבלאי אז למה לא לשים אותו ברכיב שמתאים לו?

הכותרת בלוח השנה נלקחת ממערך של מלל כך שאם רוצים לשנות את המלל זה די פשוט

שורה 24

זו שורה די ארוכה אבל זה בעיקר בשביל נגישות כך שאין צורך להיבהל ממנה.

יש שם מאפיינים כמו tabindex כדי שהתא יקבל פוקוס בגלישת מקלדת, role בשביל הקורא מסך, aria שגם נועד לקורא המסך, וארועים עבור לחיצה רגילה בעכבר ועבור לחיצה במקלדת.

שורה 27 – 28

במידה ויש מידע על ארועים באותו היום הם יתווספו לתא עם שינוי בעיצוב בהתאם למקום שהם נמצאים בוא, תחילת/ אמצע/סוף הארוע.

CSS

מבחינת העיצוב אין משהו מיוחד, הוא רק נראה ארוך בגלל שיש טיפול במספר מצבים של גודל לוח השנה.

יש לנו גדול, בינוני וקטן, המצבים האלו גורמים להכפלת שורות כדי לתת טיפול מתאים.

בסוף הקוד, יש 2 נקודות שבירה עבור לוח שנה גדול.

TS

שורה 10

משנה שקובע את גודל הלוח שנה, במידה שלא נקבע מבחוץ גודל מסויים אז ברירת המחדל שלו, זה גדול.

שורה 11

מערך שארועים שיאוכלסו בלוח שנה על פי התאריכים המתאימים. כל ארוע צריך להיות עם תאריך התחלה וסיום

שורה 12

כאשר המשתמש בחר תאריך מסויים, הרכיב יוציא את התאריך החוצה לרכיב שעוטף אותו.

שורה 13

במידה שרוצים לסמן תאריך מבחוץ.

שורה 16 – 21

משיכת התאריך המלא, מתוך התאריך נוציא את השנה, החודש, המיקום של היום הראשון והיום האחרון בלוח השנה והמיקום של היום הנוכחי.

שורה 22

אחראי על התנועה של לוח השנה בחודשים של השנה.

במידה שהוא שווה ל-0 אז מדובר על החודש הנוכחי, במידה שהוא מעל או מתחת לאפס אז זה אומר שבודקים את החודש הנוכחי בתוספת של חודשים על פי המספר במשתנה.

אם אנחנו נמצאים בחודש ה-8 והמשתנה שווה ל-2 אז נעבור לחודש 10 (8+2) אם המשתנה שווה למינוס 2 אז נעבור לחודש 6 (8 + -2).

הדבר הנוסף שנשאר לנו הוא להוסיף תאים ריקים לפני ואחרי החודש, בגלל שהחודש לא תמיד מתחיל ביום ראשון ונגמר ביום שבת, זה אומר שיש לנו תאים ריקים שהם מיצגים את החלקים מהחודש הקודם.

אנחנו מטפלים בזה בפונקציה בשורה 55

שורה 83

אנחנו מחזירים זיהוי (KEY) לכל רכיב במערך שעובר ngFor כדי למנוע הרצה מחדש של כל המערך ללא צורך.

שורה 115

מטפלת בארועים שנמצאים בתאים שבחודש בתוספת של CLASS מתאים עבור תחילת הארוע וסוף הארוע.

פוסטים קשורים:

תמונת אווירה של אופציותרכיבים: בניה של רכיב AUTOCOMPLETE נגיש באנגולר תמונת אווירה של רשימת סימוןרכיבים: בניה של רכיב MULTI-SELECT נגיש באנגולר תמונת אווירה של מטריה כתומהרכיבים: בניה של רכיב SELECT נגיש באנגולר תמונת אווירה של חצים לכל הכיווניםבניה של טופס דינמי על פי מידע מהשרת באנגולר
angular אנגולר מדריך אנגולר רכיבים

אודות המחבר

עידן יצחקי להציג את כל הפוסטים של עידן יצחקי


« פוסט קודם
פוסט הבא »

השארת תגובה

ביטול

חיפוש באתר
בחירת העורכים
29 בדצמבר 2023 עידן יצחקי

שדה טקסט עשיר עם תמונות

אתם הולכים להיות מופתעים עד כמה HTML יכול להיות חכם ולבצע משהו כל כך מורכב, שאם אנחנו היינו רוצים ליצור

1 באוקטובר 2021 עידן יצחקי

איך למשוך דינמית favicon של אתרים אחרים ב-JS

בפוסט זה נראה איך אפשר על פי לינקים בדף למשוך את ה-favicon מהדומיין שלהם באופן דינמי, בדיקה של תקינות התמונה

פופולרי
Javascript functions – היכרות עם סוגי פונקציות
Javascript
21 בדצמבר 2024 אין תגובות
Nested routing in angular standalone component
Typescript
15 בנובמבר 2024 תגובה אחת
בחרו לפי תגיות
angular blockchain css ethers express front-end fullstack GQL html javascript next js nextjs nodejs react hooks reactjs solidity webgl אנגולר בלוקציין וורדפרס לימודי אנגולר לימודי וורדפרס לימוד ריאקט מדריך front-end מדריך GQL מדריך אנגולר מדריך וורדפרס מדריך חינם react מדריך ריאקט מפתח בלוק מפתח בלוקציין מתכנת front-end מתכנת בלוקציין מתכנת פרונט סולידיטי קורס front end קורס fullstack קורס nextjs קורס אנגולר קורס בלוקציין קורס בלוקציין בחינם קורס סולידיטי קורס ריאקט קורס תכנות קורס תכנות בחינם
סינון על פי קטגוריות
CSS fullstack HTML IIS Javascript nodeJs SEO Typescript אנגולר בלוקציין בניית אתרים וורדפרס חיפוש עבודה כלים נוספים כללי נגישות קורסים ריאקט תלת מימד תקלות ופתרונות
צור קשר
כל הזכויות שמורות לקודקודייל
ליצירת קשר: @ קודקודייל
גלילה לראש העמוד