Rails Calendar with Tui and StimulusJs
Tags: rails, javascript, webdev, ruby
Published: 2021-03-18 20:06:44

We will create a Rails 6 calendar Application using StimulusJs and Tui Calendar. To begin, we first create a new Rails application.
``` ruby
rails new calendar --database=postgresql
```
Once we have created our application, we then proceed to install StimulusJs.
``` ruby
rails webpacker:install:stimulus
```
We then proceed to install Tui Calendar using yarn.
``` javascript
yarn add tui-calendar
```
## Modeling Data
Once we have all our dependencies installed we can proceed to scaffolding the data we wish to store in our application. We would create a model called Event with the following attributes and types: 
```
CalendarID : Integer
Title : string
Location: string 
start: datetime
end: datetime
```
We run our scaffold generator to create the model, views and the controller for our data.
``` ruby
rails g scaffold schedule title:string calendar_id:integer start:datetime end:datetime location:string 
rails db:migrate
```
We then define the root path of our application
```
config/routes.rb
```
``` ruby
root 'schedules#index'
```
## Displaying the Calendar
Once the initial setup is done, we can now proceed to display our calendar. Navigate to 
app/views/schedules/index.html.erb and clear the scaffolded index page. We create a div tag and give it an id of calendar. we also provide some json data to the div tag. We wrap this div tag in a stimulus controller called calendar.
```
app/views/schedules/index.html.erb
```
``` html
<div data-controller="calendar">
<%= tag.div nil, data: {schedules: @schedules .to_json}, id: "calendar"%>
</div>
```
We then proceed to create our stimulus controller called ‘calendar_controller.js’ under the path app/javascript/controllers/calendar_controller.js and export the class
``` javascript
import { Controller } from "stimulus"
export default class extends Controller {
  connect() {
  }
}
```
Once we have done that we import several libraries:
``` javascript
import Rails from "@rails/ujs"
import 'tui-time-picker/dist/tui-time-picker.css';
import "tui-calendar/dist/tui-calendar.css";
import Calendar from "tui-calendar";
```
Note: We simply follow the steps on the github document page to import Calendar and its stylesheets. Once we have done that, we create a new instance of Calendar, and define its attributes such as id, name, defaultView, color. This provided in the code below:
``` javascript
 calendar = new Calendar(document.getElementById('calendar'), {
        id: "1",
        name: "My Calendar",
        defaultView: 'month',
        color: '#00a9ff',
          bgColor: '#00a9ff',
          dragBgColor: '#00a9ff',
          borderColor: 'red',
    
        milestone: true,    // Can be also ['milestone', 'task']
        scheduleView: true,  // Can be also ['allday', 'time']
        useCreationPopup: true,
        useDetailPopup: true,
        template: {
    
          popupDetailRepeat: function(schedule) {
            return 'Repeat : ' + schedule.recurrenceRule;
          },
          
          popupStateFree: function() {
            return 'Free';
          },
            milestone: function(schedule) {
                return '<span style="color:red;"><i class="fa fa-flag"></i> ' + schedule.title + '</span>';
            },
            milestoneTitle: function() {
                return 'Milestone';
            },
            task: function(schedule) {
                return '&nbsp;&nbsp;#' + schedule.title;
            },
            taskTitle: function() {
                return '<label><input type="checkbox" />Task</label>';
            },
            allday: function(schedule) {
                return schedule.title + ' <i class="fa fa-refresh"></i>';
            },
            alldayTitle: function() {
                return 'All Day';
            },
            time: function(schedule) {
                return schedule.title + ' <i class="fa fa-refresh"></i>' + schedule.start;
            }
        },
        month: {
            daynames: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
            startDayOfWeek: 0,
            narrowWeekend: true
        },
        week: {
            daynames: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
            startDayOfWeek: 0,
            narrowWeekend: true
        }
    });
```
After saving our changes, our calendar becomes viewable.
![Tui Calendar](https://i.imgur.com/kO3fgXu.png)

## Displaying Data on the Calendar
### Getting Calendar Data
We must define a method that helps us obtain the calendar data. We have already passed the data as json to our index page, so we must parse the data to make it available in our calendar controller. We store this parse information in a variable called schedules. Once we have done that, we iterate over the schedule and create a calendar Schedule that would display each element on the calendar. 
``` javascript
    getCalendardata(){
        var schedules = JSON.parse(document.querySelector("#calendar").dataset.schedules);
        window.schedules = schedules;
        schedules.forEach(schedule => {
        this.calendar.createSchedules([
        {
          id: schedule.id,
          calendarId: '1',
          title: schedule.title,
          category: 'time',
          dueDateClass: schedule.dueDateClass,
          location: schedule.location,
          start: schedule.start,
          end: schedule.end
        }
        ])
        });
      }
```
We then call this method underr connect(){} in Our Calendar Controller.
``` javascript
connect() {
      this.getCalendardata()
}
```
### Creating Calendar schedules
First we must make some changes to our schedules controller. After a successful update, create or destroy action, we do not want to be redirected. To solve this, we simply comment out several lines from our controller.

```ruby 
 def create
    @schedule = Schedule.new(schedule_params)
    respond_to do |format|
      if @schedule.save
        # format.html { redirect_to @schedule, notice: "Schedule was successfully created." }
        format.json { render :show, status: :created, location: @schedule }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @schedule.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @schedule.update(schedule_params)
        # format.html { redirect_to @schedule, notice: "Schedule was successfully updated." }
        format.json { render :show, status: :ok, location: @schedule }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @schedule.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @schedule.destroy
    respond_to do |format|
      # format.html { redirect_to schedules_url, notice: "Schedule was successfully destroyed." }
      format.json { head :no_content }
    end
  end
```
Secondly, we would relax the strong parameters on our controllers a bit.
``` ruby
    def schedule_params
      params.permit(:title, :calendar_id, :start, :end, :location)
    end
```
To begin display events on the calendar we must first define a javascript method that creates events. We define a method called createCalendarSchedule. Tui provides certain event handlers that we can use to create events. We will use ‘beforeCreateSchedule’ to create a schedule. When a user clicks on the calendar, a popup form is presented. When data is filled in the popup, we collect the information, create a formdata, and submit it via Rails.ajax

```javascript
reateCalendarSchedule(){
        let calendar = this.calendar;
        calendar.on('beforeCreateSchedule', function(event) {
        var triggerEventName = event.triggerEventName;
        var schedule =  {
          id: 1,
          calendarId: '1',
          title: event.title,
          category: 'time',
          location: event.location,
          start: event.start,
          end: event.end
        }
        if (triggerEventName === 'click') {
            // open writing simple schedule popup
            // schedule = {...};
        } else if (triggerEventName === 'dblclick') {
            // open writing detail schedule popup
            // schedule = {...};
        }
      
        calendar.createSchedules([schedule]);
        let formData = new FormData()
        formData.append('title', schedule.title);
        formData.append('category', schedule.category);
        formData.append('start', schedule.start._date);
        formData.append('end', schedule.end._date);
        formData.append('location', schedule.location);
      
        Rails.ajax({
          type: "POST",
          url: '/schedules',
          data: formData
        })
       
      });
      } 
```
We then call this method under connect(){} in Our Calendar Controller.
```javascript
connect() {
      this.getCalendardata()
      this.createCalendarSchedule()
  }
```
![Tui Calendar](https://i.imgur.com/qKmu2BA.gif)
### Updating Calendar Events
We would use another event handler to update calendar schedules. When we click on an already created schedule, a pop would appear that would allow us to edit or delete this schedule. We would use ‘beforeUpdateSchedule’ event to handle submitting our data. 
```javascript
updatedCalendarSchedule(){
        let calendar = this.calendar;
        calendar.on('beforeUpdateSchedule', function(event) {
          var schedule = event.schedule;
          var changes = event.changes;
          var formUpdate = new FormData()    
          if (changes.end) {
          formUpdate.append("end", changes.end._date)    
          }
          if (changes.start) {
          formUpdate.append("start", changes.start._date)    
          }
          if (changes.title) {
          formUpdate.append("title", changes.title)  
          }
          if (changes.category) {
          formUpdate.append("category", changes.category)  
          }
          calendar.updateSchedule(schedule.id, schedule.calendarId, changes);
          
          Rails.ajax({
          type: "PATCH",
          url: '/schedules/'+ schedule.id,
          data: formUpdate
          })
          
          });
      }
```
We create a form with our updated data and submit it via Rails.ajax to ‘/schedules/:id’.
We then call this method under connect(){}
```javascript
  connect() {
      this.updatedCalendarSchedule()
      this.getCalendardata()
      this.createCalendarSchedule()
  }
```
![Tui Calendar](https://i.imgur.com/ppB1IlH.gif)
### Deleting Calendar Events
We now define a method that uses ‘the BeforeDeleteSchedule’ event handler to delete a schedule entry. This event is called when we click on a schedule on the calendar and click on ‘Delete’. The function makes a request via Rails.ajax to delete with selected schedule based on its ID.
```javascript
      deleteCalendarSchedule(){
        let calendar = this.calendar
       calendar.on('beforeDeleteSchedule', function(event) {
          var schedule = event.schedule;
          calendar.deleteSchedule(schedule.id, schedule.calendarId)
          
          Rails.ajax({
            type: "DELETE",
            url: '/schedules/'+ schedule.id,
          })
          });
      }

```
We then call this method under connect(){}
``` javascript
  connect() {
      this.deleteCalendarSchedule()
      this.updatedCalendarSchedule()
      this.getCalendardata()
      this.createCalendarSchedule()
  }
```
![Tui Calendar](https://i.imgur.com/AZ2TIAd.gif)

{% github timnans/Calendar- %}