Skip to content

Commit

Permalink
feat(): add new methods to make the panel dynamic
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasGysemans committed Apr 28, 2021
1 parent 9afc46f commit b45c411
Show file tree
Hide file tree
Showing 6 changed files with 382 additions and 27 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## [1.2.0] - April 28, 2021

New methods:

+ toggleItem
+ removeItem
+ removeItems
+ getAllIDS
+ getItem
+ addItem
+ replaceItemWith
+ deletePanel
+ isOpen

New option:

+ id

## [1.1.3] - April 28, 2021

Bug fixes
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ The items have also specific options:
|name|type|default value|description|
|----|----|-------------|-----------|
|`title`|string|(_required_)|The title of the item.|
|`id`|positive number|from 0, incrementing|The id of the item. Used to recognize items via some methods like `removeItem()` etc.|
|`icon`|string|null|The path to an image.|
|`fontawesome_icon`|string|null|The className of a Fontawesome icon.|
|`fontawesome_color`|string|null|The color of the Fontawesome icon.|
Expand All @@ -81,6 +82,26 @@ The items have also specific options:

In order to use `fontawesome_icon` and `fontawesome_color`, make sure you've installed [Fontawesome](https://cdnjs.com/libraries/font-awesome) in your project.

## Make the panel dynamic

Use the following methods to modify the content of the panel after its creation:

* `toggleItem(id:number, disable=false)`: set disable to true if you want to disable the items. Set disable to false if you just want the item to disappear (display:none). Select the item with its ID (by default the first item has an ID of 0, then 1 etc.).

* `removeItem(id:number)`: removes an item.

* `removeItems(ids:number[])`: removes several items.

* `getAllIDS()`: gets the id of each item.

* `getItem(id:number)`: gets an item according to its ID.

* `addItem(item)`: adds a new item.

* `replaceItemWith(item, id:number)`: selects an item with its ID and replaces it by the new one.

* `deletePanel()`: deletes the panel.

## Customize the panel

