/ Gists / Vue.js

Gists - Vue.js

On gists

Vue directive + class tooltip (4 learning)

Vue.js

directive.js #

/* https://github.com/hekigan/vue-directive-tooltip */


/*
 * @author: laurent blanes <laurent.blanes@gmail.com>
 * @tutorial: https://hekigan.github.io/vue-directive-tooltip/
 */
import Tooltip from './tooltip.js';

const BASE_CLASS = 'vue-tooltip';
const POSITIONS = ['auto', 'top', 'bottom', 'left', 'right'];
const SUB_POSITIONS = ['start', 'end'];

/**
 * usage:
 *
 * // basic usage:
 * <div v-tooltip="'my content'">
 * or
 * <div v-tooltip="{content: 'my content'}">
 *
 * // change position of tooltip
 * // options: auto (default) | bottom | top | left | right
 *
 * // change sub-position of tooltip
 * // options: start | end
 *
 * <div v-tooltip.top="{content: 'my content'}">
 *
 * // add custom class
 * <div v-tooltip="{class: 'custom-class', content: 'my content'}">
 *
 * // toggle visibility
 * <div v-tooltip="{visible: false, content: 'my content'}">
 */
export default {
    name: 'tooltip',
    config: {},
    install (Vue, installOptions) {
        Vue.directive('tooltip', {
            bind (el, binding, vnode) {
                if (installOptions) {
                    Tooltip.defaults(installOptions);
                }
            },
            inserted (el, binding, vnode, oldVnode) {
                if (installOptions) {
                    Tooltip.defaults(installOptions);
                }

                let options = filterBindings(binding, vnode);
                el.tooltip = new Tooltip(el, options);

                if (binding.modifiers.notrigger && binding.value.visible === true) {
                    el.tooltip.show();
                }

                if (binding.value && binding.value.visible === false) {
                    el.tooltip.disabled = true;
                }
            },
            componentUpdated (el, binding, vnode, oldVnode) {
                if (hasUpdated(binding.value, binding.oldValue)) {
                    update(el, binding, vnode, oldVnode);
                }
            },
            unbind (el, binding, vnode, oldVnode) {
                el.tooltip.destroy();
            }
        });
    }
};

/**
 *
 * @param {*} vnode component's properties
 * @param {*} oldvnode component's previous properties
 * @return boolean
 */
function hasUpdated (value, oldValue) {
    let updated = false;

    if (typeof value === 'string' && value !== oldValue) {
        updated = true;
    } else if (isObject(value)) {
        Object.keys(value).forEach(prop => {
            if (value[prop] !== oldValue[prop]) {
                updated = true;
            }
        });
    }
    return updated;
}

/**
 * Sanitize data
 * @param {*} binding
 * @param {*} vnode
 * @return {*} filtered data object
 */
function filterBindings (binding, vnode) {
    const delay = !binding.value || isNaN(binding.value.delay) ? Tooltip._defaults.delay : binding.value.delay;

    if (binding.value.ref) {
        if (vnode.context.$refs[binding.value.ref]) {
            binding.value.html = vnode.context.$refs[binding.value.ref];
        } else {
            console.error(`[Tooltip] no REF element [${binding.value.ref}]`); // eslint-disable-line
        }
    }

    return {
        class: getClass(binding),
        id: (binding.value) ? binding.value.id : null,
        html: (binding.value) ? binding.value.html : null,
        placement: getPlacement(binding),
        title: getContent(binding),
        triggers: getTriggers(binding),
        fixIosSafari: binding.modifiers.ios || false,
        offset: (binding.value && binding.value.offset) ? binding.value.offset : Tooltip._defaults.offset,
        delay
    };
}

/**
 * Get placement from modifiers
 * @param {*} binding
 */
function getPlacement ({modifiers, value}) {
    let MODS = Object.keys(modifiers);
    if (MODS.length === 0 && isObject(value) && typeof value.placement === 'string') {
        MODS = value.placement.split('.');
    }
    let head = 'bottom';
    let tail = null;
    for (let i = 0; i < MODS.length; i++) {
        const pos = MODS[i];
        if (POSITIONS.indexOf(pos) > -1) {
            head = pos;
        }
        if (SUB_POSITIONS.indexOf(pos) > -1) {
            tail = pos;
        }
    }
    // console.log((head && tail) ? `${head}-${tail}` : head);
    // return 'auto';
    return (head && tail) ? `${head}-${tail}` : head;
}

/**
 * Get trigger value from modifiers
 * @param {*} binding
 * @return String
 */
function getTriggers ({modifiers}) {
    let trigger = [];
    if (modifiers.notrigger) {
        return trigger;
    } else if (modifiers.manual) {
        trigger.push('manual');
    } else {
        if (modifiers.click) {
            trigger.push('click');
        }

        if (modifiers.hover) {
            trigger.push('hover');
        }

        if (modifiers.focus) {
            trigger.push('focus');
        }

        if (trigger.length === 0) {
            trigger.push('hover', 'focus');
        }
    }

    return trigger;
}

/**
 * Check if the variable is an object
 * @param {*} value
 * @return Boolean
 */
function isObject (value) {
    return typeof value === 'object';
}

/**
 * Check if the variable is an html element
 * @param {*} value
 * @return Boolean
 */
function isElement (value) {
    return value instanceof window.Element;
}

/**
 * Get the css class
 * @param {*} binding
 * @return HTMLElement | String
 */
function getClass ({value}) {
    if (value === null) {
        return BASE_CLASS;
    } else if (isObject(value) && typeof value.class === 'string') {
        return `${BASE_CLASS} ${value.class}`;
    } else if (Tooltip._defaults.class) {
        return `${BASE_CLASS} ${Tooltip._defaults.class}`;
    } else {
        return BASE_CLASS;
    }
}

/**
 * Get the content
 * @param {*} binding
 * @return HTMLElement | String
 */
function getContent ({value}, vnode) {
    if (value !== null && isObject(value)) {
        if (value.content !== undefined) {
            return `${value.content}`;
        } else if (value.id && document.getElementById(value.id)) {
            return document.getElementById(value.id);
        } else if (value.html && document.getElementById(value.html)) {
            return document.getElementById(value.html);
        } else if (isElement(value.html)) {
            return value.html;
        } else if (value.ref && vnode) {
            return vnode.context.$refs[value.ref] || '';
        } else {
            return '';
        }
    } else {
        return `${value}`;
    }
}

/**
 * Action on element update
 * @param {*} el Vue element
 * @param {*} binding
 */
function update (el, binding, vnode, oldVnode) {
    if (typeof binding.value === 'string') {
        el.tooltip.content(binding.value);
    } else {
        if (binding.value && binding.value.class && binding.value.class.trim() !== el.tooltip.options.class.replace(BASE_CLASS, '').trim()) {
            el.tooltip.class = `${BASE_CLASS} ${binding.value.class.trim()}`;
        }

        el.tooltip.content(getContent(binding, vnode));

        if (!binding.modifiers.notrigger && binding.value && typeof binding.value.visible === 'boolean') {
            el.tooltip.disabled = !binding.value.visible;
            return;
        } else if (binding.modifiers.notrigger) {
            el.tooltip.disabled = false;
        }

        const dir = vnode.data.directives[0];

        if (dir.oldValue.visible !== dir.value.visible) {
            if (!el.tooltip.disabled) {
                el.tooltip.toggle(dir.value.visible);
            }
        }
    }
}

On gists

Composition API - examples

Vue.js

composition.vue #

<template>
  <div>
    <h2 class="text-h4">{{ name }}</h2>
    <button @click="score++" class="btn">
      {{ score }} twice is {{ double }}
    </button>

    <hr />

    <button @click="score2++" class="btn">
      {{ score2 }} triple is {{ triple }}
    </button>

    <hr />
    <ul>
      <li v-for="data in dataList" :key="data.id">{{ data.name }}</li>
    </ul>

    <hr />

    <button v-if="!divVisible" @click="showDiv">Show div</button>
    <button v-if="divVisible" @click="hideDiv">Hide div</button>
    <div v-if="divVisible" class="bg-red-100">My super hidden div :)</div>

    <hr />
    <form @submit.prevent="submitForm">
      <input type="text" v-model="title" />
    </form>
  </div>
</template>

<script>
import { ref, computed, reactive, toRefs, onMounted } from 'vue'
import { data } from '@/frontapp/components/data.js'

export default {
  components: {},
  props: ['name'],

  // props, context nebo { emit } nebo { emit, slots }
  setup(props, { emit }) {
    console.log(props, 'our props')

    // data
    const score = ref(1)
    const dude = ref('Kcko')
    const dataList = ref(data)

    const state = reactive({
      score2: 12,
      triple: computed(() => state.score2 * 3),
      divVisible: false
    })

    // computed
    const double = computed(() => score.value * 2 + 7)
    const hello = computed(() => 'Hello, I am ' + dude.value)

    // methods
    const showDiv = () => {
      state.divVisible = true
    }
    const hideDiv = () => {
      state.divVisible = false
    }

    // v nadrazenem prvku / komponentne nebo  standardne -> view @new-list-coming="nejakaMetoda($event)"
    const title = ref('')
    const submitForm = () => {
      emit('new-list-coming', title.value)
    }

    onMounted(() => {
      console.log('Mounted yeah!')
    })

    return {
      score,
      double,
      hello,
      ...toRefs(state),
      dataList,
      showDiv,
      hideDiv,
      submitForm
    }
  }
}
</script>

On gists

Component - default + merged options

Vue.js

BcVideo.vue #

<template>
  <video-player v-bind="mergedVideoSettings" />
</template>

<script>
import VideoPlayer from './VideoPlayer.local.vue'
export default {
  props: {
    videoSettings: Object
  },
  components: {
    VideoPlayer
  },
  computed: {
    mergedVideoSettings() {
      return {
        ...this.defaultVideoSettings,
        ...this.videoSettings
      }
    }
  },
  data() {
    return {
      defaultVideoSettings: {
        mask: false,
        colors: 'var(--primaryColor)'
      }
    }
  },
  mounted() {}
}
</script>

<style lang="scss" scoped></style>

On gists

v-model, Parent <-> Child 2 way databinding

Vue.js

1.vue #

<!--Emit + props-->

<!--PARENT-->
<template>
  <div>
    <h1>A1 - parent</h1>
    <input
      :value="time"
      @input="time = $event.target.value"
      class="border border-gray-400"
    />
    <b1 @on-parent="this.time = $event" :time="time" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      time: null
    }
  },
  methods: {},
  computed: {},
  mounted() {}
}
</script>



<!--CHILD-->
<template>
  <div>
    <h1>B1 - child</h1>
    <input
      :value="time"
      @input="sendData($event.target.value)"
      class="border border-gray-400"
    />
  </div>
</template>

<script>
export default {
  emits: ['on-parent'],
  props: ['time'],
  data() {
    return {}
  },
  methods: {
    sendData(val) {
      this.$emit('on-parent', val)
    }
  },
  computed: {},
  mounted() {}
}
</script>


On gists

Use Global Utility Methods For DRYer Code

Vue.js

utils.js #

// import store from '../store' <-- To access your Vuex store
import Vue from 'vue' // <-- used for vue-toastification

class Utils {
  // Copy a string to user's clipboard
  copyToClipboard(text) {
    let copyText = document.createElement('input')
    document.body.appendChild(copyText)
    copyText.value = text
    copyText.select()
    document.execCommand('copy')
    document.body.removeChild(copyText)

    // Show toast on copy success
    // (using the vue-toastification package here)
    Vue.$toast.success('Copied address to clipboard: ' + text, {
      position: 'top-right',
      timeout: 3000
    })
  }
}

export default new Utils()

On gists

Call a Child Component’s Method from Parent

Vue.js

component.vue #

// Parent.vue
<template>
  <ChildComponent ref="child" />
</template>


// In Parent.vue's methods
this.$refs.child.methodName()

On gists

Refresh (Reload) a Specific Component Dynamically

Vue.js

component.vue #

<template>
  <component-to-re-render :key="reloadMe" />
</template>

<script>
export default { 
  data() { 
    return { 
      reloadMe: 0, 
    }
  }; 
    
  methods: { 
    forceRerender() { this.reloadMe += 1; } 
  } 
}
</script>

On gists

VUE - Directives

Vue.js

Directives.vue #

export default {
  install (Vue) {

    Vue.directive('test-demo', {
      mounted(el, binding) {
        
        const modifiers = binding.modifiers
        const state = binding.arg === "a" ? "a" : "b"

        if (state === 'a') {
          el.style.color = 'red'
        } else {
          el.style.color = 'green'
        }

        if (modifiers.underline) {
          el.style.textDecoration = "underline"
        }
        if (modifiers.overline) {
          el.style.textDecoration = "overline"
        }

      }
    })

    Vue.directive('width', {
      mounted: function (el, binding) {
        el.style.width = binding.value + 'px'
      }
    })

    // mounted + update both
    Vue.directive('color-swatch', function (el, binding) {
      el.style.backgroundColor = binding.value
    })

    Vue.directive('demo', function (el, binding) {

      el.style.color = binding.value.color // => "white"
      el.textContent = binding.value.text
    })
  }

}