rails: change image on click when image url is dynamic - ruby-on-rails

I use Rails 4 and have a page with one large image and 6 thumbnails.
When a user clicks on one of the thumbnails then the large image should change to the one of the clicked thumbnails.
in my app/views/products/show.html.erb:
<div class="product-teaser-column">
<%= image_tag product_image_url(#product, type: :three_quarter, color: colors.first), itemprop: "image", class: 'teaser' %>
</div>
<div class="product-thumbnail-column">
<ul class="product-thumbnails" class "thumbnails inline">
<% type_map.except(:thumb, :reversiblefront).each_with_index do |type, index| %>
<li class='thumbnail'>
<%= image_tag product_image_url(#product, type: type, color: colors.first), itemprop: "image", class: 'thumbnail' %>
</li>
<% end %>
</ul>
</div>
I used to have image files in app/assets/images and implemented this feature with jQuery.
In app/assets/javascripts/application.js:
// change teaserimage on thumbnail-click
$(function() {
var images = [
"/assets/Nile_1_16_0119.jpg", "/assets/Nile_1_16_0095.jpg",
"/assets/Nile_1_16_0131.jpg", "/assets/Nile_1_16_0144.jpg",
"/assets/thumb_midnight.jpg", "/assets/back_midnight.jpg"
];
$(".thumbnail").click(function() {
var index = $(this).parent().index();
$('img.teaser').attr('src', images[index]);
$(this).parent().siblings().removeClass('active');
$(this).parent().addClass('active');
});
});
How can I achieve that when the image Urls are dynamic?
app/helpers/products_helpers.rb:
module ProductsHelper
def product_image_url(product, type:, color:)
source = URI.escape "#{product_image_base_url}/#{collection_folder(product)}/"\
"#{product_number(product)}_#{product_type(type)}"\
"_#{format_color_name(color.name)}.jpg"
image_exist?(source, product) ? source : false
end
def product_image_base_url
ENV['PRODUCT_IMAGE_BASE_URL']
end
def product_number(product)
product.number.strip.upcase
end
def product_type(type)
type_map.fetch(type)
end
def type_map
#type_map ||= {
full: "01",
three_quarter: "02",
medium: "03",
close_up: "04",
front: :front,
back: :back,
thumb: :thumb,
reversiblefront: :reversiblefront
}
end
end

Like Hardik suggested i could just replace in my js-code the images array with the thumbnail image urls:
var productImages = $("img.thumbnail").map(function() {
return this.src;
}).get();
I had misleadingly something with precompiled assets in mind when i was asking. Thanks Hardik for guiding me to the right direction!

Related

Breadcrumbs on Rails with Tailwind

I'm trying to write a custom builder to use with Breadcrumbs on Rails. I am using Tailwind for my styles but it doesn't seem to play nicely with Rails-generated code.
I have the following builder:
class TailwindBreadcrumbsBuilder < BreadcrumbsOnRails::Breadcrumbs::Builder
def render
#context.content_tag(:nav, class: 'flex py-3 px-5 text-slate-700 bg-slate-50 rounded-lg border border-slate-200', aria: { label: 'Breadcrumb' }) do
#context.content_tag(:ol, class: 'inline-flex items-center space-x-1 md:space-x-3') do
#elements.collect do |element|
render_element(element)
end.join.html_safe
end
end
end
def render_element(element)
current = #context.current_page?(compute_path(element))
aria = current ? { aria: { current: 'page' } } : {}
#context.content_tag(:li, aria) do
#context.content_tag(:div, class: 'flex items-center') do
link_or_text = #context.link_to_unless_current(compute_name(element), compute_path(element), element.options.merge(class: 'ml-1 text-sm font-medium text-slate-700 hover:text-white md:ml-2'))
divider = #context.content_tag(:span, (#options[:separator] || '>').html_safe, class: 'divider') unless current
link_or_text + (divider || '')
end
end
end
end
and I initialize the breadcrumbs on the page with:
<%= render_breadcrumbs builder: ::TailwindBreadcrumbsBuilder %>
However, not all styles are being applied (for example, the white text on hover does not work).
I suspect the Tailwind server doesn't compile these classes since they are Ruby-generated. Any idea how I can get this builder to work with Tailwind?
Thanks in advance
If you are using Tailwind v3, the classes are "purged" by default.
Since this is a ruby helper, I'd assume that this particular file was not added to the content list in tailwind.config.js.
Perhaps try adding something like this your config file:
module.exports = {
content: [
"./app/views/**/*.html.erb",
"./app/helpers/**/*.rb",
"./app/javascript/**/*.js",
"path/to/your/file.rb"
],
// ... your other configs
}
Hope that helps!

Preview multiple images right after selection with stimulus javascript (ruby on rails)

I am dealing with a rails project where I am trying to upload multiple images and have them previewed right after image selection. I am using stimulus, ruby on rails, js6
The HTML is a form used to create a new product - I am also using cloudinary - to upload multiple pictures and simpleformfor
app/views/products/_form.html.erb
<%= simple_form_for product do |m| %>
<div class="d-flex justify-content-between" data-controller="upload">
<%= m.input :photos, as: :file, input_html: { multiple: true, class: 'hidden', id: 'photo-input',
data: {action: 'change->upload#displayPreview'} },
label_html: { class: 'upload-photo'}, label: ':camera: Upload a photo' %>
<% if #product.photos.attached? %>
<% #product.photos.each do |photo| %>
<%= cl_image_tag photo.key, height: 100, width: 200, crop: :fill, data: { target: 'upload.image'} %>
<% end %>
<% end %>
</div>
<%= m.button :submit, class: 'btn-primary' %>
<% end %>
the HTML form code for 1 image is as follow
<div data-controller="upload">
<label class="file optional upload-photo" for="photo-input">Upload photo</label>
<input class="form-control-file file optional hidden" id="photo-input" data-action="change->upload#displayPreview" type="file" name="product[photos][]">
<%= cl_image_tag "", height: 100, width: 200, crop: :fill, data: { 'upload-target': 'image', 'upload-index-value': 0 } %>
</div>
</div>
The code for 1 image is as follow and working fine if the form was just for 1 image, but how would i transform the following code to accommodate for multiple image uploading?
I tried several things using for loops and this.imageTargets.forEach((element) => {.. and even indexes but at no avail..
javascript/controllers/upload_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = ['image']
displayPreview(event) {
const input = event.target
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = (event) => {
this.imageTarget.src = event.currentTarget.result;
}
reader.readAsDataURL(input.files[0])
this.imageTarget.classList.remove('hidden');
}
}
}
I appreciate your comments and alternative solutions..
For file fields with multiple images, you need to use the for loop in your JS which will detect the input files length. For your reference you can update your js as like.
image_controller.js
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
static targets = ["input"]
preview() {
var input = this.inputTarget
var files = input.files
var imgLoc = document.getElementById("Img")
for (var i = 0; i < files.length; i++) {
let reader = new FileReader()
reader.onload = function() {
let image = document.createElement("img")
imgLoc.appendChild(image)
image.style.height = '100px'
image.src = reader.result
}
reader.readAsDataURL(files[i])
}
}
}
As you are displaying the output by replacing the preview.png, for this you don't need the image preview. This will create images.
_form.html.erb
<div class="mb-3" data-controller="image" id = "Img">
<%=f.label :images, class: "form-label" %>
<%= f.file_field :images, multiple: true, class: "form-control", accept: "image/png, image/jpeg, image/jpg", "data-image-target": "input", "data-action": "image#preview" %>
</div>

