/ Gists / Vue.js

Gists - Vue.js

On gists

useOnClickOutside

Vue.js

useOnClickOutside.js #

import { onMounted, onUnmounted } from 'vue';

export const useOnClickOutside = (ref = null, callback = () => {}) => {
  function handleClickOutside(event) {
    if (ref.value && !ref.value.contains(event.target)) {
      callback()
    }
  }

  onMounted(() => {
    document.addEventListener('mousedown', handleClickOutside);
  })

  onUnmounted(() => {
    document.removeEventListener('mousedown', handleClickOutside);
  });
}

// usage

<template>
    <div ref="container">View</div>
</template>
<script>
import { ref } from 'vue';
export default {
    setup() {
        const container = ref(null);
        useOnClickOutside(container, () => {
            console.log('Clicked outside'); 
        })
    }
}
</script>

On gists

useViewport

Vue.js

useViewport.js #

import { ref, onMounted, onUnmounted } from 'vue';

export const MOBILE = 'MOBILE'
export const TABLET = 'TABLET'
export const DESKTOP = 'DESKTOP'

export const useViewport = (config = {}) => {
  const { mobile = null, tablet = null } = config;
  let mobileWidth = mobile ? mobile : 768;
  let tabletWidth = tablet ? tablet : 922;
  let device = ref(getDevice(window.innerWidth));
  function getDevice(width) {
    if (width < mobileWidth) {
      return MOBILE;
    } else if (width < tabletWidth) {
      return TABLET;
    }
    return DESKTOP;
  }

  const handleResize = () => {
    device.value = getDevice(window.innerWidth);
  }

  onMounted(() => {
    window.addEventListener('resize', handleResize);
  });

  onUnmounted(() => {
    window.removeEventListener('resize', handleResize);
  });

  return {
    device
  }
}


// usage
const { device } = useViewport({ mobile: 700, table: 900 });

On gists

useCopyToClipboard

JS oneliners Vue.js

useCopyToClipboard.js #

function copyToClipboard(text) {
  let input = document.createElement('input');
  input.setAttribute('value', text);
  document.body.appendChild(input);
  input.select();
  let result = document.execCommand('copy');
  document.body.removeChild(input);
  return result;
}

export const useCopyToClipboard = () => {
  return (text) => {
    if (typeof text === "string" || typeof text == "number") {
      return copyToClipboard(text);
    }
    return false;
  }
}

// usage
const copyToClipboard = useCopyToClipboard();
copyToClipboard('just copy');

On gists

useNetworkStatus

Vue.js

useNetworkStatus.js #

import { onMounted, onUnmounted } from 'vue';

export const useNetworkStatus = (callback = () => { }) => {
  const updateOnlineStatus = () => {
    const status = navigator.onLine ? 'online' : 'offline';
    callback(status);
  }

  onMounted(() => {
    window.addEventListener('online', updateOnlineStatus);
    window.addEventListener('offline', updateOnlineStatus);
  });

  onUnmounted(() => {
    window.removeEventListener('online', updateOnlineStatus);
    window.removeEventListener('offline', updateOnlineStatus);
  })
}


