/ Gists

Gists

On gists

Advanced Patterns: Mixins and Composition

JavaScript-OOP JavaScript

composition.js #

// https://medium.com/@genildocs/mastering-object-oriented-programming-in-javascript-from-zero-to-hero-c718c3182eba

// Mixin for logging functionality
const LoggerMixin = {
  log(message) {
    console.log(`[${this.constructor.name}] ${message}`);
  },

  logError(error) {
    console.error(`[${this.constructor.name}] ERROR: ${error}`);
  }
};

// Mixin for validation
const ValidatorMixin = {
  validate(data, rules) {
    for (const [field, rule] of Object.entries(rules)) {
      if (!rule(data[field])) {
        return { valid: false, field };
      }
    }
    return { valid: true };
  }
};

class UserService {
  constructor() {
    // Apply mixins
    Object.assign(this, LoggerMixin, ValidatorMixin);
  }

On gists

Reactivity in composables

Popular ⭐ Vue.js

App.vue #

<template>
  <TheBase :state="state" />
</template>


<script setup>
import { ref } from 'vue'
import TheBase from './TheBase.vue'


const state = ref(111)

setTimeout(() => {
  state.value = 222
}, 2000)
</script>

On gists

3 Vanilla-Lite DOM Patterns That Achieve jQuery’s Ease

JavaScript-OOP JavaScript

1.js #

// Pattern 1 — Bind Helper

export function S(selector) {
  const nodes = document.querySelectorAll(selector);
  nodes.forEach(bindAll); // Bind helpers to node(s)

  return nodes.length > 1 ? nodes : nodes[0]; // native node(s) returned
}

export function C(tag) {
  const node = document.createElement(tag);
  return bindAll(node); // Bind helpers to node(s)
}

function bindAll(node) {
  node.addClass = addClass.bind(node);
  node.attr = attr.bind(node);

  return node;
}

//--- Extension functions ---//
function addClass(...cls)   {
  this.classList.add(...cls);
  return this;
}

function attr(key, val) {
  if (val !== undefined) {
    this.setAttribute(key, val);
    return this; 
  }

  return this.getAttribute(key);
}


// Usage:
import { S, C } from './helper.js';

const btn = C('button').addClass('btn', 'primary').attr('type', 'submit');
btn.click(); // native method

S('.card').forEach(el => el.addClass('highlight').attr('data-live', '1'));

On gists

Slots drilling

Vue.js

better-way.vue #

<!-- Parent -->
  <script setup>
    provide('transportFree', useSlots().transportfree)  // slot existuje i kdyz ho vubec nepouziju v komponente, ve smyslu <slot name="transportfree" />, staci zvenci <template #transportfree>content</template>
 </script>
 
 <!-- Child -->
 <script setup>
    const transportFree = inject('transportFree')
 </script>
 <template>
    <component :is="transportFree" />
 </template>

On gists

bete

bete #

_

On gists

Starting style

CSS CSS trick

style.css #

/* https://jsbin.com/fayirefaqu/2/edit?html,css,js,output */


.modal {
  /* Hidden state */
  display: none;
  opacity: 1;
}

.modal.open {
  display: block;
  /* Regular transition works now */
  transition: opacity 300ms;
  opacity: 1;
}

/* Define starting styles when modal becomes displayed */
@starting-style {
  .modal.open {
    opacity: 0;
  }
}

On gists

Compute & transform logic outside of UI component

Popular ⭐ Vue.js

index.md #

Vue.js Media Transformer - Všechny varianty implementace

1. Composable (Vue 3 Composition API)

// composables/useMediaObjects.js
export function useMediaObjects() {
  const transformMediaObjects = (productGallery, productVideos) => {
    const finalObjects = []

    // Photos
    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    // Videos
    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  const extractYouTubeId = (url) => {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  return {
    transformMediaObjects,
    extractYouTubeId
  }
}

Použití v komponentě:

// Product.vue
<script setup>
import { computed } from 'vue'
import { useMediaObjects } from '@/composables/useMediaObjects'

const props = defineProps(['productGallery', 'product'])

const { transformMediaObjects } = useMediaObjects()

const mediaObjects = computed(() => {
  return transformMediaObjects(props.productGallery, props.product?.product_video)
})
</script>

2. JS Třída - Statické metody

// services/MediaTransformer.js
export class MediaTransformer {
  static transform(productGallery, productVideos) {
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  static extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  static generateThumbnail(youtubeId, quality = 'sddefault') {
    return `https://img.youtube.com/vi/${youtubeId}/${quality}.jpg`
  }
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return MediaTransformer.transform(props.productGallery, props.product?.product_video)
})
</script>

3. JS Třída - Export třídy (instance v komponentě)

// services/MediaTransformer.js
export class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
    this.defaultVideoType = config.defaultVideoType || 'video'
  }

  transform(productGallery, productVideos) {
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: this.defaultVideoType,
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/${this.thumbnailQuality}.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  setConfig(newConfig) {
    Object.assign(this, newConfig)
    return this
  }
}

Použití:

// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaTransformer = ref(new MediaTransformer({ thumbnailQuality: 'hqdefault' }))

const mediaObjects = computed(() => {
  return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>

4. JS Třída - Export instance (singleton)

// services/MediaTransformer.js
class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
  }

  transform(productGallery, productVideos) {
    // stejná implementace jako výše...
  }

  extractYouTubeId(url) {
    // stejná implementace...
  }

  setConfig(newConfig) {
    Object.assign(this, newConfig)
    return this
  }
}

// Export instance
export const mediaTransformer = new MediaTransformer()

// Případně export obou
export { MediaTransformer }

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return mediaTransformer.transform(props.productGallery, props.product?.product_video)
})

// Změna konfigurace kdykoliv:
// mediaTransformer.setConfig({ thumbnailQuality: 'maxresdefault' })
</script>

5. Singleton Pattern

// services/MediaTransformer.js
class MediaTransformer {
  static _instance = null

  constructor(config = {}) {
    if (MediaTransformer._instance) {
      return MediaTransformer._instance
    }

    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
    MediaTransformer._instance = this
  }

  static getInstance(config = {}) {
    if (!this._instance) {
      this._instance = new MediaTransformer(config)
    }
    return this._instance
  }

  transform(productGallery, productVideos) {
    // implementace...
  }

  extractYouTubeId(url) {
    // implementace...
  }
}

export default MediaTransformer

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import MediaTransformer from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  const transformer = MediaTransformer.getInstance({ thumbnailQuality: 'hqdefault' })
  return transformer.transform(props.productGallery, props.product?.product_video)
})
</script>

6. Factory Function

// services/MediaTransformer.js
class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
  }

  transform(productGallery, productVideos) {
    // implementace...
  }

  extractYouTubeId(url) {
    // implementace...
  }
}

export const createMediaTransformer = (config = {}) => {
  return new MediaTransformer(config)
}

// Export i třídy pro pokročilé použití
export { MediaTransformer }

Použití:

// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { createMediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaTransformer = ref(createMediaTransformer({ thumbnailQuality: 'maxresdefault' }))

const mediaObjects = computed(() => {
  return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>

7. Plain Object/Service

// services/mediaService.js
export const mediaService = {
  defaultConfig: {
    thumbnailQuality: 'sddefault'
  },

  transform(productGallery, productVideos, config = {}) {
    const finalConfig = { ...this.defaultConfig, ...config }
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/${finalConfig.thumbnailQuality}.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  },

  extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaService } from '@/services/mediaService'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return mediaService.transform(
    props.productGallery, 
    props.product?.product_video,
    { thumbnailQuality: 'hqdefault' }
  )
})
</script>

8. Pure Functions (Helper)

// utils/mediaTransform.js
export const transformMediaObjects = (productGallery, productVideos, config = {}) => {
  const { thumbnailQuality = 'sddefault' } = config
  const finalObjects = []

  if (productGallery) {
    finalObjects.push(
      ...productGallery.map((photo, index) => ({
        ...photo,
        type: 'photo',
        rank: index + 1
      }))
    )
  }

  if (productVideos) {
    finalObjects.push(
      ...productVideos.map((videoUrl, index) => {
        const youtubeId = extractYouTubeId(videoUrl)
        return {
          url: videoUrl,
          type: 'video',
          rank: finalObjects.length + index + 1,
          previewThumb: youtubeId 
            ? `https://img.youtube.com/vi/${youtubeId}/${thumbnailQuality}.jpg` 
            : null
        }
      })
    )
  }

  return finalObjects
}

export const extractYouTubeId = (url) => {
  return url.match(
    /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
  )?.[1]
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { transformMediaObjects } from '@/utils/mediaTransform'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return transformMediaObjects(
    props.productGallery, 
    props.product?.product_video,
    { thumbnailQuality: 'hqdefault' }
  )
})
</script>

Srovnání variant

Varianta Výhody Nevýhody Použití
Composable Vue 3 native, reaktivita, auto-import Vue specific Vue 3 projekty
Statické metody Jednoduché, žádná konfigurace Není flexibilní Jednoduché transformace
Export třídy Flexibilní konfigurace Musíš vytvářet instance Různé konfigurace
Export instance Žádné new, sdílený stav Pevná konfigurace Jednotná konfigurace
Singleton Jedna instance, lazy loading Složitější pattern Globální konfigurace
Factory Flexibilní vytváření Extra abstrakce Komplexní objekty
Plain Object Jednoduché, framework agnostic Není OOP Malé utility
Pure Functions Nejjednodušší, testovatelné Žádná organizace Funkcionální přístup

Doporučení

  • Pro jednoduché transformace: Pure Functions nebo Statické metody
  • Pro flexibilní konfiguraci: Export třídy nebo Factory
  • Pro Vue 3 projekty: Composable
  • Pro globální použití: Export instance s setConfig

On gists

Vue parent - child

Popular ⭐ Vue.js

vue1.js #

// https://play.vuejs.org/#eNqdVE2P2jAQ/SsjXwCJBlXtic2itohDK7W72u7Rl5BMwItjW7bDUiH+e8d2krJbRNXlgOyZN1/Pb3Jkn43J9i2yOctdaYXx4NC3ZsGVaIy2Ho5gsZ5C6/ARGyMLjw9YwwlqqxsYUehogH7XVSE7RzaLt5CbAKVWzkMTLCH69lW68Si6RpMbrvJZ6oM6oIvvQHS7LywqD/nawoyut3//Bl++br3XCj6VUpS7W876ypk2qMYTzhZ3dEgN57OEvhpXSu0wBi7D6UJkqNz9b7suEh9EX5+Js+SYDWOxKfOO2KnFJntyWtE7HLkC4KzUjRES7Z3xgtjjbA7RE3yFlPr5W7R52+K0t5dbLHcX7E/uEGyc3Vt0aPfI2eDzhd2gT+7Vzx94oPPgpK5bSegrzgd0WrahxwT70qqK2j7DxW6/RokItXl0q4NH5fqhQqMBeYp4zkgwyyuj/2n3Q/YxxnF1IhYHtQUln6kGYLkVsoJxBEzeJp//VM0/xVKJPezfua1+JrRwIStJo5SFc2SI8KVWxBINukgq6u75jGJfSijuyZXdfbWr/TKmsrSKBBnXhXQ44apuVRk4hzRxoj0hs30hWyR8eDAg0s/Q3cAX4TF1wnNVYS0Urg4m4I+xyjRFw4nKv9x953/J9ITZOSW9FkxRVaSnOby32NzAuih3G6tJfXMwsm3oUxJQDelFqA7U6YXKxNTs9BvEAbCg

// Parent
<script setup>
import { ref, useTemplateRef } from 'vue'
import Modal from './Modal.vue'
const modalRef = useTemplateRef('modal');
</script>

<template>
Parent <br />
=================== <br />
<button @click="modalRef.open()">Open Modal</button>
<button @click="modalRef.close()">Close Modal</button>
<br /><br /><hr />
<Modal ref="modal" />
</template>

// Child - Modal
<template>
  Child (Modal) <br />
=================== <br />
<button @click="open()">Open Modal</button>
<button @click="close()">Close Modal</button>
<div v-show="isOpen" class="ModalContent">Modal Content</div>
</template>

<script setup>
import { ref } from 'vue'

const isOpen = ref(false)
function open() {
  isOpen.value = true 
}
function close() {
  isOpen.value = false 
}

defineExpose({ open, close })
</script>

<style>
  .ModalContent {
    padding: 1rem; background: plum;
    margin: 1rem;
  }
</style>

On gists

Css Toggle (if-else)

CSS CSS trick

demo.css #

/*
https://css-tricks.com/the-css-custom-property-toggle-trick/
*/


html {
  --mq-sm: initial;
}

@media (max-width: 600px) {
  html {
    --mq-sm: ;
  }
}

.card {
  --bg1: var(--mq-sm) green; /* first can be invalid ;) */
  --bg2: red;

  background: var(--bg1, var(--bg2));
}

On gists

Vue computed with private variable via IIFE

JS Patterns Vue.js

any-component.js #

const showThumbs = (() => {
  // really private ;-)
  const state = ref(props.enableThumbs)

  return computed({
    get: () => state.value,
    set: value => {
      state.value = value
    }
  })
  
})()

/*

-----------------------------------
Nejčastější použití je právě pro:
  - Vytvoření privátního scope
  - Enkapsulaci dat a logiky
  - Vyhnutí se globálnímu scope
  - Zachování stavu přes closure
 -----------------------------------
 * 1. Vytvoření privátního scope
 * - proměnné uvnitř neuniknou ven
 * - čisté API pro vnější svět
 */
const counter = (() => {
  let count = 0  // privátní proměnná
  return {
    increment() { count++ },
    getCount() { return count }
  }
})()

/**
 * 2. Vyhnutí se globálnímu scope
 * - dočasné proměnné zůstanou lokální
 * - neznečišťuje globální namespace
 */
(() => {
  const temp = 'nějaká dočasná proměnná'
  // temp existuje jen uvnitř, neznečišťuje globální scope
})()

/**
 * 3. Modulární pattern
 * - privátní metody a proměnné
 * - veřejné API
 */
const module = (() => {
  const privateVar = 'private'
  const privateMethod = () => { 
    console.log(privateVar) 
  }
  
  return {
    publicMethod() {
      privateMethod()  // má přístup k privátním věcem
    }
  }
})()

/**
 * 4. Closure pro zachování stavu
 * - privátní stav
 * - reaktivní hodnota
 */
const showThumbs = (() => {
  const state = ref(props.enableThumbs)  // privátní stav
  return computed({
    get: () => state.value,
    set: (value) => {
      state.value = value
    }
  })
})()

/**
 * 5. Vyhnutí se konfliktům
 * - lokální aliasy
 * - izolace kódu
 */
(() => {
  const $ = jQuery  // lokální alias pro jQuery
  // používání $ uvnitř neovlivní jiné knihovny používající $
})()

/**
 * 6. Inicializace
 * - nastavení při načtení
 * - konfigurace
 * - event listenery
 */
(() => {
  // nějaká inicializace
  const config = { 
    apiKey: 'xxx',
    endpoint: 'https://api.example.com'
  }
  
  // nastavení event listenerů
  document.addEventListener('DOMContentLoaded', () => {
    // inicializace UI
  })
  
  // další setup
  const init = () => {
    // ...
  }
  
  init()
})()