//#region Element
/**
* Applies a material design ripple effect to the element specified. Works best with buttons and similar elements.
* This comes from my GitHub repo here: https://github.com/explosion-scratch/ripple
* @memberOf element
* @function
* @example
* _$.each(document.querySelectorAll("button"), _$.ripple)
* //Accepts attributes too!
* // data-time: The time in milliseconds that it takes the ripple to fade away
* // data-color: A CSS color that the ripple should have as it's color
* // data-opacity: The starting opacity of the ripple effect.
* // data-event: The event to listen for to apply the ripple.
* @param {HTMLElement} el The element to apply the ripple effect to.
* @param {Object} obj The object with (optional) time, color, opacity and event parameters for controlling the ripple effect. If these are not present the effect relies on data-* attributes, and then defaults and look good in general.
* @param {Number} [obj.time=1000] The time in milliseconds the ripple should take.
* @param {String} [obj.color="currentColor"] The color of the ripple effect.
* @param {Number} [obj.opacity=.3] The opacity of the ripple effect.
* @param {String} [obj.event="mousedown"] The event to listen for to trigger the ripple effect.
* @returns {HTMLElement} The HTML element that the ripple effect was applied to. (The same one passed in the first param).
*/
export let ripple = (
el = req("element", "element"),
{ time, color, opacity, event },
) => {
node();
time = time || (+el.getAttribute("data-time") || 1000) * 3;
color = color || el.getAttribute("data-color") || "currentColor";
opacity = opacity || el.getAttribute("data-opacity") || ".3";
event = event || el.getAttribute("data-event") || "mousedown";
el.style.overflow = "hidden";
el.style.position = "relative";
el.addEventListener(event, (e) => {
var ripple_div = document.createElement("DIV");
ripple_div.style.position = "absolute";
ripple_div.style.background = `${color}`;
ripple_div.style.borderRadius = "50%";
var bx = el.getBoundingClientRect();
var largestdemensions;
if (bx.width > bx.height) {
largestdemensions = bx.width * 3;
} else {
largestdemensions = bx.height * 3;
}
ripple_div.style.pointerEvents = "none";
ripple_div.style.height = `${largestdemensions}px`;
ripple_div.style.width = `${largestdemensions}px`;
ripple_div.style.transform = `translate(-50%, -50%) scale(0)`;
ripple_div.style.top = `${e.pageY - (bx.top + window.scrollY)}px`;
ripple_div.style.left = `${
e.pageX - (bx.left + window.scrollX)
}px`;
ripple_div.style.transition = `opacity ${time}ms ease, transform ${time}ms ease`;
ripple_div.removeAttribute("data-ripple");
ripple_div.style.opacity = opacity;
el.appendChild(ripple_div);
setTimeout(() => {
ripple_div.style.transform = `translate(-50%, -50%) scale(1)`;
ripple_div.style.opacity = "0";
setTimeout(() => {
ripple_div.remove();
}, time);
}, 1);
});
};
/**
* Waits for an element satisfying selector to exist, then resolves promise with the element.
* @param {HTMLElement} [parent=document.documentElement] The parent element to watch.
* @param {String} selector The querySelector to watch for.
* @returns {Promise} A promise resolved when the element exists.
* @memberOf element
* @function
* @example
* _$.elementReady("#text").then((e) => e.remove());//Wait for an element with an ID of "text" then removes it.
*/
export function elementReady(
selector = req("string", "query selector"),
parent = document.documentElement,
) {
node();
return new Promise((resolve, reject) => {
const el = parent.querySelector(selector);
if (el) {
resolve(el);
}
new MutationObserver((mutationRecords, observer) => {
// Query for elements matching the specified selector
Array.from(parent.querySelectorAll(selector)).forEach(
(element) => {
resolve(element);
//Once we have resolved we don't need the observer anymore.
observer.disconnect();
},
);
}).observe(parent, {
childList: true,
subtree: true,
});
});
}
/**
* Tests if an element is a child element of another element.
* @returns {Boolean} If the element is a child or not
* @memberOf element
* @function
* @example
* _$.elementContains(document.querySelector("#container"), document.querySelector("#img"));//If the element with an id of "img" is inside the #container element this will return true, otherwise it will return false
* @example
* //Note that the easiest way to do this would be to use _$.onOutsideClick(), but this is another way that demonstrates the use of this function.
* //Listen for a click outside of an element (in this case the div#popup element) then remove the popup element.
* document.querySelector("div#popup").addEventListener("click", (e) => {
* let contains = _$.elementContains(document.querySelector("div#popup"), e.target);
* if (!contains){
* document.querySelector("div#popup".remove()
* }
* })
* @param {HTMLElement} parent The parent element to test.
* @param {HTMLElement} child The child element to test.
*/
export let elementContains = (
parent = req("HTMLElement", "parent"),
child = req("HTMLElement", "child"),
) => {
node();
return parent !== child && parent.contains(child);
};
/**
* Gets the parent elements of the element given.
* @returns {Array.<HTMLElement>} An array of the parent elements from deepest to outermost.
* @memberOf element
* @function
* @example
* //Where the html is like so:
* ```
* <html>
* <head>
* </head>
* <body>
* <div id="img">
* <img src="https://example.com/example.png">
* </div>
* </body>
* </html>
* ```
* _$.parents(document.querySelector("img"));//[div#img, body, html]
* @param {HTMLElement} el The element
*/
export let parents = (el = req("element")) => {
node();
return [
...(function* (e) {
while ((e = e.parentNode)) {
yield e;
}
})(el),
];
};
/**
* Gets all the images that are children of the specified element.
* @returns {Array} The array of image urls.
* @memberOf element
* @function
* @example
* //Get all the images on the page and convert their url's to data urls then log that list to console.
* _$.getImages().forEach(image_url => {
* image_data_list.push(_$.imageToData(image_url))
* })
* console.log(image_data_list);
* @param {HTMLElement} [el=document.documentElement] The element to get images from (e.g. document.body)
* @param {Boolean} [includeDuplicates=false] Whether to include duplicate images, defaults to false.
*/
export let getImages = (
el = document.documentElement,
includeDuplicates = false,
) => {
node();
const images = [...el.getElementsByTagName("img")].map((img) =>
img.getAttribute("src"),
);
return includeDuplicates ? images : [...new Set(images)];
};
/**
* Renders an HTML element from an object in the container specified.
* @memberOf element
* @function
* @example
* //Renders a button in the document body.
* _$.renderElement({
type: 'button',
props: {
type: 'button',
className: 'btn',
onClick: () => alert('Clicked'),
children: [{ props: { nodeValue: 'Click me' } }]
}
}, document.body)
* @param {Object} param The type of object (the HTML tagName)
* @param {HTMLElement} container The html element to render it in.
* @returns {HTMLElement} The HTML element rendered.
*/
export let renderElement = (
{ type, props = {} } = req("object", "options"),
container = req("HTMLElement", "container"),
) => {
node();
const isTextElement = !type;
const element = isTextElement
? document.createTextNode("")
: document.createElement(type);
const isListener = (p) => p.startsWith("on");
const isAttribute = (p) => !isListener(p) && p !== "children";
Object.keys(props).forEach((p) => {
if (isAttribute(p)) element[p] = props[p];
if (!isTextElement && isListener(p))
element.addEventListener(p.toLowerCase().slice(2), props[p]);
});
if (!isTextElement && props.children && props.children.length)
props.children.forEach((childElement) =>
renderElement(childElement, element),
);
container.appendChild(element);
return element;
};
/**
* Create a DOM element from a querySelector with option to include content
* @memberOf element
* @function
* @param {String} querySelector (optional) default to div
* @param {...String|Number|DOMElement} [content] (optional)
* @returns DOMElement
*
* @example
* - _$.create(); // <div>
* - _$.create('span#my-id.my-class.second-class'); // <span id="my-id" class="my-class second-class">
* - _$.create('#my-id.my-class.second-class', 'text to insert', 12345); // <div id="my-id" class="my-class second-class">
*/
export function create(querySelector = "div", ...content) {
node();
let nodeType = querySelector.match(/^[a-z0-9]+/i);
let id = querySelector.match(/#([a-z]+[a-z0-9-]*)/gi);
let classes = querySelector.match(/\.([a-z]+[a-z0-9-]*)/gi);
let attributes = querySelector.match(
/\[([a-z][a-z-]+)(=['|"]?([^\]]*)['|"]?)?\]/gi,
);
let _node = nodeType ? nodeType[0] : "div";
if (id && id.length > 1) {
throw new Error("only 1 ID is allowed");
}
const elt = document.createElement(_node);
if (id) {
elt.id = id[0].replace("#", "");
}
if (classes) {
const attrClasses = classes.join(" ").replace(/\./g, "");
elt.setAttribute("class", attrClasses);
}
if (attributes) {
attributes.forEach((item) => {
item = item.slice(0, -1).slice(1);
let [label, value] = item.split("=");
if (value) {
value = value.replace(/^['"](.*)['"]$/, "$1");
}
elt.setAttribute(label, value || "");
});
}
content.forEach((item) => {
if (typeof item === "string" || typeof item === "number") {
elt.appendChild(document.createTextNode(item));
} else if (item.nodeType === document.ELEMENT_NODE) {
elt.appendChild(item);
}
});
return elt;
}
/**
* Re-enables the use of <menu> and <menuitem> tags for corner clicking.
* @memberOf element
* @function
* @example
* //HTML:
* ```
* <h1 contextmenu="menu">Corner click me</h1>
* <menu>
* <menuitem label="An item!">
* <menuitem label="Another item!">
* </menu>
* ```
* //JS
* _$.context();
* // Now the user can corner click the items that have parents with a "contextmenu" attribute! Try it out here: https://bcs88.csb.app/
* @returns {Array.<HTMLElement>} An array of all the HTML elements passed.
*/
export let context = () => {
node();
var menu = document.createElement("UL");
menu.id = "contextMenu";
document.body.appendChild(menu);
let styles = document.createElement("STYLE");
styles.innerHTML = `#contextMenu {
pointer-events: none;
padding: 0;
opacity: 0;
transition: opacity .3s ease;
position: fixed;
padding-top: 3px;
padding-bottom: 3px;
max-height: 200px;
overflow-y: scroll;
overflow-x: hidden;
list-style: none;
z-index: 10000;
background: white;
color: #333;
font-family: sans-serif;
border-radius: 5px;
box-shadow: 2px 2px 5px #0004;
width: fit-content;
min-width: 50px;
max-width: 150px;
}
#contextMenu li {
transition: background-color .3s ease;
display: block;
min-width: 150px;
margin: 0;
padding: 10px;
}
#contextMenu li:hover {
background-color: #ddd;
cursor: pointer;
}
`;
document.body.appendChild(styles);
var elements = document.querySelectorAll("[contextmenu]");
for (let i = 0; i < elements.length; i++) {
window.addEventListener("contextmenu", (e) => {
menu.style.pointerEvents = "auto";
let items;
try {
items = document.querySelectorAll(
`#${e.target
.closest("[contextmenu]")
.getAttribute("contextmenu")} menuitem`,
);
e.preventDefault();
} catch (e) {
return true;
}
menu.innerHTML = "";
for (let j = 0; j < items.length; j++) {
const contextMenu = items[j];
const liTag = document.createElement("li");
liTag.setAttribute(
"onclick",
contextMenu.getAttribute("onclick"),
);
liTag.addEventListener("click", () => {
menu.style.opacity = 0;
menu.style.pointerEvents = "none";
});
liTag.textContent = contextMenu.getAttribute("label");
menu.innerHTML += liTag.outerHTML;
}
console.log(menu.innerHTML);
menu.style.top = `${e.clientY}px`;
menu.style.left = `${e.clientX}px`;
menu.style.opacity = 1;
});
}
var contextTimer = 0;
setInterval(() => {
contextTimer += 100;
if (contextTimer > 3000) {
menu.style.opacity = 0;
menu.style.pointerEvents = "none";
contextTimer = 0;
return;
}
}, 100);
_$.addEventListeners(menu, ["mousemove", "click", "scroll"], () => {
contextTimer = 0;
});
_$.onOutsideClick(menu, () => {
menu.style.opacity = 0;
menu.style.pointerEvents = "none";
});
return elements;
};
/**
* Tests whether the specified element is fully in view.
* @function
* @memberOf element
* @param {Element} el The DOM element to test.
* @example
* // Alerts "In view!" if the first <div> in the document is in view.
* if (_$.inView(document.querySelector("div"))) alert("In view!");
* @returns {Boolean} Whether the element is completely in view.
*/
export let inView = (el = req("HTMLElement", "element")) => {
node();
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top >= window.pageYOffset &&
left >= window.pageXOffset &&
top + height <= window.pageYOffset + window.innerHeight &&
left + width <= window.pageXOffset + window.innerWidth
);
};
/**
* Tests if the given DOM element is partially (or fully) in view.
* @function
* @memberOf element
* @param {Element} el The element to test.
* @example
* // Alerts "In view!" if the first <div> in the document is partially or fully view.
* if (_$.inPartialView(document.querySelector("div"))) alert("In view!");
* @returns {Boolean} Whether the DOM element is partially in view.
*/
export let inPartialView = (el = req("HTMLElement", "element")) => {
node();
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < window.pageYOffset + window.innerHeight &&
left < window.pageXOffset + window.innerWidth &&
top + height > window.pageYOffset &&
left + width > window.pageXOffset
);
};
/**
* @callback replaceTextCallback
* @param {String} text The text to replace
* @returns {String} The replaced text
*/
/**
* Replaces the text in an element by running it through a callback.
* @function
* @memberOf element
* @param {Element} el The element to replace the text of.
* @param {replaceTextCallback} callback The callback to run (Gets passed the element's text).
* @example
* _$.replaceText(document.querySelector("div"), (text) => text.toUpperCase());
* // Converts the text of the first <div> element to upperCase.
* @returns {HTMLElement} The HTML element passed.
*/
export let replaceText = (
el = req("HTMLElement", "element"),
callback = req("function", "callback"),
) => {
node();
_$.each(_$.textNodes(el), (node) => {
node.textContent = callback(node.textContent);
});
return el;
};
/**
* Gets a list of all the text nodes in an element
* @memberOf element
* @function
* @param {Element} el The element to get the text nodes of.
* @returns {Array} The text nodes.
* @example
* _$.textNodes(document.querySelector("h1"))[0].textContent = "hello world"; // replaces the text with "hello world" without deleting other elements
*/
export let textNodes = (el = req("HTMLElement", "element")) => {
node();
return [...el.childNodes].filter((node) => {
return (
node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() !== ""
);
});
};
/**
* Generates a querySelector for an element passed in.
* @function
* @memberOf element
* @param {Element} elem The element to generate the querySelector for.
* @example
* const textarea = document.getElementById('textarea');
* console.log(_$.querySelector(textarea)); //Logs "#textarea" to the console.
* @returns {String} The generated querySelector.
*/
export let querySelector = (elem = req("HTMLElement", "element")) => {
node();
var element = elem;
var str = "";
function loop(element) {
if (
element.getAttribute("id") &&
document.querySelectorAll(`#${element.getAttribute("id")}`)
.length === 1
) {
str = str.replace(/^/, " #" + element.getAttribute("id"));
str = str.replace(/\s/, "");
str = str.replace(/\s/g, " > ");
return str;
}
if (document.body === element) {
str = str.replace(/^/, " body");
str = str.replace(/\s/, "");
str = str.replace(/\s/g, " > ");
return str;
}
if (element.getAttribute("class")) {
var elemClasses = ".";
elemClasses += element.getAttribute("class");
elemClasses = elemClasses.replace(/\s/g, ".");
elemClasses = elemClasses.replace(/^/g, " ");
var classNth = "";
var childrens = element.parentNode.children;
if (childrens.length < 2) {
return;
}
var similarClasses = [];
for (var i = 0; i < childrens.length; i++) {
if (
element.getAttribute("class") ==
childrens[i].getAttribute("class")
) {
similarClasses.push(childrens[i]);
}
}
if (similarClasses.length > 1) {
for (var j = 0; j < similarClasses.length; j++) {
if (element === similarClasses[j]) {
j++;
classNth = ":nth-of-type(" + j + ")";
break;
}
}
}
str = str.replace(/^/, elemClasses + classNth);
} else {
var name = element.nodeName;
name = name.toLowerCase();
var nodeNth = "";
childrens = element.parentNode.children;
if (childrens.length > 2) {
var similarNodes = [];
for (var i = 0; i < childrens.length; i++) {
if (element.nodeName == childrens[i].nodeName) {
similarNodes.push(childrens[i]);
}
}
if (similarNodes.length > 1) {
for (var j = 0; j < similarNodes.length; j++) {
if (element === similarNodes[j]) {
j++;
nodeNth = ":nth-of-type(" + j + ")";
break;
}
}
}
}
str = str.replace(/^/, " " + name + nodeNth);
}
if (element.parentNode) {
loop(element.parentNode);
} else {
str = str.replace(/\s/g, " > ");
str = str.replace(/\s/, "");
return str;
}
}
loop(element);
return str;
};
/**
* Removes comments from the element specified.
* @function
* @memberOf element
* @param {Element} el The element to remove comments from.
* @example
* _$.removeComments(document.documentElement);//Removes the comments from the document element.
* @returns {HTMLElement} The HTML element with the comments removed.
*/
export let removeComments = (
el = req("HTMLElement", "HTMLElement"),
) => {
node();
const isString = typeof el === "string";
el = isString ? _$.parseHTML(el) : el.cloneNode(true);
for (const child of [...el.querySelectorAll("*"), el]) {
for (const grandchild of child.childNodes) {
if (grandchild instanceof Comment)
child.removeChild(grandchild);
}
}
return isString ? el.outerHTML : el;
};
/**
* Parses the string of HTML specified and returns an HTML element of it.
* @function
* @memberOf element
* @param {String} string The HTML string to parse.
* @param {String} [mimeType="text/html"] The mimeType of the string.
* @example
* let html = _$.parseHTML("<div id='hello'><textarea></textarea></div>");
* html.querySelector("textarea");//Returns the textarea!
* @returns {HTMLDocument} The HTML document element of the HTML string specified.
*/
export let parseHTML = (
string = req("string", "html string"),
mimeType = "text/html",
) => {
node();
const domparser = new DOMParser();
return domparser.parseFromString(string, mimeType);
};
/**
* Allows an element to be dragged and dropped.
* @function
* @memberOf element
* @param {Element} dragHandle The element that when dragged should move the dragTarget.
* @param {Element} dragTarget The element that should be moved when the dragHandle is dragged.
* @example
* _$.drag('div span', 'div'); // Allows the first <div> on the page to be dragged by the <span> element inside it.
* @returns {Element} The element.
*/
export let drag = (
dragHandle = req("String|Element", "drag handle"),
dragTarget = req("String|Element", "drag target"),
) => {
node();
let dragObj = null;
let xOffset = 0;
let yOffset = 0;
dragHandle =
typeof dragHandle === "string"
? document.querySelector(dragHandle)
: dragHandle;
dragTarget =
typeof dragTarget === "string"
? document.querySelector(dragTarget)
: dragTarget;
dragHandle.addEventListener("mousedown", startDrag, true);
dragHandle.addEventListener("touchstart", startDrag, true);
function startDrag(e) {
e.preventDefault();
e.stopPropagation();
dragObj = dragTarget;
dragObj.style.position = "absolute";
let rect = dragObj.getBoundingClientRect();
if (e.type == "mousedown") {
xOffset = e.clientX - rect.left;
yOffset = e.clientY - rect.top;
window.addEventListener("mousemove", dragObject, true);
} else if (e.type == "touchstart") {
xOffset = e.targetTouches[0].clientX - rect.left;
yOffset = e.targetTouches[0].clientY - rect.top;
window.addEventListener("touchmove", dragObject, true);
}
}
function dragObject(e) {
e.preventDefault();
e.stopPropagation();
if (dragObj == null) {
return;
} else if (e.type == "mousemove") {
dragObj.style.left = e.clientX - xOffset + "px";
dragObj.style.top = e.clientY - yOffset + "px";
} else if (e.type == "touchmove") {
dragObj.style.left =
e.targetTouches[0].clientX - xOffset + "px";
dragObj.style.top = e.targetTouches[0].clientY - yOffset + "px";
}
}
document.onmouseup = function (e) {
if (dragObj) {
dragObj = null;
window.removeEventListener("mousemove", dragObject, true);
window.removeEventListener("touchmove", dragObject, true);
}
};
};
/**
* @callback eventListenersCallback
* @param {Event} e The event object
* @returns {undefined}
*/
/**
* Adds multiple event listeners with one callback to the element specified.
* @memberOf element
* @function
* @param {Element} element The element to add the event listeners to.
* @param {Array.<String>} events The array of events to listen for.
* @param {eventListenersCallback} handler The function to run when the events happen.
* @param {Boolean|Object} [useCapture=false] Whether to use capture, or an options object.
* @param {Array} [args=false] The arguments to use in the handler function.
* @example
* // Reset a timer every user interaction.
* let timer = 0;
* setInterval(() => timer++, 1);
* _$.addEventListeners(
* document,
* ["mousemove", "click", "scroll", "keypress"],
* () => timer = 0,
* );
* @returns {Element} The HTML element passed.
*/
export let addEventListeners = (
element = req("HTMLElement", "element"),
events = req("array", "events"),
handler = () => {},
useCapture = false,
args = [],
) => {
node();
if (!(events instanceof Array)) {
throw (
"addMultipleListeners: " +
"please supply an array of eventstrings " +
'(like ["click","mouseover"])'
);
}
//create a wrapper to be able to use additional arguments
var handlerFn = function (e) {
handler.apply(this, args && args instanceof Array ? args : []);
};
for (var i = 0; i < events.length; i += 1) {
element.addEventListener(events[i], handlerFn, useCapture);
}
return element;
};
/**
* @callback sortTableCallback
* @param {HTMLTableCellElement} td The td element
* @param {HTMLTableRowElement} tr The tr element
* @param {Number} cellIndex The cell index
* @returns {String} The cell content
*/
/**
* @memberOf element
* @function
* @returns {HTMLTableElement} The table element.
* Sorts a table using JavaScript. This appends click listeners to every TH in the table.
* @param {HTMLTableElement} element The table to sort
* @param {sortTableCallback} [cellVal] The callback function to run with the element to get the value of the cell. This is passed the cell (<td>) element, and the row (<tr>) element, and the index of the cell.
* @example
* _$.sortTable(document.querySelector("table"));//Done.
* @example
* _$.sortTable(document.querySelector("table"), (i) => i.getAttribute("data-sort"));//Sorts the table by each cell's 'data-sort' attribute.
*/
export let sortTable = (
element = req("HTMLTableElement", "table element"),
cellVal = undefined,
) => {
node();
var getCellValue = function (tr, idx) {
return cellVal
? cellVal(tr.children[idx], tr, idx)
: tr.children[idx].innerText || tr.children[idx].textContent;
};
var comparer = function (idx, asc) {
return function (a, b) {
return (function (v1, v2) {
return v1 !== "" && v2 !== "" && !isNaN(v1) && !isNaN(v2)
? v1 - v2
: v1.toString().localeCompare(v2);
})(
getCellValue(asc ? a : b, idx),
getCellValue(asc ? b : a, idx),
);
};
};
Array.prototype.slice
.call(element.querySelectorAll("th"))
.forEach(function (th) {
th.addEventListener("click", function () {
var table = th.parentNode;
while (table.tagName.toUpperCase() != "TABLE")
table = table.parentNode;
Array.prototype.slice
.call(table.querySelectorAll("tr:nth-child(n+2)"))
.sort(
comparer(
Array.prototype.slice
.call(th.parentNode.children)
.indexOf(th),
(this.asc = !this.asc),
),
)
.forEach(function (tr) {
table.appendChild(tr);
});
});
});
return element;
};
/**
* Sorts a table by a <th> element.
* @memberOf element
* @function
* @returns {HTMLTableElement} The table element.
* @example
* //Note that this example pretty much recreates the _$ sortTable function, which is much more cross browser and good than this recreation. If sorting a whole table please use that.
* _$.each(document.querySelectorAll("#table th"), (th) => {
* th.addEventListener("click", () => {
* //Add event listeners to each of them.
* _$.sortTableBy(th, th.asc = !th.asc);//Toggle the "asc" attribute on the th.
* });
* })
* @param {HTMLTableElement} th The table header (<th> element) to sort with.
* @param {Boolean} ascending Whether to sort the table ascending or descending.
*/
export let sortTableBy = (
th = req("HTMLTableElement", "<th> element"),
ascending = true,
) => {
node();
var getCellValue = function (tr, idx) {
return tr.children[idx].innerText || tr.children[idx].textContent;
};
var comparer = function (idx, asc) {
return function (a, b) {
return (function (v1, v2) {
return v1 !== "" && v2 !== "" && !isNaN(v1) && !isNaN(v2)
? v1 - v2
: v1.toString().localeCompare(v2);
})(
getCellValue(asc ? a : b, idx),
getCellValue(asc ? b : a, idx),
);
};
};
var table = th.parentNode;
while (table.tagName.toUpperCase() != "TABLE")
table = table.parentNode;
Array.prototype.slice
.call(table.querySelectorAll("tr:nth-child(n+2)"))
.sort(
comparer(
Array.prototype.slice
.call(th.parentNode.children)
.indexOf(th),
ascending,
),
)
.forEach(function (tr) {
table.appendChild(tr);
});
return table;
};
/**
* Adds the specified styles to the element specified.
* @function
* @memberOf element
* @param {Element} el The element to add the styles to.
* @param {Object} styles An object that represents the styles to be added. (camelCased)
* @example
* _$.addStyles(document.documentElement, {backgroundColor: "#101010", color: "white"})
* @returns {Object} the style object of the element.
*/
export let addStyles = (
el = req("HTMLElement", "element"),
styles = req("Object", "styles"),
) => {
node();
return Object.assign(el.style, styles);
};
/**
* Creates an HTML element from the specified string.
* @function
* @memberOf element
* @param {String} str The string of the HTML element to create.
* @example
* //Returns a div with an id of "id_here" and innerText of "Testing!"
* _$.createElement("<div id='id_here'>Testing!</div>");
* @returns {Element} The created element.
*/
export let createElement = (
str = req("String", "HTML element string"),
) => {
node();
const el = document.createElement("div");
el.innerHTML = str;
return el.firstElementChild;
};
/**
* Gets a property from the computed style of an element.
* @function
* @memberOf element
* @param {Element} el The element whose styles to get.
* @param {String} prop The css-property value to get of the styles.
* @example
* console.log(_$.compStyle(document.documentElement, "background-color")); // logs the background colour of the document
* @returns {String} The computed style property for the element specified.
*/
export let compStyle = (
el = req("HTMLElement", "element"),
prop = req("String", "CSS property string"),
) => {
node();
var computedStyles = window.getComputedStyle(el);
return computedStyles.getPropertyValue(prop);
};
/**
* Get the siblings of a DOM element
* @function
* @memberOf element
* @param {Element} n The element to get siblings of
* @example
* _$.each(_$.elementSiblings(document.querySelectorAll("li")), (el) => el.style.backgroundColor = 'white');
* // Make every sibling of the first list item's background color white.
* @returns {Element[]} The array of sibling elements.
*/
export let elementSiblings = (n = req("HTMLElement", "element")) => {
node();
return [...n.parentElement.children].filter((c) => c != n);
};
/**
* Disables right click on the element specified.
* @function
* @memberOf element
* @param {HTMLElement} el The element to disable right click on.
* @example
* _$.disableRightClick(document.documentElement)
* @returns {HTMLElement} The HTML element that now has right click disabled.
*/
export let disableRightClick = (
el = req("HTMLElement", "element"),
) => {
node();
el.addEventListener("contextmenu", (e) => e.preventDefault());
return el;
};
/**
* Converts all of the styles for an element to inline CSS. This is nice for production sites because it means that they will look the same on all browsers. (Because it uses computed style.)
* @function
* @memberOf element
* @param {Element} el The element to convert.
* @example
* _$.inlineCSS(document.querySelector("h1")); // Converts the styles for the <h1> element to inline using the style="___" attribute
* @returns {Object} The computed styles of the element.
*/
export let inlineCSS = (el = req("HTMLElement", "element")) => {
node();
var cs = getComputedStyle(el, null);
var i;
for (i = 0; i < cs.length; i++) {
var s = cs[i] + "";
el.style[s] = cs[s];
}
return cs;
};
/**
* Returns an array of objects representing the attributes of a passed element.
* @param {Element} el The HMTL element to get attributes from.
* @function
* @memberOf element
* @example
* // Say the <html> tag of the document was "<html style='background-color: #101010;'>", then the function below would log "style," to the console.
* console.log(Object.keys(_$.attributes(document.documentElement).join(", "));
* @return {Array.<object>} The array of objects representing the attributes
*/
export let attributes = (el = req("HTMLElement", "element")) => {
node();
var output = [];
for (
var att, i = 0, atts = el.attributes, n = atts.length;
i < n;
i++
) {
att = atts[i];
output.push({
name: att.nodeName,
value: att.nodeValue,
});
}
return output;
};
/**
* Observes the mutations of the html element specified.
* @memberOf element
* @function
* @param {Element} element The element to observe
* @param {Function} callback The callback function to run when a mutation happens.
* @param {Object} options The options to use.
* @example
* _$.observeMutations(document, console.log); // Logs all the mutations that happen to the console.
* @returns {MutationObserver} The mutation observer.
*/
export let observeMutations = (
element = req("HTMLElement", "element"),
callback = req("function", "callback"),
options = {},
) => {
node();
const observer = new MutationObserver((mutations) =>
mutations.forEach((m) => callback(m)),
);
observer.observe(
element,
Object.assign(
{
childList: true,
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
subtree: true,
},
options,
),
);
return observer;
};
/**
* Tilts a specified element to point towards the specified position. Note that 0,0 is the center of the screen in coordinates.
* @memberOf element
* @function
* @param {Element} el The element to tilt.
* @param {Number} x The x value of the mouse
* @param {Number} y The y value of the mouse
* @param {Number} [perspective=500] The perspective
* @param {Number} [amount=30] The amount to tilt.
* @returns {String} The css transform string.
* @example
* // Tilt the first image in the document whenever the mouse moves.
* let el = document.querySelector("img");
* el.onmousemove = (e) => {
* let x = e.layerX;
* let y = e.layerY
* _$.tilt(el, x, y);
* }
*/
export let tilt = (
el = req("HTMLElement", "element"),
x = req("number", "x"),
y = req("number", "y"),
perspective = 500,
amount = 30,
) => {
node();
//Old code
/* const xVal = x
const yVal = y
const yRotation = amount * ((xVal - width / 2) / width)
const xRotation = amount * -1 * ((yVal - height / 2) / height)
const string = `perspective(${perspective}px) scale(1.1) rotateX(${xRotation}deg) rotateY(${yRotation}deg)`
el.style.transform = string */
//One liner
let transform = `perspective(${perspective}px) scale(1.1) rotateX(${
amount * -1 * ((y - el.clientHeight / 2) / el.clientHeight)
}deg) rotateY(${
amount * ((x - el.clientWidth / 2) / el.clientWidth)
}deg)`;
el.style.transform = transform;
return transform;
};
/**
* Enters fullscreen on an element.
* @memberOf element
* @function
* @param {Element} element The element to enter full screen with.
* @returns {Promise} A promise resolved when fullscreen is entered.
* @example
* _$.fullScreen(document.documentElement); // Make the window fullscreen
*/
export let fullScreen = (element = req("HTMLElement", "element")) => {
node();
if (element.requestFullScreen) return element.requestFullScreen();
else if (element.mozRequestFullScreen)
return element.mozRequestFullScreen();
else if (element.webkitRequestFullScreen)
return element.webkitRequestFullScreen();
else return new Error("Fullscreen failed");
};
/**
* Replaces the selected text in a contentEditable div with the HTML given.
* @memberOf element
* @function
* @returns {Range} A range representing the users selection.
* @example
* // Add a simple contenteditable div to the page.
* document.appendChild(_$.createElement("<div contenteditable id='text'></div>"));
* _$.replaceSelection("<b>BOLD TEXT</b> <small>Bijou is awesome</small><h1>You need to use it</h1>");
* //Replaces the selection! =)
* @param {String} replacementText The replacement HTML to replace with.
*/
export let replaceSelection = (
replacementText = req("string", "replacement text"),
) => {
node();
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
let n = document.createElement("span");
n.insertAdjacentHTML("beforeend", replacementText);
range.insertNode(n);
}
} else if (document.selection && document.selection.createRange) {
console.warn(
"You are using IE < 9, you are evil. Falling back to text not HTML.",
);
range = document.selection.createRange();
range.text = replacementText.replace(/<[^>]*>/g, "");
}
return window.getSelection();
};
//#endregion Element
Source