英文:
Javascript doesn't run on back button or menu item click in my rails application
问题
在我的Rails 7中,我有以下Javascript代码,它包含在`profile.js`文件中:
window.addEventListener('load', function () {
updateProfile();
})
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload();
}
};
window.addEventListener('popstate', function() {
updateProfile();
});
window.addEventListener( "pageshow", function ( event ) {
var historyTraversal = event.persisted ||
( typeof window.performance != "undefined" &&
window.performance.navigation.type === 2 );
if ( historyTraversal ) {
// 处理页面恢复
window.location.reload();
}
});
function fileSelected() {
// 获取选择的文件
const file = document.querySelector('#fileInput').files[0];
if (file == null){
return
}
// 创建一个新的FileReader对象
const reader = new FileReader();
// 为FileReader对象设置onload事件处理程序
reader.onload = function(event) {
// 更新头像的src属性
document.querySelector('#profile-image').src = event.target.result;
};
// 以DataURL形式读取所选文件
reader.readAsDataURL(file);
}
function updateProfile(){
var toggle_switch = document.getElementById('toggle');
var save_button = document.getElementById('save-button');
let nameInput = document.getElementById('name');
nameInput.addEventListener('input', function(event) {
let error_element = document.getElementById('error-message-name');
let regex = /^.{3,}$/; // 要求至少3个字符的正则表达式
if (regex.test(value)) {
// 值有效
error_element.classList.remove("visible-error")
error_element.classList.add("invisible-error")
} else {
// 值无效
error_element.classList.remove("invisible-error")
error_element.classList.add('visible-error');
}
});
toggle_switch.addEventListener('click', function() {
if (this.getAttribute('data-type') == "influencer"){
this.setAttribute('data-type', "vender");
document.getElementById('profile_type').value = "vender";
}else{
this.setAttribute('data-type', "influencer");
document.getElementById('profile_type').value = "influencer";
}
});
save_button.addEventListener('click', function() {
let new_name = document.getElementById('name').value;
let new_headline = document.getElementById('headline').value;
let new_country = document.getElementById('country').value;
let new_city = document.getElementById('city').value;
let new_about = document.getElementById("about").value;
let new_profile_type = document.getElementById('toggle').getAttribute('data-type');
let div = document.getElementById('user_id');
let user_id = div.getAttribute('data');
var fileInput = document.getElementById("fileInput");
var file = fileInput.files[0];
if (file == null) {
$.ajax({ // 没有上传新图像来更改
type: "PATCH",
url: encodeURI('/users/' + user_id),
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: { user: { profile_type: new_profile_type, name: new_name, headline: new_headline, country: new_country, city: new_city, about: new_about} },
success: function(response) {
console.log("更新成功。")
}
});
} else {
const formData = new FormData();
formData.append("avatar", file);
$.ajax({
url: encodeURI('/users/' + user_id),
type: "PUT",
beforeSend(xhr, options) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
xhr.setRequestHeader('image-change', true);
options.data = formData;
},
success: function(response) {
console.log("图像更新成功。")
},
error: () => {
alert("出现问题,请重试。");
}
});
}
});
}
英文:
I have the following Javascript code contained in my profile.js
file in Rails 7:
window.addEventListener('load', function () {
updateProfile();
})
window.onpageshow = function(event) {
if (event.persisted) {
window.location.reload();
}
};
window.addEventListener('popstate', function() {
updateProfile();
});
window.addEventListener( "pageshow", function ( event ) {
var historyTraversal = event.persisted ||
( typeof window.performance != "undefined" &&
window.performance.navigation.type === 2 );
if ( historyTraversal ) {
// Handle page restore.
window.location.reload();
}
});
function fileSelected() {
// Get the selected file
const file = document.querySelector('#fileInput').files[0];
if (file == null){
return
}
// Create a new FileReader object
const reader = new FileReader();
// Set the onload event handler for the FileReader object
reader.onload = function(event) {
// Update the src attribute of the profile image
document.querySelector('#profile-image').src = event.target.result;
};
// Read the selected file as a DataURL
reader.readAsDataURL(file);
}
function updateProfile(){
var toggle_switch = document.getElementById('toggle');
var save_button = document.getElementById('save-button');
let nameInput = document.getElementById('name');
nameInput.addEventListener('input', function(event) {
let error_element = document.getElementById('error-message-name');
let regex = /^.{3,}$/; // Regex that requires at least 3 characters
if (regex.test(value)) {
// Value is valid
error_element.classList.remove("visible-error")
error_element.classList.add("invisible-error")
} else {
// Value is invalid
error_element.classList.remove("invisible-error")
error_element.classList.add('visible-error');
}
});
toggle_switch.addEventListener('click', function() {
if (this.getAttribute('data-type') == "influencer"){
this.setAttribute('data-type', "vender");
document.getElementById('profile_type').value = "vender";
}else{
this.setAttribute('data-type', "influencer");
document.getElementById('profile_type').value = "influencer";
}
});
save_button.addEventListener('click', function() {
let new_name = document.getElementById('name').value;
let new_headline = document.getElementById('headline').value;
let new_country = document.getElementById('country').value;
let new_city = document.getElementById('city').value;
let new_about = document.getElementById("about").value;
let new_profile_type = document.getElementById('toggle').getAttribute('data-type');
let div = document.getElementById('user_id');
let user_id = div.getAttribute('data');
var fileInput = document.getElementById("fileInput");
var file = fileInput.files[0];
if (file == null) {
$.ajax({ //A new image was not uploaded for change
type: "PATCH",
url: encodeURI('/users/' + user_id),
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: { user: { profile_type: new_profile_type, name: new_name, headline: new_headline, country: new_country, city: new_city, about: new_about} },
success: function(response) {
console.log("Update success.")
}
});
} else {
const formData = new FormData();
formData.append("avatar", file);
$.ajax({
url: encodeURI('/users/' + user_id),
type: "PUT",
beforeSend(xhr, options) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
xhr.setRequestHeader('image-change', true);
options.data = formData;
},
success: function(response) {
console.log("Image Update success.")
},
error: () => {
alert("An issue occured. Please try again.");
}
});
}
});
}
This code should be run when a user navigates to my profile page with the following html.erb
:
<head>
<%= stylesheet_link_tag 'linked_card', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'profile_card', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_link_tag 'alert', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag "profile", "data": { "turbolinks-track": "reload" } %>
</head>
<header>
<%= render partial: "layouts/header" %>
</header>
<body class = "profile_main_body">
<%= render partial: "layouts/profile_card", locals: { user: @user } %>
<br>
<br>
<div class="col-md-6 mx-auto text-center">
<h3 class="heading-black">Link Accounts</h3>
<p class="text-muted lead">Click the button below to link an account to your profile. You will be redirected to the chosen service.</p>
<a href="<%= user_profile_path(@user.username)%>" class="border px-3 p-1 add-experience"><i class="fa fa-link"></i>&nbsp;Link Account</a>
</div>
<br>
<br>
<div class="container">
<div class="row">
<%= render partial: "layouts/linked_card" %>
<%= render partial: "layouts/linked_card" %>
</div>
</div>
<br>
</body>
<footer>
<%= render partial: "layouts/footer" %>
</footer>
I added the following line:
<%= javascript_include_tag "profile", "data": { "turbolinks-track": "reload" } %>
Because I was thinking this is a turbolinks issue. Nothing has changed though with the line above. Is there anyway I can re-run the my javascript when the user clicks a turbo link? The javascript runs fine on a full page reload. Any help would be great thanks!
答案1
得分: 1
Turbolinks在Rails 7中被Turbo取代。但是在处理JavaScript时,有很多重叠之处。
Turbolinks/Turbo(或几乎任何SPA框架)会在页面之间创建持久的浏览器会话。以“当页面加载时,我想要附加一个事件处理程序到X,执行Y”的方式思考,可能在十年前的基本JS教程中还可以,但实际上是适得其反的:
- 当元素被动态插入到DOM中时,它不起作用。就像Turbo Drive或Turbolinks替换页面内容时一样。或者每当人们尝试首次插入通过AJAX加载的内容时。
- 直接向一堆元素添加事件处理程序会增加很多开销。
- 如果你钩入一个事件像
turbolinks:change
,你可能会多次向同一个元素添加事件处理程序。仅仅切换加载/就绪事件以适应Turbo/Turbolinks等效并不一定会修复糟糕的代码,而可能只会引入新问题。 - 这是一个有缺陷的心理模型,因为你构建JS只是为了“在页面Y上frobnob X”,而不是考虑可重用的UI组件或以可重用的方式增强元素的行为。
那么应该怎么办?
不要直接为所有东西分配ID和事件处理程序。你最终只会得到重复的ID和混乱的JS。
相反,使用委托来捕获事件,当事件冒泡到DOM的顶部时:
// 当用户单击class="foo"的按钮时执行一些令人惊叹的操作
document.addEventListener('click', function(event){
let el = event.target;
if (!el.matches('.foo')) return;
// ...
});
使用类和属性来定位元素,而不是ID。现在不是2010年了,查询DOM要快得多。
使用DOM遍历和表单API来获取与点击/更改等事件相关的元素。记住,在JS中函数实际上可以接受参数。如果需要从后端传递附加信息到你的JS,使用数据属性。
这基本上就是Stimulus所做的:
<div data-controller="hello">
<input type="text">
<button>Greet</button>
</div>
// src/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log("Hello, Stimulus!", this.element)
}
}
英文:
Turbolinks is replaced by Turbo in Rails 7. However there is a lot of overlap in how you should think about JS.
Turbolinks/Turbo (or almost any SPA framework for that matter) creates a persistent browser session across pages. Thinking in terms of "when the page is loaded I want to attach an event handler to X that does Y" might have been OK in a basic JS tutorial ten years ago but is actually counter-productive:
- It doesn't work when elements are inserted dynamically into the DOM. Like when Turbo Drive or Turbolinks replaces the page contents. Or whenever people try to insert content loaded with AJAX for the first time.
- Adding event handlers directly to a bunch of elements adds a lot of overhead.
- If you hook into an event like
turbolinks:change
you might be adding the event handler multiple times to the same element. Just switching out the load/ready event for the Turbo/Turbolinks equivilent won't necissarily fix stinky code and may just introduce new issues. - It's a broken mental model as you build your JS to just "frobnob X on page Y" instead of thinking in terms of reusable UI components or augmenting the behavior of elements in a reusable way.
So what then?
Stop assigning IDs and event handlers directly to everything. You're just going to end up with duplicate IDs and garbage JS.
Instead use delegation to catch the event as it bubbles to the top of the DOM:
// Do something awesome when the user clicks buttons with class="foo"
document.addEventHandler('click', function(event){
let el = event.target;
if (!el.matches('.foo')) return;
// ...
});
Use classes and attributes to target elements. Not ID's. Its not 2010 and querying the DOM is much faster.
Use DOM traversal and the form API to get elements relative to the element that was clicked/changed/etc. Remember that in JS functions can actually take arguments. Use data attributes if you need to pass additional information from the backend to your JS.
This is basically what Stimulus does:
<div data-controller="hello">
<input type="text">
<button>Greet</button>
</div>
// src/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log("Hello, Stimulus!", this.element)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论