/ Gists / Vue.js

Gists - Vue.js

On gists

Composable simple storage

Vue.js

example.js #

// src/composables/counter.js
import { computed, readonly, ref } from 'vue';

const count = ref(0);
const next = computed(() => count.value + 1);
const plusOne = () => { count.value += 1 };

export function useCounter() {
  return {
    count: readonly(count),
    next,
    plusOne,
  };
}

On gists

Simple dragging

Vue.js

example.js #

// https://codesandbox.io/s/crimson-sky-7zozs?file=/src/main.js&ck_subscriber_id=1584215866&utm_source=convertkit&utm_medium=email&utm_campaign=%F0%9F%94%A5+%28%2366%29+Multiple+v-models+%28and+4+bonus+tips%21%29+-+8426354

<template>
  <div class="drag-container">
    <img
      alt="Vue logo"
      src="./assets/logo.png"
      :style="{
        left: `${x}px`,
        top: `${y}px`,
        cursor: dragging ? 'grabbing' : 'grab',
      }"
      draggable="false"
      @mousedown="dragging = true"
    />
  </div>
</template>

<script>
import { ref } from "vue";

export default {
  setup() {
    const dragging = ref(false);
    const mouseX = ref(0);
    const mouseY = ref(0);
    const x = ref(100);
    const y = ref(100);

    window.addEventListener("mousemove", (e) => {
      if (dragging.value) {
        const diffX = e.clientX - mouseX.value;
        const diffY = e.clientY - mouseY.value;
        x.value += diffX;
        y.value += diffY;
      }
      mouseX.value = e.clientX;
      mouseY.value = e.clientY;
    });

    window.addEventListener("mouseup", () => {
      dragging.value = false;
    });

    return {
      x,
      y,
      dragging,
    };
  },
};
</script>

<style scoped>
.drag-container {
  width: 100vw;
  height: 100vh;
}

img {
  cursor: grab;
  position: relative;
}
</style>

On gists

Dynamic classes

Vue.js

examples.js #

<div :class="disabled && 'disabled-component'"></div>

<div :class="disabled ? 'disabled-component' : 'not-yet-disabled'"></div>

<div
  :class="{
    primary: isPrimary,
    secondary: isSecondary,
    tertiary: isTertiary,
  }"
/>

<div
  :class="[
    isPrimary && 'primary',
    isSecondary && 'secondary',
    isTertiary && 'tertiary',
  ]"
/>

<div :class="computedClasses" />

<div :class="[computedClasses, { 'active': isActive }]"></div>


const computedClasses = computed(() => ({
  'text-red': isError.value,
  'text-green': isSuccess.value,
  'font-bold': shouldBeBold.value
}))

// or
const computedClasses = computed(() => [
  isError.value && 'text-red',
  isSuccess.value && 'text-green',
  shouldBeBold.value && 'font-bold'
].filter(Boolean))

On gists

Options API vs Composition API

Vue.js

fight.md #

Source:
https://medium.com/arcana-network-blog/vue-3-composition-api-basics-and-patterns-44813f2c785d

Options API vs Composition API

Mixin vs Composition API

Comparison


On gists

Composables vs Mixins

Vue.js

explanation.js #

/*
https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-composable/
*/

/*
Clarity of Data/Methods Source
Mixins = Data Source Obscured
Composables = Transparent Source of Data and Functions
*/

//MyComponent.vue
import ProductMixin from './ProductMixin'
import BrandMixin from './BrandMixin'
import UserMixin from './UserMixin'
export default{
    mixins:[ProductMixin, BrandMixin, UserMixin],
    created(){
        // Where in the world did name come from? 
        // Let me look through each of the registered mixins to find out
        // Oh man, it's not in any of them...
        // It must be from a globally registered mixin
        console.log(this.site)

        // Oh, I got lucky here turns out the first mixin I inspected has the name
        console.log(this.name)
    }
}

//MyComponent.vue
import useProduct from './useProduct'
import useBrand from './useBrand'
import useUser from './useUser'
export default{
    setup(){
        const { name } = useProduct()

        return { name }
  }
    created(){
        // Where in the world did name come from? 
        // ah, it's not in setup anywhere... this doesn't exist and is an error
        console.log(this.site)

        // Oh, nice I can see directly in setup this came from useProduct
        console.log(this.name)
    }
}



