Capybara find element by content - capybara

Can I find an element (of any type) by its content with Capybara?
Ideally I would like to write code like this:
find('Copy from a paragraph') # finds the p element
find('Copy from a link') # finds a element
find('Copy from a button') # finds button element
etc.

If you know the type of the element, you could write:
find('p', text: 'text in paragraph')
find('a', text: 'text in link')
find('button', text: 'text in button')

all might answer this
desired_element = nil
all('p').each do |elem|
if elem.text == 'Copy from a paragraph'
desired_element = elem
end
end
replace 'p' with 'a' or 'input' as needed.
Hope this helps

I don't believe there is a solution that you like. Consider this HTML:
<div>
<p>
<span>hello</span>
</p>
</div>
All three of these elements "contain" the text hello. Every containing element (body, html, etc.) would match. Perhaps you can help us understand why you want to do this?

Found an answer for a very similar question about how to find an element without specifying its type, and it works for me right now. In my case I firstly define a parent selector and then find an element within this selector by text only.
The answer is not final, but it works. All that is necessary on this step is to deal with exact match better than using regexp.
search = find(:css, #{parent_selector}).find(:css, '*', :text => /\A#{element_text}\z/)

Related

Best way to handle data attributes in Slim

I was evaluating Slim as a replacement for HAML in a personal project, and it doesn't appear to handle HTML5 data attributes as gracefully as HAML. I was hoping someone may have also run into this, or may have known about an option/syntax I haven't yet found in their docs.
HAML allows you to define HTML 5 data attributes simply by using nested hashes like so:
%a{data: {key1: 'val', key2: 'val'}}
resulting in
<a data-key1='val' data-key2='val'></a>
There are multiple ways in Slim
As Hash
Attributes which will be hyphenated if a Hash is given (e.g. data={a:1,b:2} will render as data-a="1" data-b="2")
Use it directly as "mu is too short" mentioned, quite intuitive.
a data-title="help" data-content="foo"
Use Ruby code. I often do this and rarely above.
= link_to 'foo', bar_path, data: {a: 'a', b: 'b'}
Use the splat operator:
h1#section-title*{'data-url'=>'test', 'data-id'=>'test'} = #project.name
.your-class*{data: {first_attribute: 'first value', second_attribute: 'second value'} }
Will produce
<div class="your-class" data-first_attribute="first value" data-second_attribute="second value"></div>
I prefer this kind to fix...
#products.each do |product|
.module data-id=product.id
It is working for me

How do I get the HTML 'name' attribute a rails form builder will generate for a certain field?

When you've got a form field such as this:
<%= f.text_field :last_name %>
it will generate this in HTML:
<input id="person_last_name" name="person[last_name]" size="30" type="text" />
I'd like to know if there's any way to get the name attribute (in this case "person[last_name]") that will be generated.
It seems a bit of an odd thing to want to get but I've got my reasons! I also can't be bothered launching into a lengthy explanation too.
After inspecting the form object, I found that you can get the object_name from it.
So this worked well for me: "#{f.object_name}[field_name]"
Which will generate: object[object_attributes][0][field_name]
Well, as expected, the comment you have is quite true :)
The place in the source where this happens is the InstanceTag class to which all the tag generation drills down. The method is called tag_name.
ActionView::Helpers::InstanceTag.new(
ActiveModel::Naming.param_key(#object_in_form_for),
:your_attribute,
:this_param_is_ignored
).send(:tag_name)
Also there is tag_name_with_index attribute which accepts index as first parameter. Also both tag_name and tag_name_with_index have optional parameter multiple = false, which is used for arrays (just adds [] to the end of the generated name).
Finally, there are similar methods if you need id instead of name - tag_id and tag_id_with_index respectively.
Neutrino's answer is great. Just want to add what I found, may not be exactly right. But it worked for me.
Find below method in action_view/helpers/form_tag_helper.rb
def sanitized_object_name
#sanitized_object_name ||= #object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
end
Hope it helps.

rails: get a teaser/excerpt for an article

I have a page that will list news articles. To cut down on the page's length, I only want to display a teaser (the first 200 words / 600 letters of the article) and then display a "more..." link, that, when clicked, will expand the rest of the article in a jQuery/Javascript way. Now, I've all that figured out and even found the following helper method on some paste page, which will make sure, that the news article (string) is not chopped up right in the middle of a word:
def shorten (string, count = 30)
if string.length >= count
shortened = string[0, count]
splitted = shortened.split(/\s/)
words = splitted.length
splitted[0, words-1].join(" ") + ' ...'
else
string
end
end
The problem that I have is that the news article bodies that I get from the DB are formatted HTML. So if I'm unlucky, the above helper will chop up my article string right in the middle of an html tag and insert the "more..." string there (e.g. between ""), which will corrupt my html on the page.
Is there any way around this or is there a plugin out there that I can use to generate excerpts/teasers from an HTML string?
You can use a combination of Sanitize and Truncate.
truncate("And they found that many people were sleeping better.",
:omission => "... (continued)", :length => 15)
# => And they found... (continued)
I'm doing a similar task where I have blog posts and I just want to show a quick excerpt. So in my view I simply do:
sanitize(truncate(blog_post.body, length: 150))
That strips out the HTML tags, gives me the first 150 characters and is handled in the view so it's MVC friendly.
Good luck!
My answer here should do work. The original question (err, asked by me) was about truncating markdown, but I ended up converting the markdown to HTML then truncating that, so it should work.
Of course if your site gets much traffic, you should cache the excerpt (perhaps when the post is created/updated, you could store the excerpt in the database?), this would also mean you could allow the user to modify or enter their own excerpt
Usage:
>> puts "<p><b>Something</p>".truncate_html(5, at_end = "...")
=> <p><b>Someth...</b></p>
..and the code (copied from the other answer):
require 'rexml/parsers/pullparser'
class String
def truncate_html(len = 30, at_end = nil)
p = REXML::Parsers::PullParser.new(self)
tags = []
new_len = len
results = ''
while p.has_next? && new_len > 0
p_e = p.pull
case p_e.event_type
when :start_element
tags.push p_e[0]
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
when :end_element
results << "</#{tags.pop}>"
when :text
results << p_e[0][0..new_len]
new_len -= p_e[0].length
else
results << "<!-- #{p_e.inspect} -->"
end
end
if at_end
results << "..."
end
tags.reverse.each do |tag|
results << "</#{tag}>"
end
results
end
private
def attrs_to_s(attrs)
if attrs.empty?
''
else
' ' + attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ')
end
end
end
Thanks a lot for your answers!
However, in the meantime I stumbled upon the jQuery HTML Truncator plugin, which perfectly fits my purposes and shifts the truncation to the client-side. It doesn't get any easier :-)
you would have to write a more complex parsers if you dont want to split in the middle of html elements. it would have to remember if it is in the middle of a <> block and if its between two tags.
even if you did that, you would still have problems. if some put the whole article into an html element, since the parser couldnt split it anywhere, because of the missing closing tag.
if it is possible at all i would try not to put any tags into the articles or keep it to tags that dont contain anything (no <div> and so on). that way you would only have to check if you are in the middle of a tag which is pretty simple:
def shorten (string, count = 30)
if string.length >= count
shortened = string[0, count]
splitted = shortened.split(/\s/)
words = splitted.length
if(splitted[words-1].include? "<")
splitted[0,words-2].join(" ") + ' ...'
else
splitted[0, words-1].join(" ") + ' ...'
else
string
end
end
I would have sanitized the HTML and extracted the first sentence. Assuming you have an article model, with a 'body' attribute that contains the HTML:
# lib/core_ext/string.rb
class String
def first_sentence
self[/(\A[^.|!|?]+)/, 1]
end
end
# app/models/article.rb
def teaser
HTML::FullSanitizer.new.sanitize(body).first_sentence
end
This would convert "<b>This</b> is an <em>important</em> article! And here is the rest of the article." into "This is an important article".
I solved this using following solution
Install gem 'sanitize'
gem install sanitize
and used following code, here body is text containing html tags.
<%= content_tag :div, Sanitize.clean(truncate(body, length: 200, separator: ' ', omission: "... #{ link_to '(continue)', '#' }"), Sanitize::Config::BASIC).html_safe %>
Gives excerpt with valid html.
I hope it helps somebody.
There is now a gem named HTMLTruncator that takes care of this for you. I've used it to display post excerpts and the like, and it's very robust.
If you are using Active Text, I would suggest first converting the text using to_plain_text.
truncate(sanitize(career.content.body.to_plain_text), length: 150).squish

What is the best way to return multiple tags from a Rails Helper?

I want to create a hidden field and create a link in one helper and then output both to my erb.
<%= my_cool_helper "something", form %>
Should out put the results of
link_to "something", a_path
form.hidden_field "something".tableize, :value => "something"
What would the definition of the helper look like? The details of what link_to and the form.hidden_field don't really matter. What matters is, how do I return the output from two different calls.
There are several ways to do this.
Remember that the existing rails helpers like link_to, etc, just output strings. You can concatenate the strings together and return that (which is what I do most of the time, if things are simple).
EG:
link_to( "something", something_path ) + #NOTE THE PLUS FOR STRING CONCAT
form.hidden_field('something'.tableize, :value=>'something')
If you're doing things which are more complicated, you could just put that code in a partial, and have your helper call render :partial.
If you're doing more complicated stuff than even that, then you may want to look at errtheblog's block_to_partial helper, which is pretty cool
So far the best I have come up with is:
def my_cool_helper(name, form)
out = capture { link_to name, a_path }
out << capture { form.hidden_field name.tableize, value => 'something' }
end
Is there a better way?
Using safe_join.
I typically prefer just concatenating with +, as shown in Orion Edwards's Answer, but here's another option I recently discovered.
safe_join( [
link_to( "something", something_path ),
form.hidden_field( "something".tableize, value: "something" )
] )
It has the advantage of explicitly listing all of the elements and the joining of those elements.
I find that with long elements, the + symbol can get lost at the end of the line. Additionally, if you're concatenating more than a few elements, I find listing them in an Array like this to be more obvious to the next reader.
If you want to buffer other output which apart from string then you can use concat instead of +.
see this - http://thepugautomatic.com/2013/06/helpers/
def output_siblings
div1 = tag.div 'some content'
div2 = tag.div 'other content'
div1 + div2
end
just simplifying some other answers in here.
This worked for me.
def format_paragraphs(text)
text.split(/\r?\n/).sum do |paragraph|
tag.p(paragraph)
end
end

In Rails, What's the Best Way to Get Autocomplete that Shows Names but Uses IDs?

I want to have a text box that the user can type in that shows an Ajax-populated list of my model's names, and then when the user selects one I want the HTML to save the model's ID, and use that when the form is submitted.
I've been poking at the auto_complete plugin that got excised in Rails 2, but it seems to have no inkling that this might be useful. There's a Railscast episode that covers using that plugin, but it doesn't touch on this topic. The comments point out that it could be an issue, and point to model_auto_completer as a possible solution, which seems to work if the viewed items are simple strings, but the inserted text includes lots of junk spaces if (as I would like to do) you include a picture into the list items, despite what the documentation says.
I could probably hack model_auto_completer into shape, and I may still end up doing so, but I am eager to find out if there are better options out there.
I rolled my own. The process is a little convoluted, but...
I just made a text_field on the form with an observer. When you start typing into the text field, the observer sends the search string and the controller returns a list of objects (maximum of 10).
The objects are then sent to render via a partial which fills out the dynamic autocomplete search results. The partial actually populates link_to_remote lines that post back to the controller again. The link_to_remote sends the id of the user selection and then some RJS cleans up the search, fills in the name in the text field, and then places the selected id into a hidden form field.
Phew... I couldn't find a plugin to do this at the time, so I rolled my own, I hope all that makes sense.
I've got a hackneyed fix for the junk spaces from the image. I added a :after_update_element => "trimSelectedItem" to the options hash of the model_auto_completer (that's the first hash of the three given). My trimSelectedItem then finds the appropriate sub-element and uses the contents of that for the element value:
function trimSelectedItem(element, value, hiddenField, modelID) {
var span = value.down('span.display-text')
console.log(span)
var text = span.innerText || span.textContent
console.log(text)
element.value = text
}
However, this then runs afoul of the :allow_free_text option, which by default changes the text back as soon as the text box loses focus if the text inside is not a "valid" item from the list. So I had to turn that off, too, by passing :allow_free_text => true into the options hash (again, the first hash). I'd really rather it remained on, though.
So my current call to create the autocompleter is:
<%= model_auto_completer(
"line_items_info[][name]", "",
"line_items_info[][id]", "",
{:url => formatted_products_path(:js),
:after_update_element => "trimSelectedItem",
:allow_free_text => true},
{:class => 'product-selector'},
{:method => 'GET', :param_name => 'q'}) %>
And the products/index.js.erb is:
<ul class='products'>
<%- for product in #products -%>
<li id="<%= dom_id(product) %>">
<%= image_tag image_product_path(product), :alt => "" %>
<span class='display-text'><%=h product.name %></span>
</li>
<%- end -%>
</ul>

Resources