Problem with code

Topic summary

A Shopify store developer is troubleshooting a Liquid-based flag customizer feature that allows customers to combine two country flags into one image. The tool works inconsistently—some customers can see and interact with the flags while others cannot.

Initial Problem:

  • Code includes dropdown selectors for two countries, dual canvas elements for flag rendering, and zoom/positioning controls
  • Intermittent failures prevent flag images from displaying for certain users

Proposed Solutions:
Two community members provided revised code focusing on:

  • Ensuring flags fully load before initializing dropdowns and canvas logic
  • Adding error handling and loading state indicators
  • Fixing race conditions and timing issues
  • Improving mobile support and zoom limits

Current Status:
The discussion remains unresolved. After implementing the suggested fixes, a new issue emerged where no flags can be selected. The original poster clarified the data source is flags.js (not flags.json as assumed in the solutions). One helper has requested access to the full code via private message to provide a more targeted fix.

Summarized with AI on October 29. AI used: claude-sonnet-4-5-20250929.
<div class="container">
  <div class="flag-customizer">
   <h1 style="color: black; font-weight: bold">Anpassa din flagga</h1>
   <h5 style="color: black; font-weight: bold">Funktionen tillåter dig både att zooma och justera placeringen på länderna</h5>
   <h6 style="color: black; font-weight: bold">(Obs, det kan ibland ta en stund innan länderna visas när du väljer dem. Starta om sidan om tekniska fel uppstår)</h6>

<label for="flag1" style="color: black; font-weight: bold">Land 1:</label>
<select id="flag1" style="color: black; font-weight: bold">
  <option>Välj</option>
</select>

<br>

<label for="flag2" style="color: black; font-weight: bold">Land 2:</label>
<select id="flag2" style="color: black; font-weight: bold">
  <option>Välj</option>
</select>
  
  <div class="flag-container">
    <canvas id="flagCanvas1" width="5906" height="3544"></canvas>
    <canvas id="flagCanvas2" width="5906" height="3544"></canvas>
  </div>
  
  <button id="uploadFlagBtn">Spara</button>

</div>

<style>

  #flagCanvas1, #flagCanvas2 {
    position: absolute;
    width: 100%;
    height: 100%;
  }

  #flagCanvas1 {
    clip-path: polygon(100% 0, 0 0, 0 100%);
  }

  #flagCanvas2 {
    clip-path: polygon(0 100%, 100% 0, 100% 100%);
  }
</style>

<script>
  // Funktion för att skapa alternativ
  function populateSelect(selectId) {
    const selectElement = document.getElementById(selectId);

    countries.forEach(country => {
      const option = document.createElement("option");
      option.value = country.value;
      option.textContent = country.label;
      selectElement.appendChild(option);
    });
  }

  // När sidan har laddats
  document.addEventListener("DOMContentLoaded", function () {
    populateSelect("flag1"); // Fyll första dropdown
    populateSelect("flag2"); // Fyll andra dropdown
  });
</script>

<script>
  fetch('{{ "flags.json" | asset_url }}?v={{ settings.theme_version }}')
    .then(response => response.json())
    .then(flags => {
      // Exempel: använd flaggorna
      console.log(flags);

      // Ladda flaggor i select-element
      const flag1Select = document.getElementById("flag1");
      const flag2Select = document.getElementById("flag2");

      for (const [key, url] of Object.entries(flags)) {
        const option1 = document.createElement("option");
        option1.value = key;
        option1.textContent = key.replace(/_/g, " ");
        flag1Select.appendChild(option1);

        const option2 = document.createElement("option");
        option2.value = key;
        option2.textContent = key.replace(/_/g, " ");
        flag2Select.appendChild(option2);
      }
    })
    .catch(error => console.error("Kunde inte läsa flaggdata:", error));
</script>

