Added jQuery FullCalendar tutorial code (#249)
This commit is contained in:
parent
5ca1a89c85
commit
d37bbfbd7a
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.1.0",
|
||||
"libraryName": "spfx-js-fullcalendar",
|
||||
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-js-fullcalendar",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
9913
tutorial-migrate-fullcalendar/01-migrated-sewp-to-spfx/npm-shrinkwrap.json
generated
Normal file
9913
tutorial-migrate-fullcalendar/01-migrated-sewp-to-spfx/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface ITasksCalendarWebPartProps {
|
||||
description: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ITasksCalendarStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'tasksCalendarStrings' {
|
||||
const strings: ITasksCalendarStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('TasksCalendarWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
11
tutorial-migrate-fullcalendar/01-migrated-sewp-to-spfx/typings/@ms/odsp.d.ts
vendored
Normal file
11
tutorial-migrate-fullcalendar/01-migrated-sewp-to-spfx/typings/@ms/odsp.d.ts
vendored
Normal 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;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.1.0",
|
||||
"libraryName": "spfx-js-fullcalendar",
|
||||
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-js-fullcalendar",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface ITasksCalendarWebPartProps {
|
||||
listName: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Tasks calendar settings",
|
||||
"BasicGroupName": "Data",
|
||||
"ListNameFieldLabel": "List name"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ITasksCalendarStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ListNameFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'tasksCalendarStrings' {
|
||||
const strings: ITasksCalendarStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('TasksCalendarWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.1.0",
|
||||
"libraryName": "spfx-js-fullcalendar",
|
||||
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-js-fullcalendar",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
9986
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/npm-shrinkwrap.json
generated
Normal file
9986
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface ITasksCalendarWebPartProps {
|
||||
listName: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Tasks calendar settings",
|
||||
"BasicGroupName": "Data",
|
||||
"ListNameFieldLabel": "List name"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ITasksCalendarStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ListNameFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'tasksCalendarStrings' {
|
||||
const strings: ITasksCalendarStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('TasksCalendarWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
11
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/typings/@ms/odsp.d.ts
vendored
Normal file
11
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/typings/@ms/odsp.d.ts
vendored
Normal 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;
|
1
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/typings/tsd.d.ts
vendored
Normal file
1
tutorial-migrate-fullcalendar/03-transformed-js-to-typescript/typings/tsd.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.1.0",
|
||||
"libraryName": "spfx-js-fullcalendar",
|
||||
"libraryId": "e6a89529-5fef-4883-b84c-c6bc27b0bfb0",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -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" />
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "spfx-js-fullcalendar",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
9986
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/npm-shrinkwrap.json
generated
Normal file
9986
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface ITasksCalendarWebPartProps {
|
||||
listName: string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": ""
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Tasks calendar settings",
|
||||
"BasicGroupName": "Data",
|
||||
"ListNameFieldLabel": "List name"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ITasksCalendarStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ListNameFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'tasksCalendarStrings' {
|
||||
const strings: ITasksCalendarStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('TasksCalendarWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
11
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/typings/@ms/odsp.d.ts
vendored
Normal file
11
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/typings/@ms/odsp.d.ts
vendored
Normal 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;
|
1
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/typings/tsd.d.ts
vendored
Normal file
1
tutorial-migrate-fullcalendar/04-replaced-jquery-ajax-with-spfx/typings/tsd.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
|
@ -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" />
|
Loading…
Reference in New Issue