August 23, 2024

Why did the calendar go to therapy?
Because it had too many dates.
Read on for more bad jokes and to learn how you can manage time with Calendars in Inuko apps.

calendar

Calendars

Calendar is the tool we use to plan and review our future lives. The "killer feature" of any calendar app is to be quick.
To make it super fast to schedule future work at a specific time slot. And to be extremely efficient at showing us what's already on the future menu.

These two tasks are of course not isolated. In these busy times, we often have to first find a free spot and then plan a new task.

Finding the right spot might also involve consulting the calendars of our coworkers (or family members). And the other side of this coin is to ensure others can see when and maybe even what we are up to.

Finally whether at work or at home, our business (and life:) managers will need to plan for us.

In a nutshell what we need in a calendar:

  • A flexible view that allows us to see the right level of detail. Whether we plan a two week vacation or trying to squeeze in a 15min call.
  • We need all the information in one place. Ours and our teams business and personal commitments in one place. Using two or more tools to gather an overview or to remember what needs to be done when, is a great recipe for a stressful day.

In the next two sections we will highlight how calendars in inuko apps fulfil these needs.

The last section is dedicated to the technical aspects - how to programatically access external calendars and how to create an open calendar data-source.

Flexible calendar display

Getting a good overview or planning long duration work, a calendar that displays the whole month is ideally suited. However if we need to plan with minute precision we need something else. Thus in our apps we offer separate calendar “views”.

  • the month view display the whole month and we can quickly move to the next or previous months.

MonthCalendar

  • the week and working week views, show the 7 or 5 days of a week.

WeekCalendar

  • the three day view is a compromise when we want to see detailed information, but we also need to know what goes on the day before and after.
  • the day view is the most detailed display, concentrating on the up-to-the-minute display.

Of course all views show the current day and the day views also the current time. We can quickly skip to the next or previous time slot (month, week, day) and return to today.

By default only the event date and name is shown on the calendar. And if the event has a color field, it will be automatically used. Read more in the later technical sections, for how to display more data and how to add visual effects such as strikethrough for canceled events.

Scheduling is super quick, double click a time slot to create a new record, drag&drop to reschedule existing (or to make the event longer/shorter).

Information (event) sources

There is no limit for application business objects. Any list of records can be displayed as a calendar. You only need to choose the date field. We can also add predefined filters or allow the app user to filter data as well. That means we can create a calendar where each user sees her own events only. And/or a calendar for a manager where all users’ events are displayed and the manager can toggle between users.

Finally calendars can be accessed from the main menu, but since they are but a “display mode” for a record list, they are also available for associated records on record forms. This way you can get a quick overview of client meetings straight from the client form. Client calendar

Exchange calendars

That is not all, optionally user’s exchange calendar events can be displayed along the business events. Each user can select his one or more exchange calendars and the resulting events are show side by side with the business events. As a convenience, clicking on the exchange (outlook) events will open them in outlook. Exchange calendar

Access your Inuko app events from 3rd party apps

Finally we can also turn our app into a calendar-data source. Inuko cloud offers a dedicated secured link for user’s business calendars. This way a user can subscribe from their preferred calendar app.

Calendar subscription menu Calendar subscription add

Technical Bonus

In this section we shall look at how to customize the event card. Then how to read exchange calendars and how to create a calendar api on nodejs that can be used for calendar subscription in 3rd party apps.

Event card customization

To customize the calendar event display we need to add a method with the name renderEvent to the business object default handler script.

In the example below, we add a status icon depending on the event status and we also mark canceled events with a line-through.

const renderEvent = ({ obj, field }, React) => {
	if (field === "name") {
		const style = { color: "red" };
		let icon = "clock";
		if (obj.status === "Canceled") {
			icon = "ban";
			style.textDecoration = "line-through";
		} else if (obj.status === "Completed") {
			icon = "check";
			style.color = "green";
		}
		return React.createElement("span", { style: style }, [
			React.createElement("i", { className: "fa fa-" + icon }, ""),
			React.createElement("span", undefined, obj.name)]);
	}
}

