🐁-Query

Build your own selector engine

Although I'm not a fan of jQuery, I find it extremely useful to work with a selector engine.

It is much more convenient and faster to work with $ than with document.querySelectorAll etc.

For this reason I have created a small library to make my work easier.

Ok, I'll show you how I did it:

makeOwnEngine.htm:

<!DOCTYPE html>
<html lang="de">

<head>
  <meta charset="UTF-8">
  <title>Selector Engine</title>
  <link type="text/css" href="makeOwnEngine.css" rel="stylesheet">
  <script type="text/javascript" src="makeOwnEngine.js"></script>
</head>

<body>
  <h1 id="test">Selector Engine</h1>
  <p class="sample-class">Sample Line 1</p>
  <p class="sample-class">Sample Line 2</p>
  
  <script>
    console.log($('#test'))
  </script>

</body>

</html>

makeOwnEngine.css

.bold {
  font-weight: bold;
}

.red {
  color: red;
}

.blue {
  color: blue;
}

.border {
  border:1px solid grey;
}

makeOwnEngine.js

"use strict";
const $ = function (selector = null){
  class selection {
    constructor (selector){
      if (selector) {
        this.nodes = 
          (selector === 'document') ? [document] :
          document.querySelectorAll(selector)
      }
    }
  }
  return selector = new selection(selector)
};

console ouput:

selection {nodes: NodeList(1)}
nodes: NodeList(1)
0: h1#test
length: 1
[[Prototype]]: NodeList
[[Prototype]]: Object

That's not particularly useful yet, we need a little more functionality.

First we have to be able to iterate over the nodelist, we add the following line to makeOwnEngine.js:

each = callback => (this.nodes.forEach( i => callback(i)), this)

Now I would like to assign styles to the elements:

To do this, I add another line to the makeOwnEngine.js:

addClass = classes => this.each(function (i) {
      i.classList.add(...classes.split(',')
      .map(s => s.trim()))}, this)  

the complete makeOwnEngine.js

"use strict";
const $ = function (selector = null){
  class selection {
    constructor (selector){
      if (selector) {
        this.nodes = 
          (selector === 'document') ? [document] :
          (selector.nodeType) ? [selector] :
          document.querySelectorAll(selector)
      }
    }
    each = callback => (this.nodes.forEach( i => callback(i)), this)
    addClass = classes => this.each(function (i) {
      i.classList.add(...classes.split(',')
      .map(s => s.trim()))}, this)  
  }
  return selector = new selection(selector)
};

Now i would assign style to the elements

makeOwnEngine.htm:

<!DOCTYPE html>
<html lang="de">

<head>
  <meta charset="UTF-8">
  <title>Selector Engine</title>
  <link type="text/css" href="makeOwnEngine.css" rel="stylesheet">
  <script type="text/javascript" src="makeOwnEngine.js"></script>

</head>

<body>
  <h1 id="test">Selector Engine</h1>
  <p class="sample-class">Sample Line 1</p>
  <p class="sample-class">Sample Line 2</p>
  <script>
    $('#test')
      .addClass('blue')
      .addClass('border')
    $('.sample-class').addClass('red,bold')
  </script>
</body>

</html>


Oh nice, chaining works 😃

This is the basis of my library, which I have expanded to include many more functions:

Samples:

on = (e,cb) => this.each(i => i.addEventListener(e, cb), this)  
// use  
$('.sample-class').on('click', ()=>console.log('click'))
// -------------------------
removeClass = c => this.each( e =>
      e.classList.remove(...c.split(',')
      .map(s => s.trim())),this)
// use
$('#test').removeClass('border')

more samples

var $ = (function (sel = null) { 
  class _{
    constructor(sel){
      if (sel){
      this.els = 
        (sel === 'document') ? [document] : 
        (sel === 'window') ? [window] : 
        (sel.nodeType) ? [sel] :
        document.querySelectorAll(sel)
      }
    }
    insSheet = sheet => $(document.head).create('style')
          .toNode().sheet.insertRule(sheet)
    //functions for Nodes!
    addClass = c => this.each(function (i) {
      i.classList.add(...c.split(',').map(s => s.trim()))}, this)  
    addOptions = o => this.each ( i => o.forEach((el,key) => i[key] = new Option(el,el)),this) 
    addStyle = s => this.each(e => e.setAttribute('style', 
      (e.getAttribute(`style`)==null)? s : e.getAttribute(`style`)+s), this)
    afterHtml = h => this.each( i => i.insertAdjacentHTML('afterend',h),this)
    append = (e,n) => $(e).toNode().appendChild(n) 
    beforeHtml = h => this.each( i => i.insertAdjacentHTML('beforebegin',h),this)
    click = cb => this.each(i => i.addEventListener('click', cb),this)
    create = t => $(this.els[0].appendChild(document.createElement(t)))
    each = cb => (this.els.forEach( i => cb(i)), this)
    find = e => $(this.els[0].querySelector(e))
    getData = a => this.els[0].getAttribute('data-' + a)
    getParent = e => $(this.els[0].parentNode)
    getValue = e => this.els[0].value
    hasClass = c => this.els[0].classList.contains(c)
    hasDataAtt = a => a in this.els[0].dataset
    html = h => this.each( i => i.innerHTML = h, this)
    inlineStyle = (s,v) => this.each(e => e.style[s]=v, this)
    insHtml = h => this.each( i => i.insertAdjacentHTML('beforeEnd',h), this)
    last = () => this.els[this.els.length-1]
    exists = () => this.els.length
    on = (e,cb) => this.each(i => i.addEventListener(e, cb), this)
    parentEl = c => $(this.els[0].closest(c))
    removeClass = c => this.each( e =>
      e.classList.remove(...c.split(',').map(s => s.trim())),this)
    removeStyle = () => this.each(e => e.removeAttribute('style'), this)
    resetStyles = () => this.each(e => e.setAttribute('style','all:unset;'), this)
    setData = (k,v) => (this.els[0].dataset[k]=v, this)
    setDataAll = (k,v) => this.each(e => e.dataset[k]=v, this)
    style = s => this.each(e => e.setAttribute('style',s), this)
    source = s => this.each(e => e.src = s, this)
    text = t => this.each( i => i.innerText = t, this)
    toggleClass = c => this.each(e => e.classList.toggle(c),this)   
    toNode = n => this.els[0]
    value = v => this.each( i => i.value=v, this)
    watch = (callback=null) => {
      let handleEvents = evt => handleValues(evt, evt.target)
      let fillValues = el => {
        if (el.dataset.filter)
                el.value = window[el.dataset.filter](el.value)
        if (el.dataset.source)
          $(`[data-target="${el.dataset.source}"]`).text(el.dataset.func ? window[el.dataset.func](el.value) : el.value)
      }
      let handleValues = (evt=null, el) => {
        switch ((evt == null) ? null : evt.type){
            case 'input':
              fillValues(el)
              break;
            case 'click':
              if (el.dataset.action) window[el.dataset.action](el)
              if (el.dataset.func) window[el.dataset.func](el)
              break;
            default:             
              fillValues(el)
          }
      }
      $(`[data-source]`).each(el=>handleValues(null, el));
      ['input', 'click'].forEach( ev => 
        $(this.els[0]).on(ev, e => handleEvents(e)))
      if (callback) callback()
      
      return this
    }
  }
  return sel => new _(sel)
})();

simple Image viewer:

const showPic = picUrl =>   
    $('body').create('div').
      inlineStyle('background-color', 'rgba(0,0,0,0.02)').
      inlineStyle('width', '100vw').
      inlineStyle('height', '100vh').
      inlineStyle('position', 'absolute').
      inlineStyle('top', '0').
      inlineStyle('left', '0').
      inlineStyle('display', 'block').
      inlineStyle('z-index', '5000').
      inlineStyle('backdrop-filter', 'blur(5px)').
      inlineStyle('display', 'flex').
      inlineStyle('align-items', 'center').
      inlineStyle('justify-content', 'center').  
      on('click', el => $(el.target).toNode().remove()).
      create('img').
        source(picUrl).
        inlineStyle('max-width', '80vw').
        inlineStyle('max-height', '80vh').
        inlineStyle('box-shadow', 'rgba(0, 0, 0, 0.35) 0px 5px 15px').
        on('click', el => $(el.target).
        parentEl('div').
        toNode().remove()).
      parentEl('div').
      create('p').
        insHtml('✕').
        inlineStyle('display', 'block').
        inlineStyle('padding', '10px').
        inlineStyle('font-size', '20px').
        inlineStyle('font-weight', 'bold').
        inlineStyle('cursor', 'pointer').
        inlineStyle('line-height', '20px').
        inlineStyle('margin', '0').
        inlineStyle('background-color', '#c00').
        inlineStyle('position', 'absolute').
        inlineStyle('top', '0').
        inlineStyle('left', '0').
        on('mouseover', el => $(el.target).inlineStyle('color', 'white')).
        on('mouseout', el => $(el.target).inlineStyle('color', 'black')).
        on('click', el => $(el.target).
            parentEl('div').
            toNode().remove()
          )

coded by Frank Wisniewski, Adenau