Classes_Selector.js

import {
	empty,
	hasProp,
	isArrayish,
	isFunction,
	isObject,
	isString,
	parseHTML,
} from "../Utils/Utils.js"
/**
 * @memberOf module:Core.Selector
 * @private
 * @inner
 * @const {Number}
 * @default 3
 */
const NODETEXT = 3


/**
 * Errores Personalizados de la Clase Selector
 * @namespace SelectorError
 * @memberOf module:Core.Selector
 * @class
 * @extends {Error} Error
 */
export class SelectorError extends Error {
	constructor(...params) {
		super(...params)

		if (Error.captureStackTrace) {
			Error.captureStackTrace(this, SelectorError)
		}

		this.name = 'SelectorError'
	}
}

/**
 * Selecciona los Elementos y almacena en una matriz,
 * <blockquote>Esta clase esta pensada para el uso de [Fascino JS]{@link Fascino}, para usarla use [Fascino JS]{@link Fascino}.</blockquote>
 * @namespace Selector
 * @memberOf module:Core
 * @class
 * @requires Utils
 * @example
 * let div = new Selector('div.miclassdiv') // Retorna todos los div con la clase miclassdiv
 * let span = new Selector('span#miSpanID', div) // Retorna todos los span miSpanID hijo de div.miclassdiv
 */
export default class Selector {
	/**
	 * @param  {(String|Element|Object|Function|Array)} selector el elemento a seleccionar si es un String vea los selectores CSS
	 * @param  {Element} context  El elemento padre de donde se seleccionara el elemento dado ejm. <code>p[context] > span[selector]</code>
	 * @return {module:Core.Selector}  Un objecto con nuevas funciones para el elemento
	 */
	constructor(selector, context = document) {
		this.Elem = Array.from('')
		this.context = context

		if (empty(selector)) {
			return this
		}

		if (isString(selector)) {
			selector = selector.trim()
		}

		this.sel(selector)

		return this
	}
	/**
	 * Combina el nuevo elemento con los ya obtenidos
	 * @memberOf module:Core.Selector
	 * @protected
	 * @param  {(Array|Object|Element|window|document)} otherEl
	 * @return {void}
	 */
	mergeEl(otherEl) {
		if (!empty(otherEl) && otherEl.length === undefined) {
			otherEl = [otherEl]
		} else if (otherEl.self === window) {
			otherEl = [otherEl]
		}
		let e = [].slice.call(otherEl)
		if (e.length > 0) {
			e.forEach((el) => {
				this.Elem.push(el)
			})
		}
	}
	/**
	 * Valida y verifica el selector dado
	 * @memberOf module:Core.Selector
	 * @protected
	 * @param  {*} sel
	 * @return {Object}
	 */
	sel(sel) {
		if (isFunction(sel)) {
			document.addEventListener('DOMContentLoaded', sel, false)
			return this
		}

		if (sel instanceof Element) {
			this.mergeEl([sel])
			return this
		}

		if (sel instanceof NodeList) {
			this.mergeEl(sel)
			return this
		}

		if (sel instanceof HTMLCollection) {
			let dHtmlCol = Array.from(sel)
			this.mergeEl(dHtmlCol)
			return this
		}

		if (isObject(sel) && hasProp(sel, 'Elem')) {
			this.mergeEl(sel.Elem)
			return this
		}

		switch (sel) {
			case 'window':
				sel = window
				break
			case 'document':
				sel = document
				break
			case 'body':
				sel = document.body
				break
			case 'html':
				sel = document.documentElement
				break
			case ':root':
				sel = document.documentElement
				break
			case 'doctype':
				sel = document.doctype
				break
		}


		if (sel && (sel.nodeType || sel.self === window)) {
			this.mergeEl(sel)
			return this
		}

		if (isArrayish(sel)) {
			if (hasProp(sel, '_prevObj')) {
				this._prevObj = sel._prevObj
			}
			this.mergeEl(sel)
			return this
		}

		if (typeof sel !== 'string' && (sel.self && sel.self !== window)) {
			return this
		}

		const pHtml = parseHTML(sel)
		if (pHtml.length === 1 && pHtml[0].nodeType === NODETEXT) {
			try {
				const el = this.querySelector(sel)
				if (el.length === 0) {
					return this
				} else {
					this.mergeEl(el)
				}
			} catch (e) {
				throw new SelectorError(`\nFascino:\n ${sel} is not a valid selector`)
			}
		} else if (pHtml.length > 0) {
			this.mergeEl(pHtml)
		} else {
			this.mergeEl(sel)
		}

		return this
	}
	/**
	 * Hace uso de querySelector y valida la cantidad de elementos
	 * @memberOf module:Core.Selector
	 * @protected
	 * @param  {String} selector
	 * @return {(Array|NodeList)}
	 */
	querySelector(selector) {
		let d = this.context || document
		return d.querySelectorAll(selector).length > 1 ?
				d.querySelectorAll(selector) :
				d.querySelector(selector) != null ? [d.querySelector(selector)] : []
	}
}