Rails5/6:webpacker如何引入JS函数?

Rails 5/6: How to include JS functions with webpacker?

我正在尝试将 Rails 3 应用程序更新为 Rails 6,但由于我的 Javascript 功能无法访问,所以现在默认的 webpacker 出现问题。

我得到:ReferenceError: Can't find variable: functionName 所有 js 函数触发器。

我做的是:

我将 'Hello World from Webpacker' 记录到控制台,但是当我尝试在浏览器中通过 <div id="x" onclick="functionX()"></div> 访问一个简单的 JS 函数时,我收到了引用错误。

我知道asset pipeline已经被webpacker取代了,这对于包含模块来说应该很好,但是我应该如何包含简单的JS函数?我错过了什么?

提前致谢?

来自官方rails应用指南:

Where to Stick Your JavaScript

Whether you use the Rails asset pipeline or add a tag directly to a view, you have to make a choice about where to put any local JavaScript file.

We have a choice of three locations for a local JavaScript file:

The app/assets/javascripts folder,the lib/assets/javascripts folder and the vendor/assets/javascripts folder

Here are guidelines for selecting a location for your scripts:

Use app/assets/javascripts for JavaScript you create for your application.

Use lib/assets/javascripts for scripts that are shared by many applications (but use a gem if you can).

Use vendor/assets/javascripts for copies of jQuery plugins, etc., from other developers. In the simplest case, when all your JavaScript files are in the app/assets/javascripts folder, there’s nothing more you need to do.

Add JavaScript files anywhere else and you will need to understand how to modify a manifest file.

更多阅读: http://railsapps.github.io/rails-javascript-include-external.html

查看 webpacker "packs" js 文件和功能:

/***/ "./app/javascript/dashboard/project.js":
/*! no static exports found */
/***/ (function(module, exports) {

  function myFunction() {...}

因此 webpacker 将这些函数存储在另一个函数中,使它们无法访问。不确定为什么会这样,或者如何正确解决它。

不过,有一个解决方法。您可以:

1) 更改函数签名:

function myFunction() { ... }

至:

window.myFunction = function() { ... }

2) 保持函数签名不变,但您仍然需要添加对它们的引用,如 所示: window.myFunction = myFunction

这将使您的函数可以从 "window" 对象全局访问。

有关从旧资产管道转移到新 webpacker 做事方式的说明,您可以在此处查看:

https://www.calleerlandsson.com/replacing-sprockets-with-webpacker-for-javascript-in-rails-5-2/

这是在 Rails 5.2 中从资产管道迁移到 webpacker 的指南,它让您了解 Rails 6 中的不同之处,因为 webpacker 是javascript。特别是:

Now it’s time to move all of your application JavaScript code from app/assets/javascripts/ to app/javascript/.

To include them in the JavaScript pack, make sure to require them in app/javascript/pack/application.js:

require('your_js_file')

因此,在 app/javascript/hello.js 中创建一个文件,如下所示:

console.log("Hello from hello.js");

然后,在app/javascript/packs/application.js中添加这一行:

require("hello")

(注意不需要扩展名)

现在,您可以在打开浏览器控制台的情况下加载页面,并在控制台中看到 "Hello!" 消息。只需在 app/javascript 目录中添加您需要的任何内容,或者更好地创建子目录以保持您的代码井井有条。


更多信息:

这个问题被诅咒了。以前接受的答案不仅错了,而且错得离谱,而且投票最多的答案仍然差一英里。

上面的 anode84 仍在尝试以旧方式做事,如果您尝试这样做,webpacker 会妨碍您。当你转向 webpacker 时,你必须完全改变你做 javascript 的方式并思考 javascript 。没有"scoping issue"。当您将代码放入 Web 包时,它是 self-contained 并且您使用 import/export 在文件之间共享代码。默认情况下没有什么是全局的。

我明白为什么这令人沮丧。您可能和我一样,习惯于在 javascript 文件中声明一个函数,然后在 HTML 文件中调用它。或者只是在 HTML 文件的末尾添加一些 javascript。我从 1994 年开始从事网络编程(不是打字错误),所以我看到一切都在多次演变。 Javascript进化了。你必须学习新的做事方式。

如果您想向表单或其他内容添加操作,您可以在 app/javascript 中创建一个文件来执行您想要的操作。要向其获取数据,可以使用数据属性、隐藏字段等。如果该字段不存在,则代码不运行。

这是一个您可能会觉得有用的示例。如果表单具有 Google reCAPTCHA 并且用户在提交表单时未选中该框,我将使用它来显示弹出窗口:

