table-schedule
Version:
Vanilla JS table schedule with DnD support.
400 lines (322 loc) • 12 kB
Markdown
# TableSchedule.js
> Vanilla JS table schedule with DnD support.
[Demo](https://monkey-d-pixel.github.io/table-schedule/index.html)
In this document, *event* or *event item* stands for event item in the schedule, while *Event* stands for JavaScript `Event` object.
## Contents
- [TableSchedule.js](#tableschedulejs)
- [Contents](#contents)
- [Features](#features)
- [Getting Started](#getting-started)
- [Installing](#installing)
- [Usage](#usage)
- [Options](#options)
- [num](#num)
- [startDate](#startdate)
- [dayStart](#daystart)
- [dayEnd](#dayend)
- [gap](#gap)
- [quantizing](#quantizing)
- [createThreshold](#createthreshold)
- [stretchThreshold](#stretchthreshold)
- [moveYThreshold](#moveythreshold)
- [dateFormat](#dateformat)
- [directChange](#directchange)
- [extraDataset](#extradataset)
- [labelGroups](#labelgroups)
- [groupHeaderText](#groupheadertext)
- [Custom Events](#custom-events)
- [create](#create)
- [modify](#modify)
- [remove](#remove)
- [APIs](#apis)
- [addEvent(eventItem)](#addeventeventitem)
- [updateEvent(coords, modified)](#updateeventcoords-modified)
- [deleteEvent(coords)](#deleteeventcoords)
- [clear(which)](#clearwhich)
- [changeStartDate(date)](#changestartdatedate)
- [rerender()](#rerender)
- [updateRect()](#updaterect)
- [destroy()](#destroy)
- [getEvent(elem)](#geteventelem)
- [getElem(eventItem)](#getelemeventitem)
- [events](#events)
- [Browser Support](#browser-support)
- [Contribute](#contribute)
- [License](#license)
## Features
What TableSchedule.js provides:
- Drag-and-Drop to trigger aed (i.e. add / edit / delete) Events.
- Quantization or snap-to-grid by default when DnD, switchable by holding SHIFT key.
- APIs to aed event items programmatically.
- Manages events data and makes them accessible through custom Events as well as an instance property.
What it doesn't provide:
- UI components for filling forms / confirmation / changing dates or whatever. **It only provides an interface for displaying and manipulating event items.** Say if you're using Bootstrap, you might need to initialize a form modal when add / edit Events fire.
## Getting Started
### Installing
npm
```shell
npm install table-schedule
```
then
```js
import TableSchedule from 'table-schedule'
```
or
```js
var TableSchedule = require('table-schedule')
```
or
```html
<script src="/path/to/table-schedule.js"></script>
```
and don't forget css
```html
<link rel="stylesheet" href="/path/to/table-schedule.css">
```
### Usage
Prepare a `<TABLE>` element, wrapped in an `overflow:auto` element.
```html
<div style="overflow:auto">
<table class="table-schedule">
</table>
</div>
```
Initiate the instance.
```js
var ts = new TableSchedule('.table-schedule')
```
The constructor takes two params:
- **element**
- Type: `CSS selector` or `HTMLElement`
- The container for the widget, **must be a `<TABLE>` element or equivalent selector**
- **options** (optional)
- Type: `Object`
- See [Options](#Options) below.
Then add Event handlers:
```js
var $ts = document.querySelector('.table-schedule')
$ts.addEventListener('create', function(e) {
const { item } = e.detail
// Assume you have a very convenient prompt dialog like this
fantasyDialog({
fields: [
// you can put date time values from e.detail into the form
{label: 'Date', type: 'date', initValue: item.date},
{label: 'Start Time', type: 'time', initValue: item.start},
{label: 'End Time', type: 'time', initValue: item.end},
// you might need other data
{label: 'Title', type: 'text'},
{label: 'Content', type: 'textarea'},
],
onConfirm: function(load) { // load contains all field values
ts.addEvent({
date: load[0],
start: load[1],
end: load[2],
title: load[3],
content: load[4]
})
}
})
})
$ts.addEventListener('modify', function(e) {
const { item, coords, mod } = e.detail
// if you don't need a form to modify other information
var modified = Object.assign({}, item, mod)
ts.updateEvent(coords, modified)
// else you still need to provide a form for input
})
$ts.addEventListener('remove', function(e) {
const { coords } = e.detail
ts.deleteEvent(coords)
})
```
All events' data are stored in the instance's `events` property, which is a 2-dimensional array.
`instance.events[i][j]` is the j-th (starts from 0) added event on i-th (starts from 0) date among the current period.
These two indices together bind the event item's UI element and its data, and I'll call them a pair of coordinates for the event item in the rest of the document.
## Options
### num
- Type: `Number`
- Default: 7
Length of the period.
### startDate
- Type: `Date`
- Default: `new Date()`
Initial start date of the period.
### dayStart
- Type: `Number`
- Default: `6`
Start hour of the day.
### dayEnd
- Type: `Number`
- Default: `22`
End hour of the day.
### gap
- Type: `Number`
- Default: `10`
Equivalent minutes of cell height.
### quantizing
- Type: `Boolean`
- Default: `true`
Use quantization without(true) or with(false) holding SHIFT key.
### createThreshold
- Type: `Number`
- Default: `10`
In minutes.
When a new event is drawn, `create` Event only fires if the event item's duration equals or is longer than this.
When change event item's duration (by dragging the handle), `delete` Event will fire when duration is shorter than this.
### stretchThreshold
- Type: `Number`
- Default: `5`
In minutes.
When dragging an event item's handle, the item's duration (height) won't start to change until mouse / finger moved equivalent pixels (default is half of cell height) in Y axis.
### moveYThreshold
- Type: `Number`
- Default: `5`
In minutes.
When dragging an event item, the item won't start to move until mouse / finger moved equivalent pixels (default is half of cell height) in Y axis.
### dateFormat
- Type: `function`
- Default: `date => {format(date, 'MM/dd')}`,
The date texts displayed in the table header take what this function returns.
Params:
- `date` - Date object
### directChange
- Type: `Boolean|Array`
- Default: `false`
Whether to automatically apply aed changes to the event items when corresponding Event fires.
When it is `true`, all changes will be applied.
Or it can be an array of any of:
- `create`: apply add changes
- `modify`: apply all edit changes
- `start`/`end`/`date`/`datetime`: apply specific edit changes. See [modify](#modify)
- `remove`: apply delete changes
### extraDataset
- Type: `Object`
- Default: `null`
Add extra dataset entries to event elements. For example, `{id: 'ID'}` will add a `data-id` to the element with the value from event item's `ID` property.
### labelGroups
- Type: `Boolean`
- Default: `false`
Whether to show group headers below the top table header.
### groupHeaderText
- Type: `Object|String|function`
- Default: `null`
What text to show in group headers.
- `null` - eventItem.group
- `String` - a property value from eventItem
- `function` - a function which takes eventItem.group, e.g. group => ('Group - ' + group)
## Custom Events
These 3 `CustomEvent`s are fired on `mouseup` or `touchend`, which is after a drag-and-drop action on the schedule. All of them come with a `detail` property which contains useful information.
### create
Fired when a new event item is drawn.
Event.detail:
- **item** `Object`: new event item
- **date** `String`: date string formatted in `yyyy/MM/dd`
- **start** `String`: start time, formatted in `HH:mm`
- **end** `String`: end time, formatted in `HH:mm`
### modify
Fired when change either date / start / end of an event item.
Event.detail:
- **item** `Object`: reference to the original event item, which is untouched since added through `instance.addEvent`
- **coords** `Array`: the coordinates for the event item
- **mod** `Object`: the modification made to the event item
- **type** `String`: type of the modification, can be either of:
- `'start'`- when DnD an event item within its date and the start time is changed
- `'end'` - when DnD the handle bar at the event item's bottom and the end time is changed
- `'date'` - when DnD an event item to another date with start time not changed
- `'datetime'` - when drag-n-drap an event item to another date and start time
- **date** `String`: the event item's date after modifying
- **start** `String`: the event item's start time after modifying
- **end** `String`: the event item's end time after modifying
### remove
Fired when drag an event item's handle and change the event item's duration to a value smaller than `options.createThreshold`.
Event.detail:
- **item** `Object`: reference to the original event item
- **coords** `Array`: the coordinates for the event item
## APIs
### addEvent(eventItem)
- **eventItem**
- Type: `Object`
- (return value)
- Type: `this`
Add new event item to the schedule.
`eventItem` MUST include these properties:
- **date** `Sting|Object`: a date string that can be parsed by `Date.parse` or a `Date` object
- **start** `String`: start time, in `HH:mm` format
- **end** `String`: end time, in `HH:mm` format
may include these properties:
- title `String`: event item's title
- content `String`: event item's content, **which will be rendered using `innerHTML`**
- style `Object`: additional styles you want to apply to the event item.
- className `String`: additional classnames you want to apply to the event item.
- group `String|Number`: event items with a same group key will be grouped together, while event items with different group keys will never be in a same column.
Besides all above which will take effect on display, you can put any properties you want in it, like an ID for the event or whatever. `eventItem` is stored in the instance untouched and is available in Event.detail.
### updateEvent(coords, modified)
- **coords**
- Type: `Array`
- coordinates
- **modified**
- Type: `Object`
- modified eventItem
- (return value)
- Type: `this`
Update the event item `instance.events[coords[0]][coords[1]]` to `modified`.
### deleteEvent(coords)
- **coords**
- Type: `Array`
- (return value)
- Type: `this`
Delete event item `instance.events[coords[0]][coords[1]]`
### clear(which)
- **which**
- Type: any
- clear event items on which date(s)
- (return value)
- Type: `this`
`which` can be:
- `Number`: Date index, value constrained in [0, options.num]
- `String`: Date string
- `Date`: Date object
- `Array`: array of values of any types above
- `undefined`: to clear all
### changeStartDate(date)
- **date**
- Type: `Object`
- Target start date
- (return value)
- Type: `this`
Change the start date to target date, will also alter `instance.events` accordingly.
### rerender()
- (return value)
- Type: `this`
Rerender all event items in the schedule.
### updateRect()
- (return value)
- Type: `this`
Update the container's size and position. You'll need to call this after any changes that affect the container's size and position, e.g. the container is switched from hidden to shown, a toolbar appears above the widget, etc.
### destroy()
Destroy the instance.
### getEvent(elem)
- **elem**
- Type: `Object`
- the event item element
- (return value)
- Type: `Object`
- a reference to the corresponding event item stored in `instance.events` or `null`.
### getElem(eventItem)
- **eventItem**
- Type: `Object`
- the event item
- (return value)
- Type: `Object`
- a reference to the corresponding event item element in the schedule or `null`
### events
- Type: `Array`
All event items.
## Browser Support
Not tested yet but supposed to and should work in all latest modern browsers.
## Contribute
If you find this widget useful and are willing to help, any issue or PR is welcomed.
## License
[MIT](https://opensource.org/licenses/MIT).