Skip to content

aadswebdesign/tooltip_module

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

Docs for tooltipModule

   Why I have created this?

   It's for a combination of reasons:

  • The need of a tooltip feature for my github page and my moduleEditor where I'm working on.
  • Learning and get some experiences with the 'pointer api', to make such a feature.

   What I have created?

   A 'light weight', 'OOP' tooltipModule for use on module devices.
   I kept it simple, just a 'rectangle' that pops up as a replacement of the 'title'. attribute.

   Technical:

     The structure:

  • tooltipModule
    • factory
      • handlers
             ☛ pointer_events_handler.js 1
             ☛ pointer_evt_types_handler.js 2
      • module_functions.js 3
    • tooltip.css 4
    • tooltip_ctn.js 5
    • tooltip_module.js 6
    • tooltip_create.js 7
    • tooltip_actions.js 8

     Before I continue, a few notes first.

  • Using 'async iife''s in the 'class constructor' explained!
           ☛ What the 'class constructor' finally wants are objects!
           ☛ What an 'iife' finally produces is an object!
  • I never export classes directly!
    • I pass them to an arrow function and that is what I export.
    • Anytime I call those functions they are new, without using the keyword 'new' !
  • If I want objects, I use 'obj_args', otherwise I use 'args'!

     Then the code:

1: (pointerEventsHandler)

