Home > front end >  Javascript stops working when a link is clicked
Javascript stops working when a link is clicked

Time:07-09

I have been trying to create a facebook clone app using rails 7. I have used javascript to render some fronted features. At first nothing seems amiss but when I click on a link on the page to redirect to some other view the javascript stops working. I believe that this might be due to the script being loaded before the DOM. What seemed to work was to reload the page and then everything worked as expected. I thus tried using window.location.reload() command in javascript which worked but then as I added more links the task became too tedious and also if I wished to redirect to another page the function redirected me to the same page. So is there a way to implement all the javascript features effectively without having to reload the page.

app/javascript/application.js

// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"

function home(){
    let link = document.querySelector(".title-container")
    if (link!=null){
    link.addEventListener("click", function(){
        var location = window.location.origin 
        window.location.assign(location)
        // console.log(window.location)
        window.localStorage.setItem("activeBtn", "#index")
        // window.location.reload()
    })
}

    // let begin_session = document.querySelector(".actions")
    // console.log(begin_session)
    // if (begin_session != null)
    // {
    //     begin_session.addEventListener("click", function(){
    //         // var location = window.location.origin   "/"
    //         window.location.assign(location)
    //         console.log(location)
    //         window.localStorage.setItem("activeBtn", "#index")
    //         // window.location.reload()
    //     })  
    // }
    // console.log(window.location)    
}

function refresh(){
    let links = document.querySelector("a")
    console.log(links)
    for(let i=0; i<links.length ; i   )
    {
        links[i].addEventListener("click", function(){
            console.log(this)

            window.location.reload()
        })
    }
}

function PageTransition(){
    const sectBtns = document.querySelectorAll(".page-control")
    // console.log(sectBtns)

    let flag = true
    var activeBtn = localStorage.getItem("activeBtn")

    // console.log(document.querySelector(activeBtn))
    if (document.querySelector(activeBtn) != null){
    document.querySelector(activeBtn).className  = " active"
    }

    for (let i=0; i<sectBtns.length; i  ){
        if (sectBtns[i].className.includes("active")){
            flag = false
            break
        }
    }

    if (flag)
    {
        if (document.querySelector("#home") != null) 
        document.querySelector("#home").className  = " active"

    }
    for (let i = 0; i < sectBtns.length; i  ){
        sectBtns[i].addEventListener("click", function(){
            // console.log(this)
            let currentBtn = document.querySelectorAll(".active");
            // currentBtn[0].className = currentBtn[0].className.replace("active", "")
            // this.className  = " active"
            var id = "#" this.id
            localStorage.setItem("activeBtn", id)
            console.log(localStorage.getItem("activeBtn"))
            console.log(window.location)
            var location = window.location.origin   localStorage.getItem("activeBtn").replace("#","/")
            window.location.assign(location)
            // refresh()
        })
    }

}

function profileMenu(){
    const profileBtn = document.querySelector(".profile")
    const posts = document.querySelector(".posts")
    // const navbar = document.querySelector(".navbar")
    const menu = document.querySelector(".profile-menu")

    if (profileBtn!= null){
    profileBtn.addEventListener("click", function(){
        if (menu.style.display === "none"){
            menu.style.display = "block";
        }else{
            menu.style.display = "none"
        }
    })

    if (posts != null){
    posts.addEventListener("click", function(){
        if (menu.style.display === "block")
        menu.style.display = "none"
    })
}
}
}

// function like(){
//     const btn = document.querySelectorAll(".like-btn")
//     for (let i=0; i< btn.length; i  ){
//         let current_btn = btn[i]    
//         current_btn.addEventListener("click", function(){
//         if (current_btn.className.includes("liked"))
//             current_btn.className = current_btn.className.replace("liked", "")
//         else
//             current_btn.className  = " liked"
//         })
//     }
// }

function new_post(){

    const bar = document.querySelector(".post-creator")
    if (bar != null){
    const poster = document.querySelector(".new-post")
    const homepage = document.querySelector(".homepage")
    const body = document.querySelector("body")
    const close = document.querySelector(".close")
    bar.addEventListener("click", function(){
        if (poster.style.display === "none"){
            poster.style.display = "block"
            console.log(poster.style.display)
            homepage.style.opacity = "0.5"
            poster.style.opacity = "1"
            var btn = document.querySelector('.submit')
            console.log(btn)
            btn.addEventListener("click", function(){
                window.location.reload()    
            })

        }
        else{
            poster.style.display = "none"
            homepage.style.opacity = "1"
        }
    })
    
    // homepage.addEventListener("click", function(){
    //     if (poster.style.display === "block")
    //     poster.style.display = "none"
    // })
    close.addEventListener("click", function(){
        if (poster.style.display === "block"){
            poster.style.display = "none"
            homepage.style.opacity = "1"
            window.location.reload()

        }
    })
    }
}


switch (document.readyState){
case "interactive":
    PageTransition()
    profileMenu()
    // like()
    new_post()
    home()
    // refresh()
    console.log("hi")
    break;
}

CodePudding user response:

You can simply add defer attribute to your script tag

<script src="script.js" defer></script>

CodePudding user response:

Not sure of the context but could be the way your JS is being loaded. These functions should not be in app/javascript/application.js it's the entry point for the entire application's JS. Also, there's no reason to set any attributes for the window object. Page specific JS should only be loaded on the pages you need it helps with separation of concerns.

This video has key concepts for import maps https://youtu.be/PtxZvFnL2i0

Stimulus will work great in this situation and it can handle state for you. Even if you don't want to utilize all of its capabilities it's not complicated to set up looks like you already have it installed so it may be a quick solution while also matching your app's current hierarchy.

in the root of your project directory run

$ rails g stimulus controller_that_renders_the_view/controller_action

e.g. I have a PostsController under app/controllers/posts_controller.rb and I want JS for its index action (PostsController#index) in app/views/posts/index.html.erb

$ rails g stimulus posts/index

This will create the file app/javascript/controllers/posts/index_controller.js

add console.log('app/javascript/controllers/posts/index_controller.js connected') to the connect function in your new file like this:

// app/javascript/controllers/posts/index_controller.js

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="posts--index"
export default class extends Controller {
  connect() {
    console.log('app/javascript/controllers/posts/index_controller.js connected')
  }
}

Open the config/importmap.rb file and you should see the line

pin_all_from "app/javascript/controllers", under: "controllers"

unless it has been removed. Mine from a fresh Rails app looks like this:

# config/importmap.rb

# Pin npm packages by running ./bin/importmap

pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers" 
# ^^^ Because all from app/javascript/controllers is pinned you will be able to use all files under app/javascript/controllers throughout the app without additional setup

importmap.rb is in the config dir and since your JS is being pinned through it you'll need to restart your server to make use of the new file

You may also want to precompile your assets to make sure all assets are compiled and available to your app.

$ rails assets:precompile && rails s

Wrap your HTML in an element with the data attribute data-controller="posts--index" that you want your stimulus controller to have access to:

<!-- app/views/posts/index.html.erb -->

<span data-controller="posts--index">
  <!-- app/javascript/controllers/posts/index_controller.js will have access to everything wrapped in this span -->

  <p style="color: green"><%= notice %></p>
  <h1>Posts</h1>

  <div id="posts">
    <% @posts.each do |post| %>
      <%= render post %>
      <p>
        <%= link_to "Show this post", post %>
      </p>
    <% end %>
  </div>
  <%= link_to "New post", new_post_path %>

</span>

Open the browser console and visit the posts#index page and you should see app/javascript/controllers/posts/index_controller.js connected logged in the console.

you can also add initialize() and disconnect() functions to your posts/index_controller to get the hang of it like this:

// app/javascript/controllers/posts/index_controller.js

import { Controller } from "@hotwired/stimulus"

const postControllerIndexFilePath = "app/javascript/controllers/posts/index_controller.js"

// Connects to data-controller="posts--index"
export default class extends Controller {

  // Optional function for when controller is initialized, can remove
  initialize(){
    console.log(`${postControllerIndexFilePath} initialized`)
  }

  // Optional function for when controller is connected, can remove
  connect(){
    console.log(`${postControllerIndexFilePath} connected`)
    this.exFunction()
    // Other functions you want to execute when controller is connected
  }

  // Optional function for when controller is disconnected, can remove
  disconnect() {
    console.log(`${postControllerIndexFilePath} disconnected`)
  }


  exFunction(){
    console.log("\nsuper cool exFunction function")
  }
}

Stimulus allows you to interact with targets via data-attributes so you don't have to select them with query selectors. You can learn more about it here. Didn't cover separating your JS files with content_for blocks so you can yield file specific JS to their relevant pages but DHH goes over that in the first link.

Also, feel free to check out this import_map demo repo, made it a few months ago to get familiar with import_maps.

  • Related