You can change the style of the panel by modifying the CSS file. There are the main variables defined at the beginning of the file:
Expand Down
178 changes: 165 additions & 13 deletions src/JSPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class JSPanel {
/**
* @constructs JSPanel
* @param {HTMLButtonElement} button The button which will display the panel.
* @param {{top?:number,right?:number,bottom?:number,left?:number,items:Array<{title:string,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}>}} options The options to customize the panel.
* @param {{top?:number,right?:number,bottom?:number,left?:number,items:Array<{title:string,id?:number,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}>}} options The options to customize the panel.
*/
constructor(button, options) {
/**
Expand Down Expand Up @@ -56,8 +56,11 @@ class JSPanel {
//
if (this.options.items) {
const container = this._createEl("div", { className: "container-items" });
for (let item of this.options.items) {
for (let i = 0; i < this.options.items.length; i++) {
const item = this.options.items[i];
if (item) {
if (!item.id)
item.id = i;
const built_item = this._buildItem(item);
container.appendChild(built_item);
}
Expand All @@ -73,7 +76,7 @@ class JSPanel {
document.addEventListener("click", (e) => {
const target = e.target;
if (target && this.panel) {
if (!this.panel.contains(target) && this._isOpen()) {
if (!this.panel.contains(target) && this.isOpen()) {
this._closePanel();
}
}
Expand All @@ -83,7 +86,7 @@ class JSPanel {
this._insertAfterButton(this.panel);
this.panel.onkeydown = (e) => {
if (e.key === "Tab" || e.keyCode === 9) {
if (this._isOpen())
if (this.isOpen())
this._focusInPanel(e);
}
};
Expand All @@ -93,7 +96,7 @@ class JSPanel {
// So that it's easier for the user to close the panel with his/her keyboard.
this.button.onkeydown = (e) => {
if (e.key === "Tab" || e.keyCode === 9) {
if (this._isOpen()) {
if (this.isOpen()) {
e.preventDefault();
const active_elements = this._getAllActiveItems();
if (active_elements && active_elements[0]) {
Expand Down Expand Up @@ -124,9 +127,9 @@ class JSPanel {
/**
* Checks if the panel is currently opened or not.
* @returns {boolean} True if the panel is opened.
* @private
* @public
*/
_isOpen() {
isOpen() {
if (this.panel) {
return !this.panel.classList.contains("panel-hidden");
}
Expand All @@ -142,7 +145,7 @@ class JSPanel {
_togglePanel(e) {
if (this.button && this.panel) {
e.stopPropagation();
if (this._isOpen()) {
if (this.isOpen()) {
this._closePanel();
}
else {
Expand All @@ -157,9 +160,10 @@ class JSPanel {
/**
* Gets all the items from the panel if it's open.
* @returns {NodeListOf<HTMLButtonElement>|null} All the items.
* @private
*/
_getAllItems() {
if (this._isOpen()) {
if (this.isOpen()) {
return this.panel.querySelectorAll("button");
}
else {
Expand All @@ -169,12 +173,13 @@ class JSPanel {
/**
* Gets all the active items from the panel if it's open.
* @returns {Array<HTMLElement>|null} All the items that have an onclick property.
* @private
*/
_getAllActiveItems() {
if (this._isOpen()) {
if (this.isOpen()) {
const active_elements = Array.from(this.panel.querySelectorAll("button"));
active_elements.push(this.button);
return active_elements;
return active_elements.filter((e) => e.style.display !== "none" && !e.hasAttribute("disabled"));
}
else {
return null;
Expand Down Expand Up @@ -229,17 +234,19 @@ class JSPanel {
}
/**
* Builds an item.
* @param {{title:string,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}} item The item to build.
* @param {{title:string,id?:number,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}} item The item to build.
* @returns {HTMLElement} The item as an HTML element.
* @private
*/
_buildItem(item) {
const id = item.id.toString();
if (item.separator) {
const div = this._createEl("div", { className: 'jspanel-separator' });
const div = this._createEl("div", { className: 'jspanel-separator', attributes: [["data-id", id]] });
return div;
}
else {
const button = this._createEl("button");
button.setAttribute("data-id", id);
button.setAttribute("aria-label", item.title);
if ((item.icon && !item.fontawesome_icon) || (item.icon && item.fontawesome_icon)) {
const icon = this._createEl("img", { attributes: [["src", item.icon]] });
Expand Down Expand Up @@ -277,6 +284,7 @@ class JSPanel {
/**
* Blocks the focus inside the panel while it's open.
* @param {KeyboardEvent} e The keyboard event.
* @private
*/
_focusInPanel(e) {
const all_items = this._getAllActiveItems();
Expand Down Expand Up @@ -341,4 +349,148 @@ class JSPanel {
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
/**
* Toggles an item.
* @param {number} id The id of the item.
* @param {boolean} disable If the item is a button (not a separator), then, instead of display:none, we disable it. By default: false.
* @public
* @since 1.2.0
*/
toggleItem(id, disable = false) {
if (this.panel) {
const items = Array.from(this.panel.querySelectorAll("[data-id='" + id + "']"));
if (disable) {
if (items)
for (let item of items) {
if (item.tagName.toLowerCase() === "button") {
item.hasAttribute("disabled") ? item.removeAttribute("disabled") : item.setAttribute("disabled", "disabled");
}
else {
if (items)
for (let item of items)
item.style.display = item.style.display == "none" ? null : "none";
}
}
}
else {
if (items)
for (let item of items)
item.style.display = item.style.display == "none" ? null : "none";
}
}
}
/**
* Removes an item.
* @param {number} id The id of the item to remove.
* @public
* @since 1.2.0
*/
removeItem(id) {
if (this.panel) {
const item = this.getItem(id);
if (item && item.parentElement) {
item.parentElement.removeChild(item);
}
}
}
/**
* Removes several items.
* @param {Array<number>} ids The ids of the items.
* @public
* @since 1.2.0
*/
removeItems(ids) {
if (this.panel) {
for (let id of ids) {
this.removeItem(id);
}
}
}
/**
* Gets the id of each item.
* @returns {Array<number>} The list of ids.
* @public
* @since 1.2.0
*/
getAllIDS() {
if (this.panel) {
const all_items = Array.from(this.panel.querySelectorAll("[data-id]"));
const all_ids = [];
if (all_items) {
for (let item of all_items) {
all_ids.push(parseInt(item.getAttribute("data-id")));
}
return all_ids;
}
}
return [];
}
/**
* Gets an item.
* @param id The id of the item.
* @returns {HTMLElement|null} The item.
* @public
* @since 1.2.0
*/
getItem(id) {
if (this.panel) {
return this.panel.querySelector("[data-id='" + id + "']");
}
return null;
}
/**
* Builds a new item to be added to the panel after its creation.
* @param {{title:string,id?:number,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}} new_item The new item to be built.
* @param default_new_id The ID of the item, just in case the user did not specify any.
* @returns The HTML element of an item.
* @private
* @since 1.2.0
*/
_buildNewItem(new_item, default_new_id) {
if (!new_item.id && (default_new_id === null || default_new_id === undefined))
throw new Error("An item must have an ID.");
if (!new_item.id)
new_item.id = default_new_id;
const build_item = this._buildItem(new_item);
return build_item;
}
/**
* Adds an item
* @param {{title:string,id?:number,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}} new_item The new item to be built.
* @public
* @since 1.2.0
*/
addItem(new_item) {
if (this.panel) {
this.panel.appendChild(this._buildNewItem(new_item, Math.max(...this.getAllIDS())));
}
}
/**
* Replaces an item by another one.
* @param {{title:string,id?:number,icon?:string,fontawesome_icon?:string,fontawesome_color?:string,className?:string,attributes?:Array<Array<string>>,onclick?:Function,separator?:boolean}} new_item The new item.
* @param {number} id The id of the item to be replaced.
* @public
* @since 1.2.0
*/
replaceItemWith(new_item, id) {
if (this.panel) {
const current_item = this.getItem(id);
if (current_item) {
const new_built_item = this._buildNewItem(new_item, parseInt(current_item.getAttribute("data-id")));
current_item.replaceWith(new_built_item);
}
}
}
/**
* Deletes the panel.
* @public
* @since 1.2.0
*/
deletePanel() {
if (this.panel && this.panel.parentElement) {
this.panel.parentElement.removeChild(this.panel);
this._closePanel();
this.panel = null;
}
}
}
Loading

0 comments on commit b45c411

Please sign in to comment.