import * as MFT from './../mdl_functions.js';
class PointerEventsHandler{
  #evt_type;
  #evt_cb;
  #parent_el;
  constructor(...args){
    const [parent_el,evt_type,evt_cb,evt_options = false] = args
    this.#evt_type = evt_type;
    this.#evt_cb = evt_cb;
    this.#parent_el = parent_el ?? null;
    (async()=> {
      if(this.#parent_el !== null){
        this.#parent_el.addEventListener(this.#evt_type,this.#evt_cb,evt_options);
      }
    })();
  }
}
export const pointerEventsHandler = async (...args)=>{

2: (pointerEvtTypesHandler)

import * as MFT from './../mdl_functions.js';
import {pointerEventsHandler} from './pointer_events_handler.js';
class PointerEvtTypesHandler{
  #evt_type;
  #evt_cb_mouse;
  #evt_cb_pen;
  #evt_cb_touch;
  #parent_el;
  evt_target;
  constructor(obj_args){
    const {parent_el,evt_type,evt_cb_mouse,evt_cb_pen,evt_cb_touch,
    prevent_default = true,evt_options = false} = obj_args;
    this.#evt_cb_mouse = evt_cb_mouse ?? null;
    this.#evt_cb_pen = evt_cb_pen ?? null;
    this.#evt_cb_touch = evt_cb_touch ?? null;
    this.#evt_type = evt_type;
    this.#parent_el = parent_el ?? null;
    (async()=> {
      if(this.#parent_el !== null){
        const evt_manipulator= async(evt)=>{
        if(prevent_default === true){
          evt.preventDefault();
        }
        this.evt_target = evt.target;
        switch(evt.pointerType){
          case "mouse":{
            if(this.#evt_cb_mouse !== null){
              await this.#evt_cb_mouse(evt,this.evt_target);
            }
          }
          break;
          case "pen":{
            if(this.#evt_cb_pen !== null){
              await this.#evt_cb_pen(evt,this.evt_target);
            }
          }
          break;
          case "touch":{
            if(this.#evt_cb_touch !== null){
              await this.#evt_cb_touch(evt,this.evt_target);
            }
          }
          break;
          default:{
            console.log(`pointerType ${evt.pointerType} is not supported`);						
          }
        }
      }
      await pointerEventsHandler(this.#parent_el,this.#evt_type,evt_manipulator,evt_options);
     }
   })();
    //console.table({'PointerEvtTypesHandler': obj_args});
  }
}
export const pointerEvtTypesHandler = async (obj_args)=>{
  return new PointerEvtTypesHandler(obj_args);
};

3: (functions that I use all over the place)

export const addClass = async (...args)=>{
  const [elem,add_class,log = false]= args;
  let el;
  if(null !== elem){
    el = elem;
    if(!el.classList.contains(add_class)){
      el.classList.add(add_class);
    }
    if(log === true) console.log(`class ${add_class} added to: ${el}`);		
  }
  return await el;
};

export const addClasses = async (...args)=>{
  const [elem,classes=[]]= args;
  let el
  if(null !== elem){
    for(const cl of classes){
      el = await addClass(elem,cl);
    }	
  }
  return el;
};

export async function createElem(elem = null){
  if(null !== elem){
    return await document.createElement(elem);
  }	
}

export async function createObjects(...args){
  const [map_object = null, map_entries = null] = args;
  if(map_object !== null && map_entries !== null){
    const map = new Map([[map_object,map_entries]]);
    return map.get(map_object);
  }
  return null;
};

export const getBoundings = async (...args) =>{
  const [elem = null] = args;
  if(elem !== null){
    return elem.getBoundingClientRect();
  }
  return;
};

export const getTagNames = async (...args) => {
  const [tag, parent_el = null,log = false] = args
  let el;
  if(parent_el !== null){
    el = parent_el.getElementsByTagName(tag);
  }else{
    el = document.getElementsByTagName(tag);
  }
  if(log === true){
    console.log(`getTagNames(${tag})`,el);
  }
  return el;
}

export const uniqueArray = (array) => Array.from(new Set(array));

4: (It isn't that much and could be included in a already existed sylesheet too!)

.sticky{
  position: sticky;
  inset: 0;	
}	
tooltip-ctn{
  padding: 0.15rem 0.125rem;
  height: fit-content;
  width: fit-content;
  font-size: 0.65rem;
  color: #254f4b;
  background: #f9f6bd;  
  border:1px solid;
  border-radius: 0.2rem;
  white-space: nowrap;
  z-index: 100;
}

5: (next is a custom element and should be present in the js stack!)

//tooltipModule/tooltip_ctn.js
class TooltipCtn extends HTMLElement{
  constructor() {
    super();
  }
}
export function tooltipCtnDefine(){
  customElements.define("tooltip-ctn", TooltipCtn);
}

6: (tooltipModule)

//tooltipModule/tooltip_module.js
import * as MFT from './factory/mdl_functions.js';
import {tooltipCreate} from './tooltip_create.js';
class TooltipModule{
  #added_tags;
  get_tags;
  body;
  constructor(...args){
    const [added_tags] = args;
    this.#added_tags = added_tags ?? null;
    this.body = document.body;
    (async()=> {
      if(this.#added_tags !== null){
        this.get_tags = await MFT.getTagNames('*',this.#added_tags);
      }else
        this.get_tags = await MFT.getTagNames('*',this.body);
      if(this.get_tags.length > 0){
        for(const tag_elem of MFT.uniqueArray(this.get_tags)){
          await tooltipCreate(tag_elem,this.body);
        }
      }			
    })();
  }
}
export const tooltipModule = async (...args)=>{
  return new TooltipModule(...args);
}

7: (tooltipCreate)

//tooltipModule/tooltip_create.js
import * as MFT from './factory/mdl_functions.js';
import {tooltipActions} from './tooltip_actions.js';
class TooltipCreate{
  #body_el;
  #parent_el;
  create_elem;	
  constructor(...args){
    const[parent_el,body_el] = args;
    this.#body_el = body_el;
    this.#parent_el = parent_el ?? null;
    (async()=> {
      this.create_elem = await MFT.createElem('tooltip-ctn');
      this.create_elem.cloneNode(true);
      this.create_elem.role = 'tooltip';
      await MFT.addClasses(this.create_elem,['tooltip-ctn','fixed','display-inline']);
      if(this.#parent_el !== null && this.#parent_el.hasAttribute('title')){
        await tooltipActions(this.#parent_el,this.create_elem,this.#body_el);
      }	
    })();
    //console.table({'TooltipCreate': args});
  }
}
export const tooltipCreate = async (...args)=>{
  return new TooltipCreate(...args);
}

8: (tooltipActions)

//tooltipModule/tooltip_actions.js
import * as MFT from './factory/mdl_functions.js';
import {pointerEvtTypesHandler} from './factory/handlers/pointer_evt_types_handler.js';
class TooltipActions{
  #body_el;
  #tooltip_el;
  #parent_el;
  tt_down_enter_move;
  tt_out_up;
  constructor(...args){
    const[parent_el,tooltip_el,body_el] = args;
    this.#parent_el = parent_el ?? null;
    this.#tooltip_el = tooltip_el;
    this.#body_el = body_el;
    (async()=> {
      if(this.#parent_el !== null){
        const pointer_data = await MFT.createObjects('pointer_obj',{
          parent_el: this.#parent_el
        });
        pointer_data.down = {
          evt_type: 'pointerdown',
          evt_cb_mouse: (...args)=>{
            const [evt,evt_target] = args;
            /**
             *	pointerType.mouse I only use for testing					
             *	as this module is module devices only					
             */						
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_pen: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_touch: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          prevent_default: false,			
        };
        pointer_data.enter = {
          evt_type: 'pointerenter',
          evt_cb_mouse: (...args)=>{
            const [evt,evt_target] = args;
            /**
             *	pointerType.mouse I only use for testing					
             *	as this module is module devices only					
             */						
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_pen: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_touch: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },				
        };
        pointer_data.move = {
          evt_type: 'pointermove',
          evt_cb_mouse: (...args)=>{
            const [evt,evt_target] = args;
            /**
             *	pointerType.mouse I only use for testing					
             *	as this module is module devices only					
             */						
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_pen: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_touch: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_down_enter_move(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },			
        };
        pointer_data.out = {
          evt_type: 'pointerout',
          evt_cb_mouse: (...args)=>{
            const [evt,evt_target] = args;
            /**
             *	pointerType.mouse I only use for testing					
             *	as this module is module devices only					
             */						
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_pen: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_touch: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },				
        };
        pointer_data.up = {
          evt_type: 'pointerup',
          evt_cb_mouse: (...args)=>{
            const [evt,evt_target] = args;
            /**
             *	pointerType.mouse I only use for testing					
             *	as this module is module devices only					
             */						
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_pen: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },
          evt_cb_touch: (...args)=>{
            const [evt,evt_target] = args;
            this.tt_out_up(this.#parent_el,this.#tooltip_el,evt,evt_target);
          },				
        };
        const {parent_el,down,enter,move,out,up} = pointer_data;
        //here I smuggle a bit, because I have this on the todo list
        await Promise.all([
          pointerEvtTypesHandler({parent_el,...down}),
          pointerEvtTypesHandler({parent_el,...enter}),
          pointerEvtTypesHandler({parent_el,...move}),
          pointerEvtTypesHandler({parent_el,...out}),
          pointerEvtTypesHandler({parent_el,...up})
        ]);				
      }
    })();
  }
  tt_down_enter_move = (...args)=>{
    const [parent_el,tooltip_el,pointer_evt,evt_target] = args;
    (async()=> {
      const tt_data = await MFT.createObjects('tt_obj',{});
      if(evt_target.hasAttribute('title')){
        const body_data = await MFT.getBoundings(this.#body_el);
        tt_data.bd = {
          bd_width: body_data.width,	
          bd_right: body_data.right,	
        };
        const {bd_width,bd_right} = tt_data.bd;
        tt_data.evt = {
          evt_sX: pointer_evt.screenX,
          evt_sY: pointer_evt.screenY,
          evt_cX: pointer_evt.clientX,
          evt_cY: pointer_evt.clientY,
        };
        const {evt_sX,evt_sY,evt_cX,evt_cY} = tt_data.evt;
        const tooltip_el_data = await MFT.getBoundings(tooltip_el);
        tt_data.ted = {
          ted_width: tooltip_el_data.width,
          ted_height: tooltip_el_data.height,	
          ted_right: tooltip_el_data.right,	
        };
        const {ted_width,ted_height,ted_right} = tt_data.ted;
        tooltip_el.textContent = evt_target.title;
        const container = this.#body_el.firstElementChild;
        this.#body_el.insertBefore(tooltip_el,container);
        const target_left1 = evt_sX - evt_cX;
        let to_right = target_left1;
        if(ted_right >= bd_right){
          to_right = target_left1 +(ted_right - bd_right);
        }else{
          to_right = (target_left1 - 5);
        }
        const target_left2 = evt_sX - to_right;	
        tooltip_el.style.left = `${target_left2}px`;
        const target_height = evt_target.offsetHeight;
        const target_top1 = evt_sY - evt_cY;
        const target_top2 = evt_sY - (target_top1 + target_height + 5);
        tooltip_el.style.top = `${target_top2}px`;
      }
    })();
  }
  tt_out_up = (...args)=>{
    const [parent_el,tooltip_el = null,tooltip_evt,evt_target] = args;
    (async()=> {
      if(parent_el.hasAttribute('title')){
        if(tooltip_el !== null){
          tooltip_el.replaceWith('');
        }
      }
    })();
  }
}
export const tooltipActions = async (...args)=>{
  return new TooltipActions(...args);
}

   How to use?

  It's not complicated but there are some things to notice!\

  • Initial and after you have imported this module
    • await tooltipModule(); 1
  • If there are callbacks who add new elements to the domtree, they have to be added too!
    • await tooltipModule(parent_el); 2
// getting the parent of the added elements
const parent_el = document.getElementById('parent_id');
// or
const parent_el = title_elem.parentElement;
await tooltipModule(parent_el);

     A breakdown:

    ☛ 1: Here the 'tooltip module' gets all the tags of the html page at the time of invoking but has no knowledge about the elements that are appended after.
    ☛ 2: That's why the 'tooltip module' has to be re-invoked when new elements are appended to the domtree.

   module size:

  • js: 16kb
  • css: 1kb

About

A Javascript OOP based tooltip module for mobile devices that is just 17kb

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors