Wednesday, August 9, 2017

Add a calendar page

CakePHP
1. Setting up CakePHP3 with CentOS

2. Bake and Migration


4. Add a calendar page

5. Use "contain"

6. Use find('list')

Introduction


Last time, we used "bake" command to add a page, but this time, we will add a page without using "bake" command. bake command is easy and very quick to create various pages and functions, but if you need to adjust details of the pages, sometimes it is better to manually create pages without bake command so that you can decide every behavior by yourself.

In cakephp, to add a page, you need 2 or 3 files: controller, view (template) and probably a model (table) file if you want to use database for the page.

As a practice, we will add a calendar page in this post.

Database

Do the following commands to create a migration file:
$ cd /vagrant/cake
$ bin/cake bake migration CreateProducts name:string description:text created modified

Then do migrate the file:
$ bin/cake migrations migrate

You have the necessary table in the database now.

Model


Create the model for calendars.
$ bin/cake bake model Calendars

Now you have an entity file for calendars in cake/src/Model/Entity and a table file in cake/src/Model/Table.

Controller


At first, we will create the controller. The directory is something like this: C:\MyVM\MyCentOS\cake\src\Controller.
To create a php file, just create a txt file and change it to php file.




Then open the CalendarsController.php. Write like this in the php file:
<?php
namespace App\Controller;

use App\Controller\AppController;

class CalendarsController extends AppController
{
    public function index()
    {
        //Do nothing. Just simply display.
    }
}

Then save and close it.

View


Now we will create a view. Open "Template" foler which exists under cake/src. The directory should be something like this: C:\MyVM\MyCentOS\cake\src\Template
Then create a "Calendars" folder in the Template folder. In this folder, we will store our views for the calendar pages.


 Open the Calendars folder and create a "index.ctp".


Open the index.ctp and write like this:
<h1>Calendar</h1>
<p>This page is made to display calendar.</p>
And save and close it.

Now access the index page from the browser. The file can be accessed by yourURL/calendar/index. Or if you just access yourURL/calendar, the index page is automatically displayed. My URL is http://192.168.33.10/cake/, so the calendar page can be accessed from either http://192.168.33.10/cake/calendar or http://192.168.33.10/cake/calendar/index.

New page is added!

Calendar Helper


Now we will create a calendar view to display a calendar. Create a CalendarsHelper.php in Helper folder. The directory should be something like this: C:\MyVM\MyCentOS\cake\src\View\Helper




Open the CalendarsHelper.php and write like this inside:
<?php
namespace App\View\Helper;

use Cake\View\Helper;

class CalendarsHelper extends Helper {

   public function makeCalendar($month, $year, $eventArray=[]){
        $days = cal_days_in_month(CAL_GREGORIAN, $month, $year);
        $weekDayArray = ["0"=>"Sun", "1"=>"Mon", "2"=>"Tue", "3"=>"Wed", "4"=>"Thu", "5"=>"Fri", "6"=>"Sat"];
    
        echo "<table class='calendar'><tr>";
        for($i=0;$i<7;$i++){
             echo "<td class='weekdays'>".$weekDayArray[$i]."</td>";
        }
        echo "</tr><tr>";
    
        $firstdate = $year.'-'.$this->startWithZero($month).'-01';
        $firstDaysWeekDay = $this->getWeekday($firstdate);
        $weekdaynum = 0;
        for($i=1,$j=1;$j<=$days;$i++){
            if($i<8){
                if($firstDaysWeekDay <= $i-1){
                    $event = $this->setEvent($eventArray, $j);
                    echo "<td>".$j.$event."</td>";
                    if($i-1 === 6){
                        echo "</tr><tr>";
                    }
                    $j++;
                    continue;
                }else{
                    echo "<td></td>";
                    continue;
                }
            }else{
                $event = $this->setEvent($eventArray, $j);
                echo "<td>".$j.$event."</td>";
                $weekdaynum++;
                $j++;
            }
            if($weekdaynum%7===0) echo "</tr><tr>";
        }
        echo "</tr></table>";
    }

    private function getWeekday($date) {
        return date('w', strtotime($date));
    }

    private function startWithZero($num){
        if(is_numeric($num) && strlen($num) < 2){
            return "0".$num;               
        }else{
            return $num;       
        }
    }
 
    private function setEvent($eventArray, $j){
        $event = "";
        if(isset($eventArray[$this->startWithZero($j)])){
            if(is_array($eventArray[$this->startWithZero($j)])){
                foreach($eventArray[$this->startWithZero($j)] as $value){
                    $link = ''; //Link for each events here.
                    $eventdetail = isset($value['event']) ? $value['event'] : '';
                    $class = isset($value['eventClassName']) ? 'class="'.$value['eventClassName'].'"' : '';
                    $id = isset($value['id']) ? 'id="'.$value['id'].'"' : '';
                    $event .= "<div class='event-wrapper'><a ".$id." ".$class." href='".$link."'>".$eventdetail."</a></div>";
                }
            }
        }
        return $event;
    }

}
?>
Save and close it.

Now you can use this helper in the Template files.

Call view helper in template file


Open the index.ctp of Calendar. The directory should be something like C:\MyVM\MyCentOS\cake\src\Template\Calendar\index.ctp

