Added jQuery FullCalendar tutorial code (#249)

This commit is contained in:
Waldek Mastykarz 2017-06-27 12:50:16 +02:00 committed by Vesa Juvonen
parent 5ca1a89c85
commit d37bbfbd7a
98 changed files with 41997 additions and 0 deletions

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.0",
"libraryName": "spfx-js-fullcalendar",
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"environment": "spo"
}
}

View File

@ -0,0 +1,18 @@
# Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor Web Part to SharePoint Framework
Sample jQuery FullCalendar solution migrated from a Script Editor Web Part to the SharePoint Framework.
![jQuery FullCalendar solution built using Script Editor Web Part](https://devofficecdn.azureedge.net/sharepointdocumentation/images/fullcalendar-sewp.png)
More information about the solution is available at [https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx).
## Minimal Path to Awesome
- In the command line run:
- `npm i`
- `gulp serve --nobrowser`
- In the web browser navigate to the hosted version of the SharePoint workbench located in the same site as where the Tasks list is, eg. *https://contoso.sharepoint.com/sites/team/_layouts/15/workbench.aspx*.
[More information](../README.md)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorial-migrate-fullcalendar/01" />

View File

@ -0,0 +1,17 @@
{
"entries": [
{
"entry": "./lib/webparts/tasksCalendar/TasksCalendarWebPart.js",
"manifest": "./src/webparts/tasksCalendar/TasksCalendarWebPart.manifest.json",
"outputPath": "./dist/tasks-calendar.bundle.js"
}
],
"externals": {
"jquery": "https://code.jquery.com/jquery-1.11.1.min.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
"fullcalendar": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"
},
"localizedResources": {
"tasksCalendarStrings": "webparts/tasksCalendar/loc/{locale}.js"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-js-fullcalendar",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "spfx-js-fullcalendar-client-side-solution",
"id": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "solution/spfx-js-fullcalendar.sppkg"
}
}

View File

@ -0,0 +1,9 @@
{
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,45 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "spfx-js-fullcalendar",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"author": {
"name": "Waldek Mastykarz",
"url": "https://blog.mastykarz.nl"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.1.0",
"@microsoft/sp-webpart-base": "~1.1.0",
"@types/webpack-env": ">=1.12.1 <1.14.0"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.1.0",
"@microsoft/sp-module-interfaces": "~1.1.0",
"@microsoft/sp-webpart-workbench": "~1.1.0",
"gulp": "~3.9.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
}
}

View File

@ -0,0 +1,3 @@
export interface ITasksCalendarWebPartProps {
description: string;
}

View File

@ -0,0 +1,52 @@
.tasksCalendar {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
padding: 20px;
}
.listItem {
max-width: 715px;
margin: 5px auto 5px auto;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: #0078d7;
border-color: #0078d7;
color: #ffffff;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
font-weight: 400;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: 600;
font-size: 14px;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "88991757-fd46-4085-bf49-4f75d345554e",
"alias": "TasksCalendarWebPart",
"componentType": "WebPart",
"version": "*", // The "*" signifies that the version should be taken from the package.json
"manifestVersion": 2,
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*/
"safeWithCustomScriptDisabled": false,
"preconfiguredEntries": [{
"groupId": "88991757-fd46-4085-bf49-4f75d345554e",
"group": { "default": "Under Development" },
"title": { "default": "Tasks calendar" },
"description": { "default": "Shows tasks in calendar view" },
"officeFabricIconFontName": "Calendar",
"properties": {
"description": "Tasks calendar"
}
}]
}

View File

@ -0,0 +1,55 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './TasksCalendar.module.scss';
import * as strings from 'tasksCalendarStrings';
import { ITasksCalendarWebPartProps } from './ITasksCalendarWebPartProps';
import 'jquery';
import 'moment';
import 'fullcalendar';
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;
(window as any).webAbsoluteUrl = this.context.pageContext.web.absoluteUrl;
require('./script');
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITasksCalendarStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'tasksCalendarStrings' {
const strings: ITasksCalendarStrings;
export = strings;
}

View File

@ -0,0 +1,124 @@
var moment = require('moment');
var PATH_TO_DISPFORM = window.webAbsoluteUrl + "/Lists/Tasks/DispForm.aspx";
var TASK_LIST = "Tasks";
var COLORS = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];
displayTasks();
function displayTasks() {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: function (calEvent, jsEvent, view) {
window.location = PATH_TO_DISPFORM + "?ID=" + calEvent.id;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: function (event, delta, revertFunc) {
updateTask(event.id, event.start, event.end);
},
// put the events on the calendar
events: function (start, end, timezone, callback) {
var startDate = start.format('YYYY-MM-DD');
var endDate = end.format('YYYY-MM-DD');
var restQuery = "/_api/Web/Lists/GetByTitle('" + TASK_LIST + "')/items?$select=ID,Title,\
Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";
$.ajax({
url: window.webAbsoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done(function (data, textStatus, jqXHR) {
var personColors = {};
var colorNo = 0;
var events = data.value.map(function (task) {
var assignedTo = task.AssignedTo.map(function (person) {
return person.Title;
}).join(', ');
var color = personColors[assignedTo];
if (!color) {
color = COLORS[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= COLORS.length) {
colorNo = 0;
}
return {
title: task.Title + " - " + assignedTo,
id: task.ID,
color: color, // specify the background color and border color can also create a class and use className paramter.
start: moment.utc(task.StartDate).add("1", "days"),
end: moment.utc(task.DueDate).add("1", "days") // add one day to end date so that calendar properly shows event ending on that day
};
});
callback(events);
});
}
});
}
function updateTask(id, startDate, dueDate) {
// subtract the previously added day to the date to store correct date
var sDate = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
var dDate = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";
$.ajax({
url: window.webAbsoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then(function (data, textStatus, jqXHR) {
return $.ajax({
url: window.webAbsoluteUrl +
"/_api/Web/Lists/getByTitle('" + TASK_LIST + "')/Items(" + id + ")",
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done(function (data, textStatus, jqXHR) {
alert("Update Successful");
})
.fail(function (jqXHR, textStatus, errorThrown) {
alert("Update Failed");
})
.always(function () {
displayTasks();
});
}

View File

@ -0,0 +1,9 @@
/// <reference types="mocha" />
import { assert } from 'chai';
describe('TasksCalendarWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"types": [
"es6-promise",
"es6-collections",
"webpack-env"
]
}
}

View File

@ -0,0 +1,11 @@
// Type definitions for Microsoft ODSP projects
// Project: ODSP
/* Global definition for UNIT_TEST builds
Code that is wrapped inside an if(UNIT_TEST) {...}
block will not be included in the final bundle when the
--ship flag is specified */
declare const UNIT_TEST: boolean;
/* Global defintion for SPO builds */
declare const DATACENTER: boolean;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.0",
"libraryName": "spfx-js-fullcalendar",
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"environment": "spo"
}
}

View File

@ -0,0 +1,18 @@
# Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor Web Part to SharePoint Framework
Sample jQuery FullCalendar solution migrated from a Script Editor Web Part to the SharePoint Framework.
![jQuery FullCalendar solution built using Script Editor Web Part](https://devofficecdn.azureedge.net/sharepointdocumentation/images/fullcalendar-sewp.png)
More information about the solution is available at [https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx).
## Minimal Path to Awesome
- In the command line run:
- `npm i`
- `gulp serve --nobrowser`
- In the web browser navigate to the hosted version of the SharePoint workbench located in the same site as where the Tasks list is, eg. *https://contoso.sharepoint.com/sites/team/_layouts/15/workbench.aspx*.
[More information](../README.md)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorial-migrate-fullcalendar/02" />

View File

@ -0,0 +1,17 @@
{
"entries": [
{
"entry": "./lib/webparts/tasksCalendar/TasksCalendarWebPart.js",
"manifest": "./src/webparts/tasksCalendar/TasksCalendarWebPart.manifest.json",
"outputPath": "./dist/tasks-calendar.bundle.js"
}
],
"externals": {
"jquery": "https://code.jquery.com/jquery-1.11.1.min.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
"fullcalendar": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"
},
"localizedResources": {
"tasksCalendarStrings": "webparts/tasksCalendar/loc/{locale}.js"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-js-fullcalendar",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "spfx-js-fullcalendar-client-side-solution",
"id": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "solution/spfx-js-fullcalendar.sppkg"
}
}

View File

@ -0,0 +1,9 @@
{
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,45 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "spfx-js-fullcalendar",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"author": {
"name": "Waldek Mastykarz",
"url": "https://blog.mastykarz.nl"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.1.0",
"@microsoft/sp-webpart-base": "~1.1.0",
"@types/webpack-env": ">=1.12.1 <1.14.0"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.1.0",
"@microsoft/sp-module-interfaces": "~1.1.0",
"@microsoft/sp-webpart-workbench": "~1.1.0",
"gulp": "~3.9.1",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
}
}

View File

@ -0,0 +1,3 @@
export interface ITasksCalendarWebPartProps {
listName: string;
}

View File

@ -0,0 +1,52 @@
.tasksCalendar {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
padding: 20px;
}
.listItem {
max-width: 715px;
margin: 5px auto 5px auto;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: #0078d7;
border-color: #0078d7;
color: #ffffff;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
font-weight: 400;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: 600;
font-size: 14px;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "88991757-fd46-4085-bf49-4f75d345554e",
"alias": "TasksCalendarWebPart",
"componentType": "WebPart",
"version": "*", // The "*" signifies that the version should be taken from the package.json
"manifestVersion": 2,
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*/
"safeWithCustomScriptDisabled": false,
"preconfiguredEntries": [{
"groupId": "88991757-fd46-4085-bf49-4f75d345554e",
"group": { "default": "Under Development" },
"title": { "default": "Tasks calendar" },
"description": { "default": "Shows tasks in calendar view" },
"officeFabricIconFontName": "Calendar",
"properties": {
"listName": ""
}
}]
}

View File

@ -0,0 +1,178 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './TasksCalendar.module.scss';
import * as strings from 'tasksCalendarStrings';
import { ITasksCalendarWebPartProps } from './ITasksCalendarWebPartProps';
var $: any = require('jquery');
var moment: any = require('moment');
import 'fullcalendar';
var COLORS = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;
this.displayTasks();
}
private displayTasks() {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent, jsEvent, view) => {
(window as any).location = this.context.pageContext.web.absoluteUrl +
"/Lists/" + escape(this.properties.listName) + "/DispForm.aspx?ID=" + calEvent.id;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event, delta, revertFunc) => {
this.updateTask(event.id, event.start, event.end);
},
// put the events on the calendar
events: (start, end, timezone, callback) => {
var startDate = start.format('YYYY-MM-DD');
var endDate = end.format('YYYY-MM-DD');
var restQuery = "/_api/Web/Lists/GetByTitle('" + escape(this.properties.listName) + "')/items?$select=ID,Title,\
Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";
$.ajax({
url: this.context.pageContext.web.absoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done((data, textStatus, jqXHR) => {
var personColors = {};
var colorNo = 0;
var events = data.value.map((task) => {
var assignedTo = task.AssignedTo.map((person) => {
return person.Title;
}).join(', ');
var color = personColors[assignedTo];
if (!color) {
color = COLORS[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= COLORS.length) {
colorNo = 0;
}
return {
title: task.Title + " - " + assignedTo,
id: task.ID,
color: color, // specify the background color and border color can also create a class and use className paramter.
start: moment.utc(task.StartDate).add("1", "days"),
end: moment.utc(task.DueDate).add("1", "days") // add one day to end date so that calendar properly shows event ending on that day
};
});
callback(events);
});
}
});
}
private updateTask(id, startDate, dueDate) {
// subtract the previously added day to the date to store correct date
var sDate = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
var dDate = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";
$.ajax({
url: this.context.pageContext.web.absoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then((data, textStatus, jqXHR) => {
return $.ajax({
url: this.context.pageContext.web.absoluteUrl +
"/_api/Web/Lists/getByTitle('" + escape(this.properties.listName) + "')/Items(" + id + ")",
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done((data, textStatus, jqXHR) => {
alert("Update Successful");
})
.fail((jqXHR, textStatus, errorThrown) => {
alert("Update Failed");
})
.always(() => {
this.displayTasks();
});
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
protected get disableReactivePropertyChanges(): boolean {
return true;
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Tasks calendar settings",
"BasicGroupName": "Data",
"ListNameFieldLabel": "List name"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITasksCalendarStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}
declare module 'tasksCalendarStrings' {
const strings: ITasksCalendarStrings;
export = strings;
}

View File

@ -0,0 +1,9 @@
/// <reference types="mocha" />
import { assert } from 'chai';
describe('TasksCalendarWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"types": [
"es6-promise",
"es6-collections",
"webpack-env"
]
}
}

View File

@ -0,0 +1,11 @@
// Type definitions for Microsoft ODSP projects
// Project: ODSP
/* Global definition for UNIT_TEST builds
Code that is wrapped inside an if(UNIT_TEST) {...}
block will not be included in the final bundle when the
--ship flag is specified */
declare const UNIT_TEST: boolean;
/* Global defintion for SPO builds */
declare const DATACENTER: boolean;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.0",
"libraryName": "spfx-js-fullcalendar",
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"environment": "spo"
}
}

View File

@ -0,0 +1,18 @@
# Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor Web Part to SharePoint Framework
Sample jQuery FullCalendar solution migrated from a Script Editor Web Part to the SharePoint Framework.
![jQuery FullCalendar solution built using Script Editor Web Part](https://devofficecdn.azureedge.net/sharepointdocumentation/images/fullcalendar-sewp.png)
More information about the solution is available at [https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx).
## Minimal Path to Awesome
- In the command line run:
- `npm i`
- `gulp serve --nobrowser`
- In the web browser navigate to the hosted version of the SharePoint workbench located in the same site as where the Tasks list is, eg. *https://contoso.sharepoint.com/sites/team/_layouts/15/workbench.aspx*.
[More information](../README.md)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorial-migrate-fullcalendar/03" />

View File

@ -0,0 +1,17 @@
{
"entries": [
{
"entry": "./lib/webparts/tasksCalendar/TasksCalendarWebPart.js",
"manifest": "./src/webparts/tasksCalendar/TasksCalendarWebPart.manifest.json",
"outputPath": "./dist/tasks-calendar.bundle.js"
}
],
"externals": {
"jquery": "https://code.jquery.com/jquery-1.11.1.min.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
"fullcalendar": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"
},
"localizedResources": {
"tasksCalendarStrings": "webparts/tasksCalendar/loc/{locale}.js"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-js-fullcalendar",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "spfx-js-fullcalendar-client-side-solution",
"id": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "solution/spfx-js-fullcalendar.sppkg"
}
}

View File

@ -0,0 +1,9 @@
{
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,45 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
{
"name": "spfx-js-fullcalendar",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"author": {
"name": "Waldek Mastykarz",
"url": "https://blog.mastykarz.nl"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.1.0",
"@microsoft/sp-webpart-base": "~1.1.0",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"moment": "^2.18.1"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.1.0",
"@microsoft/sp-module-interfaces": "~1.1.0",
"@microsoft/sp-webpart-workbench": "~1.1.0",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/fullcalendar": "^2.7.44",
"@types/jquery": "^1.10.32",
"@types/mocha": ">=2.2.33 <2.6.0",
"gulp": "~3.9.1"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
}
}

View File

@ -0,0 +1,3 @@
export interface ITasksCalendarWebPartProps {
listName: string;
}

View File

@ -0,0 +1,52 @@
.tasksCalendar {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
padding: 20px;
}
.listItem {
max-width: 715px;
margin: 5px auto 5px auto;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: #0078d7;
border-color: #0078d7;
color: #ffffff;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
font-weight: 400;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: 600;
font-size: 14px;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "88991757-fd46-4085-bf49-4f75d345554e",
"alias": "TasksCalendarWebPart",
"componentType": "WebPart",
"version": "*", // The "*" signifies that the version should be taken from the package.json
"manifestVersion": 2,
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*/
"safeWithCustomScriptDisabled": false,
"preconfiguredEntries": [{
"groupId": "88991757-fd46-4085-bf49-4f75d345554e",
"group": { "default": "Under Development" },
"title": { "default": "Tasks calendar" },
"description": { "default": "Shows tasks in calendar view" },
"officeFabricIconFontName": "Calendar",
"properties": {
"listName": ""
}
}]
}

View File

@ -0,0 +1,187 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './TasksCalendar.module.scss';
import * as strings from 'tasksCalendarStrings';
import { ITasksCalendarWebPartProps } from './ITasksCalendarWebPartProps';
import * as $ from 'jquery';
import 'fullcalendar';
import * as moment from 'moment';
interface ITask {
ID: number;
Title: string;
StartDate: string;
DueDate: string;
AssignedTo: [{ Title: string }];
}
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
private readonly colors: string[] = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;
this.displayTasks();
}
private displayTasks(): void {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent: FC.EventObject, jsEvent: MouseEvent, view: FC.ViewObject): void => {
(window as any).location = `${this.context.pageContext.web.absoluteUrl}\
/Lists/${escape(this.properties.listName)}/DispForm.aspx?ID=${calEvent.id}`;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event: FC.EventObject, delta: moment.Duration, revertFunc: Function): void => {
this.updateTask(event.id, <moment.Moment>event.start, <moment.Moment>event.end);
},
// put the events on the calendar
events: (start: moment.Moment, end: moment.Moment, timezone: string, callback: Function): void => {
const startDate: string = start.format('YYYY-MM-DD');
const endDate: string = end.format('YYYY-MM-DD');
const restQuery: string = `/_api/Web/Lists/GetByTitle('${escape(this.properties.listName)}')/items?$select=ID,Title,\
Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '${startDate}' and DueDate le '${endDate}')or(StartDate ge '${startDate}' and StartDate le '${endDate}'))`;
$.ajax({
url: this.context.pageContext.web.absoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done((data: { value: ITask[] }, textStatus: string, jqXHR: JQueryXHR): void => {
let personColors: { [person: string]: string; } = {};
let colorNo: number = 0;
const events: FC.EventObject[] = data.value.map((task: ITask): FC.EventObject => {
const assignedTo: string = task.AssignedTo.map((person: { Title: string }): string => {
return person.Title;
}).join(', ');
let color: string = personColors[assignedTo];
if (!color) {
color = this.colors[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= this.colors.length) {
colorNo = 0;
}
return {
title: `${task.Title} - ${assignedTo}`,
id: task.ID,
// specify the background color and border color can also create a class and use className parameter
color: color,
start: moment.utc(task.StartDate).add("1", "days"),
// add one day to end date so that calendar properly shows event ending on that day
end: moment.utc(task.DueDate).add("1", "days")
};
});
callback(events);
});
}
});
}
private updateTask(id: number, startDate: moment.Moment, dueDate: moment.Moment): void {
// subtract the previously added day to the date to store correct date
const sDate: string = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
const dDate: string = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";
$.ajax({
url: this.context.pageContext.web.absoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then((data: { FormDigestValue: string }, textStatus: string, jqXHR: JQueryXHR): JQueryXHR => {
return $.ajax({
url: `${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`,
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done((data: {}, textStatus: string, jqXHR: JQueryXHR): void => {
alert("Update Successful");
})
.fail((jqXHR: JQueryXHR, textStatus: string, errorThrown: string): void => {
alert("Update Failed");
})
.always((): void => {
this.displayTasks();
});
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
protected get disableReactivePropertyChanges(): boolean {
return true;
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Tasks calendar settings",
"BasicGroupName": "Data",
"ListNameFieldLabel": "List name"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITasksCalendarStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}
declare module 'tasksCalendarStrings' {
const strings: ITasksCalendarStrings;
export = strings;
}

View File

@ -0,0 +1,9 @@
/// <reference types="mocha" />
import { assert } from 'chai';
describe('TasksCalendarWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"types": [
"es6-promise",
"es6-collections",
"webpack-env"
]
}
}

View File

@ -0,0 +1,11 @@
// Type definitions for Microsoft ODSP projects
// Project: ODSP
/* Global definition for UNIT_TEST builds
Code that is wrapped inside an if(UNIT_TEST) {...}
block will not be included in the final bundle when the
--ship flag is specified */
declare const UNIT_TEST: boolean;
/* Global defintion for SPO builds */
declare const DATACENTER: boolean;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />

View File

@ -0,0 +1,25 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,8 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.1.0",
"libraryName": "spfx-js-fullcalendar",
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"environment": "spo"
}
}

View File

@ -0,0 +1,18 @@
# Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor Web Part to SharePoint Framework
Sample jQuery FullCalendar solution migrated from a Script Editor Web Part to the SharePoint Framework.
![jQuery FullCalendar solution built using Script Editor Web Part](https://devofficecdn.azureedge.net/sharepointdocumentation/images/fullcalendar-sewp.png)
More information about the solution is available at [https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx).
## Minimal Path to Awesome
- In the command line run:
- `npm i`
- `gulp serve --nobrowser`
- In the web browser navigate to the hosted version of the SharePoint workbench located in the same site as where the Tasks list is, eg. *https://contoso.sharepoint.com/sites/team/_layouts/15/workbench.aspx*.
[More information](../README.md)
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorial-migrate-fullcalendar/04" />

View File

@ -0,0 +1,17 @@
{
"entries": [
{
"entry": "./lib/webparts/tasksCalendar/TasksCalendarWebPart.js",
"manifest": "./src/webparts/tasksCalendar/TasksCalendarWebPart.manifest.json",
"outputPath": "./dist/tasks-calendar.bundle.js"
}
],
"externals": {
"jquery": "https://code.jquery.com/jquery-1.11.1.min.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
"fullcalendar": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"
},
"localizedResources": {
"tasksCalendarStrings": "webparts/tasksCalendar/loc/{locale}.js"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-js-fullcalendar",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,10 @@
{
"solution": {
"name": "spfx-js-fullcalendar-client-side-solution",
"id": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
"version": "1.0.0.0"
},
"paths": {
"zippedPackage": "solution/spfx-js-fullcalendar.sppkg"
}
}

View File

@ -0,0 +1,9 @@
{
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,45 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-unused-imports": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"valid-typeof": true,
"variable-name": false,
"whitespace": false
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
{
"name": "spfx-js-fullcalendar",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"author": {
"name": "Waldek Mastykarz",
"url": "https://blog.mastykarz.nl"
},
"dependencies": {
"@microsoft/sp-core-library": "~1.1.0",
"@microsoft/sp-webpart-base": "~1.1.0",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"moment": "^2.18.1"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.1.0",
"@microsoft/sp-module-interfaces": "~1.1.0",
"@microsoft/sp-webpart-workbench": "~1.1.0",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/fullcalendar": "^2.7.44",
"@types/jquery": "^1.10.32",
"@types/mocha": ">=2.2.33 <2.6.0",
"gulp": "~3.9.1"
},
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
}
}

View File

@ -0,0 +1,3 @@
export interface ITasksCalendarWebPartProps {
listName: string;
}

View File

@ -0,0 +1,52 @@
.tasksCalendar {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
padding: 20px;
}
.listItem {
max-width: 715px;
margin: 5px auto 5px auto;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: #0078d7;
border-color: #0078d7;
color: #ffffff;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
font-weight: 400;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: 600;
font-size: 14px;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "88991757-fd46-4085-bf49-4f75d345554e",
"alias": "TasksCalendarWebPart",
"componentType": "WebPart",
"version": "*", // The "*" signifies that the version should be taken from the package.json
"manifestVersion": 2,
/**
* This property should only be set to true if it is certain that the webpart does not
* allow arbitrary scripts to be called
*/
"safeWithCustomScriptDisabled": false,
"preconfiguredEntries": [{
"groupId": "88991757-fd46-4085-bf49-4f75d345554e",
"group": { "default": "Under Development" },
"title": { "default": "Tasks calendar" },
"description": { "default": "Shows tasks in calendar view" },
"officeFabricIconFontName": "Calendar",
"properties": {
"listName": ""
}
}]
}

View File

@ -0,0 +1,177 @@
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-webpart-base';
import { escape } from '@microsoft/sp-lodash-subset';
import styles from './TasksCalendar.module.scss';
import * as strings from 'tasksCalendarStrings';
import { ITasksCalendarWebPartProps } from './ITasksCalendarWebPartProps';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
import * as $ from 'jquery';
import 'fullcalendar';
import * as moment from 'moment';
interface ITask {
ID: number;
Title: string;
StartDate: string;
DueDate: string;
AssignedTo: [{ Title: string }];
}
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
private readonly colors: string[] = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;
this.displayTasks();
}
private displayTasks(): void {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent: FC.EventObject, jsEvent: MouseEvent, view: FC.ViewObject): void => {
(window as any).location = `${this.context.pageContext.web.absoluteUrl}\
/Lists/${escape(this.properties.listName)}/DispForm.aspx?ID=${calEvent.id}`;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event: FC.EventObject, delta: moment.Duration, revertFunc: Function): void => {
this.updateTask(event.id, <moment.Moment>event.start, <moment.Moment>event.end);
},
// put the events on the calendar
events: (start: moment.Moment, end: moment.Moment, timezone: string, callback: Function): void => {
const startDate: string = start.format('YYYY-MM-DD');
const endDate: string = end.format('YYYY-MM-DD');
const restQuery: string = `/_api/Web/Lists/GetByTitle('${escape(this.properties.listName)}')/items?$select=ID,Title,\
Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '${startDate}' and DueDate le '${endDate}')or(StartDate ge '${startDate}' and StartDate le '${endDate}'))`;
this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + restQuery, SPHttpClient.configurations.v1, {
headers: {
'Accept': "application/json;odata.metadata=none"
}
})
.then((response: SPHttpClientResponse): Promise<{ value: ITask[] }> => {
return response.json();
})
.then((data: { value: ITask[] }): void => {
let personColors: { [person: string]: string; } = {};
let colorNo: number = 0;
const events: FC.EventObject[] = data.value.map((task: ITask): FC.EventObject => {
const assignedTo: string = task.AssignedTo.map((person: { Title: string }): string => {
return person.Title;
}).join(', ');
let color: string = personColors[assignedTo];
if (!color) {
color = this.colors[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= this.colors.length) {
colorNo = 0;
}
return {
title: `${task.Title} - ${assignedTo}`,
id: task.ID,
// specify the background color and border color can also create a class and use className paramter
color: color,
start: moment.utc(task.StartDate).add("1", "days"),
// add one day to end date so that calendar properly shows event ending on that day
end: moment.utc(task.DueDate).add("1", "days")
};
});
callback(events);
});
}
});
}
private updateTask(id: number, startDate: moment.Moment, dueDate: moment.Moment): void {
// subtract the previously added day to the date to store correct date
const sDate: string = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
const dDate: string = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";
this.context.spHttpClient.post(`${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`, SPHttpClient.configurations.v1, {
body: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
})
.then((response: SPHttpClientResponse): void => {
if (response.ok) {
alert("Update Successful");
}
else {
alert("Update Failed");
}
this.displayTasks();
});
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
protected get disableReactivePropertyChanges(): boolean {
return true;
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Tasks calendar settings",
"BasicGroupName": "Data",
"ListNameFieldLabel": "List name"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITasksCalendarStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}
declare module 'tasksCalendarStrings' {
const strings: ITasksCalendarStrings;
export = strings;
}

View File

@ -0,0 +1,9 @@
/// <reference types="mocha" />
import { assert } from 'chai';
describe('TasksCalendarWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"types": [
"es6-promise",
"es6-collections",
"webpack-env"
]
}
}

View File

@ -0,0 +1,11 @@
// Type definitions for Microsoft ODSP projects
// Project: ODSP
/* Global definition for UNIT_TEST builds
Code that is wrapped inside an if(UNIT_TEST) {...}
block will not be included in the final bundle when the
--ship flag is specified */
declare const UNIT_TEST: boolean;
/* Global defintion for SPO builds */
declare const DATACENTER: boolean;

View File

@ -0,0 +1 @@
/// <reference path="@ms/odsp.d.ts" />

View File

@ -0,0 +1,84 @@
# Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor Web Part to SharePoint Framework
Sample jQuery FullCalendar solution migrated from a Script Editor Web Part to the SharePoint Framework.
![jQuery FullCalendar solution built using Script Editor Web Part](https://devofficecdn.azureedge.net/sharepointdocumentation/images/fullcalendar-sewp.png)
Sub folders represent the different stages of the migration process. Each folder contains the complete solution that you can run in browser. More information about the solution is available at [https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx).
| Folder | Stage | More information
| ------------- | ------------- | ------------- |
| 01-migrated-sewp-to-spfx | Original solution migrated to SPFx. As much as possible of the original code left unaltered | [details](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx)
| 02-added-configuration | Extended the code with support for configuring the web part through the property pane | [details](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx#add-support-for-configuring-the-web-part-through-web-part-properties)
| 03-transformed-js-to-typescript | Transformed plain JavaScript to TypeScript | [details](https://dev.office.com/sharepoint/docs/spfx/web-parts/guidance/migrate-jquery-fullcalendar-script-to-spfx#transform-the-plain-javascript-code-to-typescript)
| 04-replaced-jquery-ajax-with-spfx | Replaced jQuery AJAX with the SharePoint Framework SPHttpClient
## Used SharePoint Framework Version
![v1.1.0](https://img.shields.io/badge/SPFx-v1.1.0-green.svg)
## Applies to
* [SharePoint Framework](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
* [Office 365 developer tenant](http://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
## Solution
| Solution | Author(s) |
| ------------- | ------------- |
| tutorial-migrate-fullcalendar | Waldek Mastykarz (MVP, [Rencore](https://rencore.com), @waldekm)
## Version history
| Version | Date | Comments |
| ------------- | ------------- | ------------- |
| 1.0.0 | June, 27 2017 | Initial commit |
## Disclaimer
**THIS CODE IS PROVIDED AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
----------
## Build and run the tutorials
To build and run this client-side project, you will need to clone and build the tutorials project. Because the solution retrieves its data from SharePoint, you will also need a Task list named **Tasks** with some data in it. To preview the web part use the hosted version of the SharePoint Workbench loaded in the context of the site where the Tasks list is located.
Clone this repo by executing the following command in your console:
```sh
git clone https://github.com/SharePoint/sp-dev-fx-webparts.git
```
Navigate to the cloned repo folder which should be the same as the repo name:
```sh
cd sp-dev-fx-webparts
```
Navigate to the `tutorials` folder:
```sh
cd tutorials
```
Navigate to the `specific web part` folder:
```sh
cd 'subfolder'
```
Now run the following command to install the npm packages:
```sh
npm install
```
This will install the required npm packages and dependencies to build and run the SharePoint Framework project.
Once the npm packages are installed, run the command to preview your web parts in SharePoint Workbench:
```sh
gulp serve --nobrowser
```
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/tutorial-migrate-fullcalendar" />