Home > database >  How do I change text label on svg click using javascript in Rails 7 app?
How do I change text label on svg click using javascript in Rails 7 app?

Time:02-05

Users can click a checkmark icon to mark a park as Visited (grey checkmark) or Not Visited (green checkmark).

I want to also make the text next to the checkmark icon change every time the user clicks the checkmark (between "Visited" and "Not visited") - even better if the text is also part of the clickable area.

I'm new to Javascript so I'm sure this is basic but I'm not sure how to do it.

javascript/controllers/visits_controller.js

import { Controller } from '@hotwired/stimulus';
import axios from 'axios';

export default class extends Controller {
  HEADERS = { 'ACCEPT': 'application/json' };

  visit() {
    if (this.element.dataset.userLoggedIn === 'false' ) {
      return document.querySelector(".sign-in-link").click();
    }

    if (this.element.dataset.visited === 'true') {
      this.unvisitPark();
    } else {
      this.visitPark();
    }
  }

  getVisitPath() {
    return '/api/visits';
  }

  getUnvisitPath(visitId) {
    return `/api/visits/${visitId}`
  }

  unvisitPark() {
    axios.delete(this.getUnvisitPath(this.element.dataset.visitId), {
      headers: this.HEADERS
    })
    .then((response) => {
      this.element.dataset.visited = 'false'
      this.element.dataset.visitId = '';
      this.element.setAttribute('stroke', '#D3D3D3');
      this.element.setAttribute('stroke-width', '2');
    });
  }

  visitPark() {
    axios.post(this.getVisitPath(), {
      user_id: this.element.dataset.userId,
      park_id: this.element.dataset.parkId
    }, {
      headers: this.HEADERS
    })
    .then((response) => {
      this.element.dataset.visited = 'true'
      this.element.dataset.visitId = response.data.id;
      this.element.setAttribute('stroke', '#5FA777');
      this.element.setAttribute('stroke-width', '4');
    });
  }
}

views/parks/_park_cards.erb

<% @parks.each do |park| %>
...
  <div >
    <span>Visited</span> // <-- Make this text change whenever the svg is clicked
    <div >
      <svg
           data-controller="visits"
           data-user-logged-in="<%= user_signed_in? %>"
           data-user-id="<%= current_user&.id %>"
           data-park-id="<%= park.id %>"
           data-visit-id="<%= park.visits.find_by(user: current_user)&.id %>"
           data-visited="<%= park.visited_by?(current_user) %>"
           data-action="click->visits#visit"
           stroke="<%= current_user && current_user.visited_parks.include?(park) ? '#5FA777' : '#D3D3D3' %>"
           stroke-width="<%= current_user && current_user.visited_parks.include?(park) ? 4 : 2 %>"
           fill="none"
           width="24"
           height="24"
           xmlns="http://www.w3.org/2000/svg"
           viewBox="0 0 24 24"
           >
           <path
             stroke-linecap="round"
             stroke-linejoin="round"
             d="M4.5 12.75l6 6 9-13.5" />
       </svg>
     </div>
   </div>
...
<% end %>

CodePudding user response:

I managed to get pretty close to what I want by adding text to the SVG itself.

However, I am still struggling with the spacing, and there might be an accessibility issue by using text inside the image rather than as a separate text field.

I'm hoping someone will contribute an answer using application text rather than text inside the svg image, but in case someone is reading this and wants to use this approach, here it is:

<% @parks.each do |park| %>
...
  <div >
    <svg
         data-controller="visits"
         data-user-logged-in="<%= user_signed_in? %>"
         data-user-id="<%= current_user&.id %>"
         data-park-id="<%= park.id %>"
         data-visit-id="<%= park.visits.find_by(user: current_user)&.id %>"
         data-visited="<%= park.visited_by?(current_user) %>"
         data-action="click->visits#visit"
         stroke="<%= current_user && current_user.visited_parks.include?(park) ? '#5FA777' : '#D3D3D3' %>"
         stroke-width="<%= current_user && current_user.visited_parks.include?(park) ? 4 : 3 %>"
         fill="none"
         width="160"
         height="24"
         xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 24 24"
         >
         <path
           stroke-linecap="round"
           stroke-linejoin="round"
           d="M4.5 12.75l6 6 9-13.5" />
        <text
          id="visited-text"
          x="-68"
          y="18"
          stroke="currentColor"
          fill="currentColor"
          stroke-width="0"
          >
            <%= current_user && current_user.visited_parks.include?(park) ? "Visited" : "Not visited" %>
        </text>
     </svg>
   </div>
...
<% end %>
import { Controller } from '@hotwired/stimulus';
import axios from 'axios';

export default class extends Controller {
  HEADERS = { 'ACCEPT': 'application/json' };

  visit() {
    if (this.element.dataset.userLoggedIn === 'false' ) {
      return document.querySelector(".sign-in-link").click();
    }

    if (this.element.dataset.visited === 'true') {
      this.unvisitPark();
    } else {
      this.visitPark();
    }
  }

  getVisitPath() {
    return '/api/visits';
  }

  getUnvisitPath(visitId) {
    return `/api/visits/${visitId}`
  }

  unvisitPark() {
    axios.delete(this.getUnvisitPath(this.element.dataset.visitId), {
      headers: this.HEADERS
    })
    .then((response) => {
      this.element.dataset.visited = 'false'
      this.element.dataset.visitId = '';
      this.element.setAttribute('stroke', '#D3D3D3');
      this.element.setAttribute('stroke-width', '2');
      this.element.getElementById('visited-text').textContent = "Not visited";
    });
  }

  visitPark() {
    axios.post(this.getVisitPath(), {
      user_id: this.element.dataset.userId,
      park_id: this.element.dataset.parkId
    }, {
      headers: this.HEADERS
    })
    .then((response) => {
      this.element.dataset.visited = 'true'
      this.element.dataset.visitId = response.data.id;
      this.element.setAttribute('stroke', '#5FA777');
      this.element.setAttribute('stroke-width', '4');
      this.element.getElementById('visited-text').textContent = "Visited";
    });
  }
}
  • Related