/*
Naming Collisions
Mixins = Risk of Naming Collisions
Composables = NO risk of naming collisions
*/

//MyComponent.vue

import ProductMixin from './ProductMixin' // name = AirMax
import BrandMixin from './BrandMixin' // name = Nike
import UserMixin from './UserMixin' // name = John Doe
export default{
    mixins:[ProductMixin, BrandMixin, UserMixin],
    created(){  
        // Actually I'm not so lucky,
        // yeah I found the name in ProductMixin
        // but turns out UserMixin had a name too
        console.log(this.name) // John Doe
    }
}

//MyComponent.vue
import useProduct from './useProduct' // name = AirMax
import useBrand from './useBrand' // name = Nike
import useUser from './useUser' // name = John Doe
export default{
    setup(){
        const { name: productName } = useProduct()
        const { name: brandName } = useBrand()
        const { name: userName } = useUser()

        return { productName, brandName, userName }
  }
    created(){
        // Yay! Nothing is lost and I can get the name of each of the things
        // together in my component but when writing the composables
        // I don't have to think at all about what variable names might collide
        // with names in other composables
        console.log(this.productName)
        console.log(this.brandName)
        console.log(this.userName)
    }
}


/*
Mutating Module's Reactive Data from the Component
Mixins = Can NOT Safeguard Own Reactive Data
Composables = Can Safeguard Own Reactive Data
*/
// RequestMixin.js
 export default {
  data(){
        return {
            loading: false,
            payload: null
        }
  },
    methods:{
        async makeRequest(url){
            this.loading = true
            const res = await fetch(url)
            this.payload = await res.json()
            this.loading = false
        }
    }
}

// useRequest.js
import { readonly, ref } from "vue";
export default () => {
    // data
  const loading = ref(false);
  const payload = ref(null);

    // methods
  const makeRequest = async (url) => {
    loading.value = true;
    const res = await fetch(url);
    payload.value = await res.json();
  };

    // exposed
  return {
    payload: readonly(payload), //notice the readonly here
    loading: readonly(loading), // and here
    makeRequest
  };
};



/*
Global State with Composables
*/
//CounterMixins.js
export default{
    data(){
        return { count: 0 }
    },
    methods:{
        increment(){
            this.count ++
        }
    }
}

//useCounter.js
import {ref, readonly} from 'vue'
export default () => {
  const count = ref(0)
    const increment = ()=> count.value++

    return {
        count: readonly(count), 
        increment
    }
}

//useCounter.js
import {ref, readonly} from 'vue'
const count = ref(0)
export default () => {
    const increment = ()=> count.value++

    return {
        count: readonly(count), 
        increment
    }
}

On gists

Props to css styles

Vue.js

component.vue #

 <HelloWorld color="red" bg="green" />

<template>
  <div :style="cssProps">KUK</div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: ['color', 'bg'],
  computed: {
    cssProps() {
      return {
        '--color': this.color,
        '--bg': this.bg,
      };
    },
  },
};
</script>

<style scoped>
div {
  color: var(--color);
  background: var(--bg);
}
</style>

On gists

How to pass props as initial data

Vue.js

app.js #

<script>
export default {
  //...
  props: {
    record: {
      type: Object,
      required: true,
    },
  },

  data() {
    return {
      recordLocal: { ...this.record },
    };
  },
  //...
};
</script>

On gists

How to reset a component’s initial data

Vue.js

app.vue #

<script>
const initialState = () => {
  return {
    modalBodyDisplay: "getUserInput",
    submitButtonText: "Lookup",
    addressToConfirm: null,
    bestViewedByTheseBounds: null,
    location: {
      name: null,
    },
  };
};

export default {
  data() {
    return initialState();
  },

  methods: {
    reset() {
      Object.assign(this.$data, initialState());
    },
  },
};
</script>

On gists

useScrollToBottom

Vue.js

useScrollToBottom.js #

// pagination list, load more (or lazy load) 

import { onMounted, onUnmounted } from 'vue';

export const useScrollToBottom = (callback = () => { }) => {
  const handleScrolling = () => {
    if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
      callback();
    }
  }

  onMounted(() => {
    window.addEventListener('scroll', handleScrolling);
  });

  onUnmounted(() => {
    window.removeEventListener('scroll', handleScrolling);
  });
}

// usage 
useScrollToBottom(() => { console.log('Scrolled to bottom') })

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>