Schedule Template

Schedule Template

A simple template that lets you display events on a timeline, as well as organize them in groups (week days, conference rooms etc…)

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the MIT license. You can support our project with a Paypal donation 🙌

We’ve come across this web component many times: when we check the schedule of a conference, or the timetable of the classes of our gym. From a web designer perspective, it is handy to have a simple, responsive template to use if you ever need to create a schedule table. So we built one!

Creating the structure

The HTML structure is composed of three different elements: a div.timeline for the events timeline(09:00, 09:30, ..), a div.events wrapping the events list and a div.event-modal for the modal window used to provide more details about the selected event.

<div class="cd-schedule">
   <div class="timeline">
      <ul>
         <li><span>09:00</span></li>
         <li><span>09:30</span></li>
         <!-- additional elements here -->
      </ul>
   </div>

   <div class="events">
      <ul>
         <li class="events-group">
            <div class="top-info"><span>Monday</span></div>

            <ul>
               <li class="single-event" data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1">
                  <a href="#0">
                     <em class="event-name">Abs Circuit</em>
                  </a>
               </li>

               <!-- other events here -->
            </ul>
         </li>

         <li class="events-group">
            <div class="top-info"><span>Tuesday</span></div>

            <ul>
               <!-- events here -->
            </ul>
         </li>

         <!-- additional li.events-group here -->
      </ul>
   </div>

   <div class="event-modal">
      <header class="header">
         <div class="content">
            <span class="event-date"></span>
            <h3 class="event-name"></h3>
         </div>

         <div class="header-bg"></div>
      </header>

      <div class="body">
         <div class="event-info"></div>
         <div class="body-bg"></div>
      </div>

      <a href="#0" class="close">Close</a>
   </div>
</div>

Adding style

On small devices (window width less than 800px), all the events inside an .events-group are lined up horizontally: we set a display: flex to the .events-group > ul element and an overflow-x: scroll to make the events scrollable.

.cd-schedule .events .events-group > ul {
  position: relative;
  padding: 0 5%;
  /* force its children to stay on one line */
  display: flex;
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;
}

.cd-schedule .events .single-event {
  /* force them to stay on one line */
  flex-shrink: 0;
  float: left;
  height: 150px;
  width: 70%;
  max-width: 300px;
}

As for the .event-modal, it is has a fixed position and it is moved to the right outside the viewport. When the user selects an event, the .modal-is-open class is used to translate the .event-modal back into the viewport.

.cd-schedule .event-modal {
  position: fixed;
  z-index: 3;
  top: 0;
  right: 0;
  height: 100%;
  width: 100%;
  visibility: hidden;
  transform: translateX(100%);
  transition: transform .4s, visibility .4s;
}

.cd-schedule.modal-is-open .event-modal {
  /* .modal-is-open class is added as soon as an event is selected */
  transform: translateX(0);
  visibility: visible;
}

On bigger devices, all the events are in absolute position and placed inside a timetable: the top position and the height of each event are evaluated using the data-start and data-end attributes of the event itself and set using JavaScript (more in the Events handling section).

@media only screen and (min-width: 800px) {
  .cd-schedule .events {
    float: left;
    width: 100%;
  }
  .cd-schedule .events .events-group {
    width: 20%;
    float: left;
  }
  .cd-schedule .events .single-event {
    position: absolute;
    z-index: 3;
    /* top position and height will be set using js */
    width: calc(100% + 2px);
    left: -1px;
  }
}

As for the .event-modal, the opening/closing animation is created using jQuery combined with CSS Transitions and Transformations (more in the Events handling section).

Events handling

To implement this event schedule, we created a SchedulePlan object and used the scheduleReset() and initEvents() functions to init the schedule and attach event handlers to the proper elements.

