Advertisement
Guest User

Untitled

a guest
Mar 28th, 2017
49
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.20 KB | None | 0 0
  1. /**
  2. * A collection of helper prototype for everyday DOM traversal, manipulation,
  3. * and event binding. Sort of a minimalist jQuery, mainly for demonstration
  4. * purposes. MIT @ m3g4p0p
  5. */
  6. window.$ = (function (undefined) {
  7.  
  8. /**
  9. * Duration constants
  10. * @type {Object}
  11. */
  12. const DURATION = {
  13. DEFAULT: 500
  14. }
  15.  
  16. /**
  17. * Style constants
  18. * @type {Object}
  19. */
  20. const STYLE = {
  21. SHOW: 'block',
  22. HIDE: 'none'
  23. }
  24.  
  25. /**
  26. * Style property constants
  27. * @type {Object}
  28. */
  29. const PROPERTY = {
  30. DISPLAY: 'display'
  31. }
  32.  
  33. /**
  34. * Type constants
  35. * @type {Object}
  36. */
  37. const TYPE = {
  38. FUNCTION: 'function',
  39. STRING: 'string'
  40. }
  41.  
  42. /**
  43. * Map elements to events
  44. * @type {WeakMap}
  45. */
  46. const eventMap = new WeakMap()
  47.  
  48. /**
  49. * Function to fade an element
  50. * @param {HTMLElement} element
  51. * @param {Number} from
  52. * @param {Number} to
  53. * @param {Number} duration
  54. * @param {Function} callback
  55. * @return {void}
  56. */
  57. const fade = function fade (element, from, to, duration, callback) {
  58. const start = window.performance.now()
  59.  
  60. element.style.display = STYLE.SHOW
  61.  
  62. window.requestAnimationFrame(function step (timestamp) {
  63. const progress = timestamp - start
  64. element.style.opacity = from + (progress / duration) * (to - from)
  65.  
  66. if (progress < duration) {
  67. window.requestAnimationFrame(step)
  68. } else {
  69. if (element.style.opacity <= 0) {
  70. element.style.display = STYLE.HIDE
  71. }
  72.  
  73. if (callback) {
  74. callback.call(element)
  75. }
  76. }
  77. })
  78. }
  79.  
  80. /**
  81. * The methods in the collection'S prototype
  82. * @type {Object}
  83. */
  84. const prototype = {
  85.  
  86. ///////////////
  87. // Traversal //
  88. ///////////////
  89.  
  90. /**
  91. * Check if a node is already contained in the collection
  92. * @param {HTMLElement} element
  93. * @return {Boolean}
  94. */
  95. has (element) {
  96. return Array.from(this).includes(element)
  97. },
  98.  
  99. /**
  100. * Add an element or a list of elements to the collection
  101. * @param {mixed} element
  102. * @returns {this}
  103. */
  104. add (element) {
  105. const elements = element.length !== undefined ? element : [element]
  106.  
  107. Array.from(elements).forEach(element => {
  108. if (element && !this.has(element)) {
  109. Array.prototype.push.call(this, element)
  110. }
  111. })
  112.  
  113. return this
  114. },
  115.  
  116. /**
  117. * Find descendants of the current collection matching a selector
  118. * @param {String} selector
  119. * @return {this}
  120. */
  121. find (selector) {
  122. return Array.from(this).reduce(
  123. (carry, element) => carry.add(element.querySelectorAll(selector)),
  124. Object.create(prototype)
  125. )
  126. },
  127.  
  128. /**
  129. * Filter the current collection by a selector or filter function
  130. * @param {String|Function} selector
  131. * @return {this}
  132. */
  133. filter (selector) {
  134. return Object.create(prototype).add(
  135. Array.from(this).filter(
  136. typeof selector === TYPE.FUNCTION
  137. ? selector
  138. : element => element.matches(selector)
  139. )
  140. )
  141. },
  142.  
  143. /**
  144. * Get a collection containing the adjecent next siblings
  145. * of the current collection, optionally filtered by a selector
  146. * @param {String|undefined} selector
  147. * @return {this}
  148. */
  149. next (selector) {
  150. return Object.create(prototype).add(
  151. Array.from(this)
  152. .map(element => element.nextElementSibling)
  153. .filter(element => element && (!selector || element.matches(selector)))
  154. )
  155. },
  156.  
  157. /**
  158. * Get a collection containing the adjecent previous siblings
  159. * of the current collection, optionally filtered by a selector
  160. * @param {String|undefined} selector
  161. * @return {this}
  162. */
  163. prev (selector) {
  164. return Object.create(prototype).add(
  165. Array.from(this)
  166. .map(element => element.previousElementSibling)
  167. .filter(element => element && (!selector || element.matches(selector)))
  168. )
  169. },
  170.  
  171. /**
  172. * Get a collection containing the immediate parents of
  173. * the current collection, optionally filtered by a selector
  174. * @param {String|undefined} selector
  175. * @return {this}
  176. */
  177. parent (selector) {
  178. return Object.create(prototype).add(
  179. Array
  180. .from(this)
  181. .map(element => element.parentNode)
  182. .filter(element => !selector || element.matches(selector))
  183. )
  184. },
  185.  
  186. /**
  187. * Get a collection containing the immediate parents of the
  188. * current collection, or, if a selector is specified, the next
  189. * ancestor that matches that selector
  190. * @param {String|undefined} selector
  191. * @return {this}
  192. */
  193. parents (selector) {
  194. return Object.create(prototype).add(
  195. Array.from(this).map(function walk (element) {
  196. const parent = element.parentNode
  197.  
  198. return parent && (!selector || parent.matches(selector))
  199. ? parent
  200. : walk(parent)
  201. })
  202. )
  203. },
  204.  
  205. /**
  206. * Get a collection containing the immediate children of the
  207. * current collection, optionally filtered by a selector
  208. * @param {String|undefined} selector
  209. * @return {this}
  210. */
  211. children (selector) {
  212. return Object.create(prototype).add(
  213. Array
  214. .from(this)
  215. .reduce((carry, element) => carry.concat(...element.children), [])
  216. .filter(element => !selector || element.matches(selector))
  217. )
  218. },
  219.  
  220. //////////////////
  221. // Manipulation //
  222. //////////////////
  223.  
  224. /**
  225. * Add a class to all elements in the current collection
  226. * @param {String} className
  227. * @returns {this}
  228. */
  229. addClass (className) {
  230. Array.from(this).forEach(el => {
  231. el.classList.add(className)
  232. })
  233.  
  234. return this
  235. },
  236.  
  237. /**
  238. * Remove a class from all elements in the current collection
  239. * @param {String} className
  240. * @return {this}
  241. */
  242. removeClass (className) {
  243. Array.from(this).forEach(el => {
  244. el.classList.remove(className)
  245. })
  246.  
  247. return this
  248. },
  249.  
  250. /**
  251. * Set the value property of all elements in the current
  252. * collection, or, if no value is specified, get the value
  253. * of the first element in the collection
  254. * @param {mixed} newVal
  255. * @return {this}
  256. */
  257. val (newVal) {
  258. if (!newVal) {
  259. return this[0].value
  260. }
  261.  
  262. Array.from(this).forEach(el => {
  263. el.value = newVal
  264. })
  265.  
  266. return this
  267. },
  268.  
  269. /**
  270. * Set the HTML of all elements in the current collection,
  271. * or, if no markup is specified, get the HTML of the first
  272. * element in the collection
  273. * @param {String|undefined} newHtml
  274. * @return {this}
  275. */
  276. html (newHtml) {
  277. if (!newHtml) {
  278. return this[0].innerHtml
  279. }
  280.  
  281. Array.from(this).forEach(el => {
  282. el.innerHtml = newVal
  283. })
  284.  
  285. return this
  286. },
  287.  
  288. /**
  289. * Set the text of all elements in the current collection,
  290. * or, if no markup is specified, get the HTML of the first
  291. * element in the collection
  292. * @param {String|undefined} newText
  293. * @return {this}
  294. */
  295. text (newText) {
  296. if (!newText) {
  297. return this[0].textContent
  298. }
  299.  
  300. Array.from(this).forEach(el => {
  301. el.textContent = newText
  302. })
  303.  
  304. return this
  305. },
  306.  
  307. ///////////////////////
  308. // CSS and animation //
  309. ///////////////////////
  310.  
  311. /**
  312. * Hide all elements in the current collection
  313. * @return {this}
  314. */
  315. hide () {
  316. Array.from(this).forEach(element => {
  317. element.style.display = null
  318.  
  319. if (window.getComputedStyle(element).getPropertyValue(PROPERTY.DISPLAY) !== STYLE.HIDE) {
  320. element.style.display = STYLE.HIDE
  321. }
  322. })
  323.  
  324. return this
  325. },
  326.  
  327. /**
  328. * Show all elements in the current collection
  329. * @return {this}
  330. */
  331. show () {
  332. Array.from(this).forEach(element => {
  333. element.style.display = null
  334.  
  335. if (window.getComputedStyle(element).getPropertyValue(PROPERTY.DISPLAY) === STYLE.HIDE) {
  336. element.style.display = STYLE.SHOW
  337. }
  338. })
  339.  
  340. return this
  341. },
  342.  
  343. /**
  344. * Set the CSS of the elements in the current collection
  345. * by either specifying the CSS property and value, or
  346. * an object containing the style declarations
  347. * @param {String|object} style
  348. * @param {mixed} value
  349. * @return {this}
  350. */
  351. css (style, value) {
  352. const currentStyle = {}
  353.  
  354. if (typeof style === TYPE.STRING) {
  355.  
  356. if (!value) {
  357. return this[0] && window
  358. .getComputedStyle(this[0])
  359. .getPropertyValue(style)
  360. }
  361.  
  362. currentStyle[style] = value
  363. } else {
  364. Object.assign(currentStyle, style)
  365. }
  366.  
  367. Array.from(this).forEach(element => {
  368. Object.assign(element.style, currentStyle)
  369. })
  370.  
  371. return this
  372. },
  373.  
  374. /**
  375. * Fade the elements in the current collection in; optionally
  376. * takes the fade duration and a callback that gets executed
  377. * on each element after the animation finished
  378. * @param {Number|undefined} duration
  379. * @param {Function|undefined} callback
  380. * @return {this}
  381. */
  382. fadeIn (duration, callback) {
  383. Array.from(this).forEach(element => {
  384. fade(element, 0, 1, duration || DURATION.DEFAULT, callback)
  385. })
  386.  
  387. return this
  388. },
  389.  
  390. /**
  391. * Fade the elements in the current collection out; optionally
  392. * takes the fade duration and a callback that gets executed
  393. * on each element after the animation finished
  394. * @param {Number|undefined} duration
  395. * @param {Function|undefined} callback
  396. * @return {this}
  397. */
  398. fadeOut (duration, callback) {
  399. Array.from(this).forEach(element => {
  400. fade(element, 1, 0, duration || DURATION.DEFAULT, callback)
  401. })
  402.  
  403. return this
  404. },
  405.  
  406. ////////////
  407. // Events //
  408. ////////////
  409.  
  410. /**
  411. * Bind event listeners to all elements in the current collection,
  412. * optionally delegated to a target element specified as 2nd argument
  413. * @param {String} type
  414. * @param {Function|String} target
  415. * @param {Function|undefined} callback
  416. * @return {this}
  417. */
  418. on (type, target, callback) {
  419. const handler = callback
  420. ? function (event) {
  421. if (event.target.matches(target)) {
  422. callback.call(this, event)
  423. }
  424. }
  425. : target
  426.  
  427. Array.from(this).forEach(element => {
  428. const events = eventMap.get(element) || eventMap.set(element, {}).get(element)
  429.  
  430. events[type] = events[type] || []
  431. events[type].push(handler)
  432. element.addEventListener(type, handler)
  433. })
  434.  
  435. return this
  436. },
  437.  
  438. /**
  439. * Remove event listeners from the elements in the current
  440. * collection; if no handler is specified, all listeners of
  441. * the given type will be removed
  442. * @param {String} type
  443. * @param {Function|undefined} callback
  444. * @return {this}
  445. */
  446. off (type, callback) {
  447. Array.from(this).forEach(element => {
  448. const events = eventMap.get(element)
  449. const callbacks = events && events[type]
  450.  
  451. if (callback) {
  452. element.removeEventListener(type, callback)
  453.  
  454. if (callbacks) {
  455. events[type] = callbacks.filter(current => current !== callback)
  456. }
  457. } else if (callbacks) {
  458. delete events[type]
  459.  
  460. callbacks.forEach(callback => {
  461. element.removeEventListener(type, callback)
  462. })
  463. }
  464. })
  465.  
  466. return this
  467. },
  468.  
  469. ///////////////////
  470. // Miscellaneous //
  471. ///////////////////
  472.  
  473. /**
  474. * Execute a funtion on each element in the current collection
  475. * @param {Function} fn
  476. * @return {this}
  477. */
  478. each (fn) {
  479. Array.from(this).forEach(element => {
  480. fn.call(element)
  481. })
  482.  
  483. return this
  484. }
  485. }
  486.  
  487. /**
  488. * Create a new collection
  489. * @param {String} selector
  490. * @param {HTMLElement|undefined} context
  491. * @return {Object}
  492. */
  493. return function createCollection (selector, context) {
  494. const initial = typeof selector === TYPE.STRING
  495. ? (context || document).querySelectorAll(selector)
  496. : selector
  497.  
  498. const instance = Object.create(prototype)
  499.  
  500. return initial
  501. ? instance.add(initial)
  502. : instance
  503. }
  504. })()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement