include view helpers for view being called from a Service object - ruby-on-rails

I have an object that prebuilds a cached page. I call it like this:
str = ActionView::Base.new(Rails.configuration.paths['app/views'])
.render(partial: 'item_generator/item_with_modal',
locals: { item: #item, format: 'div',
create_external: #create_external,
view_file: #view_file })
How would I make it so that ApplicationHelper would be included so that helpers could be called?
I currently have something like:
<%=raw ApplicationController.helpers.js_clean(item.detail) %>
but would rather just have:
<%=raw js_clean(item.detail) %>

Use extend to include the helpers in the ActionView::Base instance:
str = ActionView::Base.new(Rails.configuration.paths['app/views']).tap do |v|
v.extend(ApplicationController.helpers)
end.render(
partial: 'item_generator/item_with_modal',
locals: {
item: #item,
format: 'div',
create_external: #create_external,
view_file: #view_file
}
)

Related

How to pass a parameter from a partial layout to a partial?

I want to display a form partial within a shared partial layout form_layout
# views/layouts/shared/_form_layout.html.haml
= form_with(model: record, remote: true) do |form|
.fields
= yield
/// other stuff
# views/articles/_form.html.haml
# ?? want to access the form builder here
# views/articles/new.html.haml
= render partial: 'form', layout: 'layouts/shared/form_layout', locals: { record: Article.new }
How can I access the FormBuilder object inside the partial template?
You should try this-
= render layout: "layouts/shared/form_layout", locals: { record: Article.new} do
# block contents..

Rails Locals - Passing Connected Model Data

My model structure is pretty solid. I have MarketingDeliverySystem has_many MarketingSections. MarketingSections has_many MarketingVideos.
I have another segment: GroupDevelopment has_many GroupSections. GroupSections has_many GroupVideos.
I'm trying to use a partial to pass the variables, thus DRYing it all up.
I have the following that I'm trying to pass to the partial:
= render partial: '/sales_presentations/sales_presentation',
locals: { marketing_delivery_system: #marketing_delivery_system,
first_video: first_marketing_video(#marketing_delivery_system),
sales_presentation: #marketing_delivery_system}
Then in the partial I have the following:
.rounded-box-header.blue-bg #{sales_presentation.title}
ul
- sales_presentation.sections.ordered.each_with_index do |section, index|
- list_class = 'section show'
- list_class = 'section hide' if index != 0
li
= link_to section.title, '#', class: 'section', data: { id: section.id }
ul class="#{list_class}" data-section-id="#{section.id}"
- section.videos.ordered.each do |video|
li.video
= link_to video.title, '#',
class: 'video video-link',
data: { video: video.youtube_link,
sales_presentation: sales_presentation.title.parameterize }
.seven.columns
.row
div id="#{sales_presentation.title.parameterize}-container"
video {
id="#{sales_presentation.title.parameterize}-video-player"
class="video-js vjs-default-skin videos"
height=400
poster=""
controls preload='none'
data-default-url="#{first_video(sales_presentation)&.youtube_link}"
I previously had issues with sales_presentation.title at the top until I updated the locals.
My question/issue is how do I pass in through the locals to use for sales_presentation.sections instead to use #marketing_delivery_system.marketing.sections?
I thought I could just put that in through locals:
sales_presentation.sections: #marketing_delivery_system.marketing_sections but I end up with a massive syntax error.
I've also tried creating a partial view for these two and then changed sales_presentation throughout the view to mod. Then changed mod.sections to mod_section and setting that in the locals to mod_section: #marketing_delivery_system.marketing_section. The problem then gets into that I end up needing to hit video later in the iteration. So then that has the same issue.
You misunderstand the meaning of locals in partials.
Says we have
<%= render partial: 'image', locals: {size: #image.size, extension: #image.extension} %>
It means that in image partial now we can use local variable size and extension (keys) as #image.size and #image.extension (values).
Put in locals: {} all local variables you want.
So you can't write in locals sales_presentation.sections: #marketing_delivery_system.marketing.sections
But you can sales_presentation_sections: #marketing_delivery_system.marketing.section
Also you have problem with this code:
locals: { marketing_delivery_system: #marketing_delivery_system,
first_video: first_marketing_video(#marketing_delivery_system),
sales_presentation: #marketing_delivery_system }
marketing_delivery_system and sales_presentation will be with the same value.

Using js.erb in rails

In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.
I've tried to use respond_to in controller as below,
registrations-controller.rb
def new
super
#cities = CS.get(:us,params[:state])
respond_to do |format|
format.js { render '/new.js.erb' }# layout: false }
format.html
end
end
new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
<div class="signup-input-container">
<div class="field">
<%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
</div>
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
</div>
<div class="field">
<%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %>
</div>
</div>
<% end %>
new.js.erb
var city = document.getElementById("city-picker");
while (city.firstChild) city.removeChild(city.firstChild);
var placeholder = document.createElement("option");
placeholder.text = "Choose a city";
placeholder.value = "";
city.appendChild(placeholder);
<% #cities.each do |c| %>
city.options[city.options.length] = new Option('<%= c %>');
<% end %>
main.js
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
$.ajax({
url: "/states?state=" + state.value,
type: "GET"
})
})
I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?
To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.
To setup a separate controller you can do it by:
# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'
# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
# GET
def index
#cities = CS.get(:us, params[:state_id])
respond_to do |f|
f.json { render json: #cities }
end
end
end
I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.
// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks,
$(document).on('change', '#state-picker', function(){
$.getJSON("/states/" + $(this).value() + "/cities", function(data){
// using a fragment avoids updating the DOM for every iteration.
var $frag = $('<select>');
$.each(data, function(city){
$frag.append$('<option>' + data + '</option>');
});
$('#city-picker').empty()
.append($('frag').children('option'));
});
});

simple_form_for: update a part of the content with ajax

I have a form which call a partial:
# form.html.haml
= simple_form_for(resource) do |form|
= form.input :foo
= render partial :some_fields, locals: { form: form }
# _some_fields.html.haml
= form.input :bar
My problem is I have to update the partial with ajax and I don't know how to create the form var from my controller:
def ajax_form_fields
render partial :some_fields, locals: { form: ? }
end
Any idea?
The only way I found to achieve this is to add this line at the beginning of _some_fields.html.haml:
- form ||= nil; simple_form_for(resource) { |f| form ||= f }
So, I don't need to pass the form var to the partial:
def ajax_form_fields
render partial :some_fields
end

Rails: using partials for inputs is correct?

I'm building a Rails app. I've done all the logic (db, controllers, models, etc). Now its time to make it nice.
In order to centralize the view of the app I was thinking in creating partials for the common stuff. For example one partial called common/_text_input.html.erb that will contain
<div class="field">
<%= f.label id %><br />
<%= f.text_field id %>
</div>
This will be called from inside a form using
<%= render partial: "common/text_input", locals: { f: f, id: :name } %>
Is this approach correct? Is there any other option in rails to do this?
If this is the correct way to do this, how can I acchieve this for a form tag, for example (where content is inserted inside it)?
Thanks
1 - There is another option to do this, Helpers and content_tag:
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
Usage:
= form_for #resource do |f|
= text_input(f, :first_name)
= text_input(f, :last_name, input: { style: 'color: red;' }, label: { class: :another_class })
2 - It is correct to do with partials, but it is not as flexible as the Helpers are (see the options hash and the possibility to use another method in specific cases). To handle the form_tag (i.e. no form_builder), you can implement a new method:
# usage
= form_tag root_path, method: :get do
= text_input(nil, :search, input: { value: params[:search] }, label: { content: "Search for something!" })
# helper methods
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
return text_input_tag(attribute, options) if form_builder.blank?
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
def text_input_tag(attribute, options = {})
value = options[:input].try(:delete, :value)
label_content = options[:label].try(:delete, :content)
content_tag :div, options[:div] do
label_tag(attribute, label_content, options[:label]) + content_tag(:br) + text_field_tag(attribute, value, options[:input])
end
end

Resources