/ Gists

Gists

On gists

Insert only when @var is true

MySql MySql tricks MySql - advanced

insert.sql #

SET @cnt = (SELECT COUNT(1) FROM image_size WHERE folder = '840x1190');

INSERT INTO `image_size` 
    (`id`, `folder`, `quality`, `xsize`, `ysize`, 
    `image_resize_id`, `diagonal`, `watermark__image_id`, 
    `watermark_xsize`, `watermark_ysize`, 
    `watermark_xpos`, `watermark_ypos`, 
    `crop_left`, `crop_top`, `trim`, `grayscale`, 
    `filter_color`, `filter_opacity`)
    
SELECT NULL, '840x1190', 95, 840, 1190, 
       0, NULL, NULL, 
       NULL, NULL, 
       NULL, NULL, 
       NULL, NULL, 0, 0, 
       NULL, NULL
WHERE @cnt = 0;

On gists

12 multiple watchers

cdruc vue

Page.vue #

<script setup>
import {reactive, watch} from "vue";
import BaseLabel from "./BaseLabel.vue";
import BaseInput from "./BaseInput.vue";
import BaseTextarea from "./BaseTextarea.vue";
import SecondaryButton from "./SecondaryButton.vue";
import BaseButton from "./BaseButton.vue";
import BaseCheckbox from "./BaseCheckbox.vue";
import BaseSwitch from "./BaseSwitch.vue";

const form = reactive({
  name: null,
  is_sellable: false,
  is_available_online: false,
  is_available_in_stores: false,
  price: null,
  description: null,
});

/**
 * Separate watchers
 */
watch(
  () => form.is_available_online,
  isAvailableOnline => {
    if (!isAvailableOnline && !form.is_available_in_stores) {
      form.is_sellable = false;
    }
  }
);

watch(
  () => form.is_available_in_stores,
  isAvailableInStores => {
    if (!isAvailableInStores && !form.is_available_online) {
      form.is_sellable = false;
    }
  }
);

/**
 * Watch multiple values in the same watch function
 */
watch(
  () => [form.is_available_online, form.is_available_in_stores],
  ([isAvailableOnline, isAvailableInStores]) => {
    if (!isAvailableOnline && !isAvailableInStores) {
      form.is_sellable = false;
    }
  }
);

/**
 * Combine watched values
 */
watch(
  () => form.is_available_online || form.is_available_in_stores,
  isSellable => (form.is_sellable = isSellable)
);
</script>

<template>
  <form class="bg-white p-6 rounded-xl shadow">
    <div class="space-y-6 sm:space-y-5">
      <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
        <BaseLabel required>Name</BaseLabel>
        <div class="mt-1 sm:mt-0 sm:col-span-2">
          <BaseInput
            v-model="form.name"
            placeholder="Enter product name"
            type="text"
          ></BaseInput>
        </div>
      </div>

      <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
        <BaseLabel>Can be sold?</BaseLabel>
        <div class="mt-1 sm:mt-0 sm:col-span-2">
          <BaseSwitch v-model="form.is_sellable"></BaseSwitch>
        </div>
      </div>

      <template v-if="form.is_sellable">
        <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
          <BaseLabel>Available in online stores</BaseLabel>
          <div class="mt-1 sm:mt-0 sm:col-span-2">
            <BaseCheckbox v-model:checked="form.is_available_online" />
          </div>
        </div>

        <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
          <BaseLabel>Available in physical stores</BaseLabel>
          <div class="mt-1 sm:mt-0 sm:col-span-2">
            <BaseCheckbox v-model:checked="form.is_available_in_stores" />
          </div>
        </div>

        <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
          <BaseLabel>Price</BaseLabel>
          <div class="mt-1 sm:mt-0 sm:col-span-2">
            <BaseInput
              v-model="form.price"
              placeholder="Enter price"
              type="number"
            ></BaseInput>
          </div>
        </div>
      </template>

      <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start">
        <BaseLabel>Description</BaseLabel>
        <div class="mt-1 sm:mt-0 sm:col-span-2">
          <BaseTextarea
            v-model="form.description"
            placeholder="Enter product description"
            rows="3"
          ></BaseTextarea>
        </div>
      </div>
    </div>

    <div class="pt-5">
      <div class="flex justify-end">
        <SecondaryButton type="button">Cancel</SecondaryButton>
        <BaseButton type="submit">Save</BaseButton>
      </div>
    </div>
  </form>
</template>

On gists

14 always use computed instead of functions

cdruc vue

old.vue #

<script setup>
import {ref, computed} from "vue";

const people = ref([
  {id: 1, firstName: 'Constantin', lastName: 'Druc'},
  {id: 2, firstName: 'Jack', lastName: 'Dorsey'},
  {id: 3, firstName: 'Bill', lastName: 'Burr'},
  {id: 4, firstName: 'Hugh', lastName: 'Jackman'},
  {id: 5, firstName: 'Tracey', lastName: 'Johnes'},
]);

function fullName(firstName, lastName) {
  console.log('fullName() was executed');
  return firstName + ' ' + lastName;
}
</script>

<template>
  <ul>
    <li v-for="person in people" :key="person.id">
      {{ fullName(person.firstName, person.lastName) }}
    </li>
  </ul>
</template>

On gists

10 communication parent -> child, siblings

cdruc vue

ParentChild.vue #

<script setup>
import ShoppingCart from "./ShoppingCart.vue";
import {ref} from "vue";
 
const isOpened = ref(false);
</script>
 
<template>
  <button @click="isOpened = true">Open cart</button>
  <ShoppingCart :is-opened="isOpened" @toggle="(value) => isOpened = value"/>
</template>

On gists

08 Videoplayer (skeleton) Composition API

cdruc vue

VideoPlayer.vue #

<script setup>
import axios from "axios";
import Player from "@vimeo/player";
import {throttle} from "lodash";
import ForwardIcon from "./svgs/ForwardIcon";
import BackwardIcon from "./svgs/BackwardIcon";
import LoadingIcon from "./svgs/LoadingIcon";
import {computed, onMounted, onUnmounted, ref, useSlots} from "vue";

const props = defineProps({
  lessonId: Number,
  trackProgress: Boolean
});

const speedIndex = ref(1);
let player = null;
const speedOptions = [0.75, 1, 1.25, 1.5, 1.75, 2.0];
const currentSpeed = computed(() => speedOptions[speedIndex.value]);

if (window.localStorage.getItem('speedIndex')) {
  speedIndex.value = parseInt(window.localStorage.getItem('speedIndex'));
}

onMounted(() => {
  player = new Player(useSlots().default()[0].el);
  player.on('play', () => player.setPlaybackRate(currentSpeed.value));

  if (props.trackProgress) {
    player.on('progress', throttle((event) => updateProgress(event.percent), 10000));
    player.on('ended', () => updateProgress(1));
  }
});

onUnmounted(() => {
  player.off('play');
  player.off('progress');
  player.off('ended');
});

function updateProgress(percent) {
  return axios.put(`/lessons/${props.lessonId}/progress`, {percent: percent});
}

function toggleSpeed() {
  if (typeof speedOptions[speedIndex.value + 1] !== "undefined") {
    speedIndex.value += 1;
  } else {
    speedIndex.value = 0;
  }

  window.localStorage.setItem('speedIndex', speedIndex.value);
  player.setPlaybackRate(currentSpeed.value);
}

async function goForward(seconds) {
  const currentTime = await player.getCurrentTime();
  const totalDuration = await player.getDuration();
  player.setCurrentTime(Math.min(totalDuration, (currentTime + seconds)));
}

async function goBack(seconds) {
  const currentTime = await player.getCurrentTime();
  player.setCurrentTime(Math.max(0, (currentTime - seconds)));
}
</script>

<template>
  <div class="relative aspect-w-16 aspect-h-9">
    <div class="flex absolute inset-0 justify-center items-center">
      <LoadingIcon class="w-14 h-14 text-white animate-spin"></LoadingIcon>
    </div>

    <slot></slot>
  </div>

  <div class="flex justify-end">
    <div class="grid grid-cols-5 w-full max-w-xl divide-x divide-gray-700">
      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="toggleSpeed()">
        <span class="hidden sm:inline-block">Speed:</span><span>{{ currentSpeed }}x</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goBack(5)">
        <BackwardIcon class="w-4 h-4"/>
        <span>5s</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goForward(5)">
        <span>5s</span>
        <ForwardIcon class="w-4 h-4"/>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goBack(10)">
        <BackwardIcon class="w-4 h-4"/>
        <span>10s</span>
      </button>

      <button
        class="inline-flex justify-center items-center p-2 space-x-1 text-base font-medium text-white cursor-pointer hover:text-orange-600 focus:outline-none"
        @click="goForward(10)">
        <span>10s</span>
        <ForwardIcon class="w-4 h-4"/>
      </button>
    </div>
  </div>
</template>

On gists

06 Loading script inside the Vue.js

cdruc vue

useScript.js #

import {onUnmounted} from "vue";

