英文:
Object attached to window object is undefined inside a Stimulus JS controller
问题
在浏览器控制台中,你可以输入 window.theme.white
并得到 #fff
返回,但是在 Stimulus 控制器内,window.theme
未定义。你已尝试了一种方法:
var window = this.element.ownerDocument.defaultView;
但是似乎没有成功,theme
对象没有附加到其中。也许是另一个 window
实例?你感到困惑。
这是添加主题到 window
对象的代码:
// ...
document.querySelectorAll("link[href]").forEach((link) => {
const href = link.href.split("/");
if(href.pop() === "dark.css"){
// Add theme to the window object
window.theme = darkTheme;
} else {
// Add theme to the window object
window.theme = lightTheme;
}
});
这是未能找到 window.theme
对象的 Stimulus 控制器代码:
import { Controller } from "@hotwired/stimulus";
import "../../modules/vector-maps/world"
export default class extends Controller {
static targets = ["targetMap"]
constructor() {
super();
this.map = new Object();
}
connect() {
this.InitMap();
}
mapResize(event) {
this.map.updateSize();
}
InitMap() {
var markers = [{
coords: [37.77, -122.41],
name: "San Francisco: 375"
},
// ... 还有更多标记
console.log('window object: ', window)
console.log('theme object attached to window: ', window.theme)
this.map = new jsVectorMap({
map: "world",
selector: "#world_map",
zoomButtons: true,
markers: markers,
markerStyle: {
initial: {
r: 9,
stroke: window.theme.white,
strokeWidth: 7,
stokeOpacity: .4,
fill: '#3B7DDD'
},
// ... 还有更多样式
},
regionStyle: {
initial: {
fill: '#e9ecef'
}
},
zoomOnScroll: false
});
}
}
你提到将主题代码复制到 Stimulus 控制器构造函数中可以解决问题。这可能是由于竞争条件引起的。
英文:
I'm trying to setup a stimulus controller in a Rails 7 Bootstrap project. There are theme variables that are set to the window
object.
So in the browser console, i can enter window.theme.white
, and I get back #fff
. These variables are needed to properly initialize a map object.
However, inside the stimulus controller, window.theme
is undefined.
I have also tried the following:
var window = this.element.ownerDocument.defaultView;
inside the stimulus controller, and it seems to work, insofar as that I get the same window
object than in the browser, but the theme
object is not attached. So maybe it's another instance of window
? I'm stumped.
EDIT 1: my bad. window
object IS accesible. But the theme object attached to it is not. I have modified the title and question accordingly
This is the code that adds the theme to the windows object. It's imported as part of the application.js
main js file:
/*
* Add color theme colors to the window object
* so this can be used by the charts and vector maps
*/
const lightTheme = {
"id": "light",
"name": "Light",
"primary": "#3B7DDD",
"secondary": "#6c757d",
"success": "#1cbb8c",
"info": "#17a2b8",
"warning": "#fcb92c",
"danger": "#dc3545",
"white": "#fff",
"gray-100": "#f8f9fa",
"gray-200": "#e9ecef",
"gray-300": "#dee2e6",
"gray-400": "#ced4da",
"gray-500": "#adb5bd",
"gray-600": "#6c757d",
"gray-700": "#495057",
"gray-800": "#343a40",
"gray-900": "#212529",
"black": "#000"
};
const darkTheme = {
"id": "dark",
"name": "Dark",
"primary": "#3B7DDD",
"secondary": "#7a828a",
"success": "#1cbb8c",
"info": "#17a2b8",
"warning": "#fcb92c",
"danger": "#dc3545",
"white": "#222E3C",
"gray-100": "#384350",
"gray-200": "#4e5863",
"gray-300": "#646d77",
"gray-400": "#7a828a",
"gray-500": "#91979e",
"gray-600": "#a7abb1",
"gray-700": "#bdc0c5",
"gray-800": "#d3d5d8",
"gray-900": "#e9eaec",
"black": "#fff"
}
document.querySelectorAll("link[href]").forEach((link) => {
const href = link.href.split("/");
if(href.pop() === "dark.css"){
// Add theme to the window object
window.theme = darkTheme;
}
else {
// Add theme to the window object
window.theme = lightTheme;
}
});
So nothing fancy. Just global vars attached to the window
object.
This is the Stimulus controller that fails to find the window.theme
object:
import { Controller } from "@hotwired/stimulus"
import "../../modules/vector-maps/world"
export default class extends Controller {
static targets = ["targetMap"]
constructor() {
super();
this.map = new Object();
}
connect() {
this.InitMap();
}
mapResize(event) {
this.map.updateSize();
}
InitMap() {
var markers = [{
coords: [37.77, -122.41],
name: "San Francisco: 375"
},
{
coords: [40.71, -74.00],
name: "New York: 350"
},
{
coords: [39.09, -94.57],
name: "Kansas City: 250"
},
{
coords: [36.16, -115.13],
name: "Las Vegas: 275"
},
{
coords: [32.77, -96.79],
name: "Dallas: 225"
}
];
console.log('window object: ', window)
console.log('theme object attached to window: ', window.theme)
this.map = new jsVectorMap({
map: "world",
selector: "#world_map",
zoomButtons: true,
markers: markers,
markerStyle: {
initial: {
r: 9,
stroke: window.theme.white,
strokeWidth: 7,
stokeOpacity: .4,
fill: '#3B7DDD'
},
hover: {
fill: '#3B7DDD',
stroke: '#3B7DDD'
}
},
regionStyle: {
initial: {
fill: '#e9ecef'
}
},
zoomOnScroll: false
});
}
}
And this is the error, along with the console logs showing window.theme
as undefined
EDIT 2:
Copying the theme code into the stimulus controller constructor to set the theme global variables to the window
object does work. Maybe it's a race condition ?
答案1
得分: 1
以下是您要翻译的内容:
大多数情况下,您的application.js
代码在Stimulus应用程序初始化之后运行。
您可以重新排列它们添加到HTML的方式,以便Stimulus首先加载,或者设置一种知道主题何时准备好的方法。
另一种方法可能是将主题代码放入Stimulus控制器中,但这需要考虑一些边缘情况,特别是您可能希望主题代码尽快运行。
以下是使用事件分发来知道主题何时准备好的示例。
// ...所有其他主题内容
document.querySelectorAll("link[href]").forEach((link) => {
const href = link.href.split("/");
if(href.pop() === "dark.css"){
// 将主题添加到window对象
window.theme = darkTheme;
}
else {
// 将主题添加到window对象
window.theme = lightTheme;
}
});
// 新代码在此处
document.dispatchEvent(new CustomEvent('theme:ready', {
detail: { theme: window.theme },
cancelable: false
}));
现在在您的Stimulus控制器中,您可以设置一个Promise来检查主题是否存在,如果不存在,则在运行init方法之前等待事件触发。
import { Controller } from "@hotwired/stimulus";
import "../../modules/vector-maps/world";
export default class extends Controller {
static targets = ["targetMap"]
constructor() {
super();
this.map = new Object();
}
async connect() {
theme = await new Promise(resolve => {
if (window.theme) resolve(window.theme)
document.addEventListener('theme:ready', ({ detail: { theme } }) => {
resolve(theme);
})
})
this.InitMap(theme); // 传递主题值,以便更容易理解该方法,而不是访问全局变量
// 附注 - 以大写字母开头的方法可能会令人困惑,最好使用小驼峰命名法
}
// ...其他方法
}
注意:您不需要读取主题并将其传递给initMap
,但以这种方式可能会更容易理解发生了什么。
Stimulus应该在Promise解析之前等待连接方法完成,并且现在只有在有主题时才会运行initMap
。
假设application.js
仅在您的Stimulus代码之后加载,这应该在不到100毫秒内完成。然而,现在您的代码不需要担心加载顺序。
英文:
Most likely, your application.js
code is running after your Stimulus application initialises.
You can either re-order the way these get added to your HTML so that Stimulus loads first or set up a way to know when the theme is ready.
Another approach could be to put your theme code into a Stimulus controller, but that comes with some edge cases to consider, especially as you probably want your theme code running as soon as possible.
Here is an example of using an event dispatching to know when the theme is ready.
// ... all other theme stuff
document.querySelectorAll("link[href]").forEach((link) => {
const href = link.href.split("/");
if(href.pop() === "dark.css"){
// Add theme to the window object
window.theme = darkTheme;
}
else {
// Add theme to the window object
window.theme = lightTheme;
}
});
// new code here
document.dispatchEvent(new CustomEvent('theme:ready', {
detail: { theme: window.theme },
cancelable: false
}));
Now in your Stimulus controller, you can set up a Promise to check if the theme is there and if not, wait for the event to fire before running your init method.
import { Controller } from "@hotwired/stimulus"
import "../../modules/vector-maps/world"
export default class extends Controller {
static targets = ["targetMap"]
constructor() {
super();
this.map = new Object();
}
async connect() {
theme = await new Promise(resolve => {
if (window.theme) resolve(window.theme)
document.addEventListener('theme:ready', ({ detail: { theme } }) => {
resolve(theme);
})
})
this.InitMap(theme); // passing in the theme value so that the method is easier to reason about, not accessing globals
// aside - methods starting with a capital could be confusing, best to use lowerCamelCase
}
// ... other methods
}
Note: You do not need to read out the theme and pass it to initMap
but it probably will be slightly better to understand what's happening this way.
Stimulus should wait for the promise to resolve before completing the connect method and initMap
will now only run when you have a theme.
Assuming application.js
is loading only just after your Stimulus code, this should happen in less than a 100ms. However, now you have code that does not need to worry about what order things load.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论