first commit

This commit is contained in:
2020-10-17 14:15:26 +02:00
commit 5a262a1115
10 changed files with 1468 additions and 0 deletions

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
# 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…).
[Article on CodyHouse](https://codyhouse.co/gem/schedule-template)
[Demo](https://codyhouse.co/demo/schedule-template)
[License](https://codyhouse.co/license)
## Dependencies
This experiment is built upon the [CodyHouse Framework](https://github.com/CodyHouse/codyhouse-framework).
Make sure to include both the style.scss and util.js files of the framework.

1
assets/css/style.css Normal file

File diff suppressed because one or more lines are too long

561
assets/css/style.scss Normal file
View File

@@ -0,0 +1,561 @@
@import '../../../../../codyhouse-framework/main/assets/css/style.scss'; // ⚠️ make sure to import the CodyHouse framework
@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600'); // custom font
// --------------------------------
// Schedule Template - by CodyHouse.co
// --------------------------------
:root {
// colors
@include defineColorHSL(--cd-color-event-1, 199, 25%, 46%); // Smalt Blue
@include defineColorHSL(--cd-color-event-2, 271, 23%, 26%); // Martinique
@include defineColorHSL(--cd-color-event-3, 162, 14%, 68%); // Edward
@include defineColorHSL(--cd-color-event-4, 31, 89%, 68%); // Rajah
@include defineColorHSL(--cd-color-text, 0, 0%, 13%); // Black
@include defineColorHSL(--cd-color-border, 0, 0%, 92%); // Grey
// font
--font-primary: 'Source Sans Pro', sans-serif;
//schedule template
--schedule-rows-number: 19;
--schedule-rows-height: 50px;
}
body {
color: var(--cd-color-text);
}
a {
color: var(--cd-color-event-3);
}
.cd-schedule {
position: relative;
&::before { // never visible - this is used in js to check the current MQ
content: 'mobile';
display: none;
}
.js & {
@include breakpoint(md) {
width: calc(100% - 2*var(--component-padding));
margin-left: auto;
margin-right: auto;
max-width: var(--max-width-xl);
&::before {
content: 'desktop';
}
}
}
}
.cd-schedule__timeline { // events time
display: none;
.js & {
@include breakpoint(md) {
display: block;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
padding-top: var(--schedule-rows-height);
li {
position: relative;
height: var(--schedule-rows-height);
&::after { // this is used to create the table horizontal lines
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background: var(--cd-color-border);
}
&:last-of-type::after {
display: none;
}
span {
display: none;
}
}
}
@include breakpoint(lg) {
li {
&::after {
width: calc(100% - 60px);
left: 60px;
}
span {
display: inline-block;
transform: translateY(-50%);
font-size: var(--text-sm);
}
&:nth-of-type(2n) span {
display: none;
}
}
}
}
}
.cd-schedule__events {
position: relative;
z-index: 1;
.js & {
@include breakpoint(md) {
width: 100%;
> ul {
display: flex;
flex-wrap: nowrap;
}
}
@include breakpoint(lg) {
width: calc(100% - 60px); // 60px is the .cd-schedule__timeline > li::after element left
margin-left: 60px;
}
}
}
.cd-schedule__group { // group of same day events
margin-bottom: var(--space-lg);
.js & {
@include breakpoint(md) {
flex-basis: 0;
flex-grow: 1;
border: 1px solid var(--cd-color-border);
margin-bottom: 0; // reset style
&:not(:first-of-type) {
border-left-width: 0;
}
}
}
}
.cd-schedule__group > ul {
position: relative;
padding: 0 var(--component-padding);
display: flex;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
&::after { // never visible - used to add a right padding to .cd-schedule__group > ul
display: inline-block;
content: '-';
width: 1px;
height: 100%;
opacity: 0;
color: transparent;
}
.js & {
@include breakpoint(md) {
height: calc(var(--schedule-rows-height)*var(--schedule-rows-number));
overflow: visible;
padding: 0;
&::after { // reset style
display: none;
}
}
}
}
.cd-schedule__top-info { // day label
width: 100%;
padding: 0 var(--component-padding);
> span {
display: inline-block;
margin-bottom: var(--space-sm);
font-weight: bold;
}
.js & {
@include breakpoint(md) {
// vertically center its content
display: flex;
align-items: center;
justify-content: center;
height: var(--schedule-rows-height);
border-bottom: 1px solid var(--cd-color-border);
padding: 0; // reset style
> span {
font-weight: normal;
font-size: var(--text-sm);
margin-bottom: 0;
}
}
}
}
.cd-schedule__event {
flex-shrink: 0; // force them to stay on one line
float: left; // flex fallback
height: 150px;
width: 70%;
max-width: 300px;
margin-right: var(--space-md);
transition: opacity .2s, background .2s;
a {
display: block;
height: 100%;
padding: var(--space-sm);
box-shadow: inset 0 -3px 0 rgba(#000, .2);
text-decoration: none;
}
a::before { // event start/end date
content: attr(data-start)' - 'attr(data-end);
}
.js & {
@include breakpoint(sm) {
width: 40%;
}
@include breakpoint(md) {
position: absolute;
z-index: 3;
width: calc(100% + 2px); // top position and height will be set using js
left: -1px;
max-width: none; // reset style
margin-right: 0;
a {
padding: var(--space-sm);
box-shadow: 0 10px 20px rgba(#000, .1), inset 0 -3px 0 rgba(#000, .2);
}
}
}
}
.js {
@include breakpoint(md) {
.cd-schedule__event--selected { // classes added when an user select the event
visibility: hidden;
}
.cd-schedule--loading .cd-schedule__event {
// the class .cd-schedule--loading is added by default to the .cd-schedule element
// it is removed as soon as the single events are placed in the schedule plan (using javascript)
opacity: 0;
}
}
}
.cd-schedule__name, // event name in the schedule template
.cd-schedule__event a::before, // event date in the schedule template
.cd-schedule-modal__name, // event name in the modal element
.cd-schedule-modal__date { // event date in the modal element
display: block;
color: var(--color-white);
font-weight: bold;
@include fontSmooth;
}
.cd-schedule__name,
.cd-schedule-modal__name {
font-size: var(--text-lg);
@include breakpoint(md) {
font-size: calc(var(--text-sm)*1.2);
}
}
.cd-schedule-modal__date, // not included in the the HTML but added using JavScript
.cd-schedule__event a::before {
opacity: .7;
margin-bottom: var(--space-xxxs);
@include breakpoint(md) {
font-size: calc(var(--text-xs)*1.05);
margin-bottom: var(--space-xxxxs);
}
}
.cd-schedule__event [data-event="event-1"],
.cd-schedule-modal[data-event="event-1"] .cd-schedule-modal__header-bg {
// this is used to set a background color for the event and the modal window
background: var(--cd-color-event-1);
}
.cd-schedule__event [data-event="event-2"],
.cd-schedule-modal[data-event="event-2"] .cd-schedule-modal__header-bg {
background: var(--cd-color-event-2);
}
.cd-schedule__event [data-event="event-3"],
.cd-schedule-modal[data-event="event-3"] .cd-schedule-modal__header-bg {
background: var(--cd-color-event-3);
}
.cd-schedule__event [data-event="event-4"],
.cd-schedule-modal[data-event="event-4"] .cd-schedule-modal__header-bg {
background: var(--cd-color-event-4);
}
.cd-schedule-modal {
position: fixed;
z-index: 3;
top: 0;
right: 0;
height: 100%;
width: 100%;
visibility: hidden;
transform: translateZ(0); // Force Hardware acceleration
transform: translateX(100%);
transition: transform .4s, visibility .4s;
transition-timing-function: cubic-bezier(.5,0,.1,1);
@include breakpoint(md) {
// reset style
right: auto;
width: auto;
height: auto;
transform: translateX(0);
will-change: transform, width, height;
transition: height .4s, width .4s, transform .4s, visibility .4s;
transition-timing-function: cubic-bezier(.5,0,.1,1);
}
}
.cd-schedule-modal__header {
position: relative;
height: 70px;
display: flex;
align-content: center;
width: 100%;
@include breakpoint(md) {
position: absolute;
display: block;
top: 0;
left: 0;
height: 100%;
}
}
.cd-schedule-modal__content {
position: relative;
z-index: 3;
display: flex;
align-items: center;
padding: var(--space-sm) var(--component-padding);
@include breakpoint(md) {
// reset style
display: block;
padding: var(--space-sm);
}
}
.cd-schedule-modal__body {
position: relative;
width: 100%;
height: calc(100% - 70px); // 70px is the .cd-schedule-modal__header height
@include breakpoint(md) {
height: 100%;
width: auto;
}
}
.cd-schedule-modal__event-info {
position: relative;
z-index: 2;
line-height: var(--body-line-height);
height: 100%;
overflow: hidden;
font-size: calc(var(--text-sm) * 1.2);
> div {
overflow: auto;
height: 100%;
padding: var(--space-md) var(--component-padding);
}
@include breakpoint(md) {
opacity: 0;
font-size: var(--text-sm);
> div {
padding: calc(var(--space-md)*1.3) calc(var(--space-lg)*1.2) calc(var(--space-md)*1.3) calc(var(--space-md)*1.3);
}
}
}
.cd-schedule-modal__header-bg,
.cd-schedule-modal__body-bg { // these are the morphing backgrounds - visible on desktop only
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
@include breakpoint(md) {
// Force Hardware acceleration
transform: translateZ(0);
will-change: transform;
backface-visibility: hidden;
}
}
.cd-schedule-modal__header-bg {
z-index: 2;
transform-origin: top center;
@include breakpoint(md) {
transition: transform .4s;
transition-timing-function: cubic-bezier(.5,0,.1,1);
}
}
.cd-schedule-modal__body-bg {
z-index: 1;
background: var(--color-white);
transform-origin: top left;
@include breakpoint(md) {
opacity: 0;
transform: none;
}
}
.cd-schedule-modal--no-transition {
transition: none;
.cd-schedule-modal__header-bg,
.cd-schedule-modal__body-bg {
transition: none !important;
}
}
.cd-schedule-modal__date {
display: none;
@include breakpoint(md) {
display: block;
}
}
.cd-schedule-modal__close { // close modal icon
position: absolute;
z-index: 3;
top: 0;
right: 0;
height: 70px;
width: 70px;
background: alpha(var(--color-black), .1);
&::before, &::after { // these are the two lines of the 'X' icon
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 2px;
height: 22px;
background: var(--color-white);
backface-visibility: hidden;
}
&::before {
transform: translateX(-50%) translateY(-50%) rotate(45deg);
}
&::after {
transform: translateX(-50%) translateY(-50%) rotate(-45deg);
}
@include breakpoint(md) {
width: 40px;
height: 40px;
background: transparent;
opacity: 0;
&::after, &::before {
background: var(--cd-color-text);
height: 16px;
}
}
}
.cd-schedule-modal--open { // this class is added as soon as an event is selected
transform: translateX(0);
visibility: visible;
.cd-schedule-modal__event-info > div { // smooth scroll on iOS touch deviceS
-webkit-overflow-scrolling: touch;
}
}
@include breakpoint(md) {
.cd-schedule-modal--animation-completed .cd-schedule-modal__close,
.cd-schedule-modal--content-loaded.cd-schedule-modal--animation-completed .cd-schedule-modal__event-info {
// the .cd-schedule-modal--animation-completed class is added when the modal animation is completed
// the .cd-schedule-modal--content-loaded class is added when the modal content has been loaded (using ajax)
opacity: 1;
transition: opacity .2s;
}
.cd-schedule-modal--open .cd-schedule-modal__body-bg {
opacity: 1;
transition: transform .4s;
transition-timing-function: cubic-bezier(.5,0,.1,1);
}
}
.cd-schedule__cover-layer { // layer between the content and the modal window
position: fixed;
z-index: 2;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: alpha(var(--color-black), 0.8);
opacity: 0;
visibility: hidden;
transition: opacity .4s, visibility .4s;
}
.cd-schedule-modal--open + .cd-schedule__cover-layer {
opacity: 1;
visibility: visible;
}
//demo style
.cd-main-header h1 {
color: var(--cd-color-text);
font-weight: 700;
}
.cd-article-link {
font-size: var(--text-sm);
transition: opacity .2s;
&:hover {
opacity: 0.8;
}
}

376
assets/js/main.js Normal file
View File

@@ -0,0 +1,376 @@
// Schedule Template - by CodyHouse.co
function ScheduleTemplate( element ) {
this.element = element;
this.timelineItems = this.element.getElementsByClassName('cd-schedule__timeline')[0].getElementsByTagName('li');
this.timelineStart = getScheduleTimestamp(this.timelineItems[0].textContent);
this.timelineUnitDuration = getScheduleTimestamp(this.timelineItems[1].textContent) - getScheduleTimestamp(this.timelineItems[0].textContent);
this.topInfoElement = this.element.getElementsByClassName('cd-schedule__top-info')[0];
this.singleEvents = this.element.getElementsByClassName('cd-schedule__event');
this.modal = this.element.getElementsByClassName('cd-schedule-modal')[0];
this.modalHeader = this.element.getElementsByClassName('cd-schedule-modal__header')[0];
this.modalHeaderBg = this.element.getElementsByClassName('cd-schedule-modal__header-bg')[0];
this.modalBody = this.element.getElementsByClassName('cd-schedule-modal__body')[0];
this.modalBodyBg = this.element.getElementsByClassName('cd-schedule-modal__body-bg')[0];
this.modalClose = this.modal.getElementsByClassName('cd-schedule-modal__close')[0];
this.modalDate = this.modal.getElementsByClassName('cd-schedule-modal__date')[0];
this.modalEventName = this.modal.getElementsByClassName('cd-schedule-modal__name')[0];
this.coverLayer = this.element.getElementsByClassName('cd-schedule__cover-layer')[0];
this.modalMaxWidth = 800;
this.modalMaxHeight = 480;
this.animating = false;
this.supportAnimation = Util.cssSupports('transition');
this.initSchedule();
};
ScheduleTemplate.prototype.initSchedule = function() {
this.scheduleReset();
this.initEvents();
};
ScheduleTemplate.prototype.scheduleReset = function() {
// according to the mq value, init the style of the template
var mq = this.mq(),
loaded = Util.hasClass(this.element, 'js-schedule-loaded'),
modalOpen = Util.hasClass(this.modal, 'cd-schedule-modal--open');
if( mq == 'desktop' && !loaded ) {
Util.addClass(this.element, 'js-schedule-loaded');
this.placeEvents();
modalOpen && this.checkEventModal(modalOpen);
} else if( mq == 'mobile' && loaded) {
//in this case you are on a mobile version (first load or resize from desktop)
Util.removeClass(this.element, 'cd-schedule--loading js-schedule-loaded');
this.resetEventsStyle();
modalOpen && this.checkEventModal();
} else if( mq == 'desktop' && modalOpen ) {
//on a mobile version with modal open - need to resize/move modal window
this.checkEventModal(modalOpen);
Util.removeClass(this.element, 'cd-schedule--loading');
} else {
Util.removeClass(this.element, 'cd-schedule--loading');
}
};
ScheduleTemplate.prototype.resetEventsStyle = function() {
// remove js style applied to the single events
for(var i = 0; i < this.singleEvents.length; i++) {
this.singleEvents[i].removeAttribute('style');
}
};
ScheduleTemplate.prototype.placeEvents = function() {
// on big devices - place events in the template according to their time/day
var self = this,
slotHeight = this.topInfoElement.offsetHeight;
for(var i = 0; i < this.singleEvents.length; i++) {
var anchor = this.singleEvents[i].getElementsByTagName('a')[0];
var start = getScheduleTimestamp(anchor.getAttribute('data-start')),
duration = getScheduleTimestamp(anchor.getAttribute('data-end')) - start;
var eventTop = slotHeight*(start - self.timelineStart)/self.timelineUnitDuration,
eventHeight = slotHeight*duration/self.timelineUnitDuration;
this.singleEvents[i].setAttribute('style', 'top: '+(eventTop-1)+'px; height: '+(eventHeight +1)+'px');
}
Util.removeClass(this.element, 'cd-schedule--loading');
};
ScheduleTemplate.prototype.initEvents = function() {
var self = this;
for(var i = 0; i < this.singleEvents.length; i++) {
// open modal when user selects an event
this.singleEvents[i].addEventListener('click', function(event){
event.preventDefault();
//if(!self.animating) self.openModal(this.getElementsByTagName('a')[0]);
});
}
//close modal window
this.modalClose.addEventListener('click', function(event){
event.preventDefault();
if( !self.animating ) self.closeModal();
});
this.coverLayer.addEventListener('click', function(event){
event.preventDefault();
if( !self.animating ) self.closeModal();
});
};
ScheduleTemplate.prototype.openModal = function(target) {
var self = this;
var mq = self.mq();
this.animating = true;
//update event name and time
this.modalEventName.textContent = target.getElementsByTagName('em')[0].textContent;
this.modalDate.textContent = target.getAttribute('data-start')+' - '+target.getAttribute('data-end');
this.modal.setAttribute('data-event', target.getAttribute('data-event'));
//update event content
this.loadEventContent(target.getAttribute('data-content'));
Util.addClass(this.modal, 'cd-schedule-modal--open');
setTimeout(function(){
//fixes a flash when an event is selected - desktop version only
Util.addClass(target.closest('li'), 'cd-schedule__event--selected');
}, 10);
if( mq == 'mobile' ) {
self.modal.addEventListener('transitionend', function cb(){
self.animating = false;
self.modal.removeEventListener('transitionend', cb);
});
} else {
var eventPosition = target.getBoundingClientRect(),
eventTop = eventPosition.top,
eventLeft = eventPosition.left,
eventHeight = target.offsetHeight,
eventWidth = target.offsetWidth;
var windowWidth = window.innerWidth,
windowHeight = window.innerHeight;
var modalWidth = ( windowWidth*.8 > self.modalMaxWidth ) ? self.modalMaxWidth : windowWidth*.8,
modalHeight = ( windowHeight*.8 > self.modalMaxHeight ) ? self.modalMaxHeight : windowHeight*.8;
var modalTranslateX = parseInt((windowWidth - modalWidth)/2 - eventLeft),
modalTranslateY = parseInt((windowHeight - modalHeight)/2 - eventTop);
var HeaderBgScaleY = modalHeight/eventHeight,
BodyBgScaleX = (modalWidth - eventWidth);
//change modal height/width and translate it
self.modal.setAttribute('style', 'top:'+eventTop+'px;left:'+eventLeft+'px;height:'+modalHeight+'px;width:'+modalWidth+'px;transform: translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');
//set modalHeader width
self.modalHeader.setAttribute('style', 'width:'+eventWidth+'px');
//set modalBody left margin
self.modalBody.setAttribute('style', 'margin-left:'+eventWidth+'px');
//change modalBodyBg height/width ans scale it
self.modalBodyBg.setAttribute('style', 'height:'+eventHeight+'px; width: 1px; transform: scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');
//change modal modalHeaderBg height/width and scale it
self.modalHeaderBg.setAttribute('style', 'height: '+eventHeight+'px; width: '+eventWidth+'px; transform: scaleY('+HeaderBgScaleY+')');
self.modalHeaderBg.addEventListener('transitionend', function cb(){
//wait for the end of the modalHeaderBg transformation and show the modal content
self.animating = false;
Util.addClass(self.modal, 'cd-schedule-modal--animation-completed');
self.modalHeaderBg.removeEventListener('transitionend', cb);
});
}
//if browser do not support transitions -> no need to wait for the end of it
this.animationFallback();
};
ScheduleTemplate.prototype.closeModal = function() {
var self = this;
var mq = self.mq();
var item = self.element.getElementsByClassName('cd-schedule__event--selected')[0],
target = item.getElementsByTagName('a')[0];
this.animating = true;
if( mq == 'mobile' ) {
Util.removeClass(this.modal, 'cd-schedule-modal--open');
self.modal.addEventListener('transitionend', function cb(){
Util.removeClass(self.modal, 'cd-schedule-modal--content-loaded');
Util.removeClass(item, 'cd-schedule__event--selected');
self.animating = false;
self.modal.removeEventListener('transitionend', cb);
});
} else {
var eventPosition = target.getBoundingClientRect(),
eventTop = eventPosition.top,
eventLeft = eventPosition.left,
eventHeight = target.offsetHeight,
eventWidth = target.offsetWidth;
var modalStyle = window.getComputedStyle(self.modal),
modalTop = Number(modalStyle.getPropertyValue('top').replace('px', '')),
modalLeft = Number(modalStyle.getPropertyValue('left').replace('px', ''));
var modalTranslateX = eventLeft - modalLeft,
modalTranslateY = eventTop - modalTop;
Util.removeClass(this.modal, 'cd-schedule-modal--open cd-schedule-modal--animation-completed');
//change modal width/height and translate it
self.modal.style.width = eventWidth+'px';self.modal.style.height = eventHeight+'px';self.modal.style.transform = 'translateX('+modalTranslateX+'px) translateY('+modalTranslateY+'px)';
//scale down modalBodyBg element
self.modalBodyBg.style.transform = 'scaleX(0) scaleY(1)';
//scale down modalHeaderBg element
// self.modalHeaderBg.setAttribute('style', 'transform: scaleY(1)');
self.modalHeaderBg.style.transform = 'scaleY(1)';
self.modalHeaderBg.addEventListener('transitionend', function cb(){
//wait for the end of the modalHeaderBg transformation and reset modal style
Util.addClass(self.modal, 'cd-schedule-modal--no-transition');
setTimeout(function(){
self.modal.removeAttribute('style');
self.modalBody.removeAttribute('style');
self.modalHeader.removeAttribute('style');
self.modalHeaderBg.removeAttribute('style');
self.modalBodyBg.removeAttribute('style');
}, 10);
setTimeout(function(){
Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
}, 20);
self.animating = false;
Util.removeClass(self.modal, 'cd-schedule-modal--content-loaded');
Util.removeClass(item, 'cd-schedule__event--selected');
self.modalHeaderBg.removeEventListener('transitionend', cb);
});
}
//if browser do not support transitions -> no need to wait for the end of it
this.animationFallback();
};
ScheduleTemplate.prototype.checkEventModal = function(modalOpen) {
// this function is used on resize to reset events/modal style
this.animating = true;
var self = this;
var mq = this.mq();
if( mq == 'mobile' ) {
//reset modal style on mobile
self.modal.removeAttribute('style');
self.modalBody.removeAttribute('style');
self.modalHeader.removeAttribute('style');
self.modalHeaderBg.removeAttribute('style');
self.modalBodyBg.removeAttribute('style');
Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
self.animating = false;
} else if( mq == 'desktop' && modalOpen) {
Util.addClass(self.modal, 'cd-schedule-modal--no-transition cd-schedule-modal--animation-completed');
var item = self.element.getElementsByClassName('cd-schedule__event--selected')[0],
target = item.getElementsByTagName('a')[0];
var eventPosition = target.getBoundingClientRect(),
eventTop = eventPosition.top,
eventLeft = eventPosition.left,
eventHeight = target.offsetHeight,
eventWidth = target.offsetWidth;
var windowWidth = window.innerWidth,
windowHeight = window.innerHeight;
var modalWidth = ( windowWidth*.8 > self.modalMaxWidth ) ? self.modalMaxWidth : windowWidth*.8,
modalHeight = ( windowHeight*.8 > self.modalMaxHeight ) ? self.modalMaxHeight : windowHeight*.8;
var HeaderBgScaleY = modalHeight/eventHeight,
BodyBgScaleX = (modalWidth - eventWidth);
setTimeout(function(){
self.modal.setAttribute('style', 'top:'+(windowHeight/2 - modalHeight/2)+'px;left:'+(windowWidth/2 - modalWidth/2)+'px;height:'+modalHeight+'px;width:'+modalWidth+'px;transform: translateY(0) translateX(0)');
//change modal modalBodyBg height/width
self.modalBodyBg.style.height = modalHeight+'px';self.modalBodyBg.style.transform = 'scaleY(1) scaleX('+BodyBgScaleX+')';self.modalBodyBg.style.width = '1px';
//set modalHeader width
self.modalHeader.setAttribute('style', 'width:'+eventWidth+'px');
//set modalBody left margin
self.modalBody.setAttribute('style', 'margin-left:'+eventWidth+'px');
//change modal modalHeaderBg height/width and scale it
self.modalHeaderBg.setAttribute('style', 'height: '+eventHeight+'px;width:'+eventWidth+'px; transform:scaleY('+HeaderBgScaleY+');');
}, 10);
setTimeout(function(){
Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
self.animating = false;
}, 20);
}
};
ScheduleTemplate.prototype.loadEventContent = function(content) {
// load the content of an event when user selects it
var self = this;
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
self.modal.getElementsByClassName('cd-schedule-modal__event-info')[0].innerHTML = self.getEventContent(httpRequest.responseText);
Util.addClass(self.modal, 'cd-schedule-modal--content-loaded');
}
}
};
httpRequest.open('GET', content+'.html');
httpRequest.send();
};
ScheduleTemplate.prototype.getEventContent = function(string) {
// reset the loaded event content so that it can be inserted in the modal
var div = document.createElement('div');
div.innerHTML = string.trim();
return div.getElementsByClassName('cd-schedule-modal__event-info')[0].innerHTML;
};
ScheduleTemplate.prototype.animationFallback = function() {
if( !this.supportAnimation ) { // fallback for browsers not supporting transitions
var event = new CustomEvent('transitionend');
self.modal.dispatchEvent(event);
self.modalHeaderBg.dispatchEvent(event);
}
};
ScheduleTemplate.prototype.mq = function(){
//get MQ value ('desktop' or 'mobile')
var self = this;
return window.getComputedStyle(this.element, '::before').getPropertyValue('content').replace(/'|"/g, "");
};
function getScheduleTimestamp(time) {
//accepts hh:mm format - convert hh:mm to timestamp
time = time.replace(/ /g,'');
var timeArray = time.split(':');
var timeStamp = parseInt(timeArray[0])*60 + parseInt(timeArray[1]);
return timeStamp;
};
function init() {
var scheduleTemplate = document.getElementsByClassName('js-cd-schedule'),
scheduleTemplateArray = [],
resizing = false;
if( scheduleTemplate.length > 0 ) { // init ScheduleTemplate objects
for( var i = 0; i < scheduleTemplate.length; i++) {
(function(i){
scheduleTemplateArray.push(new ScheduleTemplate(scheduleTemplate[i]));
})(i);
}
window.addEventListener('resize', function(event) {
// on resize - update events position and modal position (if open)
if( !resizing ) {
resizing = true;
(!window.requestAnimationFrame) ? setTimeout(checkResize, 250) : window.requestAnimationFrame(checkResize);
}
});
window.addEventListener('keyup', function(event){
// close event modal when pressing escape key
if( event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape' ) {
for(var i = 0; i < scheduleTemplateArray.length; i++) {
scheduleTemplateArray[i].closeModal();
}
}
});
function checkResize(){
for(var i = 0; i < scheduleTemplateArray.length; i++) {
scheduleTemplateArray[i].scheduleReset();
}
resizing = false;
};
}
}
init();
// after changes call .scheduleReset();

174
assets/js/util.js Normal file
View File

@@ -0,0 +1,174 @@
// Utility function
function Util () {};
/*
class manipulation functions
*/
Util.hasClass = function(el, className) {
if (el.classList) return el.classList.contains(className);
else return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'));
};
Util.addClass = function(el, className) {
var classList = className.split(' ');
if (el.classList) el.classList.add(classList[0]);
else if (!Util.hasClass(el, classList[0])) el.className += " " + classList[0];
if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' '));
};
Util.removeClass = function(el, className) {
var classList = className.split(' ');
if (el.classList) el.classList.remove(classList[0]);
else if(Util.hasClass(el, classList[0])) {
var reg = new RegExp('(\\s|^)' + classList[0] + '(\\s|$)');
el.className=el.className.replace(reg, ' ');
}
if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' '));
};
Util.toggleClass = function(el, className, bool) {
if(bool) Util.addClass(el, className);
else Util.removeClass(el, className);
};
Util.setAttributes = function(el, attrs) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
};
/*
DOM manipulation
*/
Util.getChildrenByClassName = function(el, className) {
var children = el.children,
childrenByClass = [];
for (var i = 0; i < el.children.length; i++) {
if (Util.hasClass(el.children[i], className)) childrenByClass.push(el.children[i]);
}
return childrenByClass;
};
/*
Animate height of an element
*/
Util.setHeight = function(start, to, element, duration, cb) {
var change = to - start,
currentTime = null;
var animateHeight = function(timestamp){
if (!currentTime) currentTime = timestamp;
var progress = timestamp - currentTime;
var val = parseInt((progress/duration)*change + start);
element.setAttribute("style", "height:"+val+"px;");
if(progress < duration) {
window.requestAnimationFrame(animateHeight);
} else {
cb();
}
};
//set the height of the element before starting animation -> fix bug on Safari
element.setAttribute("style", "height:"+start+"px;");
window.requestAnimationFrame(animateHeight);
};
/*
Smooth Scroll
*/
Util.scrollTo = function(final, duration, cb) {
var start = window.scrollY || document.documentElement.scrollTop,
currentTime = null;
var animateScroll = function(timestamp){
if (!currentTime) currentTime = timestamp;
var progress = timestamp - currentTime;
if(progress > duration) progress = duration;
var val = Math.easeInOutQuad(progress, start, final-start, duration);
window.scrollTo(0, val);
if(progress < duration) {
window.requestAnimationFrame(animateScroll);
} else {
cb && cb();
}
};
window.requestAnimationFrame(animateScroll);
};
/*
Focus utility classes
*/
//Move focus to an element
Util.moveFocus = function (element) {
if( !element ) element = document.getElementsByTagName("body")[0];
element.focus();
if (document.activeElement !== element) {
element.setAttribute('tabindex','-1');
element.focus();
}
};
/*
Misc
*/
Util.getIndexInArray = function(array, el) {
return Array.prototype.indexOf.call(array, el);
};
Util.cssSupports = function(property, value) {
if('CSS' in window) {
return CSS.supports(property, value);
} else {
var jsProperty = property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase();});
return jsProperty in document.body.style;
}
};
/*
Polyfills
*/
//Closest() method
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
}
if (!Element.prototype.closest) {
Element.prototype.closest = function(s) {
var el = this;
if (!document.documentElement.contains(el)) return null;
do {
if (el.matches(s)) return el;
el = el.parentElement || el.parentNode;
} while (el !== null && el.nodeType === 1);
return null;
};
}
//Custom Event() constructor
if ( typeof window.CustomEvent !== "function" ) {
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}
/*
Animation curves
*/
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};

