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>
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 });
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');
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}`);
}
// 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');
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();
}
<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>
<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>
/*
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>