How to fill an attribute with haml-generated DOM? - haml

Is there a way to render some haml-generated DOM into an element's attribute? A concrete usage example would be a bootstrap tooltip which allows html in its "title"-attribute.
I have tried to define a local variable but this syntax does not work:
!!! 5
%body
- tooltipDom =
%i Some
%strong very complex
%i DOM
%a{:"data-html" => "true", :title => tooltipDom, :"data-toggle" => "tooltip"}
What would be working syntax to get this html into the variable? Or is there another way to achieve this, e.g. rendering a partial inside the attribute somehow?
Please do not just suggest to simply write pure HTML-markup into the attribute. That is clearly not what I am looking for.

Defining the desired Haml in a partial and rendering that into the variable will work fine and do the job:
!!! 5
%body
- tooltipDom = render partial: 'some_partial'
%a{:"data-html" => "true", :title => tooltipDom, :"data-toggle" => "tooltip"}

Related

How can I pass content to a parent template from a child template in Silverstripe

I want to pass some content from a elemental block up to a parent template (to change the page header) - Is this possible? I don't have any example code for what I'm trying because I don't have any idea how to implement it.
This would be similar to the content_for facility in Rails / ERB templates.
Parent:
Page Title <%= yield :title %>
Child Template:
<% content_for :title do %>
<b>, A simple page</b>
<% end %>
Is there anything like this or some other way to do this in SS templates?
Silverstripe Templates are a one-way street - you can't pass values upstream.
What you can do in this case is add a method to the page object to fetch the correct title from the block:
public function getTitleForTemplate()
{
return $this->MyBlock->PageTitle;
}
And then in your template you can simply call that method or as in this example treat it like a property:
Page Title $TitleForTemplate
If you want to use a template to render out this property (e.g. if you have different styling or markup for different blocks which belong to different pages) you can render the template in that method. There are many ways to do this but perhaps the most sensible would be to call renderWith() on the block object:
public function getTitleForTemplate()
{
return $this->MyBlock->renderWith(['type' => 'Includes', 'templates' => ['MyBlockTitle']);
}
and then have a template in your theme's templates/Includes directory called MyBlockTitle.ss which renders out the title with any styling and markup you need for it.
In other words: data belongs in models, the template is just there to display that data. So if you need some specific data to be displayed in the page template, the page object should have that data available.
Disclaimer: I haven't tested this code, it was done from memory. Some of the syntax especially for the second example may be slightly different in reality.
To expand on Guy's answer, to find the block i wanted to use to add to the page title and breadcrumbs, I used the ElementalArea and it's Elements.
public function getTitleForTemplate(){
$output = "" ;
foreach( $this->ElementalArea->Elements() as $element ){
if($element->ClassName == 'MySite\AwardsElement'){
if ( $element->Award() ){
$output .= "{$element->Award()->title } ";
}
}
}
return $output;
}

Escape attribute value / prevent html sanitization in Haml

Here is my Haml:
%a{:href => "/settings", "data-icon" => "⚙"}
Which outputs:
<a data-icon='&#9881;' href='/settings'>Settings</a>
I want to output:
<a data-icon='⚙' href='/settings'>Settings</a>
So I've tried escaping the ampersand:
%a{:href => "/settings", "data-icon" => "\⚙"}
But it seems escapes only work for the first character of a line.
I've also tried different methods of interpolation/escaping with no success:
"#{"⚙"}"
Using plain html (Settings) is a stopgap measure that I do not consider a solution. What if I want to set the data-icon programatically?
Another option is:
%a{:href => "#", "data-icon" => "⚙"} Settings
But what if I want to use PUA characters? It also makes it a lot harder to tell what the character is in the markup.
Demo
You need to set the escape_attrs option to false or :once.
$ haml --no-escape-attrs
%a{:href => "/settings", "data-icon" => "⚙"}
output:
<a data-icon='⚙' href='/settings'></a>
--no-escape-attrs sets the escape_attrs option to false from the command line. See the docs for info on how to set options in other cases.
(It doesn’t look like codepen.io lets you specify Haml options, so I can’t provide a demo there).

URL parameter in the URL itself instead of after ? in Rails

I have the following line in the routes:
match 'area/:area_id/stores', :to => "stores_directory#index", :as => "stores_directory"
I have a form where I can select an area_id as:
<%= form_tag stores_directory_path, :method=>:get do %>
<select id="area_id" name="area_id">
....
</select>
This ads the area_id as a parameter after ?. So, my question is: How would I make it to add it in between area and stores?
I see one solution (not the best maybe, but also quite acceptable). It is possible to write your own middleware to handle this request, but this is the wrong way I believe (in this case).
Instead, use JS redirect
This could be written as a method (example using JQuery):
$('#area_id option').click(function() {
if (this.value) { // check value is not empty
document.location.href = Routes.stores_directory_path({area_id: this.value})
}
});
OR using options_for_select:
options = []
areas.each do |a|
options << [a.to_s, a.id, {:onclick => "document.location.href = Routes.stores_directory_path({area_id: #{a.id}})"}]
end
options_for_select(options)
Here I used js-routes gem for nice url's.
Hope this will be helpful.

newbie: render different view and layout based on condition loose #object access

I am trying to render a view + layout based on a condition. The following code seems to work but loses access to #objects set upfront. I call this code in a ProfilesController method Show.
#profiles = Profile.where(:user_id => current_user.id).first
if #profile.nil? == true
render :view => "show",
:layout => "application"
else
render :template => "profiles/my_profile",
:layout => "profiles"
end
Gives output:
undefined method `profiles' for nil:NilClass
How could one render based on a condition and still preserve the previous set #objects (in this case the access to #profiles)
Setting layout in view is not the way to go, its not the rails way and was not advised so even if possible. I changed setting layout in controller action and render the right code in my show partial wich complete solved the issue.

Have I coded myself into a corner with this custom route?

I don't even know how to write a proper title for this. I kind of cobbled together some routing code based on a bunch of different articles, and now I'm wondering if I've painted myself into a corner.
I've got a NewsArticle model, and I want the links to look like this:
/news # List of all articles
/news/2011 # List of articles published this year
/news/2011/06 # List of articles published this month
/news/2011/06/28 # List of articles published on this day
/news/2011/06/28/my-post-title # Actual article
Ok, going against the Rails way already, but so be it.
I've got routes setup like this:
controller :news_articles, :via => [:get] do
match '/news(/:year/(/:month(/:day)))' => :index, :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }
match '/news/:year/:month/:day/:id' => :show
end
Note there is no :as declaration. That's because when I do add something like :as => "news_archive" then I end up with news_archive_path which returns something stupid like "/news?year=2010&month=4". So, I excluded that bit and wrote my own path methods in my application helper file:
def news_archive_path(year = nil, month = nil, day = nil)
return "/news" if year.nil?
t = Time.zone.local(year.to_i, month.nil? ? nil : month.to_i, day.nil? ? nil : day.to_i)
if month.nil?
"/news/#{t.year}"
elsif day.nil?
"/news/#{t.year}/#{"%02d" % t.month}"
else
"/news/#{t.year}/#{"%02d" % t.month}/#{"%02d" % t.day}"
end
end
def news_article_path(article)
t = article.published_at.in_time_zone
"#{news_archive_path(t.year, t.month, t.day)}/#{article.friendly_id}"
end
Great, this all works in practice. But now I've run into a problem where I'm testing my controllers and I want to make sure that the right links appear on the rendered templates. (Oh yeah, I'm not keeping separate view tests but instead using render_views in my controller tests.) But the tests are failing with the error undefined methodnews_article_path' for #`.
So, have I just approached this all wrong and painted myself into a corner? Or can I get out of this by somehow including the helper methods in the controller test? Or do I just suck it up for the sake of getting the test to pass and hardcode the links as I expect them to be?
To make news_article_path available you need to do:
class MyClass
include Rails.application.routes.url_helpers
end
Now MyClass#news_article_path will be defined. You can try to include the url_helpers into your test case. I've seen that break recently with "stack level too deep" errors (it creates an infinite recursion of "super" calls in initialize). If it doesn't work, create your own view helpers class and do "MyViewHelper.new.news_article_path" etc.
It's probably not a good idea to be generating the paths by concatenating string in your path helpers. Instead, simply use the url_for() method. Eg:
def news_article_path(article, options = {})
url_for(options.merge(:controller => :articles,
:action => :show,
:id => article.id,
:year => article.year,
:month => article.month,
:day => article.day))
end
(This assumes day, month, year are added as quick methods on your model, eg:
def year
self.published_at.in_time_zone.year
end
For your tests, either include the helper methods via something along these lines: My helper methods in controller - or use the url_for method again.
...That said - as you suggested, testing views within controller tests isn't ideal. :)