DEV Community

Aad Pouw
Aad Pouw

Posted on

Some js here for a change (getElemsByDataset)!

Explanation:

As I'm working on the development
of a contentEditable based editor,
I make use of the dataset
attribute intensively.

Mostly I work with the
DOM tree directly but there are situations that I have to collect the elements.

For that I use
document.getElementsByTagname (this wrapped in a method,called 'getTagNames()').

It's okay and I get things
done with it. Just sometimes it would be more handy if could collect a bunch of elements with different tag names, at once!. Sure I can use document.getElementsByClassname but that also has its pitfalls.

Therefor I began
to think, why is there not
something like
'*.getElementsByDatasetNameValue'?
Because then you can give a group of elements a related dataset name/value and collect them with one call, regardless of their 'tagnames'.

As it kept me busy, I gave it a closer look.
I began with reading about those built-inn HTML methods and how they are built first. For this I had a look at whatwg.org#concept-getelementsbytagname / 4.5. Interface Document. Interesting to get some idea of how they are built but that's it.

Then I asked myself, can I create something like that with js and I gave it a try?

The steps:

One:

I created a class and
a related export function for it 'GetElemsByDataset \ getElemsByDataset' \
    "I have kept 'NameValue' left out of it because this is clear and long enough."

Two:

☛ Find a way to
collect the elements for it first and this I did by creating this method.

/*
 * @action: collecting the tags.
 * @notice: It is possible to omit a parent element
   but not recommended because that could become a huge list of elements.
 */
get_tag_names = async (...args) => {
  const [prt_el = null, log = false] = args
  let el;
  if(prt_el !== null)
    el = prt_el.getElementsByTagName('*');
  else
    el = document.getElementsByTagName('*');
  if(log === true)
    console.log('getTagNames(*)',el);
  return el;
}

Three:

☛ Creating from this obtained values a 'for of loop', and collect the elements based on 'dataset names and values' within the class constructor.

constructor(...args){
  const [prt_el,data_name, data_val,log = false] = args;
  this.#dn = data_name;
  this.#dv = data_val ?? null;
  this.#prt_el = prt_el ?? null;
  this.#get_tags = [];
  (async()=> {
    if(this.#prt_el !== null){
      /*
       * @action: collecting the tags.
       * @description: this method is using the '*' to collect all the available elements from the parent element and are placed in a 'for of'.
       */
       const tags = await this.get_tag_names(this.#prt_el,log);
       for(const tag of this.unique_array(tags)){
         /*
          * @action: collecting  the elements based on a dataset 'name' or 'name/value pair'.
          */
          if(tag.dataset[this.#dn] !== undefined){
            if(this.#dv === null){
              tag.dataset[this.#dn];
              this.#get_tags.push(tag);
           }else if(tag.dataset[this.#dn] === this.#dv){ 
             tag.dataset[this.#dn] = this.#dv;
             this.#get_tags.push(tag);
           }
        }
      }
      const gt = this.unique_array(this.#get_tags);
      if(log === true)
        console.log('elems: ', gt );
    }       
  })();
}

How to use:

  1. just copy/paste it, place it in your script folder and use 'import' to connect to it (how you do that is up to you).
  2. it accepts four arguments.
    • parent_el: ☛ This is the nearest element that holds the sub elements with the desired dataset names.
    • data_name: ☛ Here you pass the dataset name like this 'fooBar' but not 'data-foo-bar' or 'foo-bar'!
    • data_val: ☛ Here you can pass a dataset value! This is optional.
    • log: ☛ standard value is 'false', when set to true it shows the relevant data in your dev tools.

Example/Use Case:

☛ This is a class of a new project that I just started last Wednessday. This class uses also some other methods but the focus here is getting the elements by dataset 'name/value'.

/*
 * @action: creates & collects objects for later use elsewhere. 
 * @description: 
*/
constructor(obj_args){
  const {get_prts} = obj_args;
  const create_obj = MH.createObjects;
  const remove_obj = MH.removeObjects;
  const ctb = MH.createTreeBlock;
  const gb = MH.getBoundings;
  //where I focus on.
  const gebd = MH.getElemsByDataset;
  (async()=> {
    const dom_data = await create_obj('dom_obj',{...obj_args});
    await remove_obj(['grant_data','get_prts'],dom_data);
    if(get_prts.length > 0){
      for(const prt_ctn of get_prts){
       //where the magic happens!
       this.layers = await gebd(prt_ctn,'topLayer');
       if(this.layers.length > 0){
         const layers = this.layers;
         for(const layer of layers){
          const prefs = layer.dataset.prefix;
          //another magical method but not the focus here!
          await ctb(gb,layer,dom_data,`${prefs}_ctn_data`,prefs,3,true);
          }
          await DE.dataTopLayer(dom_data);
          await DE.dataMainLayer(dom_data);
          await DE.dataBtmLayer(dom_data);
        }
      }
    }
  })();
console.table({'DTLS': obj_args});
}

This results in:

    (3) ➜ top_ctn
<section class="relative" data-top-layer="df-top-ctn" data-prefix="top">

    (3) ➜ main_ctn
<main class="relative" role="presentation" data-top-layer="df-main-ctn" data-prefix="main">

    (3) ➜ btm_ctn
<section data-top-layer="df-bottom-ctn" data-prefix="btm">

The goal of this method!

top_ctn_data
  Object { top_ctn: section.relative, top_ctn_dims: DOMRect }   
main_ctn_data
  Object { main_ctn: main.relative, main_ctn_dims: DOMRect }
btm_ctn_data
  Object { btm_ctn: section, btm_ctn_dims: DOMRect }

You will find it here:

   ☛ getElemsByDataset:

That's it!

Top comments (0)