// usage
useNetworkStatus((status) => { 
    console.log(`Your network status is ${status}`);
}

On gists

useStorage

Vue.js

useStorage.js #

// https://javascript.plainenglish.io/10-useful-custom-hooks-with-vue-js-37f0fd42ce0d

import { ref } from 'vue';

const getItem = (key, storage) => {
  let value = storage.getItem(key);
  if (!value) {
    return null;
  }
  try {
    return JSON.parse(value)
  } catch (error) {
    return value;
  }
}

export const useStorage = (key, type = 'session') => {
  let storage = null;
  switch (type) {
    case 'session':
      storage = sessionStorage;
      break;
    case 'local':
      storage = localStorage;
      break;
    default:
      return null;
  }
  const value = ref(getItem(key, storage));
  const setItem = (storage) => {
    return (newValue) => {
      value.value = newValue;
      storage.setItem(key, JSON.stringify(newValue));
    }
  }
  return [
    value,
    setItem(storage)
  ]
}


// usage
const [token, setToken] = useStorage('token');
setToken('new token');

On gists

useWindowResize

Vue.js

useWindowResize.js #

import { ref, onMounted, onUnmounted } from 'vue';

export function useWindowResize() {
  const width = ref(window.innerWidth);
  const height = ref(window.innerHeight);
  const handleResize = () => {
    width.value = window.innerWidth;
    height.value = window.innerHeight;
  }

  onMounted(() => {
    window.addEventListener('resize', handleResize)
  });

  onUnmounted(() => {
    window.removeEventListener('resize', handleResize)
  })

  return {
    width,
    height
  }
}


// usage
setup() {
    const { width, height } = useWindowResize();
}

On gists

$attrs

Vue.js

index.vue #

<template>
  <div>
    <p>
      <custom-checkbox>Simple example</custom-checkbox>
    </p>
    <p>
      <custom-checkbox :disabled="true">Disabled</custom-checkbox>
    </p>
  </div>
</template>

<script>
import CustomCheckbox from './CustomCheckbox'
export default {
  components: { CustomCheckbox }
}
</script>

<style scoped>

</style>

On gists

Recursion

Vue.js

index.vue #

<template>
  <div>
    <tree-list
      :children="treeItems"
      :parents="[]"
    />
  </div>
</template>

<script>
  import TreeList from './TreeList.vue'

  export default {
    components: {TreeList},
    compontents: {
      TreeList
    },
    data () {
      return {
        treeItems: [
          {
            title: 'First top level',
            children: [
              {
                title: 'First sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Second sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Third sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              }
            ]
          },
          {
            title: 'Second top level',
            children: [
              {
                title: 'First sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Second sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Third sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              }
            ]
          },
          {
            title: 'Third top level',
            children: [
              {
                title: 'First sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Second sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              },
              {
                title: 'Third sub level',
                children: [
                  {title: 'First sub-sub level'},
                  {title: 'Second sub-sub level'},
                  {title: 'Third sub-sub level'}
                ]
              }
            ]
          }
        ]
      }
    }
  }
</script>

<style scoped>

</style>

On gists

V-model in loops and computed

Vue.js

App.vue #

/* 
  https://stackblitz.com/edit/superlasice-efzixd?file=src%2FApp.vue 
*/

<template>
  <div id="app">
    <button v-on:click="add">add new row</button>
    <p>Total price {{ total }} | {{ total2 }}</p>

    <ul>
      <li v-for="(item, index) in items">
        Name<br />
        <input type="text" v-model="item.name" />
        <br />

        Quantity<br />
        <input type="number" v-model.number="item.quantity" min="1" />
        <br />

        Price<br />
        <input
          type="number"
          v-model.number="item.price"
          min="0.00"
          max="10000"
          step="1"
        />
        <br />

        Total (readonly) <br />
        <input v-model="totalItems[index]" readonly /> <br />

        total in row:
        {{ totalItem(item) }}
        <br />
        <br />

        <button v-on:click="remove(index)">Delete row</button>
        <hr />
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      items: [
        { name: 'A', quantity: 1, price: 20 },
        { name: 'B', quantity: 2, price: 30 },
      ],
    };
  },
  methods: {
    add() {
      this.items.push({
        name: 'New product',
        quantity: 0,
        price: 0,
      });
    },
    remove(index) {
      this.items.splice(index, 1);
    },
    totalItem(item) {
      return item.price * item.quantity;
    },
  },
  computed: {
    totalItems() {
      let arr = [];
      this.items.forEach((item) => {
        arr.push(parseFloat(item.price) * parseFloat(item.quantity));
      });

      return arr;
    },
    total() {
      let sum = 0;
      this.items.forEach((item) => {
        sum += parseFloat(item.price) * parseFloat(item.quantity);
      });

      return sum;
    },
    // another approach how to sum
    total2() {
      return this.items.reduce((prev, item) => {
        return prev + item.price * item.quantity;
      }, 0);
    },
  },
};
</script>

On gists

Resize window

Vue.js

resize-window.vue #

methods: {
  resizeWindow() {
    window.dispatchEvent(new Event('resize'))
  }
},
mounted() {
  this.resizeWindow()
}