2

Basing on the Acquire Asset Path from JavaScript I tried to capture the src of my image:

var icon = "1d0"
// ...
// some logic to get the right icon
// ...
var $icon = $("<img />", {
  class: "day-check-icon",
  src: "<%= asset_path('" + icon + ".png') %>"
})

Obviously, it is not going to work, but it gives an idea of what I want to achieve.


How can I dynamically render an image with src pointing to my app/assets/images/, assuming its (images) name is stored in the JavaScript variable?

  • Hm, pass the image path as an argument from rails view by calling the JS? That way you can get rid of ERB inside the JS. – Arup Rakshit Aug 19 '18 at 14:30
  • I have no idea what you mean. Could you please show me code example? –  Aug 19 '18 at 15:20
  • from where you will get the icon names? You said it will be dynamic. So question based on what the names will be changed? – Arup Rakshit Aug 19 '18 at 15:22
  • Based on the call to the openweatherapi and the received response. –  Aug 19 '18 at 15:55
  • Ok, so make a global function, and call the function from received resposne callback functions. You need to show some code, to get help from us to tweak for you. :) – Arup Rakshit Aug 19 '18 at 16:02
  • Do you mean like fetching asset source via AJAX? –  Aug 19 '18 at 16:58
  • yes show that, and also how are you adding the image to dom etc. – Arup Rakshit Aug 19 '18 at 16:59
  • Since only the server knows the asset digestions you have two options. 1) Create a small AJAX handler that converts asset names to digested asset names. 2) If you know what values could be returned by the external source you could provided the asset mappings beforehand. Then if the name is returned from the external resource, fetch the digested asset name from the mapping. I currently don't have the time to answer this with tested examples, but this should at least give you an idea. – 3limin4t0r Aug 19 '18 at 18:39
  • @JohanWentholt consider posting it as an answer, it is pretty much what I end up doing –  Aug 23 '18 at 07:53
  • @Ben I will, but I didn't have the time before. I'll have a look at it this evening. I can't go around posting answers without them working in a dummy environment. – 3limin4t0r Aug 23 '18 at 08:11

1 Answers1

1

Like you said yourself, your example is obviously not going to work. Let me give you a short rundown why before we get into the solutions.

The issue is pretty simple, the server resolves all ERB code of .erb files before sending the file to the client. This means the client will never see the ERB code and cannot interact with it. You can complement your .js or .coffee files with ERB code, but the other way around isn't possible. The other issue you're having has to do with the asset digestions (which is returned on by default). The server knows these digested file paths, but the client doesn't unless the info is provided by the server.

The same goes for external resources (openweatherapi), they also don't know what the digestion of an asset is. So when interacting with an external resource from the JavaScript of your client you'll need some way to convert the returned asset paths by the external resource to the digested asset path.

The simplest solution is turning digestions off, but you can read here why they should normally be turned on.

#1 Provide asset paths up front

Let's say you know that the external API always request a certain assets. Maybe you host the images of the API on your own Rails server so you don't depend on some other server (for assets). Let's assume you store the images in the vendor/assets/images/openweatherapi directory, since they third party. With the following JavaScript you'll provide the client a dictionary to translate normal asset names to digested asset names.

<% # lib/assets/javascripts/openweatherapi_assets.js.erb %>

<% # create a global variable assets if it doesn't exist already %>
var assets = assets || {};
assets.openweatherapi = assets.openweatherapi || {};

<% # go to path vendor/assets/images/openweather/* select all children that %>
<% # are a file and loop through each of them %>
<% Dir['vendor/assets/images/openweatherapi/*'].select(&File.method(:file?)).each do |path| %>
  <% # the asset path doesn't include the (app|lib|vendor)/assets/images/ part %>
  <% asset_path = path.sub('vendor/assets/images/', '') %>
  <% file_name = File.basename(path) %>

<% # create the dictionary %>
assets.openweatherapi['<%= file_name %>'] = '<%= asset_path(asset_path) %>';

<% end %>

Don't forget to include your JavaScript file in application.js, preferably before the inclusion of app JavaScript files so you can use the variable from the get go. Now to fetch the digested asset path from your JavaScript simply search for the path in the dictionary.

var icon = "1d0.png";
var $icon = $("<img />", {
  class: "day-check-icon",
  src: assets.openweatherapi[icon],
});

#2 Make AJAX calls to fetch asset paths

The other options is sending the asset name to the server in an AJAX call and let the server provide you the correct digested asset path. This is done pretty simple by doing the following:

# config/routes.rb
Rails.application.routes.draw do
  resources :assets, only: :index
end

# app/controllers/assets_controller.rb
class AssetsController < ApplicationController
  def index
    @name = params.require(:name)
  end
end

# app/views/assets/index.json.jbuilder
json.name @name
json.path asset_path(@name)

<% # app/assets/javascripts/assets.js.erb %>
<% routes = Rails.application.routes.url_helpers %>
function getAsset(name) {
  return Promise.resolve($.getJSON('<%= routes.assets_path %>', {name: name}));
}

Again, make sure the file is included in application.js preferably before any files that depend on the getAsset function. Then you can do the following:

var icon = "1d0.png";
getAsset(icon).then(icon => {
  var $icon = $("<img />", {
    class: "day-check-icon",
    src: icon.path,
  });
});

Keep in mind you could further optimize the AJAX approach allowing you to fetch multiple icon paths with a single request. But this should give you the general idea. Also remember that AJAX calls add additional nesting and delay that can be avoided with the first approach.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52