Search code examples
javascriptvue.jscalendarcss-grid

How to make a simple calendar using CSS Grid, Vue and moment/date-fns


I've made a simple calendar using the Vuejs, momentjs and css-grids layout. But the week starts with Sunday (in american style). How can I change the code to make it start with Monday (european style calendar)? I tried to change the column method decrementing it to 2, but only shows properly august and other months started with wrong weekdays. Also I guess I need somehow change the css option grid-template-columns to support Mondays as the first days of week.

Here's the codepen: https://codepen.io/moogeek/pen/oNWyWvM

const calendar = new Vue({
  el: '#app',
  data() {
    return {
      date:moment(),
      days: [],
      monthName: '',
    }
  },
  methods: {
    column(index) {
      if (index == 0) {
        return this.days[0].day() + 1
      }
    },
    isToday(day) {
      return moment().isSame(day, 'day')
    },
    updateMonth(){
      this.monthName = this.date.format("MMMM YYYY")
      let monthDate = this.date.startOf('month');
    
      this.days = [...Array(monthDate.daysInMonth())].map((_, i) => monthDate.clone().add(i, 'day'))
    },
    nextMonth(){
      this.date.add(1,'month')
      this.updateMonth()
    },
    previousMonth(){
      this.date.subtract(1,'month')
      this.updateMonth()
    },
  },
  mounted() {
    this.updateMonth()
  }
})
html {
  overflow:hidden;
}

#app {
    width:100%;
    height:100%;
    font-size: 100%;
    user-select: none;
    overflow:hidden;
}

.page-content {
  overflow: hidden;
}

.calendar-wrap {
  padding-top:4vh;
}

.calendar-header {
    width: 100vw;
    font-size: 170%;
    text-align:center;
    background-color: transparent;
}
.calendar-header .month-name, .calendar-header button{
  display: inline-block;
}

.calendar-header .month-name{
  color:#000;
  font-size:150%;
}
.calendar-header .current-month-value {
  color:#000;
}

.calendar-header .link, .calendar-header .link:hover,.calendar-header .link:active {text-decoration:none;}

.calendar-header .calendar-prev-month-button {
  margin-right: 1vh;
}

.calendar-header .calendar-next-month-button {
  margin-left: 1vh;
}

.calendar-wrapper {
    align-items: center;
    box-sizing: border-box;
    display: flex;
    justify-content: center;
    padding: 0 2em 0 2em;
  }  
  #calendar{
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    max-width: 1024px;
    width: 100%;
    transition: transform .3s ease 0s;
  }

  #calendar > *{
    align-items: center;
    display: flex;
    justify-content: center;
  }
  
  #calendar > *::before {
    content: "";
    display: inline-block;
    height: 0;
    padding-bottom: 100%;
    width: 1px;
  }
  
  #calendar > *.today{
    color: black;
    border: 0.1em solid black;
    border-radius: 100%;
  }

  #calendar > .day {
    border-radius: 100%;
    margin:3px;
  }
<html>
  <head>
      <meta charset="utf-8">
      <title>Calendar</title>


  </head>
 <body>
   <div id="app" class="page-content">
      <div class="calendar-wrap">      
              <section class="calendar-header">
                  <a @click="previousMonth()"class="link icon-only calendar-prev-month-button"><i class="icon icon-prev"></i></a>
                  <a class="current-month-value link">{{ monthName }}</a>
                  <a @click="nextMonth()" class="link icon-only calendar-next-month-button"><i class="icon icon-next"></i></a>
              </section>

              <section id="calendar-wrapper" class="calendar-wrapper skeletons">
                <main id="calendar">
                  
                    <div class="weekday">S</div>
                    <div class="weekday">M</div>
                    <div class="weekday">T</div>
                    <div class="weekday">W</div>
                    <div class="weekday">T</div>
                    <div class="weekday">F</div>
                    <div class="weekday">S</div>
                  
                  <div v-for="(day, index) in days"
                      :data-date="day.format('DD.MM.YYYY')"
                      :style="{ gridColumn: column(index) }" 
                      :class="{ day, today: isToday(day) }">
                    <span>{{ day.format('D') }}</span>
                  </div>
                </main> 
              </section>
    </div>
</div>
</body>
</html>

upd. Ok perhaps I came to a better solution of wrapping weekdays and days in separated containers and adding grid-column:7 property to the first day child. Also changed column function so it doesn't add addiontal offset:

https://codepen.io/moogeek/pen/ExmRzgb

Now onmount the August month is showing correctly, but if I click prev/next months it is somehow cloned out with September's days order... Please could you let me know what am I doing wrong and how do I correct the code so it can show all months properly:


Solution

  • Ok I've temporarily added an additional check if the first day of current month is Sunday - then grid-column property is not assigned: https://codepen.io/moogeek/pen/JjNBBqo

    <div v-for="(day, index) in days"
        :data-date="day.format('DD.MM.YYYY')" 
        :style="{gridColumn:setGridColumn(index)}"
        :class="{ day, today: isToday(day) }">
        <span>{{ day.format('D') }}</span>
    </div>
    
    //inside methods
    setGridColumn(index){
          return (this.days[0].startOf('month').format('e')!=6) ? this.column(index) : null;
    }
    

    But maybe there are another options to achieve this or possibly it leads to other bugs?