How to save using the Mercury editor

I want to get the save function in the Mercury editor working but to no avail.
I have a model to save the page, title and content.
mercury.js:
$(window).bind('mercury:ready', function() {
var link = $('#mercury_iframe').contents().find('#edit_link');
Mercury.saveURL = link.data('save-url');
link.hide();
});
$(window).bind('mercury:saved', function() {
window.location = window.location.href.replace(/\/editor\//i, '/');
});
static_pages_controller.rb:
def update
#static_page = StaticPage.find(params[:id])
#static_page.page = params[:page]
#static_page.title = params[:content][:aboutContainer][:value][:about_title][:value]
#static_page.content = params[:content][:aboutContainer][:value][:about_content][:value]
#static_page.save!
render plain: ''
end
about.html.erb:
<% provide(:title, 'About') %>
<div class="container" id="aboutContainer" data-mercury="full">
<h1 id="about_title"><%= raw #static_page.title %></h1>
<div class="col-sm-12">
<p id="description about_content"><%= raw #static_page.content %></p>
</div>
<p><%= link_to "Edit Page", "/editor" + request.path, id: "edit_link",
data: {save_url: static_page_update_path(#static_page)} %></p>
</div>
Ok, so i basically realised that I needed a show action so I can grab records from the model and save to the #static_page object
I was following this guide: http://railscasts.com/episodes/296-mercury-editor?autoplay=true
Please note I had to change my routes to using those in the link (or similar routes to them) and had to place them before the default mercury routes and had to change:
#static_page.title = params[:content][:aboutContainer][:value][:about_title][:value]
#static_page.content = params[:content][:aboutContainer][:value][:about_content][:value]
to:
#static_page.title = params[:content][:about_title][:value]
#static_page.content = params[:content][:about_content][:value]
I then removed the class 'container' div in about.html.erb and moved all the code to show.html.erb not needing about.html.erb.

Rails carousel producing 'undefined method `image_path' in controller

I'm trying to make a carousel in a Rails app and can't seem to get around a problem I'm having. I'm new to Rails - so it's probably a simple resolution. This produces a 'undefined method `image_path' in my controller. Thanks in advance for any help!
Here is my controller:
def golf
images = ['CreekCoverLarge.jpg', 'CreekWindmillLarge.jpg', 'CreekExtraLarge.jpg', 'CreekExtra2Large.jpg']
#paths = images.map {|name| image_path name}
end
And this is my view:
<div class="item active">
<%= #paths.each do |path| %>
<%= link_to(image_tag(path, class: "img-responsive", size: "600x440"), "#", data: {toggle: "modal", target: "#myModal"}, "onclick"=>"getImage(this)", "data-img-address" => path) %>
<% end %>
</div>
use:
ActionController::Base.helpers.image_path
Although, cant you just loop through images in the view and call image_path there?
Edit:
Example as requested:
Controller:
def golf
#images = ['CreekCoverLarge.jpg', 'CreekWindmillLarge.jpg', 'CreekExtraLarge.jpg', 'CreekExtra2Large.jpg']
end
View:
<div class="item active">
<%= #images.each do |path| %>
<%= link_to(image_path(path, class: "img-responsive", size: "600x440"), "#", data: {toggle: "modal", target: "#myModal"}, "onclick"=>"getImage(this)", "data-img-address" => path) %>
<% end %>
</div>
This is untested, but should work.

Only get information in increments for Rails loop

Given the code below:
var photos = [
<% current_app_user.photos.each do |photo| %>
{ url: "<%= photo.thumbnail_uri(400, 189) %>", title: "<%= truncate(photo.name, 65) %>", path :"<%= photo_path(photo) %>" },
<% end %>
];
How would I modify it to only pull 10 photos at a time?
Initially, I'd like to generate 10, then with the click of a button append 10 more and so on.
You should do this in a controller, not in your view, but inside a controller you should do it like this:
#photos = current_app_user.photos.all(:limit => 10).map do |p|
{ :url => p.thumbnail_uri(400, 189),
:title => p.name,
:path => photo_path(p) }
end
And in your view:
var photos = <%= #photos.to_json %>;
No code in your view other than what's strictly necessary :)
If you only want 10 results you could simply use the limit method:
current_app_user.photos.limit(10).each { |photo| # etc... }
If you need to get all the photos but chunk them into blocks of 10 and you're using jQuery you could this code that I created that adds a each_slice function to the core jQuery object. It might be useful in this situation.

Resources