function SchedulePlan( element ) {
   this.element = element;
   this.timeline = this.element.find('.timeline');
   //...
  
   this.eventsWrapper = this.element.find('.events');
   this.eventsGroup = this.eventsWrapper.find('.events-group');
   this.singleEvents = this.eventsGroup.find('.single-event');
   //..

   this.scheduleReset();
   this.initEvents();
}

On big devices, the scheduleReset() method takes care of placing the events inside the timetable and set their height. To evaluate the height, for example, we calculate the duration of the event (data-end minus data-start), divide it by the 'eventUnit' (in our case it's 30 minutes) and then multiply it by the height of 'timeline unit' (in our case, 50px).

var self = this;
this.singleEvents.each(function(){
   //place each event in the grid -> need to set top position and height
   var start = getScheduleTimestamp($(this).attr('data-start')), //getScheduleTimestamp converts hh:mm to timestamp
       duration = getScheduleTimestamp($(this).attr('data-end')) - start;

   var eventTop = self.eventUnitHeight*(start - self.timelineStart)/self.timelineUnitDuration,
       eventHeight = self.eventUnitHeight*duration/self.timelineUnitDuration;
  
   $(this).css({
      top: (eventTop -1) +'px',
      height: (eventHeight+1)+'px'
   });
});

When the user selects an event, the jQuery load() function is used to load the content of the event just selected (its data-content is used to determine the file content to be loaded).
In addition to that, on big devices, the .event-modal is animated to show the event content. First, the .event-modal is placed on top of the selected event and its height and width are changed to be equal to the ones of the selected event; then the .header-bg and .body-bg elements are scaled up to create the morphing animation; at the end of this animation, the modal content is revealed.

SchedulePlan.prototype.openModal = function(event) {
   var self = this;
   var mq = self.mq();
   this.animating = true;

   //update event name and time
   this.modalHeader.find('.event-name').text(event.find('.event-name').text());
   this.modalHeader.find('.event-date').text(event.find('.event-date').text());
   this.modal.attr('data-event', event.parent().attr('data-event'));

   //update event content
   this.modalBody.find('.event-info').load(event.parent().attr('data-content')+'.html .event-info > *', function(data){
      //once the event content has been loaded
      self.element.addClass('content-loaded');
   });

   this.element.addClass('modal-is-open');

   if( mq == 'mobile' ) {
      self.modal.one(transitionEnd, function(){
         self.modal.off(transitionEnd);
         self.animating = false;
      });
   } else {
      //change modal height/width and translate it
      self.modal.css({
         top: eventTop+'px', //this is the selected event top position
         left: eventLeft+'px', //this is the selected event left position
         height: modalHeight+'px', //this is the modal final height
         width: modalWidth+'px', //this is the modal final width
      });
      transformElement(self.modal, 'translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');

      //set modalHeader width
      self.modalHeader.css({
         width: eventWidth+'px',  //this is the selected event width
      });
      //set modalBody left margin
      self.modalBody.css({
         marginLeft: eventWidth+'px',
      });

      //change modalBodyBg height/width and scale it
      self.modalBodyBg.css({
         height: eventHeight+'px',
         width: '1px',
      });
      transformElement(self.modalBodyBg, 'scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');

      //change modal modalHeaderBg height/width and scale it
      self.modalHeaderBg.css({
         height: eventHeight+'px',
         width: eventWidth+'px',
      });
      transformElement(self.modalHeaderBg, 'scaleY('+HeaderBgScaleY+')');

      self.modalHeaderBg.one(transitionEnd, function(){
         //wait for the  end of the modalHeaderBg transformation and show the modal content
         self.modalHeaderBg.off(transitionEnd);
         self.animating = false;
         self.element.addClass('animation-completed');
      });
   }
};

One note: we implemented a simple load() function to upload the new html content, but you may wanna replace it with a $.ajax call in order to handle errors, beforeSend request etc. according to your project.

Join our newsletter

Get our monthly recap with the latest CodyHouse news

We use cookies to give you the best possible website experience. By using CodyHouse, you agree to our Privacy Policy.