plus-8.x-4.x-dev/js/Drupal.Url.es6.js
js/Drupal.Url.es6.js
/**
* @file
* Drupal+ Url.
*/
((Drupal) => {
'use strict';
const elementUrlMap = new Map(Object.entries({
code: new Set([window.HTMLAppletElement].filter(Boolean)),
data: new Set([HTMLObjectElement].filter(Boolean)),
href: new Set([HTMLAnchorElement, HTMLAreaElement, HTMLBaseElement, HTMLLinkElement].filter(Boolean)),
src: new Set([HTMLAudioElement, HTMLEmbedElement, HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLScriptElement, HTMLSourceElement, HTMLTrackElement, HTMLVideoElement].filter(Boolean)),
}));
const getElementUrl = (element) => {
let url = null;
elementUrlMap.forEach((elements, prop) => {
elements.forEach((value) => {
if (url === null && element instanceof value) {
url = element[prop];
}
});
});
return url;
};
/**
* Retrieves the file mime type from an element.
*
* @param {HTMLElement} element
* An element.
*
* @return {Array}
* The mime type.
*/
const getElementMimeType = (element) => {
let mimeType = null;
if (element instanceof HTMLAnchorElement) {
mimeType = element.type;
}
else if (element instanceof HTMLFormElement) {
mimeType = element.enctype;
}
return (mimeType && mimeType.split(/;\s*/).filter(Boolean)) || [];
};
/**
* Private properties.
*
* @type {Object<String, WeakMap>}
*
* @private
*/
const _ = {
absolute: new WeakMap(),
basename: new WeakMap(),
element: new WeakMap(),
extension: new WeakMap(),
filename: new WeakMap(),
hash: new WeakMap(),
langcode: new WeakMap(),
mimeType: new WeakMap(),
origin: new WeakMap(),
path: new WeakMap(),
protocol: new WeakMap(),
query: new WeakMap(),
relation: new WeakMap(),
relative: new WeakMap(),
size: new WeakMap(),
title: new WeakMap(),
uid: new WeakMap(),
url: new WeakMap(),
};
/**
* @class Url
*/
class Url {
constructor() {
_.absolute.set(this, null);
_.basename.set(this, null);
_.element.set(this, null);
_.extension.set(this, null);
_.filename.set(this, null);
_.hash.set(this, null);
_.langcode.set(this, null);
_.mimeType.set(this, null);
_.origin.set(this, null);
_.path.set(this, null);
_.protocol.set(this, null);
_.query.set(this, null);
_.relation.set(this, null);
_.relative.set(this, null);
_.size.set(this, null);
_.title.set(this, null);
_.uid.set(this, null);
_.url.set(this, null);
}
/**
* The absolute URL.
*
* @return {String}
* The value.
*/
get absolute() {
return _.absolute.get(this) || '';
}
/**
* Sets the absolute URL.
*
* @param {String} value
* The value to set.
*/
set absolute(value) {
const origin = _.origin.get(this) || window.location.origin;
let url;
try {
url = new URL(value, origin);
}
catch (e) {
return Drupal.fatal(Drupal.t('Only absolute URLs (or protocol relative URLs) can be set on the "absolute" property: @value'), { '@value': value });
}
_.url.set(this, url);
_.absolute.set(this, url.href);
// Reset other properties so they can be reevaluated.
_.basename.set(this, null);
_.extension.set(this, null);
_.filename.set(this, null);
_.hash.set(this, null);
_.origin.set(this, null);
_.path.set(this, null);
_.protocol.set(this, null);
_.query.set(this, null);
_.relative.set(this, null);
}
/**
* The base filename, without the extension.
*
* @return {String}
* The value.
*
* @see http://locutus.io/php/filesystem/basename/
*/
get basename() {
return this.getPrivateProperty(_, 'basename', () => {
let url = this.path;
let basename = '';
const last = url && url.charAt(url.length - 1);
if (last === '/' || last === '\\') {
url = url.slice(0, -1);
}
url = url && url.replace(/^.*[/\\]/g, '');
const extension = `.${this.extension}`;
if (url.substr(url.length - extension.length) === extension) {
basename = url.substr(0, url.length - extension.length);
}
return basename;
});
}
/**
* The known element that the values were extract from, if any.
*
* @return {HTMLElement}
* The value.
*/
get element() {
return _.element.get(this);
}
/**
* Sets the element property.
*
* @param {HTMLElement} value
* The value to set.
*/
set element(value) {
if (!getElementUrl(value)) {
return Drupal.fatal(Drupal.t('Only HTMLElement objects where a URL value can be extracted are allowed to be set on the "element" property: @value'), { '@value': value });
}
_.element.set(this, value);
// Reset other properties so they can be reevaluated.
_.absolute.set(this, null);
_.basename.set(this, null);
_.extension.set(this, null);
_.filename.set(this, null);
_.hash.set(this, null);
_.langcode.set(this, null);
_.mimeType.set(this, null);
_.origin.set(this, null);
_.path.set(this, null);
_.protocol.set(this, null);
_.query.set(this, null);
_.relation.set(this, null);
_.relative.set(this, null);
_.size.set(this, null);
_.title.set(this, null);
_.uid.set(this, null);
_.url.set(this, null);
}
/**
* The extension of the file.
*
* @return {String}
* The value.
*
* @see http://stackoverflow.com/a/12900504
*/
get extension() {
return this.getPrivateProperty(_, 'extension', () => {
const path = this.path;
/* eslint no-bitwise: ["error", { "allow": [">>>"] }] */
return path && /tar\.gz$/.test(path) ? 'tar.gz' : path.slice((path.lastIndexOf('.') - 1 >>> 0) + 2) || '';
});
}
/**
* The known filename of the file.
*
* @return {String}
* The value.
*/
get filename() {
return this.getPrivateProperty(_, 'filename', () => [this.basename, this.extension].filter(Boolean).join('.'));
}
/**
* The known hash of the file.
*
* @return {String}
* The value.
*/
get hash() {
return this.getPrivateProperty(_, 'hash', () => {
const url = _.url.get(this);
return (url && url.hash) || '';
});
}
/**
* The known language code of the file, if any.
*
* @return {String}
* The value.
*/
get langcode() {
return this.getPrivateProperty(_, 'langcode', element => (element && (element.hreflang || element.lang)) || '');
}
/**
* The known mime type of the file, if any.
*
* @return {String}
* The value.
*/
get mimeType() {
return this.getPrivateProperty(_, 'mimeType', element => getElementMimeType(element).shift() || '');
}
/**
* The origin of the file, if any.
*
* @return {String}
* The value.
*/
get origin() {
return this.getPrivateProperty(_, 'origin', (element) => {
const url = _.url.get(this);
return (element && element.origin) || (url && url.origin) || '';
});
}
/**
* Setter for the origin of the file.
*
* @param {String} value
* The value to set.
*/
set origin(value) {
_.origin.set(this, value);
}
/**
* The path of the file (without origin), if any.
*
* @return {String}
* The value.
*/
get path() {
return this.getPrivateProperty(_, 'path', () => {
const url = _.url.get(this);
return (url && url.pathname) || '';
});
}
/**
* The protocol of the file.
*
* @return {String}
* The value.
*/
get protocol() {
return this.getPrivateProperty(_, 'protocol', () => {
const url = _.url.get(this);
return (url && url.protocol.replace(/:$/, '')) || '';
});
}
/**
* The known query of the file, if any.
*
* @return {String}
* The value.
*/
get query() {
return this.getPrivateProperty(_, 'query', () => {
const url = _.url.get(this);
return (url && url.search) || '';
});
}
/**
* The known relationship of the file, if any.
*
* @return {String}
* The value.
*/
get relation() {
return this.getPrivateProperty(_, 'relation', element => (element && element.rel) || '');
}
/**
* The file relative URL (minus the origin).
*
* @return {String}
* The value.
*/
get relative() {
return this.getPrivateProperty(_, 'relation', () => {
const url = this.absolute;
return (url && url.replace(this.origin, '')) || '';
});
}
/**
* The known size of the file, if any.
*
* @return {Number}
* The value.
*/
get size() {
return this.getPrivateProperty(_, 'size', (element) => {
let size = 0;
// Extract the file size from the mime type, if provided.
const mimeType = getElementMimeType(element).slice(1);
for (let i = 0, l = mimeType.slice(0).length; i < l; i++) {
const parts = mimeType[i].split('=');
if (parts && parts[0] === 'length') {
size = parts[1];
break;
}
}
return size;
}, ['fileSize', 'size']);
}
/**
* The known title of the file, if any.
*
* @return {String}
* The value.
*/
get title() {
return this.getPrivateProperty(_, 'title', element => (element && element.title) || '');
}
/**
* The identifier of the user who is associated with this file, if any.
*
* @return {Number}
* The value.
*/
get uid() {
return this.getPrivateProperty(_, 'uid', 0);
}
/**
* The absolute URL.
*
* An alias for DrupalUrl.absolute.
*
* @type {String}
*
* @see Url.absolute
*/
get url() {
return this.absolute;
}
/**
* Sets the absolute URL.
*
* @param {String} value
* The value to set.
*/
set url(value) {
this.absolute = value;
}
/**
* Sets a specific property.
*
* @param {String|Object|HTMLElement} name
* The name of a supported property to set. Can also be an object of
* key/value properties to set or an HTMLElement object.
* @param {*} [value]
* The value to set if the "name" provided was a string of the property
* to set.
*
* @return {Url}
* The instance.
*/
set(name, value) {
let values = {};
if (typeof name === 'string') {
values[name] = value;
}
else if (name instanceof HTMLElement) {
values.element = name;
}
else if (typeof name === 'object') {
values = { ...name };
}
// Convert "url" alias to "absolute".
if (values.url && values.absolute === undefined) {
values.absolute = values.url;
delete values.url;
}
// Only merge in supported properties, typecasting as needed.
const keys = Object.keys(values);
for (let i = 0, l = keys.length; i < l; i++) {
const prop = keys[i];
if (_.hasOwnProperty(prop) || this.hasOwnProperty(prop)) {
this[prop] = values[prop];
}
}
return this;
}
getPrivateProperty(properties, name, defaultValue = '', dataAttributes = null) {
const prop = properties[name];
let value = prop.get(this);
// Immediately return the value if it's set.
if (value !== null) {
return value;
}
const element = properties.element ? properties.element.get(this) : _.element.get(this);
// Attempt to get a data attribute value from an element.
if (element) {
if (dataAttributes === null) {
dataAttributes = [name];
}
if (dataAttributes) {
for (let i = 0, l = dataAttributes.length; i < l; i++) {
if (element.dataset[dataAttributes[i]] !== undefined) {
value = element.dataset[dataAttributes[i]];
break;
}
}
}
}
// Handle defaultValue as a callback by invoking it and then setting
// the return value as the defaultValue.
if (value === null && typeof defaultValue === 'function') {
defaultValue = defaultValue(element);
if (defaultValue === undefined || defaultValue === null) {
defaultValue = '';
}
}
// Typecast the new value from the default value.
value = Drupal.typeCast(defaultValue, value);
// Set the property.
prop.set(this, value);
// Return it.
return value;
}
/**
* Constructs a new file object from values.
*
* @param {Object|HTMLElement|Url} [obj]
* The values to assign to the new DrupalUrl. If an HTMLElement is
* provided, all relevant data will be extracted from it.
* @param {String} [origin = window.location.origin]
* A fallback origin for the URL. By default, the origin will
* automatically be determined from the URL, but if it is a relative URL
* this will be used to make an absolute URL. It defaults to the the
* origin of the current page.
*
* @return {Url}
* The new DrupalUrl instance.
*/
static create(obj, origin = null) {
if (obj instanceof this) {
return obj;
}
const instance = new this();
if (obj) {
instance.set('origin', origin || window.location.origin + Drupal.settings.basePath + Drupal.settings.pathPrefix);
instance.set(typeof obj === 'string' ? { url: obj } : obj);
}
return instance;
}
}
/**
* Export to Drupal.
*
* @type {Url}
*/
Drupal.Url = Url;
})(window.Drupal);