// For any form, on submit find out if there's a recaptcha
// field on the form, and if so, make sure the recaptcha
// was completed before submission.
document.addEventListener("turbolinks:load", function() {
  document.querySelectorAll('form').forEach(function(form) {
    form.addEventListener('submit', function(event) {
      const response_field = document.getElementById('g-recaptcha-response');
      // This ensures that the response field is part of the form
      if (response_field && form.compareDocumentPosition(response_field) & 16) {
        if (response_field.value == '') {
          alert("Please verify that you are not a robot.");
          event.preventDefault();
          event.stopPropagation();
          return false;
        }
      }
    });
  });
});

注意这是self-contained。它不依赖任何其他模块,也没有其他依赖它。您只需在您的包中需要它,它就会监视所有表单提交。

这是在加载页面时加载带有 geojson 叠加层的 google 地图的另一个示例:

document.addEventListener("turbolinks:load", function() {
  document.querySelectorAll('.shuttle-route-version-map').forEach(function(map_div) {
    let shuttle_route_version_id = map_div.dataset.shuttleRouteVersionId;
    let geojson_field = document.querySelector(`input[type=hidden][name="geojson[${shuttle_route_version_id}]"]`);

    var map = null;

    let center = {lat: 36.1638726, lng: -86.7742864};
    map = new google.maps.Map(map_div, {
      zoom: 15.18,
      center: center
    });

    map.data.addGeoJson(JSON.parse(geojson_field.value));

    var bounds = new google.maps.LatLngBounds();
    map.data.forEach(function(data_feature) {
      let geom = data_feature.getGeometry();
      geom.forEachLatLng(function(latlng) {
        bounds.extend(latlng);
      });
    });
    map.setCenter(bounds.getCenter());
    map.fitBounds(bounds); 
  });
});

当页面加载时,我寻找带有 class "shuttle-route-version-map" 的 div。对于我找到的每一个,数据属性 "shuttleRouteVersionId" (data-shuttle-route-version-id) 都包含路线的 ID。我已将 geojson 存储在一个隐藏字段中,可以根据该 ID 轻松查询该字段,然后初始化地图,添加 geojson,然后根据该数据设置地图中心和边界。同样,它是 self-contained,除了 Google 地图功能。

您还可以学习如何使用 import/export 来共享代码,这真的很强大。

所以,再显示一个如何使用 import/export。这是一段简单的代码,可以设置 "watcher" 来监视您的位置:

var driver_position_watch_id = null;

export const watch_position = function(logging_callback) {
  var last_timestamp = null;

  function success(pos) {
    if (pos.timestamp != last_timestamp) {
      logging_callback(pos);
    }
    last_timestamp = pos.timestamp;
  }

  function error(err) {
    console.log('Error: ' + err.code + ': ' + err.message);
    if (err.code == 3) {
      // timeout, let's try again in a second
      setTimeout(start_watching, 1000);
    }
  }

  let options = {
    enableHighAccuracy: true,
    timeout: 15000, 
    maximumAge: 14500
  };

  function start_watching() {
    if (driver_position_watch_id) stop_watching_position();
    driver_position_watch_id = navigator.geolocation.watchPosition(success, error, options);
    console.log("Start watching location updates: " + driver_position_watch_id);  
  }

  start_watching();
}

export const stop_watching_position = function() {
  if (driver_position_watch_id) {
    console.log("Stopped watching location updates: " + driver_position_watch_id);
    navigator.geolocation.clearWatch(driver_position_watch_id);
    driver_position_watch_id = null;
  }
}

导出两个函数:"watch_position" 和 "stop_watching_position"。要使用它,您需要将这些函数导入到另一个文件中。

import { watch_position, stop_watching_position } from 'watch_location';

document.addEventListener("turbolinks:load", function() {
  let lat_input = document.getElementById('driver_location_check_latitude');
  let long_input = document.getElementById('driver_location_check_longitude');

  if (lat_input && long_input) {
    watch_position(function(pos) {
      lat_input.value = pos.coords.latitude;
      long_input.value = pos.coords.longitude;
    });
  }
});

当页面加载时,我们寻找名为 "driver_location_check_latitude" 和 "driver_location_check_longitude" 的字段。如果它们存在,我们设置一个带有回调的观察者,当它们发生变化时,回调会用纬度和经度填充这些字段。这就是如何在模块之间共享代码。

所以,这又是一种非常不同的做事方式。如果模块化和组织得当,您的代码会更清晰、更可预测。

这就是未来,所以与它抗争(设定 "window.function_name" 就是与它抗争)会让你一事无成。

替换自定义 java 脚本文件中的代码 来自

function function_name() {// body //}

window.function_name = function() {// body //}