Componentes
Plantillas
Atributos
Integraciones
Comprobador del sitio
Código personalizado

MemberScripts

Una solución basada en atributos para añadir funciones a su sitio Webflow.
Simplemente copie algo de código, añada algunos atributos y listo.

Muchas gracias. Hemos recibido su envío.
¡Uy! Algo ha ido mal al enviar el formulario.
¿Necesita ayuda con MemberScripts?

Todos los clientes de Memberstack pueden solicitar asistencia en el Slack 2.0. Tenga en cuenta que no se trata de funciones oficiales y que no se puede garantizar la asistencia.

Campos personalizados
UX

#58 - Entradas del deslizador de rango de precios

Cree una entrada de rango de precios con entradas individuales para mín. y máx.


<!-- 💙 MEMBERSCRIPT #58 v0.1 💙 RANGE SLIDER INPUT -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js"></script>
<style>
    .irs {
      font-family: inherit;
    }
</style>
<script>
    $(document).ready(function() {
        var rangeSlider = $('[ms-code-input="range-slider"]');
        var priceFromInput = $('[ms-code-input="price-from"]');
        var priceToInput = $('[ms-code-input="price-to"]');
        
        // Set the default range values
        var defaultFrom = 20000;
        var defaultTo = 50000;

        rangeSlider.ionRangeSlider({
            skin: "round", // You can also try "flat", "big", "modern", "sharp", or "square". Default styles can be overridden with CSS.
            type: "double",
            grid: true,
            min: 0,
            max: 100000,
            from: defaultFrom,
            to: defaultTo,
            prefix: "$",
            onStart: function(data) {
                priceFromInput.val(data.from);
                priceToInput.val(data.to);
            },
            onChange: function(data) {
                priceFromInput.val(data.from);
                priceToInput.val(data.to);
            }
        });

        // Get the initial range values and update the inputs
        var initialRange = rangeSlider.data("ionRangeSlider");
        var initialData = initialRange.result;
        priceFromInput.val(initialData.from);
        priceToInput.val(initialData.to);

        // Update the range slider and inputs when the inputs lose focus
        priceFromInput.on("blur", function() {
            var value = $(this).val();
            var toValue = priceToInput.val();
            
            // Perform validation
            if (
                value < initialRange.options.min ||
                value > initialRange.options.max ||
                value >= toValue ||
                value > initialData.to // Check if fromValue is higher than the current toValue
            ) {
                value = defaultFrom;
            }

            rangeSlider.data("ionRangeSlider").update({
                from: value
            });
            priceFromInput.val(value);
        });

        priceToInput.on("blur", function() {
            var value = $(this).val();
            var fromValue = priceFromInput.val();
            
            // Perform validation
            if (
                value < initialRange.options.min ||
                value > initialRange.options.max ||
                value <= fromValue ||
                value < initialData.from // Check if toValue is lower than the current fromValue
            ) {
                value = defaultTo;
            }

            rangeSlider.data("ionRangeSlider").update({
                to: value
            });
            priceToInput.val(value);
        });
    });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #58 v0.1 💙 RANGE SLIDER INPUT -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js"></script>
<style>
    .irs {
      font-family: inherit;
    }
</style>
<script>
    $(document).ready(function() {
        var rangeSlider = $('[ms-code-input="range-slider"]');
        var priceFromInput = $('[ms-code-input="price-from"]');
        var priceToInput = $('[ms-code-input="price-to"]');
        
        // Set the default range values
        var defaultFrom = 20000;
        var defaultTo = 50000;

        rangeSlider.ionRangeSlider({
            skin: "round", // You can also try "flat", "big", "modern", "sharp", or "square". Default styles can be overridden with CSS.
            type: "double",
            grid: true,
            min: 0,
            max: 100000,
            from: defaultFrom,
            to: defaultTo,
            prefix: "$",
            onStart: function(data) {
                priceFromInput.val(data.from);
                priceToInput.val(data.to);
            },
            onChange: function(data) {
                priceFromInput.val(data.from);
                priceToInput.val(data.to);
            }
        });

        // Get the initial range values and update the inputs
        var initialRange = rangeSlider.data("ionRangeSlider");
        var initialData = initialRange.result;
        priceFromInput.val(initialData.from);
        priceToInput.val(initialData.to);

        // Update the range slider and inputs when the inputs lose focus
        priceFromInput.on("blur", function() {
            var value = $(this).val();
            var toValue = priceToInput.val();
            
            // Perform validation
            if (
                value < initialRange.options.min ||
                value > initialRange.options.max ||
                value >= toValue ||
                value > initialData.to // Check if fromValue is higher than the current toValue
            ) {
                value = defaultFrom;
            }

            rangeSlider.data("ionRangeSlider").update({
                from: value
            });
            priceFromInput.val(value);
        });

        priceToInput.on("blur", function() {
            var value = $(this).val();
            var fromValue = priceFromInput.val();
            
            // Perform validation
            if (
                value < initialRange.options.min ||
                value > initialRange.options.max ||
                value <= fromValue ||
                value < initialData.from // Check if toValue is lower than the current fromValue
            ) {
                value = defaultTo;
            }

            rangeSlider.data("ionRangeSlider").update({
                to: value
            });
            priceToInput.val(value);
        });
    });
</script>
Ver Memberscript
Campos personalizados

#57 - Entrada del selector de tiempo

Añada un selector de hora a su formulario y rellene previamente la hora en un campo.


<!-- 💙 MEMBERSCRIPT #57 v0.1 💙 TIME PICKER -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.css">
<script src="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.js"> </script>
<script>
$(document).ready(function() {
    var tpSpinbox = new tui.TimePicker(document.querySelector('[ms-code-timepicker="box"]'), {
        inputType: 'spinbox',
        showMeridiem: true // If you don't use AM/PM remove this line
    });

    // Setup an event handler for when the time is selected
    tpSpinbox.on('change', function() {
        // Get the selected time
        var hour = tpSpinbox.getHour();
        var minute = tpSpinbox.getMinute();

        var selectedTime = hour + ':' + (minute < 10 ? '0' : '') + minute;

        // Update the value of the input element
        document.querySelector('[ms-code-timepicker="input"]').value = selectedTime;
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #57 v0.1 💙 TIME PICKER -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.css">
<script src="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.js"> </script>
<script>
$(document).ready(function() {
    var tpSpinbox = new tui.TimePicker(document.querySelector('[ms-code-timepicker="box"]'), {
        inputType: 'spinbox',
        showMeridiem: true // If you don't use AM/PM remove this line
    });

    // Setup an event handler for when the time is selected
    tpSpinbox.on('change', function() {
        // Get the selected time
        var hour = tpSpinbox.getHour();
        var minute = tpSpinbox.getMinute();

        var selectedTime = hour + ':' + (minute < 10 ? '0' : '') + minute;

        // Update the value of the input element
        document.querySelector('[ms-code-timepicker="input"]').value = selectedTime;
    });
});
</script>
Ver Memberscript
Campos personalizados

#56 - Pares de opciones de entrada

Combine los valores de varias entradas en un solo campo.


<!-- 💙 MEMBERSCRIPT #56 v0.1 💙 INPUT OPTION PAIRS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
    var groups = {};

    // Get all inputs with the attribute ms-code-combine-inputs
    var inputs = $('input[ms-code-combine-inputs], select[ms-code-combine-inputs]');

    // For each input
    inputs.each(function() {
        // Split the attribute value at the dash
        var parts = $(this).attr('ms-code-combine-inputs').split('-');

        // If the group doesn't exist yet, create it
        if (!groups[parts[0]]) {
            groups[parts[0]] = {
                targets: [],
                values: [],
            };
        }

        // If it's a target, add it to the targets
        if (parts[1] == 'target') {
            groups[parts[0]].targets.push($(this));
        } else {
            // It's an input, add it to the values and attach a listener
            groups[parts[0]].values.push($(this));

            $(this).on('input change', function() {
                // On input or change, combine all values with a space in between
                // and set the targets' value
                var combinedValue = '';
                $.each(groups[parts[0]].values, function(index, value) {
                    combinedValue += $(this).val();
                    if (index < groups[parts[0]].values.length - 1) {
                        combinedValue += ' '; // Add a space between values
                    }
                });

                $.each(groups[parts[0]].targets, function() {
                    $(this).val(combinedValue);
                });
            });
        }
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #56 v0.1 💙 INPUT OPTION PAIRS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
    var groups = {};

    // Get all inputs with the attribute ms-code-combine-inputs
    var inputs = $('input[ms-code-combine-inputs], select[ms-code-combine-inputs]');

    // For each input
    inputs.each(function() {
        // Split the attribute value at the dash
        var parts = $(this).attr('ms-code-combine-inputs').split('-');

        // If the group doesn't exist yet, create it
        if (!groups[parts[0]]) {
            groups[parts[0]] = {
                targets: [],
                values: [],
            };
        }

        // If it's a target, add it to the targets
        if (parts[1] == 'target') {
            groups[parts[0]].targets.push($(this));
        } else {
            // It's an input, add it to the values and attach a listener
            groups[parts[0]].values.push($(this));

            $(this).on('input change', function() {
                // On input or change, combine all values with a space in between
                // and set the targets' value
                var combinedValue = '';
                $.each(groups[parts[0]].values, function(index, value) {
                    combinedValue += $(this).val();
                    if (index < groups[parts[0]].values.length - 1) {
                        combinedValue += ' '; // Add a space between values
                    }
                });

                $.each(groups[parts[0]].targets, function() {
                    $(this).val(combinedValue);
                });
            });
        }
    });
});
</script>
Ver Memberscript
Campos personalizados

#55 - Cambiar el estilo de las casillas de verificación

Cambiar los estilos de otro elemento cuando se marca una casilla de verificación.


<!-- 💙 MEMBERSCRIPT #55 v0.1 💙 UPDATE CHECKBOX PARENT STYLES -->
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
  // Get all the checkbox elements
  var checkboxes = document.querySelectorAll('[ms-code-checkbox="check"]');

  // Iterate over each checkbox element
  checkboxes.forEach(function(checkbox) {
    // Get the boolean wrap element associated with the current checkbox
    var booleanWrap = checkbox.closest('[ms-code-checkbox="wrap"]');

    // Function to update the boolean wrap class based on checkbox state
    function updateBooleanWrapClass() {
      if (checkbox.checked) {
        booleanWrap.classList.add('checked');
      } else {
        booleanWrap.classList.remove('checked');
      }
    }

    // Check the initial value of the checkbox
    updateBooleanWrapClass();

    // Add an event listener to the checkbox to handle changes
    checkbox.addEventListener('change', function() {
      updateBooleanWrapClass();
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #55 v0.1 💙 UPDATE CHECKBOX PARENT STYLES -->
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
  // Get all the checkbox elements
  var checkboxes = document.querySelectorAll('[ms-code-checkbox="check"]');

  // Iterate over each checkbox element
  checkboxes.forEach(function(checkbox) {
    // Get the boolean wrap element associated with the current checkbox
    var booleanWrap = checkbox.closest('[ms-code-checkbox="wrap"]');

    // Function to update the boolean wrap class based on checkbox state
    function updateBooleanWrapClass() {
      if (checkbox.checked) {
        booleanWrap.classList.add('checked');
      } else {
        booleanWrap.classList.remove('checked');
      }
    }

    // Check the initial value of the checkbox
    updateBooleanWrapClass();

    // Add an event listener to the checkbox to handle changes
    checkbox.addEventListener('change', function() {
      updateBooleanWrapClass();
    });
  });
});
</script>
Ver Memberscript
Campos personalizados
UX

#54 - Lógica del campo de formulario de casilla de verificación

Bloquear otros campos/elementos si una casilla de verificación no está marcada.


<!-- 💙 MEMBERSCRIPT #54 v0.1 💙 CHECKBOX FIELD FORM LOGIC -->
<style>
  .disabled {
    pointer-events: none;
    opacity: 0.5;
  }
</style>
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
  // Get all the trigger checkboxes
  var triggerCheckboxes = document.querySelectorAll('[ms-code-field-logic-trigger]');

  // Iterate over each trigger checkbox
  triggerCheckboxes.forEach(function(checkbox) {
    // Get the value of the trigger checkbox's attribute
    var triggerValue = checkbox.getAttribute('ms-code-field-logic-trigger');

    // Function to update the target elements' class based on checkbox state
    function updateTargetElementsClass() {
      // Find the associated target elements based on the attribute value
      var targetElements = document.querySelectorAll('[ms-code-field-logic-target="' + triggerValue + '"]');

      // Check the new value of the trigger checkbox
      if (!checkbox.checked) {
        // Add the "disabled" class to each target element
        targetElements.forEach(function(targetElement) {
          targetElement.classList.add('disabled');
        });
      } else {
        // Remove the "disabled" class from each target element
        targetElements.forEach(function(targetElement) {
          targetElement.classList.remove('disabled');
        });
      }
    }

    // Check the initial value of the trigger checkbox
    updateTargetElementsClass();

    // Add an event listener to the trigger checkbox to handle changes
    checkbox.addEventListener('change', function() {
      updateTargetElementsClass();
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #54 v0.1 💙 CHECKBOX FIELD FORM LOGIC -->
<style>
  .disabled {
    pointer-events: none;
    opacity: 0.5;
  }
</style>
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
  // Get all the trigger checkboxes
  var triggerCheckboxes = document.querySelectorAll('[ms-code-field-logic-trigger]');

  // Iterate over each trigger checkbox
  triggerCheckboxes.forEach(function(checkbox) {
    // Get the value of the trigger checkbox's attribute
    var triggerValue = checkbox.getAttribute('ms-code-field-logic-trigger');

    // Function to update the target elements' class based on checkbox state
    function updateTargetElementsClass() {
      // Find the associated target elements based on the attribute value
      var targetElements = document.querySelectorAll('[ms-code-field-logic-target="' + triggerValue + '"]');

      // Check the new value of the trigger checkbox
      if (!checkbox.checked) {
        // Add the "disabled" class to each target element
        targetElements.forEach(function(targetElement) {
          targetElement.classList.add('disabled');
        });
      } else {
        // Remove the "disabled" class from each target element
        targetElements.forEach(function(targetElement) {
          targetElement.classList.remove('disabled');
        });
      }
    }

    // Check the initial value of the trigger checkbox
    updateTargetElementsClass();

    // Add an event listener to the trigger checkbox to handle changes
    checkbox.addEventListener('change', function() {
      updateTargetElementsClass();
    });
  });
});
</script>
Ver Memberscript
JSON

#53 - Actualizar elementos JSON con un formulario

Permita que sus miembros cambien detalles sobre sus artículos JSON.


<!-- 💙 MEMBERSCRIPT #53 v0.1 💙 UPDATE JSON ITEMS WITH A FORM -->
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const memberstack = window.$memberstackDom;

    // Add click event listener to the document
    document.addEventListener("click", async function(event) {
      const target = event.target;

      // Check if the clicked element has ms-code-edit-item attribute
      const editItem = target.closest('[ms-code-edit-item="prompt"]');
      if (editItem) {
        // Get the item key from the closest ancestor element with ms-code-item-key attribute
        const key = editItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');

        // Retrieve the current member JSON data
        const member = await memberstack.getMemberJSON();

        // SET THE TARGET - EDIT ME
        let targetObject = member.data.projects; // Update this line with the desired target location

        if (member.data && targetObject && targetObject[key]) {
          // Get the form element with the ms-code-edit-item="form" attribute
          const form = document.querySelector('form[ms-code-edit-item="form"]');

          if (form) {
            // Loop through the form fields
            for (const field of form.elements) {
              const jsonName = field.getAttribute('ms-code-json-name');
              if (jsonName && targetObject[key].hasOwnProperty(jsonName)) {
                // Pre-fill the form field with the corresponding value from the JSON item
                field.value = targetObject[key][jsonName];
              }
            }

            // Get the modal element with the ms-code-edit-item="modal" attribute
            const modal = document.querySelector('[ms-code-edit-item="modal"]');
            if (modal) {
              // Set the display property of the modal to flex
              modal.style.display = 'flex';
            }

            // Add submit event listener to the form
            form.addEventListener("submit", async function(event) {
              event.preventDefault(); // Prevent the form from submitting normally

              // Create an object to hold the updated values
              const updatedValues = {};

              // Loop through the form fields
              for (const field of form.elements) {
                const jsonName = field.getAttribute('ms-code-json-name');
                if (jsonName) {
                  // Update the corresponding value in the updatedValues object
                  updatedValues[jsonName] = field.value;
                }
              }

              // Update the target object with the new values
              targetObject[key] = { ...targetObject[key], ...updatedValues };

              // Update the member JSON using the Memberstack SDK
              await memberstack.updateMemberJSON({
                json: member.data
              });

              // Optional: Display a success message or perform any other desired action
              console.log('Member JSON updated successfully');
            });
          } else {
            console.error('Form element not found');
          }
        } else {
          console.error(`Could not find item with key: ${key}`);
        }
      }
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #53 v0.1 💙 UPDATE JSON ITEMS WITH A FORM -->
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const memberstack = window.$memberstackDom;

    // Add click event listener to the document
    document.addEventListener("click", async function(event) {
      const target = event.target;

      // Check if the clicked element has ms-code-edit-item attribute
      const editItem = target.closest('[ms-code-edit-item="prompt"]');
      if (editItem) {
        // Get the item key from the closest ancestor element with ms-code-item-key attribute
        const key = editItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');

        // Retrieve the current member JSON data
        const member = await memberstack.getMemberJSON();

        // SET THE TARGET - EDIT ME
        let targetObject = member.data.projects; // Update this line with the desired target location

        if (member.data && targetObject && targetObject[key]) {
          // Get the form element with the ms-code-edit-item="form" attribute
          const form = document.querySelector('form[ms-code-edit-item="form"]');

          if (form) {
            // Loop through the form fields
            for (const field of form.elements) {
              const jsonName = field.getAttribute('ms-code-json-name');
              if (jsonName && targetObject[key].hasOwnProperty(jsonName)) {
                // Pre-fill the form field with the corresponding value from the JSON item
                field.value = targetObject[key][jsonName];
              }
            }

            // Get the modal element with the ms-code-edit-item="modal" attribute
            const modal = document.querySelector('[ms-code-edit-item="modal"]');
            if (modal) {
              // Set the display property of the modal to flex
              modal.style.display = 'flex';
            }

            // Add submit event listener to the form
            form.addEventListener("submit", async function(event) {
              event.preventDefault(); // Prevent the form from submitting normally

              // Create an object to hold the updated values
              const updatedValues = {};

              // Loop through the form fields
              for (const field of form.elements) {
                const jsonName = field.getAttribute('ms-code-json-name');
                if (jsonName) {
                  // Update the corresponding value in the updatedValues object
                  updatedValues[jsonName] = field.value;
                }
              }

              // Update the target object with the new values
              targetObject[key] = { ...targetObject[key], ...updatedValues };

              // Update the member JSON using the Memberstack SDK
              await memberstack.updateMemberJSON({
                json: member.data
              });

              // Optional: Display a success message or perform any other desired action
              console.log('Member JSON updated successfully');
            });
          } else {
            console.error('Form element not found');
          }
        } else {
          console.error(`Could not find item with key: ${key}`);
        }
      }
    });
  });
</script>
Ver Memberscript
UX

#52 - Retraso en la redirección de la página

Redirigir a los miembros a una nueva página con un retraso opcional.


<!-- 💙 MEMBERSCRIPT #52 v0.1 💙 DELAYED REDIRECT TO NEW PAGE -->
<script>
  const redirectToNewPage = function() {
    setTimeout(function() {
      window.location.href = "/your-page";
    }, 1000); // Delay in milliseconds
  };

  redirectToNewPage();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #52 v0.1 💙 DELAYED REDIRECT TO NEW PAGE -->
<script>
  const redirectToNewPage = function() {
    setTimeout(function() {
      window.location.href = "/your-page";
    }, 1000); // Delay in milliseconds
  };

  redirectToNewPage();
</script>
Ver Memberscript
UX

#51 - Mostrar metadatos de los miembros

Muestre los metadatos de los afiliados de forma dinámica en su sitio web.


<!-- 💙 MEMBERSCRIPT #51 v0.2 💙 DISPLAY MEMBER METADATA -->
<script>
  function replaceTextWithMetadata(metadata) {
    var els = Array.from(document.querySelectorAll('[ms-code-member-meta]'));
    els.forEach((el) => {
      const key = el.getAttribute('ms-code-member-meta');
      const value = metadata[key];
      if (value !== undefined) {
        el.innerHTML = value;
        el.value = value;
        el.src = value;
      }
    });
  }

  const memberstack = window.$memberstackDom;
  memberstack.getCurrentMember()
    .then(({ data: member }) => {
      if (member && member.metaData) {
        replaceTextWithMetadata(member.metaData);
      }
    })
    .catch((error) => {
      console.error('Error retrieving member data:', error);
    });
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #51 v0.2 💙 DISPLAY MEMBER METADATA -->
<script>
  function replaceTextWithMetadata(metadata) {
    var els = Array.from(document.querySelectorAll('[ms-code-member-meta]'));
    els.forEach((el) => {
      const key = el.getAttribute('ms-code-member-meta');
      const value = metadata[key];
      if (value !== undefined) {
        el.innerHTML = value;
        el.value = value;
        el.src = value;
      }
    });
  }

  const memberstack = window.$memberstackDom;
  memberstack.getCurrentMember()
    .then(({ data: member }) => {
      if (member && member.metaData) {
        replaceTextWithMetadata(member.metaData);
      }
    })
    .catch((error) => {
      console.error('Error retrieving member data:', error);
    });
</script>
Ver Memberscript
JSON
UX

#50 - Modo oscuro entre dispositivos

Opción de modo oscuro persistente que sigue funcionando en los diferentes dispositivos de sus miembros.

Código de la cabeza

Put this in the <head> section of your site.


<!-- 💙 MEMBERSCRIPT #50 HEAD CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script> 
  document.addEventListener('DOMContentLoaded', function() {
    const themePreference = localStorage.getItem('themePreference');
    if (themePreference === 'dark') {
      document.body.classList.add('dark');
    }
  });
</script>

Código del cuerpo

Put this in the </body> section of your site.


<!-- 💙 MEMBERSCRIPT #50 BODY CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const darkModeToggle = document.querySelector('[ms-code-dark-mode="toggle"]');
    const bodyElement = document.querySelector('.body');

    // Function to check if theme preference is saved in member JSON
    function checkThemePreference() {
      const memberstack = window.$memberstackDom;
      memberstack.getMemberJSON()
        .then(function(memberData) {
          const themePreference = memberData.data?.themePreference;
          if (themePreference === 'dark') {
            enableDarkMode();
          } else {
            disableDarkMode();
          }
        })
        .catch(function(error) {
          console.error('Error retrieving member data:', error);
        });
    }

    // Function to enable dark mode
    function enableDarkMode() {
      darkModeToggle.classList.add('dark');
      bodyElement.classList.add('dark');
      updateThemePreference('dark');
    }

    // Function to disable dark mode
    function disableDarkMode() {
      darkModeToggle.classList.remove('dark');
      bodyElement.classList.remove('dark');
      updateThemePreference('light');
    }

    // Function to update theme preference in member JSON
    function updateThemePreference(themePreference) {
      const memberstack = window.$memberstackDom;
      memberstack.getMemberJSON()
        .then(function(memberData) {
          memberData.data = memberData.data || {};
          memberData.data.themePreference = themePreference;
          memberstack.updateMemberJSON({ json: memberData.data })
            .then(function() {
              localStorage.setItem('themePreference', themePreference);
            })
            .catch(function(error) {
              console.error('Error updating member data:', error);
            });
        })
        .catch(function(error) {
          console.error('Error retrieving member data:', error);
        });
    }

    // Event listener for dark mode toggle
    darkModeToggle.addEventListener('click', function() {
      if (darkModeToggle.classList.contains('dark')) {
        disableDarkMode();
      } else {
        enableDarkMode();
      }
    });

    // Apply transition duration and timing to all elements
    const transitionDuration = '0.0s';
    const transitionTiming = 'ease';
    const elementsToTransition = [darkModeToggle, bodyElement];
    elementsToTransition.forEach(function(element) {
      element.style.transitionDuration = transitionDuration;
      element.style.transitionTimingFunction = transitionTiming;
    });

    // Check theme preference on page load
    const savedThemePreference = localStorage.getItem('themePreference');
    if (savedThemePreference === 'dark') {
      enableDarkMode();
    } else {
      disableDarkMode();
    }
    checkThemePreference();
  });
</script>
v0.1

Código de la cabeza

Put this in the <head> section of your site.


<!-- 💙 MEMBERSCRIPT #50 HEAD CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script> 
  document.addEventListener('DOMContentLoaded', function() {
    const themePreference = localStorage.getItem('themePreference');
    if (themePreference === 'dark') {
      document.body.classList.add('dark');
    }
  });
</script>

Código del cuerpo

Put this in the </body> section of your site.


<!-- 💙 MEMBERSCRIPT #50 BODY CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const darkModeToggle = document.querySelector('[ms-code-dark-mode="toggle"]');
    const bodyElement = document.querySelector('.body');

    // Function to check if theme preference is saved in member JSON
    function checkThemePreference() {
      const memberstack = window.$memberstackDom;
      memberstack.getMemberJSON()
        .then(function(memberData) {
          const themePreference = memberData.data?.themePreference;
          if (themePreference === 'dark') {
            enableDarkMode();
          } else {
            disableDarkMode();
          }
        })
        .catch(function(error) {
          console.error('Error retrieving member data:', error);
        });
    }

    // Function to enable dark mode
    function enableDarkMode() {
      darkModeToggle.classList.add('dark');
      bodyElement.classList.add('dark');
      updateThemePreference('dark');
    }

    // Function to disable dark mode
    function disableDarkMode() {
      darkModeToggle.classList.remove('dark');
      bodyElement.classList.remove('dark');
      updateThemePreference('light');
    }

    // Function to update theme preference in member JSON
    function updateThemePreference(themePreference) {
      const memberstack = window.$memberstackDom;
      memberstack.getMemberJSON()
        .then(function(memberData) {
          memberData.data = memberData.data || {};
          memberData.data.themePreference = themePreference;
          memberstack.updateMemberJSON({ json: memberData.data })
            .then(function() {
              localStorage.setItem('themePreference', themePreference);
            })
            .catch(function(error) {
              console.error('Error updating member data:', error);
            });
        })
        .catch(function(error) {
          console.error('Error retrieving member data:', error);
        });
    }

    // Event listener for dark mode toggle
    darkModeToggle.addEventListener('click', function() {
      if (darkModeToggle.classList.contains('dark')) {
        disableDarkMode();
      } else {
        enableDarkMode();
      }
    });

    // Apply transition duration and timing to all elements
    const transitionDuration = '0.0s';
    const transitionTiming = 'ease';
    const elementsToTransition = [darkModeToggle, bodyElement];
    elementsToTransition.forEach(function(element) {
      element.style.transitionDuration = transitionDuration;
      element.style.transitionTimingFunction = transitionTiming;
    });

    // Check theme preference on page load
    const savedThemePreference = localStorage.getItem('themePreference');
    if (savedThemePreference === 'dark') {
      enableDarkMode();
    } else {
      disableDarkMode();
    }
    checkThemePreference();
  });
</script>
Ver Memberscript
Campos personalizados
UX

#49 - Desactivar la primera opción en una entrada de selección

Evite que los usuarios seleccionen la opción de marcador de posición en sus entradas de selección.


<!-- 💙 MEMBERSCRIPT #49 v0.1 💙 DISABLE FIRST OPTION IN SELECT INPUT -->
<script>
    let selects = document.querySelectorAll("select[ms-code=hide-first-option]");
    selects.forEach((select) => { 
        let options = select.getElementsByTagName("option");
        options[0].hidden = true;
    });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #49 v0.1 💙 DISABLE FIRST OPTION IN SELECT INPUT -->
<script>
    let selects = document.querySelectorAll("select[ms-code=hide-first-option]");
    selects.forEach((select) => { 
        let options = select.getElementsByTagName("option");
        options[0].hidden = true;
    });
</script>
Ver Memberscript
Campos personalizados
UX

#48 - Autocompletar entradas de dirección

Rellene previamente todas las entradas de direcciones mediante la API de Google Places.

Código de la cabeza

Place this in your page <head>


<!-- 💙 MEMBERSCRIPT #48 HEAD CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&libraries=places&callback=initAutocomplete" async defer> </script>
<style>
.pac-logo::after {
display: none;
}
.pac-container {
border-radius: 5px;
border: 1px solid #ccc;
}
.pac-item {
padding: 0 10px;
}
</style>

Código del cuerpo

Place this in your page </body>


<!-- 💙 MEMBERSCRIPT #48 BODY CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script>
let autocomplete;

function initAutocomplete() {
  autocomplete = new google.maps.places.Autocomplete(
    document.querySelector('input[ms-code-input="address"]'),
    {
      componentRestrictions: { country: ['US'] },
      fields: ['address_components'],
      types: ['address']
    }
  );

  autocomplete.addListener('place_changed', function() {
    const place = autocomplete.getPlace();

    if (place) {
      const addressInput = document.querySelector('input[ms-code-input="address"]');
      const cityInput = document.querySelector('input[ms-code-input="city"]');
      const regionInput = document.querySelector('input[ms-code-input="region"]');
      const countryInput = document.querySelector('input[ms-code-input="country"]');
      const postalCodeInput = document.querySelector('input[ms-code-input="postal-code"]');

      addressInput.value = extractAddress(place);
      cityInput.value = extractCity(place);
      regionInput.value = extractRegion(place);
      countryInput.value = extractCountry(place);
      postalCodeInput.value = extractPostalCode(place);
    }
  });
}

function extractAddress(place) {
  let address = '';
  const streetNumber = extractComponent(place, 'street_number');
  const route = extractComponent(place, 'route');

  if (streetNumber) {
    address += streetNumber + ' ';
  }
  if (route) {
    address += route;
  }

  return address.trim();
}

function extractComponent(place, componentType) {
  for (const component of place.address_components) {
    if (component.types.includes(componentType)) {
      return component.long_name;
    }
  }
  return '';
}

function extractCity(place) {
  for (const component of place.address_components) {
    if (component.types.includes('locality')) {
      return component.long_name;
    }
  }
  return '';
}

function extractRegion(place) {
  for (const component of place.address_components) {
    if (component.types.includes('administrative_area_level_1')) {
      return component.long_name;
    }
  }
  return '';
}

function extractCountry(place) {
  for (const component of place.address_components) {
    if (component.types.includes('country')) {
      return component.long_name;
    }
  }
  return '';
}

function extractPostalCode(place) {
  for (const component of place.address_components) {
    if (component.types.includes('postal_code')) {
      return component.long_name;
    }
  }
  return '';
}
</script>
v0.1

Código de la cabeza

Place this in your page <head>


<!-- 💙 MEMBERSCRIPT #48 HEAD CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&libraries=places&callback=initAutocomplete" async defer> </script>
<style>
.pac-logo::after {
display: none;
}
.pac-container {
border-radius: 5px;
border: 1px solid #ccc;
}
.pac-item {
padding: 0 10px;
}
</style>

Código del cuerpo

Place this in your page </body>


<!-- 💙 MEMBERSCRIPT #48 BODY CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script>
let autocomplete;

function initAutocomplete() {
  autocomplete = new google.maps.places.Autocomplete(
    document.querySelector('input[ms-code-input="address"]'),
    {
      componentRestrictions: { country: ['US'] },
      fields: ['address_components'],
      types: ['address']
    }
  );

  autocomplete.addListener('place_changed', function() {
    const place = autocomplete.getPlace();

    if (place) {
      const addressInput = document.querySelector('input[ms-code-input="address"]');
      const cityInput = document.querySelector('input[ms-code-input="city"]');
      const regionInput = document.querySelector('input[ms-code-input="region"]');
      const countryInput = document.querySelector('input[ms-code-input="country"]');
      const postalCodeInput = document.querySelector('input[ms-code-input="postal-code"]');

      addressInput.value = extractAddress(place);
      cityInput.value = extractCity(place);
      regionInput.value = extractRegion(place);
      countryInput.value = extractCountry(place);
      postalCodeInput.value = extractPostalCode(place);
    }
  });
}

function extractAddress(place) {
  let address = '';
  const streetNumber = extractComponent(place, 'street_number');
  const route = extractComponent(place, 'route');

  if (streetNumber) {
    address += streetNumber + ' ';
  }
  if (route) {
    address += route;
  }

  return address.trim();
}

function extractComponent(place, componentType) {
  for (const component of place.address_components) {
    if (component.types.includes(componentType)) {
      return component.long_name;
    }
  }
  return '';
}

function extractCity(place) {
  for (const component of place.address_components) {
    if (component.types.includes('locality')) {
      return component.long_name;
    }
  }
  return '';
}

function extractRegion(place) {
  for (const component of place.address_components) {
    if (component.types.includes('administrative_area_level_1')) {
      return component.long_name;
    }
  }
  return '';
}

function extractCountry(place) {
  for (const component of place.address_components) {
    if (component.types.includes('country')) {
      return component.long_name;
    }
  }
  return '';
}

function extractPostalCode(place) {
  for (const component of place.address_components) {
    if (component.types.includes('postal_code')) {
      return component.long_name;
    }
  }
  return '';
}
</script>
Ver Memberscript
JSON
UX

#47 - Mostrar fecha del miembro JSON

Indique a los afiliados una fecha, por ejemplo, cuándo vence su plan.


<!-- 💙 MEMBERSCRIPT #47 v0.1 💙 DISPLAY ONE TIME DATE -->
<script>
  document.addEventListener("DOMContentLoaded", async function() {
    const memberstack = window.$memberstackDom;

    const formatDate = function(date) {
      const options = { month: 'long', day: 'numeric', year: 'numeric' };
      return new Date(date).toLocaleDateString('en-US', options);
      // Replace 'en-US' with one of these depending on your locale: en-US, en-GB, en-CA, en-AU, fr-FR, de-DE, es-ES, it-IT, ja-JP, ko-KR, pt-BR, ru-RU, zn-CH, ar-SA
    };

    const updateTextSpans = async function() {
      const member = await memberstack.getMemberJSON();

      if (!member.data || !member.data['one-time-date']) {
        // Member data or one-time date not available, do nothing
        return;
      }

      const oneTimeDate = formatDate(member.data['one-time-date']);
      const textSpans = document.querySelectorAll('[ms-code-display-text="one-time-date"]');

      textSpans.forEach(span => {
        span.textContent = oneTimeDate;
      });
    };

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

<!-- 💙 MEMBERSCRIPT #47 v0.1 💙 DISPLAY ONE TIME DATE -->
<script>
  document.addEventListener("DOMContentLoaded", async function() {
    const memberstack = window.$memberstackDom;

    const formatDate = function(date) {
      const options = { month: 'long', day: 'numeric', year: 'numeric' };
      return new Date(date).toLocaleDateString('en-US', options);
      // Replace 'en-US' with one of these depending on your locale: en-US, en-GB, en-CA, en-AU, fr-FR, de-DE, es-ES, it-IT, ja-JP, ko-KR, pt-BR, ru-RU, zn-CH, ar-SA
    };

    const updateTextSpans = async function() {
      const member = await memberstack.getMemberJSON();

      if (!member.data || !member.data['one-time-date']) {
        // Member data or one-time date not available, do nothing
        return;
      }

      const oneTimeDate = formatDate(member.data['one-time-date']);
      const textSpans = document.querySelectorAll('[ms-code-display-text="one-time-date"]');

      textSpans.forEach(span => {
        span.textContent = oneTimeDate;
      });
    };

    updateTextSpans();
  });
</script>
Ver Memberscript
Campos personalizados
UX

#46 - Confirmar contraseña

Añade una entrada de confirmación de contraseña a tus formularios de registro y restablecimiento de contraseña.


<!-- 💙 MEMBERSCRIPT #46 v0.1 💙 CONFIRM PASSWORD INPUT -->
<script>

var password = document.querySelector('[data-ms-member=password]')
  , confirm_password = document.querySelector('[ms-code-password=confirm]')

function validatePassword(){
  if(password.value != confirm_password.value) {
 		confirm_password.setCustomValidity("Passwords Don't Match");
    confirm_password.classList.add("invalid")
    confirm_password.classList.remove("valid")
  } else {
 		confirm_password.setCustomValidity('');
    confirm_password.classList.remove("invalid")
    confirm_password.classList.add("valid")
  }
}

password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #46 v0.1 💙 CONFIRM PASSWORD INPUT -->
<script>

var password = document.querySelector('[data-ms-member=password]')
  , confirm_password = document.querySelector('[ms-code-password=confirm]')

function validatePassword(){
  if(password.value != confirm_password.value) {
 		confirm_password.setCustomValidity("Passwords Don't Match");
    confirm_password.classList.add("invalid")
    confirm_password.classList.remove("valid")
  } else {
 		confirm_password.setCustomValidity('');
    confirm_password.classList.remove("invalid")
    confirm_password.classList.add("valid")
  }
}

password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>
Ver Memberscript
Campos personalizados
UX

#45 - Mostrar/Ocultar contraseña

Añade un botón de mostrar/ocultar contraseña a cualquier formulario con una entrada de contraseña.


<!-- 💙 MEMBERSCRIPT #45 v0.2 💙 SHOW AND HIDE PASSWORD -->
<script>
  document.querySelectorAll("[ms-code-password='transform']").forEach(function(button) {
    button.addEventListener("click", transform);
  });

  var isPassword = true;

  function transform() {
    var passwordInputs = document.querySelectorAll("[data-ms-member='password'], [data-ms-member='new-password'], [data-ms-member='current-password']");

    passwordInputs.forEach(function(myInput) {
      var inputType = myInput.getAttribute("type");

      if (isPassword) {
        myInput.setAttribute("type", "text");
      } else {
        myInput.setAttribute("type", "password");
      }
    });

    isPassword = !isPassword;
  }
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #45 v0.2 💙 SHOW AND HIDE PASSWORD -->
<script>
  document.querySelectorAll("[ms-code-password='transform']").forEach(function(button) {
    button.addEventListener("click", transform);
  });

  var isPassword = true;

  function transform() {
    var passwordInputs = document.querySelectorAll("[data-ms-member='password'], [data-ms-member='new-password'], [data-ms-member='current-password']");

    passwordInputs.forEach(function(myInput) {
      var inputType = myInput.getAttribute("type");

      if (isPassword) {
        myInput.setAttribute("type", "text");
      } else {
        myInput.setAttribute("type", "password");
      }
    });

    isPassword = !isPassword;
  }
</script>
Ver Memberscript
Visibilidad condicional

#44 - Mostrar elemento si atributo coincide con ID de miembro

Mostrar elementos condicionalmente si tienen un atributo que coincida con el ID de los miembros.


<!-- 💙 MEMBERSCRIPT #44 v0.1 💙 SHOW ELEMENT IF ATTRIBUTE MATCHES MEMBER ID -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  if (localStorage.getItem("_ms-mem")) {
    const memberData = JSON.parse(localStorage.getItem("_ms-mem"));
    const memberId = memberData.id;

    const elements = document.querySelectorAll("[ms-code-member-id='" + memberId + "']");
    elements.forEach(element => {
      element.style.display = "block";
    });
  }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #44 v0.1 💙 SHOW ELEMENT IF ATTRIBUTE MATCHES MEMBER ID -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  if (localStorage.getItem("_ms-mem")) {
    const memberData = JSON.parse(localStorage.getItem("_ms-mem"));
    const memberId = memberData.id;

    const elements = document.querySelectorAll("[ms-code-member-id='" + memberId + "']");
    elements.forEach(element => {
      element.style.display = "block";
    });
  }
});
</script>
Ver Memberscript
Modales

#43 - Bloquear el desplazamiento cuando el modal está abierto

Evitar que la página se desplace cuando alguien abre un modal.


<!-- 💙 MEMBERSCRIPT #43 v0.1 💙 BLOCK SCROLLING WHEN MODAL IS OPEN -->
<style>
.no-scroll {
  overflow: hidden;
}
</style>
<script>
function isDesktopViewport() {
  return window.innerWidth >= 900; // Adjust the breakpoint width as needed
}

const codeBlocks = document.querySelectorAll('[ms-code-block-scroll]');

function handleScrollBlock(event) {
  if (isDesktopViewport()) {
    document.body.classList.add('no-scroll');
  }
}

function handleScrollUnblock(event) {
  if (isDesktopViewport()) {
    document.body.classList.remove('no-scroll');
  }
}

codeBlocks.forEach(codeBlock => {
  codeBlock.addEventListener('mouseenter', handleScrollBlock);
  codeBlock.addEventListener('mouseleave', handleScrollUnblock);
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #43 v0.1 💙 BLOCK SCROLLING WHEN MODAL IS OPEN -->
<style>
.no-scroll {
  overflow: hidden;
}
</style>
<script>
function isDesktopViewport() {
  return window.innerWidth >= 900; // Adjust the breakpoint width as needed
}

const codeBlocks = document.querySelectorAll('[ms-code-block-scroll]');

function handleScrollBlock(event) {
  if (isDesktopViewport()) {
    document.body.classList.add('no-scroll');
  }
}

function handleScrollUnblock(event) {
  if (isDesktopViewport()) {
    document.body.classList.remove('no-scroll');
  }
}

codeBlocks.forEach(codeBlock => {
  codeBlock.addEventListener('mouseenter', handleScrollBlock);
  codeBlock.addEventListener('mouseleave', handleScrollUnblock);
});
</script>
Ver Memberscript
Campos personalizados

#42 - Campo de formulario del editor de imágenes

Permite que los usuarios suban y editen fotos y, a continuación, envíalas a Google Drive.

Código de la cabeza

Place this in your page <head>


<!-- 💙 MEMBERSCRIPT #42 HEAD CODE v0.2 💙 FILE EDITOR FEATURE -->
<link rel="stylesheet" href="https://unpkg.com/filepond@^4/dist/filepond.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" />

Código del cuerpo

Place this in your page </body>


<!-- 💙 MEMBERSCRIPT #42 BODY CODE v0.2 💙 FILE EDITOR FEATURE -->
<script> src="https://unpkg.com/filepond-plugin-file-encode/dist/filepond-plugin-file-encode.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.js"> </script>
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script> src="https://scaleflex.cloudimg.io/v7/plugins/filerobot-image-editor/latest/filerobot-image-editor.min.js"> </script>
<style>
  .dXhZSB {
  background-color: #2962ff;
  }
  .FIE_root * {
  font-family: inherit !important;
  }
  .SfxModal-Wrapper * {
  font-family: inherit !important;
  }
  .jpHEiD {
  font-family: inherit !important;
  }
  #editor_container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 999;
  }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
  // Register the plugins
  FilePond.registerPlugin(FilePondPluginImagePreview);
  FilePond.registerPlugin(FilePondPluginImageEdit);

  const inputElement = document.querySelector('input[type="file"]');
  const pond = FilePond.create(inputElement, {
    credits: false,
    name: 'fileToUpload',
    storeAsFile: true,
    imageEditEditor: {
      open: (file, instructions) => {
        console.log('Open editor', file, instructions);
        openFilerobotImageEditor(file, instructions);
      },
      onconfirm: (output) => {
        console.log('Confirm editor', output);
        handleImageEditConfirm(output);
      },
      oncancel: () => {
        console.log('Cancel editor');
        handleImageEditCancel();
      },
      onclose: () => {
        console.log('Close editor');
        handleImageEditClose();
      }
    }
  });

  function openFilerobotImageEditor(file, instructions) {
    const imageURL = URL.createObjectURL(file);
    const config = {
      source: imageURL,
      onSave: (updatedImage) => {
        confirmCallback(updatedImage);
      },
      annotationsCommon: {
        fill: '#ff0000'
      },
      Text: {
        text: 'Add your text here',
        font: 'inherit'
      }, // Set font to inherit from the page body
      Rotate: {
        angle: instructions.rotation,
        componentType: 'slider'
      },
      tabsIds: [
        'Adjust',
        'Annotate',
        'Watermark'
      ],
      defaultTabId: 'Annotate',
      defaultToolId: 'Text'
    };

    const editorContainer = document.createElement('div');
    editorContainer.id = 'editor_container';
    document.body.appendChild(editorContainer);

    const filerobotImageEditor = new window.FilerobotImageEditor(editorContainer, config);

    const confirmCallback = (output) => {
      console.log('Confirmed:', output);

      const dataURL = output.imageBase64;
      const file = dataURLToFile(dataURL, output.name);

      // Add the file to FilePond
      pond.addFiles([file]);

      document.body.removeChild(editorContainer); // Remove the editor container
    };

    function dataURLToFile(dataURL, fileName) {
      const arr = dataURL.split(',');
      const mime = arr[0].match(/:(.*?);/)[1];
      const fileExtension = mime.split('/')[1];
      const updatedFileName = fileName + '.' + fileExtension;
      const bstr = atob(arr[1]);
      const n = bstr.length;
      const u8arr = new Uint8Array(n);
      for (let i = 0; i < n; i++) {
        u8arr[i] = bstr.charCodeAt(i);
      }
      return new File([u8arr], updatedFileName, { type: mime });
    }

    const cancelCallback = () => {
      console.log('Canceled');
      document.body.removeChild(editorContainer); // Remove the editor container
    };

    const closeButton = document.createElement('button');
    closeButton.textContent = 'Close';
    closeButton.addEventListener('click', () => {
      filerobotImageEditor.onClose();
    });

    const buttonContainer = document.createElement('div');
    buttonContainer.appendChild(closeButton);

    editorContainer.appendChild(buttonContainer);

    filerobotImageEditor.render({
      onClose: (closingReason) => {
        console.log('Closing reason', closingReason);
        filerobotImageEditor.terminate();
      },
    });
  }

  function handleImageEditConfirm(output) {
    console.log('Image edit confirmed:', output);
    // Handle the confirmed output here
  }

  function handleImageEditCancel() {
    console.log('Image edit canceled');
    // Handle the canceled edit here
  }

  function handleImageEditClose() {
    console.log('Image editor closed');
    // Handle the editor close here
  }
});
</script>

v0.2

Código de la cabeza

Place this in your page <head>


<!-- 💙 MEMBERSCRIPT #42 HEAD CODE v0.2 💙 FILE EDITOR FEATURE -->
<link rel="stylesheet" href="https://unpkg.com/filepond@^4/dist/filepond.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" />

Código del cuerpo

Place this in your page </body>


<!-- 💙 MEMBERSCRIPT #42 BODY CODE v0.2 💙 FILE EDITOR FEATURE -->
<script> src="https://unpkg.com/filepond-plugin-file-encode/dist/filepond-plugin-file-encode.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.js"> </script>
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script> src="https://scaleflex.cloudimg.io/v7/plugins/filerobot-image-editor/latest/filerobot-image-editor.min.js"> </script>
<style>
  .dXhZSB {
  background-color: #2962ff;
  }
  .FIE_root * {
  font-family: inherit !important;
  }
  .SfxModal-Wrapper * {
  font-family: inherit !important;
  }
  .jpHEiD {
  font-family: inherit !important;
  }
  #editor_container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 999;
  }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
  // Register the plugins
  FilePond.registerPlugin(FilePondPluginImagePreview);
  FilePond.registerPlugin(FilePondPluginImageEdit);

  const inputElement = document.querySelector('input[type="file"]');
  const pond = FilePond.create(inputElement, {
    credits: false,
    name: 'fileToUpload',
    storeAsFile: true,
    imageEditEditor: {
      open: (file, instructions) => {
        console.log('Open editor', file, instructions);
        openFilerobotImageEditor(file, instructions);
      },
      onconfirm: (output) => {
        console.log('Confirm editor', output);
        handleImageEditConfirm(output);
      },
      oncancel: () => {
        console.log('Cancel editor');
        handleImageEditCancel();
      },
      onclose: () => {
        console.log('Close editor');
        handleImageEditClose();
      }
    }
  });

  function openFilerobotImageEditor(file, instructions) {
    const imageURL = URL.createObjectURL(file);
    const config = {
      source: imageURL,
      onSave: (updatedImage) => {
        confirmCallback(updatedImage);
      },
      annotationsCommon: {
        fill: '#ff0000'
      },
      Text: {
        text: 'Add your text here',
        font: 'inherit'
      }, // Set font to inherit from the page body
      Rotate: {
        angle: instructions.rotation,
        componentType: 'slider'
      },
      tabsIds: [
        'Adjust',
        'Annotate',
        'Watermark'
      ],
      defaultTabId: 'Annotate',
      defaultToolId: 'Text'
    };

    const editorContainer = document.createElement('div');
    editorContainer.id = 'editor_container';
    document.body.appendChild(editorContainer);

    const filerobotImageEditor = new window.FilerobotImageEditor(editorContainer, config);

    const confirmCallback = (output) => {
      console.log('Confirmed:', output);

      const dataURL = output.imageBase64;
      const file = dataURLToFile(dataURL, output.name);

      // Add the file to FilePond
      pond.addFiles([file]);

      document.body.removeChild(editorContainer); // Remove the editor container
    };

    function dataURLToFile(dataURL, fileName) {
      const arr = dataURL.split(',');
      const mime = arr[0].match(/:(.*?);/)[1];
      const fileExtension = mime.split('/')[1];
      const updatedFileName = fileName + '.' + fileExtension;
      const bstr = atob(arr[1]);
      const n = bstr.length;
      const u8arr = new Uint8Array(n);
      for (let i = 0; i < n; i++) {
        u8arr[i] = bstr.charCodeAt(i);
      }
      return new File([u8arr], updatedFileName, { type: mime });
    }

    const cancelCallback = () => {
      console.log('Canceled');
      document.body.removeChild(editorContainer); // Remove the editor container
    };

    const closeButton = document.createElement('button');
    closeButton.textContent = 'Close';
    closeButton.addEventListener('click', () => {
      filerobotImageEditor.onClose();
    });

    const buttonContainer = document.createElement('div');
    buttonContainer.appendChild(closeButton);

    editorContainer.appendChild(buttonContainer);

    filerobotImageEditor.render({
      onClose: (closingReason) => {
        console.log('Closing reason', closingReason);
        filerobotImageEditor.terminate();
      },
    });
  }

  function handleImageEditConfirm(output) {
    console.log('Image edit confirmed:', output);
    // Handle the confirmed output here
  }

  function handleImageEditCancel() {
    console.log('Image edit canceled');
    // Handle the canceled edit here
  }

  function handleImageEditClose() {
    console.log('Image editor closed');
    // Handle the editor close here
  }
});
</script>

Ver Memberscript
Campos personalizados

#41 - Entradas de números de teléfono perfectas

Entradas de números de teléfono internacionales, como debe ser.

Con búsqueda de IP

Utilice esta opción si desea que el país IP de los usuarios se rellene automáticamente. IMPORTANTE: No utilice esta opción con formularios de perfil o se comportará de forma errática.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITH IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      $.get("https://ipinfo.io", function(response) {
        var countryCode = response.country;
        iti.setCountry(countryCode);
      }, "jsonp");

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>

Sin búsqueda de IP

Utilícelo en formularios de perfil y/o si no desea que se rellene automáticamente en función de la IP del usuario.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITHOUT IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>
v0.2

Con búsqueda de IP

Utilice esta opción si desea que el país IP de los usuarios se rellene automáticamente. IMPORTANTE: No utilice esta opción con formularios de perfil o se comportará de forma errática.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITH IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      $.get("https://ipinfo.io", function(response) {
        var countryCode = response.country;
        iti.setCountry(countryCode);
      }, "jsonp");

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>

Sin búsqueda de IP

Utilícelo en formularios de perfil y/o si no desea que se rellene automáticamente en función de la IP del usuario.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITHOUT IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>
Ver Memberscript
Campos personalizados
UX

#40 - Cargador de archivos de arrastrar y soltar

Añada fácilmente una función de carga de archivos mediante arrastrar y soltar a su sitio Webflow.

Importante

Si estás usando MemberScript #38 , ¡asegúrate de poner este script DESPUÉS!


<!-- 💙 MEMBERSCRIPT #40 v0.1 💙 DRAG AND DROP FILE UPLOADER -->
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const inputElement = document.querySelector('input[type="file"]');
    const pond = FilePond.create(inputElement, {
      credits: false,
      name: 'fileToUpload',
      storeAsFile: true
      // for more property options, go to https://pqina.nl/filepond/docs/api/instance/properties/
    });
  });
</script>
v0.1

Importante

Si estás usando MemberScript #38 , ¡asegúrate de poner este script DESPUÉS!


<!-- 💙 MEMBERSCRIPT #40 v0.1 💙 DRAG AND DROP FILE UPLOADER -->
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const inputElement = document.querySelector('input[type="file"]');
    const pond = FilePond.create(inputElement, {
      credits: false,
      name: 'fileToUpload',
      storeAsFile: true
      // for more property options, go to https://pqina.nl/filepond/docs/api/instance/properties/
    });
  });
</script>
Ver Memberscript
Campos personalizados
UX

#39 - Seleccionar mejor los campos

Añade búsquedas y una mejor interfaz de usuario para seleccionar y multiseleccionar campos.

Código de la cabeza

Put this in the <head> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 HEAD CODE 💙 BETTER SELECT FIELDS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />

Código del cuerpo

Put this in the </body> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 BODY CODE 💙 BETTER SELECT FIELDS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"> </script>
<script>
$(document).ready(function() {
    $('[ms-code-custom-select="select-with-search"]').select2();
});
</script>
v0.1

Código de la cabeza

Put this in the <head> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 HEAD CODE 💙 BETTER SELECT FIELDS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />

Código del cuerpo

Put this in the </body> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 BODY CODE 💙 BETTER SELECT FIELDS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"> </script>
<script>
$(document).ready(function() {
    $('[ms-code-custom-select="select-with-search"]').select2();
});
</script>
Ver Memberscript
No hemos podido encontrar ningún script para esa búsqueda... por favor, inténtalo de nuevo.
Slack

¿Necesitas ayuda con MemberScripts? ¡Únete a nuestra comunidad Slack de más de 5.500 miembros! 🙌

Los MemberScripts son un recurso comunitario de Memberstack - si necesitas ayuda para que funcionen con tu proyecto, ¡únete al Slack de Memberstack 2.0 y pide ayuda!

Únete a nuestro Slack
Escaparate

Explore empresas reales que han tenido éxito con Memberstack

No se fíe sólo de nuestra palabra: eche un vistazo a las empresas de todos los tamaños que confían en Memberstack para su autenticación y sus pagos.

Ver todas las historias de éxito
Incluso Webflow utiliza Memberstack.
Empezar a construir

Empieza a construir tus sueños

Memberstack es 100% gratis hasta que estés listo para lanzarla - así que, ¿a qué estás esperando? Crea tu primera aplicación y empieza a construir hoy mismo.