export default function (src) {
  return new Promise((resolve, reject) => {
    let script = document.querySelector(`script[src="${src}"]`);

    if (!script) {
      script = document.createElement("script");
      script.src = src;
      script.async = true;
      script.setAttribute("data-status", "loading");
      document.head.append(script);
    }

    if (script.getAttribute("data-status") === "loaded") {
      resolve();
    }

    function onScriptLoad() {
      resolve();
      script.setAttribute("data-status", "loaded");
    }

    function onScriptError() {
      reject();
      script.setAttribute("data-status", "error");
    }

    script.addEventListener("load", onScriptLoad);
    script.addEventListener("error", onScriptError);

    onUnmounted(() => {
      if (document.head.contains(script)) {
        script.removeEventListener("load", onScriptLoad);
        script.removeEventListener("error", onScriptError);
        document.head.removeChild(script);
      }
    })
  });
}

On gists

04 multiple v-model

cdruc vue

CheckoutForm.vue #

<template>
    <form class="my-20 mx-auto max-w-2xl">
        <h3 class="mb-2 mt-6 text-lg font-medium">Personal info</h3>
        <div class="grid grid-cols-2 gap-6 mb-6">
            <label class="block text-sm font-medium text-gray-700">
                First name
                <input 
                       class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500" 
                       type="text"
                       v-model="form.firstName"
                       placeholder="First name"
                />
            </label>

            <label class="block text-sm font-medium text-gray-700">
                Last name
                <input 
                       class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500" 
                       type="text"
                       v-model="form.lastName"
                       placeholder="Last name"
                />
            </label>

            <label class="block text-sm font-medium text-gray-700">
                E-mail
                <input 
                       class="block mt-1 w-full text-sm placeholder-gray-400 rounded-md border-gray-300 focus:ring-blue-500 focus:border-blue-500" 
                       type="email"
                       v-model="form.email"
                       placeholder="E-mail address"
                />
            </label>
        </div>
        
        <AddressFieldGroup
            label="Delivery address"
            v-model:street="form.deliveryAddress.street"
            v-model:streetNumber="form.deliveryAddress.streetNumber"
            v-model:postcode="form.deliveryAddress.postcode"
            v-model:city="form.deliveryAddress.city"
        />
        
        <AddressFieldGroup
            label="Billing address"
            v-model:street="form.billingAddress.street"
            v-model:streetNumber="form.billingAddress.streetNumber"
            v-model:postcode="form.billingAddress.postcode"
            v-model:city="form.billingAddress.city"
        />

    </form>
</template>

<script>
import {ref} from 'vue';
import AddressFieldGroup from './AddressFieldGroup.vue';

export default {
    components: {AddressFieldGroup},
    setup() {
        const form = ref({
            firstName: '321321',
            lastName: '321321',
            email: '321321',
            deliveryAddress: {
                street: '321321',
                streetNumber: '321',
                postcode: '321',
                city: '321',
            },
            billingAddress: {
                street: '',
                streetNumber: '',
                postcode: '',
                city: '',
            },
        });

        return {
            form,
        };
    },
};
</script>

On gists

03 copy to clipboard

cdruc vue

CopyToClipBoard.vue #

<script>
export default {
  data() {
    return {
      status: 'idle',
    };
  },
  methods: {
    copy(text) {
      // create textarea
      const el = document.createElement('textarea');
      
      // assign value
      el.value = text;
      
      // style textarea
      el.style.position = 'absolute';
      el.style.left = '-90000px';
      document.body.appendChild(el);

      // select its contents
      el.select();
      
      // execute copy
      document.execCommand("copy");
      
      // remove textarea
      document.body.removeChild(el);

      this.status = "copied";
      setTimeout(() => this.status = "idle", 1000);
    },
  },
  render() {
    return this.$slots.default({
      status: this.status,
      copy: this.copy
    });
  }
};
</script>

On gists

Promise.all

JavaScript

examples.js #

// Parallel
async function getData() {
    const [user, posts] = await Promise.all([
        fetchUserData(),
        fetchPostsData()
    ]);
    console.log(user, posts);
}


// non parallel
function getData() {
    return Promise.all([fetchUser(), fetchPosts()])
        .then(([user, posts]) => {
            console.log(user, posts);
        })
        .catch((error) => {
            console.error(error); 
        });
}

On gists

Content from Slot

Vue.js

TheTest.vue #

<script setup>
import   { useSlots, onMounted, ref } from "vue";

const test = ref()

onMounted(() => {
// 1
console.log(test.value.children[0])
 
 // 2 nebo bez refu primo ze slotu useSlots().default()[0].el
})
</script>

<template>
  <!-- nejde ref umistit primo na slot, nutno to takto obalit, nebo viz druhy zpusob -->
  <div ref="test"> 
  <slot />
  </div>
</template>