I am creating a calendar in Angular that has a previous & next month button that currently displays the correct data of that month when I press either the next or previous button only once. The question that I have is how can I increment the months when clicking on either the previous or next button. I have looked at Temporal's Documentation a good bit but I still am confused on how to simply increment the months. Here is some of the code I have so far. Any help would be greatly appreciated!
currentMonth = Temporal.Now.plainDateISO();
nextMonth = this.currentMonth.add({ months: 1 });
previousMonth = this.currentMonth.subtract({ months: 1 });
daysInCurrentMonth = this.currentMonth.daysInMonth;
daysInNextMonth = this.nextMonth.daysInMonth;
daysInPreviousMonth = this.previousMonth.daysInMonth;
onStartMonth() {
for (let i = 1; i <= this.daysInCurrentMonth; i++) {
let div = this.calendarData.nativeElement;
let calendarDates = this.renderer.createElement('div');
this.add(calendarDates, 'dates');
this.set(calendarDates, 'innerText', i);
this.append(div, calendarDates);
}
}
onNextMonth() {
for (let i = 1; i <= this.daysInNextMonth; i++) {
let div = this.calendarData.nativeElement;
let calendarDates = this.renderer.createElement('div');
this.add(calendarDates, 'dates');
this.set(calendarDates, 'innerText', i);
this.append(div, calendarDates);
}
}
onPreviousMonth() {
for (let i = 1; i <= this.daysInPreviousMonth; i++) {
let div = this.calendarData.nativeElement;
let calendarDates = this.renderer.createElement('div');
this.add(calendarDates, 'dates');
this.set(calendarDates, 'innerText', i);
this.append(div, calendarDates);
}
}
Your comment: "Since the current month is August; when I press the next month button, I always get September, likewise when I press the previous month I always get July"
From that comment, your issue is not with the Temporal API. The problem is that you've essentially hard-coded the current, next and previous months when you should probably be updating these dynamically (as well as the respective days in month. When you press the next button you should update current month to be the next month and then recalcuate daysInCurrentMonth.
currentMonth = Temporal.Now.plainDateISO();
// Reset to this month
onStartMonth() {
this.currentMonth = Temporal.Now.plainDateISO();
renderCalendar();
}
// move to next month
onNextMonth() {
this.currentMonth = this.currentMonth.add({ months: 1 });
renderCalendar();
}
// move previous month
onPreviousMonth() {
this.currentMonth = this.currentMonth.subtract({ months: 1 });
renderCalendar();
}
// Render the calendar
renderCalendar(){
const daysInCurrentMonth = this.currentMonth.daysInMonth;
const div = this.calendarData.nativeElement;
for (let i = 1; i <= daysInCurrentMonth; i++) {
let calendarDates = this.renderer.createElement('div');
this.add(calendarDates, 'dates');
this.set(calendarDates, 'innerText', i);
this.append(div, calendarDates);
}
}
You now only care about the current tracked month and can easily navigate forward or backward any number of times. Your rendering got much simpler too as any changes you need to make can be made in that 1 function without having to be replicated multiple times.
If you still need next and previous month info (incl days in months) then you should probably recalculate these as well when current month changes.
Related
I am creating a calendar with events using reactjs, Now when the calendar shows the current month, when I click next its shows the April month if I click again next I get the following error.
TypeError: Cannot read property 'eventSlots' of undefined.
The error appears in a function where I try to get days with the event here its.
getDaysWithEvents() {
// Get all the days in this months calendar view
// Sibling Months included
const days = this.getCalendarDays();
// Set Range Limits on calendar
this.calendar.setStartDate(days[0]);
this.calendar.setEndDate(days[days.length - 1]);
// Iterate over each of the supplied events
this.props.events.forEach((eventItem) => {
const eventStart = this.getCalendarDayObject(eventItem.start);
const eventEnd = this.getCalendarDayObject(eventItem.end);
const eventMeta = this.getEventMeta(days, eventStart, eventEnd);
if (eventMeta.isVisibleInView) {
const eventLength = eventMeta.visibleEventLength;
console.log("Days", days);
const eventSlotIndex = days[eventMeta.firstVisibleDayIndex].eventSlots.indexOf(false); // this line returns error
//console.log("eventSotsindex", eventSlotIndex);
let dayIndex = 0;
// For each day in the event
while (dayIndex < eventLength) {
// Clone the event object so we acn add day specfic data
const eventData = Object.assign({}, eventItem);
if (dayIndex === 0) {
// Flag first day of event
eventData.isFirstDay = true;
}
if (dayIndex === eventLength - 1) {
// Flag last day of event
eventData.isLastDay = true;
}
if (!eventData.isFirstDay || !eventData.isLastDay) {
// Flag between day of event
eventData.isBetweenDay = true;
}
// Apply Event Data to the correct slot for that day
//console.log(eventMeta, dayIndex, days);
if (days[eventMeta.firstVisibleDayIndex + dayIndex]) {
if (days[eventMeta.firstVisibleDayIndex + dayIndex].eventSlots) {
days[eventMeta.firstVisibleDayIndex + dayIndex].eventSlots[eventSlotIndex] = eventData;
}
}
// Move to next day of event
dayIndex++;
}
}
});
return days;
}
The line of code causing the problem : const eventSlotIndex = days[eventMeta.firstVisibleDayIndex].eventSlots.indexOf(false);
NOTE: If someone wishes here is a repository with full code calenar
demo, clone, npm install and npm start
What do I need to do to solve the problem? any help or contributions will be appreciated.
The eventMeta.firstVisibleDayIndex goes out of bounds (i.e., eventMeta.firstVisibleDayIndex is greater than or equal to days.length).
A workaround would be to reset the first day index to 0 as follows:
const firstDayIndex = eventMeta.firstVisibleDayIndex < days.length ? eventMeta.firstVisibleDayIndex : 0;
const eventSlotIndex = days[firstDayIndex].eventSlots.indexOf(false);
i am trying to make a calendar and simply select a day (which is a ) to change its css class, having a reactjs class based component with daySelected as a state prop.
inside createCalendar() i get the day number using an indicator (date) and i increment it inside the for loop, having onClick() to pass the date to handleClick(date).
When i select any day i always get the last day as selected.
I want to have pass the variable date to handelClick(date) from the selected day, please suggest any solution.
ps : i need to make everthing inside a single component to control the state prop daySelected
handleClick = day => {
this.setState(state => ({
checked: !state.checked,
daySelected: day
}));
};
createCalendar(currentMonth, currentYear) {
let firstDay = new Date(currentYear, currentMonth).getDay();
let daysInMonth =
32 - new Date(currentYear, currentMonth, 32).getDate() - 1;
var days = [];
let date = 0;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 7; j++) {
if (i === 0 && j < firstDay) {
var x = (
<div className={"Day"}>
<p> </p>
</div>
);
} else if (date > daysInMonth) {
break;
} else {
date++;
var x = (
// I want to get the variable date from here
<div
value={date}
onClick={() => this.handleClick(date)}
className={this.state.daySelected == date ? "Day-checked" : "Day"}
>
<p>{date}</p>
</div>
);
}
days.push(x);
}
}
return days;
}
You're overwritting a x value in a loop (using one reference), you should use a function.
Just convert subcomponent definition to a function, sth like const x = (date) => {... and use it in a loop.
I have a calendar type page in my app which has to calculate the appointments due on that day. This operation only takes about 35ms per day but when you're looking at a month, it adds up. Currently, as I am looping through my dates, nothing is rendered until ALL the days have been loaded.
I thought I'd try and get a simpler example to test with so:
<span v-for="n in 100000000">{{ n }} </span>
Even doing something as simple as this, the span won't render until it's been through all 100 million iterations.
How can I get this to render 1 then 2, then 3 etc rather than rendering when it's completed the loop?
Edit: This is my real world example:
<planner-day
v-for="date in getDates(index, index + selectedFilters.daysPerRow)"
v-if="plannerStartDate"
:date="date"
:timeDisplayType="selectedFilters.timeDisplayType"
:timeInterval="selectedFilters.timeInterval"
:jobs="items"
:filteredDateRange="selectedFilters.dateRange"
:jobDueDates="jobDueDates"
:roundJobCounts="roundJobCounts"
></planner-day>
index above is from an outer loop. This is getDates():
getDates (rowIndex) {
let rangeStart = parseInt(rowIndex) * parseInt(this.selectedFilters.daysPerRow)
let rangeEnd = rangeStart + parseInt(this.selectedFilters.daysPerRow)
let dates = []
let currentRow = 0
var currentDate = this.selectedFilters.dateRange.start.clone().subtract(1, 'days')
while (currentDate.add(1, 'days').diff(this.selectedFilters.dateRange.end) <= 0 && currentRow < rangeEnd) {
if (currentRow >= rangeStart) {
dates.push(currentDate.clone().startOf('day'))
}
currentRow++
}
return dates
}
You can try this using a directive like this:
<outer-loop v-for="(item, index) in myArray" v-getDates="index">
<planner-day v-for="date in myDates"></<planner-day>
</outer-loop>
In the component script
data(){
return{
myDates:[]
};
},
directive:{
getDates: {
bind(el, binding, Vnode){
var vm = Vnode.context;
var index = binding.value;
let rangeStart = parseInt(index) * parseInt(vm.selectedFilters.daysPerRow)
let rangeEnd = rangeStart + parseInt(vm.selectedFilters.daysPerRow)
let currentRow = 0
var currentDate = vm.selectedFilters.dateRange.start.clone().subtract(1, 'days')
var myLoop = function(i){
if (i >= rangeStart) {
vm.myDates.push(currentDate.clone().startOf('day'));
}
if(currentDate.add(1, 'days').diff(vm.selectedFilters.dateRange.end) <= 0 && i < rangeEnd){
i++;
myLoop(i);
}
}
myLoop(currentRow);
}
}
}
The loop will be adding items to myDates[ ] array. Since myDates[ ] is initialized in data option it is reactive and you can loop through it.
Here is the example fiddle.
I'm trying to sort a JS array, but in a different fashion. Here's my scenario:
Say we have the following dates in the array (these will be each item's expiryDate)
1. 2015/09/23
2. 2015/10/10
3. 2015/07/05
4. 2015/07/24
And the current date is 2015/07/04
And we select 2015/09 (which is vm.sortModel in the example) as the sorting date
The display order must be as follows -
3.
1.
4.
2.
Here's what I have so far, but it's obviously not working...
for (var i = 0; i < vm.tableData.length; i++) {
var entity = vm.tableData[i];
entity.items.sort(function(a, b) {
if (a.expiresThisWeek() < b.expiresThisWeek()) {
return 1;
}
//Now check for stuff in months AFTER the selected year/month combo
if (vm.sortModel) {
var tmp = vm.sortModel.split('/');
var currentDateSelection = new Date(tmp[0], tmp[1]);
if (a.getExpiryDateObject() >= currentDateSelection) {
return 1;
} else {
return -1;
}
}
if (a.getExpiryDateObject() < b.getExpiryDateObject()) {
return -1;
} else if (a.getExpiryDateObject() > b.getExpiryDateObject()) {
return 1;
}
return 0;
});
}
Basically, if an item expires in this week, it must be at the top of the table, then we should list all the items in the "selected month", then all the months AFTER that month, and then only the months before that.
Very strange, I know, but business gets what business wants.
Thanks for any help!
EDIT
Here's the expiresThisWeek method
model.expiresThisWeek = function() {
var todayDate = new Date();
//We want to check if the item expires within 7 days from today
var endDate = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate() + 7);
var date = model.getExpiryDateObject();
return todayDate < date && date < endDate;
}
So, basically I'm building a rental form where you have three basic dropdowns for Month, Day, Year for a "rent from" and then another three for "rent till."
That's easy enough and I've even got decent leap year support and such. The issue comes with generating the "rent till" based on the "rent from." I'd like the "rent till" to show only the next five days in a dropdown afterwords and I'm having serious difficulty working out logic to do it efficiently.
Also, the range is variable, so I'm trying to work a function in that takes a 'length of rental' parameter to effect the changes. The code also uses reference points. So that the pick up dropdowns are "pick_up_day", "pick_up_month" etc. This is to try to make the code portable to multiple projects with consistently ID'd HTML elements.
Here's my basic logic and what I've got so far:
This is the code that sets up the from-dates and such.
function populate_dates(identifier) {
"use strict";
var i, currentday, node, textbox, maxdays;
while (document.getElementById(identifier + "days").hasChildNodes()) {
document.getElementById(identifier + "days").removeChild(document.getElementById(identifier + "days").firstChild);
}
currentday = new Date();
switch (parseInt(document.getElementById(identifier + "month").value, 10)) {
case 2:
if (new Date(currentday.getFullYear(), 1, 29).getDate() === 29) {
maxdays = 29;
} else {
maxdays = 28;
}
break;
case 9:
case 4:
case 6:
case 11:
maxdays = 30;
break;
default:
maxdays = 31;
break;
}
for (i = 1; i <= maxdays; i = i + 1) {
node = document.createElement("option");
textbox = document.createTextNode(i);
node.value = i;
node.appendChild(textbox);
document.getElementById(identifier + "days").appendChild(node);
}
}
window.onload = function() {
"use strict";
var currentday, i, node, textbox;
currentday = new Date();
for (i = 0; i <= 1; i = i + 1) {
node = document.createElement("option");
textbox = document.createTextNode(currentday.getFullYear() + i);
node.value = currentday.getFullYear() + i;
node.appendChild(textbox);
document.getElementById("pick_up_year").appendChild(node);
}
document.getElementById("pick_up_month").onchange = function () {
populate_dates("pick_up_");
};
populate_dates("pick_up_");
};
Now for the logic regarding the 'till' date I have worked out some pseudocode like this, but it seems overly complex for such a simple task:
function a(referencer, ref1, ref2, rentlength) {
//Make datetime object out of ref1's current date;
datetime.setDays(datetime.getDate() + rentlength)
for (i = 0; i <= rentlength; i = i + 1) {
datetime.setDays(datetime.getDate() + i);
outmatrix[outmatrix.length] = [getDates, getMonth, getYear];
}
outmatrix.removeduplicates;
date_generator(ref2, outmatrix)
}
function date_generator(referencer, inputmatrix) {
Clean up referencer
Loop through, create nodes based on array[y+1][x];
}
Is there a better way to do this that I'm just not seeing? What is a good way to turn a Datetime object into three dropdowns while removing duplicates? Is there a better way to handle this than the way I am currently?
In terms of the logic, you'll need to set the date to the sum of the start date and the rental time, in DATE format.