替换`switch`并根据属性调用子方法

时间:2017-11-21 10:03:07

标签: typescript

我是OOP的新手。我calendar有3 views(暂时)。我有abstract类,它只创建table个对象,abstract CalendarTable 类,它包含一些setter和getter以及abstract方法和一个子类日历,它会覆盖基类方法。问题是每个方法都有switch根据我的属性view's调用currentView方法,其中日历中的每个视图都为instantiated构造

日历

export class Calendar extends my.core.calendar.CalendarTable {
        weeklyView: my.calendar.WeeklyView;
        monthlyView: my.calendar.MontlyView;
        dayView: my.calendar.DayView;

        constructor(cfg: my.core.calendar.iCalendarConfiguration) {
            super(cfg);

            this.weeklyView = new my.calendar.WeeklyView(this);
            this.monthlyView = new my.calendar.MontlyView(this);
            this.dayView = new my.calendar.DayView(this);
        }


        onResize(): void {
            /**
             *  Repaint all appointments on window resize
             *  For many reasons
             */
            this.bindAppointments();
        }

        createCalendar(): void {

            /** Clear everything on change */
            if (this.tBody.rows.length > 0) {
                this.tBody.clear();
                this.tHead.clear();
            }

            switch (this.config.currentView) {
                case "month":
                    this.monthlyView.createMontlyView();
                    break;
                case "day":
                    this.dayView.createDayView();
                    break;
                case "week":
                    this.weeklyView.createWeeklyView();
                    break;
            }

            this.bindAppointments();
        }

        bindAppointments(): void {
            this.clearAll();

            switch (this.config.currentView) {
                case "month":
                    this.monthlyView.bindMonthAppointments();
                    break;
                case "day":
                    this.dayView.bindDayAppointments();
                    break;
                case "week":
                    this.weeklyView.bindWeekAppointments();
                    break;
            }
        }

        Next(sender, e, data): void {
            switch (this.config.currentView) {
                case "month":
                    this.monthlyView.monthNavigationChange(true);
                    break;
                case "day":
                    this.dayView.dayNavigationChange(true);
                    break;
                case "week":
                    this.weeklyView.weekNavigationChange(true);
                    break;
            }
            this.createCalendar();
            this.updateLabels();
        }

        Previous(sender, e, data): void {
            switch (this.config.currentView) {
                case "month":
                    this.monthlyView.monthNavigationChange(false);
                    break;
                case "day":
                    this.dayView.dayNavigationChange(false);
                    break;
                case "week":
                    this.weeklyView.weekNavigationChange(false);
                    break;
            }
            this.createCalendar();
            this.updateLabels();
        }

        TabClick(sender: any, event: any, data: any): void {

            switch (sender.id.toLowerCase()) {
                case "day":
                    this.setActiveTab('day', event);
                    // update currentdate
                    break;
                case "month":
                    this.setActiveTab('month', event);
                    // update currentdate
                    break;
                case "week":
                    this.setActiveTab('week', event);
                    // update currentdate
                    break;
            }
            this.createCalendar();
            this.updateLabels();
        }


        updateLabels(): void {
            let date = new Date(this.config.currentDate);
            switch (this.config.currentView) {
                case "month":
                    this.currentDateMonth.value = String(this.calendar_months_label[date.getMonth()]) + ' ' + String(date.getFullYear());
                    break;
                case "day":
                    this.currentDateMonth.value = String(this.config.currentDate.getDate()) + ' ' + String(this.calendar_months_label[this.config.currentDate.getMonth()]) + ' ' + String(this.config.currentDate.getFullYear());
                    break;
                case "week":
                    this.currentDateMonth.value = String(this.getPreviousWeekStr(this.weekStart, this.weekEnd, this.calendar_months_label[this.config.currentDate.getMonth()], this.config.currentDate.getFullYear()));
                    break;
            }
        }

我认为这是糟糕的设计。我的基本用户处理程序是TabClick, Next, Previous。如何让每个view类扩展日历并覆盖他的方法,并且当其中一个处理程序触发调用相应的视图覆盖方法而无需切换以确定我当前的视图和调用实例化的类方法?

附加其中一个view类,以了解每个视图类的基本概念。其他2有类似的方法,只是不同的渲染逻辑,计算等。

export class MontlyView {
        table: my.core.calendar.CalendarTable;

