Komponenten
Schablonen
Attribute
Integrationen
Standort-Tester
Benutzerdefinierter Code

Mitglieder-Skripte

Eine attributbasierte Lösung zum Hinzufügen von Funktionen zu Ihrer Webflow-Site.
Kopieren Sie einfach etwas Code, fügen Sie einige Attribute hinzu, und schon sind Sie fertig.

Vielen Dank! Ihr Beitrag ist eingegangen!
Huch! Beim Absenden des Formulars ist etwas schief gelaufen.
Benötigen Sie Hilfe mit MemberScripts?

Alle Memberstack-Kunden können im 2.0 Slack um Unterstützung bitten. Bitte beachten Sie, dass dies keine offiziellen Funktionen sind und der Support nicht garantiert werden kann.

Benutzerdefinierte Felder

#118 - Speichern der letzten IP-Adresse des Mitglieds

Aktualisieren Sie ein benutzerdefiniertes Feld mit der letzten IP-Adresse, mit der sich Ihre Mitglieder anmelden.


<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
  const memberstack = window.$memberstackDom;

  memberstack.getCurrentMember().then(async (response) => {
    if (response && response.data) {
      const member = response.data;
      try {
        // Fetch the current IP address from the ipify API
        const ipResponse = await fetch('https://api.ipify.org?format=json');
        const ipData = await ipResponse.json();
        const currentIpAddress = ipData.ip;

        // Retrieve the stored IP address from Memberstack's custom fields
        const storedIpAddress = member.customFields["last-ip"];

        // Check if the IP address has changed and update if necessary
        if (currentIpAddress !== storedIpAddress) {
          await memberstack.updateMember({
            customFields: {
              "last-ip": currentIpAddress
            }
          });
          // Optional: Uncomment the line below to log a message when the IP is updated
          // console.log('IP address updated');
        } else {
          // Optional: Uncomment the line below to log when the IP remains unchanged
          // console.log('IP address unchanged, no update needed');
        }
      } catch (error) {
        // Log any errors encountered during the fetch or update process
        console.error('Error checking or updating member IP:', error);
      }
    } else {
      // Optional: Uncomment the line below to log when no member is logged in
      // console.log('No member is currently logged in');
    }
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
  const memberstack = window.$memberstackDom;

  memberstack.getCurrentMember().then(async (response) => {
    if (response && response.data) {
      const member = response.data;
      try {
        // Fetch the current IP address from the ipify API
        const ipResponse = await fetch('https://api.ipify.org?format=json');
        const ipData = await ipResponse.json();
        const currentIpAddress = ipData.ip;

        // Retrieve the stored IP address from Memberstack's custom fields
        const storedIpAddress = member.customFields["last-ip"];

        // Check if the IP address has changed and update if necessary
        if (currentIpAddress !== storedIpAddress) {
          await memberstack.updateMember({
            customFields: {
              "last-ip": currentIpAddress
            }
          });
          // Optional: Uncomment the line below to log a message when the IP is updated
          // console.log('IP address updated');
        } else {
          // Optional: Uncomment the line below to log when the IP remains unchanged
          // console.log('IP address unchanged, no update needed');
        }
      } catch (error) {
        // Log any errors encountered during the fetch or update process
        console.error('Error checking or updating member IP:', error);
      }
    } else {
      // Optional: Uncomment the line below to log when no member is logged in
      // console.log('No member is currently logged in');
    }
  });
</script>
Ansicht Memberscript
UX

#Nr. 117 - Fortschrittsbalken für das Seitenblättern

Eine flexible und benutzerdefinierte Anzeige für das Blättern der Seite, um den Fortschritt des Blätterns anzuzeigen.


<!-- 💙 MEMBERSCRIPT #117 v0.1 💙 - PAGE SCROLL PROGRESS BAR -->
<script>
  // Function to update the progress bar
  function updateProgressBar() {
    const container = document.querySelector('[ms-code-ps="container"]');
    const bar = document.querySelector('[ms-code-ps="bar"]');
    const startElement = document.querySelector('[ms-code-ps="start"]');
    const endElement = document.querySelector('[ms-code-ps="end"]');

    if (!container || !bar) return;

    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

    let startPosition = 0;
    let endPosition = documentHeight - windowHeight;

    if (startElement) {
      const startRect = startElement.getBoundingClientRect();
      startPosition = scrollTop + startRect.top - windowHeight;
    }

    if (endElement) {
      const endRect = endElement.getBoundingClientRect();
      endPosition = scrollTop + endRect.top - windowHeight;
    }

    const scrollRange = endPosition - startPosition;
    const scrollProgress = scrollTop - startPosition;
    const scrollPercentage = Math.max(0, Math.min(100, (scrollProgress / scrollRange) * 100));

    // Use requestAnimationFrame for smooth animation
    requestAnimationFrame(() => {
      bar.style.width = `${scrollPercentage}%`;
      bar.style.transition = 'width 0.1s linear';
    });
  }

  // Throttle function to limit how often updateProgressBar is called
  function throttle(func, limit) {
    let inThrottle;
    return function() {
      const args = arguments;
      const context = this;
      if (!inThrottle) {
        func.apply(context, args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    };
  }

  // Add scroll event listener with throttling
  window.addEventListener('scroll', throttle(updateProgressBar, 10));

  // Initial call to set the correct width on page load
  updateProgressBar();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #117 v0.1 💙 - PAGE SCROLL PROGRESS BAR -->
<script>
  // Function to update the progress bar
  function updateProgressBar() {
    const container = document.querySelector('[ms-code-ps="container"]');
    const bar = document.querySelector('[ms-code-ps="bar"]');
    const startElement = document.querySelector('[ms-code-ps="start"]');
    const endElement = document.querySelector('[ms-code-ps="end"]');

    if (!container || !bar) return;

    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

    let startPosition = 0;
    let endPosition = documentHeight - windowHeight;

    if (startElement) {
      const startRect = startElement.getBoundingClientRect();
      startPosition = scrollTop + startRect.top - windowHeight;
    }

    if (endElement) {
      const endRect = endElement.getBoundingClientRect();
      endPosition = scrollTop + endRect.top - windowHeight;
    }

    const scrollRange = endPosition - startPosition;
    const scrollProgress = scrollTop - startPosition;
    const scrollPercentage = Math.max(0, Math.min(100, (scrollProgress / scrollRange) * 100));

    // Use requestAnimationFrame for smooth animation
    requestAnimationFrame(() => {
      bar.style.width = `${scrollPercentage}%`;
      bar.style.transition = 'width 0.1s linear';
    });
  }

  // Throttle function to limit how often updateProgressBar is called
  function throttle(func, limit) {
    let inThrottle;
    return function() {
      const args = arguments;
      const context = this;
      if (!inThrottle) {
        func.apply(context, args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    };
  }

  // Add scroll event listener with throttling
  window.addEventListener('scroll', throttle(updateProgressBar, 10));

  // Initial call to set the correct width on page load
  updateProgressBar();
</script>
Ansicht Memberscript
UX

#116 - Hervorgehobene Textlinks teilen

Erlauben Sie den Benutzern, Text zu markieren und den Link mit anderen zu teilen!


<!-- 💙 MEMBERSCRIPT #116 v0.1 💙 - SHARE HIGHLIGHTED TEXT LINKS -->
<script>
  // Function to encode text and position for URL
  function encodeSelection(text, nodeIndex, textOffset) {
    return btoa(encodeURIComponent(JSON.stringify({ text, nodeIndex, textOffset })));
  }

  // Function to decode selection from URL
  function decodeSelection(encoded) {
    try {
      return JSON.parse(decodeURIComponent(atob(encoded)));
    } catch (e) {
      // If parsing fails, assume it's just the text in the old format
      return { text: decodeURIComponent(atob(encoded)) };
    }
  }

  // Function to remove existing highlight
  function removeExistingHighlight() {
    const existingHighlight = document.querySelector('.ms-highlight');
    if (existingHighlight) {
      const parent = existingHighlight.parentNode;
      parent.replaceChild(document.createTextNode(existingHighlight.textContent), existingHighlight);
      parent.normalize(); // Merge adjacent text nodes
    }
  }

  // Function to handle text selection
  function handleSelection() {
    const selection = window.getSelection();
    if (selection.toString().length > 0) {
      removeExistingHighlight();

      const range = selection.getRangeAt(0);
      const selectedText = selection.toString();
      const textNodes = getAllTextNodes(document.body);
      const nodeIndex = textNodes.indexOf(range.startContainer);
      const textOffset = range.startOffset;

      // Create a unique identifier for the selection
      const selectionId = encodeSelection(selectedText, nodeIndex, textOffset);

      // Update URL with the selection parameter
      const url = new URL(window.location);
      url.searchParams.set('highlight', selectionId);
      window.history.pushState({}, '', url);

      // Highlight the selected text
      highlightText(selectionId, range);
    }
  }

  // Function to highlight text
  function highlightText(selectionId, range) {
    const span = document.createElement('span');
    span.className = 'ms-highlight';
    span.id = selectionId;
    range.surroundContents(span);
  }

  // Function to highlight and scroll to text based on URL parameter
  function highlightFromURL() {
    removeExistingHighlight();

    const url = new URL(window.location);
    const highlightId = url.searchParams.get('highlight');

    if (highlightId) {
      const { text, nodeIndex, textOffset } = decodeSelection(highlightId);
      const textNodes = getAllTextNodes(document.body);

      if (nodeIndex !== undefined && textOffset !== undefined) {
        // Use precise location if available
        if (nodeIndex < textNodes.length) {
          const node = textNodes[nodeIndex];
          if (node.textContent.substr(textOffset, text.length) === text) {
            const range = document.createRange();
            range.setStart(node, textOffset);
            range.setEnd(node, textOffset + text.length);
            highlightText(highlightId, range);
          }
        }
      } else {
        // Fall back to searching for the first occurrence of the text
        for (let node of textNodes) {
          const index = node.textContent.indexOf(text);
          if (index !== -1) {
            const range = document.createRange();
            range.setStart(node, index);
            range.setEnd(node, index + text.length);
            highlightText(highlightId, range);
            break;
          }
        }
      }

      const highlightedSpan = document.getElementById(highlightId);
      if (highlightedSpan) {
        highlightedSpan.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }
  }

  // Helper function to get all text nodes
  function getAllTextNodes(element) {
    const textNodes = [];
    const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
    let node;
    while (node = walk.nextNode()) {
      textNodes.push(node);
    }
    return textNodes;
  }

  // Add event listener for text selection
  document.addEventListener('mouseup', handleSelection);

  // Call highlightFromURL when the page loads
  window.addEventListener('load', highlightFromURL);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #116 v0.1 💙 - SHARE HIGHLIGHTED TEXT LINKS -->
<script>
  // Function to encode text and position for URL
  function encodeSelection(text, nodeIndex, textOffset) {
    return btoa(encodeURIComponent(JSON.stringify({ text, nodeIndex, textOffset })));
  }

  // Function to decode selection from URL
  function decodeSelection(encoded) {
    try {
      return JSON.parse(decodeURIComponent(atob(encoded)));
    } catch (e) {
      // If parsing fails, assume it's just the text in the old format
      return { text: decodeURIComponent(atob(encoded)) };
    }
  }

  // Function to remove existing highlight
  function removeExistingHighlight() {
    const existingHighlight = document.querySelector('.ms-highlight');
    if (existingHighlight) {
      const parent = existingHighlight.parentNode;
      parent.replaceChild(document.createTextNode(existingHighlight.textContent), existingHighlight);
      parent.normalize(); // Merge adjacent text nodes
    }
  }

  // Function to handle text selection
  function handleSelection() {
    const selection = window.getSelection();
    if (selection.toString().length > 0) {
      removeExistingHighlight();

      const range = selection.getRangeAt(0);
      const selectedText = selection.toString();
      const textNodes = getAllTextNodes(document.body);
      const nodeIndex = textNodes.indexOf(range.startContainer);
      const textOffset = range.startOffset;

      // Create a unique identifier for the selection
      const selectionId = encodeSelection(selectedText, nodeIndex, textOffset);

      // Update URL with the selection parameter
      const url = new URL(window.location);
      url.searchParams.set('highlight', selectionId);
      window.history.pushState({}, '', url);

      // Highlight the selected text
      highlightText(selectionId, range);
    }
  }

  // Function to highlight text
  function highlightText(selectionId, range) {
    const span = document.createElement('span');
    span.className = 'ms-highlight';
    span.id = selectionId;
    range.surroundContents(span);
  }

  // Function to highlight and scroll to text based on URL parameter
  function highlightFromURL() {
    removeExistingHighlight();

    const url = new URL(window.location);
    const highlightId = url.searchParams.get('highlight');

    if (highlightId) {
      const { text, nodeIndex, textOffset } = decodeSelection(highlightId);
      const textNodes = getAllTextNodes(document.body);

      if (nodeIndex !== undefined && textOffset !== undefined) {
        // Use precise location if available
        if (nodeIndex < textNodes.length) {
          const node = textNodes[nodeIndex];
          if (node.textContent.substr(textOffset, text.length) === text) {
            const range = document.createRange();
            range.setStart(node, textOffset);
            range.setEnd(node, textOffset + text.length);
            highlightText(highlightId, range);
          }
        }
      } else {
        // Fall back to searching for the first occurrence of the text
        for (let node of textNodes) {
          const index = node.textContent.indexOf(text);
          if (index !== -1) {
            const range = document.createRange();
            range.setStart(node, index);
            range.setEnd(node, index + text.length);
            highlightText(highlightId, range);
            break;
          }
        }
      }

      const highlightedSpan = document.getElementById(highlightId);
      if (highlightedSpan) {
        highlightedSpan.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }
    }
  }

  // Helper function to get all text nodes
  function getAllTextNodes(element) {
    const textNodes = [];
    const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
    let node;
    while (node = walk.nextNode()) {
      textNodes.push(node);
    }
    return textNodes;
  }

  // Add event listener for text selection
  document.addEventListener('mouseup', handleSelection);

  // Call highlightFromURL when the page loads
  window.addEventListener('load', highlightFromURL);
</script>
Ansicht Memberscript
UX

#Nr. 115 - Ein zufälliges Passwort generieren

Reibungslose Anmeldung. Verlangt oder erlaubt den Mitgliedern, in Zukunft ein Passwort festzulegen.


<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
    var passwordInput = document.querySelector('[data-ms-member="password"]');
    
    if (passwordInput) {
        // Function to generate random password
        function generatePassword() {
            var timestamp = Date.now().toString(36);
            var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
            var randomChars = '';
            for (var i = 0; i < 16; i++) {
                randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
            }
            return (timestamp + randomChars).slice(0, 32);
        }

        // Generate and set password
        passwordInput.value = generatePassword();

        // Block password managers and prevent editing
        passwordInput.setAttribute('autocomplete', 'off');
        passwordInput.setAttribute('readonly', 'readonly');

        // Prevent copy and paste
        passwordInput.addEventListener('copy', function(e) {
            e.preventDefault();
        });
        passwordInput.addEventListener('paste', function(e) {
            e.preventDefault();
        });

        // Prevent dragging
        passwordInput.addEventListener('dragstart', function(e) {
            e.preventDefault();
        });

        // Prevent context menu
        passwordInput.addEventListener('contextmenu', function(e) {
            e.preventDefault();
        });
    }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
    var passwordInput = document.querySelector('[data-ms-member="password"]');
    
    if (passwordInput) {
        // Function to generate random password
        function generatePassword() {
            var timestamp = Date.now().toString(36);
            var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
            var randomChars = '';
            for (var i = 0; i < 16; i++) {
                randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
            }
            return (timestamp + randomChars).slice(0, 32);
        }

        // Generate and set password
        passwordInput.value = generatePassword();

        // Block password managers and prevent editing
        passwordInput.setAttribute('autocomplete', 'off');
        passwordInput.setAttribute('readonly', 'readonly');

        // Prevent copy and paste
        passwordInput.addEventListener('copy', function(e) {
            e.preventDefault();
        });
        passwordInput.addEventListener('paste', function(e) {
            e.preventDefault();
        });

        // Prevent dragging
        passwordInput.addEventListener('dragstart', function(e) {
            e.preventDefault();
        });

        // Prevent context menu
        passwordInput.addEventListener('contextmenu', function(e) {
            e.preventDefault();
        });
    }
});
</script>
Ansicht Memberscript
UX

#Nr. 114 - Schaltfläche "Nach oben scrollen

Fügen Sie eine Schaltfläche hinzu, die beim Anklicken an den Anfang der Seite blättert,


<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
    
    if (scrollTopButton) {
        // Set initial styles
        scrollTopButton.style.opacity = '0';
        scrollTopButton.style.visibility = 'hidden';
        scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';

        // Function to check scroll position and toggle button visibility
        function toggleButtonVisibility() {
            if (window.pageYOffset > 300) {
                scrollTopButton.style.opacity = '1';
                scrollTopButton.style.visibility = 'visible';
            } else {
                scrollTopButton.style.opacity = '0';
                scrollTopButton.style.visibility = 'hidden';
            }
        }

        // Initial check on page load
        toggleButtonVisibility();

        // Check on scroll
        window.addEventListener('scroll', toggleButtonVisibility);

        // Scroll to top when button is clicked
        scrollTopButton.addEventListener('click', function() {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });
    }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
    
    if (scrollTopButton) {
        // Set initial styles
        scrollTopButton.style.opacity = '0';
        scrollTopButton.style.visibility = 'hidden';
        scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';

        // Function to check scroll position and toggle button visibility
        function toggleButtonVisibility() {
            if (window.pageYOffset > 300) {
                scrollTopButton.style.opacity = '1';
                scrollTopButton.style.visibility = 'visible';
            } else {
                scrollTopButton.style.opacity = '0';
                scrollTopButton.style.visibility = 'hidden';
            }
        }

        // Initial check on page load
        toggleButtonVisibility();

        // Check on scroll
        window.addEventListener('scroll', toggleButtonVisibility);

        // Scroll to top when button is clicked
        scrollTopButton.addEventListener('click', function() {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });
    }
});
</script>
Ansicht Memberscript
Integration

#113 - RSS-Feeds

Verwenden Sie eine Webflow-Benutzeroberfläche, um einen RSS-Feed direkt auf Ihrer Website anzuzeigen.


<!-- 💙 MEMBERSCRIPT #113 v0.2 💙 - RSS FEEDS IN WEBFLOW -->
<script>
(function() {
  // console.log('RSS Feed Script starting...');

  const CORS_PROXIES = [
    'https://corsproxy.io/?',
    'https://api.allorigins.win/raw?url=',
    'https://cors-anywhere.herokuapp.com/',
    'https://thingproxy.freeboard.io/fetch/',
    'https://yacdn.org/proxy/'
  ];

  function loadScript(src, onLoad, onError) {
    const script = document.createElement('script');
    script.src = src;
    script.onload = onLoad;
    script.onerror = onError;
    document.head.appendChild(script);
  }

  async function fetchWithFallback(url) {
    for (const proxy of CORS_PROXIES) {
      try {
        const response = await fetch(proxy + encodeURIComponent(url));
        if (response.ok) {
          return await response.text();
        }
      } catch (error) {
        console.warn(`Failed to fetch with proxy ${proxy}:`, error);
      }
    }
    throw new Error('All CORS proxies failed');
  }

  function initRSSFeed() {
    if (typeof RSSParser === 'undefined') {
      console.error('RSSParser is not defined.');
      return;
    }

    const parser = new RSSParser({
      customFields: {
        item: [
          ['media:content', 'mediaContent', {keepArray: true}],
          ['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
          ['enclosure', 'enclosure', {keepArray: true}],
        ]
      }
    });

    document.querySelectorAll('[ms-code-rss-feed]').forEach(element => {
      const url = element.getAttribute('ms-code-rss-url');
      const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;

      fetchWithFallback(url)
        .then(str => parser.parseString(str))
        .then(feed => {
          renderRSSItems(element, feed.items.slice(0, limit), {
            showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
            showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
            dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
            target: element.getAttribute('ms-code-rss-target') || '_self'
          });
        })
        .catch(err => {
          console.error('Error fetching or parsing RSS feed:', err);
          element.textContent = `Failed to load RSS feed from ${url}. Error: ${err.message}`;
        });
    });
  }

  function renderRSSItems(element, items, options) {
    const templateItem = element.querySelector('[ms-code-rss-item]');
    if (!templateItem) return;

    element.innerHTML = ''; // Clear existing items

    items.forEach(item => {
      const itemElement = templateItem.cloneNode(true);

      const title = itemElement.querySelector('[ms-code-rss-title]');
      if (title) {
        const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
        title.textContent = truncate(item.title, titleLength);
      }

      const description = itemElement.querySelector('[ms-code-rss-description]');
      if (description) {
        const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
        description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
      }

      const date = itemElement.querySelector('[ms-code-rss-date]');
      if (date && options.showDate && item.pubDate) {
        date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
      }

      const img = itemElement.querySelector('[ms-code-rss-image]');
      if (img && options.showImage) {
        const imgUrl = getImageUrl(item);
        if (imgUrl) {
          img.src = imgUrl;
          img.alt = item.title;
          img.removeAttribute('srcset');
        }
      }

      const linkElement = itemElement.querySelector('[ms-code-rss-link]');
      if (linkElement) {
        linkElement.setAttribute('href', item.link);
        linkElement.setAttribute('target', options.target);
      }

      element.appendChild(itemElement);
    });
  }

  function getImageUrl(item) {
    const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
    for (let source of sources) {
      if (item[source] && item[source][0]) {
        return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
      }
    }
    return null;
  }

  function truncate(str, length) {
    if (!str) return '';
    if (length === Infinity) return str;
    return str.length > length ? str.slice(0, length) + '...' : str;
  }

  function stripHtml(html) {
    const tmp = document.createElement('DIV');
    tmp.innerHTML = html || '';
    return tmp.textContent || tmp.innerText || '';
  }

  function formatDate(date, format) {
    if (!(date instanceof Date) || isNaN(date)) return '';
    const options = format === 'long' ? 
          { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } : 
    undefined;
    return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
  }

  function getRelativeTimeString(date, lang = navigator.language) {
    const timeMs = date.getTime();
    const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
    const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
    const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
    const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
    const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
    const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
    return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
  }

  loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
    console.error('Error loading RSS Parser script');
    loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
      console.error('Error loading RSS Parser script from backup CDN');
    });
  });

})();
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #113 v0.2 💙 - RSS FEEDS IN WEBFLOW -->
<script>
(function() {
  // console.log('RSS Feed Script starting...');

  const CORS_PROXIES = [
    'https://corsproxy.io/?',
    'https://api.allorigins.win/raw?url=',
    'https://cors-anywhere.herokuapp.com/',
    'https://thingproxy.freeboard.io/fetch/',
    'https://yacdn.org/proxy/'
  ];

  function loadScript(src, onLoad, onError) {
    const script = document.createElement('script');
    script.src = src;
    script.onload = onLoad;
    script.onerror = onError;
    document.head.appendChild(script);
  }

  async function fetchWithFallback(url) {
    for (const proxy of CORS_PROXIES) {
      try {
        const response = await fetch(proxy + encodeURIComponent(url));
        if (response.ok) {
          return await response.text();
        }
      } catch (error) {
        console.warn(`Failed to fetch with proxy ${proxy}:`, error);
      }
    }
    throw new Error('All CORS proxies failed');
  }

  function initRSSFeed() {
    if (typeof RSSParser === 'undefined') {
      console.error('RSSParser is not defined.');
      return;
    }

    const parser = new RSSParser({
      customFields: {
        item: [
          ['media:content', 'mediaContent', {keepArray: true}],
          ['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
          ['enclosure', 'enclosure', {keepArray: true}],
        ]
      }
    });

    document.querySelectorAll('[ms-code-rss-feed]').forEach(element => {
      const url = element.getAttribute('ms-code-rss-url');
      const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;

      fetchWithFallback(url)
        .then(str => parser.parseString(str))
        .then(feed => {
          renderRSSItems(element, feed.items.slice(0, limit), {
            showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
            showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
            dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
            target: element.getAttribute('ms-code-rss-target') || '_self'
          });
        })
        .catch(err => {
          console.error('Error fetching or parsing RSS feed:', err);
          element.textContent = `Failed to load RSS feed from ${url}. Error: ${err.message}`;
        });
    });
  }

  function renderRSSItems(element, items, options) {
    const templateItem = element.querySelector('[ms-code-rss-item]');
    if (!templateItem) return;

    element.innerHTML = ''; // Clear existing items

    items.forEach(item => {
      const itemElement = templateItem.cloneNode(true);

      const title = itemElement.querySelector('[ms-code-rss-title]');
      if (title) {
        const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
        title.textContent = truncate(item.title, titleLength);
      }

      const description = itemElement.querySelector('[ms-code-rss-description]');
      if (description) {
        const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
        description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
      }

      const date = itemElement.querySelector('[ms-code-rss-date]');
      if (date && options.showDate && item.pubDate) {
        date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
      }

      const img = itemElement.querySelector('[ms-code-rss-image]');
      if (img && options.showImage) {
        const imgUrl = getImageUrl(item);
        if (imgUrl) {
          img.src = imgUrl;
          img.alt = item.title;
          img.removeAttribute('srcset');
        }
      }

      const linkElement = itemElement.querySelector('[ms-code-rss-link]');
      if (linkElement) {
        linkElement.setAttribute('href', item.link);
        linkElement.setAttribute('target', options.target);
      }

      element.appendChild(itemElement);
    });
  }

  function getImageUrl(item) {
    const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
    for (let source of sources) {
      if (item[source] && item[source][0]) {
        return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
      }
    }
    return null;
  }

  function truncate(str, length) {
    if (!str) return '';
    if (length === Infinity) return str;
    return str.length > length ? str.slice(0, length) + '...' : str;
  }

  function stripHtml(html) {
    const tmp = document.createElement('DIV');
    tmp.innerHTML = html || '';
    return tmp.textContent || tmp.innerText || '';
  }

  function formatDate(date, format) {
    if (!(date instanceof Date) || isNaN(date)) return '';
    const options = format === 'long' ? 
          { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } : 
    undefined;
    return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
  }

  function getRelativeTimeString(date, lang = navigator.language) {
    const timeMs = date.getTime();
    const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
    const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
    const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
    const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
    const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
    const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
    return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
  }

  loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
    console.error('Error loading RSS Parser script');
    loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
      console.error('Error loading RSS Parser script from backup CDN');
    });
  });

})();
</script>
Ansicht Memberscript
Marketing

#Nr. 112 - Vorher & Nachher Schieberegler

Fügen Sie Ihrer Webflow-Website ganz einfach einen Vorher/Nachher-Fotoslider hinzu.


<!-- 💙 MEMBERSCRIPT #112 v0.1 💙 - BEFORE & AFTER SLIDERS -->
<script>
document.addEventListener('DOMContentLoaded', () => {
    const wraps = document.querySelectorAll('[ms-code-ba-wrap]');

    wraps.forEach(wrap => {
        const before = wrap.querySelector('[ms-code-ba-before]');
        const after = wrap.querySelector('[ms-code-ba-after]');
        
        // Create slider element
        const slider = document.createElement('div');
        slider.setAttribute('ms-code-ba-slider', wrap.getAttribute('ms-code-ba-wrap'));
        wrap.appendChild(slider);

        let isDown = false;

        // Ensure proper positioning
        wrap.style.position = 'relative';
        wrap.style.overflow = 'hidden';
        before.style.width = '100%';
        before.style.display = 'block';
        after.style.position = 'absolute';
        after.style.top = '0';
        after.style.left = '0';
        after.style.width = '100%';
        after.style.height = '100%';
        slider.style.position = 'absolute';
        slider.style.top = '0';
        slider.style.bottom = '0';
        slider.style.width = '4px';
        slider.style.background = 'white';
        slider.style.cursor = 'ew-resize';
        slider.style.zIndex = '3';

        const setPosition = (position) => {
            const clampedPosition = Math.max(0, Math.min(1, position));
            slider.style.left = `${clampedPosition * 100}%`;
            after.style.clipPath = `inset(0 0 0 ${clampedPosition * 100}%)`;
        };

        const move = (e) => {
            if (!isDown && e.type !== 'mousemove') return;
            e.preventDefault();

            const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
            const rect = wrap.getBoundingClientRect();
            const position = (x - rect.left) / rect.width;
            
            setPosition(position);
        };

        const easeBack = () => {
            setPosition(0.5); // Move back to center
        };

        wrap.addEventListener('mousedown', () => isDown = true);
        wrap.addEventListener('mouseup', () => isDown = false);
        wrap.addEventListener('mouseleave', () => {
            isDown = false;
            easeBack();
        });
        wrap.addEventListener('mousemove', move);

        wrap.addEventListener('touchstart', (e) => {
            isDown = true;
            move(e);
        });
        wrap.addEventListener('touchmove', move);
        wrap.addEventListener('touchend', () => {
            isDown = false;
            easeBack();
        });

        // Initialize position
        setPosition(0.5);
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #112 v0.1 💙 - BEFORE & AFTER SLIDERS -->
<script>
document.addEventListener('DOMContentLoaded', () => {
    const wraps = document.querySelectorAll('[ms-code-ba-wrap]');

    wraps.forEach(wrap => {
        const before = wrap.querySelector('[ms-code-ba-before]');
        const after = wrap.querySelector('[ms-code-ba-after]');
        
        // Create slider element
        const slider = document.createElement('div');
        slider.setAttribute('ms-code-ba-slider', wrap.getAttribute('ms-code-ba-wrap'));
        wrap.appendChild(slider);

        let isDown = false;

        // Ensure proper positioning
        wrap.style.position = 'relative';
        wrap.style.overflow = 'hidden';
        before.style.width = '100%';
        before.style.display = 'block';
        after.style.position = 'absolute';
        after.style.top = '0';
        after.style.left = '0';
        after.style.width = '100%';
        after.style.height = '100%';
        slider.style.position = 'absolute';
        slider.style.top = '0';
        slider.style.bottom = '0';
        slider.style.width = '4px';
        slider.style.background = 'white';
        slider.style.cursor = 'ew-resize';
        slider.style.zIndex = '3';

        const setPosition = (position) => {
            const clampedPosition = Math.max(0, Math.min(1, position));
            slider.style.left = `${clampedPosition * 100}%`;
            after.style.clipPath = `inset(0 0 0 ${clampedPosition * 100}%)`;
        };

        const move = (e) => {
            if (!isDown && e.type !== 'mousemove') return;
            e.preventDefault();

            const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
            const rect = wrap.getBoundingClientRect();
            const position = (x - rect.left) / rect.width;
            
            setPosition(position);
        };

        const easeBack = () => {
            setPosition(0.5); // Move back to center
        };

        wrap.addEventListener('mousedown', () => isDown = true);
        wrap.addEventListener('mouseup', () => isDown = false);
        wrap.addEventListener('mouseleave', () => {
            isDown = false;
            easeBack();
        });
        wrap.addEventListener('mousemove', move);

        wrap.addEventListener('touchstart', (e) => {
            isDown = true;
            move(e);
        });
        wrap.addEventListener('touchmove', move);
        wrap.addEventListener('touchend', () => {
            isDown = false;
            easeBack();
        });

        // Initialize position
        setPosition(0.5);
    });
});
</script>
Ansicht Memberscript
UX

#Nr. 111 - Automatisch rotierende Registerkarten

Der einfachste Weg, um Ihre Tabs mit einem Timer automatisch rotieren zu lassen.


<!-- 💙 MEMBERSCRIPT #111 v0.1 💙 - AUTO-ROTATING TABS -->
<script>
  // Function to rotate tabs
  function initializeTabRotator() {
    // Find all tab containers with the ms-code-rotate-tabs attribute
    const tabContainers = document.querySelectorAll('[ms-code-rotate-tabs]');

    tabContainers.forEach(container => {
      const interval = parseInt(container.getAttribute('ms-code-rotate-tabs'), 10);
      const tabLinks = container.querySelectorAll('.w-tab-link');
      const tabContent = container.closest('.w-tabs').querySelector('.w-tab-content');
      const tabPanes = tabContent.querySelectorAll('.w-tab-pane');
      let currentIndex = Array.from(tabLinks).findIndex(link => link.classList.contains('w--current'));
      let rotationTimer;

      // ANIMATION CONFIGURATION
      // Modify these values to adjust the animation behavior
      const FADE_OUT_DURATION = 300; // Duration for fading out the current tab (in milliseconds)
      const FADE_IN_DURATION = 100;  // Duration for fading in the new tab (in milliseconds)
      const EASING_FUNCTION = 'ease'; // Choose from: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'
      // or use a cubic-bezier function like 'cubic-bezier(0.1, 0.7, 1.0, 0.1)'
      // Additional easing options (uncomment to use):
      // const EASING_FUNCTION = 'ease-in-quad';
      // const EASING_FUNCTION = 'ease-out-quad';
      // const EASING_FUNCTION = 'ease-in-out-quad';
      // const EASING_FUNCTION = 'ease-in-cubic';
      // const EASING_FUNCTION = 'ease-out-cubic';
      // const EASING_FUNCTION = 'ease-in-out-cubic';
      // const EASING_FUNCTION = 'ease-in-quart';
      // const EASING_FUNCTION = 'ease-out-quart';
      // const EASING_FUNCTION = 'ease-in-out-quart';
      // const EASING_FUNCTION = 'ease-in-quint';
      // const EASING_FUNCTION = 'ease-out-quint';
      // const EASING_FUNCTION = 'ease-in-out-quint';
      // const EASING_FUNCTION = 'ease-in-sine';
      // const EASING_FUNCTION = 'ease-out-sine';
      // const EASING_FUNCTION = 'ease-in-out-sine';
      // const EASING_FUNCTION = 'ease-in-expo';
      // const EASING_FUNCTION = 'ease-out-expo';
      // const EASING_FUNCTION = 'ease-in-out-expo';
      // const EASING_FUNCTION = 'ease-in-circ';
      // const EASING_FUNCTION = 'ease-out-circ';
      // const EASING_FUNCTION = 'ease-in-out-circ';
      // const EASING_FUNCTION = 'ease-in-back';
      // const EASING_FUNCTION = 'ease-out-back';
      // const EASING_FUNCTION = 'ease-in-out-back';
      // END OF ANIMATION CONFIGURATION

      function switchToTab(index) {
        // Fade out current tab
        tabPanes[currentIndex].style.transition = `opacity ${FADE_OUT_DURATION}ms ${EASING_FUNCTION}`;
        tabPanes[currentIndex].style.opacity = '0';

        setTimeout(() => {
          // Remove active classes and update ARIA attributes for current tab and pane
          tabLinks[currentIndex].classList.remove('w--current');
          tabLinks[currentIndex].setAttribute('aria-selected', 'false');
          tabLinks[currentIndex].setAttribute('tabindex', '-1');
          tabPanes[currentIndex].classList.remove('w--tab-active');

          // Update current index
          currentIndex = index;

          // Add active classes and update ARIA attributes for new current tab and pane
          tabLinks[currentIndex].classList.add('w--current');
          tabLinks[currentIndex].setAttribute('aria-selected', 'true');
          tabLinks[currentIndex].setAttribute('tabindex', '0');
          tabPanes[currentIndex].classList.add('w--tab-active');

          // Fade in new tab
          tabPanes[currentIndex].style.transition = `opacity ${FADE_IN_DURATION}ms ${EASING_FUNCTION}`;
          tabPanes[currentIndex].style.opacity = '1';

          // Update the data-current attribute on the parent w-tabs element
          const wTabsElement = container.closest('.w-tabs');
          if (wTabsElement) {
            wTabsElement.setAttribute('data-current', tabLinks[currentIndex].getAttribute('data-w-tab'));
          }
        }, FADE_OUT_DURATION);
      }

      function rotateToNextTab() {
        const nextIndex = (currentIndex + 1) % tabLinks.length;
        switchToTab(nextIndex);
      }

      function startRotation() {
        clearInterval(rotationTimer);
        rotationTimer = setInterval(rotateToNextTab, interval);
      }

      // Add click event listeners to tab links
      tabLinks.forEach((link, index) => {
        link.addEventListener('click', (e) => {
          e.preventDefault();
          switchToTab(index);
          startRotation(); // Restart rotation from this tab
        });
      });

      // Start the initial rotation
      startRotation();
    });
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initializeTabRotator);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #111 v0.1 💙 - AUTO-ROTATING TABS -->
<script>
  // Function to rotate tabs
  function initializeTabRotator() {
    // Find all tab containers with the ms-code-rotate-tabs attribute
    const tabContainers = document.querySelectorAll('[ms-code-rotate-tabs]');

    tabContainers.forEach(container => {
      const interval = parseInt(container.getAttribute('ms-code-rotate-tabs'), 10);
      const tabLinks = container.querySelectorAll('.w-tab-link');
      const tabContent = container.closest('.w-tabs').querySelector('.w-tab-content');
      const tabPanes = tabContent.querySelectorAll('.w-tab-pane');
      let currentIndex = Array.from(tabLinks).findIndex(link => link.classList.contains('w--current'));
      let rotationTimer;

      // ANIMATION CONFIGURATION
      // Modify these values to adjust the animation behavior
      const FADE_OUT_DURATION = 300; // Duration for fading out the current tab (in milliseconds)
      const FADE_IN_DURATION = 100;  // Duration for fading in the new tab (in milliseconds)
      const EASING_FUNCTION = 'ease'; // Choose from: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'
      // or use a cubic-bezier function like 'cubic-bezier(0.1, 0.7, 1.0, 0.1)'
      // Additional easing options (uncomment to use):
      // const EASING_FUNCTION = 'ease-in-quad';
      // const EASING_FUNCTION = 'ease-out-quad';
      // const EASING_FUNCTION = 'ease-in-out-quad';
      // const EASING_FUNCTION = 'ease-in-cubic';
      // const EASING_FUNCTION = 'ease-out-cubic';
      // const EASING_FUNCTION = 'ease-in-out-cubic';
      // const EASING_FUNCTION = 'ease-in-quart';
      // const EASING_FUNCTION = 'ease-out-quart';
      // const EASING_FUNCTION = 'ease-in-out-quart';
      // const EASING_FUNCTION = 'ease-in-quint';
      // const EASING_FUNCTION = 'ease-out-quint';
      // const EASING_FUNCTION = 'ease-in-out-quint';
      // const EASING_FUNCTION = 'ease-in-sine';
      // const EASING_FUNCTION = 'ease-out-sine';
      // const EASING_FUNCTION = 'ease-in-out-sine';
      // const EASING_FUNCTION = 'ease-in-expo';
      // const EASING_FUNCTION = 'ease-out-expo';
      // const EASING_FUNCTION = 'ease-in-out-expo';
      // const EASING_FUNCTION = 'ease-in-circ';
      // const EASING_FUNCTION = 'ease-out-circ';
      // const EASING_FUNCTION = 'ease-in-out-circ';
      // const EASING_FUNCTION = 'ease-in-back';
      // const EASING_FUNCTION = 'ease-out-back';
      // const EASING_FUNCTION = 'ease-in-out-back';
      // END OF ANIMATION CONFIGURATION

      function switchToTab(index) {
        // Fade out current tab
        tabPanes[currentIndex].style.transition = `opacity ${FADE_OUT_DURATION}ms ${EASING_FUNCTION}`;
        tabPanes[currentIndex].style.opacity = '0';

        setTimeout(() => {
          // Remove active classes and update ARIA attributes for current tab and pane
          tabLinks[currentIndex].classList.remove('w--current');
          tabLinks[currentIndex].setAttribute('aria-selected', 'false');
          tabLinks[currentIndex].setAttribute('tabindex', '-1');
          tabPanes[currentIndex].classList.remove('w--tab-active');

          // Update current index
          currentIndex = index;

          // Add active classes and update ARIA attributes for new current tab and pane
          tabLinks[currentIndex].classList.add('w--current');
          tabLinks[currentIndex].setAttribute('aria-selected', 'true');
          tabLinks[currentIndex].setAttribute('tabindex', '0');
          tabPanes[currentIndex].classList.add('w--tab-active');

          // Fade in new tab
          tabPanes[currentIndex].style.transition = `opacity ${FADE_IN_DURATION}ms ${EASING_FUNCTION}`;
          tabPanes[currentIndex].style.opacity = '1';

          // Update the data-current attribute on the parent w-tabs element
          const wTabsElement = container.closest('.w-tabs');
          if (wTabsElement) {
            wTabsElement.setAttribute('data-current', tabLinks[currentIndex].getAttribute('data-w-tab'));
          }
        }, FADE_OUT_DURATION);
      }

      function rotateToNextTab() {
        const nextIndex = (currentIndex + 1) % tabLinks.length;
        switchToTab(nextIndex);
      }

      function startRotation() {
        clearInterval(rotationTimer);
        rotationTimer = setInterval(rotateToNextTab, interval);
      }

      // Add click event listeners to tab links
      tabLinks.forEach((link, index) => {
        link.addEventListener('click', (e) => {
          e.preventDefault();
          switchToTab(index);
          startRotation(); // Restart rotation from this tab
        });
      });

      // Start the initial rotation
      startRotation();
    });
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initializeTabRotator);
</script>
Ansicht Memberscript
UX

#Nr. 110 - QuickInfos für Webflow

Fügen Sie Ihrer Webflow-Website mit Attributen ganz einfach Tippy.js-Tooltips hinzu.


<!-- 💙 MEMBERSCRIPT #110 v0.1 💙 - TOOLTIPS FOR WEBFLOW -->
<script>
  // Function to load Tippy.js, its CSS, and additional theme/animation CSS
  function loadTippy(callback) {
    // Load Tippy.js script
    const script = document.createElement('script');
    script.src = 'https://unpkg.com/@popperjs/core@2';
    script.onload = function() {
      const tippyScript = document.createElement('script');
      tippyScript.src = 'https://unpkg.com/tippy.js@6';
      tippyScript.onload = function() {
        // Load Tippy.js CSS
        const cssFiles = [
          'https://unpkg.com/tippy.js@6/dist/tippy.css',
          'https://unpkg.com/tippy.js@6/themes/light.css',
          'https://unpkg.com/tippy.js@6/themes/light-border.css',
          'https://unpkg.com/tippy.js@6/animations/shift-away.css',
          'https://unpkg.com/tippy.js@6/animations/shift-toward.css',
          'https://unpkg.com/tippy.js@6/animations/scale.css',
          'https://unpkg.com/tippy.js@6/animations/perspective.css'
        ];
        
        let loadedCount = 0;
        cssFiles.forEach(file => {
          const link = document.createElement('link');
          link.href = file;
          link.rel = 'stylesheet';
          link.onload = function() {
            loadedCount++;
            if (loadedCount === cssFiles.length) {
              // Call the callback function when everything is loaded
              callback();
            }
          };
          document.head.appendChild(link);
        });
      };
      document.head.appendChild(tippyScript);
    };
    document.head.appendChild(script);
  }

  // Function to initialize Tippy tooltips
  function initializeTippyTooltips() {
    // Select all elements with any ms-code-tooltip-* attribute
    const elements = document.querySelectorAll('[ms-code-tooltip-top], [ms-code-tooltip-bottom], [ms-code-tooltip-left], [ms-code-tooltip-right], [ms-code-tooltip-content]');

    elements.forEach(element => {
      const tippyOptions = {};

      // Content and Placement
      if (element.hasAttribute('ms-code-tooltip-top')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-top');
        tippyOptions.placement = 'top';
      } else if (element.hasAttribute('ms-code-tooltip-bottom')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-bottom');
        tippyOptions.placement = 'bottom';
      } else if (element.hasAttribute('ms-code-tooltip-left')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-left');
        tippyOptions.placement = 'left';
      } else if (element.hasAttribute('ms-code-tooltip-right')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-right');
        tippyOptions.placement = 'right';
      } else if (element.hasAttribute('ms-code-tooltip-content')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-content');
      }

      if (element.hasAttribute('ms-code-tooltip-placement')) {
        tippyOptions.placement = element.getAttribute('ms-code-tooltip-placement');
      }

      // Theme
      if (element.hasAttribute('ms-code-tooltip-theme')) {
        tippyOptions.theme = element.getAttribute('ms-code-tooltip-theme');
      }

      // Animation
      if (element.hasAttribute('ms-code-tooltip-animation')) {
        tippyOptions.animation = element.getAttribute('ms-code-tooltip-animation');
      }

      // Max Width
      if (element.hasAttribute('ms-code-tooltip-maxwidth')) {
        tippyOptions.maxWidth = parseInt(element.getAttribute('ms-code-tooltip-maxwidth'));
      }

      // Delay
      if (element.hasAttribute('ms-code-tooltip-delay')) {
        tippyOptions.delay = JSON.parse(element.getAttribute('ms-code-tooltip-delay'));
      }

      // Duration
      if (element.hasAttribute('ms-code-tooltip-duration')) {
        tippyOptions.duration = JSON.parse(element.getAttribute('ms-code-tooltip-duration'));
      }

      // Interactive
      if (element.hasAttribute('ms-code-tooltip-interactive')) {
        tippyOptions.interactive = element.getAttribute('ms-code-tooltip-interactive') === 'true';
      }

      // Arrow
      if (element.hasAttribute('ms-code-tooltip-arrow')) {
        tippyOptions.arrow = element.getAttribute('ms-code-tooltip-arrow') === 'true';
      }

      // Trigger
      if (element.hasAttribute('ms-code-tooltip-trigger')) {
        tippyOptions.trigger = element.getAttribute('ms-code-tooltip-trigger');
      }

      // Hide On Click
      if (element.hasAttribute('ms-code-tooltip-hideOnClick')) {
        tippyOptions.hideOnClick = element.getAttribute('ms-code-tooltip-hideOnClick') === 'true';
      }

      // Follow Cursor
      if (element.hasAttribute('ms-code-tooltip-followCursor')) {
        tippyOptions.followCursor = element.getAttribute('ms-code-tooltip-followCursor');
      }

      // Offset
      if (element.hasAttribute('ms-code-tooltip-offset')) {
        tippyOptions.offset = JSON.parse(element.getAttribute('ms-code-tooltip-offset'));
      }

      // Z-Index
      if (element.hasAttribute('ms-code-tooltip-zIndex')) {
        tippyOptions.zIndex = parseInt(element.getAttribute('ms-code-tooltip-zIndex'));
      }

      // Allow HTML
      if (element.hasAttribute('ms-code-tooltip-allowHTML')) {
        tippyOptions.allowHTML = element.getAttribute('ms-code-tooltip-allowHTML') === 'true';
      }

      // Touch
      if (element.hasAttribute('ms-code-tooltip-touch')) {
        const touchValue = element.getAttribute('ms-code-tooltip-touch');
        tippyOptions.touch = touchValue === 'true' || touchValue === 'false' ? (touchValue === 'true') : JSON.parse(touchValue);
      }

      // Initialize Tippy instance
      tippy(element, tippyOptions);
    });
  }

  // Wait for the DOM to be fully loaded, then load Tippy and initialize tooltips
  document.addEventListener('DOMContentLoaded', function() {
    loadTippy(initializeTippyTooltips);
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #110 v0.1 💙 - TOOLTIPS FOR WEBFLOW -->
<script>
  // Function to load Tippy.js, its CSS, and additional theme/animation CSS
  function loadTippy(callback) {
    // Load Tippy.js script
    const script = document.createElement('script');
    script.src = 'https://unpkg.com/@popperjs/core@2';
    script.onload = function() {
      const tippyScript = document.createElement('script');
      tippyScript.src = 'https://unpkg.com/tippy.js@6';
      tippyScript.onload = function() {
        // Load Tippy.js CSS
        const cssFiles = [
          'https://unpkg.com/tippy.js@6/dist/tippy.css',
          'https://unpkg.com/tippy.js@6/themes/light.css',
          'https://unpkg.com/tippy.js@6/themes/light-border.css',
          'https://unpkg.com/tippy.js@6/animations/shift-away.css',
          'https://unpkg.com/tippy.js@6/animations/shift-toward.css',
          'https://unpkg.com/tippy.js@6/animations/scale.css',
          'https://unpkg.com/tippy.js@6/animations/perspective.css'
        ];
        
        let loadedCount = 0;
        cssFiles.forEach(file => {
          const link = document.createElement('link');
          link.href = file;
          link.rel = 'stylesheet';
          link.onload = function() {
            loadedCount++;
            if (loadedCount === cssFiles.length) {
              // Call the callback function when everything is loaded
              callback();
            }
          };
          document.head.appendChild(link);
        });
      };
      document.head.appendChild(tippyScript);
    };
    document.head.appendChild(script);
  }

  // Function to initialize Tippy tooltips
  function initializeTippyTooltips() {
    // Select all elements with any ms-code-tooltip-* attribute
    const elements = document.querySelectorAll('[ms-code-tooltip-top], [ms-code-tooltip-bottom], [ms-code-tooltip-left], [ms-code-tooltip-right], [ms-code-tooltip-content]');

    elements.forEach(element => {
      const tippyOptions = {};

      // Content and Placement
      if (element.hasAttribute('ms-code-tooltip-top')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-top');
        tippyOptions.placement = 'top';
      } else if (element.hasAttribute('ms-code-tooltip-bottom')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-bottom');
        tippyOptions.placement = 'bottom';
      } else if (element.hasAttribute('ms-code-tooltip-left')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-left');
        tippyOptions.placement = 'left';
      } else if (element.hasAttribute('ms-code-tooltip-right')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-right');
        tippyOptions.placement = 'right';
      } else if (element.hasAttribute('ms-code-tooltip-content')) {
        tippyOptions.content = element.getAttribute('ms-code-tooltip-content');
      }

      if (element.hasAttribute('ms-code-tooltip-placement')) {
        tippyOptions.placement = element.getAttribute('ms-code-tooltip-placement');
      }

      // Theme
      if (element.hasAttribute('ms-code-tooltip-theme')) {
        tippyOptions.theme = element.getAttribute('ms-code-tooltip-theme');
      }

      // Animation
      if (element.hasAttribute('ms-code-tooltip-animation')) {
        tippyOptions.animation = element.getAttribute('ms-code-tooltip-animation');
      }

      // Max Width
      if (element.hasAttribute('ms-code-tooltip-maxwidth')) {
        tippyOptions.maxWidth = parseInt(element.getAttribute('ms-code-tooltip-maxwidth'));
      }

      // Delay
      if (element.hasAttribute('ms-code-tooltip-delay')) {
        tippyOptions.delay = JSON.parse(element.getAttribute('ms-code-tooltip-delay'));
      }

      // Duration
      if (element.hasAttribute('ms-code-tooltip-duration')) {
        tippyOptions.duration = JSON.parse(element.getAttribute('ms-code-tooltip-duration'));
      }

      // Interactive
      if (element.hasAttribute('ms-code-tooltip-interactive')) {
        tippyOptions.interactive = element.getAttribute('ms-code-tooltip-interactive') === 'true';
      }

      // Arrow
      if (element.hasAttribute('ms-code-tooltip-arrow')) {
        tippyOptions.arrow = element.getAttribute('ms-code-tooltip-arrow') === 'true';
      }

      // Trigger
      if (element.hasAttribute('ms-code-tooltip-trigger')) {
        tippyOptions.trigger = element.getAttribute('ms-code-tooltip-trigger');
      }

      // Hide On Click
      if (element.hasAttribute('ms-code-tooltip-hideOnClick')) {
        tippyOptions.hideOnClick = element.getAttribute('ms-code-tooltip-hideOnClick') === 'true';
      }

      // Follow Cursor
      if (element.hasAttribute('ms-code-tooltip-followCursor')) {
        tippyOptions.followCursor = element.getAttribute('ms-code-tooltip-followCursor');
      }

      // Offset
      if (element.hasAttribute('ms-code-tooltip-offset')) {
        tippyOptions.offset = JSON.parse(element.getAttribute('ms-code-tooltip-offset'));
      }

      // Z-Index
      if (element.hasAttribute('ms-code-tooltip-zIndex')) {
        tippyOptions.zIndex = parseInt(element.getAttribute('ms-code-tooltip-zIndex'));
      }

      // Allow HTML
      if (element.hasAttribute('ms-code-tooltip-allowHTML')) {
        tippyOptions.allowHTML = element.getAttribute('ms-code-tooltip-allowHTML') === 'true';
      }

      // Touch
      if (element.hasAttribute('ms-code-tooltip-touch')) {
        const touchValue = element.getAttribute('ms-code-tooltip-touch');
        tippyOptions.touch = touchValue === 'true' || touchValue === 'false' ? (touchValue === 'true') : JSON.parse(touchValue);
      }

      // Initialize Tippy instance
      tippy(element, tippyOptions);
    });
  }

  // Wait for the DOM to be fully loaded, then load Tippy and initialize tooltips
  document.addEventListener('DOMContentLoaded', function() {
    loadTippy(initializeTippyTooltips);
  });
</script>
Ansicht Memberscript
Benutzerdefinierte Felder
UX

#109 - Benutzerdefinierte Multi Selects

Individuell gestaltete Mehrfachauswahlen mit Suche, Tastaturauswahl und mehr.


<!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
<script>
  $(document).ready(function() {
    $('[ms-code-select-wrapper]').each(function() {
      const $wrapper = $(this);
      const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
      const $input = $wrapper.find('[ms-code-select="input"]');
      const $list = $wrapper.find('[ms-code-select="list"]');
      const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
      const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
      const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());

      let selectedOptions = [];
      let highlightedIndex = -1;

      const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
      const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
      $templateSelectedTag.remove();

      const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
      const templateNewTagHTML = $templateNewTag.prop('outerHTML');
      $templateNewTag.remove();

      function createSelectedTag(value) {
        const $newTag = $(templateSelectedTagHTML);
        $newTag.find('[ms-code-select="tag-name-selected"]').text(value);
        $newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
          e.stopPropagation();
          removeTag(value);
        });
        return $newTag;
      }

      function addTag(value) {
        if (!selectedOptions.includes(value) && options.includes(value)) {
          selectedOptions.push(value);
          $selectedWrapper.append(createSelectedTag(value));
          updateInput();
          filterOptions();
        }
      }

      function removeTag(value) {
        selectedOptions = selectedOptions.filter(option => option !== value);
        $selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
        updateInput();
        if (isMulti && selectedOptions.length > 0) {
          $input.val($input.val() + ', ');
        }
        filterOptions();
      }

      function updateInput() {
        $input.val(selectedOptions.join(', '));
      }

      function toggleList(show) {
        $list.toggle(show);
      }

      function createOptionElement(value) {
        const $option = $(templateNewTagHTML);
        $option.text(value);
        $option.on('click', function() {
          selectOption(value);
        });
        return $option;
      }

      function selectOption(value) {
        if (isMulti) {
          addTag(value);
          $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
          $input.focus();
        } else {
          selectedOptions = [value];
          $selectedWrapper.empty().append(createSelectedTag(value));
          updateInput();
          toggleList(false);
        }
        filterOptions();
      }

      function filterOptions() {
        const inputValue = $input.val();
        const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
        let visibleOptionsCount = 0;

        $list.find('[ms-code-select="tag-name-new"]').each(function() {
          const $option = $(this);
          const optionText = $option.text().toLowerCase();
          const matches = optionText.includes(searchTerm.toLowerCase());
          const isSelected = selectedOptions.includes($option.text());
          $option.toggle(matches && !isSelected);
          if (matches && !isSelected) visibleOptionsCount++;
        });

        $emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
        highlightedIndex = -1;
        updateHighlight();
      }

      function cleanInput() {
        const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
        const validValues = inputValues.filter(v => options.includes(v));
        selectedOptions = validValues;
        $selectedWrapper.empty();
        selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
        updateInput();
        filterOptions();
      }

      function handleInputChange() {
        const inputValue = $input.val();
        const inputValues = inputValue.split(',').map(v => v.trim());
        const lastValue = inputValues[inputValues.length - 1];

        if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
          inputValues.pop();
          const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
          newValidValues.forEach(addTag);
          $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
        } else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
          addTag(lastValue);
          $input.val(selectedOptions.join(', ') + ', ');
        }

        filterOptions();
      }

      function initializeWithValue() {
        const initialValue = $input.val();
        if (initialValue) {
          const initialValues = initialValue.split(',').map(v => v.trim());
          initialValues.forEach(value => {
            if (options.includes(value)) {
              addTag(value);
            }
          });
          updateInput();
          filterOptions();
        }
      }

      function updateHighlight() {
        $list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
        if (highlightedIndex >= 0) {
          $list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
            .addClass('highlighted')
            .css('background-color', '#e0e0e0');
        }
      }

      function handleKeyDown(e) {
        const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
        const optionCount = visibleOptions.length;

        switch (e.key) {
          case 'ArrowDown':
            e.preventDefault();
            highlightedIndex = (highlightedIndex + 1) % optionCount;
            updateHighlight();
            break;
          case 'ArrowUp':
            e.preventDefault();
            highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
            updateHighlight();
            break;
          case 'Enter':
            e.preventDefault();
            if (highlightedIndex >= 0) {
              const selectedValue = visibleOptions.eq(highlightedIndex).text();
              selectOption(selectedValue);
            }
            break;
        }
      }

      $.each(options, function(i, option) {
        $list.append(createOptionElement(option));
      });

      $input.on('focus', function() {
        toggleList(true);
        if (isMulti) {
          const currentVal = $input.val().trim();
          if (currentVal !== '' && !currentVal.endsWith(',')) {
            $input.val(currentVal + ', ');
          }
          this.selectionStart = this.selectionEnd = this.value.length;
        }
        filterOptions();
      });

      $input.on('click', function(e) {
        e.preventDefault();
        this.selectionStart = this.selectionEnd = this.value.length;
      });

      $input.on('blur', function() {
        setTimeout(function() {
          if (!$list.is(':hover')) {
            toggleList(false);
            cleanInput();
          }
        }, 100);
      });

      $input.on('input', handleInputChange);
      $input.on('keydown', handleKeyDown);

      $list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
        $(this).css('background-color', '#e0e0e0');
      });

      $list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
        if (!$(this).hasClass('highlighted')) {
          $(this).css('background-color', '');
        }
      });

      initializeWithValue();
      toggleList(false);
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
<script>
  $(document).ready(function() {
    $('[ms-code-select-wrapper]').each(function() {
      const $wrapper = $(this);
      const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
      const $input = $wrapper.find('[ms-code-select="input"]');
      const $list = $wrapper.find('[ms-code-select="list"]');
      const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
      const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
      const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());

      let selectedOptions = [];
      let highlightedIndex = -1;

      const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
      const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
      $templateSelectedTag.remove();

      const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
      const templateNewTagHTML = $templateNewTag.prop('outerHTML');
      $templateNewTag.remove();

      function createSelectedTag(value) {
        const $newTag = $(templateSelectedTagHTML);
        $newTag.find('[ms-code-select="tag-name-selected"]').text(value);
        $newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
          e.stopPropagation();
          removeTag(value);
        });
        return $newTag;
      }

      function addTag(value) {
        if (!selectedOptions.includes(value) && options.includes(value)) {
          selectedOptions.push(value);
          $selectedWrapper.append(createSelectedTag(value));
          updateInput();
          filterOptions();
        }
      }

      function removeTag(value) {
        selectedOptions = selectedOptions.filter(option => option !== value);
        $selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
        updateInput();
        if (isMulti && selectedOptions.length > 0) {
          $input.val($input.val() + ', ');
        }
        filterOptions();
      }

      function updateInput() {
        $input.val(selectedOptions.join(', '));
      }

      function toggleList(show) {
        $list.toggle(show);
      }

      function createOptionElement(value) {
        const $option = $(templateNewTagHTML);
        $option.text(value);
        $option.on('click', function() {
          selectOption(value);
        });
        return $option;
      }

      function selectOption(value) {
        if (isMulti) {
          addTag(value);
          $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
          $input.focus();
        } else {
          selectedOptions = [value];
          $selectedWrapper.empty().append(createSelectedTag(value));
          updateInput();
          toggleList(false);
        }
        filterOptions();
      }

      function filterOptions() {
        const inputValue = $input.val();
        const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
        let visibleOptionsCount = 0;

        $list.find('[ms-code-select="tag-name-new"]').each(function() {
          const $option = $(this);
          const optionText = $option.text().toLowerCase();
          const matches = optionText.includes(searchTerm.toLowerCase());
          const isSelected = selectedOptions.includes($option.text());
          $option.toggle(matches && !isSelected);
          if (matches && !isSelected) visibleOptionsCount++;
        });

        $emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
        highlightedIndex = -1;
        updateHighlight();
      }

      function cleanInput() {
        const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
        const validValues = inputValues.filter(v => options.includes(v));
        selectedOptions = validValues;
        $selectedWrapper.empty();
        selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
        updateInput();
        filterOptions();
      }

      function handleInputChange() {
        const inputValue = $input.val();
        const inputValues = inputValue.split(',').map(v => v.trim());
        const lastValue = inputValues[inputValues.length - 1];

        if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
          inputValues.pop();
          const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
          newValidValues.forEach(addTag);
          $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
        } else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
          addTag(lastValue);
          $input.val(selectedOptions.join(', ') + ', ');
        }

        filterOptions();
      }

      function initializeWithValue() {
        const initialValue = $input.val();
        if (initialValue) {
          const initialValues = initialValue.split(',').map(v => v.trim());
          initialValues.forEach(value => {
            if (options.includes(value)) {
              addTag(value);
            }
          });
          updateInput();
          filterOptions();
        }
      }

      function updateHighlight() {
        $list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
        if (highlightedIndex >= 0) {
          $list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
            .addClass('highlighted')
            .css('background-color', '#e0e0e0');
        }
      }

      function handleKeyDown(e) {
        const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
        const optionCount = visibleOptions.length;

        switch (e.key) {
          case 'ArrowDown':
            e.preventDefault();
            highlightedIndex = (highlightedIndex + 1) % optionCount;
            updateHighlight();
            break;
          case 'ArrowUp':
            e.preventDefault();
            highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
            updateHighlight();
            break;
          case 'Enter':
            e.preventDefault();
            if (highlightedIndex >= 0) {
              const selectedValue = visibleOptions.eq(highlightedIndex).text();
              selectOption(selectedValue);
            }
            break;
        }
      }

      $.each(options, function(i, option) {
        $list.append(createOptionElement(option));
      });

      $input.on('focus', function() {
        toggleList(true);
        if (isMulti) {
          const currentVal = $input.val().trim();
          if (currentVal !== '' && !currentVal.endsWith(',')) {
            $input.val(currentVal + ', ');
          }
          this.selectionStart = this.selectionEnd = this.value.length;
        }
        filterOptions();
      });

      $input.on('click', function(e) {
        e.preventDefault();
        this.selectionStart = this.selectionEnd = this.value.length;
      });

      $input.on('blur', function() {
        setTimeout(function() {
          if (!$list.is(':hover')) {
            toggleList(false);
            cleanInput();
          }
        }, 100);
      });

      $input.on('input', handleInputChange);
      $input.on('keydown', handleKeyDown);

      $list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
        $(this).css('background-color', '#e0e0e0');
      });

      $list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
        if (!$(this).hasClass('highlighted')) {
          $(this).css('background-color', '');
        }
      });

      initializeWithValue();
      toggleList(false);
    });
  });
</script>
Ansicht Memberscript
UX

#Nr. 108 - Benutzerdefinierte Formularübermittlungsschaltflächen

Erstellen Sie ein beliebiges Element in Webflow und verwenden Sie es, um eine beliebige Art von Formular zu übermitteln.


<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
    // Find all elements with the ms-code-submit-new attribute
    const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');

    // Add click event listeners to each new submit button
    newSubmitButtons.forEach(button => {
        button.addEventListener('click', function(e) {
            e.preventDefault(); // Prevent default action if it's a link

            // Get the value of the ms-code-submit-new attribute
            const submitId = this.getAttribute('ms-code-submit-new');

            // Find the corresponding old submit button
            const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);

            // If found, trigger a click on the old submit button
            if (oldSubmitButton) {
                oldSubmitButton.click();
            } else {
                console.error(`No matching old submit button found for ID: ${submitId}`);
            }
        });
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
    // Find all elements with the ms-code-submit-new attribute
    const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');

    // Add click event listeners to each new submit button
    newSubmitButtons.forEach(button => {
        button.addEventListener('click', function(e) {
            e.preventDefault(); // Prevent default action if it's a link

            // Get the value of the ms-code-submit-new attribute
            const submitId = this.getAttribute('ms-code-submit-new');

            // Find the corresponding old submit button
            const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);

            // If found, trigger a click on the old submit button
            if (oldSubmitButton) {
                oldSubmitButton.click();
            } else {
                console.error(`No matching old submit button found for ID: ${submitId}`);
            }
        });
    });
});
</script>
Ansicht Memberscript
UX

#Nr. 107 - Plan mit Funkgeräten auswählen

Fügen Sie den Anmeldeformularen und den Formularen zur Planaktualisierung ein Auswahlradio für den Plan hinzu.


<!-- 💙 MEMBERSCRIPT #107 v0.1 💙 SELECT PLAN WITH RADIO BUTTONS -->
<script>
  (function() {
    const PRICE_ATTRIBUTES = [
      'data-ms-plan:add',
      'data-ms-plan:update',
      'data-ms-price:add',
      'data-ms-price:update'
    ];

    function findElementWithAttribute(form) {
      // First, check if the form itself has one of the attributes
      for (let attr of PRICE_ATTRIBUTES) {
        if (form.hasAttribute(attr)) {
          return { element: form, attribute: attr };
        }
      }

      // If not found on form, search child elements
      for (let attr of PRICE_ATTRIBUTES) {
        let element = Array.from(form.querySelectorAll('*')).find(el => el.hasAttribute(attr));
        if (element) {
          return { element, attribute: attr };
        }
      }
      return null;
    }

    function updateAttribute(radio) {
      const form = radio.closest('form');
      if (!form) return;

      const result = findElementWithAttribute(form);
      if (result) {
        result.element.setAttribute(result.attribute, radio.value);
      }
    }

    function handleRadioChange(e) {
      updateAttribute(e.target);
    }

    function initializeRadioButtons() {
      const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
      forms.forEach(form => {
        const radios = form.querySelectorAll('input[type="radio"]');
        radios.forEach(radio => {
          radio.addEventListener('change', handleRadioChange);
          if (radio.checked) {
            updateAttribute(radio);
          }
        });
      });
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initializeRadioButtons);
    } else {
      initializeRadioButtons();
    }
  })();

  // Only keep this section if you have an update plan form on the page
  (function() {
    function updateRadioButtonState(radio) {
      radio.checked = true;
      radio.dispatchEvent(new Event('change'));

      // Update custom radio button UI if present
      const customRadio = radio.parentElement.querySelector('.w-radio-input');
      if (customRadio) {
        customRadio.classList.add('w--redirected-checked');
      }
    }

    function checkAndSelectPlan() {
      const msMemData = localStorage.getItem('_ms-mem');
      if (!msMemData) return;

      try {
        const memberData = JSON.parse(msMemData);
        const activePlanConnections = memberData.planConnections?.filter(conn => conn.active) || [];

        if (activePlanConnections.length === 0) return;

        const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
        forms.forEach(form => {
          const radios = form.querySelectorAll('input[type="radio"]');
          radios.forEach(radio => {
            const matchingPlan = activePlanConnections.find(conn => conn.payment.priceId === radio.value);
            if (matchingPlan) {
              updateRadioButtonState(radio);
            }
          });
        });
      } catch (error) {
        console.error('Error processing _ms-mem data:', error);
      }
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', checkAndSelectPlan);
    } else {
      checkAndSelectPlan();
    }
  })();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #107 v0.1 💙 SELECT PLAN WITH RADIO BUTTONS -->
<script>
  (function() {
    const PRICE_ATTRIBUTES = [
      'data-ms-plan:add',
      'data-ms-plan:update',
      'data-ms-price:add',
      'data-ms-price:update'
    ];

    function findElementWithAttribute(form) {
      // First, check if the form itself has one of the attributes
      for (let attr of PRICE_ATTRIBUTES) {
        if (form.hasAttribute(attr)) {
          return { element: form, attribute: attr };
        }
      }

      // If not found on form, search child elements
      for (let attr of PRICE_ATTRIBUTES) {
        let element = Array.from(form.querySelectorAll('*')).find(el => el.hasAttribute(attr));
        if (element) {
          return { element, attribute: attr };
        }
      }
      return null;
    }

    function updateAttribute(radio) {
      const form = radio.closest('form');
      if (!form) return;

      const result = findElementWithAttribute(form);
      if (result) {
        result.element.setAttribute(result.attribute, radio.value);
      }
    }

    function handleRadioChange(e) {
      updateAttribute(e.target);
    }

    function initializeRadioButtons() {
      const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
      forms.forEach(form => {
        const radios = form.querySelectorAll('input[type="radio"]');
        radios.forEach(radio => {
          radio.addEventListener('change', handleRadioChange);
          if (radio.checked) {
            updateAttribute(radio);
          }
        });
      });
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initializeRadioButtons);
    } else {
      initializeRadioButtons();
    }
  })();

  // Only keep this section if you have an update plan form on the page
  (function() {
    function updateRadioButtonState(radio) {
      radio.checked = true;
      radio.dispatchEvent(new Event('change'));

      // Update custom radio button UI if present
      const customRadio = radio.parentElement.querySelector('.w-radio-input');
      if (customRadio) {
        customRadio.classList.add('w--redirected-checked');
      }
    }

    function checkAndSelectPlan() {
      const msMemData = localStorage.getItem('_ms-mem');
      if (!msMemData) return;

      try {
        const memberData = JSON.parse(msMemData);
        const activePlanConnections = memberData.planConnections?.filter(conn => conn.active) || [];

        if (activePlanConnections.length === 0) return;

        const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
        forms.forEach(form => {
          const radios = form.querySelectorAll('input[type="radio"]');
          radios.forEach(radio => {
            const matchingPlan = activePlanConnections.find(conn => conn.payment.priceId === radio.value);
            if (matchingPlan) {
              updateRadioButtonState(radio);
            }
          });
        });
      } catch (error) {
        console.error('Error processing _ms-mem data:', error);
      }
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', checkAndSelectPlan);
    } else {
      checkAndSelectPlan();
    }
  })();
</script>
Ansicht Memberscript
JSON

#Nr. 106 - Liken und Speichern von CMS-Artikeln

Erlauben Sie Ihren Mitgliedern, CMS-Elemente in ihrem Profil zu speichern.


<!-- 💙 MEMBERSCRIPT #106 v0.2 💙 SAVING & UNSAVING CMS ITEMS -->
<style>
  [ms-code-save], [ms-code-unsave] {
    display: none;
  }
  [ms-code-save-item] {
    display: none;
  }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
  const memberstack = window.$memberstackDom;
  let isLoggedIn = false;
  let savedItems = [];

  async function checkMemberLogin() {
    try {
      const member = await memberstack.getCurrentMember();
      return !!member;
    } catch (error) {
      return false;
    }
  }

  function getSavedItems(memberData) {
    return memberData.savedItems || [];
  }

  function updateButtonVisibility() {
    const saveButtons = document.querySelectorAll('[ms-code-save]');
    const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');

    saveButtons.forEach(button => {
      const itemId = button.getAttribute('ms-code-save');
      button.style.display = !savedItems.includes(itemId) ? 'block' : 'none';
    });

    unsaveButtons.forEach(button => {
      const itemId = button.getAttribute('ms-code-unsave');
      button.style.display = savedItems.includes(itemId) ? 'block' : 'none';
    });
  }

  function updateItemVisibility() {
    const saveLists = document.querySelectorAll('[ms-code-save-list]');
    saveLists.forEach(list => {
      const filter = list.getAttribute('ms-code-save-list');
      const items = list.querySelectorAll('[ms-code-save-item]');
      items.forEach(item => {
        const saveButton = item.querySelector('[ms-code-save]');
        if (!saveButton) {
          item.style.display = 'block';
          return;
        }
        const itemId = saveButton.getAttribute('ms-code-save');
        
        if (!isLoggedIn || filter === 'all') {
          item.style.display = 'block';
        } else if (filter === 'saved' & savedItems.includes(itemId)) {
          item.style.display = 'block';
        } else if (filter === 'unsaved' & !savedItems.includes(itemId)) {
          item.style.display = 'block';
        } else {
          item.style.display = 'none';
        }
      });
    });
  }

  async function handleButtonClick(event) {
    if (!isLoggedIn) return;

    const button = event.currentTarget;
    const action = button.getAttribute('ms-code-save') ? 'save' : 'unsave';
    const itemId = button.getAttribute(action === 'save' ? 'ms-code-save' : 'ms-code-unsave');
    
    if (action === 'save' && !savedItems.includes(itemId)) {
      savedItems.push(itemId);
    } else if (action === 'unsave') {
      savedItems = savedItems.filter(id => id !== itemId);
    }

    try {
      await memberstack.updateMemberJSON({ json: { savedItems } });
    } catch (error) {
      // Silently handle the error
    }

    updateButtonVisibility();
    updateItemVisibility();
  }

  function addClickListeners() {
    const saveButtons = document.querySelectorAll('[ms-code-save]');
    const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
    saveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
    unsaveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
  }

  async function initializeScript() {
    isLoggedIn = await checkMemberLogin();

    if (isLoggedIn) {
      try {
        const result = await memberstack.getMemberJSON();
        const memberData = result.data || {};
        savedItems = getSavedItems(memberData);
      } catch (error) {
        // Silently handle the error
      }
    }

    updateButtonVisibility();
    updateItemVisibility();
    addClickListeners();

    // Set up a MutationObserver to watch for changes in the DOM
    const observer = new MutationObserver((mutations) => {
      let shouldUpdate = false;
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          shouldUpdate = true;
        }
      });
      if (shouldUpdate) {
        updateButtonVisibility();
        updateItemVisibility();
        addClickListeners();
      }
    });

    // Start observing the document with the configured parameters
    observer.observe(document.body, { childList: true, subtree: true });
  }

  initializeScript();
});
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #106 v0.2 💙 SAVING & UNSAVING CMS ITEMS -->
<style>
  [ms-code-save], [ms-code-unsave] {
    display: none;
  }
  [ms-code-save-item] {
    display: none;
  }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
  const memberstack = window.$memberstackDom;
  let isLoggedIn = false;
  let savedItems = [];

  async function checkMemberLogin() {
    try {
      const member = await memberstack.getCurrentMember();
      return !!member;
    } catch (error) {
      return false;
    }
  }

  function getSavedItems(memberData) {
    return memberData.savedItems || [];
  }

  function updateButtonVisibility() {
    const saveButtons = document.querySelectorAll('[ms-code-save]');
    const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');

    saveButtons.forEach(button => {
      const itemId = button.getAttribute('ms-code-save');
      button.style.display = !savedItems.includes(itemId) ? 'block' : 'none';
    });

    unsaveButtons.forEach(button => {
      const itemId = button.getAttribute('ms-code-unsave');
      button.style.display = savedItems.includes(itemId) ? 'block' : 'none';
    });
  }

  function updateItemVisibility() {
    const saveLists = document.querySelectorAll('[ms-code-save-list]');
    saveLists.forEach(list => {
      const filter = list.getAttribute('ms-code-save-list');
      const items = list.querySelectorAll('[ms-code-save-item]');
      items.forEach(item => {
        const saveButton = item.querySelector('[ms-code-save]');
        if (!saveButton) {
          item.style.display = 'block';
          return;
        }
        const itemId = saveButton.getAttribute('ms-code-save');
        
        if (!isLoggedIn || filter === 'all') {
          item.style.display = 'block';
        } else if (filter === 'saved' & savedItems.includes(itemId)) {
          item.style.display = 'block';
        } else if (filter === 'unsaved' & !savedItems.includes(itemId)) {
          item.style.display = 'block';
        } else {
          item.style.display = 'none';
        }
      });
    });
  }

  async function handleButtonClick(event) {
    if (!isLoggedIn) return;

    const button = event.currentTarget;
    const action = button.getAttribute('ms-code-save') ? 'save' : 'unsave';
    const itemId = button.getAttribute(action === 'save' ? 'ms-code-save' : 'ms-code-unsave');
    
    if (action === 'save' && !savedItems.includes(itemId)) {
      savedItems.push(itemId);
    } else if (action === 'unsave') {
      savedItems = savedItems.filter(id => id !== itemId);
    }

    try {
      await memberstack.updateMemberJSON({ json: { savedItems } });
    } catch (error) {
      // Silently handle the error
    }

    updateButtonVisibility();
    updateItemVisibility();
  }

  function addClickListeners() {
    const saveButtons = document.querySelectorAll('[ms-code-save]');
    const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
    saveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
    unsaveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
  }

  async function initializeScript() {
    isLoggedIn = await checkMemberLogin();

    if (isLoggedIn) {
      try {
        const result = await memberstack.getMemberJSON();
        const memberData = result.data || {};
        savedItems = getSavedItems(memberData);
      } catch (error) {
        // Silently handle the error
      }
    }

    updateButtonVisibility();
    updateItemVisibility();
    addClickListeners();

    // Set up a MutationObserver to watch for changes in the DOM
    const observer = new MutationObserver((mutations) => {
      let shouldUpdate = false;
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          shouldUpdate = true;
        }
      });
      if (shouldUpdate) {
        updateButtonVisibility();
        updateItemVisibility();
        addClickListeners();
      }
    });

    // Start observing the document with the configured parameters
    observer.observe(document.body, { childList: true, subtree: true });
  }

  initializeScript();
});
</script>
Ansicht Memberscript
UX
Benutzerdefinierte Abläufe

#Nr. 105 - Auschecken nach Anmeldung

Automatischer Start der Kasse, wenn ein Mitglied einen Preis auswählt, bevor es sich einloggt.


<!-- 💙 MEMBERSCRIPT #105 v0.1 💙 CHECKOUT AFTER LOGIN -->
<script>
  /* Checks if the current URL matches the configured redirect URL, or if no specific URL is required */
  function isCorrectPage() {
    return redirectOnLoginURL === '' || window.location.pathname === redirectOnLoginURL;
  }

  /* Checks if Memberstack is fully loaded before running any Memberstack-specific code.*/
  function memberstackReady(callback) {
    function checkAndExecute() {
      if (window.$memberstackDom) {
        callback(); // Memberstack is ready, run the callback function.
      } else {
        setTimeout(checkAndExecute, 100); // Wait for 100ms and check again.
      }
    }

    checkAndExecute(); // Start checking if Memberstack is ready.
  }

  /* Initiates the Stripe checkout process with a specified price ID.*/
  async function initiateCheckout(priceId) {
    try {
      // Set a flag in session storage to indicate that the checkout page was accessed.
      sessionStorage.setItem('ms_checkout_viewed', 'true');
      await window.$memberstackDom.purchasePlansWithCheckout({
        priceId, // The price ID for the product being purchased.
        returnUrl: window.location.href, // Redirect the user back here after completing the checkout.
      });
    } catch (error) {
      console.error('Failed to initiate payment:', error); // Provide error details in the console.
    }
  }

  /* Main execution flow that starts once Memberstack is confirmed to be ready */
  memberstackReady(() => {
    window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
      if (member && sessionStorage.getItem('ms_price') && !sessionStorage.getItem('ms_checkout_viewed')) {
        initiateCheckout(sessionStorage.getItem('ms_price')); // Start the checkout process if conditions are met.
      }
    }).catch(error => {
      console.error('Failed to retrieve user data:', error); // Log an error if fetching member data fails.
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #105 v0.1 💙 CHECKOUT AFTER LOGIN -->
<script>
  /* Checks if the current URL matches the configured redirect URL, or if no specific URL is required */
  function isCorrectPage() {
    return redirectOnLoginURL === '' || window.location.pathname === redirectOnLoginURL;
  }

  /* Checks if Memberstack is fully loaded before running any Memberstack-specific code.*/
  function memberstackReady(callback) {
    function checkAndExecute() {
      if (window.$memberstackDom) {
        callback(); // Memberstack is ready, run the callback function.
      } else {
        setTimeout(checkAndExecute, 100); // Wait for 100ms and check again.
      }
    }

    checkAndExecute(); // Start checking if Memberstack is ready.
  }

  /* Initiates the Stripe checkout process with a specified price ID.*/
  async function initiateCheckout(priceId) {
    try {
      // Set a flag in session storage to indicate that the checkout page was accessed.
      sessionStorage.setItem('ms_checkout_viewed', 'true');
      await window.$memberstackDom.purchasePlansWithCheckout({
        priceId, // The price ID for the product being purchased.
        returnUrl: window.location.href, // Redirect the user back here after completing the checkout.
      });
    } catch (error) {
      console.error('Failed to initiate payment:', error); // Provide error details in the console.
    }
  }

  /* Main execution flow that starts once Memberstack is confirmed to be ready */
  memberstackReady(() => {
    window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
      if (member && sessionStorage.getItem('ms_price') && !sessionStorage.getItem('ms_checkout_viewed')) {
        initiateCheckout(sessionStorage.getItem('ms_price')); // Start the checkout process if conditions are met.
      }
    }).catch(error => {
      console.error('Failed to retrieve user data:', error); // Log an error if fetching member data fails.
    });
  });
</script>
Ansicht Memberscript
UX

#Nr. 104 - Online-Anzeige

Zeigen Sie Ihren Website-Besuchern Ihren Online-Status auf der Grundlage von Zeitzonen.


<!-- 💙 MEMBERSCRIPT #104 v0.1 💙 ONLINE INDICATOR -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    const businessHours = {
        start: 9, // Business hours start at 9 AM
        end: 17, // Business hours end at 5 PM
        days: [1, 2, 3, 4, 5] // Monday to Friday
    };
    const colors = {
        businessHours: '#34b426',
        outsideBusinessHours: '#F25022'
    };

    const wrappers = document.querySelectorAll('[ms-code-online-wrapper]');
    wrappers.forEach(wrapper => {
        const timeZone = wrapper.getAttribute('ms-code-online-wrapper');
        const dot = wrapper.querySelector('[ms-code-online="dot"]');
        const timeSpan = wrapper.querySelector('[ms-code-online="time"]');

        const now = new Date();
        const formatter = new Intl.DateTimeFormat('en-US', {
            hour: 'numeric',
            minute: '2-digit',
            timeZone: timeZone
        });
        const formattedTime = formatter.format(now);

        if (timeSpan) timeSpan.textContent = formattedTime;

        const currentDay = now.getDay();
        const currentHour = new Date().toLocaleTimeString('en-US', {
            hour: '2-digit',
            hour12: false,
            timeZone: timeZone
        });

        const isBusinessDay = businessHours.days.includes(currentDay);
        const isBusinessHour = currentHour >= businessHours.start && currentHour < businessHours.end;

        if (dot) {
            dot.style.backgroundColor = (isBusinessDay && isBusinessHour) ? colors.businessHours : colors.outsideBusinessHours;
        }
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #104 v0.1 💙 ONLINE INDICATOR -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    const businessHours = {
        start: 9, // Business hours start at 9 AM
        end: 17, // Business hours end at 5 PM
        days: [1, 2, 3, 4, 5] // Monday to Friday
    };
    const colors = {
        businessHours: '#34b426',
        outsideBusinessHours: '#F25022'
    };

    const wrappers = document.querySelectorAll('[ms-code-online-wrapper]');
    wrappers.forEach(wrapper => {
        const timeZone = wrapper.getAttribute('ms-code-online-wrapper');
        const dot = wrapper.querySelector('[ms-code-online="dot"]');
        const timeSpan = wrapper.querySelector('[ms-code-online="time"]');

        const now = new Date();
        const formatter = new Intl.DateTimeFormat('en-US', {
            hour: 'numeric',
            minute: '2-digit',
            timeZone: timeZone
        });
        const formattedTime = formatter.format(now);

        if (timeSpan) timeSpan.textContent = formattedTime;

        const currentDay = now.getDay();
        const currentHour = new Date().toLocaleTimeString('en-US', {
            hour: '2-digit',
            hour12: false,
            timeZone: timeZone
        });

        const isBusinessDay = businessHours.days.includes(currentDay);
        const isBusinessHour = currentHour >= businessHours.start && currentHour < businessHours.end;

        if (dot) {
            dot.style.backgroundColor = (isBusinessDay && isBusinessHour) ? colors.businessHours : colors.outsideBusinessHours;
        }
    });
});
</script>
Ansicht Memberscript
Integration

#Nr. 103 - Benutzerdefiniertes Buchungsformular

Fügen Sie Ihrer Website ein benutzerdefiniertes Buchungsformular hinzu, das ein Google-Kalenderereignis erstellt.


<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
  function getNextBusinessDays() {
    let businessDays = [];
    let currentDate = moment();
    currentDate.add(1, 'days');
    
    while (businessDays.length < 14) {
      if (currentDate.day() !== 0 && currentDate.day() !== 6) {
        let formattedDay = currentDate.format('dddd, MMMM D');
        let rawDay = currentDate.format('YYYY-MM-DD');
        businessDays.push({formattedDay, rawDay});
      }
      currentDate.add(1, 'days');
    }
    return businessDays;
  }

  function generateTimeSlots() {
    let slots = [];
    let startHour = 9;
    let endHour = 16.5;
    let currentTime = moment().startOf('day').add(startHour, 'hours');
    
    while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
      let formattedTime = currentTime.format('h:mm A');
      let timeValue = currentTime.format('HH:mm');
      slots.push({formattedTime, timeValue});
      currentTime.add(30, 'minutes');
    }
    return slots;
  }

  function updateTimestamp(day, time, timezone) {
    let timestampInput = document.getElementById('timestamp');
    if (!timestampInput) {
        timestampInput = document.createElement('input');
        timestampInput.type = 'hidden';
        timestampInput.id = 'timestamp';
        timestampInput.name = 'timestamp';
        document.querySelector('form').appendChild(timestampInput);
    }

    let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
    timestampInput.value = datetime.valueOf();
  }

  function populateFields() {
    const days = getNextBusinessDays();
    const times = generateTimeSlots();
    const daySelect = document.querySelector('[ms-code-booking="day"]');
    const timeSelect = document.querySelector('[ms-code-booking="time"]');
    const form = daySelect.closest('form');
    const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
    
    days.forEach(({formattedDay, rawDay}) => {
      let option = new Option(formattedDay, rawDay);
      daySelect.appendChild(option);
    });
    
    times.forEach(({formattedTime, timeValue}) => {
      let option = new Option(formattedTime, timeValue);
      timeSelect.appendChild(option);
    });

    function handleSelectChange() {
      if (daySelect.value && timeSelect.value) {
        updateTimestamp(daySelect.value, timeSelect.value, timezone);
      }
    }

    daySelect.addEventListener('change', handleSelectChange);
    timeSelect.addEventListener('change', handleSelectChange);
  }

  populateFields();
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
  function getNextBusinessDays() {
    let businessDays = [];
    let currentDate = moment();
    currentDate.add(1, 'days');
    
    while (businessDays.length < 14) {
      if (currentDate.day() !== 0 && currentDate.day() !== 6) {
        let formattedDay = currentDate.format('dddd, MMMM D');
        let rawDay = currentDate.format('YYYY-MM-DD');
        businessDays.push({formattedDay, rawDay});
      }
      currentDate.add(1, 'days');
    }
    return businessDays;
  }

  function generateTimeSlots() {
    let slots = [];
    let startHour = 9;
    let endHour = 16.5;
    let currentTime = moment().startOf('day').add(startHour, 'hours');
    
    while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
      let formattedTime = currentTime.format('h:mm A');
      let timeValue = currentTime.format('HH:mm');
      slots.push({formattedTime, timeValue});
      currentTime.add(30, 'minutes');
    }
    return slots;
  }

  function updateTimestamp(day, time, timezone) {
    let timestampInput = document.getElementById('timestamp');
    if (!timestampInput) {
        timestampInput = document.createElement('input');
        timestampInput.type = 'hidden';
        timestampInput.id = 'timestamp';
        timestampInput.name = 'timestamp';
        document.querySelector('form').appendChild(timestampInput);
    }

    let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
    timestampInput.value = datetime.valueOf();
  }

  function populateFields() {
    const days = getNextBusinessDays();
    const times = generateTimeSlots();
    const daySelect = document.querySelector('[ms-code-booking="day"]');
    const timeSelect = document.querySelector('[ms-code-booking="time"]');
    const form = daySelect.closest('form');
    const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
    
    days.forEach(({formattedDay, rawDay}) => {
      let option = new Option(formattedDay, rawDay);
      daySelect.appendChild(option);
    });
    
    times.forEach(({formattedTime, timeValue}) => {
      let option = new Option(formattedTime, timeValue);
      timeSelect.appendChild(option);
    });

    function handleSelectChange() {
      if (daySelect.value && timeSelect.value) {
        updateTimestamp(daySelect.value, timeSelect.value, timezone);
      }
    }

    daySelect.addEventListener('change', handleSelectChange);
    timeSelect.addEventListener('change', handleSelectChange);
  }

  populateFields();
});
</script>
Ansicht Memberscript
Benutzerdefinierte Felder
UX

#Nr. 102 - Automatische Anpassung der Textarea-Höhe

Vergrößern oder verkleinern Sie die Höhe einer Textarea je nach ihrem Inhalt.


<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');

    elements.forEach(element => {
        if (element.tagName.toLowerCase() === 'textarea') {
            element.addEventListener('input', function() {
                autoResize(this);
            }, false);
        }
    });

    function autoResize(element) {
        const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
        element.style.height = 'auto';
        element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment

        if (element.scrollHeight > maxHeight) {
            element.style.height = `${maxHeight}px`;
            element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
        } else {
            element.style.height = `${element.scrollHeight}px`;
        }
    }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');

    elements.forEach(element => {
        if (element.tagName.toLowerCase() === 'textarea') {
            element.addEventListener('input', function() {
                autoResize(this);
            }, false);
        }
    });

    function autoResize(element) {
        const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
        element.style.height = 'auto';
        element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment

        if (element.scrollHeight > maxHeight) {
            element.style.height = `${maxHeight}px`;
            element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
        } else {
            element.style.height = `${element.scrollHeight}px`;
        }
    }
});
</script>
Ansicht Memberscript
Benutzerdefinierte Felder
UX

#101 - Automatische Anpassung der Eingabebreite

Erhöhen oder verringern Sie die Breite einer Eingabe je nach Inhalt.


<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const elements = document.querySelectorAll('[ms-code-resize-input="width"]');

    // Store the initial widths
    const initialWidths = new Map();
    elements.forEach(element => {
        initialWidths.set(element, element.offsetWidth);
    });

    elements.forEach(element => {
        element.addEventListener('input', function() {
            autoResizeWidth(this);
        });
    });

    function autoResizeWidth(element) {
        // Find the nearest hidden measure element
        const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure' 
                         ? element.nextElementSibling 
                         : null;
        if (!measurer) return; // Exit if no measurer is found

        measurer.textContent = element.value;

        const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
        const minWidth = initialWidths.get(element);
        const contentWidth = measurer.offsetWidth;

        if (contentWidth > minWidth && contentWidth < maxWidth) {
            element.style.width = `${contentWidth}px`;
        } else if (contentWidth >= maxWidth) {
            element.style.width = `${maxWidth}px`;
        } else {
            element.style.width = `${minWidth}px`;
        }
    }
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const elements = document.querySelectorAll('[ms-code-resize-input="width"]');

    // Store the initial widths
    const initialWidths = new Map();
    elements.forEach(element => {
        initialWidths.set(element, element.offsetWidth);
    });

    elements.forEach(element => {
        element.addEventListener('input', function() {
            autoResizeWidth(this);
        });
    });

    function autoResizeWidth(element) {
        // Find the nearest hidden measure element
        const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure' 
                         ? element.nextElementSibling 
                         : null;
        if (!measurer) return; // Exit if no measurer is found

        measurer.textContent = element.value;

        const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
        const minWidth = initialWidths.get(element);
        const contentWidth = measurer.offsetWidth;

        if (contentWidth > minWidth && contentWidth < maxWidth) {
            element.style.width = `${contentWidth}px`;
        } else if (contentWidth >= maxWidth) {
            element.style.width = `${maxWidth}px`;
        } else {
            element.style.width = `${minWidth}px`;
        }
    }
  });
</script>
Ansicht Memberscript
Benutzerdefinierte Felder
Integration

#Nr. 100 - Bild-Uploads automatisch komprimieren

Komprimieren Sie Bild-Uploads, einschließlich Profilbilder.


<!-- 💙 MEMBERSCRIPT #100 v0.1 💙 AUTO-COMPRESSED IMAGE UPLOADS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/compressorjs/1.2.1/compressor.min.js" integrity="sha512-MgYeYFj8R3S6rvZHiJ1xA9cM/VDGcT4eRRFQwGA7qDP7NHbnWKNmAm28z0LVjOuUqjD0T9JxpDMdVqsZOSHaSA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
  const compressibleInputs = document.querySelectorAll('input[type="file"][ms-code-file_compress]');

  compressibleInputs.forEach(fileInput => {
    let isCompressing = false;

    fileInput.addEventListener('change', function (event) {
      if (isCompressing) {
        isCompressing = false;
        return;
      }

      if (fileInput.files.length === 0) {
        return;
      }

      const originalFile = fileInput.files[0];
      const compressionLevel = parseFloat(fileInput.getAttribute('ms-code-file_compress'));

      new Compressor(originalFile, {
        quality: compressionLevel,
        maxWidth: 2000,
        maxHeight: 2000,
        success(compressedResult) {
          const compressedFile = new File([compressedResult], originalFile.name, {
            type: compressedResult.type,
            lastModified: Date.now(),
          });

          const dataTransfer = new DataTransfer();
          dataTransfer.items.add(compressedFile);
          fileInput.files = dataTransfer.files;

          isCompressing = true;
          fileInput.dispatchEvent(new Event('change', { bubbles: true }));
        },
        error(err) {
          console.error('Compression Error: ', err.message);
        },
      });

      event.stopPropagation();
    }, true);
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #100 v0.1 💙 AUTO-COMPRESSED IMAGE UPLOADS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/compressorjs/1.2.1/compressor.min.js" integrity="sha512-MgYeYFj8R3S6rvZHiJ1xA9cM/VDGcT4eRRFQwGA7qDP7NHbnWKNmAm28z0LVjOuUqjD0T9JxpDMdVqsZOSHaSA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
  const compressibleInputs = document.querySelectorAll('input[type="file"][ms-code-file_compress]');

  compressibleInputs.forEach(fileInput => {
    let isCompressing = false;

    fileInput.addEventListener('change', function (event) {
      if (isCompressing) {
        isCompressing = false;
        return;
      }

      if (fileInput.files.length === 0) {
        return;
      }

      const originalFile = fileInput.files[0];
      const compressionLevel = parseFloat(fileInput.getAttribute('ms-code-file_compress'));

      new Compressor(originalFile, {
        quality: compressionLevel,
        maxWidth: 2000,
        maxHeight: 2000,
        success(compressedResult) {
          const compressedFile = new File([compressedResult], originalFile.name, {
            type: compressedResult.type,
            lastModified: Date.now(),
          });

          const dataTransfer = new DataTransfer();
          dataTransfer.items.add(compressedFile);
          fileInput.files = dataTransfer.files;

          isCompressing = true;
          fileInput.dispatchEvent(new Event('change', { bubbles: true }));
        },
        error(err) {
          console.error('Compression Error: ', err.message);
        },
      });

      event.stopPropagation();
    }, true);
  });
});
</script>
Ansicht Memberscript
Benutzerdefinierte Felder

#Nr. 99 - Benutzerdefinierte Datei-Eingaben

Verwandeln Sie alles in eine Dateieingabe!


<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
  const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');

  uploadButtons.forEach(button => {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.style.display = 'none';
    fileInput.name = button.getAttribute('ms-code-file_uploader');
    fileInput.accept = button.getAttribute('ms-code-file_types');

    document.body.appendChild(fileInput);

    button.addEventListener('click', function (e) {
      e.preventDefault();
      fileInput.click();
    });

    fileInput.addEventListener('change', function () {
      const fileName = fileInput.files[0].name;
      button.querySelector('div').textContent = fileName;
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
  const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');

  uploadButtons.forEach(button => {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.style.display = 'none';
    fileInput.name = button.getAttribute('ms-code-file_uploader');
    fileInput.accept = button.getAttribute('ms-code-file_types');

    document.body.appendChild(fileInput);

    button.addEventListener('click', function (e) {
      e.preventDefault();
      fileInput.click();
    });

    fileInput.addEventListener('change', function () {
      const fileName = fileInput.files[0].name;
      button.querySelector('div').textContent = fileName;
    });
  });
});
</script>
Ansicht Memberscript
Wir konnten keine Skripte für diese Suche finden... bitte versuchen Sie es erneut.
Slack

Brauchen Sie Hilfe mit MemberScripts? Treten Sie unserer Slack-Community mit über 5.500 Mitgliedern bei! 🙌

MemberScripts sind eine Community-Ressource von Memberstack - wenn du Hilfe brauchst, damit sie mit deinem Projekt funktionieren, melde dich bitte im Memberstack 2.0 Slack an und bitte um Hilfe!

Unserem Slack beitreten
Schaukasten

Entdecken Sie echte Unternehmen, die mit Memberstack erfolgreich waren

Verlassen Sie sich nicht nur auf unser Wort - schauen Sie sich die Unternehmen aller Größen an, die sich auf Memberstack für ihre Authentifizierung und Zahlungen verlassen.

Alle Erfolgsgeschichten anzeigen
Auch Webflow verwendet Memberstack!
Mit dem Bau beginnen

Bauen Sie Ihre Träume

Memberstack ist 100% kostenlos, bis Sie bereit sind, zu starten - worauf warten Sie also noch? Erstellen Sie Ihre erste App und beginnen Sie noch heute mit der Entwicklung.