Using Mustache lambda functions in ERB - ruby-on-rails

I'm trying to share my Mustache templates in Rails across the server and the client on the lines of this Railscast.
All is well, except that I'm unable to figure out where and how to put the definition of a lambda function on the server side.
Let's say my html.erb looks like:
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => #post %>
<% else %>
<script type="text/template" id="template">
<%= render 'template' %>
</script>
<% end %>
The Mustache handler looks like this (exactly as in the Railscast):
module MustacheTemplateHandler
def self.call(template)
if template.locals.include? 'mustache'
"Mustache.render(#{template.source.inspect}, mustache).html_safe"
else
"#{template.source.inspect}.html_safe"
end
end
end
ActionView::Template.register_template_handler(:mustache, MustacheTemplateHandler)
Now for the following template:
<h1>{{title}}</h1>
<div>
{{#marked}}{{content}}{{/marked}}
</div>
the lambda marked is easy to handle in JavaScript, but how can I define it in my Rails code to render content using Redcarpet?
Update
Since posting this, I have tried to expand on the idea of helper functions in the screencast. I now have
<% if params['client_side'].nil? %>
<%= render 'template', :mustache => process(#post) %>
<% else %>
...
The process is defined in ApplicationHelper as
def process(obj)
{
marked: lambda {|text| markdown(Mustache.render(text))}
}
end
This has two problems:
text inside the (Ruby) lambda function is indeed '{{content}}', but Mustache.render(text) fails to do anything with it — it's returning an empty string.
The above code will now only render the marked field and I haven't been able to find a way to retain the other (unprocessed) attributes of the author object (e.g. title). obj.attributes seems like a promising start, but I don't know how to combine the processed response for marked with the other attributes even if #1 above worked.

I got this working myself. The process method in ApplicationHelper now looks like this (using the new lambda syntax):
def process(obj)
obj['marked'] = ->(text) { markdown(Mustache.render(text, obj)) }
obj.attributes
end
This will now catch all invocations of marked in any template.

Related

FormBuilder helper method with block capture not working in ERB

I have a custom FormBuilder that has a group method that acts as a wrapper around each label/input/hint/error combo. The group method itself is very simple, but when I try to use it from ERB like I would with "fields_for" or similar, it does not render properly.
def group(**options, &)
options[:class] = class_names(options[:class] || "flex flex-col mt-4", options.delete(:classes))
content_tag(:div, capture(&), **options)
end
In the ViewComponent that is using the form helper I can do the following just fine
def call
#form.group(**#group) do
concat #form.label(:tags, #label.delete(:text), **#label)
concat #form.text_field(:tags, **#system_arguments)
end
end
But if I try to write that in an ERB partial, it either does not render the wrapper from group at all, or it only renders the text_field and not the label
<%= #form.group(**#group) do %>
<%= #form.label(:tags, #label.delete(:text), **#label) %>
<%= #form.text_field(:tags, **#system_arguments) %>
<% end %>
Not sure what I'm missing to get the ERB version to work properly...
There is a fix, but it's not released yet:
https://github.com/ViewComponent/view_component/pull/1650
# Gemfile
gem "view_component", github: "ViewComponent/view_component"
# config/application.rb
config.view_component.capture_compatibility_patch_enabled = true
https://viewcomponent.org/known_issues.html#compatibility-with-rails-form-helpers

how to passing data with javascript in rails [duplicate]

In Rails 3.1 it is not possible to access controller instance variables in an asset js.erb or coffee.erb file using syntax such as <%= #foo %>, where #foo is set in the controller. So then the question is what are the best ways for passing controller variables to CoffeeScript or JavaScript assets.
This question has kind of been asked in multiple convoluted forms on the forum, but my point in asking it again is to have a place where all recommendations are gathered together, and the code supplied is simple and readable. Also note that I'm specifically referring to assets and not view response files.
a couple of ways I have done this in the past
put the data in hidden fields, access the data in js/coffee
# single value
<%= hidden_field_tag "foo_name", #foo.name, { :id => "foo-name" } %>
$('#foo-name').val();
# when the 'value' has multiple attributes
<%= hidden_field_tag "foo", #foo.id, { :id => "foo", "data-first-name" => #foo.first_name, "data-last-name" => #foo.last_name } %>
$foo = $('#foo')
console.log $foo.val()
console.log $foo.data("firstName")
console.log $foo.data("lastName")
another option: load data into js data structure in erb, access it from js/coffee
<% content_for(:head) do %>
<script>
window.App = window.App || {};
window.App.Data = window.App.Data || {};
window.App.Data.fooList = [
<% #list.each do |foo| %>
<%= foo.to_json %>,
<% end %>
];
</script>
<% end %>
# coffee
for foo in window.App.Data.fooList
console.log "#{foo.id}, #{foo.first_name} #{foo.last_name}"
I am not a big fan of constructing javascript data from ruby in erb like this, something about it just feels wrong - it can be effective though
and another option: make an ajax call and get the data on-demand from the server
I am also interested in other ideas and approaches
There is a really nice rail cast and quite recent (feb. 2012) about this specific topic:
#324 Passing Data to JavaScript
It shows 3 ways: a script tag, a data attribute, and the Gon gem.
I think house covered all the available techniques. I would only mention that using an AJAX call is best used when you have a large volume of data, dynamic data or combination of both.
Rather than use a hidden field I chose to add a data attribute to the container div which jquery can pick up.
<div class="searchResults" data-query="<%= #q %>"></div>
then the jquery to access it
url: "/search/get_results?search[q]=" + $(".searchResults").data("query") + "&page=" + p
I feel this is the cleanest way to pass data to javascript. After having found no way to pass a variable to a coffee script file with the rails asset pipeline from a controller. This is the method I now use. Can't wait till someone does set up the controller way with rails that will be the best.
In the controller:
#foo_attr = { "data-foo-1" => 1, "data-foo-2" => 2 }
In the view (HAML):
#foo{#foo_attr}
In the CoffeeScript asset:
$("#foo").data("foo-1")
$("#foo").data("foo-2")
In situations where your javascript data gets out of hand, using the gon gem is still the preferred way to go in rails, even in 2015. After setting up gon, you are able to pass data to your javascript files by simply assigning the data to the gon object in rails.
(Gemfile)
gem 'gon'
(controller)
def index
gon.products = Product.all
(layouts)
<%= include_gon %>
(public/javascripts/your_js_can_be_here.js)
alert(gon.products[0]['id');
(html source automatically produced)
<script>
window.gon = {};
gon.products = [{"created_at":"2015", "updated_at":"2015, "id":1, "etc":"etc"}];
You can read more verbose implementation details on Gon or the two other rails-javascript channels from Ryan Bate's screencast.
http://railscasts.com/episodes/324-passing-data-to-javascript
You can edit and add variables to the params array in the controller then access them in the response.js.erb. Here's an example with params[:value]:
def vote
value = params[:type] == "up" ? 1 : -1
params[:value] = value
#public_comment = PublicComment.find(params[:id])
have_voted = #public_comment.evaluators_for(:pub_votes_up) << #public_comment.evaluators_for(:pub_votes_down)
unless have_voted.include?(#current_user) # vote
#public_comment.add_or_update_evaluation(:"pub_votes_#{params[:type]}", value, #current_user)
else # unvote
#public_comment.delete_evaluation(:"pub_votes_#{params[:type]}", #current_user)
params[:value] = 0
end
respond_to do |format|
format.js # vote.js.erb
end
end
And here's an example accompanying response.js.erb
button = $('<%= ".pub#{params[:type]}_#{params[:id]}" %>')
label = button.find('strong')
<% comment = PublicComment.find(params[:id]) %>
label.html('<%= comment.reputation_for(:"pub_votes_#{params[:type]}").to_i %>')
<% if params[:value] == 1 %>
button.addClass('btn-success')
<% elsif params[:value] == -1 %>
button.addClass('btn-danger')
<% else %>
if button.hasClass('btn-success') { button.removeClass('btn-success') }
if button.hasClass('btn-danger') { button.removeClass('btn-danger') }
<% end %>

Rails: Refactoring HTML producing helper method to use a partial - how to use blocks there?

After my earlier question Developing helper functions that generate HTML: should I rather use nested content_tag()s or partials? I am convinced now to rewrite some of my more complex HTML generating helper functions to use templates instead of nested content_tag() calls.
So here's my original helper:
def bootstrap_navlist(&block)
classes = ['nav', 'nav-list']
content_tag(:ul, class: classes.join(' ')) do
capture(self, &block)
end
end
And that's what I came up with using a partial now:
def bootstrap_navlist(&block)
render partial: 'bootstrap/navlist'
end
# views/bootstrap/_navlist.html.erb
<ul class="<%= ['nav', 'nav-list'].join(' ') %>">
How do I output the block here??
</ul>
The block looks something like this, but it can be any HTML you like:
= bootstrap_navlist do |navlist|
= navlist.item 'Home', '#'
= navlist.sublist 'Meine Favoriten', '/favorites' do |sublist|
As you can guess, I'm not sure how to output the block in the view. Should I simply capture it in the helper and pass it as a :local variable? Or is there a more common way?
Thanks a lot.
This is a case where the content tags were not deeply nested an would be reasonable as a helper.
Your helper, and partial would look like this:
def bootstrap_navlist(&block)
render template: 'bootstrap/navlist', :locals => { :block => block }
end
<ul class="nav nav-list">
<%= capture(self, &block) %>
</ul>

Rails resource with AJAX

In rails, what kind of AJAX call would need to be made in order to create and modify resources. Say if i had a resource like this
Man(age: integer, country_from:string, residence:string)
Now how would this be made through an AJAX call (like sending post to my create function, how would parameters be sent in, how would my controllers be setup). Be detailed, my AJAX is very, very, very weak. (right now i have my Man made like rails generate scaffold Man age:int country_from:string ...)
PS
Im using rails 3
So I believe there are two sides to this: the javascript and the controller changes.
In your controller you need to ensure it can return json output (or xml or whatever your chosen ajax-y output is):
def man
# do your work
return_data = {}
# initialize your return data
respond_to do |format|
render :json => return_data.to_json, :layout => nil
end
end
There are many ways to generate your json output but basically you have to make sure it's in a shape that is easily consumed on the view javascript.
I use jQuery and here's the code to execute an ajax call:
function foo(some_param) {
$.ajax({
type: 'GET',
url: "/<controller>/man?FOO=" + some_params,
dataType: 'json',
success: handle_success,
error: handle_errors
}
function handle_success(data) {
# process return JSON. it's a javascript object corresponding to the shape
# of your JSON. If your json was a hash server side, it will be an 'object', etc
}
function handle_error(data) {
# handle error cases based upon failure in the infrastructure, not
# failure cases that you encounter due to input, etc.
}
You can tie the foo function to some button or onclick as you desire.
I am not sure this is complete enough. Let me know if you need more detail, etc.
Rails 3 can help by telling the form that you want it to be "remote" (ajax)
<%= form_for #man, :remote=>true do |f| %>
<div class="field">
<%= f.label :man %>
<%= f.text_field :man %>
</div>
<%= f.submit "Save", :disable_with=>"Saving..."%>
<% end %>
in your Controllers
class MansController < ApplicationController
respond_to :js, :html
def update
#man = Man.find(params[:id])
#man.update_attributes(params[:man])
respond_with #man
end
end
Then, you can have
/app/views/mans/update.js.erb
<% if #man.errors.any? %>
alert("This is javascript code that either renders the form in place of the other form");
<% else %>
alert("success!")
<% end %>
Note: Wherever I say "mans" above, it might be "men"

Confused on Advanced Rails Layout Nesting

I currently have a few layouts that my application uses (the ... is identical between all layouts):
# application.html.erb
...
<div id="section"><div class="wrapper"><%= yield %></div></div>
...
# wide.html.erb
...
<div id="section" class="wide"><div class="container-12"><%= yield %></div></div>
...
# thin.html.erb
...
<div id="section" class="thin"><div class="container-06"><%= yield %></div></div>
...
I am looking to re-factor the code to reduce duplication, however the strange placement of the class variables (outside the yield) makes it difficult. Should I be using variables for declaring the class values within my layout (and move to a single layout) or should I be adding container.html.erb layout that contains the duplicate ... then render the three other layouts from it (or does another third option exist that I am missing)? I'm looking for the "Rails" way to do it if possible. Thanks!
module ApplicationHelper
def section_helper(outer_class=nil,inner_class)
content_tag(:div, :class=> outer_class, :id => :section) do
content_tag(:div, :class=> inner_class) do
yield
end
end
end
end
and in the layouts:
<%= section_helper("wrapper") { yield } %>
<%= section_helper("wide","container-12") { yield } %>
<%= section_helper("thin","container-06") { yield } %>
This works nicely for the first case where there is no "outer" class, since :class => nil renders nothing. But you could also pass in a hash if having an optional first argument is confusing.
We've done something like this using an instance variable like #sectionclass. Then we set it to a default in the ApplicationController and flip it to page specific values in other controllers. Then your page would be something like this:
<div id="section" class="<%= #sectionclass %>"><div class="container-12"><%= yield %></div></div>
The nice part of the instance variable is that the nil case fails silently with an empty string. (Albeit some may say this is 'bad').

Resources