bitmovin-player-ui
Version:
Bitmovin Player UI Framework
489 lines (488 loc) • 18.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DOM = void 0;
/**
* Simple DOM manipulation and DOM element event handling modeled after jQuery (as replacement for jQuery).
*
* Like jQuery, DOM operates on single elements and lists of elements. For example: creating an element returns a DOM
* instance with a single element, selecting elements returns a DOM instance with zero, one, or many elements. Similar
* to jQuery, setters usually affect all elements, while getters operate on only the first element.
* Also similar to jQuery, most methods (except getters) return the DOM instance facilitating easy chaining of method
* calls.
*
* Built with the help of: http://youmightnotneedjquery.com/
*/
var DOM = /** @class */ (function () {
function DOM(something, attributes, component) {
if (something instanceof Array) {
if (something.length > 0 && something[0] instanceof HTMLElement) {
var elements = something;
this.elements = elements;
}
}
else if (something instanceof HTMLElement) {
var element = something;
this.elements = [element];
}
else if (something instanceof Document || something instanceof ShadowRoot) {
// When a document or the ShadowRoot is passed in, we do not do anything with it, but by setting
// this.elements to null we give the event handling method a means to detect if the events should be
// registered on the document or ShadowRoot instead of elements.
this.documentOrShadowRoot = something;
this.elements = null;
}
else if (attributes) {
var tagName = something;
var element = document.createElement(tagName);
for (var attributeName in attributes) {
var attributeValue = attributes[attributeName];
if (attributeValue != null) {
element.setAttribute(attributeName, attributeValue);
}
}
if (component) {
element.component = component;
}
this.elements = [element];
}
else {
var selector = something;
this.elements = this.findChildElements(selector);
}
}
Object.defineProperty(DOM.prototype, "length", {
/**
* Gets the number of elements that this DOM instance currently holds.
* @returns {number} the number of elements
*/
get: function () {
return this.elements ? this.elements.length : 0;
},
enumerable: false,
configurable: true
});
DOM.prototype.get = function (index) {
if (index === undefined) {
return this.elements;
}
else if (!this.elements || index >= this.elements.length || index < -this.elements.length) {
return undefined;
}
else if (index < 0) {
return this.elements[this.elements.length - index];
}
else {
return this.elements[index];
}
};
/**
* A shortcut method for iterating all elements. Shorts this.elements.forEach(...) to this.forEach(...).
* @param handler the handler to execute an operation on an element
*/
DOM.prototype.forEach = function (handler) {
if (!this.elements) {
return;
}
this.elements.forEach(function (element) {
handler(element);
});
};
DOM.prototype.findChildElementsOfElement = function (element, selector) {
var childElements = element.querySelectorAll(selector);
// Convert NodeList to Array
// https://toddmotto.com/a-comprehensive-dive-into-nodelists-arrays-converting-nodelists-and-understanding-the-dom/
return [].slice.call(childElements);
};
DOM.prototype.findChildElements = function (selector) {
var _this = this;
var allChildElements = [];
if (this.elements) {
this.forEach(function (element) {
allChildElements = allChildElements.concat(_this.findChildElementsOfElement(element, selector));
});
}
else {
return this.findChildElementsOfElement(document, selector);
}
return allChildElements;
};
/**
* Finds all child elements of all elements matching the supplied selector.
* @param selector the selector to match with child elements
* @returns {DOM} a new DOM instance representing all matched children
*/
DOM.prototype.find = function (selector) {
var allChildElements = this.findChildElements(selector);
return new DOM(allChildElements);
};
/**
* Focuses to the first input element
*/
DOM.prototype.focusToFirstInput = function () {
var inputElements = this.findChildElements('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (inputElements.length > 0) {
inputElements[0].focus();
}
};
/**
* Focuses to the first input element
*/
DOM.prototype.scrollTo = function (x, y) {
this.elements[0].scrollTo(x, y);
};
DOM.prototype.html = function (content) {
if (arguments.length > 0) {
return this.setHtml(content);
}
else {
return this.getHtml();
}
};
DOM.prototype.getHtml = function () {
return this.elements[0].innerHTML;
};
DOM.prototype.setHtml = function (content) {
if (content === undefined || content == null) {
// Set to empty string to avoid innerHTML getting set to 'undefined' (all browsers) or 'null' (IE9)
content = '';
}
this.forEach(function (element) {
element.innerHTML = content;
});
return this;
};
/**
* Clears the inner HTML of all elements (deletes all children).
* @returns {DOM}
*/
DOM.prototype.empty = function () {
this.forEach(function (element) {
element.innerHTML = '';
});
return this;
};
/**
* Returns the current value of the first form element, e.g. the selected value of a select box or the text if an
* input field.
* @returns {string} the value of a form element
*/
DOM.prototype.val = function () {
var element = this.elements[0];
if (element instanceof HTMLSelectElement || element instanceof HTMLInputElement) {
return element.value;
}
else {
// TODO add support for missing form elements
throw new Error("val() not supported for ".concat(typeof element));
}
};
DOM.prototype.attr = function (attribute, value) {
if (arguments.length > 1) {
return this.setAttr(attribute, value);
}
else {
return this.getAttr(attribute);
}
};
/**
* Removes the attribute of the element.
* @param attribute
*/
DOM.prototype.removeAttr = function (attribute) {
this.forEach(function (element) {
element.removeAttribute(attribute);
});
};
DOM.prototype.getAttr = function (attribute) {
return this.elements[0].getAttribute(attribute);
};
DOM.prototype.setAttr = function (attribute, value) {
this.forEach(function (element) {
element.setAttribute(attribute, value);
});
return this;
};
DOM.prototype.data = function (dataAttribute, value) {
if (arguments.length > 1) {
return this.setData(dataAttribute, value);
}
else {
return this.getData(dataAttribute);
}
};
DOM.prototype.getData = function (dataAttribute) {
return this.elements[0].getAttribute('data-' + dataAttribute);
};
DOM.prototype.setData = function (dataAttribute, value) {
this.forEach(function (element) {
element.setAttribute('data-' + dataAttribute, value);
});
return this;
};
/**
* Appends one or more DOM elements as children to all elements.
* @param childElements the child elements to append
* @returns {DOM}
*/
DOM.prototype.append = function () {
var childElements = [];
for (var _i = 0; _i < arguments.length; _i++) {
childElements[_i] = arguments[_i];
}
var appendElements = function (node) {
childElements.forEach(function (childElement) {
childElement.elements.forEach(function (_, index) {
node.appendChild(childElement.elements[index]);
});
});
};
if (this.elements) {
this.forEach(function (element) { return appendElements(element); });
}
else {
appendElements(this.documentOrShadowRoot);
}
return this;
};
/**
* Prepends one or more DOM elements as children to all elements.
* @param childElements the child elements to prepend
* @returns {DOM}
*/
DOM.prototype.prepend = function () {
var childElements = [];
for (var _i = 0; _i < arguments.length; _i++) {
childElements[_i] = arguments[_i];
}
var insertElements = function (node) {
childElements.forEach(function (childElement) {
childElement.elements.forEach(function (_, index) {
node.insertBefore(childElement.elements[index], node.firstChild);
});
});
};
if (this.elements) {
this.forEach(function (element) { return insertElements(element); });
}
else {
insertElements(this.documentOrShadowRoot);
}
return this;
};
/**
* Removes all elements from the DOM.
*/
DOM.prototype.remove = function () {
var removeElements = function (node) {
var parent = node.parentNode;
if (parent) {
parent.removeChild(node);
}
};
if (this.elements) {
this.forEach(function (element) { return removeElements(element); });
}
else {
removeElements(this.documentOrShadowRoot);
}
};
/**
* Returns the offset of the first element from the document's top left corner.
* @returns {Offset}
*/
DOM.prototype.offset = function () {
var element = this.elements[0];
var elementRect = element.getBoundingClientRect();
var htmlRect = document.body.parentElement.getBoundingClientRect();
// Virtual viewport scroll handling (e.g. pinch zoomed viewports in mobile browsers or desktop Chrome/Edge)
// 'normal' zooms and virtual viewport zooms (aka layout viewport) result in different
// element.getBoundingClientRect() results:
// - with normal scrolls, the clientRect decreases with an increase in scroll(Top|Left)/page(X|Y)Offset
// - with pinch zoom scrolls, the clientRect stays the same while scroll/pageOffset changes
// This means, that the combination of clientRect + scroll/pageOffset does not work to calculate the offset
// from the document's upper left origin when pinch zoom is used.
// To work around this issue, we do not use scroll/pageOffset but get the clientRect of the html element and
// subtract it from the element's rect, which always results in the offset from the document origin.
// NOTE: the current way of offset calculation was implemented specifically to track event positions on the
// seek bar, and it might break compatibility with jQuery's offset() method. If this ever turns out to be a
// problem, this method should be reverted to the old version and the offset calculation moved to the seek bar.
return {
top: elementRect.top - htmlRect.top,
left: elementRect.left - htmlRect.left,
};
};
/**
* Returns the width of the first element.
* @returns {number} the width of the first element
*/
DOM.prototype.width = function () {
// TODO check if this is the same as jQuery's width() (probably not)
return this.elements[0].offsetWidth;
};
/**
* Returns the height of the first element.
* @returns {number} the height of the first element
*/
DOM.prototype.height = function () {
// TODO check if this is the same as jQuery's height() (probably not)
return this.elements[0].offsetHeight;
};
/**
* Returns the size of the first element.
* @return {Size} the size of the first element
*/
DOM.prototype.size = function () {
return { width: this.width(), height: this.height() };
};
/**
* Attaches an event handler to one or more events on all elements.
* @param eventName the event name (or multiple names separated by space) to listen to
* @param eventHandler the event handler to call when the event fires
* @param options the options for this event handler
* @returns {DOM}
*/
DOM.prototype.on = function (eventName, eventHandler, options) {
var _this = this;
var events = eventName.split(' ');
events.forEach(function (event) {
if (_this.elements == null) {
_this.documentOrShadowRoot.addEventListener(event, eventHandler, options);
}
else {
_this.forEach(function (element) {
element.addEventListener(event, eventHandler, options);
});
}
});
return this;
};
/**
* Removes an event handler from one or more events on all elements.
* @param eventName the event name (or multiple names separated by space) to remove the handler from
* @param eventHandler the event handler to remove
* @param options the options for this event handler
* @returns {DOM}
*/
DOM.prototype.off = function (eventName, eventHandler, options) {
var _this = this;
var events = eventName.split(' ');
events.forEach(function (event) {
if (_this.elements == null) {
_this.documentOrShadowRoot.removeEventListener(event, eventHandler, options);
}
else {
_this.forEach(function (element) {
element.removeEventListener(event, eventHandler, options);
});
}
});
return this;
};
/**
* Adds the specified class(es) to all elements.
* @param className the class(es) to add, multiple classes separated by space
* @returns {DOM}
*/
DOM.prototype.addClass = function (className) {
this.forEach(function (element) {
var _a;
if (element.classList) {
var classNames = className.split(' ').filter(function (className) { return className.length > 0; });
if (classNames.length > 0) {
(_a = element.classList).add.apply(_a, classNames);
}
}
else {
element.className += ' ' + className;
}
});
return this;
};
/**
* Removed the specified class(es) from all elements.
* @param className the class(es) to remove, multiple classes separated by space
* @returns {DOM}
*/
DOM.prototype.removeClass = function (className) {
this.forEach(function (element) {
var _a;
if (element.classList) {
var classNames = className.split(' ').filter(function (className) { return className.length > 0; });
if (classNames.length > 0) {
(_a = element.classList).remove.apply(_a, classNames);
}
}
else {
element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
});
return this;
};
/**
* Checks if any of the elements has the specified class.
* @param className the class name to check
* @returns {boolean} true if one of the elements has the class attached, else if no element has it attached
*/
DOM.prototype.hasClass = function (className) {
var hasClass = false;
this.forEach(function (element) {
if (element.classList) {
if (element.classList.contains(className)) {
// Since we are inside a handler, we can't just 'return true'. Instead, we save it to a variable
// and return it at the end of the function body.
hasClass = true;
}
}
else {
if (new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className)) {
// See comment above
hasClass = true;
}
}
});
return hasClass;
};
DOM.prototype.css = function (propertyNameOrCollection, value) {
if (typeof propertyNameOrCollection === 'string') {
var propertyName = propertyNameOrCollection;
if (arguments.length === 2) {
return this.setCss(propertyName, value);
}
else {
return this.getCss(propertyName);
}
}
else {
var propertyValueCollection = propertyNameOrCollection;
return this.setCssCollection(propertyValueCollection);
}
};
/**
* Removes an inline CSS property if it exists
* @param propertyName name of the property to remove
* @param elementIndex index of the element whose CSS property should get removed
*/
DOM.prototype.removeCss = function (propertyName, elementIndex) {
if (elementIndex === void 0) { elementIndex = 0; }
return this.elements[elementIndex].style.removeProperty(propertyName);
};
DOM.prototype.getCss = function (propertyName) {
return getComputedStyle(this.elements[0])[propertyName];
};
DOM.prototype.setCss = function (propertyName, value) {
this.forEach(function (element) {
// <any> cast to resolve TS7015: http://stackoverflow.com/a/36627114/370252
element.style[propertyName] = value;
});
return this;
};
DOM.prototype.setCssCollection = function (ruleValueCollection) {
this.forEach(function (element) {
// http://stackoverflow.com/a/34490573/370252
Object.assign(element.style, ruleValueCollection);
});
return this;
};
return DOM;
}());
exports.DOM = DOM;