How to make a dynamic calendar with Laravel 7 /Blade syntax

Worked on a small project that was about a website that must render a calendar for dynamic years, and point out of its a vacation, and some type of text for it.

This is one of the pages ( the homepage ), which loops thru all months in the given active year.

The empty card is for Ads, and its for a local project, so there is a cyrilic involved in the days.

So how we ended up here, and lets see how the code looks like

 

Years

Lets take a look at the Year Model in App , nothing serious or strange:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Year extends Model
{
    protected $guarded = [];
    public function months() {
        return $this->hasMany('App\Month','year_id');
    }


    public function slug() {
        $slug = url('g/'.$this->slug);

        return $slug;
    }

    public $casts = [
        'date_year' => 'date',
    ];

}

And the Migration File :

 

 public function up()
    {
        Schema::create('years', function (Blueprint $table) {
            $table->id();
            $table->timestamps();

            $table->string('str_name');
            $table->unsignedInteger('year_integer');
            $table->string('slug');
            $table->boolean('is_visible_nav')->default(true);
            $table->date('date_year')->nullable();
            $table->string('seo_title')->nullable();
            $table->string('seo_description')->nullable();
            $table->longText('body')->nullable();

        });
    }

 

Some people might think why is that even needed, when we can render everything dynamic with Carbon, and not involving a single query. That is correct, but its better to have these custom meta texts for google and for SEO, which will allow you to edit specifically the metas and text body for different years.

 

Months

Model:

 

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Month extends Model
{
    protected $guarded = [];


    public function year() {
        return $this->belongsTo('App\Year','year_id');
    }

    public function days() {
        return $this->hasMany('App\Day','month_id');
    }

    public $casts = [
        'date_month' => 'date',
    ];

    public function urlslug() {
        $slug = url( 'kalendar/' .$this->slug);

        return $slug;
    }

}

And the migration file:

 

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCalendarMonthsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('months', function (Blueprint $table) {
            $table->id();
            $table->string('str_name');
            $table->unsignedInteger('month_integer');
            $table->unsignedInteger('firstday');
            $table->unsignedInteger('lastday');
            $table->unsignedInteger('totaldays');
            $table->string('slug');

            $table->date('date_month')->nullable();
            $table->string('seo_title')->nullable();
            $table->string('seo_description')->nullable();
            $table->longText('body')->nullable();
            $table->timestamps();

            $table->foreignId('year_id')->nullable();
            $table->foreign('year_id')
                ->references('id')->on('years')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('calendar_months');
    }
}

 

So now we have to deal with - rendering and showing the real calendar blocks to the users. As you tought, the idea is to generate dynamically these calendars. Just to mention , that you dont even need the database for it - you can edit it a little bit and just use dynamic urls - for example /calendar/2056/03/ - to render for year 2056 and March month.

 

Rendering Views

 

So This is our main view file that is responsible for rendering a single block of month from the calendar.

Lets examine the whoel html table containing the blade syntax and after that looking more into the different components of it.

 <table class="table table-bordered table-vcenter border-2x">
            <thead>
            <th class="text-primary text-center">Monday</th>
            <th class="text-primary text-center">Tuesday</th>
            <th class="text-primary text-center">Wednesday</th>
            <th class="text-primary text-center">Thursday </th>
            <th class="text-primary text-center">Friday </th>
            <th class="text-white text-center bg-primary-light">Saturday </th>
            <th class="text-white text-center bg-primary-light">Sunday </th>
            </thead>
            <tbody>

                @if ($month->firstday == 0)
                    {!! str_repeat('<td class="bg-gray text-center datetd"></td>',6) !!}

                @else
                    {!! str_repeat('<td class="bg-gray text-center datetd"></td>',$month->firstday -1) !!}
                @endif


            @forelse($month->days as $day)

                    @php($dt = $day->date_day)

                @if($dt->dayOfWeek == \Carbon\Carbon::SUNDAY)

                    <td class="text-center bg-primary-light datetd {{$day->is_holiday ? 'bg-gd-pulse' : ''}}">
                        <a href="{{$day->urlslug()}}" class="text-white"><u>{{$day->day_integer}}</u></a>
                    </td>
                    <tr>

                        @elseif($dt->dayOfWeek == \Carbon\Carbon::SATURDAY)

                            <td class="text-center bg-primary-light datetd {{$day->is_holiday ? 'bg-danger' : ''}}">
                                <a href="{{$day->urlslug()}}" class="text-white"><u>{{$day->day_integer}}</u></a>
                            </td>

                @else
                            @if ($day->is_day_idea)
                                <td class="text-center datetd  bg-warning">
                                    <a href="{{$day->urlslug()}}" class="text-white"><u>{{$day->day_integer}}</u></a>
                                </td>
                            @else

                        <td class="text-center datetd  {{$day->is_holiday  ? 'bg-danger' : ''}}">
                            <a href="{{$day->urlslug()}}" class="{{$day->is_holiday ? 'bg-danger text-white' : ''}}"><u>{{$day->day_integer}}</u></a>
                        </td>

                    @endif
                @endif

            @empty

            @endforelse
                @if ($month->lastday == 0)

                @else
                    {!! str_repeat('<td class="bg-gray datetd"></td>',7 - $month->lastday) !!}

                @endif


            </tbody>
        </table>

 

The first thing you might notice is :

 

    @if ($month->firstday == 0)
                    {!! str_repeat('<td class="bg-gray text-center datetd"></td>',6) !!}

                @else
                    {!! str_repeat('<td class="bg-gray text-center datetd"></td>',$month->firstday -1) !!}
                @endif

What we do here is - if firstday of the month is equalls 0 , which means Sunday, then we render 6 <td> boxes which are empty, to fill the table, here is an example of it:

Otherwise, we need to start from the proper td box of the table, with this peace of code:

                @else
                    {!! str_repeat('<td class="bg-gray text-center datetd"></td>',$month->firstday -1) !!}
                @endif

In other words - we repeat and fill td boxes, with - firstday of the month - 1

 

In the foreach/ forelse, its nothing complicated, we just fill the dates and render them and most of the code inside it , its PROJECT BASED, aka : checking if its sunday/saturday, so highlight them, or checking if its holiday.

 

In the end , to fill properly the boxes, we have :

 

  @if ($month->lastday == 0)

                @else
                    {!! str_repeat('<td class="bg-gray datetd"></td>',7 - $month->lastday) !!}

                @endif

 

This will render properly every box in the calendar.