bharatb-futurism

bharatb-futurism

LiveView re-render is causing DOM elements to reset

I am dealing with an issue where a LiveView re-render is causing DOM elements to reset, which includes clearing a div that has a base64 encoded src attribute. When working with Phoenix LiveView, once we enter the prompt, LiveView updates the DOM based on the server state, and any client-side changes that are not reflected in the server state, the custom image is not being selected and gets disappear.

  def render(assigns) do
    ~H"""
    <img
      id="hiddenImage"
      style="display:none;"
      alt="Hidden Image"
      width="250px"
      height="250px"
      style="border-radius:2px;border:1px solid grey;"
      src={@image_base64}
    />
    <div id="prompt-editor">
      <div class="mx-auto max-w-6xl grid grid-cols-1 md:grid-cols-3 gap-x-16 mt-6 mb-12">
        <div class="col-span-2 relative mx-4">
          <a
            href="/recipes/all"
            class="absolute -top-8 inline-block h-5 justify-center items-center gap-2 inline-flex  cursor-pointer"
          >
            <.icon name="hero-arrow-left" class="w-5 h-5" />
            <span class="text-slate-600 text-sm font-semibold leading-tight">
              Back to Recipes
            </span>
          </a>
          <.h1 class="text-5xl font-bold"><%= @recipe.name %></.h1>
          <div class="flex justify-between items-center">
            <.support class="text-xl">
              <%= gettext("Fill in the blanks of your recipe to generate art") %>
            </.support>
            <%!-- <.hollow_button
            disabled={@generating}
            class={if @generating, do: [extend: "opacity-50"], else: [extend: ""]}
          >
            <%= gettext("Surprise me!") %>
          </.hollow_button> --%>
          </div>
          <%= if @recipe.upload_capture_image do %>
            <div class="mt-6">
              <label for="upload-image" class="block text-sm font-medium text-gray-700">
                Upload Image
              </label>
              <input type="file" id="fileInput" accept="image/*" />
              <br /><br />
              <img
                id="previewHolder"
                alt="Uploaded Image Preview"
                width="250px"
                height="250px"
                style="border-radius:2px;border:1px solid grey;"
                src={@image_base64}
              />
              <div class="mt-4 text-center ml-6">
                <a href="#" class="text-blue-500 hover:underline" onclick="openCamera()">
                  Capture Image from Camera
                </a>
              </div>
            </div>
            <div id="myModal" class="modal">
              <div class="modal-content">
                <span class="close" onclick="closeModal()">&times;</span>
                <div class="video-container">
                  <video id="video" autoplay></video>
                </div>
                <button class="capture-button" onclick="captureImage()">Capture</button>
                <canvas id="canvas" style="display: none;"></canvas>
              </div>
            </div>
          <% end %>
          <.form
            phx-submit="save"
            phx-change="change"
            for={%{}}
            class={if @generating, do: "opacity-50", else: ""}
            id="bharat"
          >
            <div phx-remove="append" id="edit-prompt-owner-id" phx-hook="ImageHook">
              <input type="hidden" name="owner_id" phx-hook="ShopifyID" id="owner-id" />
            </div>
            <.big_body_text>
              <.madlib_editor
                chunks={@chunks}
                madlib={Recipe.split_madlib(@recipe.madlib)}
                reset_key={@reset_key}
              />
              <input type="hidden" id="image_base64" name="image_base64" value="" />
              <.button
                disabled={@generating}
                class={[
                  "inline-block h-12 min-w-[6rem] text-center",
                  if(@generating, do: "bg-slate-600", else: "")
                ]}
              >
                <%= if @generating do %>
                  <svg
                    class="animate-spin h-5 w-5 text-white inline"
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                  >
                    <circle
                      class="opacity-25"
                      cx="12"
                      cy="12"
                      r="10"
                      stroke="currentColor"
                      stroke-width="4"
                    >
                    </circle>
                    <path
                      class="opacity-75"
                      fill="currentColor"
                      d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
                    >
                    </path>
                  </svg>
                <% else %>
                  <.icon name="hero-sparkles" class="mr-2" /> <%= gettext("Generate") %>
                <% end %>
              </.button>
            </.big_body_text>
          </.form>
        </div>

        <script>
            let ImageHook = {
              mounted() {
                const storedImage = localStorage.getItem('image_base64');
                if (storedImage) {
                  document.getElementById('previewHolder').setAttribute('src', storedImage);
                }
              },
              updated() {
                const storedImage = localStorage.getItem('image_base64');
                if (storedImage) {
                  document.getElementById('image_base64').value=storedImage;
                  document.getElementById('previewHolder').setAttribute('src', storedImage);
                }
              }
            };
            document.addEventListener('DOMContentLoaded', function () {
                const storedImage = localStorage.getItem('image_base64');
                if (storedImage) {
                  document.getElementById('hiddenImage').value=storedImage;
                  document.getElementById('previewHolder').setAttribute('src', storedImage);
                  document.getElementById('image_base64').value = storedImage;
                }
            });
            document.getElementById('fileInput').addEventListener('change', function () {
                readURL(this);
            });

            function readURL(input) {
              if (input.files && input.files[0]) {
                var reader = new FileReader();
                reader.onload = (e) => {
                  const base64String = e.target.result;
                  document.getElementById('previewHolder').setAttribute('src', base64String);
                  document.getElementById('hiddenImage').setAttribute('src', base64String);
                  compressImageFromFile(e.target.result, 4096).then(base64String => {
                      document.getElementById('image_base64').value = base64String;
                      localStorage.setItem('image_base64', base64String);
                      console.log(base64String);
                  });
                };
                reader.readAsDataURL(input.files[0]);
            } else {
              document.getElementById('hiddenImage').setAttribute('src', base64String);
                //alert('Select a file to see preview');
            }
          }
            let video = document.getElementById('video');
            let canvas = document.getElementById('canvas');
            let modal = document.getElementById('myModal');
            function openCamera() {
              modal.style.display = "block";
              navigator.mediaDevices.getUserMedia({ video: true })
                .then(stream => {
                  video.srcObject = stream;
                })
                .catch(err => {
                  console.log("An error occurred: " + err);
                });
            }
            function closeModal() {
              modal.style.display = "none";
              let stream = video.srcObject;
              let tracks = stream.getTracks();

              tracks.forEach(track => {
                track.stop();
              });

              video.srcObject = null;
            }
          function captureImage() {
            canvas.width = video.videoWidth;
            canvas.height = video.videoHeight;
            let context = canvas.getContext('2d');
            context.drawImage(video, 0, 0, canvas.width, canvas.height);

            let base64Image = canvas.toDataURL('image/jpeg');
            console.log('Captured Image in Base64:', base64Image);

            compressImageFromFile(base64Image, 4096).then(base64String => {
              document.getElementById('previewHolder').setAttribute('src', base64String);
              document.getElementById('hiddenImage').setAttribute('src', base64String);
              document.getElementById('image_base64').value = base64String;
              localStorage.setItem('image_base64', base64String);
              console.log(base64String);
            });

            closeModal();
          }
            function compressImageFromFile(dataUrl, maxLength) {
              return new Promise((resolve) => {
                var img = new Image();
                img.onload = function () {
                    var canvas = document.createElement('canvas');
                    var context = canvas.getContext('2d');
                    var width = img.width;
                    var height = img.height;

                    // Calculate the new dimensions
                    var ratio = Math.min(1, Math.sqrt(maxLength / (width * height)));
                    width = width * ratio;
                    height = height * ratio;

                    canvas.width = width;
                    canvas.height = height;
                    context.drawImage(img, 0, 0, width, height);

                    var base64String = canvas.toDataURL('image/jpeg', 0.7); // Adjust the quality to compress further if needed

                    // If the compressed image is still too large, reduce the quality further
                    while (base64String.length > maxLength && quality > 0.1) {
                        quality -= 0.1;
                        base64String = canvas.toDataURL('image/jpeg', quality);
                    }

                    resolve(base64String);
                };
                img.src = dataUrl;
              });
            }
        </script>

        <div class="col-span-1 mx-4">
          <.image_loading :if={@generating} />
          <div :if={!@generating}>
            <div :if={!@image.id} class="grid grid-cols-2 gap-4" id="preview-images">
              <%= for {image, index} <- Recipes.get_featured_preview_images(@recipe, 4) |> Enum.with_index() do %>
                <.recipe_preview_image
                  id={"preview-image-#{@recipe.id}-#{index}"}
                  src={
                    Roboart.File.path_to_full_url(image.cached_image_url) || image.original_image_url
                  }
                  alt={image.prompt}
                  image={image}
                  recipe={@recipe}
                  href={
                    if image.id,
                      do: ~p"/recipes/#{@recipe.slug}/images/new?image=#{image.id}",
                      else: ~p"/recipes/#{@recipe.slug}/images/new"
                  }
                />
              <% end %>
            </div>
            <div :if={@image.id} id="selected-image">
              <div :if={@output} class="grid grid-cols-4 gap-4 pb-4" id="preview-images">
                <%= for {image, index} <- @output |> Enum.with_index() do %>
                  <.recipe_preview_image
                    id={"preview-image-#{@recipe.id}-#{index}"}
                    src={
                      Roboart.File.path_to_full_url(image.cached_image_url) ||
                        image.original_image_url
                    }
                    alt={image.prompt}
                    image={image}
                    recipe={@recipe}
                    selected={image == @image}
                    event="select_image"
                  />
                <% end %>
              </div>
              <.recipe_preview_image
                id={"selected-image-#{@recipe.id}"}
                src={
                  Roboart.File.path_to_full_url(@image.cached_image_url) || @image.original_image_url
                }
                href="#"
                alt={@image.prompt}
                image={@image}
                recipe={@recipe}
              />
              <div class="mt-6">
                <.cta_button
                  click={JS.navigate(~p"/images/#{@image.id}/edit")}
                  class={[extend: "block w-full"]}
                >
                  Print this design <.icon name="hero-arrow-right" />
                </.cta_button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <.live_component
        module={CommunityGallery}
        id="community-gallery"
        images={if @show_my_images, do: @my_images, else: @recipe.images}
        recipe={@recipe}
        show_my_images={@show_my_images}
      />
    </div>
    """
  end

    <%= for chunk <- @madlib do %>
      <%= if Recipe.variable_chunk?(chunk) do %>
        
        <.madlib_input
          id={"chunk_#{Recipe.chunk_to_key(chunk)}-#{@reset_key}"}
          label={Recipe.label(chunk)}
          name={"chunk[#{Recipe.chunk_to_key(chunk)}]"}
          value={String.trim(Map.get(@chunks, Recipe.chunk_to_key(chunk), ""))}
        />
      <% else %>
        <%= chunk %>
      <% end %>
    <% end %>

Due to this loop every time when we enter a character image got cleared from

<img
          id="previewHolder"
          alt="Uploaded Image Preview"
          width="250px"
          height="250px"
          style="border-radius:2px;border:1px solid grey;"
          src={@image_base64}
        />

Please suggest

Marked As Solved

bharatb-futurism

bharatb-futurism

You are right but for that It was need to create a div element which will have phx-update=“ignore” and inside the div it is needed to keep hidden field and preview image field so that DOM will be updated but due the div imagepreview field will be ignored.

Also Liked

sodapopcan

sodapopcan

Hello and welcome!

If you want to manage any client-state on the client, you need to add phx-update="ignore" to the parent of whatever you want left alone. You can read more here.

Where Next?

Popular in Questions Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New

Other popular topics Top

stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement