Created
August 6, 2020 08:33
-
-
Save SanjayBoricha/ad0a6a2b52fc94862d44b6ded5a39b6e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Collapsible - A plug and play plugin for expanding and | |
* collapsing elements (i.e. accordion) on a website. | |
*/ | |
let Collapsible; | |
(function() { | |
'use strict'; | |
/** | |
* Creates an instance of Collapsible. | |
* | |
* @constructor | |
* @param {Object} options | |
* @param {(HTMLElement|NodeList)} options.node The HTML elements that will be manipulated. | |
* @param {HTMLElement} [options.eventNode] The HTML element on which the eventListener will be attached. | |
* @param {Boolean} [options.isCollapsed] Assign the state of the node element. | |
* @param {Boolean} [options.observe] Assign a MutationObserver to observe child DOM changes. | |
* @param {Function} [options.expandCallback] Assign a callback for the [{@link Collapsible.prototype.expand} event. | |
* @param {Function} [options.collapseCallback] Assign a callback for the {@link Collapsible.prototype.collapse} event. | |
* @param {Function} [options.observeCallback] Assign a callback for the {@link Collapsible.prototype.initObserver} event. | |
*/ | |
Collapsible = function(options) { | |
// Initialize HTML nodes | |
if (NodeList.prototype.isPrototypeOf(options.node)) { | |
options.node.forEach(function(nodeItem) { | |
var singleNodeOptions = options; | |
singleNodeOptions.node = nodeItem; | |
new Collapsible(singleNodeOptions); | |
}); | |
return; | |
} else if (options.node instanceof HTMLElement) { | |
this.node = options.node; | |
this.eventNode = (options.eventNode ? this.node.querySelector(options.eventNode) : this.node); | |
this.isCollapsed = (typeof this.node.dataset.collapsibleCollapsed !== 'undefined' | |
? true | |
: null); | |
if (!this.isCollapsed) { | |
this.isCollapsed = ((options.isCollapsed | |
&& typeof options.isCollapsed === 'boolean') | |
? options.isCollapsed | |
: false); | |
} | |
this.observe = (typeof options.observe === 'boolean' ? options.observe : false); | |
this.expandCallback = (typeof options.expandCallback === 'function' ? options.expandCallback : null); | |
this.collapseCallback = (typeof options.collapseCallback === 'function' ? options.collapseCallback : null); | |
this.observeCallback = (typeof options.observeCallback === 'function' ? options.observeCallback : null); | |
this.mutationCallback = (typeof options.mutationCallback === 'function' ? options.mutationCallback : null); | |
this.init(); | |
} else { | |
console.error(options.node, 'is not a NodeList or an instance of HTMLElement'); | |
} | |
}; | |
/** | |
* Initialize the collapsing and expanding events. | |
*/ | |
Collapsible.prototype.init = function() { | |
this.updateHeights(); | |
if (this.isCollapsed) { | |
this.node.style.height = this.collapsedHeight + 'px'; | |
this.node.classList.add('is-collapsed'); | |
} else { | |
this.node.classList.add('is-expanded'); | |
} | |
this.eventNode.addEventListener('click', function() { | |
this.toggleCollapse(); | |
}.bind(this)); | |
window.addEventListener('resize', this.updateHeights.bind(this, null)); | |
// Observe children of the node | |
if (this.observe) { | |
this.initObserver(); | |
} | |
// Attach the prototype instance to the node | |
this.node.collapsible = this; | |
}; | |
/** | |
* Update the collapsed and expanded heights on page resize. | |
* | |
* @param {int} [heightDifference] Height value to add or subtract from the parent. | |
*/ | |
Collapsible.prototype.updateHeights = function(heightDifference) { | |
heightDifference = heightDifference || 0; | |
// Calculate the collapsed height | |
this.collapsedHeight = Collapsible.parseNumber( | |
window.getComputedStyle(this.eventNode)['height'] | |
); | |
// Calculate the expanded height | |
this.node.style.height = 'auto'; | |
this.expandedHeight = Collapsible.parseNumber( | |
window.getComputedStyle(this.node)['height'] | |
); | |
// Add or subtract the childNode's height difference | |
this.expandedHeight += heightDifference; | |
this.expandedHeight = Math.max(this.expandedHeight, this.collapsedHeight); | |
// Reset height to what it was before | |
if (this.isCollapsed) { | |
this.node.style.height = this.collapsedHeight + 'px'; | |
} | |
this.updateParentNode(this.expandedHeight - this.collapsedHeight); | |
}; | |
/** | |
* Toggle the node state and calls the appropriate function. | |
*/ | |
Collapsible.prototype.toggleCollapse = function() { | |
if (this.isCollapsed) { | |
this.expand(); | |
} else { | |
this.collapse(); | |
} | |
}; | |
/** | |
* Expand the node. | |
*/ | |
Collapsible.prototype.expand = function() { | |
this.updateHeights(); | |
void this.node.offsetWidth; | |
this.node.style.height = 'auto'; | |
this.node.style.height = this.expandedHeight + 'px'; | |
this.node.classList.remove('is-collapsed'); | |
this.node.classList.add('is-expanded'); | |
this.isCollapsed = false; | |
// Create a custom event | |
var expandEvent = new CustomEvent('toggle', { | |
bubbles: true, | |
detail: { | |
action: 'expand', | |
origin: this.eventNode | |
} | |
}); | |
this.node.dispatchEvent(expandEvent); | |
// Run callback if it exists | |
if (this.expandCallback) { | |
this.expandCallback.bind(this, expandEvent)(); | |
} | |
this.updateParentNode(this.expandedHeight - this.collapsedHeight); | |
}; | |
/** | |
* Collapse the node. | |
*/ | |
Collapsible.prototype.collapse = function() { | |
this.node.style.height = window.getComputedStyle(this.node)['height']; | |
void this.node.offsetWidth; | |
this.node.style.height = this.collapsedHeight + 'px'; | |
this.node.classList.remove('is-expanded'); | |
this.node.classList.add('is-collapsed'); | |
this.isCollapsed = true; | |
// Create a custom event | |
var collapseEvent = new CustomEvent('toggle', { | |
bubbles: true, | |
detail: { | |
action: 'collapse', | |
origin: this.eventNode | |
} | |
}); | |
this.node.dispatchEvent(collapseEvent); | |
// Run callback if it exists | |
if (this.collapseCallback) { | |
this.collapseCallback.bind(this, collapseEvent)(); | |
} | |
this.updateParentNode(-(this.expandedHeight - this.collapsedHeight)); | |
}; | |
/** | |
* Update parent heights if collapsible. | |
* | |
* @see {@link Collapsible.prototype.updateHeights} | |
*/ | |
Collapsible.prototype.updateParentNode = function(heightDifference) { | |
if (this.node.parentNode && this.node.parentNode.collapsible) { | |
this.node.parentNode.collapsible.updateHeights(heightDifference); | |
} | |
}; | |
/** | |
* Observe the direct children list of the node. | |
* Will adjust the expanded height automatically if necessary. | |
*/ | |
Collapsible.prototype.initObserver = function() { | |
this.mutationObserver = new window.MutationObserver(function(mutationsList) { | |
var mutatedNode, | |
mutationAction; | |
if (mutationsList[0]['addedNodes'].length > 0) { | |
mutationAction = 'add'; | |
mutatedNode = mutationsList[0]['addedNodes'][0]; | |
} else { | |
mutationAction = 'remove'; | |
mutatedNode = mutationsList[0]['removedNodes'][0]; | |
} | |
if (!this.isCollapsed) { | |
var mutatedNodeStyle = window.getComputedStyle(mutatedNode); | |
var mutatedNodeHeight = | |
Collapsible.parseNumber( | |
mutatedNodeStyle['height']) + | |
Collapsible.parseNumber( | |
mutatedNodeStyle['margin-top']) + | |
Collapsible.parseNumber( | |
mutatedNodeStyle['margin-bottom']); | |
this.node.style.height = 'auto'; | |
var currentHeight = Collapsible.parseNumber( | |
window.getComputedStyle(this.node)['height'] | |
); | |
if (mutationAction == 'add') { | |
this.node.style.height = (currentHeight - mutatedNodeHeight) + 'px'; | |
void this.node.offsetWidth; | |
this.node.style.height = currentHeight + 'px'; | |
} else { | |
this.node.style.height = this.expandedHeight + 'px'; | |
void this.node.offsetWidth; | |
this.node.style.height = currentHeight + 'px'; | |
} | |
} | |
this.expandedHeight = currentHeight; | |
// Create a custom event | |
var mutationEvent = new CustomEvent('mutate', { | |
bubbles: true, | |
detail: { | |
action: mutationAction, | |
node: mutatedNode | |
} | |
}); | |
this.node.dispatchEvent(mutationEvent); | |
// Run callback if it exists | |
if (this.mutationCallback) { | |
this.mutationCallback.bind(this, mutationEvent)(); | |
} | |
}.bind(this)); | |
this.mutationObserver.observe(this.node, { | |
childList: true | |
}); | |
}; | |
// Helper functions | |
Collapsible.parseNumber = function(numberString) { | |
return Number.parseInt(numberString.slice(0, -2)); | |
}; | |
// Expose the prototype function to the global scope | |
})(); | |
export default Collapsible; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Collapsible</title> | |
<style> | |
.collapse-container { | |
transition: height 400ms ease-in-out; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="collapse-container overflow-hidden"> | |
<div class="header select-none relative"> | |
Header | |
</div> | |
<div class="collapse overflow-hidden"> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium, quisquam. <br> | |
</div> | |
</div> | |
<script src="./main.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Collapsible from "./collapsible.js | |
new Collapsible({ | |
node: document.querySelectorAll('.collapse-container'), | |
eventNode: '.header', | |
isCollapsed: isCollapsed | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment