/ Gists

Gists

On gists

useElementSize - own composable

Vue.js

useElementSize.js #

import { ref, watch } from 'vue';

function useElementSize(element) {
  const width = ref(0);
  const height = ref(0);

  let observer = null;
   
  function disconnect() {
    if (observer !== null) {
      observer.disconnect();
      observer = null;
    }
  }

  function connect(element) {
    disconnect();
    observer = new ResizeObserver((entries) => {
      const rect = entries[0]?.contentRect;
      if (rect) {
        width.value = rect.width;
        height.value = rect.height;
      }
    });

    observer.observe(element);
  }

  watch(
    element,
    (el) => {
      if (el) connect(el);
      else disconnect();
    }
  )
  
  return {
    width,
    height,
  };
}

On gists

Access Control (cross origin allow for all) - API

PHP Web Api

api.php #

<?php

header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");

$films = [
    [
        'id' => 1628,
        'name' => 'Sám doma',
        'url' => 'https://www.csfd.cz/film/1628-sam-doma/prehled/',
    ],
    [
        'id' => 1629,
        'name' => 'Sám doma 2',
        'url' => 'https://www.csfd.cz/film/1629-sam-doma-2-ztracen-v-new-yorku/prehled/',
    ]
];

echo json_encode($films);

On gists

Fast images via API fetch

JavaScript

fetch.js #

fetch("https://dog.ceo/api/breeds/image/random/10")
 .then((response) => response.json())
 .then((data) => data.message)
 .then(arrayOfLinks => console.log(arrayOfLinks))

On gists

Alpine reactivity like jQuery

Alpine.js

reactivity.js #

let button = document.querySelector('button')
let span = document.querySelector('span')
 
let proxy = Alpine.reactive({ color: ‘blue’ })
 
Alpine.effect(() => {
    span.textContent = proxy.color
})
 
button.addEventListener('click', () => {
    proxy.color = (proxy.color == ‘blue’) ? ‘green’ : ‘blue’
}) 

On gists

Fetch / async

Alpine.js

fetch-async-ways.js #

async function getItems() {
    let response = await fetch('/api/items/all')
    let json = await response.json()
    return json
}

<div x-text="JSON.stringify(getItems())"></div>

// or alternatively:
<div
    x-data="{
        items: {},

        async getItems() {
            this.items = await (await fetch('/api/items/all')).json();
        }
    }"
    x-init="getItems">
</div>

On gists

Alpine - magic (custom method)

Alpine.js

overlap.js #

// https://github.com/markmead/alpinejs-overlap/blob/main/src/index.js

export default function (Alpine) {
  Alpine.magic('overlap', (el, {}) => (targetId) => {
    const targetEl = document.querySelector(targetId)

    return checkOverlap(
      targetEl.getBoundingClientRect(),
      el.getBoundingClientRect()
    )
  })

  function checkOverlap(targetBounding, elBounding) {
    return !(
      targetBounding.top > elBounding.bottom ||
      targetBounding.right < elBounding.left ||
      targetBounding.bottom < elBounding.top ||
      targetBounding.left > elBounding.right
    )
  }
}

On gists

Alpine directive - slugify

Alpine.js

slugify.js #

// https://github.com/markmead/alpinejs-slug

import slugify from 'slugify'

export default function (Alpine) {
  Alpine.directive('slug', (el, { expression }, { evaluateLater, effect }) => {
    let setInputValue = evaluateLater(expression)

    effect(() => {
      setInputValue((string) => {
        el.value = slugify(string, {
          lower: true,
        })
      })
    })
  })
}

On gists

Alpine js - directive (example)

Alpine.js

example.js #

//  https://github.com/markmead/alpinejs-textarea-autogrow

export default function (Alpine) {
  Alpine.directive('grow', (el) => {
    el.addEventListener('input', () => {
      el.style.height = 'auto'

      el.style.height = `${el.scrollHeight}px`
    })
  })
}

On gists

TodoList

Alpine.js

index.html #

<!-- https://codepen.io/ryangjchandler/pen/qBOEgjg -->

<div x-data="toDoList()" class="max-w-2xl mx-auto px-12 py-8 rounded-lg shadow-lg bg-gray-200">
    <div class="flex flex-col items-center justify-center mb-8">
        <h1 class="text-3xl font-bold mb-8">
            To Do List
        </h1>
        <input type="text" x-model="newTodo" placeholder="I need to..." class="mx-auto px-4 py-2 rounded shadow text-lg min-w-full" @keydown.enter="addToDo">
    </div>
    <div class="bg-white w-full rounded shadow mb-8">
        <template x-for="(todo, index) in todos" :key="index">
            <div class="flex items-center py-4" :class="{ 'border-b border-gray-400': ! isLastToDo(index) }">
                <div class="w-1/12 text-center">
                    <input type="checkbox" @change="toggleToDoCompleted(index)" :checked="todo.completed">
                </div>
                <div class="w-10/12">
                    <p x-text="todo.todo" :class="{ 'line-through': todo.completed }"></p>
                </div>
                <div class="w-1/12 text-center">
                    <button class="bg-red-600 text-white px-2 py-1 rounded hover:bg-red-700" @click="deleteToDo(index)">
                        &cross;
                    </button>
                </div>
            </div>
        </template>
    </div>
    <div>
        <span x-text="numberOfToDosCompleted()"></span> / <span x-text="toDoCount()"></span> to dos completed
    </div>
</div>


<script>
  function toDoList() {
    return {
        newTodo: "",
        todos: [],
        addToDo() {
            this.todos.push({
                todo: this.newTodo,
                completed: false
            });

            this.newTodo = "";
        },
        toggleToDoCompleted(index) {
            this.todos[index].completed = !this.todos[index].completed;
        },
        deleteToDo(index) {
            this.todos = this.todos.filter((todo, todoIndex) => {
                return index !== todoIndex
            })
        },
        numberOfToDosCompleted() {
            return this.todos.filter(todo => todo.completed).length;
        },
        toDoCount() {
            return this.todos.length
        },
        isLastToDo(index) {
            return this.todos.length - 1 === index
        }
    };
}

  
</script>

On gists

No need remove event listener

JavaScript

examples.js #

// https://javascript.plainenglish.io/you-dont-need-removeeventlistener-to-remove-dom-event-listeners-12db93cd8bf6

// 1. once

document.querySelector('#btn').addEventListener('click', () => {
  console.log('clicked');
}, {once: true});


// 2. AbortController
const controller = new AbortController();

document.querySelector('#btn').addEventListener('click', () => {
  console.log('clicked');
}, { signal: controller.signal });

// abort the listener!
controller.abort();


// or
const controller = new AbortController();
const { signal } = controller;

document.querySelector('#btn').addEventListener('click', () => {
  console.log('clicked');
}, { signal });

document.querySelector('#btn').addEventListener('mouseenter', () => {
  console.log('mouseenter');
}, { signal })

window.addEventListener('scroll', () => {
  console.log('scroll');
}, { signal })

// Remove all listeners at once:
controller.abort();


// 3. clone element
const button = document.querySelector('#btn');
button.replaceWith(button.cloneNode(true));