3
event-abs-circuit.html Executable file
View File

@@ -0,0 +1,3 @@
<div class="cd-schedule-modal__event-info">
<div>Abs Circuit. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non? </div>
</div>

3
event-restorative-yoga.html Executable file
View File

@@ -0,0 +1,3 @@
<div class="cd-schedule-modal__event-info">
<div>Restorative Yoga. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
</div>

3
event-rowing-workout.html Executable file
View File

@@ -0,0 +1,3 @@
<div class="cd-schedule-modal__event-info">
<div>Rowing Workout. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
</div>

3
event-yoga-1.html Executable file
View File

@@ -0,0 +1,3 @@
<div class="cd-schedule-modal__event-info">
<div>Yoga 1. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
</div>

329
index.html Normal file
View File

@@ -0,0 +1,329 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>document.getElementsByTagName("html")[0].className += " js";</script>
<link rel="stylesheet" href="assets/css/style.css">
<title>Schedule Template | CodyHouse</title>
</head>
<body>
<!-- Buttons to add to scheduled elements -->
<div class="cd-schedule cd-schedule--loading margin-top-lg margin-bottom-lg js-cd-schedule">
<div class="cd-schedule__timeline">
<ul>
<li><span>09:00</span></li>
<li><span>09:30</span></li>
<li><span>10:00</span></li>
<li><span>10:30</span></li>
<li><span>11:00</span></li>
<li><span>11:30</span></li>
<li><span>12:00</span></li>
<li><span>12:30</span></li>
<li><span>13:00</span></li>
<li><span>13:30</span></li>
<li><span>14:00</span></li>
<li><span>14:30</span></li>
<li><span>15:00</span></li>
<li><span>15:30</span></li>
<li><span>16:00</span></li>
<li><span>16:30</span></li>
<li><span>17:00</span></li>
<li><span>17:30</span></li>
<li><span>18:00</span></li>
</ul>
</div> <!-- .cd-schedule__timeline -->
<!--<div class="cd-schedule__events">
<ul>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Monday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="11:00" data-end="12:30" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="14:00" data-end="15:15" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Tuesday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="10:00" data-end="11:00" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="11:30" data-end="13:00" data-content="event-restorative-yoga" data-event="event-4" href="#0">
<em class="cd-schedule__name">Restorative Yoga</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="13:30" data-end="15:00" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="15:45" data-end="16:45" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Wednesday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="09:00" data-end="10:15" data-content="event-restorative-yoga" data-event="event-4" href="#0">
<em class="cd-schedule__name">Restorative Yoga</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="10:45" data-end="11:45" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
<div style="white-space: nowrap;">
<button onclick="this.parentElement.style.visibility = 'hidden'">Accept</button>
<button onclick="this.parentElement.parentElement.style.visibility = 'hidden'">Remove</button>
</div>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="8:00" data-end="10:45" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="13:45" data-end="15:00" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Thursday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="12:00" data-end="13:45" data-content="event-restorative-yoga" data-event="event-4" href="#0">
<em class="cd-schedule__name">Restorative Yoga</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="15:30" data-end="16:30" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="17:00" data-end="18:30" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Friday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="10:00" data-end="11:00" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="12:30" data-end="14:00" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="15:45" data-end="16:45" data-content="event-yoga-1" data-event="event-1" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Saturday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="11:00" data-end="12:30" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="14:00" data-end="15:15" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
<li class="cd-schedule__group">
<div class="cd-schedule__top-info"><span>Sunday</span></div>
<ul>
<li class="cd-schedule__event">
<a data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1" href="#0">
<em class="cd-schedule__name">Abs Circuit</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="11:00" data-end="12:30" data-content="event-rowing-workout" data-event="event-2" href="#0">
<em class="cd-schedule__name">Rowing Workout</em>
</a>
</li>
<li class="cd-schedule__event">
<a data-start="14:00" data-end="15:15" data-content="event-yoga-1" data-event="event-3" href="#0">
<em class="cd-schedule__name">Yoga Level 1</em>
</a>
</li>
</ul>
</li>
</ul>
</div>
-->
<div id="calendar_wrapper" class="cd-schedule__events">
</div>
<div class="cd-schedule-modal">
<header class="cd-schedule-modal__header">
<div class="cd-schedule-modal__content">
<span class="cd-schedule-modal__date"></span>
<h3 class="cd-schedule-modal__name"></h3>
</div>
<div class="cd-schedule-modal__header-bg"></div>
</header>
<div class="cd-schedule-modal__body">
<div class="cd-schedule-modal__event-info"></div>
<div class="cd-schedule-modal__body-bg"></div>
</div>
<a href="#0" class="cd-schedule-modal__close text-replace">Close</a>
</div>
<div class="cd-schedule__cover-layer"></div>
</div> <!-- .cd-schedule -->
<script>
class Appointment {
constructor(name, start, end, type = 1) {
this.name = name;
this.start = start;
this.end = end;
this.type = type; //needs to be in [1, 4]
}
as_html = function() {
return `<li class="cd-schedule__event">
<a data-start="${ this.start }" data-end="${ this.end }" data-content="event-yoga-1" data-event="event-${this.type}" href="#0">
<em class="cd-schedule__name">${this.name}</em>
</a>
</li>`
}
}
class Day {
constructor(name, appointment_list) {
this.name = name
this.appointment_list = appointment_list;
}
as_html = function() {
var result = `<li class="cd-schedule__group"><div class="cd-schedule__top-info"><span> ${ this.name }</span></div><ul>`;
this.appointment_list.forEach(appoinment => {
result += appoinment.as_html();
});
result += '</ul></li>';
return result
}
}
class Schedule {
constructor(day_list){
this.day_list = day_list
}
as_html = function() {
var result = '<ul>';
this.day_list.forEach(day => {
result += day.as_html();
});
result += '</ul>';
return result;
}
}
document.getElementById("calendar_wrapper").innerHTML += new Schedule([
new Day("Monday", [
new Appointment("Test", "9:00", "10:00", 1),
new Appointment("Test", "17:00", "18:00", 1),
]),
new Day("Tuesday", [
]),
new Day("Wednesday", [
]),
new Day("Thursday", [
]),
new Day("Friday", [
]),
new Day("Saturday", [
]),
new Day("Sunday", [
])
]).as_html();
</script>
<script src="assets/js/util.js"></script> <!-- util functions included in the CodyHouse framework -->
<script src="assets/js/main.js"></script>
</body>
</html>