Skip to content

Commit

Permalink
feat(events): dispatch show events (show and shown)
Browse files Browse the repository at this point in the history
  • Loading branch information
Johann-S committed Mar 21, 2019
1 parent eb7b41c commit 8b49845
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 51 deletions.
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Features:
- [Install](#install)
- [How to use it](#how-to-use-it)
- [Methods](#methods)
- [Events](#events)
- [Compatibility](#compatibility)
- [Support me](#support-me)
- [Thanks](#thanks)
Expand Down Expand Up @@ -158,7 +159,8 @@ Pass your `Stepper` DOMElement

### next

Will navigate to the next step of your stepper
Will navigate to the next step of your stepper. This method also emit `show.bs-stepper` before showing the step and
`shown.bs-stepper` when the step is displayed.

```js
var stepper = new Stepper(document.querySelector('.bs-stepper'))
Expand All @@ -167,11 +169,13 @@ stepper.next()

### previous

Will navigate to the previous step of your stepper
Will navigate to the previous step of your stepper. This method also emit `show.bs-stepper` before showing the step and
`shown.bs-stepper` when the step is displayed.

### to

Will navigate to a step of your stepper.
Will navigate to a step of your stepper. This method also emit `show.bs-stepper` before showing the step and
`shown.bs-stepper` when the step is displayed.

```js
var stepper = new Stepper(document.querySelector('.bs-stepper'))
Expand All @@ -182,12 +186,40 @@ stepper.to(2)

### reset

Will reset you stepper to the first step (usefull for linear stepper)
Will reset you stepper to the first step (usefull for linear stepper). This method also emit `show.bs-stepper` before showing the step and
`shown.bs-stepper` when the step is displayed.

### destroy

Remove stored data relative to your stepper and listeners.

## Events

The methods which trigger a step change trigger two events:
- `show.bs-stepper`
- `shown.bs-stepper`

You can listen on those events like that:

```js
var stepperEl = document.getElementById('stepper')
var stepper = new Stepper(stepperEl)

stepperEl.addEventListener('show.bs-stepper', function (event) {
// You can call prevent to stop the rendering of your step
// event.preventDefault()

// indexStep contains the id of the step which will be displayed
console.warn(event.detail.indexStep)
})

stepperEl.addEventListener('shown.bs-stepper', function (event) {
console.warn('step shown')
})
```

If you need to prevent the display of a step, you have to call `preventDefault` on the `show.bs-stepper` listener.

## Compatibility

bsStepper is compatible with:
Expand Down
25 changes: 11 additions & 14 deletions src/js/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { showContent, showStep, Selectors, ClassName, customProperty, detectAnimation } from './util'
import { show, Selectors, ClassName, customProperty, detectAnimation } from './util'
import { clickStepLinearListener, clickStepNonLinearListener } from './listeners'

const DEFAULT_OPTIONS = {
Expand Down Expand Up @@ -30,16 +30,15 @@ class Stepper {
}

detectAnimation(this._stepsContents, this.options.animation)
if (this._steps.length) {
showStep(this._steps[this._currentIndex], this._steps)
showContent(this._stepsContents[this._currentIndex], this._stepsContents)
}

this._setLinkListeners()
Object.defineProperty(this._element, customProperty, {
value: this,
writable: true
})

if (this._steps.length) {
show(this._element, this._currentIndex)
}
}

// Private
Expand All @@ -60,36 +59,34 @@ class Stepper {
next () {
this._currentIndex = (this._currentIndex + 1) <= this._steps.length - 1 ? this._currentIndex + 1 : (this._steps.length - 1)

showStep(this._steps[this._currentIndex], this._steps)
showContent(this._stepsContents[this._currentIndex], this._stepsContents)
show(this._element, this._currentIndex)
}

previous () {
this._currentIndex = (this._currentIndex - 1) >= 0 ? this._currentIndex - 1 : 0

showStep(this._steps[this._currentIndex], this._steps)
showContent(this._stepsContents[this._currentIndex], this._stepsContents)
show(this._element, this._currentIndex)
}

to (stepNumber) {
const tempIndex = stepNumber - 1

this._currentIndex = tempIndex >= 0 && tempIndex < this._steps.length
? tempIndex
: 0

showStep(this._steps[this._currentIndex], this._steps)
showContent(this._stepsContents[this._currentIndex], this._stepsContents)
show(this._element, this._currentIndex)
}

reset () {
this._currentIndex = 0
showStep(this._steps[this._currentIndex], this._steps)
showContent(this._stepsContents[this._currentIndex], this._stepsContents)
show(this._element, this._currentIndex)
}

destroy () {
this._steps.forEach(step => {
const trigger = step.querySelector(Selectors.TRIGGER)

if (this.options.linear) {
trigger.removeEventListener('click', clickStepLinearListener)
} else {
Expand Down
7 changes: 3 additions & 4 deletions src/js/listeners.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { closest } from './polyfill'
import { Selectors, customProperty, showStep, showContent } from './util'
import { Selectors, customProperty, show } from './util'

function clickStepLinearListener (event) {
event.preventDefault()
Expand All @@ -11,11 +11,10 @@ function clickStepNonLinearListener (event) {
const step = closest(event.target, Selectors.STEPS)
const stepperNode = closest(step, Selectors.STEPPER)
const stepper = stepperNode[customProperty]

const stepIndex = stepper._steps.indexOf(step)

stepper._currentIndex = stepIndex
showStep(step, stepper._steps)
showContent(stepper._stepsContents[stepIndex], stepper._stepsContents)
show(stepperNode, stepIndex)
}

export {
Expand Down
31 changes: 30 additions & 1 deletion src/js/polyfill.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
let matches = window.Element.prototype.matches
let closest = (element, selector) => element.closest(selector)
let WinEvent = (inType, params) => new window.Event(inType, params)
let createCustomEvent = (eventName, params) => {
const cEvent = new window.CustomEvent(eventName, params)

return cEvent
}

/* istanbul ignore next */
function polyfill () {
Expand Down Expand Up @@ -35,11 +40,35 @@ function polyfill () {
return e
}
}

if (typeof window.CustomEvent !== 'function') {
const originPreventDefault = window.Event.prototype.preventDefault

createCustomEvent = (eventName, params) => {
const evt = document.createEvent('CustomEvent')

params = params || { bubbles: false, cancelable: false, detail: null }
evt.initCustomEvent(eventName, params.bubbles, params.cancelable, params.detail)
evt.preventDefault = function () {
if (!this.cancelable) {
return
}

originPreventDefault.call(this)
Object.defineProperty(this, 'defaultPrevented', {
get: function () { return true }
})
}

return evt
}
}
}

polyfill()

export {
closest,
WinEvent
WinEvent,
createCustomEvent
}
60 changes: 43 additions & 17 deletions src/js/util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WinEvent, closest } from './polyfill'
import { WinEvent, createCustomEvent } from './polyfill'

const MILLISECONDS_MULTIPLIER = 1000
const Selectors = {
Expand All @@ -18,18 +18,44 @@ const ClassName = {
const transitionEndEvent = 'transitionend'
const customProperty = 'bsStepper'

const showStep = (step, stepList) => {
if (step.classList.contains(ClassName.ACTIVE)) {
const show = (stepperNode, indexStep) => {
const stepper = stepperNode[customProperty]

if (stepper._steps[indexStep].classList.contains(ClassName.ACTIVE) || stepper._stepsContents[indexStep].classList.contains(ClassName.ACTIVE)) {
return
}

const showEvent = createCustomEvent('show.bs-stepper', {
cancelable: true,
detail: {
indexStep
}
})
stepperNode.dispatchEvent(showEvent)

const activeStep = stepper._steps.filter(step => step.classList.contains(ClassName.ACTIVE))
const activeContent = stepper._stepsContents.filter(content => content.classList.contains(ClassName.ACTIVE))

if (showEvent.defaultPrevented) {
return
}

const stepperNode = closest(step, Selectors.STEPPER)
const activeStep = stepList.filter(step => step.classList.contains(ClassName.ACTIVE))
if (activeStep.length) {
activeStep[0].classList.remove(ClassName.ACTIVE)
}
if (activeContent.length) {
activeContent[0].classList.remove(ClassName.ACTIVE)
activeContent[0].classList.remove(ClassName.BLOCK)
}

showStep(stepperNode, stepper._steps[indexStep], stepper._steps)
showContent(stepperNode, stepper._stepsContents[indexStep], stepper._stepsContents, activeContent)
}

const showStep = (stepperNode, step, stepList) => {
stepList.forEach(step => {
const trigger = step.querySelector(Selectors.TRIGGER)

trigger.setAttribute('aria-selected', 'false')
// if stepper is in linear mode, set disabled attribute on the trigger
if (stepperNode.classList.contains(ClassName.LINEAR)) {
Expand All @@ -39,32 +65,32 @@ const showStep = (step, stepList) => {

step.classList.add(ClassName.ACTIVE)
const currentTrigger = step.querySelector(Selectors.TRIGGER)

currentTrigger.setAttribute('aria-selected', 'true')
// if stepper is in linear mode, remove disabled attribute on current
if (stepperNode.classList.contains(ClassName.LINEAR)) {
currentTrigger.removeAttribute('disabled')
}
}

const showContent = (content, contentList) => {
if (content.classList.contains(ClassName.ACTIVE)) {
return
}
const showContent = (stepperNode, content, contentList, activeContent) => {
const shownEvent = createCustomEvent('shown.bs-stepper', {
cancelable: true,
detail: {
indexStep: contentList.indexOf(content)
}
})

function complete () {
content.classList.add(ClassName.BLOCK)
content.removeEventListener(transitionEndEvent, complete)
}

const activeContent = contentList.filter(content => content.classList.contains(ClassName.ACTIVE))
if (activeContent.length) {
activeContent[0].classList.remove(ClassName.ACTIVE)
activeContent[0].classList.remove(ClassName.BLOCK)
stepperNode.dispatchEvent(shownEvent)
}

if (content.classList.contains(ClassName.FADE)) {
content.classList.remove(ClassName.NONE)
const duration = getTransitionDurationFromElement(content)

content.addEventListener(transitionEndEvent, complete)
if (activeContent.length) {
activeContent[0].classList.add(ClassName.NONE)
Expand All @@ -74,6 +100,7 @@ const showContent = (content, contentList) => {
emulateTransitionEnd(content, duration)
} else {
content.classList.add(ClassName.ACTIVE)
stepperNode.dispatchEvent(shownEvent)
}
}

Expand Down Expand Up @@ -126,8 +153,7 @@ const detectAnimation = (contentList, animation) => {
}

export {
showContent,
showStep,
show,
Selectors,
ClassName,
customProperty,
Expand Down
19 changes: 11 additions & 8 deletions tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,23 @@ <h2>Vertical stepper</h2>
</div>
<script src="dist/js/bs-stepper.js"></script>
<script>
var stepper1 = null
var stepper2 = null
var stepper3 = null
var stepper1Node = document.querySelector('#stepper1')
var stepper1 = new Stepper(document.querySelector('#stepper1'))

document.addEventListener('DOMContentLoaded', function () {
stepper1 = new Stepper(document.querySelector('#stepper1'))
stepper2 = new Stepper(document.querySelector('#stepper2'), {
stepper1Node.addEventListener('show.bs-stepper', function (event) {
console.warn('show.bs-stepper', event)
})
stepper1Node.addEventListener('shown.bs-stepper', function (event) {
console.warn('shown.bs-stepper', event)
})

var stepper2 = new Stepper(document.querySelector('#stepper2'), {
linear: false,
animation: true
})
stepper3 = new Stepper(document.querySelector('#stepper3'), {
var stepper3 = new Stepper(document.querySelector('#stepper3'), {
animation: true
})
})
</script>
</body>
</html>
Loading

0 comments on commit 8b49845

Please sign in to comment.