1. Gemfileにfullcalendar-railsとmomentjs-railsを追加
gem 'therubyracer'
gem 'fullcalendar-rails'
gem 'momentjs-rails'
gem 'jquery-rails'
gemをインストール
$ bundle install
2. カレンダーの表示
rails側にコントローラ・モデル作成
$ rails generate controller events index show new edit
$ rails generate model events
migrationファイルを編集
/db/migrate/◯◯_create_events.rb
class CreateEvents < ActiveRecord::Migration[5.0]
def change
create_table :events do |t|
t.string :title
t.datetime :start
t.datetime :end
t.string :color
t.boolean :allday
t.timestamps
end
end
end
データベースにテーブル追加
$ rake db:migrate
(productionも使う場合はRAILS_ENV=productionを追加)
コントローラ編集
/app/controllers/events_controller.rb
class EventsController < ApplicationController
before_action :set_event, only: [:show, :edit, :update, :destroy] #パラメータのidからレコードを特定するメソッド
def index
@events = Event.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @events }
format.json { render :json => @events }
end
end
def show
end
def new
@event = Event.new
end
def edit
end
def create
@event = Event.new(event_params)
respond_to do |format|
if @event.save
format.html { redirect_to @event, notice: 'Event was successfully created.' }
format.json { render :show, status: :created, location: @event }
else
format.html { render :new }
format.json { render json: @event.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @event.update(event_params)
format.html { redirect_to @event, notice: 'Event was successfully updated.' }
format.json { render :show, status: :ok, location: @event }
else
format.html { render :edit }
format.json { render json: @event.errors, status: :unprocessable_entity }
end
end
end
def destroy
@event.destroy
respond_to do |format|
format.html { redirect_to events_url, notice: 'Event was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_event
@event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(
:title,
:start,
:end,
:color,
:allday
)
end
end
(APIも使う場合は下記参考)
/config/routes.rb
Rails.application.routes.draw do
# ここから追記
namespace :api, { format: 'json' } do
namespace :v1 do
resources :events
end
end
# 追記終了
...
end
/app/controllers/api/v1/events_controller.rb
require "#{Rails.root}/app/controllers/application_controller.rb"
module Api
module V1
class EventsController < ApplicationController
load_and_authorize_resource
# CSRF対策
protect_from_forgery except: [:create, :update]
def index
@events = Event.order(:id).limit(params[:limit]).offset(params[:offset])
json = @events
render json: json.to_json
end
def show
@event = Event.find(params[:id])
render json: @event.to_json
end
def edit
@event = Event.find(params[:id])
end
def update
@event = Event.find(params[:id])
event_params.require(:title)
event_params.require(:start)
event_params.require(:end)
# event_params.require(:color)
# event_params.require(:allday)
respond_to do |format|
format.any
if @event.update!(event_params)
@event.save
render json: @event.to_json
else
render json: {status: "ng", code: 500, content: {message: "エラーだよ"}}
end
end
end
def new
@event = Event.new
end
def create
event_params.require(:title)
event_params.require(:start)
event_params.require(:end)
# event_params.require(:color)
# event_params.require(:allday)
@event = Event.new(event_params)
respond_to do |format|
format.any
if @event.save!
render json: @event
else
render json: {status: "ng", code: 500, content: {message: "エラーだよ"}}
end
end
end
def destroy
@event = Event.find(params[:id])
@event.destroy
render json: @event
end
private
def event_params
params[:event]
.permit(
:title,
:start,
:end,
:color,
:allday
)
end
end
end
end
viewに表示領域を作成
app/views/events/index.html.erb
<div id="calendar"></div>
jqueryでfullcalenderを呼び出し
app/assets/javascript/calendar.js
//設定例
$(document).ready(function() {
var select = function(start, end) {
var title = window.prompt("title");
start_time = start.unix()
var d = new Date( start_time * 1000 );
var year = d.getYear() + 1900;
var month = d.getMonth() + 1;
var day = d.getDate();
var hour = ( d.getHours() < 10 ) ? '0' + d.getHours() : d.getHours();
var min = ( d.getMinutes() < 10 ) ? '0' + d.getMinutes() : d.getMinutes();
var moment_start = year+"-"+month+"-"+day+" "+hour+":"+min;
var start_time = moment(moment_start).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
end_time = end.unix()
var d = new Date( end_time * 1000 );
var year = d.getYear() + 1900;
var month = d.getMonth() + 1;
var day = d.getDate();
var hour = ( d.getHours() < 10 ) ? '0' + d.getHours() : d.getHours();
var min = ( d.getMinutes() < 10 ) ? '0' + d.getMinutes() : d.getMinutes();
var moment_end = year+"-"+month+"-"+day+" "+hour+":"+min;
var end_time = moment(moment_end).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
var data = {
event: {
title: title,
start: start_time,
end: end_time,
allday: false
}
}
$.ajax({
type: "POST",
url: "/api/v1/events",
data: data,
success: function() {
calendar.fullCalendar('refetchEvents');
}
});
calendar.fullCalendar('unselect');
};
var calendar = $('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,agendaWeek,agendaDay'
},
axisFormat: 'H:mm',
timeFormat: 'H:mm',
monthNames: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
monthNamesShort: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
dayNames: ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'],
dayNamesShort: ['日','月','火','水','木','金','土'],
events: "/api/v1/events",
editable: true, // 編集可
selectable: true, // 選択可
selectHelper: true, // 選択時にプレースホルダーを描画
ignoreTimezone: false, // 自動選択解除
select: select, // 選択時に関数にパラメータ引き渡す
buttonText: {
prev: '<', // ‹
next: '>', // ›
prevYear: '<<', // «
nextYear: '>>', // »
today: '今日',
month: '月',
week: '週',
day: '日'
},
height: 800, // 高さ
defaultView: 'agendaWeek', // 初期表示ビュー
eventLimit: true, // allow "more" link when too many events
firstDay: 0, // 最初の曜日, 0:日曜日
weekends: true, // 土曜、日曜を表示
weekMode: 'fixed', // 週モード (fixed, liquid, variable)
weekNumbers: false, // 週数を表示
slotDuration: '00:30:00', // 表示する時間軸の細かさ
snapDuration: '00:15:00', // スケジュールをスナップするときの動かせる細かさ
minTime: "00:00:00", // スケジュールの開始時間
maxTime: "24:00:00", // スケジュールの最終時間
defaultTimedEventDuration: '10:00:00', // 画面上に表示する初めの時間(スクロールされている場所)
allDaySlot: false, // 終日スロットを非表示
allDayText:'allday', // 終日スロットのタイトル
slotMinutes: 15, // スロットの分
snapMinutes: 15, // 選択する時間間隔
firstHour: 9, // スクロール開始時間
eventClick: function(event) { //イベントをクリックしたときに実行
var id = event.id
var show_url = "/events/"+id
location.href = show_url;
},
eventResize: function(event) { //イベントをサイズ変更した際に実行
var id = event.id
var update_url = "/api/v1/events/"+id
var event_start_time = event._start._d
var year = event_start_time.getYear() + 1900;
var month = event_start_time.getMonth() + 1;
var day = event_start_time.getDate();
var hour = ( event_start_time.getHours() < 10 ) ? '0' + event_start_time.getHours() : event_start_time.getHours();
var min = ( event_start_time.getMinutes() < 10 ) ? '0' + event_start_time.getMinutes() : event_start_time.getMinutes();
var moment_start = year+"-"+month+"-"+day+" "+hour+":"+min;
var start_time = moment(moment_start).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
var event_end_time = event._end._d
var year = event_end_time.getYear() + 1900;
var month = event_end_time.getMonth() + 1;
var day = event_end_time.getDate();
var hour = ( event_end_time.getHours() < 10 ) ? '0' + event_end_time.getHours() : event_end_time.getHours();
var min = ( event_end_time.getMinutes() < 10 ) ? '0' + event_end_time.getMinutes() : event_end_time.getMinutes();
var moment_end = year+"-"+month+"-"+day+" "+hour+":"+min;
var end_time = moment(moment_end).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
var data = {
event: {
title: event.title,
start: start_time,
end: end_time,
allday: false
}
}
$.ajax({
type: "PATCH",
url: update_url,
data: data,
success: function() {
calendar.fullCalendar('refetchEvents');
}
});
calendar.fullCalendar('unselect');
},
eventDrop: function(event) { //イベントをドラッグ&ドロップした際に実行
var id = event.id
var update_url = "/api/v1/events/"+id
var event_start_time = event._start._d
var year = event_start_time.getYear() + 1900;
var month = event_start_time.getMonth() + 1;
var day = event_start_time.getDate();
var hour = ( event_start_time.getHours() < 10 ) ? '0' + event_start_time.getHours() : event_start_time.getHours();
var min = ( event_start_time.getMinutes() < 10 ) ? '0' + event_start_time.getMinutes() : event_start_time.getMinutes();
var moment_start = year+"-"+month+"-"+day+" "+hour+":"+min;
var start_time = moment(moment_start).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
var event_end_time = event._end._d
var year = event_end_time.getYear() + 1900;
var month = event_end_time.getMonth() + 1;
var day = event_end_time.getDate();
var hour = ( event_end_time.getHours() < 10 ) ? '0' + event_end_time.getHours() : event_end_time.getHours();
var min = ( event_end_time.getMinutes() < 10 ) ? '0' + event_end_time.getMinutes() : event_end_time.getMinutes();
var moment_end = year+"-"+month+"-"+day+" "+hour+":"+min;
var end_time = moment(moment_end).add(-9, 'hour').format("YYYY-MM-DD HH:mm");
var data = {
event: {
title: event.title,
start: start_time,
end: end_time,
allday: false
}
}
$.ajax({
type: "PATCH",
url: update_url,
data: data,
success: function() {
calendar.fullCalendar('refetchEvents');
}
});
calendar.fullCalendar('unselect');
}
});
});