Object attached to window object is undefined inside a Stimulus JS controller

huangapple go评论42阅读模式
英文:

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

Object attached to window object is undefined inside a Stimulus JS controller

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.

huangapple
  • 本文由 发表于 2023年6月29日 13:30:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76578275.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定