bookingsync-calendar-widget
Advanced tools
Comparing version
@@ -5,3 +5,3 @@ { | ||
{ | ||
"id": 5125, | ||
"id": 833, | ||
"type": "maps", | ||
@@ -8,0 +8,0 @@ "attributes": { |
{ | ||
"name": "bookingsync-calendar-widget", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "BookingSync Calendar Widget", | ||
@@ -16,3 +16,4 @@ "main": "dist/calendar-widget.js", | ||
"chai": "3.4.1", | ||
"widget-utils": "0.0.12" | ||
"widget-utils": "0.0.12", | ||
"tether-drop": "1.4.2" | ||
}, | ||
@@ -41,3 +42,4 @@ "devDependencies": { | ||
"webpack-dev-server": "2.1.0-beta.11", | ||
"yargs": "3.32.0" | ||
"yargs": "3.32.0", | ||
"tether": "1.4.0" | ||
}, | ||
@@ -44,0 +46,0 @@ "repository": { |
@@ -1,2 +0,2 @@ | ||
# Calendar Widget [jsbin demo](http://jsbin.com/tiqicahupu/edit?js,output) | ||
# Calendar Widget | ||
[](https://circleci.com/gh/BookingSync/calendar-widget) | ||
@@ -11,8 +11,5 @@ | ||
## Install Yarn (Recommended) | ||
* `npm install -g yarn` | ||
## Getting started with Development | ||
* `yarn` or `npm install` to get the project's dependencies. | ||
* `npm start` to start development server with live-reload and hot module replacement | ||
* `yarn` to get the project's dependencies. | ||
* `yarn start` to start development server with live-reload and hot module replacement | ||
* go to [http://localhost:8080/index.html](http://localhost:8080/index.html) | ||
@@ -28,44 +25,7 @@ | ||
## Usage | ||
Include `dist/calendar-widget.js`. | ||
Make sure there is a placeholder on page with minimum configuration: | ||
``` | ||
<div data-calendar-widget data-rental-ids="1"></div> | ||
``` | ||
All config support: | ||
``` | ||
rentalIds: 1 // rental to show availabilities | ||
startOfWeek: 0, // 0 Mo ... 6 Su, by ISO | ||
monthStart: // start with current month by default, or M '0...11' | ||
yearStart: // start with current year, or YYYY | ||
displayMonths: 2, | ||
lang: //'en', 'fr', 'de' | ||
selectable: false, | ||
showRates: false, | ||
showMinStay: false, | ||
``` | ||
``` | ||
<div data-calendar-widget | ||
data-rental-ids=1 | ||
data-lang="fr" | ||
data-show-rates=true | ||
data-min-stay=true | ||
data-display-months=6 | ||
data-start-of-week=0 > | ||
</div> | ||
``` | ||
Api host and namespace can be configurable | ||
`data-api-host`, `data-api-namespace`, `data-api-maps-route` | ||
defaults are: `https://www.bookingsync.com`, `/api/v2/public` and `/maps.json?rental_id={params}` | ||
place `{params}` where api expects params. | ||
## Config | ||
| __Option__ | __Description__ | __Type__ | __Default__ | | ||
| --- | --- | --- | --- | | ||
| `apiHost` | API host name | String | `http://localhost:3000` for development, `https://www.bookingsync.com` for production | | ||
| `apiNamespace` | API namespase | String | `/api/v2/public` | | ||
| `apiMapsRoute` | route for maps | String | `/maps.json?rental_id={params}` - `{params}` is replaced with `rentalId` | |
/* global document, module */ | ||
import { merge, ajax, isArray } from 'widget-utils'; | ||
import Calendar from './calendar'; | ||
import config from './config'; | ||
let initialized = []; | ||
const loadRental = (opts, rentalId, calendar) => { | ||
calendar.toggleLoading(); | ||
const onSuccess = (rental) => { | ||
calendar.toggleLoading(); | ||
if (isArray(rental.data) && rental.data[0].attributes) { | ||
calendar.addMaps(rental.data[0].attributes); | ||
} else { | ||
console.error('expects json-api data format'); | ||
} | ||
}; | ||
const onError = () => { | ||
calendar.toggleLoading(); | ||
console.error('Server error happened'); | ||
}; | ||
ajax(opts.rentalUrl(rentalId), onSuccess, onError); | ||
}; | ||
Calendar.init = () => { | ||
const calendarElements = document.querySelectorAll('[data-calendar-widget]'); | ||
Calendar.init = (opts) => { | ||
const initialized = []; | ||
const options = opts || {}; | ||
const calendarElements = document.querySelectorAll('[data-bookingsync-calendar-widget]'); | ||
const len = calendarElements.length; | ||
if (calendarElements.length === 0) { | ||
console.warn('expects at least one HTMLElement with "data-calendar-widget" and "data-rental-ids"'); | ||
} | ||
for (let i = 0; i < len; i += 1) { | ||
const el = calendarElements[i]; | ||
const opts = merge(config, el.dataset); | ||
opts.el = el; | ||
options.el = calendarElements[i]; | ||
const cal = new Calendar(options); | ||
if (el.dataset.rentalIds) { | ||
const cal = new Calendar(opts); | ||
loadRental(opts, el.dataset.rentalIds, cal); | ||
initialized.push(cal); | ||
} else { | ||
console.warn('data-rental-ids is mandatory'); | ||
if (options.el.dataset.rentalId) { | ||
cal.loadMaps(options.el.dataset.rentalId); | ||
} | ||
initialized.push(cal); | ||
} | ||
if (initialized.length === 1) { | ||
initialized = initialized[0]; | ||
} | ||
Calendar.instances = initialized; | ||
return initialized; | ||
}; | ||
@@ -62,2 +30,2 @@ | ||
module.exports = initialized; | ||
module.exports = Calendar; |
@@ -190,8 +190,8 @@ import { is, merge, monthLength, isString } from 'widget-utils'; | ||
return avail.reduce((curr, state, index, arr) => { | ||
const length = monthLength(year, month); | ||
const tree = curr; | ||
const minStay = minMap[index]; | ||
const rate = rates[index]; | ||
const isAvailable = state === 0; | ||
const prevAvailable = arr[index - 1] === 0; | ||
const length = monthLength(year, month); | ||
const tree = curr; | ||
const minStay = minMap[index]; | ||
const rate = rates[index]; | ||
const isAvailable = state === 0; | ||
const prevAvailable = arr[index - 1] === 0; | ||
const isMorningBlocked = (isAvailable && !prevAvailable); | ||
@@ -198,0 +198,0 @@ |
@@ -1,55 +0,37 @@ | ||
/* global VERSION, Node, document, require */ | ||
/* global VERSION, Node, NODE_ENV, document, require */ | ||
import { | ||
addClass, | ||
removeClass, | ||
isArray, | ||
isObject, | ||
Emitter, | ||
merge, | ||
elementFromString, | ||
traverseToParentWithAttr, | ||
destroyElement, | ||
monthLength, | ||
is, | ||
isFunction, | ||
isNumeric, | ||
traverseObj, | ||
addClass, removeClass, isArray, isObject, Emitter, | ||
merge, elementFromString, traverseToParentWithAttr, destroyElement, monthLength, is, isFunction, | ||
isNumeric, traverseObj, ajax, isInside, | ||
} from 'widget-utils'; | ||
import Drop from 'tether-drop'; | ||
import * as tpls from './templates'; | ||
import CalendarTree from './calendar-tree'; | ||
import config from './config'; | ||
import locales from './locales'; | ||
import { reset } from './styles/reset.scss'; | ||
import reset from './styles/Reset.scss'; | ||
import css from './styles/Calendar.scss'; | ||
import { | ||
calendar, chunky, highlighted, invalid, | ||
selected, actionsEnabled, body, tableHeader, caption, selectedStart, selectedEnd, | ||
reversed, direct, selectingReversed, selectingDirect, dropBasic, focus, | ||
} from './styles/calendar.scss'; | ||
const { documentElement: { lang } } = document; | ||
const formatDate = (format, year, month, day) => { | ||
function pad(number) { | ||
if (number < 10) { | ||
return `0${number}`; | ||
} | ||
return number; | ||
} | ||
const { calendar, chunky, highlighted, invalid, | ||
selected, actionsEnabled, body, tableHeader, caption, selectedStart, selectedEnd, | ||
selecting } = css; | ||
return format | ||
.replace('dd', pad(day)) | ||
.replace('mm', pad(month + 1)) | ||
.replace('yyyy', year); | ||
}; | ||
const { documentElement: { lang } } = document; | ||
const currDate = new Date(); | ||
const isLater = (start, end) => new Date(...start) < new Date(...end); | ||
// const getISO = (date, period) => { | ||
// function pad(number) { | ||
// if (number < 10) { | ||
// return `0${number}`; | ||
// } | ||
// return number; | ||
// } | ||
// | ||
// switch (period) { | ||
// case 'year': | ||
// return date.getUTCFullYear(); | ||
// case 'month': | ||
// return pad(date.getUTCMonth() + 1); | ||
// case 'date': | ||
// return pad(date.getUTCDate()); | ||
// default: | ||
// return date; | ||
// } | ||
// }; | ||
const dateToIso = (year, month, day) => { | ||
@@ -63,5 +45,7 @@ function pad(number) { | ||
return new Date(`${year}-${pad(month + 1)}-${day}`); | ||
return new Date(`${year}-${pad(month + 1)}-${pad(day)}`); | ||
}; | ||
const isLater = (start, end) => dateToIso(...start) < dateToIso(...end); | ||
const validationOfRange = (cell, index, range) => { | ||
@@ -74,16 +58,2 @@ if (index === range.length - 1) { | ||
const defaults = { | ||
startOfWeek: 0, // 0 Mo ... 6 Su, by ISO | ||
minRange: 1, // can select one night | ||
monthStart: currDate.getUTCMonth(), // start with current month by default M '0...12' | ||
yearStart: currDate.getUTCFullYear(), // start with current year YYYY | ||
daysPerWeek: 7, // FIXME support calendar rendering | ||
displayMonths: 2, | ||
selectable: false, | ||
showRates: false, | ||
showMinStay: false, | ||
reverseSelecting: false, // select end date first | ||
isBackDisabled: true, | ||
}; | ||
export default class Calendar extends Emitter { | ||
@@ -94,23 +64,37 @@ constructor(opts, maps) { | ||
this.VERSION = VERSION; | ||
this.opts = merge(defaults, opts); | ||
this.el = opts.el; | ||
this.dom = {}; | ||
this.opts = traverseObj(this.opts, a => a, (b) => { | ||
if (b === 'true' || b === 'false') { | ||
return (b === 'true'); | ||
if (isObject(opts)) { | ||
if (!opts.el) { | ||
console.error('el must be HTML element'); | ||
return; | ||
} | ||
if (isNumeric(b)) { | ||
return parseInt(b, 10); | ||
const configDataset = merge(config, opts.el.dataset); | ||
this.opts = merge(configDataset, opts); | ||
this.opts = traverseObj(this.opts, a => a, (b) => { | ||
if (b === 'true' || b === 'false') { | ||
return (b === 'true'); | ||
} | ||
if (isNumeric(b)) { | ||
return parseInt(b, 10); | ||
} | ||
return b; | ||
}); | ||
if (this.opts.isDropDown) { | ||
this.initCalendarDrop(); | ||
} else { | ||
this.el = opts.el; | ||
} | ||
return b; | ||
}); | ||
this.opts.lang = Calendar.widgetLang(this.opts.lang, lang); | ||
this.locale = locales[this.opts.lang || 'en']; | ||
this.opts.lang = Calendar.widgetLang(this.opts.lang, lang); | ||
this.locale = locales[this.opts.lang || 'en']; | ||
} | ||
this.dom = {}; | ||
this.cTree = new CalendarTree(validationOfRange, {}); | ||
if (isObject(maps)) { | ||
this.cTree.addMaps(maps, maps.start_date || currDate); | ||
this.cTree.addMaps(maps, maps.start_date || this.opts.currDate); | ||
} | ||
@@ -124,3 +108,3 @@ | ||
// user selects end date first | ||
this.reverseSelecting = this.opts.reverseSelecting; | ||
this.isReverseSelectable = this.opts.isReverseSelectable; | ||
this.init(); | ||
@@ -130,3 +114,3 @@ } | ||
init() { | ||
addClass(this.el, calendar, reset.reset); | ||
addClass(this.el, calendar, reset); | ||
@@ -146,2 +130,10 @@ if (this.opts.showRates || this.opts.showMinStay) { | ||
changeSelectionOrder(isReverse) { | ||
if ((isReverse !== this.isReverseSelectable) && !this.isSelecting) { | ||
this.isReverseSelectable = isReverse; | ||
this.destroyMonths(); | ||
this.renderMonths(this.yearStart, this.monthStart); | ||
} | ||
} | ||
renderMonths(yearStart, monthStart) { | ||
@@ -158,7 +150,12 @@ // construct dom tree | ||
this.monthEnd = monthEnd; | ||
this.yearEnd = yearEnd; | ||
this.monthEnd = monthEnd; | ||
this.yearEnd = yearEnd; | ||
this.recoverSelections(); | ||
if (this.opts.selectable) { | ||
addClass(this.el, this.isReverseSelectable ? reversed : direct); | ||
removeClass(this.el, this.isReverseSelectable ? direct : reversed); | ||
} | ||
this.dom.months = months; | ||
@@ -178,11 +175,15 @@ this.dom.months.forEach((m) => { | ||
if (this.selectionStart) { | ||
this.selectStart(...this.selectionStart, this.cTree.selectDay(...this.selectionStart)); | ||
this.selectStart(this.selectionStart, this.cTree.selectDay(...this.selectionStart)); | ||
} | ||
if (this.selectionEnd) { | ||
this.selectEnd(...this.selectionEnd, this.cTree.selectDay(...this.selectionEnd)); | ||
this.selectEnd(this.selectionEnd, this.cTree.selectDay(...this.selectionEnd)); | ||
} | ||
if (this.highlightedBounds.length > 0) { | ||
this.highLightRange(...this.highlightedBounds); | ||
if (this.selectionStart && this.selectionEnd) { | ||
if (this.highlightedBounds.length > 0) { | ||
this.highLightRange(...this.highlightedBounds); | ||
} else { | ||
this.highLightRange(this.selectionStart, this.selectionEnd); | ||
} | ||
} | ||
@@ -193,4 +194,8 @@ } | ||
if (this.opts.isBackDisabled) { | ||
const startDate = dateToIso(this.yearStart, this.monthStart, 1); | ||
const curr = dateToIso(currDate.getFullYear(), currDate.getMonth(), 1); | ||
const startDate = dateToIso( | ||
this.yearStart, this.monthStart, 1 | ||
); | ||
const curr = dateToIso( | ||
this.opts.currDate.getFullYear(), this.opts.currDate.getMonth(), 1 | ||
); | ||
this.dom.back.disabled = startDate <= curr; | ||
@@ -214,6 +219,8 @@ } | ||
this.destroyMonths(); | ||
this.renderMonths(this.opts.yearStart, this.opts.monthStart); | ||
this.renderMonths( | ||
this.opts.yearStart || this.yearStart, | ||
this.opts.monthStart || this.monthStart | ||
); | ||
} | ||
// FIXME add renderForward renderBackward methods | ||
addBtnsEvents() { | ||
@@ -243,14 +250,18 @@ this.dom.forward.addEventListener('click', (e) => { | ||
el.addEventListener('click', (e) => { | ||
const isEndFirst = this.reverseSelecting; | ||
const isEndFirst = this.isReverseSelectable; | ||
let value; | ||
let cell; | ||
if (this.isSelecting || isEndFirst) { | ||
({ value, parent: cell } = traverseToParentWithAttr(e.target, 'data-available-out')); | ||
if (this.isSelecting) { | ||
({ | ||
value, parent: cell, | ||
} = traverseToParentWithAttr(e.target, isEndFirst ? 'data-enabled' : 'data-available-out')); | ||
} else { | ||
({ value, parent: cell } = traverseToParentWithAttr(e.target, 'data-enabled')); | ||
({ | ||
value, parent: cell, | ||
} = traverseToParentWithAttr(e.target, isEndFirst ? 'data-available-out' : 'data-enabled')); | ||
} | ||
if (is(value) && cell) { | ||
const dateValue = [el.year, el.month, parseInt(cell.getAttribute('data-value'), 10)]; | ||
const dateValue = [el.year, el.month, parseInt(cell.getAttribute('data-value'), 10)]; | ||
@@ -267,2 +278,6 @@ // for simplicity just reset selection when user interacts again | ||
} | ||
if (this.opts.isDropDown && this.selectionEnd && this.selectionStart) { | ||
this.completeSelection(isEndFirst, dateValue, cell); | ||
} | ||
} | ||
@@ -275,8 +290,8 @@ }); | ||
if (is(value) && cell) { | ||
const current = [el.year, el.month, parseInt(cell.getAttribute('data-value'), 10)]; | ||
if (this.isSelecting) { | ||
const current = [el.year, el.month, parseInt(cell.getAttribute('data-value'), 10)]; | ||
this.removeHighlight(); | ||
if (this.reverseSelecting) { | ||
if (this.isReverseSelectable && this.selectionEnd) { | ||
this.highLightRange(current, this.selectionEnd); | ||
@@ -303,29 +318,41 @@ } else { | ||
endDateFirstAction(dateValue, cell) { | ||
if (this.isSelecting && isLater(dateValue, this.selectionEnd)) { | ||
startDateFirstAction(dateValue, cell) { | ||
if (this.isSelecting && isLater(this.selectionStart, dateValue)) { | ||
if (!this.hasValidRange) { | ||
return; | ||
} | ||
this.selectStartAction(dateValue, cell); | ||
removeClass(this.el, selecting); | ||
this.selectEndAction(dateValue, cell); | ||
const fn = () => { | ||
removeClass(this.el, selectingDirect); | ||
cell.removeEventListener('mouseout', fn); | ||
}; | ||
cell.addEventListener('mouseout', fn); | ||
this.isSelecting = false; | ||
} else { | ||
addClass(this.el, selecting); | ||
addClass(this.el, selectingDirect); | ||
this.isSelecting = true; | ||
this.selectEndAction(dateValue, cell); | ||
this.selectStartAction(dateValue, cell); | ||
} | ||
} | ||
startDateFirstAction(dateValue, cell) { | ||
if (this.isSelecting && isLater(this.selectionStart, dateValue)) { | ||
endDateFirstAction(dateValue, cell) { | ||
if (this.isSelecting && isLater(dateValue, this.selectionEnd)) { | ||
if (!this.hasValidRange) { | ||
return; | ||
} | ||
this.selectEndAction(dateValue, cell); | ||
removeClass(this.el, selecting); | ||
this.selectStartAction(dateValue, cell); | ||
const fn = () => { | ||
removeClass(this.el, selectingReversed); | ||
cell.removeEventListener('mouseout', fn); | ||
}; | ||
cell.addEventListener('mouseout', fn); | ||
this.isSelecting = false; | ||
} else { | ||
addClass(this.el, selecting); | ||
addClass(this.el, selectingReversed); | ||
this.isSelecting = true; | ||
this.selectStartAction(dateValue, cell); | ||
this.selectEndAction(dateValue, cell); | ||
} | ||
@@ -335,3 +362,4 @@ } | ||
selectStartAction(dateValue, cell) { | ||
this.selectStart(...dateValue, cell); | ||
this.selectStart(dateValue, cell); | ||
this.switchInputFocus('end'); | ||
this.emit('selection-start', dateToIso(...dateValue), dateValue); | ||
@@ -344,3 +372,4 @@ if (isFunction(this.opts.onSelectStart)) { | ||
selectEndAction(dateValue, cell) { | ||
this.selectEnd(...dateValue, cell); | ||
this.selectEnd(dateValue, cell); | ||
this.switchInputFocus('start'); | ||
this.emit('selection-end', dateToIso(...dateValue), dateValue); | ||
@@ -364,13 +393,16 @@ if (isFunction(this.opts.onSelectEnd)) { | ||
const { range, isValid } = this.selectRange(start, end); | ||
let hasValidRange = isValid; | ||
let hasValidRange = this.opts.rentalId ? isValid : true; | ||
const minStay = this.opts.rentalId ? this.cTree.getDayProperty(...start, 'minStay') : this.opts.minStay; | ||
if (isArray(range)) { | ||
// if selected range less than minimum stay at start | ||
if (range.length <= this.cTree.getDayProperty(...start, 'minStay')) { | ||
if (range.length <= minStay) { | ||
hasValidRange = false; | ||
} | ||
range.map(a => addClass(a, | ||
hasValidRange ? highlighted : invalid) | ||
); | ||
range.map((a) => { | ||
removeClass(a, highlighted, invalid); | ||
addClass(a, hasValidRange ? highlighted : invalid); | ||
return a; | ||
}); | ||
@@ -394,3 +426,3 @@ this.hasValidRange = hasValidRange; | ||
this.selectionStart = null; | ||
this.selectionEnd = null; | ||
this.selectionEnd = null; | ||
@@ -407,2 +439,7 @@ if (this.cellA) { | ||
if (this.opts.isDropDown && this.opts.elStartAt && this.opts.elEndAt) { | ||
this.opts.elStartAt.value = null; | ||
this.opts.elEndAt.value = null; | ||
} | ||
return this; | ||
@@ -415,4 +452,4 @@ } | ||
} | ||
return { | ||
range: null, | ||
return { | ||
range: null, | ||
isValid: false, | ||
@@ -422,4 +459,4 @@ }; | ||
selectStart(year, month, day, cell) { | ||
this.selectionStart = [year, month, day]; | ||
selectStart(dateValue, cell) { | ||
this.selectionStart = dateValue; | ||
@@ -434,6 +471,7 @@ if (this.cellA) { | ||
} | ||
this.valueToInput('start', dateValue); | ||
} | ||
selectEnd(year, month, day, cell) { | ||
this.selectionEnd = [year, month, day]; | ||
selectEnd(dateValue, cell) { | ||
this.selectionEnd = dateValue; | ||
@@ -448,2 +486,3 @@ if (this.cellB) { | ||
} | ||
this.valueToInput('end', dateValue); | ||
} | ||
@@ -501,6 +540,2 @@ | ||
/** | ||
* | ||
* @returns {string} | ||
*/ | ||
headerTplString() { | ||
@@ -517,8 +552,2 @@ // just to make life easier with start of the week calculation | ||
/** | ||
* | ||
* @param year | ||
* @param month | ||
* @returns {string} | ||
*/ | ||
daysTplString(year, month) { | ||
@@ -556,16 +585,28 @@ const startOfMonth = new Date(year, month, 1).getUTCDay(); | ||
if ((dayCounter >= weekShiftCorrected) && dayOfMonth <= daysInMonth) { | ||
const rate = this.opts.showRates ? this.cTree.getDayProperty(year, month, dayOfMonth, 'rate') : null; | ||
const minStay = this.opts.showMinStay ? this.cTree.getDayProperty(year, month, dayOfMonth, 'minStay') : null; | ||
let rate = this.opts.showRates ? this.cTree.getDayProperty(year, month, dayOfMonth, 'rate') : null; | ||
const minStay = this.opts.showMinStay ? this.cTree.getDayProperty(year, month, dayOfMonth, 'minStay') : null; | ||
let isDisabled = this.cTree.isDayDisabled(year, month, dayOfMonth); | ||
let isOutAvailable = this.cTree.getDayProperty(year, month, dayOfMonth, 'isOutAvailable'); | ||
let isDisabledStart = this.cTree.getDayProperty(year, month, dayOfMonth, 'isMorningBlocked'); | ||
let isOutAvailable = this.cTree.getDayProperty(year, month, dayOfMonth, 'isOutAvailable'); | ||
let isDisabledStart = this.cTree.getDayProperty(year, month, dayOfMonth, 'isMorningBlocked'); | ||
const cDate = this.opts.currDate; | ||
// if rate is float, then display 2 digits after point. | ||
rate = isNumeric(rate) && rate % 1 !== 0 ? rate.toFixed(2) : rate; | ||
// in the past any availability does not make sense | ||
if (isLater([year, month, dayOfMonth], currDate)) { | ||
isDisabled = true; | ||
if (isLater( | ||
[year, month, dayOfMonth], | ||
[cDate.getUTCFullYear(), cDate.getUTCMonth(), cDate.getDate()])) { | ||
isDisabled = true; | ||
isDisabledStart = undefined; | ||
isOutAvailable = undefined; | ||
isOutAvailable = undefined; | ||
} | ||
if (!this.opts.rentalId) { | ||
isDisabled = false; | ||
isOutAvailable = true; | ||
isDisabledStart = false; | ||
} | ||
week.push(tpls.weekDay( | ||
@@ -601,13 +642,123 @@ dayOfMonth, isDisabled, isDisabledStart, isOutAvailable, | ||
/** | ||
* | ||
* @param elLang {String} | ||
* @param documentLang {String} | ||
* @returns {String} | ||
*/ | ||
loadMaps(id) { | ||
this.toggleLoading(); | ||
const onSuccess = (rental) => { | ||
this.toggleLoading(); | ||
if (isArray(rental.data) && rental.data[0].attributes) { | ||
this.addMaps(rental.data[0].attributes); | ||
this.mapsLoaded = true; | ||
} else { | ||
console.error('expects json-api data format'); | ||
} | ||
}; | ||
const onError = () => { | ||
this.toggleLoading(); | ||
console.error('Server error happened'); | ||
}; | ||
if (NODE_ENV !== 'test') { | ||
ajax(this.opts.rentalUrl(id), onSuccess, onError); | ||
} | ||
} | ||
completeSelection() { | ||
this.emit('selection-completed', this.selectionStart, this.selectionEnd); | ||
this.closeDrop(null, true); | ||
} | ||
initCalendarDrop() { | ||
const element = elementFromString('<div></div>'); | ||
this.elTarget = this.opts.el; | ||
this.el = element; | ||
document.body.appendChild(element); | ||
const MyDrop = Drop.createContext({ | ||
classPrefix: 'BookingSyncCalendar__drop', | ||
}); | ||
const calDrop = new MyDrop({ | ||
content: element, | ||
target: this.elTarget, | ||
classes: dropBasic, | ||
openOn: null, | ||
targetAttachment: 'bottom left', | ||
constrainToWindow: false, | ||
constrainToScrollParent: false, | ||
}); | ||
const onFocus = (input, isReversed) => { | ||
this.switchInputFocus(input); | ||
this.changeSelectionOrder(isReversed); | ||
if (!calDrop.isOpened()) { | ||
calDrop.open(); | ||
if (!this.mapsLoaded) { | ||
this.loadMaps(this.opts.rentalId); | ||
} | ||
} | ||
}; | ||
this.opts.elStartAt.addEventListener('focus', () => { | ||
onFocus('start', false); | ||
}); | ||
this.opts.elEndAt.addEventListener('focus', () => { | ||
onFocus('end', true); | ||
}); | ||
if (this.opts.elReset) { | ||
this.opts.elReset.addEventListener('click', () => { | ||
this.resetSelection(); | ||
}); | ||
} | ||
document.addEventListener('click', this.closeDrop.bind(this)); | ||
this.calDrop = calDrop; | ||
} | ||
valueToInput(input, dateValue) { | ||
const format = this.opts.formatDate; | ||
const value = formatDate(format, ...dateValue); | ||
if (input === 'start' && this.opts.elStartAt) { | ||
this.opts.elStartAt.value = value; | ||
} else if (input === 'end' && this.opts.elEndAt) { | ||
this.opts.elEndAt.value = value; | ||
} | ||
} | ||
switchInputFocus(type) { | ||
if (this.opts.elStartAt && this.opts.elEndAt) { | ||
if (type === 'start') { | ||
addClass(this.opts.elStartAt, focus); | ||
removeClass(this.opts.elEndAt, focus); | ||
} | ||
if (type === 'end') { | ||
addClass(this.opts.elEndAt, focus); | ||
removeClass(this.opts.elStartAt, focus); | ||
} | ||
if (type === 'any') { | ||
removeClass(this.opts.elStartAt, focus); | ||
removeClass(this.opts.elEndAt, focus); | ||
} | ||
} | ||
} | ||
closeDrop(e, force) { | ||
if (!force && (isInside(e.target, this.el) || isInside(e.target, this.elTarget))) { | ||
e.stopPropagation(); | ||
} else { | ||
this.switchInputFocus('any'); | ||
this.calDrop.close(); | ||
} | ||
} | ||
static widgetLang(elLang, documentLang) { | ||
let langFallback = elLang || documentLang; | ||
langFallback = langFallback || 'en'; | ||
if (Object.keys(locales).indexOf(langFallback) === -1) { | ||
console.warn('this language is not supported yet'); | ||
console.warn('this language is not supported yet, locale set to English'); | ||
langFallback = 'en'; | ||
@@ -614,0 +765,0 @@ } |
@@ -1,6 +0,7 @@ | ||
/* global NODE_ENV */ | ||
/* global NODE_ENV, Date */ | ||
const currDate = new Date(); | ||
export default { | ||
name: 'Calendar Widget', | ||
apiHost: NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://www.bookingsync.com', | ||
name: 'Calendar Widget', | ||
apiHost: NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://www.bookingsync.com', | ||
apiNamespace: '/api/v2/public', | ||
@@ -11,5 +12,24 @@ apiMapsRoute: '/maps.json?rental_id={params}', | ||
const route = this.apiMapsRoute.replace('{params}', ids); | ||
return NODE_ENV === 'development' ? `${this.apiHost}/rental` : `${this.apiHost}${this.apiNamespace}${route}`; | ||
}, | ||
startOfWeek: 0, // 0 Mo ... 6 Su, by ISO | ||
minStay: 1, // can select one night | ||
monthStart: currDate.getUTCMonth(), // start with current month by default M '0...12' | ||
yearStart: currDate.getUTCFullYear(), // start with current year YYYY | ||
daysPerWeek: 7, // FIXME support calendar rendering | ||
displayMonths: 2, | ||
selectable: false, | ||
showRates: false, | ||
showMinStay: false, | ||
isReverseSelectable: false, // select end date first | ||
isBackDisabled: true, | ||
isDropDown: false, | ||
el: null, | ||
elStartAt: null, | ||
elEndAt: null, | ||
elReset: null, | ||
formatDate: 'dd/mm/yyyy', | ||
rentalId: null, | ||
currDate, | ||
}; |
@@ -1,2 +0,2 @@ | ||
import s from './styles/Calendar.scss'; | ||
import s from './styles/calendar.scss'; | ||
@@ -8,3 +8,3 @@ const { disabled, info, infoExtra, morningDisabled, nightDisabled, cnt } = s; | ||
export const month = `<div class="${s.mCell}"> | ||
<table class="${s.month}"> | ||
<table class="${s.month}" role="month"> | ||
<caption class="${s.caption}"></caption> | ||
@@ -24,3 +24,3 @@ <thead class="${s.tableHeader}"><tr></tr></thead> | ||
class="${s.cell} ${dis ? disabled : ''}${disStart ? morningDisabled : ''} ${dis && isOutAvailable ? nightDisabled : ''}"> | ||
<div class="${cnt}"> | ||
<div class="${cnt}" role="day-value"> | ||
${label} | ||
@@ -27,0 +27,0 @@ </div> |
@@ -6,3 +6,2 @@ /* global describe, module, it, before, after, document, xit */ | ||
chai.expect(); | ||
@@ -9,0 +8,0 @@ |
@@ -41,4 +41,4 @@ /* global require */ | ||
{ | ||
test: /\.scss$/, | ||
loader: 'style-loader?insertAt=top&singleton!css-loader?modules=true&localIdentName=BookingSync[Name]__[Local]!sass-loader?outputStyle=expanded&sourceMap&sourceMapContents', | ||
test: /(\.scss)$/, | ||
loader: 'style-loader?insertAt=top&singleton!css-loader?modules=true&localIdentName=BookingSyncCalendar__[Local]!sass-loader?outputStyle=expanded&sourceMap&sourceMapContents', | ||
exclude: /(node_modules|bower_components)/, | ||
@@ -45,0 +45,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
345117
18.9%27
8%1630
23.2%3
50%23
4.55%30
-57.14%+ Added
+ Added
+ Added