Reading exchange calendar events.

First we need to use OAuth to get an authentication token. We explore how to do just that in this blog.
The important part is to request calendar access in the OAuth scope.

{
	scope: "openid profile email User.Read Calendars.Read.Shared",
}

To access the user's events we first have to see what calendar the user has.

export interface IUserCalendar {
	id: string;
	name: string;
}

export const getUserOutlookCalendars = async (tokenRef: ITokenRef) => {
	const access_token = await getAccessToken(tokenRef); // turn the user's offline or refresh token into an access token. 
	const jsonText = await executeGetRequest(access_token, "me/calendars");
	const json = JSON.parse(jsonText);
	return json.value as IUserCalendar[];
}

const executeGetRequest = async (access_token: string, request: string) : Promise<string> => {
	return new Promise((res, rej) => {
			
		var createCORSRequest = function (method: string, url: string) {
			var xhr = new XMLHttpRequest();	
			xhr.open(method, url, true);
			return xhr;
		};
  
		var url = "https://graph.microsoft.com/v1.0/"+request;
		var method = 'GET';
		var xhr = createCORSRequest(method, url);
  
		xhr.onload = function () {
			const text = xhr.responseText;
			res(text);
		};
  
		xhr.onerror = function () {
			const text = xhr.responseText;
			console.log(text);
			rej(new Error(text));
		};
  
		try {
			xhr.setRequestHeader('Authorization', "Bearer " + access_token);
			xhr.send();
		}
		catch (e) {
			rej(e)
		}
	})
};

Now to list all events from a calendar, we need one more call.

export interface IUserCalendarEvent {
	id: string;
	name: string;
	start: string;
	end: string;
	url?: string;
	calendarId?: string;
}

export const getUserOutlookEvents = async (tokenRef: ITokenRef, start: Date, end: Date, calendars: IUserCalendar[]) => {
	const access_token = await getAccessToken(tokenRef);
	
	const events: IUserCalendarEvent[] = [];
	for (const cal of calendars) {
		const calId = cal.id;
		 // Do not forget the top=500 part. This request will return only 20 results by default!
		const request = "me/calendars/" + calId + "/calendarView?startDateTime=" + start.toISOString() + "&endDateTime=" + end.toISOString() + "&$top=500";
		const jsonText = await executeGetRequest(access_token, request);
		const json = JSON.parse(jsonText);
		const outlookEvents = json.value as any[];
		for (const outlookEvent of outlookEvents) {
			let event: IUserCalendarEvent = {
				id: outlookEvent.id,
				start: outlookEvent.start.dateTime + "Z",
				end: outlookEvent.end.dateTime + "Z",
				name: outlookEvent.subject,
				calendarId: calId,
				url: outlookEvent.webLink
			};
			events.push(event);
		}
	}
	return events;
}

Using these methods you can now list and display user's exchange calendar events.

Create a calendar data source

Let's now look at how we can create an cloud API that calenndaring apps can use to display events.

If you are expecting something large and complicated, relax, this is actually quite simple and straightforward. We will generate create an api handler in express/nodejs that responds with a iCalendar object.