<h1>Calendar</h1>
<p>This page is made to display calendar.</p>
<?php $this->Calendar->makeCalendar(7, 2017); ?>

Save and close it. Now see the /cake/calendar/index from browser again.
Calendar for July 2017 is displayed.

Now open the index.ctp of Calendar again and modify it like this.
<h1>Calendar</h1>
<p>This page is made to display calendar.</p>
<?php echo '<span class="text-center"><p>Today:'. Date('d/m/Y').'</p></span>';
$this->Calendar->makeCalendar(Date('m'), Date('Y')); ?>
Date('Y') and Date('m') always shows current date's year and month, so the CalendarsHelper's arguments always become current date. So the calendar also always become current year and current month's calendar. Date('d/m/Y') always shows current date with "dd/mm/YYYY" format.
The modified calendar page is below:



Modify Model

Entity and Virtual fields


Open the Entity folder. The directory should be something like this: C:\MyVM\MyCentOs\cake\src\Model\Entity
The entity file of Calendars is Calendar.php. Open this file.
In "Calendar.php", write as follows, and save and close it.

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\I18n\Date;

class Calendars extends Entity
{
    protected $_accessible = [
        'date' => true,
        'event' => true,
        'detail' => true,
        'created' => true,
        'modified' => true
    ];

    protected function _getYear() //Virtual field
    {
        $date = new Date($this->_properties['date']);
        $year = $date->format('Y');
        return $year;
    }

    protected function _getMonth() //Virtual field
    {
        $date = new Date($this->_properties['date']);
        $month = $date->format('m');
        return $month;
    }

    protected function _getDay() //Virtual field
    {
        $date = new Date($this->_properties['date']);
        $day = $date->format('d');
        return $day;
    }
}

These blue functions (_getYear, _getMonth, _getDay) are called "virtual field". If you want to modify data fetched from the database before rendering ctp files, you can make virtual fields in Entity file and in the virtual field functions, you modify the fetched data. These modified data can be treated as if they were fields fetched from the database. In other words, "year", "month" and "day" field will be added to the available fields in .ctp view files.

By the way, to deal with date type data in php, you can also use strtotime() function. This function is useful.


Modify Table and Controller, then create "add.ctp"


Open the CalendarsTable.php and write as follows. And save and close it.

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class CalendarsTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('calendars');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->addBehavior('Timestamp');
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->integer('id')
            ->allowEmpty('id', 'create');

        $validator
            ->dateTime('date')
            ->requirePresence('date', 'create')
            ->notEmpty('date');

        $validator
            ->scalar('event')
            ->requirePresence('event', 'create')
            ->notEmpty('event');

        $validator
            ->scalar('detail')
            ->requirePresence('detail', 'create')
            ->notEmpty('detail');

        $validator //Add this validator!
            ->notEmpty('date', 'Date is required')
            ->notEmpty('event', 'An event is required');

        return $validator;
    }
}


Open CalendarsController.php and write as follows. Save and close it.

<?php
namespace App\Controller;
use App\Controller\AppController;

class CalendarsController extends AppController
{
    public function index()
    {
        $calendars= $this->paginate($this->Calendars);
        $this->set(compact('calendars'));
    }

    public function add()
    {
        $calendars = $this->Calendars->newEntity();
        if ($this->request->is('post')) {
            $calendars = $this->Calendars->patchEntity($calendars, $this->request->getData());
            if ($this->Calendars->save($calendars)) {
                $this->Flash->success(__('The event has been saved in the calendar.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The event could not be saved. Please, try again.'));
        }
        $this->set(compact('calendars'));
        $this->set('_serialize', ['calendars']);
    }
}

Open the template folder of Calendars: C:\MyVM\MyCentOS\cake\src\Template\Calendars
And add "add.ctp" in the folder and write as follows in the file. Then save and close it.

<?php
/**
  * @var \App\View\AppView $this
  */
?>
<nav class="large-3 medium-4 columns" id="actions-sidebar">
    <ul class="side-nav">
        <li class="heading"><?= __('Actions') ?></li>
        <li><?= $this->Html->link(__('See Calendar'), ['action' => 'index']) ?></li>
        <li><?= $this->Html->link(__('Add Event in Calendar'), ['action' => 'add']) ?></li>
    </ul>
</nav>
<div class="calendar form large-9 medium-8 columns content">
    <?= $this->Form->create($calendars) ?>
    <fieldset>
        <legend><?= __('Add Calendar') ?></legend>
        <?php
            echo $this->Form->control('date',['minYear'=>Date('Y')-100,'maxYear'=>Date('Y')+5]);
            echo $this->Form->control('event');
            echo $this->Form->control('detail');
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
</div>

Open the template folder of Calendars and open the index.ctp. And change it as follows:

<h1>Calendar</h1>
<p>This page is made to display calendar.</p>
<?php echo '<span class="text-center"><p>Today:'. Date('d/m/Y').'</p></span>';
$events = array();
foreach ($calendars as $c):
    $events[$c->day][] = ['event'=>$c->event];
endforeach;
$this->Calendars->makeCalendar(Date('m'), Date('Y'), $events); ?>

Blue property day is the virtual field defined in the Entity file.

Test the calendar


Now add an event from http://192.168.33.10/cake/calendar/add


And you will see an event is added in the calendar.