FullCalendar Events and Scheduling

#42 FullCalendar Events and Scheduling
8/14/2016

Summary

Learn how to incorporate FullCalendar into your Ruby on Rails application with listing and creating events. Using unobtrusive javascript, we can create a fast interactive calendar.
12
rails view calendar javascript ajax

Resources

FullCalendar - http://fullcalendar.io/
MomentJS - http://momentjs.com/
DateRangePicker - http://www.daterangepicker.com/
Source - https://github.com/driftingruby/042-fullcalendar

Downloads

FullCalendar 2.9.1 - https://github.com/arshaw/fullcalendar/releases/download/v2.9.1/fullcalendar-2.9.1.zip
MomentJS 2.14.1 - http://momentjs.com/downloads/moment.js

Install FullCalendar and MomentJS to your vendor directory.


Summary

If you want to use the gem instead, then you can ignore downloading FullCalendar and MomentJS. Instead, add the following to the Gemfile.

Gemfilegem 'fullcalendar-rails'
gem 'momentjs-rails'

Otherwise, be sure to include the required files in the vendor folder. The rest of the steps are the same regardless of which path you choose.

application.js//= require moment
//= require fullcalendar
application.css*= require fullcalendar
full_calendar.jsvar initialize_calendar;
initialize_calendar = function() {
  $('.calendar').each(function(){
    var calendar = $(this);
    calendar.fullCalendar({
      header: {
        left: 'prev,next today',
        center: 'title',
        right: 'month,agendaWeek,agendaDay'
      },
      selectable: true,
      selectHelper: true,
      editable: true,
      eventLimit: true,
      events: '/events.json',

      select: function(start, end) {
        $.getScript('/events/new', function() {});

        calendar.fullCalendar('unselect');
      },

      eventDrop: function(event, delta, revertFunc) {
        event_data = { 
          event: {
            id: event.id,
            start: event.start.format(),
            end: event.end.format()
          }
        };
        $.ajax({
            url: event.update_url,
            data: event_data,
            type: 'PATCH'
        });
      },
      
      eventClick: function(event, jsEvent, view) {
        $.getScript(event.edit_url, function() {});
      }
    });
  })
};
$(document).on('turbolinks:load', initialize_calendar);
view/visitors/index.html<div class='calendar'></div>
events_controller.rbclass EventsController < ApplicationController
  before_action :set_event, only: [:show, :edit, :update, :destroy]

  def index
    @events = Event.where(start: params[:start]..params[:end])
  end

  def show
  end

  def new
    @event = Event.new
  end

  def edit
  end

  def create
    @event = Event.new(event_params)
    @event.save
  end

  def update
    @event.update(event_params)
  end

  def destroy
    @event.destroy
  end

  private
    def set_event
      @event = Event.find(params[:id])
    end

    def event_params
      params.require(:event).permit(:title, :date_range, :start, :end, :color)
    end
end
events/_event.json.jbuilderdate_format = event.all_day_event? ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'

json.id event.id
json.title event.title
json.start event.start.strftime(date_format)
json.end event.end.strftime(date_format)

json.color event.color unless event.color.blank?
json.allDay event.all_day_event? ? true : false

json.update_url event_path(event, method: :patch)
json.edit_url edit_event_path(event)
events/new.js.erb$('#remote_container').html('<%= j render "new" %>');
$('#new_event').modal('show');
events/create.js.erb$('.calendar').fullCalendar(
  'renderEvent', 
  $.parseJSON("<%=j render(@event, format: :json).html_safe %>"),
  true
);
$('.modal').modal('hide');
events/edit.js.erb$('#remote_container').html('<%= j render "edit" %>');
$('#edit_event').modal('show');
events/update.js.erb$('.calendar').fullCalendar('removeEvents', [<%= @event.id %>]);
$('.calendar').fullCalendar(
  'renderEvent', 
  $.parseJSON("<%=j render(@event, format: :json).html_safe %>"), 
  true
);
$('.modal').modal('hide');
events/destroy.js.erb$('.calendar').fullCalendar('removeEvents', [<%= @event.id %>])
$('.modal').modal('hide');
events/index.json.jbuilderjson.array! @events do |event|
  date_format = event.all_day_event? ? '%Y-%m-%d' : '%Y-%m-%dT%H:%M:%S'
  json.id event.id
  json.title event.title
  json.start event.start.strftime(date_format)
  json.end event.end.strftime(date_format)
  json.color event.color unless event.color.blank?
  json.allDay event.all_day_event? ? true : false
  json.update_url event_path(event, method: :patch)
  json.edit_url edit_event_path(event)
end
635114?v=3&s=64
kobaltz said 7 months ago:

Based on some questions, the Event model looks like.

  create_table "events", force: :cascade do |t|
    t.string   "title"
    t.datetime "start"
    t.datetime "end"
    t.string   "color"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

1047150?v=3&s=64
wolfieorama said 4 months ago:

Thanks , good episode ... So how would you go about calculate the number of events per day and may be display that on the calendar ? 

635114?v=3&s=64
kobaltz said 4 months ago:

You have a few options on how to handle something like this. You could handle it on the server side with a model scope to check each individual day. However, that could be more taxing on the server if a calendar had several events; not to mention that you're calling the scope about 30 times per calendar view. An alternative would be to leverage client side calculations for the number of events. However, you could still run into issues with the client side taking a while to calculate the number of events.

Would your case use be for a smaller summary calendar? I know that the current version of FullCalendar does have a scaling feature where it will gracefully hide events and display a number of events for that day.


1047150?v=3&s=64
wolfieorama said 4 months ago:

thanks for a quick response: actually my use-case is quite simple and my events are quite controlled in the sense not more than 2 weeks form Today going forward and past events dont really matter.


So what I really want is to set a limit of events per day and when that limit is hit the background color of the day changes to indicate that the calendar for that day is full, hope that make sense ?

635114?v=3&s=64
kobaltz said 4 months ago:

Gotcha. It does make sense.

In that case, I would send an additional parameter with the JSON response of whether or not a day is blocked (which is calculated on the server side).

You can use the dayRender callback to change the color of a particular day based on the blocked days. Depending on how you are allowing new event creations, you would want to handle the validation there as well as the backend on the model.

This does get a bit tricky since you're having to validate on server side and render on client side as well.

Also check out this JSFiddle where they're adding a class to a date, dynamically, after the calendar is rendered.

1047150?v=3&s=64
wolfieorama said 4 months ago:

Yeah, thats sound like a good, good thing the events creation is being handled elsewhere not on the calendar, is calendar is purely for displaying.....

User will fist need to check on the calendar to see if there is a free day, so i am guessing the only server side check is if the events count limit has been hit or not ?

right ?

635114?v=3&s=64
kobaltz said 4 months ago:

Yeah, I'd calculate it on the server side and then pass the blocked days through the JSON render request. On the client side, put in the results of the client side validation. Overall, you just want to make sure that you're aren't allowing a client to go through a whole event record creation, only to tell them something that you already knew and could have told them from the beginning; that enrollment for that day is already filled.

1047150?v=3&s=64
wolfieorama said 4 months ago:

Sure thanks man !! will give it a shot 

1047150?v=3&s=64
wolfieorama said 4 months ago:

hay Man kinda got stuck, this is my calndar rendering code 

$(document).ready(function() {
  return $("#calendar").fullCalendar({
    header: {
      left: 'title',
      center: 'agendaDay,agendaWeek,month',
      right: 'today prev,next',
    },
    weekends: false,
    events: '/leave_events.json',
    dayRender: function( date, cell ) {
      if (moment().diff(date,'days') > 0){
        cell.css("background-color", "silver");
      }
    }
  });
});


and here is my json object builder


json.array!(@leave_events) do |leave_event|
  json.extract! leave_event, :id, :user_id, :team_id
  json.start leave_event.start_time
  json.end leave_event.end_time
end


still cant extract the count of events per day ... any tips 

635114?v=3&s=64
kobaltz said 4 months ago:

You could do something like this

class LeaveEvent < ActiveRecord::Base
  scope :events_on_date, -> (date)  { where('start_time <= ? AND end_time >= ?', date, date).size }
 ...
end

Then use the scope within the JSON builder.

1047150?v=3&s=64
wolfieorama said 4 months ago:

the thing thats troubling me now is how to pass the date in to the scope in the jbuilder without hardcoding it 


json.array!(@leave_events) do |leave_event|
  json.extract! leave_event, :id, :user_id, :team_id
  json.start leave_event.start_time
  json.end leave_event.end_time
  json.events_on_date LeaveEvent.events_on_date(date) // this line is where I can figure out stuff 
  json.max_allowable_leave leave_event.team.max_allowable_leave
end


see the rest of `calendar.js`


$(document).ready(function() {
  return $("#calendar").fullCalendar({
    header: {
      left: 'title',
      center: 'agendaDay,agendaWeek,month',
      right: 'today prev,next',
    },
    weekends: false,
    events: '/leave_events.json',
    eventLimit: 1,
    dayRender: function( date, cell ) {
      var events_on_date = ;
      $.getJSON('leave_events.json', function(json) {
        for (var i = 0; i < json.length; i++) {
          console.log(events_on_date);
          if (events_on_date > json[0].max_allowable_leave){
            cell.css("background-color", "red");
          }
        }
      });
      if (moment().diff(date,'days') > 0){
        cell.css("background-color", "silver");
      }
    }
  });
});

00000000000000000000000000000000?d=mm&f=y&s=64
[email protected] said 4 months ago:

First of all Great episode and thank you for these wonderful videos!

Question:  How do i populate calendar with recurring events if: 

I'm using ice_cube gem to build reccuring events in my rails app. i can get my recurring events to be created and show with this: 

            - @event.converted_schedule.occurrences_between(@ event.start_date, @ event.end_date).each do |date|

              = date.strftime("%A %B %d, %Y")

