Skip to content

Instantly share code, notes, and snippets.

@ypcode
Last active July 28, 2017 20:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ypcode/0ecd6fb8bef2895af897c76577ad88cc to your computer and use it in GitHub Desktop.
Save ypcode/0ecd6fb8bef2895af897c76577ad88cc to your computer and use it in GitHub Desktop.
import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneDropdown,
} from '@microsoft/sp-webpart-base';
import { SPComponentLoader } from '@microsoft/sp-loader';
import styles from './KanbanBoard.module.scss';
import * as strings from 'kanbanBoardStrings';
import { IKanbanBoardWebPartProps } from './IKanbanBoardWebPartProps';
import { AppStartup } from "../../startup";
// jQuery and jQuery UI Drag&Drop extensions
import * as $ from "jquery";
require("jquery-ui/ui/widgets/draggable");
require("jquery-ui/ui/widgets/droppable");
// Models
import { ITask, IListInfo, IFieldInfo } from "../../models/";
// Services
import {
IConfigurationService, ConfigurationServiceKey,
IDataService, DataServiceKey
} from "../../services";
// Constants
const LAYOUT_MAX_COLUMNS = 12;
export default class KanbanBoardWebPart extends BaseClientSideWebPart<IKanbanBoardWebPartProps> {
private statuses: string[] = [];
private tasks: ITask[] = [];
private availableLists: IListInfo[] = [];
private availableChoiceFields: IFieldInfo[] = [];
private dataService: IDataService = null;
private config: IConfigurationService = null;
constructor() {
super();
SPComponentLoader.loadCss('https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.min.css');
}
public onInit(): Promise<any> {
return super.onInit()
// Set the global configuration of the application
// This is where we will define the proper services according to the context (Local, Test, Prod,...)
// or according to specific settings
.then(_ => AppStartup.configure(this.context, this.properties))
// When configuration is done, we get the instances of the services we want to use
.then(serviceScope => {
this.dataService = serviceScope.consume(DataServiceKey);
this.config = serviceScope.consume(ConfigurationServiceKey);
})
// Then, we are able to use the methods of the services
// Load the available lists in the current site
.then(() => this.dataService.getAvailableLists())
.then((lists: IListInfo[]) => this.availableLists = lists);
}
public render(): void {
// Only if properly configured
if (this.properties.statusFieldName && this.properties.tasksListId) {
// Load the data
this.dataService.getStatuses()
.then((statuses: string[]) => this.statuses = statuses)
.then(() => this.dataService.getAllTasks())
.then((tasks: ITask[]) => this.tasks = tasks)
// And then render
.then(() => this._renderBoard())
.catch(error => {
console.log(error);
console.log("An error occured while loading data...");
});
} else {
this.domElement.innerHTML = `<div class="ms-MessageBar ms-MessageBar--warning">${strings.PleaseConfigureWebPartMessage}</div>`
}
}
private _getColumnSizeClassName(columnsCount: number): string {
if (columnsCount < 1) {
console.log("Invalid number of columns");
return "";
}
if (columnsCount > LAYOUT_MAX_COLUMNS) {
console.log("Too many columns for responsive UI");
return "";
}
let columnSize = Math.floor(LAYOUT_MAX_COLUMNS / columnsCount);
return "ms-u-sm" + (columnSize || 1);
}
/**
* Generates and inject the HTML of the Kanban Board
*/
private _renderBoard(): void {
let columnSizeClass = this._getColumnSizeClassName(this.statuses.length);
// The begininning of the WebPart
let html = `
<h1>Hello World!</h1>
<div class="${styles.kanbanBoard}">
<div class="${styles.container}">
<div class="ms-Grid-row ${styles.row}">`;
// For each status
this.statuses.forEach(status => {
// Append a new Office UI Fabric column with the appropriate width to the row
html += `<div class="${styles.kanbanColumn} ms-Grid-col ${columnSizeClass}" data-status="${status}">
<h3 class="ms-fontColor-themePrimary">${status}</h3>`;
// Get all the tasks in the current status
let currentGroupTasks = this.tasks.filter(t => t.Status == status);
// Add a new tile for each task in the current column
currentGroupTasks.forEach(task => {
html += `<div class="${styles.task}" data-taskid="${task.Id}">
<div class="ms-fontSize-xl">${task.Title}</div>
</div>`;
});
// Close the column element
html += `</div>`;
});
// Ends the WebPart HTML
html += `</div>
<div class="ms-Grid-row ${styles.row}">
<div class="ms-Grid-col ms-u-sm12">
<a class="ms-Link linkAddTask">Add Task</a>
</div>
</div>
</div>
</div>
</div>`;
// Apply the generated HTML to the WebPart DOM element
this.domElement.innerHTML = html;
// Make the kanbanColumn elements droppable areas
let webpart = this;
$(this.domElement).find(`.${styles.kanbanColumn}`).droppable({
tolerance: "intersect",
accept: `.${styles.task}`,
activeClass: "ui-state-default",
hoverClass: "ui-state-hover",
drop: (event, ui) => {
// Here the code to execute whenever an element is dropped into a column
let taskItem = $(ui.draggable);
let source = taskItem.parent();
let previousStatus = source.data("status");
let taskId = taskItem.data("taskid");
let target = $(event.target);
let newStatus = target.data("status");
taskItem.appendTo(target);
// If the status has changed, apply the changes
if (previousStatus != newStatus) {
webpart.dataService.updateTaskStatus(taskId, newStatus);
}
}
});
// Make the task items draggable
$(this.domElement).find(`.${styles.task}`).draggable({
classes: {
"ui-draggable-dragging": styles.dragging
},
opacity: 0.7,
helper: "clone",
cursor: "move",
revert: "invalid"
});
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
public onPropertyPaneFieldChanged(propertyName: string, oldValue: string, newValue: string) {
this.config.statusFieldInternalName = this.properties.statusFieldName;
this.config.tasksListId = this.properties.tasksListId;
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.HeaderDescription
},
groups: [
{
groupName: strings.TasksConfigurationGroup,
groupFields: [
PropertyPaneDropdown('tasksListId', {
label: strings.SourceTasksList,
options: this.availableLists.map(l => ({
key: l.Id,
text: l.Title
}))
}),
PropertyPaneDropdown('statusFieldName', {
label: strings.StatusFieldInternalName,
options: this.dataService.getAvailableChoiceFieldsFromLoadedLists().map(f => ({
key: f.InternalName,
text: f.Title
}))
})
]
}
]
}
]
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment