Office 365 makes it quite easy to create add-ins using HTML and JavaScript. Time cockpit also offers an easy-to-use API for these web technologies. In this blog article I walk you through a short sample that demonstrates how to create an Outlook calendar add-in accessing time cockpit's project database.
This sample uses the following technologies:
Do you know that we offer consulting and development services for time cockpit? If you want to integrate time cockpit with other systems like Office, accounting, CRM, etc. but you do not have the necessary technical skills in your team, please contact us. We would love to help.
Video
Source Code
You can find the complete source code in our
Github repository. Here are the two most important pieces of source code for this sample: The manifest describing the add-in and the add-in's TypeScript implementation.
Office Add-in Manifest
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Read more about Office Add-Ins manifests at https://msdn.microsoft.com/en-us/library/office/dn554255.aspx -->
<OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="MailApp">
<Id>47ACA615-DC95-469D-81EB-12F31D80348E</Id>
<Version>0.0.1.0</Version>
<ProviderName>time cockpit</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="Project Picker" />
<Description DefaultValue="Time Cockpit Project Picker Sample" />
<SupportUrl DefaultValue="http://www.timecockpit.com" />
<Hosts>
<Host Name="Mailbox" />
</Hosts>
<Requirements>
<Sets>
<Set Name="MailBox" MinVersion="1.1" />
</Sets>
</Requirements>
<FormSettings>
<Form xsi:type="ItemEdit">
<DesktopSettings>
<SourceLocation DefaultValue="https://projectpicker.azurewebsites.net/index.html" />
</DesktopSettings>
</Form>
</FormSettings>
<Permissions>ReadWriteItem</Permissions>
<Rule xsi:type="RuleCollection" Mode="Or">
<Rule xsi:type="ItemIs" ItemType="Appointment" FormType="Edit" />
</Rule>
<DisableEntityHighlighting>false</DisableEntityHighlighting>
</OfficeApp>
Add-in TypeScript Implementation
/// <reference path="typings/tsd.d.ts" />
'use strict';
/** Project data structures */
interface IProject {
APP_ProjectUuid: string;
APP_Code: string;
}
interface IOdataResult<T> {
value: T[];
}
/** Controller for project list */
class ProjectListController {
private token: string;
constructor(private $http: ng.IHttpService, private $location: ng.ILocationService) {
// Check if there is already a token in local storage
this.token = localStorage.getItem("ProjectPickerToken");
if (!this.token) {
// No token -> redirect to login page
$location.url('/getToken');
} else {
this.refreshProjectListAsync();
}
}
// Variables used for data binding
public projects: IProject[];
public isLoading: boolean = false;
/** Refreshes the project list */
private refreshProjectListAsync() {
// Indicate that loading operation is running.
// Controls loading indicator
this.isLoading = true;
// Get project list using OData
this.$http.get<IOdataResult<IProject>>(
"https://apipreview.timecockpit.com/odata/APP_Project?$select=APP_ProjectUuid,APP_Code&$top=20&$orderby=APP_Code",
{ headers: { "Authorization": "Bearer " + this.token } })
.then(
// Success -> save project list
projects => this.projects = projects.data.value,
// Error -> if unauthorized, redirect to login page
err => { if (err.status === 401) { this.$location.url("/getToken"); } })
// Reset loading indicator
.finally(() => this.isLoading = false)
}
/** Transfers project code to current appointment's subject field */
public pickAppointment(projectCode: string) {
var currentAppointment = <Office.Types.AppointmentCompose>Office.context.mailbox.item;
currentAppointment.subject.setAsync("Working on project '" + projectCode + "'");
}
public clearLocalStorage() {
localStorage.clear();
console.log("Local storage cleared");
}
}
/** Controller for login form */
class GetTokenController {
constructor(private $http: ng.IHttpService, private $location: ng.ILocationService) {
}
// Variables used for data binding
public userName: string;
public password: string;
public loginError: boolean;
/** Gets the token using basic auth */
public getToken() {
if (this.userName && this.password) {
// Convert user:password to base64
var base64UserPassword = window.btoa(this.userName + ':' + this.password);
// Get the bearer token using user + password
this.$http.get<string>(
"https://apipreview.timecockpit.com/token",
{ headers: { "Authorization": "Basic " + base64UserPassword } })
.then(
// Success -> save token in local storage
token => this.saveToken(token.data),
// Error -> activate error message
_ => this.loginError = true);
}
}
/** Saves token in local storage and redirects to project list */
private saveToken(token: string) {
localStorage.setItem("ProjectPickerToken", token);
this.$location.url("/projectList");
}
}
// Setup angular application
angular.module('ProjectPicker', [ 'ngRoute' ])
.controller('projectListController', ProjectListController)
.controller('getTokenController', GetTokenController)
.config(($routeProvider : angular.route.IRouteProvider) => {
$routeProvider
.when('/projectList', {
template: `
<h1>Project List</h1>
<p ng-click="vm.clearLocalStorage()">Clear login cache</p>
<p class="text-info" ng-show="vm.isLoading">
Loading projects from time cockpit ...
</p>
<table class="table table-hover">
<tr ng-repeat="p in vm.projects"
ng-click="vm.pickAppointment(p.APP_Code)">
<td>{{ p.APP_Code }}</td>
</tr>
</table>
`,
controller: 'projectListController',
controllerAs: 'vm'
})
.when('/getToken', {
template: `
<h1>Login</h1>
<form>
<div class="form-group">
<label for="userName">User:</label>
<input type="email" class="form-control" id="userName"
placeholder="Time cockpit user name ..."
ng-model="vm.userName">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" class="form-control" id="password"
placeholder="Time cockpit password ..."
ng-model="vm.password">
</div>
<p class="text-warning" ng-show="vm.loginError">
There was an error logging in. Correct password?
</p>
<button class="btn btn-default" ng-click="vm.getToken()"
ng-disabled="!vm.userName || !vm.password">Login</button>
</form>
`,
controller: 'getTokenController',
controllerAs: 'vm'
})
.otherwise({ redirectTo: '/projectList' });
});
// Add office initializer
Office.initialize = () => {
angular.bootstrap(jQuery('#container'), ['ProjectPicker']);
};
comments powered by