<script>

  document.addEventListener("DOMContentLoaded", function() {
  const canvas1 = document.getElementById("flagCanvas1");
  const ctx1 = canvas1.getContext("2d");
  const canvas2 = document.getElementById("flagCanvas2");
  const ctx2 = canvas2.getContext("2d");

  const flag1Select = document.getElementById("flag1");
  const flag2Select = document.getElementById("flag2");

  let flag1Image = new Image();
  flag1Image.crossOrigin = "anonymous";
  let flag2Image = new Image();
  flag2Image.crossOrigin = "anonymous";

  let flag1Zoom = 3, flag1OffsetX = -5906, flag1OffsetY = -3544;
  let flag2Zoom = 3, flag2OffsetX = -5906, flag2OffsetY = -3544;

  // Load selected flags
  flag1Select.addEventListener("change", function() {
    flag1Image.src=flags[flag1Select.value];
  });
  flag2Select.addEventListener("change", function() {
    flag2Image.src=flags[flag2Select.value];
  });

  // Set initial flags
  flag1Image.src=flags[flag1Select.value];
  flag2Image.src=flags[flag2Select.value];

  function drawFlag1() {
    ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
    ctx1.save();
    ctx1.translate(flag1OffsetX, flag1OffsetY);
    ctx1.scale(flag1Zoom, flag1Zoom);
    ctx1.drawImage(flag1Image, 0, 0, canvas1.width, canvas1.height);
    ctx1.restore();
  }

  function drawFlag2() {
    ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
    ctx2.save();
    ctx2.translate(flag2OffsetX, flag2OffsetY);
    ctx2.scale(flag2Zoom, flag2Zoom);
    ctx2.drawImage(flag2Image, 0, 0, canvas2.width, canvas2.height);
    ctx2.restore();
  }

  flag1Image.onload = drawFlag1;
  flag2Image.onload = drawFlag2;

  // Check if flags are selected
  function isFlagSelected() {
    return (
      flag1Select.value !== "Välj" &&
      flag2Select.value !== "Välj" &&
      flag1Select.value &&
      flag2Select.value
    );
  }

  // Zoom and move functionality for desktop
  canvas1.addEventListener("wheel", function(e) {
    e.preventDefault();
    flag1Zoom += e.deltaY * -0.005;
    drawFlag1();
  });
  canvas2.addEventListener("wheel", function(e) {
    e.preventDefault();
    flag2Zoom += e.deltaY * -0.005;
    drawFlag2();
  });

  // Drag and zoom functionality for canvas1
  let isDragging1 = false, startX1, startY1;
  canvas1.addEventListener("mousedown", function(e) {
    if (!isFlagSelected()) return; // Disable movement if no flags are selected
    isDragging1 = true;
    startX1 = e.offsetX*5 - flag1OffsetX;
    startY1 = e.offsetY*5 - flag1OffsetY;
  });
  canvas1.addEventListener("mousemove", function(e) {
    if (isDragging1) {
      flag1OffsetX = e.offsetX*5 - startX1;
      flag1OffsetY = e.offsetY*5 - startY1;
      drawFlag1();
    }
  });
  canvas1.addEventListener("mouseup", function() { isDragging1 = false; });
  canvas1.addEventListener("mouseleave", function() { isDragging1 = false; });

  // Touch events for drag and zoom on canvas1
  let initialTouchDistance1 = 0;
  canvas1.addEventListener("touchstart", function(e) {
    if (!isFlagSelected()) return; // Disable movement if no flags are selected
    if (e.touches.length === 1) { // Single finger touch for dragging
      isDragging1 = true;
      startX1 = e.touches[0].clientX*5 - flag1OffsetX;
      startY1 = e.touches[0].clientY*5 - flag1OffsetY;
    } else if (e.touches.length === 2) { // Two finger touch for zooming
      isDragging1 = false;
      initialTouchDistance1 = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
    }
  });
  canvas1.addEventListener("touchmove", function(e) {
    if (isDragging1 && e.touches.length === 1) {
      flag1OffsetX = e.touches[0].clientX*5 - startX1;
      flag1OffsetY = e.touches[0].clientY*5 - startY1;
      drawFlag1();
    } else if (e.touches.length === 2) {
      const currentTouchDistance1 = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      const zoomFactor = 0.2;
      flag1Zoom *= 1 + (zoomFactor * (currentTouchDistance1 / initialTouchDistance1 - 1));
      initialTouchDistance1 = currentTouchDistance1;
      drawFlag1();
    }
  });
  canvas1.addEventListener("touchend", function() { isDragging1 = false; });

  // Drag and zoom functionality for canvas2
  let isDragging2 = false, startX2, startY2;
  canvas2.addEventListener("mousedown", function(e) {
    if (!isFlagSelected()) return; // Disable movement if no flags are selected
    isDragging2 = true;
    startX2 = e.offsetX*5 - flag2OffsetX;
    startY2 = e.offsetY*5 - flag2OffsetY;
  });
  canvas2.addEventListener("mousemove", function(e) {
    if (isDragging2) {
      flag2OffsetX = e.offsetX*5 - startX2;
      flag2OffsetY = e.offsetY*5 - startY2;
      drawFlag2();
    }
  });
  canvas2.addEventListener("mouseup", function() { isDragging2 = false; });
  canvas2.addEventListener("mouseleave", function() { isDragging2 = false; });

  // Touch events for drag and zoom on canvas2
  let initialTouchDistance2 = 0;
  canvas2.addEventListener("touchstart", function(e) {
    if (!isFlagSelected()) return; // Disable movement if no flags are selected
    if (e.touches.length === 1) {
      isDragging2 = true;
      startX2 = e.touches[0].clientX*5 - flag2OffsetX;
      startY2 = e.touches[0].clientY*5 - flag2OffsetY;
    } else if (e.touches.length === 2) {
      isDragging2 = false;
      initialTouchDistance2 = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
    }
  });
  canvas2.addEventListener("touchmove", function(e) {
    if (isDragging2 && e.touches.length === 1) {
      flag2OffsetX = e.touches[0].clientX*5 - startX2;
      flag2OffsetY = e.touches[0].clientY*5 - startY2;
      drawFlag2();
    } else if (e.touches.length === 2) {
      const currentTouchDistance2 = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      const zoomFactor = 0.2;
      flag2Zoom *= 1 + (zoomFactor * (currentTouchDistance2 / initialTouchDistance2 - 1));
      initialTouchDistance2 = currentTouchDistance2;
      drawFlag2();
    }
  });
  canvas2.addEventListener("touchend", function() { isDragging2 = false; });

  // Prevent page movement during touch interactions
  document.addEventListener("touchmove", function(e) {
    if (e.target.closest(".flag-container")) {
      e.preventDefault();
    }
  }, { passive: false });

  // Funktionen som sparar flagganpassningen
  document.getElementById("uploadFlagBtn").addEventListener("click", function () {

  const flag1Value = flag1Select.value;
  const flag2Value = flag2Select.value;

  if (!flag1Value || flag1Value === "Välj" || !flag2Value || flag2Value === "Välj") {
    alert("Du måste välja två flaggor innan du sparar!"); // Visa ett meddelande
    return; // Stoppa funktionen
  }
    
  const combinedCanvas = document.createElement("canvas");
  combinedCanvas.width = 5906; // Match canvas dimensions
  combinedCanvas.height = 3544;
  const combinedCtx = combinedCanvas.getContext("2d");

  // Combine the two canvases
  combinedCtx.save();
  combinedCtx.beginPath();
  combinedCtx.moveTo(0, combinedCanvas.height);
  combinedCtx.lineTo(combinedCanvas.width, 0);
  combinedCtx.lineTo(0, 0);
  combinedCtx.closePath();
  combinedCtx.clip();
  combinedCtx.drawImage(document.getElementById("flagCanvas1"), 0, 0, combinedCanvas.width, combinedCanvas.height);
  combinedCtx.restore();

  combinedCtx.save();
  combinedCtx.beginPath();
  combinedCtx.moveTo(combinedCanvas.width, 0);
  combinedCtx.lineTo(0, combinedCanvas.height);
  combinedCtx.lineTo(combinedCanvas.width, combinedCanvas.height);
  combinedCtx.closePath();
  combinedCtx.clip();
  combinedCtx.drawImage(document.getElementById("flagCanvas2"), 0, 0, combinedCanvas.width, combinedCanvas.height);
  combinedCtx.restore();

  // Convert the canvas to a Blob
  combinedCanvas.toBlob(function (blob) {
    // Prepare the file for upload
    const file = new File([blob], "customized_flag.png", { type: "image/png" });

    // Find the Upload Lift input element (FilePond)
    const uploadInput = document.querySelector('input[type="file"].filepond--browser');

    if (uploadInput) {
      // Use FilePond's File API to add the file programmatically
      const dataTransfer = new DataTransfer();
      dataTransfer.items.add(file);
      uploadInput.files = dataTransfer.files;

      // Trigger the change event to start the upload process
      const event = new Event("change", { bubbles: true });
      uploadInput.dispatchEvent(event);

        alert("Din flagga har sparats! Scrolla nedåt för att slutföra din beställning.");
    } else {
      alert("Kunde inte hitta uppladdningsfältet. Kontrollera din konfiguration.");
    }
  }, "image/png");
});

});