where i can get all the recurrences. 

I dont know how to do it! Can you please offer some suggestions? I'm a beginner in programming!

thanks again

olsi

24507266?v=3&s=64
nekifirus said about 2 months ago:

Hello!

Code in

events/_event.json.jbuilder 

not DRY. How I can refactor it?

18031943?v=3&s=64
jinw96 said about 2 months ago:

Thanks for your nice lecture!

I have some trouble with my _form file.

It's datetime doesn't relate to the time that I drag.

How can I relate dragged time to form's time?

Can you show me your form file??

18031943?v=3&s=64
jinw96 said about 2 months ago:

Oh, I saw your _form file in your video..

But what is the Date.today? 

Is it a model you made??

635114?v=3&s=64
kobaltz said about 2 months ago:

https://ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html#method-c-today

Date.today is a ruby core class and method which creates a date object of the current date.

Photo
Adário Muatelembe said about 2 months ago:

I'm following all the steps of the episode but the calendar is not being viewed. what should I do?

Photo
Adário Muatelembe said about 1 month ago:

Greetings.

I have a difficulty, I have implemented the canderary and it works perfectly.
But it does not interpret the jQuery validations made in the form.

And how to recall alerts on creation, update and delete


Excuse me, my English is not a big deal, but I tried. Lol

00000000000000000000000000000000?d=mm&f=y&s=64
Erick said about 1 month ago:

Greetings:

I enjoyed your video_cast episode on full_calendar and have tried to implement, but seem to be missing some key parts:  1) you mention visitor/view, but give no background on who/what/why that view is needed, nor what its db would contain, 2) using Ruby 2.4.0-rc1 and rails 5.0.1, my application tries to open html code instead of json -- how dit you create your scaffold/modify routes/or ?? to stop that behavior for the full_calendar actions?, and finally, 3) do you have a copy of this rails app demo for download?

Would like to know if this is a rails 5/ruby issue or my bad typing. 

Thanks for your consideration.

635114?v=3&s=64
kobaltz said about 1 month ago:

The visitor view is just a simple controller and has an action. Within there is a DIV which contains the ID calendar which is where I'm displaying the calendar. You would place the calendar div where ever appropriate for your application.

For the requests responding as HTML instead of JSON, this could be how you're requesting the data. I'm using format: :json which will make a call to get the json data by default. I didn't do anything special in creating the events controller/model. Check your application logs to see how it is being handled. You might get some hints there. 

For the JSON list of events in the jQuery, I'm calling the url /events.json which would be the same in Ruby as calling events_path(format: :json).

Hope this helps.

00000000000000000000000000000000?d=mm&f=y&s=64
Erick said about 1 month ago:

Thank you for your response.

Yes I am (mostly) getting json actions. I have a problem since you call templates _new and _edit, and application.json.erb, you give no indicator of what should / may be in those files.

Would you be willing to share the code for the missing modules? I also get an error undefined method `midnight' for nil:Nil in all_day_event?

Again, thank you for your consideration. Your presentation has come the closest to working of the several other "add full calendar" to rails tutorials that I have tried to implement. I have found that I had to change the suffix 'js' to 'son' on the new.json.erb and _new.json.erb files for rails5 not to complain about missing templates. In your video cast, I can see that you call out 'js' as you show on the page of content under the video. ????

635114?v=3&s=64
kobaltz said about 1 month ago:

You can access the source code for this episode at https://github.com/driftingruby/042-fullcalendar

00000000000000000000000000000000?d=mm&f=y&s=64
Erick said about 1 month ago:

Awesome!

Looks great.  I need to read up on Faker used in the seed.  I have never seen that but I easily see its value.

Thank you so much for making the code available!  I will be able to study and lean from it for quite a while.

Thanks again.

00000000000000000000000000000000?d=mm&f=y&s=64
Erick said about 1 month ago:

I intend to incorporate your code into an app I am creating for a friend who wants to be an author.  As your seed program shows, it can be easy to overload the viewer.  To simplify, I plan to add to the event record a couple of additional fields, and provide a mechanism preferably on the calendar itself where the additional fields would have collection_select menus for the new event record entries (multiple selections allowed).  He could then have calendar views for specific objects (characters, story plot, etc.).  In addition, one of the fields would hold an 'offset' value which would override the Date.today and move it forward/backward in time.   Thus if he were writing a period piece about the 15th century, I would expect the calendar for that selected calendar year would show up in the calendar view; i.e., "Feb 2017" would become "Feb 1517" if the offset were -500 years.

Doing a code search for date variations does not show me where that banner date value is being generated.  

Any hints or comments that you or your viewers might be willing to share?

Thanks 

00000000000000000000000000000000?d=mm&f=y&s=64
frank004 said 27 days ago:

Love to see this one with ice_cube recurrence.

635114?v=3&s=64
kobaltz said 27 days ago:

Sounds like a great idea. Would definitely have to follow an Outlook Calendar style functionality where a single occurrence could be edited of a series. Worth looking into.

Login to Comment