app.get("/api/cal/:objectname/", mustAuth, async (req, res, next) => {
	let db;
	const entityName = (req.params as any).objectname;
	// add more api parameters to further filter what events are returned.

	try {
		const user = req.user as IUser;
		const result = await loadDatabaseRecords(db, user.id) as { 
			name: string, 
			scheduledstart: string, 
			id: string, 
			ownerid:ILookupValue, 
			modifiedon: string }[];

		const calDate = (ds: string, addSeconds = 0) => {
			let d = new Date(ds);
			if (addSeconds)
				d = new Date((d.valueOf() + addSeconds * 1000));
			const s = d.toISOString();
			let s2 = "";
			for (let c of s) {
				if (c === ":" || c === "-")
					continue;
				if (c === ".") { // millies
					s2 += "Z";
					break;
				}
				s2 += c;
			}
			return s2;
		}

		let email = "nobody@email.com";
		const ownerRecords = await db.executeQuery("SELECT emailaddress FROM systemuser WHERE id=@P0 FOR JSON PATH", true, [{ name: "P1", value: user.id, type: TYPES.UniqueIdentifier }]) as any[];
		if (ownerRecords && ownerRecords[0])
			email = ownerRecords[0].emailaddress as string;

		let sb = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//hacksw/handcal//NONSGML v1.0//EN\n";
		for (let r of result) {
			if (!r.scheduledstart)
				continue;

			sb += "BEGIN:VEVENT\n";
			sb += `UID:${r.id}@example.com\n`;
			sb += `DTSTAMP:${r.modifiedon}\n`;
			sb += `ORGANIZER;CN=${user.username}:MAILTO:${email}\n`;
			sb += `DTSTART:${calDate(r.scheduledstart)}\n`;
			sb += `DTEND:${calDate(r.scheduledstart, 3600)}\n`;
			sb += `SUMMARY:${r.name}\n`;
			//sb += "GEO:48.85299;2.36885
			sb += `BEGIN:VALARM\n`;
			sb += `TRIGGER:-PT15M\n`;
			sb += `ACTION:DISPLAY\n`;
			sb += `DESCRIPTION:Reminder\n`;
			sb += `END:VALARM\n`;
			sb += "END:VEVENT\n";
		}
		sb += "END:VCALENDAR";
		
		res.type("text/calendar");
		res.send(sb);
	}
	catch (err) {
		//res.status(500).json({ err: err });
		res.status(500).send("Update Error: " + (err as Error).message);
		next();
	}
	finally {
		db?.release();
	}
});

Most calendar apps will handle authenticated iCalendars just fine. So let's add basic authentication for the /api/cal route.


const mustAuth = (req: Request, res: Response, next: any) => {
	if (req.user) {
		return next();
	} else if (req.headers.authorization) {

		const tryLoginBasic = async () => {
			const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
			const strauth = Buffer.from(b64auth, 'base64').toString()
			const splitIndex = strauth.indexOf(':')
			const login = strauth.substring(0, splitIndex);
			const password = strauth.substring(splitIndex + 1);
			const [orgName, username] = login.split("\\");

			const user = await loadAndVerifyUser(orgName, username, password);
			if (user) {
				req.login(user, (err) => {
					if (err) {
						return next(err);
					}
					next();
				});
			}
			else {
				res.status(401).send("NOT AUTHORIZED");
			}
		}

		tryLoginBasic();
		return;
	}
	// calendar request - allow basic authentication.
	if (req.originalUrl && req.originalUrl.indexOf("/api/cal") >= 0) {
		res.set('WWW-Authenticate', 'Basic realm="home"') // change this
	}
	res.status(401).send("NOT AUTHORIZED");
};

Now we can add a new Calendar subscription in our favorite Calendar app.

Calendar subscription details

Pretty eventful

For me the calendar reminder chimes are both distracting and not distracting enough. There are times when it feels live is run by my calendar and free will is truly just an illusion.

We love to hate our calendars, but we keep coming back.

There is no way around it, we don't have time, we make time. And time-making is the job of our calendars.

Happy planning!

Continue reading

Salesforce.com REST API for data manipulation

August 15, 2024
Salesforce.com REST API for data manipulation

Need to manipulate Salesforce.com data quickly?
Would you rather use the tools and language you know?
We too, let's get it done.

Generate pdfs in browser

July 19, 2024
Generate pdfs in browser

When you build an app for clients, there will be point where they’ll ask for reporting.
What they usual mean by that, is to have a way to take a bunch of records from the database do some calculations and put it into a pdf.
So how do we build that?

Structured Query Language for the Web - Schema

July 11, 2024
Structured Query Language for the Web - Schema

In the previous posts we have introduced a javascript database class and a json based sql variant - json-fetch. We can now implement a dynamic CRUD layer, but there is an important piece missing.

©2022-2024 Inuko s.r.o
Privacy PolicyCloud
ENSKCZ
ICO 54507006
VAT SK2121695400
Vajanskeho 5
Senec 90301
Slovakia EU

contact@inuko.net