</script>

What is wrong with my code, the function is to allow the customer to choose 2 countries and make 1 flag out of it. For some customers the function works but for others it doesn’t work, they don’t get the flag images visible and they can’t do anything. How do I fix this problem, please help.

@Amel05 HI,

Here’s a cleaned-up and fixed version of your code that:

  1. Ensures flags are fully loaded before any dropdowns or canvas logic runs.
  2. Avoids timing issues by initializing everything after the fetch.
  3. Adds fallback error handling and loading text visibility.

Laddar flaggor...









Spara flagga

Please replace the previous code with this one.


  

    # Anpassa din flagga
    ##### Funktionen tillåter dig både att zooma och justera placeringen på länderna
    ###### (Obs, det kan ibland ta en stund innan länderna visas när du väljer dem. Starta om sidan om tekniska fel uppstår)

    
    

    

    
    
  
    

      
      
      

        Laddar flagga...
      

    

  
    
  

What I did here:

  • I make sure that it initialized in a better way.
  • I improve the srror handling.
  • I fixed the race condition.
  • I enhanced the Mobile Support.
  • I fix the zoom limit.

By applying these changes I make sur eit’s work well.

Let me know if it work for you. If not I will provide you anothe rsolution code.

Thanks

Now I can’t choose any flag, but it isn’t flags.json, it is flags.js.

Could you please share the collab code in the p/m so that I can take a look and fix it.

Thanks