Build Status
jPack
jPack is a vanilla javascript library of components, classes, plugin wrappers, and utilities designed to make building custom websites and web applications simpler.
But why?
Many plugins and libraries are too generic, verbose, bulky, lacking, or have a lot of dependencies.
Much of what is included here is stuff that I've written several times, in several different ways, using jQuery in the past (or wrapping other jQuery plugins).
The goal of this library is to allow me (and you, now that it's open source) to integrate slim, mostly dependency-free, components as-needed with more specific use-cases than what is currently offered elsewhere.
...where else can you get a component to grab a form from another page and stick it on the current one with XHR and XHR submission in 4 lines of custom JS?
What's Included
Component | Demo | Data Type | What it does |
---|
navigation | | object | Grabs HTML from a URL and replaces content on the current page. Handles browser history, meta title swaps, and offers several callbacks |
XHRForm | | class | Adds an on-submit listener and sends the form values using XHR with callbacks for success/failure |
FormFromURL (extends XHRForm) | | class | Grabs a form from a URL and places it on the current page (examples/FormModalFromURL shows how to put the form in a modal) and then uses an XHR request to submit the form |
request | demo | object | Provides a wrapper for window.location and easy querystring interaction |
Site | demo | class | A generic website class with properties for id, name, and config - useful for multi-tenant applications where you need to know which site is being viewed |
User | demo | class | A generic user class with properties for id, name, email, phone, etc - also allows for front-end permission checks |
strings | demo | object | Contains methods for semi-common string manipulation like creating a getter from a string ('hi' = 'getHi') |
type_checks | demo | object | Validate the value of a variable with higher specificity than built-in functions. For instance, you can validate an object contains specific keys and throw errors if not, or if it contains keys that you didn't define |
dom | demo | object | Has methods for converting just about anything into a native DOM Element or array of them (you can provide a string selector, jQuery object, native DOM object, etc). Also has some shortcuts for common DOM checks/manipulation (like removing an element, verifying an element exists in the DOM, or replacing an element with HTML) |
events | demo | object | Includes methods for attaching event handlers including shorthand methods which create handlers that prevent the browser's default action (onclick, onsubmit) |
ToggleOnMobile | demo | class | Toggle an element's visibility when you click a button. By default, the element is visible, but if the button is visible, the element will be hidden until the button is clicked. If the element is visible and the user clicks outside of it, the element is hidden. If the window is resized, the element will be shown or hidden based on visibility of the button. |
Installation
Standard Global
Download the latest release, unzip and move it into your website's public folder, then include it in your HTML.
Use either jpack.min.js or jpack.bundled.min.js, BUT NOT BOTH. The bundled file includes the dependencies and you don't need them if you already have them in your project.
<script href="/@htmlguyllc/jpack/dist/jpack.bundled.min.js">
<script>
window.addEventListener('load', function() {
jpack.strings.ucfirst('bob');
jpack.setGlobal("$");
$.strings.ucfirst('bob');
jpack.setGlobal();
strings.ucfirst('bob');
};
</script>
With NPM or Yarn:
npm i @htmlguyllc/jpack;
//or
yarn add @htmlguyllc/jpack;
ES6 (Babel)
import {strings} from '@htmlguyllc/jpack/es/strings';
strings.ucfirst('bob');
import {strings, dom} from '@htmlguyllc/jpack';
strings.ucfirst('bob');
dom.exists('a.my-link');
import * as j from '@htmlguyllc/jpack';
j.strings.ucfirst('bob');
CommonJS (Browserify)
var jpack = require('@htmlguyllc/jpack');
jpack.strings.ucfirst('bob');
Dependencies
Documentation
Navigation
[back to top](#whatsincluded)
Grabs HTML from a URL and replaces content on the current page. Handles browser history, meta and page title swaps, and offers several callbacks
Properties | Default | Notes |
---|
pushState | true | enables pushing the new URL to the browser's history each time .load() is called |
loaderEnabled | true | enables a loader if the the .load() request takes too long |
trackHistory | false | stores the URL and route in an array each time .load() is called to track history |
Primary Methods | Params (name:type) | Return | Notes |
---|
setConfig | config:object | self | sets multiple configuration options at once (see examples below for more info) |
initHistoryHandlers | | self | sets event listeners to handle back/forward navigation in the user's browser (only use if pushState is true) |
setIncomingElement | el:string | self | a selector string for the element being retrieved from another page which contains the HTML you want put on the current page |
setReplaceElement | el:string | self | a selector string for the element on the current page you want the new HTML to replace |
load | url:string, data:object, onload:function/null, options:object{incomingElement:string, replaceElement:string, pushState:bool} | self | pulls content from the provided URL and puts it on the current page - also swaps out the page title, metas, and much more - all parameters passed to this method only pertain to this specific load. They do not persist on the navigation object. |
showLoader | | self | shows the loader after the delay |
hideLoader | | self | clears the loader timeout and hides it |
reload | callback:function | self | reloads the current page using .load() |
fullReload | | void | performs a full browser refresh of the current page |
redirect | url:string | void | redirects the user to a new page (no XHR request) |
showLoader | | self | sets a timeout (using the loaderDelay) with a callback to show the loader |
hideLoader | | self | cancels the timeout if it hasn't shown yet and hides the loader |
resetConfig | | self | resets config options back to their defaults |
Event Methods | Params (name:type) | Return | Callback Params (name:type) | Notes |
---|
onBeforeRequest | callback:function | self | el:Element, data:object, config:{ selector:string, replacedSelector:string, route:string/null} | adds a callback to run prior to the request, if the callback returns false, the request will not be made |
removeOnBeforeRequest | callback:function | self | | removes an onbeforerequest callback (must provide the original function) |
onload | callback:function | self | el:Element, data:object, config:{ selector:string, replacedSelector:string, route:string/null} | add an onload callback |
removeOnload | callback:function | self | | removes an onload callback (must provide the original function) |
onUnload | callback:function | self | el:Element, data:object, {selector:string, route:string/null} | add an unload callback |
removeOnunload | callback:function | self | | removes an unload callback (must provide the original function) |
onFail | callback:function | self | error:string, url:string, data:object, axios_error:object/null | add a callback when the load() request fails - receives 2 params (error:string, axios_error:object) |
removeOnFail | callback:function | self | | removes a failure callback (must provide the original function) |
Setters/Getters | Params (name:type) | Return | Notes |
---|
getConfig | | object | returns the config options as an object |
getIncomingElement | | string | |
getReplaceElement | | string | |
setData | data:object | self | sets a data object that is automatically passed to every .onload(), .onUnload(), and .onFail() callback |
getData | | object | |
setDataItem | key:string, val:mixed | self | sets a single data value |
getDataItem | key:string | self | returns null if it doesn't exist |
clearData | | self | clears all data |
setLoaderDelay | delay:int | self | set how long a request should take in ms before the loader displays |
getLoaderDelay | | self | |
getHistory | | array | returns an array of objects containing "url" and "route" starting with the first URL passed to .load() and ending with the latest |
getLastHistoryRecord | | object | returns an object containing "url" and "route" for the last page grabbed by .load() |
To use:
import {navigation} from '@htmlguyllc/jpack/es/navigation';
navigation.setConfig({
trackHistory:false,
pushState:true,
loaderEnabled:true,
loaderDelay:300,
incomingElementSelector:'body',
replaceElementSelector:'body',
loaderClasses:'progress page-navigation-loader',
loaderInnerDivClasses:'progress-bar progress-bar-striped progress-bar-animated',
});
navigation.initHistoryHandlers();
navigation.onBeforeRequest(function(el, data, config){
return true;
});
navigation.onload(function(el, data, config){
if( typeof gtag !== 'undefined' ) {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: request.getURIWithQueryString()
});
}
var last_history = navigation.getLastHistoryRecord();
if( last_history ){
const last_url = last_history.url;
const last_route = last_history.route;
}
window.scrollTo(0, 0);
});
navigation.onUnload(function(el, data, config){
});
navigation.onFail(function(error, url, data, axios_error){
});
let data = {pass_this:'hi!'};
navigation.load('/my-url', data, function(el, data, config){
const passed = data.pass_this;
});
import {events} from '@htmlguyllc/jpack/es/events';
events.onClick('[data-href]', function(){
navigation.load(this.href);
});
navigation.setData({
scrollToTop: true
});
navigation.setDataItem('scrollToTop', true);
navigation.load('/my-url', {}, function(el, data){
if( data.scrollToTop ){
window.scrollTo(0,0);
}
});
navigation.load('/my-url', {
scrollToTop: false
}, function(el, data){
if( data.scrollToTop ){
window.scrollTo(0,0);
}
});
XHRForm
[back to top](#whatsincluded)
Adds an on-submit listener and sends the form values using XHR with callbacks for success/failure. Automatically prevents the user from submitting the form several times at once. It must finish processing before it can be submitted again.
Primary Methods | Params (name:type) | Return | Notes |
---|
constructor | form:Element,options:object | self | |
attachSubmitHandler | form:mixed | self | attaches the event listener to on submit of the passed form |
validate | form:Element | bool | passes the form to the validate callback and returns the response |
submitForm | form:Element | self | gets URL and method, checks form validity using .validate(), gets values, submits, and kicks off callbacks |
Event Methods | Params (name:type) | Return | Callback Params (name:type) | Notes |
---|
onSuccess | callback:function | self | response:mixed, form:Element | adds an onSuccess callback (you can add as many as you'd like) |
clearOnSuccessCallbacks | | self | | |
onError | callback:function | self | error:string, response:mixed, form:Element | adds an onError callback (you can add as many as you'd like) |
clearOnErrorCallbacks | | self | | |
setPreSubmitCallback | callback:function | self | form:Element, form_values:string, url:string, method:string | pass a function you want to run right before the server request is sent - return false to prevent submission, return an object with form_values, url, or method if you want to override them |
setValidateCallback | callback:function | is_valid:bool | form:Element | pass a function to validate the form and return true if it's valid, false if it's not. False prevents form submission so you must display errors for the user within here. The default callback uses Bootstrap 4's "was-validated" class to show errors and HTML5's :invalid attribute to validate |
Setters/Getters | Params (name:type) | Return | Notes |
---|
setXHRSubmit | enabled:bool | self | enable/disable the XHR submission of the form |
setSubmitURL | url:mixed | self | pass null to use the form's action, function to dynamically generate the URL (receives the form as a param), or string |
getSubmitURL | | url:string | returns whatever was set in the constructor or using setSubmitURL, not the final URL |
getFinalSubmitURL | form:Element | url:string | returns the URL the form will be submitted to after running the function (if it is one) and using all fallbacks |
setSubmitMethod | method:string | self | override the form and provide a method (GET, POST, PATCH) |
getSubmitMethod | | method:string | |
getFormValues | form:Element | self | returns data from the form to be submitted - override this if you want to manipulate it first |
To use:
import {XHRForm} from '@htmlguyllc/jpack/es/forms';
var remote_form = new XHRForm('form[name="my_form"]', {
xhrSubmit: true,
submitURL:null,
submitMethod:null,
onPreSubmit: function(form, form_values, url, method){
return {form_values:form_values, url:url, method:method};
},
onError: function(error, response, form){ alert(error); },
onSuccess: function(response, form){
if(typeof response.success === "string"){ alert(response.success); }
else{ alert("Your submission has been received"); }
},
validateForm: function(form){
form.classList.add('was-validated');
const is_valid = !form.querySelector(':invalid');
if( is_valid ) form.classList.remove('was-validated');
return is_valid;
}
});
remote_form.attachSubmitHandler();
FormFromURL
FormFromURL extends [XHRForm](#xhrform)
[back to top](#whatsincluded)
Grabs a form from a URL and places it on the current page (examples/FormModalFromURL shows how to put the form in a modal) and then uses an XHR request to submit the form
Method/Property | Params (name:type) | Return | Notes |
---|
constructor | url:string, options:object | self | |
setURL | url:string | self | set the URL to pull the form from |
getURL | | url:string | |
setIncomingElementSelector | selector:string | self | set a selector for the form element or it's parent that is returned by the URL |
getIncomingElementSelector | | selector:string | |
setInsertIntoElement | element:mixed | self | set the element that the form should be inserted into |
getInsertIntoElement | | element:mixed | |
getForm | | void | pulls the form from the URL and runs the insertForm method |
insertForm | parsed_content:object, response:mixed, form:Element/null | el:Element | inserts the form into the parent element, attaches the submit handler, triggers onload, and returns the parent element |
onload | callback:function | self | adds a callback function to be run when the form is loaded on the page |
clearOnloadCallbacks | | self | removes all onload callbacks |
There are several methods and properties inherited from XHRForm that are not listed here.
See XHRForm above for those details
To use:
import {FormFromURL} from '@htmlguyllc/jpack/es/forms';
var remote_form = new FormFromURL('/my-form', {
incomingElementSelector: null,
insertIntoElement: null,
onload: function(form){ return this; },
xhrSubmit: true,
submitURL:null,
submitMethod:null,
onPreSubmit: function(form, form_values, url, method){
return {form_values:form_values, url:url, method:method};
},
onError: function(error, response, form){ alert(error); },
onSuccess: function(response, form){
if(typeof response.success === "string"){ alert(response.success); }
else{ alert("Your submission has been received"); }
},
validateForm: function(form){
form.classList.add('was-validated');
const is_valid = !form.querySelector(':invalid');
if( is_valid ) form.classList.remove('was-validated');
return is_valid;
}
});
remote_form.getForm();
How to get and submit a form in 4 lines of javascript:
- Success/failure messages will be shown in an alert
- HTML5/browser validation is done on the required field prior to submit
Your javascript
import {FormFromURL} from '@htmlguyllc/jpack/es/forms';
new FormFromURL('/email-form', {
insertIntoElement: 'body.form-container',
}).getForm();
Your HTML
<div class="form-container"></div>
Server response when retrieving /email-form (or, just the HTML without the JSON wrapper)
{
"html": "<form><input name='email' required='required'><input type='submit' value='Submit'></form>"
}
Server response when submitting to /email-form (success)
{ "success": "Thank you for submitting, your email has been provided to spammers everywhere" }
Server response when submitting to /email-form (error)
{ "error": "Your email is required/Your email is invalid" }
Extending:
FormFromURL extends XHRForm and either can be extended as you need.
See examples/FormModalFromURL for an example
Request
[demo](https://jsfiddle.net/HTMLGuyLLC/73b2kotL/) | [back to top](#whatsincluded)
Provides a wrapper for window.location and easy querystring interaction
Method/Property | Params (name:type) | Return | Notes |
---|
query | n/a | URLSearchParams | see https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams#Methods |
isHttps | | bool | |
getDomain | | string | |
getDomainWithProtocol | | string | |
getURI | | string | also known as the path - does not include querystring |
getURIWithQueryString | | string | full URL after the domain |
getFullURL | | string | |
appendSlash | string:string | string | adds a slash (if there isn't already one) to the end of a string. |
To use:
import {request} from '@htmlguyllc/jpack/es/request';
var product_id = request.query.get('product_id');
var current_full_url = request.getFullURL();
var full_blog_url = request.appendSlash(request.getDomainWithProtocol())+'blog';
Site
[demo](https://jsfiddle.net/HTMLGuyLLC/L6brcvo3/) | [back to top](#whatsincluded)
A generic website class with properties for id, name, and config - useful for multi-tenant applications where you need to know which site is being viewed
Method/Property | Params (name:type) | Return | Notes |
---|
getId | | string/int | |
setId | id:string/int | this | |
getName | | string | this |
setName | name:string | this | |
getConfig | | object | this |
setConfig | config:object | this | overwrites all config |
getConfigItem | | mixed | returns an individual item from config |
setConfigItem | key:string,val:mixed | this | sets the value of an individual item in config |
populate | data:object | this | sets provided values all at once (id,name,config) |
Instantiation with current site data:
The easy way:
Create an object named $site with values from your server (prior to including jpack)
<?php
$user = [];
?>
<script>
const $user = {
id: <?php echo $user['id']; ?>,
fname: "<?php echo $user['fname']; ?>",
permissions: JSON.parse("<?php echo json_encode($user['permissions']); ?>"),
additionalData: JSON.parse("<?php echo json_encode([
'user_type'=>$user['user_type'],
//whatever else you might want to pass
]); ?>"),
};
</script>
Then instantiate the Site class in your JS file somewhere
const cur_site = new Site($site);
cur_site.getId();
@see: /examples/CurrentSiteSingleton for a method of instantiating the current site once and using everywhere
Note: Even though that creates a singleton which is available anywhere, it's still highly recommended that you pass that object to functions and methods where it is used (dependency injection)
The harder way:
Perform an XHR request to grab site details via a JSON API, then run the populate method on the site object.
import {Site} from '@htmlguyllc/jpack/es/site';
$.get('/my-site-info-endpoint.php', function(data){
const cur_site = new Site(JSON.parse(data));
});
Of course you can use this class for any site, not just the current one, but this is the intended usage.
User
[demo](https://jsfiddle.net/HTMLGuyLLC/Lzp5w3rg) | [back to top](#whatsincluded)
A generic user class with properties for id, name, email, phone, etc - also allows for front-end permission checks
Method/Property | Params | Return | Notes |
---|
constructor | data:object | self | |
getId | | string/int | |
setId | id:string/int | this | |
getIsGuest | | bool | if your site has users who don't login but still interact and have a user record (like guest checkout) |
setIsGuest | is_guest:bool | this | |
setIsAdmin | | bool | if your site has users who have ultimate permissions and you want to do something based on that |
setIsGuest | is_admin:bool | this | |
getUsername | | string | |
setUsername | username:string | this | |
getFname | | string | |
setFname | fname:string | this | |
getLname | | string | |
setLname | lname:string | this | |
getName | | string | returns fname and lname concatenated with a space |
getEmail | | string | |
setEmail | email:string | this | |
getPhone | | string | |
setPhone | phone:string | this | |
getPermissions | | array | |
setPermissions | permissions:array | this | |
addPermission | permission:string/int | this | |
removePermission | permission:string/int | this | |
hasPermission | permission:string/int | bool | |
getAdditionalData | | object | set any additional data about this user that doesn't fit the default getters and setters here (a better idea would be to extend this object with your custom properties/methods) |
setAdditionalData | data:object | this | |
getDataItem | key:string | mixed | returns a single value from the additional data object/array |
setDataItem | key:string, val:mixed | sets a single value in the additional data array/object | |
populate | data:object | this | sets provided values all at once (id, isGuest, isAdmin, etc) |
Instantiation with current user data:
The easy way:
Create an object named $user with values from your server
<script>
const $user = {
id: <?php echo $user['id']; ?>,
fname: "<?php echo $user['fname']; ?>",
//..
permissions: JSON.parse("<?php echo json_encode($user['permissions']); ?>"),
additionalData: JSON.parse("<?php echo json_encode([
'user_type'=>$user['user_type'],
//whatever else you might want to pass
]); ?>"),
//..
};
</script>
Then instantiate the User class in your JS file somewhere
const cur_user = new User($user);
cur_user.getId();
@see: /examples/CurrentUserSingleton for a method of instantiating the current user once and using everywhere
Note: Even though that creates a singleton which is available anywhere, it's still highly recommended that you pass that object to functions and methods where it is used (dependency injection)
The harder way:
Perform an XHR request to grab site details via a JSON API, then run the populate method on the site object.
import {User} from '@htmlguyllc/jpack/es/user';
$.get('/my-user-info-endpoint.php', function(data){
const cur_user = new User(JSON.parse(data));
});
Of course you can use this class for any User not just the current one, but that's the intended usage.
Strings
[demo](https://jsfiddle.net/HTMLGuyLLC/ebof3hm4/) | [back to top](#whatsincluded)
Contains methods for semi-common string manipulation like creating a getter from a string ('hi' = 'getHi')
Method/Property | Params (name:type) | Return | Notes |
---|
ucfirst | string:string | string | capitalizes the first letter of a string like ucfirst in PHP |
getter | string:string | string | creates a getter method name from a string |
setter | string:string | string | creates a setter method name from a string |
To Use:
import {strings} from '@htmlguyllc/jpack/es/strings';
strings.ucfirst('bob');
strings.getter('name');
strings.setter('name');
DOM
[demo](https://jsfiddle.net/HTMLGuyLLC/et42sLbm/) | [back to top](#whatsincluded)
Has methods for converting just about anything into a native DOM Element or array of them (you can provide a string selector, jQuery object, native DOM object, etc). Also has some shortcuts for common DOM checks/manipulation (like removing an element, verifying an element exists in the DOM, or replacing an element with HTML)
Method/Property | Params (name:type) | Return | Notes |
---|
getElement | el:mixed, error_on_none:bool, error_on_multiple:bool | Element/HTMLDocument/null | returns a native DOM element for whatever you provide (selector string, array of elements, single element, jQuery wrapped DOM element, etc) |
getElements | el:mixed, error_on_none:bool | array | same as getElement, except it returns all matches |
remove | el:mixed, error_if_not_found:bool | this | removes elements from the DOM - uses .getElements() |
replaceElWithHTML | el:mixed, html:string, error_if_not_found:bool | Element | replaces an element in the DOM with HTML and returns a reference to the new Element |
exists | el:mixed | bool | checks to see if it exists in the DOM |
multipleExist | el:mixed | bool | checks to see if more than 1 instance exists in the DOM |
isVisible | el:mixed, error_if_not_found:bool, error_on_multiple:bool | bool | checks to see if the provided element is visible |
To Use:
import {dom} from '@htmlguyllc/jpack/es/dom';
dom.getElement('.my-fav-button', true, true);
dom.getElements('.links', true);
dom.getElement('.my-button');
dom.getElements('.links');
dom.getElement($('a'));
dom.getElement(document.querySelectorAll('a'));
dom.exists('a');
dom.multipleExist('a');
Type Checks
[demo](https://jsfiddle.net/HTMLGuyLLC/5p9q1ofj/) | [back to top](#whatsincluded)
Validate the value of a variable with higher specificity than built-in functions. For instance, you can validate an object contains specific keys and throw errors if not, or if it contains keys that you didn't define
Method/Property | Params (name:type) | Return | Notes |
---|
isDataObject | value:object, keys:array, require_all_keys:bool, block_other_keys:bool, throw_error:bool | bool | validates that an object contains data and not a dom element, array, null or anything else that would normally return true when you call typeof |
To Use:
import {type_checks} from '@htmlguyllc/jpack/es/type_checks';
var my_obj = {id:null, name:'John Doe', email:'john@doe.com'};
type_checks.isDataObject(my_obj, ['id', 'name', 'email'], true, true, true);
Events
[demo](https://jsfiddle.net/HTMLGuyLLC/wv2hkzp5/) | [back to top](#whatsincluded)
Includes methods for attaching event handlers including shorthand methods which create handlers that prevent the browser's default action (onclick, onsubmit)
Method/Property | Params (name:type) | Return | Notes |
---|
setGlobal | namespace:string/null | self | adds each of the following functions to the global scope, a namespace is optional, but recommended. Use at your own risk! These may cause conflicts! |
onClick | el:mixed, callback:function | handler:function | prevents the browser's default so you can handle link clicks and form submissions with less code - returns an updated handler in case you need to remove it later |
onSubmit | el:mixed, callback:function | handler:function | same as .onClick() but for submit - returns an updated handler in case you need to remove it later |
onEventPreventDefault | el:mixed, event:string, callback:function | callback:function | generates and attaches a handler which prevents the default action - returns the updated handler in case you need to remove it later |
on | el:mixed, event:string, callback:function | array | attaches an event listener |
off | el:mixed, event:string, callback:function | array | removes an event listener |
trigger | el:mixed, event:string, event_options:mixed | array | triggers an event on an element/elements - uses .getElements() |
To Use:
import {events} from '@htmlguyllc/jpack/es/events';
var preventedHandler = events.onClick('a.my-link', function(){
});
events.off('a.my-link', 'click', preventedHandler);
var handler = events.onSubmit('form.my-form', function(){
});
events.trigger('.my-form', 'submit', {id:1});
Setting with a custom namespace (or no namespace):
import {events} from '@htmlguyllc/jpack/es/events';
events.setGlobal();
onClick('a', function(){
});
ToggleOnMobile
[demo](https://jsfiddle.net/HTMLGuyLLC/68og394L/) | [back to top](#whatsincluded)
Toggle an element's visibility when you click a button. By default, the element is visible, but if the button is visible, the element will be hidden until the button is clicked. If the element is visible and the user clicks outside of it, the element is hidden. If the window is resized, the element will be shown or hidden based on visibility of the button.
Method/Property | Params (name:type) | Return | Notes |
---|
constructor | btn:mixed, toggle_el:mixed, toggle_class:string, hide_on_outside_click:bool | self | |
init | | self | attaches event handlers and immediately adjusts the visibility |
destroy | | self | removes event handlers - does not change the class |
To Use:
import {ToggleOnMobile} from '@htmlguyllc/jpack/es/toggle';
const toggle = new ToggleOnMobile('.toggle-sidebar-btn', '.sidebar', 'visible', true);
toggle.init();