Bryntum Scheduler是一种由纯JavaScript构建的现代,高性能的UI组件。它可以轻松地与反应,vue或Angular一起使用。在本教程中,我们将与Microsoft团队连接并同步Bryntum调度程序。我们将进行以下操作:
- 创建一个JavaScript应用程序,用户可以使用其Microsoft 365开发人员程序帐户登录。
- 使用Microsoft Graph使用户的Microsoft团队移动。
- 显示Microsoft团队在Bryntum Scheduler中转移。
- 同步事件与用户的Microsoft团队中的Bryntum调度程序更改。
入门
克隆starter GitHub repository。入门存储库使用Vite,它是开发服务器和JavaScript Bundler。您需要Node.js版本14.18+才能工作。
现在通过运行以下命令来安装Vite Dev依赖关系:
npm install
使用npm run dev
运行本地开发服务器,您会看到一个空白页。开发服务器配置为在vite.config.js
文件中的http://localhost:8080/
上运行。 Microsoft 365身份验证将需要。
现在创建我们的bryntum调度程序。
使用Bryntum创建调度程序
我们将使用NPM安装Bryntum调度程序组件。请按照Bryntum调度程序setup guide的步骤登录Bryntum注册表组件。
然后使用NPM CLI npm init
命令初始化您的应用程序:
npm init
您将在终端中提出一系列问题;通过单击每个问题来接受所有默认值。
现在,请按照Bryntum调度程序setup guide的步骤安装Bryntum Scheduler。
让S导入Bryntum调度程序组件,并给它一些基本配置。在main.js
文件中添加以下行:
import { Scheduler } from "@bryntum/scheduler/scheduler.module.js";
import "@bryntum/scheduler/scheduler.stockholm.css";
// get the current date
var today = new Date();
// get the day of the week
var day = today.getDay();
// get the date of the previous Sunday
var previousSunday = new Date(today);
previousSunday.setDate(today.getDate() - day);
// get the date of the following Saturday
var nextSaturday = new Date(today);
nextSaturday.setDate(today.getDate() + (6 - day));
const scheduler = new Scheduler({
appendTo : "scheduler",
startDate : previousSunday,
endDate : nextSaturday,
viewPreset : 'dayAndWeek',
resources : [
{ id : 1, name : 'Dan Stevenson' },
{ id : 2, name : 'Talisha Babin' }
],
events : [
{ resourceId : 1, startDate : previousSunday, endDate : nextSaturday },
{ resourceId : 2, startDate : previousSunday, endDate : nextSaturday }
],
columns : [
{ text : 'Name', field : 'name', width : 160 }
]
});
我们将Bryntum Scheduler和CSS导入了斯德哥尔摩主题,这是五个可用主题之一。您还可以创建自定义主题。您可以阅读有关调度程序here的造型的更多信息。我们创建了一个新的bryntum调度程序实例,并将配置对象传递给了它。我们在id
的id
中将调度程序添加到DOM中。
可以设置调度程序以显示打开的特定日期;在这里,我们将startDate
设置为上一个星期日,将endDate
设置为下一个星期六,以反映Microsoft Teams UI中的日期。
我们通过数据内联,以填充调度程序资源和事件存储,以简单。您可以了解有关在Bryntum docs中使用数据的更多信息。我们为两个人提供了一个资源。在调度程序中,每个人都有一个示例"shift"
事件,可以运行一周。如果您现在运行开发服务器,您将在Bryntum调度程序中看到这些事件:
现在,让我们学习如何使用Microsoft Graph从用户的Microsoft团队中检索团队的列表。
获得对Microsoft图的访问
我们将通过在Azure Active Directory(Azure AD)中创建应用程序注册来注册Microsoft 365应用程序,该应用程序是身份验证服务。我们将执行此操作,以便用户可以使用其Microsoft 365帐户登录我们的应用程序。这将允许我们的应用访问用户授予应用程序访问权限的数据。用户将使用OAuth登录,该oauth将将访问令牌发送到我们的应用程序中,该应用将存储在会话存储中。然后,我们将使用令牌使用Microsoft Graph对Microsoft Teams数据提出授权请求。 Microsoft Graph是单个端点REST API,它使您能够从Microsoft 365应用程序访问数据。
要使用Microsoft Graph,您需要一个Microsoft account,您需要与该Microsoft帐户一起加入Microsoft 365 Developer Program。加入Microsoft 365开发人员计划时,您会询问您感兴趣的Microsoft 365开发领域;选择Microsoft Graph选项。选择最近的数据中心区域,创建您的管理用户名和密码,然后单击“继续”。接下来,选择“即时沙盒”,然后单击“下一步”。
既然您已经成功加入了开发人员程序,则可以在仪表板窗口中获取管理电子邮件地址。我们将使用它来创建一个使用Microsoft Azure的应用程序。
创建一个Azure广告应用程序连接到Microsoft 365
让我们通过在Azure Active Directory admin portal中创建应用程序注册来注册Microsoft 365应用程序。使用Microsoft 365开发人员计划帐户中的管理电子邮件地址登录。现在按照以下步骤创建Azure Active Directory应用程序:
-
给您的应用程序一个名称,选择“单租户”选项,为重定向URI选择“单页应用程序”,然后输入重定向URL的http://localhost:8080。然后单击“注册”按钮。
现在,我们可以创建一个可以使用Microsoft Graph API获取用户数据的JavaScript Web应用程序。下一步是在我们的Web应用程序中设置身份验证。
在JavaScript应用中设置Microsoft 365身份验证
要使用Microsoft Graph Rest API获取数据,我们的应用需要证明我们是我们在Azure AD中创建的应用程序的所有者。您的应用程序将从Azure AD中获取访问令牌,并将其包含在Microsoft Graph中。设置此操作后,用户将能够使用其Microsoft 365帐户登录您的应用程序。这意味着您必须在应用程序中实现身份验证或维护用户的凭据。
首先,我们将创建对身份验证和检索Microsoft团队转变所需的变量和功能。然后,我们将添加Microsoft身份验证库和Microsoft Graph SDK,我们需要进行身份验证并使用Microsoft Graph。
。在项目的根目录中创建一个名为auth.js
的文件,并添加以下代码:
const msalConfig = {
auth: {
clientId: "<your-client-ID-here>",
// comment out if you use a multi-tenant AAD app
authority: "https://login.microsoftonline.com/<your-directory-ID-here>",
redirectUri: "http://localhost:8080",
},
};
在msalConfig
变量中,用您的Azure AD应用程序随附的客户端ID替换clientID
的值,然后用您的目录ID替换authority
值。
以下代码将检查权限,创建Microsoft身份验证库客户端,记录用户并获取身份验证令牌。将其添加到文件的底部。
const msalRequest = { scopes: [] };
function ensureScope(scope) {
if (
!msalRequest.scopes.some((s) => s.toLowerCase() === scope.toLowerCase())
) {
msalRequest.scopes.push(scope);
}
}
// Initialize MSAL client
const msalClient = new msal.PublicClientApplication(msalConfig);
// Log the user in
async function signIn() {
const authResult = await msalClient.loginPopup(msalRequest);
sessionStorage.setItem("msalAccount", authResult.account.username);
}
async function getToken() {
let account = sessionStorage.getItem("msalAccount");
if (!account) {
throw new Error(
"User info cleared from session. Please sign out and sign in again."
);
}
try {
// First, attempt to get the token silently
const silentRequest = {
scopes: msalRequest.scopes,
account: msalClient.getAccountByUsername(account),
};
const silentResult = await msalClient.acquireTokenSilent(silentRequest);
return silentResult.accessToken;
} catch (silentError) {
// If silent requests fails with InteractionRequiredAuthError,
// attempt to get the token interactively
if (silentError instanceof msal.InteractionRequiredAuthError) {
const interactiveResult = await msalClient.acquireTokenPopup(msalRequest);
return interactiveResult.accessToken;
} else {
throw silentError;
}
}
}
msalRequest
变量存储当前的Microsoft身份验证库请求。它最初包含一个空范围。授予您应用程序的权限列表是访问令牌的一部分。这些是OAuth标准的范围。当您的应用程序从Azure Active Directory请求访问令牌时,它需要包含范围列表。 Microsoft Graph中的每个操作都有其自己的范围列表。每个操作所需的权限列表可在Microsoft Graph permissions reference中获得。
使用Microsoft Graph访问用户团队的变化
在项目的根目录中创建一个名为graph.js
的文件,并添加以下代码:
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Create an authentication provider
const authProvider = {
getAccessToken: async () => {
// Call getToken in auth.js
return await getToken();
}
};
// Initialize the Graph client
const graphClient = MicrosoftGraph.Client.initWithMiddleware({ authProvider });
async function getMembers() {
ensureScope("TeamMember.Read.All");
return await graphClient
.api('/teams/<your-team-ID-here>/members')
.get();
}
async function getAllShifts() {
ensureScope("Schedule.Read.All");
return await graphClient
.api('/teams/<your-team-ID-here>/schedule/shifts')
.header("Prefer", `outlook.timezone="${userTimeZone}"`)
.get();
}
我们使用auth.js
文件中的getToken
方法获得访问令牌。然后,我们使用Microsoft Graph SDK(稍后将添加)来创建一个Microsoft Graph客户端,该客户端将处理Microsoft Graph API请求。
getMembers
功能从团队ID指定的团队中检索团队成员。通过导航到Microsoft团队并从团队中复制链接来找到您的团队ID。
可以在groupid=
和第一个&
之后的链接中找到团队ID。
getAllShifts
功能接收由同一ID指定的团队中的所有班次。确保用您的团队ID替换上述代码中的<your-team-ID-here>
。
我们使用ensureScope
函数来指定访问团队移动数据所需的权限。然后,我们使用graphClient
API方法调用"/teams/"
端点,以从Microsoft Graph获取数据。 header
方法允许我们设置首选时区。团队转移日期是使用UTC存储的。我们需要为返回的团队设置时区,而返回的团队的开始日期和结束日期。这样做是为了使正确的事件时间显示在我们的bryntum调度程序中。 userTimeZone
的值在上面代码的第一行中定义。
现在,让我们添加用户的团队转移到我们的Bryntum调度程序。
添加Microsoft团队转移到Bryntum调度程序
让我们添加Microsoft 365登录链接,并导入Microsoft身份验证库和Microsoft Graph SDK。在index.html
文件中,用以下元素替换<body>
HTML元素的子元素:
<main id="main-container" role="main" class="container">
<div id="content" style="display: none">
<div id="scheduler"></div>
</div>
<a id="signin" href="#">
<img
src="./images/ms-symbollockup_signin_light.png"
alt="Sign in with Microsoft"
/>
</a>
</main>
<script src="https://alcdn.msauth.net/browser/2.1.0/js/msal-browser.min.js"
integrity="sha384-EmYPwkfj+VVmL1brMS1h6jUztl4QMS8Qq8xlZNgIT/luzg7MAzDVrRa2JxbNmk/e"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@microsoft/microsoft-graph-client/lib/graph-js-sdk.js"></script>
<script src="auth.js"></script>
<script src="graph.js"></script>
<script type = "module" src = "main.js"></script>
最初,我们的应用将仅显示登录链接。当用户登录时,将显示Bryntum调度程序。
在main.js
文件中,添加以下行以将“使用Microsoft”链接元素对象存储在变量:
中
const signInButton = document.getElementById("signin");
现在在文件底部添加以下功能:
async function displayUI() {
await signIn();
// Hide login button and initial UI
var signInButton = document.getElementById("signin");
signInButton.style = "display: none";
var content = document.getElementById("content");
content.style = "display: block";
var events = await getAllShifts();
var members = await getMembers();
members.value.forEach((member) => {
var user = {id: member.userId, name: member.displayName, hasEvent : "Unassigned"};
// append user to resources list
scheduler.resourceStore.add(user);
});
events.value.forEach((event) => {
var shift = {resourceId: event.userId, name: event.sharedShift.displayName, startDate: event.sharedShift.startDateTime, endDate: event.sharedShift.endDateTime, eventColor: event.sharedShift.theme, shiftId: event.id, iconCls: ""};
scheduler.resourceStore.forEach((resource) => {
if (resource.id == event.userId) {
resource.hasEvent = "Assigned";
resource.shiftId = event.id;
}
});
// append shift to events list
scheduler.eventStore.add(shift);
});
}
signInButton.addEventListener("click", displayUI);
export { displayUI };
displayUI
函数调用signIn
函数在auth.js
中签名。用户登录后,隐藏了登录链接并显示bryntum调度程序。我们在graph.js
文件中使用getAllShifts
函数来使团队换档。然后,我们使用检索到的团队轮班为Bryntum调度程序创建事件,并将其添加到scheduler.eventStore
商店中。
每个团队轮班都有一个唯一的ID,在创建Bryntum调度程序中的轮班时,我们添加了此ID,以便稍后确定相应的偏移。
同样,我们使用getMembers
功能来吸引团队成员并与团队成员一起填充scheduler.resourceStore
。
我们不再需要在调度程序的初始设置中创建的内联数据,因此请从我们在main.js
中定义的scheduler
中删除这些代码行。
resources : [
{ id : 1, name : 'Dan Stevenson' },
{ id : 2, name : 'Talisha Babin' }
],
events : [
{ resourceId : 1, startDate : previousSunday, endDate : nextSaturday },
{ resourceId : 2, startDate : previousSunday, endDate : nextSaturday }
],
现在使用Microsoft 365开发人员计划帐户中的管理电子邮件地址登录Microsoft Teams,并在下一周创建一些事件,然后单击“与团队共享”按钮分享您的轮班:
使用npm run dev
运行开发服务器,您将看到登录链接:
登录您用来登录Microsoft团队的同一管理电子邮件地址:
现在,您将看到您的团队在Bryntum调度程序中移动:
接下来,我们将通过在Bryntum调度程序中实现CRUD功能将团队与调度程序同步。对Bryntum调度程序的更新将更新Microsoft团队的班次。
实施CRUD
现在,我们已经将调度程序连接到图形API,我们将利用Microsoft Graph的post
,patch
和delete
方法来实现其余的CRUD功能,并通过相关的查询字符串。
这些功能中的每个功能都需要您的团队ID,因此请确保用您之前检索到的团队ID替换<your-team-ID-here>
。
创建事件
在graph.js
文件中,添加以下行:
async function createShift(name, start, end, userId, color) {
ensureScope("Schedule.ReadWrite.All");
return await graphClient
.api('/teams/<your-team-ID-here>/schedule/shifts')
.post({
"userId": userId,
"sharedShift": {
"displayName": name,
"startDateTime": start,
"endDateTime": end,
"theme": color
}
});
}
在这里,我们创建一个功能,该功能将创建一个由用户ID,名称,开始日期和从Bryntum Scheduler收集的结束日期的团队移动。该函数通过适当的范围,并定义了新的换档数据。
更新事件
在graph.js
文件中,添加以下函数:
async function updateShift(id, userId, name, start, end, color) {
ensureScope("Schedule.ReadWrite.All");
return await graphClient
.api(`/teams/<your-team-ID-here>/schedule/shifts/${id}`)
.put({
"userId": userId,
"sharedShift": {
"displayName": name,
"startDateTime": start,
"endDateTime": end,
"theme": color
}
});
}
updateShift
功能将确定适当的团队通过id
换档,然后它将使用新的用户ID,名称,开始日期和结束日期以及Bryntum Scheduler的结束日期来更新事件。该函数通过适当的范围,并定义了新的偏移数据。
删除事件
在graph.js
文件中,添加以下函数:
async function deleteShift(id) {
ensureScope("Schedule.ReadWrite.All");
return await graphClient
.api(`/teams/<your-team-ID-here>/schedule/shifts/${id}`)
.delete();
}
deleteShift
功能将确定适当的团队通过id
换移,并删除事件。
聆听Bryntum调度程序中的事件数据更改
接下来,我们将为Bryntum调度程序设置侦听器,以便知道用户何时更新调度程序事件。
用以下代码替换scheduler
的定义:
const scheduler = new Scheduler({
appendTo : "scheduler",
startDate : previousSunday,
endDate : nextSaturday,
viewPreset : 'dayAndWeek',
listeners : {
dataChange: function (event) {
updateMicrosoft(event);
}},
columns : [
{ text : 'Name', field : 'name', width : 160 }
]
});
在这里,我们在Bryntum调度程序上设置了一个侦听器,以聆听调度程序数据存储的任何更改。每当创建或更新调度程序事件时,这都会触发一个名为"update"
的事件,并且每当删除事件时称为"remove"
的事件。
从dataChange
侦听器检索的事件还将随附有关已更改的特定调度程序事件的事件数据。我们将使用事件数据来确定正在更改哪个事件以及正在更改的事件。
接下来,我们将创建一个称为updateMicrosoft
的函数,该功能将在触发适当的"update"
或"delete"
事件时更新团队。
在main.js
文件中添加以下代码:
scheduler
的定义
async function updateMicrosoft(event) {
if (event.action == "update") {
if ("name" in event.changes || "startDate" in event.changes || "endDate" in event.changes || "resourceId" in event.changes || "eventColor" in event.changes) {
if ("resourceId" in event.changes){
if (!("oldValue" in event.changes.resourceId)){
return;
}
}
if (Object.keys(event.record.data).indexOf("shiftId") == -1 && Object.keys(event.changes).indexOf("name") !== -1){
var newShift = createShift(event.record.name, event.record.startDate, event.record.endDate, event.record.resourceId, event.record.eventColor);
newShift.then(value => {
event.record.data["shiftId"] = value.id;
});
scheduler.resourceStore.forEach((resource) => {
if (resource.id == event.record.resourceId) {
resource.hasEvent = "Assigned";
}
});
} else {
if (Object.keys(event.changes).indexOf("resource") !== -1){
return;
}
updateShift(event.record.shiftId, event.record.resourceId, event.record.name, event.record.startDate, event.record.endDate, event.record.eventColor);
}
}
} else if (event.action == "remove" && "name" in event.records[0].data){
deleteShift(event.records[0].data.shiftId);
}
}
在这里,我们创建了一个函数,该函数可在Bryntum调度程序数据存储的所有更改中调用。然后,该功能调用了我们定义的Microsoft Graph Crud函数之一。
在"update"
上,我们检查更新更改是否对我们对团队的更改有效。如果更新涉及name
,startDate
,endDate
,resourceId
或eventColor
,则在调度程序中生成事件时,我们将其排除在外,因为此事件将没有创建团队变化所需的所有数据。
async function updateMicrosoft(event) {
if (event.action == "update") {
if ("name" in event.changes || "startDate" in event.changes || "endDate" in event.changes || "resourceId" in event.changes || "eventColor" in event.changes) {
if ("resourceId" in event.changes){
if (!("oldValue" in event.changes.resourceId)){
return;
}
然后,我们检查已更新的事件是否具有shiftId
;如果没有,那是因为这是一个新事件,需要创建相应的班次。我们使用createShift
创建轮班,并将Shifts id
分配给相应的Bryntum事件。
if (Object.keys(event.record.data).indexOf("shiftId") == -1 && Object.keys(event.changes).indexOf("name") !== -1){
var newShift = createShift(event.record.name, event.record.startDate, event.record.endDate, event.record.resourceId, event.record.eventColor);
newShift.then(value => {
event.record.data["shiftId"] = value.id;
});
scheduler.resourceStore.forEach((resource) => {
if (resource.id == event.record.resourceId) {
resource.hasEvent = "Assigned";
}
});
}
如果事件已经有了shiftId
,这意味着团队已经有相应的转变,需要更新此班次。
else {
if (Object.keys(event.changes).indexOf("resource") !== -1){
return;
}
updateShift(event.record.shiftId, event.record.resourceId, event.record.name, event.record.startDate, event.record.endDate, event.record.eventColor);
}
最后,如果dataChange
事件是"remove"
事件,那么我们使用deleteShift
函数删除匹配团队事件。
else if (event.action == "remove" && "name" in event.records[0].data){
deleteShift(event.records[0].data.shiftId);
}
现在尝试在bryntum调度程序中创建,更新,删除和编辑事件。您会看到团队中反映的更改。
添加样式
如果您希望您的bryntum调度程序UI看起来更像团队移动UI,然后用以下代码替换scheduler
:
const scheduler = new Scheduler({
appendTo : "scheduler",
resourceImagePath : 'images/users/',
eventStyle: 'colored',
showEventCount : true,
resourceMargin: 0,
startDate : previousSunday,
endDate : nextSaturday,
viewPreset : 'dayAndWeek',
listeners : {
dataChange: function (event) {
updateMicrosoft(event);
}},
columns : [
{ type : 'resourceInfo', text : 'Name', field : 'name', width : 160 }
],
features : {
group : 'hasEvent',
eventEdit : {
items : {
// Key to use as fields ref (for easier retrieval later)
color : {
type : 'combo',
label : 'Color',
items : ['gray', 'blue', 'purple', 'green', 'pink', 'yellow'],
// name will be used to link to a field in the event record when loading and saving in the editor
name : 'eventColor'
},
icon : {
type : 'combo',
label : 'Icon',
items: [
{ text: 'None', value: '' },
{ text: 'Door', value: 'b-fa b-fa-door-open' },
{ text: 'Car', value: 'b-fa b-fa-car' },
{ text: 'Coffee', value: 'b-fa b-fa-coffee' },
{ text: 'Envelope', value: 'b-fa b-fa-envelope' },
],
name : 'iconCls'
}
}
},
},
// Custom event renderer, simple version
eventRenderer({
eventRecord
}) {
if (eventRecord.name == "Open") {
eventRecord.iconCls = "b-fa b-fa-door-open";
return `${eventRecord.name}`;
} else if (eventRecord.name == "Vacation"){
eventRecord.iconCls = "b-fa b-fa-sun";
return `${eventRecord.name}`;
} else if (eventRecord.name == "Second shift"){
eventRecord.iconCls = "b-fa b-fa-moon";
return `${eventRecord.name}`;
} else {
return `${eventRecord.name}`;
}
}
});
在这里,我们为调度程序的样式做了一些事情。
resourceImagePath : 'images/users/',
eventStyle: 'colored',
showEventCount : true,
resourceMargin: 0,
这四行添加了用户配置文件图片,您可以将其添加到images
文件夹中的名为users
的文件夹中。这些配置文件图片应具有以下文件名结构,以与团队中的成员相关:first-name last-name.jpg
。如果用户找不到配置文件图片,则将使用其初始图片。
我们还对eventStyle
和resourceMargin
的团队的变化也相似。
showEventCount
将显示每个成员与他们的名称和个人资料图片一起有多少事件。
以下代码使用户可以在事件编辑器中为其移动选择颜色和图标:
features : {
group : 'hasEvent',
eventEdit : {
items : {
// Key to use as fields ref (for easier retrieval later)
color : {
type : 'combo',
label : 'Color',
items : ['gray', 'blue', 'purple', 'green', 'pink', 'yellow'],
// name will be used to link to a field in the event record when loading and saving in the editor
name : 'eventColor'
},
icon : {
type : 'combo',
label : 'Icon',
items: [
{ text: 'None', value: '' },
{ text: 'Door', value: 'b-fa b-fa-door-open' },
{ text: 'Car', value: 'b-fa b-fa-car' },
{ text: 'Coffee', value: 'b-fa b-fa-coffee' },
{ text: 'Envelope', value: 'b-fa b-fa-envelope' },
],
name : 'iconCls'
}
}
},
},
group
功能还使我们能够将团队成员组织成那些没有被分配的班次的成员。
最后,eventRenderer
将分配一个共享任何名称的转移图标,以相同的方式进行样式。
// Custom event renderer, simple version
eventRenderer({
eventRecord
}) {
if (eventRecord.name == "Open") {
eventRecord.iconCls = "b-fa b-fa-door-open";
return `${eventRecord.name}`;
} else if (eventRecord.name == "Vacation"){
eventRecord.iconCls = "b-fa b-fa-sun";
return `${eventRecord.name}`;
} else if (eventRecord.name == "Second shift"){
eventRecord.iconCls = "b-fa b-fa-moon";
return `${eventRecord.name}`;
} else {
return `${eventRecord.name}`;
}
}
结果是遵循团队用户体验的醒目和直观的UI。
下一步
本教程为您提供了使用Vanilla JavaScript创建Bryntum调度程序的起点,并将其与Microsoft团队同步。您可以通过多种方法可以改善Bryntum调度程序。例如,您可以添加诸如resource grouping之类的功能。看看我们的demos page,以查看可用功能的演示。