I recently created a Search Bar to see the process of creation and how I could get it to work. It was a tremendous help in getting my head to wrap around the intricacies of creating and running such a thing.
So when it came time to implement a few calendar features, I chose to do the same thing. I feel most comfortable implementing features and tools when I can understand what makes them truly work. Building the features/tools myself (when I have time) is my preferred method of accomplishing this goal.
I had some experience creating minimal calendar functionality with Ruby on Rails before this. This post was my first time attempting these features with a ReactJS Frontend & Rails backend. After completing this task, I plan to test and implement a calendar library to see the differences, which I'll be writing about next week.
The main functionalities I need are:
Ability to create a lesson on a particular date. Preferably with a calendar date selector.
Have start and end times for each lesson. Preferably with an easy to use time selector.
Have both the date and times associated with the lesson object on the backend.
Last but not least, have all of these properties accessible for viewing and editing later.
One
With the help of React Bootstrap Forms, the first goal was easy to achieve.
<Form.Group as={Row} controlId="dateGroup">
<Form.Label column xs={1}>Date:</Form.Label>
<Col xs={2}>
<Form.Control required type="date" rows="1" name="date" onChange={this.handleChange} />
</Col>
</Form.Group>
Two
After a bit of research into trying different options with date and time connected, I decided to separate the concerns. After discovering there was a 'date' type for form, I also discovered there's a time type.
<Form.Group as={Row} controlId="timeGroup">
<Form.Label column xs={1}>Start Time:</Form.Label>
<Col xs={2}>
<Form.Control required type="time" rows="1" name="start_time" onChange={this.handleChange} />
</Col>
<Form.Label column xs={1}>End Time:</Form.Label>
<Col xs={2}>
<Form.Control required type="time" rows="1" name="end_time" onChange={this.handleChange} />
</Col>
</Form.Group>
While my original goal was to create and associate at the same time, this method is simplistic, even with dealing with the separation later.
Three
This goal took slightly more work. Because of the separation of date and times, I would deal with them individually. This method ended up working out in the end, as it gave me more flexibility to manipulate and send data back and forth the way I wanted to. To start, Javascript and Ruby have different beliefs on the format a date should be by default. Ruby:
Mon, 01 Jan
JS:
Wed Jan 01 2020 12:00:00 GMT-0500 (Eastern Standard Time)
In usual fashion, JS is trying hard to be overly helpful while Ruby keeps things simple. Depending on the use case, one can be more helpful than the other. But in this scenario, I needed a universal standard. In addition to the different language date formatting, when passed through a fetch request, like all things over the internet, it becomes a string. Because I prefer to keep objects as objects, especially when going from front to back end, I converted them each way to a standard. This function took care of formatting when displaying to the user:
The start and end times had similar obstacles, which I dealt with similarly. The previous separation of date and times allowed for easier conversions. The counterpart to the previous formatDate function:
Four
The majority of my time went into this goal. Partly due to deciding to add new features as I went on. The last goal previewed viewing date and time. The editing became a mixture of the creation and viewing of a lesson.
However, after making the create, view, and edit functionalities, I decided I wanted one more feature. In most calendar implementations, there's the ability to create recurring events. This goal was the most challenging and enjoyable.
Five
I started with what I knew. It needed a way for the user to select the frequency (Once, Weekly, Monthly):
With my first attempt, I left it a once, one week, or one month. But I quickly decided I wasn't happy with having such limited options. So I added an amount for the frequency:
This option allowed the user to select how many weeks or months.
I used a simple switch statement to handle the user input:
switch (this.state.occurrence) {
case 'Weekly':
[...]
break;
case 'Monthly':
[...]
break;
default:
[...]
}
};
Default would handle the 'Once' option as it's the only option that did not require an additional choice. The fun part came next. Creating the algorithm to add weeks or months to the user input. After trial and error and some mixed results, this was the final weekly case statement:
case 'Weekly':
this.props.createLesson(this.state, this.props.history)
let i = 1;
let dayDate = new Date(this.state.date);
while ( i < this.state.occurrenceNumber){
const newDayDate = this.addDays(dayDate, 7)
dayDate = newDayDate
this.props.createLesson(this.state, this.props.history, dayDate)
i++;
};
this.addDays() was the heavy-lifting function:
addDays(date, days) {
const nextDate = new Date(Number(date))
nextDate.setDate(date.getDate() + days)
return nextDate
};
Monthly was similar with addMonths adding 30 days to the date. Unfortunately, this simple implementation does not take into account over 30 day months or leap years. Something that given more time, I would like to change.
The Default case had a simple one liner:
this.props.createLesson(this.state, this.props.history)
After taking care of how to make the calculations. I needed to connect the changes to the Rails backend:
The create lesson action:
export const createLesson = (lesson, history, date) => {
if (date) {lesson.date = date.toISOString().split("T")[0]}
return dispatch => {
fetch(`
[...]
`, {
method: 'POST',
headers:{
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({ lesson })
})
.then(resp => resp.json())
.then(lesson => {dispatch({ type: 'LESSON_CREATED', payload: lesson})
history.push(`
/lessons
`)
})
}
};
```
I won't go into too much detail as most of it's a standard asynchronous fetch request between JS and Rails. A couple of key points; The
`history.push()' commands are there to redirect the page after creating/editing.
The `
if (date) {lesson.date = date.toISOString().split("T")[0]}` was added after creating this goal. It takes into consideration that if the date is undefined because the frequency is 'Once', the request won't break. However, if there is a date argument, it converts into the universal format I chose before sending it to the Rails side.
### Conclusion
Coming up with elementary solutions to solved solutions isn't always possible with time and resources. But I find it incredibly rewarding, educational, and the best way to understand what I'm asking the code to do. Reinventing the wheel every time doesn't make sense, understanding how the wheel works to manipulate it down the road better, absolutely makes sense to me. For my post next week, I'll take a look at the different calendar library solutions, how to implement them, and how they differ from my simple solution.
### Attribution
Cover Photo by [Roman Bozhko](https://unsplash.com/@romanbozhko?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
*[This post is also available on DEV.](https://dev.to/dclements9/simple-calendar-functions-5ei5)*
<script>
const parent = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.1.1/iframeResizer.min.js';
script.charset = 'utf-8';
script.onload = function() {
window.iFrameResize({}, '.liquidTag');
};
parent.appendChild(script);
</script>