        EVENTS_DIV: string = 'cal-monthview-events-div';
        EVENTS_WRAPPER: string = 'cal-monthview-events-wrapper';

        TBODY_TD_DIV: string = 'cal-monthview-tbody-td-div';
        TBODY_TD_PREVIOUSORNEXTMONTH: string = 'cal-monthview-tbody-td-prevAndNextMonth';
        TBODY_TD_ISTODAY: string = 'cal-monthview-tbody-td-istoday';
        TBODY_TD_DISABLED: string = 'cal-monthview-tbody-td-disabled';
        TBODY_TD: string = 'cal-monthview-tbody-td';

        THEAD_TR: string = 'cal-monthview-thead-th';
        THEAD_TH: string = 'cal-monthview-thead-row';

        constructor(tbl: my.core.calendar.CalendarTable) {
            this.table = tbl;
        }

        private createMonthCellArray(day: number, nextMonth: boolean = undefined): Array<object> {
            let date = this.table.config.currentDate;

            if (nextMonth) {
                return [
                    {
                        startDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, day, 0, 0, 0, 0),
                        endDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth() + 1, day, 23, 59, 59, 999)
                    }
                ]
            } else if (nextMonth == false) {
                return [
                    {
                        startDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth() - 1, day, 0, 0, 0, 0),
                        endDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth() - 1, day, 23, 59, 59, 999)
                    }
                ]
            } else {
                return [
                    {
                        startDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth(), day, 0, 0, 0, 0),
                        endDate: new Date(new Date(date).getFullYear(), new Date(date).getMonth(), day, 23, 59, 59, 999),
                        isToday: new Date(new Date(date).getFullYear(), new Date(date).getMonth(), day).getTime() == new Date(new Date().setHours(0, 0, 0, 0)).getTime() ? 1 : 0
                    }
                ]
            }
        }

        private attachSmallAppointment() {

        }

        private attachDisabledDays(j: number, el: HTMLElement, day: number): void {
            /**
             *  We have to check for startDate and endDate aswell because they may be all provided and we will apply
             *  the same class 2 times or more
             */
            for (let i = 0; i < this.table.config.disabledDays.length; i++) {
                if (this.table.config.startDate == null && this.table.config.endDate == null) {
                    if (j == this.table.config.disabledDays[i]) {
                        el.classList.add(this.TBODY_TD_DISABLED);
                    }
                } else if (this.table.config.startDate && this.table.config.endDate) {
                    if (day < this.table.config.startDate.getDate() || day > this.table.config.endDate.getDate()) {
                        el.classList.add(this.TBODY_TD_DISABLED);
                    }
                    if (j == this.table.config.disabledDays[i]) {
                        if (!this.table.checkClassContaining(el.classList, this.TBODY_TD_DISABLED)) {
                            el.classList.add(this.TBODY_TD_DISABLED);
                        }
                    }
                } else if (this.table.config.startDate && this.table.config.endDate == null) {
                    if (day < this.table.config.startDate.getDate()) {
                        el.classList.add(this.TBODY_TD_DISABLED);
                    }
                    if (j == this.table.config.disabledDays[i]) {
                        if (!this.table.checkClassContaining(el.classList, this.TBODY_TD_DISABLED)) {
                            el.classList.add(this.TBODY_TD_DISABLED);
                        }
                    }
                } else if (this.table.config.endDate && this.table.config.startDate == null) {
                    if (day > this.table.config.endDate.getDate()) {
                        el.classList.add(this.TBODY_TD_DISABLED);
                    }
                    if (j == this.table.config.disabledDays[i]) {
                        if (!this.table.checkClassContaining(el.classList, this.TBODY_TD_DISABLED)) {
                            el.classList.add(this.TBODY_TD_DISABLED);
                        }
                    }
                }
            }
        }

        private attachStartEndDateDisableClass(j: number, el: HTMLElement, day: number): void {
            /** Construct date based on the day so we can compare year month and date, not only date. */
            let currentIterationDate = new Date(this.table.config.currentDate.getFullYear(), this.table.config.currentDate.getMonth(), day);

            if (this.table.config.startDate && this.table.config.endDate) {
                if (currentIterationDate.getTime() < this.table.config.startDate.getTime() || currentIterationDate.getTime() > this.table.config.endDate.getTime()) {
                    el.classList.add(this.TBODY_TD_DISABLED);
                }
            } else if (this.table.config.startDate && this.table.config.endDate == null) {
                if (currentIterationDate.getTime() < this.table.config.startDate.getTime()) {
                    el.classList.add(this.TBODY_TD_DISABLED);
                }
            } else if (this.table.config.endDate && this.table.config.startDate == null) {
                if (currentIterationDate.getTime() > this.table.config.endDate.getTime()) {
                    el.classList.add(this.TBODY_TD_DISABLED);
                }
            }
        }

        public createMontlyView(): void {
            let self = this.table,
                 row: my.core.table.Row = new my.core.table.Row(this.table),
                 th: my.core.table.iCell,
                 td: my.core.table.tdCell,
                 finished = false,
                 day = 1,
                 nextMonthDay = 1,
                 previousMonthDayToStart;

            /** Reapply classes */
            this.table.element.className = "table table-bordered table-responsive col-sm-12 col-md-12 cal-monthview-table";

            /** Month view header */
            this.table.calendar_days_label.forEach((label, idx) => {
                th = new my.core.table.thCell();
                th.element.innerText = label;
                th.element.classList.add(this.THEAD_TH);
                row.addCell(th);
            });
            row.element.classList.add(this.THEAD_TR);
            this.table.tHead.addRow(row);

            /** Month view body */
            for (let i = 0; i < 9; i++) {
                row = new my.core.table.Row(this.table);
                for (let j = 0; j <= 6; j++) {
                    td = new my.core.table.tdCell();
                    /** add extra class for disabled days */
                    if (this.table.config.disabledDays.length > 0) {
                        this.attachDisabledDays(j, td.element, day);
                    } else if (this.table.config.startDate || this.table.config.endDate) {
                        this.attachStartEndDateDisableClass(j, td.element, day);
                    }

                    if (!this.table.checkClassContaining(td.element.classList, this.TBODY_TD_DISABLED)) {
                        /**
                             *  Bind cell click to the table config `cellClick` function
                             *  so it could be overrided if needed.
                             *  And only if it doesn contain class '*-disabled'
                             */
                        td.events.on.click(function (sender, e, data) {
                            self.config.cellClick(sender, e, data);
                        })
                    }

                    if (day <= this.table.monthLength && (i > 0 || j >= this.table.startingDay)) {
                        /**
                         *  Create empty div with the date related
                         *  and css class
                         */
                        td.element.appendChild(this.table.createDiv(day, this.TBODY_TD_DIV));
                        td.element.classList.add(this.TBODY_TD);
                        td.data = this.createMonthCellArray(day, undefined);

                        /** Append new css class if the flag isToday == 1 */
                        if (td.data[0]['isToday'] === 1) {
                            td.element.classList.add(this.TBODY_TD_ISTODAY);
                        }
                        row.addCell(td);

                        day++;
                    } else {
                        if (!finished) {
                            /** If it's January get the December previous year days */
                            if (new Date(this.table.config.currentDate).getMonth() === 0 && previousMonthDayToStart == null) {
                                previousMonthDayToStart = this.table.calendar_days_in_month[this.table.calendar_days_in_month.length - 1] - this.table.startingDay;
                            } else if (previousMonthDayToStart == null) {
                                previousMonthDayToStart = this.table.calendar_days_in_month[new Date(this.table.config.currentDate).getMonth() - 1] - (this.table.startingDay - 1);
                            }
                            td.element.classList.add(this.TBODY_TD_PREVIOUSORNEXTMONTH);

                            /**
                             *  If day is greater than month length and the flag `finished` is false
                             *  Then we have to get the next month length and render it.
                             */
                            if (day > this.table.monthLength) {
                                td.element.appendChild(this.table.createDiv(nextMonthDay, this.TBODY_TD_DIV));
                                td.data = this.createMonthCellArray(nextMonthDay, true);
                                nextMonthDay++;
                            } else {

                                td.element.appendChild(this.table.createDiv(previousMonthDayToStart, this.TBODY_TD_DIV));
                                td.data = this.createMonthCellArray(previousMonthDayToStart, false);
                                previousMonthDayToStart++;
                            }
                            row.addCell(td);
                        }
                    }
                    /** Stop creating rows */
                    if (day > this.table.monthLength && j == 6) {
                        finished = true;
                    }
                }
                this.table.tBody.addRow(row);
            }
        }

        public monthNavigationChange(next: boolean): void {
            let month = new Date(this.table.config.currentDate).getMonth(),
                 year = new Date(this.table.config.currentDate).getFullYear();

            if (next) {
                if (month == 11) {
                    month = 0;
                    this.table.config.currentDate = new Date(year + 1, month);
                    this.table.MonthLength = month;
                } else {
                    month += 1;
                    this.table.config.currentDate = new Date(year, month);
                    this.table.MonthLength = month;
                }
            } else if (!next) {
                if (month == 0) {
                    month = 11;
                    this.table.config.currentDate = new Date(year - 1, month);
                    this.table.MonthLength = month;
                } else {
                    month -= 1;
                    this.table.config.currentDate = new Date(year, month);
                    this.table.MonthLength = month;
                }

            }
        }

        public bindMonthAppointments(): void {
            let eventsContainer = this.table.createDiv('', this.EVENTS_WRAPPER, 'events'),
                self = this.table,
                _datesOffset: Array<object> = [],
                toPrint = false;

            if (this.table.appointments) {

                for (let i = 0; i < this.table.appointments.length; i++) {
                    this.table.tBody.rows.forEach((rowObj, rowIdx) => {
                        rowObj.cells.forEach((cellObj, cellIdx) => {
                            /* Set time to 0 so we can compare date and month only */
                            let appDate = new Date(new Date(this.table.appointments[i]['startDate']).setHours(0, 0, 0, 0)),
                                cellDate = new Date(new Date(cellObj['data'][0]['startDate']).setHours(0, 0, 0, 0));

                            if (appDate.getTime() == cellDate.getTime()) {
                                let cell = rowObj.cells[cellIdx].element,
                                    eventApp,
                                    appCounter;

                                /* we have to keep track of the previous element's offsetleft for the same time */
                                if (!this.table.isDateInArray(this.table.appointments[i]['startDate'], _datesOffset)) {
                                    /* Start render at about 35% of the cell height (or almost center) */
                                    _datesOffset.push(
                                        {
                                            startDate: new Date(new Date(this.table.appointments[i]['startDate']).setHours(0, 0, 0, 0)),
                                            offsetTop: cell.offsetTop + (cell.offsetHeight * 0.33),
                                            eventCounter: 0
                                        }
                                    )
                                }
                                /* We found the f app, now render it */
                                let eventDimension = {
                                    transformX: cell.offsetLeft,
                                    transformY: '', // overrided 
                                    w: cell.offsetWidth,
                                    h: cell.offsetHeight / 3,
                                    bColor: this.table.appointments[i]['Color'],
                                }
                                /* Override offsetTop */
                                if (_datesOffset.length > 0) {
                                    _datesOffset.forEach((obj, idx) => {
                                        if (new Date(obj['startDate']).getTime() == new Date(new Date(this.table.appointments[i]['startDate']).setHours(0, 0, 0, 0)).getTime()) {
                                            eventDimension['transformY'] = obj['offsetTop'];
                                            obj['offsetTop'] += cell.offsetHeight * 0.33;
                                            obj['eventCounter'] += 1;

                                            appCounter = obj['eventCounter'];
                                        }
                                    })
                                }
                                if (appCounter <= 2) {
                                    eventApp = new my.controls.cAppointment(this.EVENTS_DIV, eventDimension);
                                    /* InnerHTML like that , because someone may want to override it outside */
                                    eventApp.element.insertAdjacentHTML('afterbegin', this.table.config.appointmentTemplate(this.table.appointments[i]));
                                    eventApp.element.addEventListener('click', function (ev) {
                                        self.config.appointmentClick(self.appointments[i], ev)
                                    });
                                    eventsContainer.appendChild(eventApp.element);
                                } else {
                                    /* create small block which shows all other appointments on button popup */
                                    this.attachSmallAppointment();
                                }
                            }
                        })
                    })
                }
            }
            this.table.element.parentElement.appendChild(eventsContainer);
        }
    } /** end monthly view */

1 个答案:

答案 0 :(得分:1)

您应该拥有所有视图的公共接口,并将当前视图实例保存在类的成员中:

interface IView {
    create();
    bindAppointments();
    navigationChange(next: boolean);
}
export class Calendar extends my.core.calendar.CalendarTable {
    currentView: IView; // Set this when the current view changes,

    bindAppointments(): void {
       this.clearAll();
       this.currentView.bindAppointments();
    }   
}