Active Scaffold dynamic action link - ruby-on-rails

Maybe someone know of a way to make a dynamic hide/show filter action link that changes on click without writing any javascript or creating partials, just with controller and helpers.
So instead of having two buttons:
config.action_links.add :index, :label=>"Hide", :parameters => {:cancelled => false}, :position => false
config.action_links.add :index, :label=>"Show", :parameters => {}, :position => false
It would be awesome to have a dynamic button that changes on click from hide - show

The important piece is that when you change links, even by using :dynamic_parameters attribute, they won't get re-rendered unless you specify
config.list.refresh_with_header = true
in your configuration. Once you do that, it's just code.
In my case, I have a list of records with optional attached documents. I want to toggle between showing a list of all the records and just the ones with attachments. I have a boolean parameter :with_attachments that is either '0' or '1'.
The action links look like
config.action_links.add :index, label: 'Show All', parameters: { with_attachments: '0' }, position: false
config.action_links.add :index, label: 'Attachment Only', parameters: { with_attachments: '1' }, position: false
config.list.refresh_with_header = true # Refresh action buttons!
The index method applies the hidden tag to the currently selected option.
def index
current = params[:with_attachments] || '0'
active_scaffold_config.action_links.each do |link|
# For :index actions (filtering the list)
if link.action == :index
# hide the one that would refresh with the current parameter value
link.html_options[:hidden] = (link.parameters[:with_attachments] == current)
end
end
super
end
And then there is CSS that makes hidden actions invisible
.active-scaffold-header .actions a[hidden=hidden] {
display: none;
}
You could probably do it with just one button and :dynamic_parameters but since I have icons attached via CSS (omitted for simplicity) that solution wouldn't work for me.

Related

Rails Admin: too many dropdown entries

in my rails_admin application, I have a table "Branch" that references another table called "Alarms". In "Branches", I'd like to select exactly one alarm and have a search box to search these alarms.
Currently, I'm only capable to create a drop down with all alarms related to that branch which causes many entries to be rendered (over 25k) and memory to be consumed.
I couldn't find an example how to create a dropdown that says "too many entries, search to select", similar to the has_and_belongs_to_many.
Here's how it looks:
class Branch < ApplicationRecord
...
# this is fine and works as expected, selecting only the first (and only) entry
show do
field :maintenance_period_trigger_alarm_id do
formatted_value do
alarm = Alarm.where(id: bindings[:object].maintenance_period_trigger_alarm_id).first
path = bindings[:view].show_path(model_name: 'alarm', id: alarm.id)
bindings[:view].link_to(alarm.name, path)
end
end
end
...
# this is where all elements are automatically loaded and rendered in the dropdown
edit do
field :maintenance_period_trigger_alarm_id, :enum do
enum do
Alarm.where(branch_id: bindings[:controller].params[:id]).collect{ |alarm| [alarm.name, alarm.id] }
end
end
end
I'm not sure how I can enable a simple "Too many entries" message to not show 25k entries in there.
#zokkker13, here is a very basic fleshed-out example that will query a people_controller.rb for people by name.
Add select2 to your page
Either download select2.js and select2.css, put them into your vendor folder and add them to your applications.js and application.css files, or just
include them from a CDN into your page. Load your page and use your browser's dev tools to make sure that select2.js is loaded.
Add a new route to config/routes.rb
resources :people do
collection do
get 'search'
end
end
Restart your server.
Add search action to people_controller.rb
# coding: utf-8
class PeopleController < ApplicationController
def search
# https://select2.org/data-sources/ajax
term = params[:term]
matches = Person.where("name ILIKE ?", "%#{term}%").to_a
# https://select2.org/data-sources/formats
matches.map!{ |m| { id: m.id, text: m.name } }
respond_to do |format|
format.json {
render status: 200, json: { results: matches }
}
end
end
end
Add search box to page
Add this to your html page:
<%= select_tag :search, nil, id: 'select2-live-search-people' %>
<script>
$(document).ready( function() {
$('#select2-live-search-people').select2({
ajax: { url: '/people/search', dataType: 'json' },
placeholder: 'Start typing to search...',
width: '300px',
minimumInputLength: 3
});
} );
</script>
.. or this, if you use haml:
= select_tag :search, nil, id: 'select2-live-search-people'
:javascript
$(document).ready( function() {
$('#select2-live-search-people').select2({
ajax: { url: '/people/search', dataType: 'json' },
placeholder: 'Start typing to search...',
width: '300px',
minimumInputLength: 3
});
} );
Modify the code so that it will query an existing model w/ existing data. Then relaod the page and start typing. You should now see the select option getting adjusted to the result of your search term. Use the minimumInputLength parameter to control how many characters are minimally needed to issue an ajax call. With potentially 25k options, you might want to increase it even more.

toggle between "active" and "inactive" records in an index view

I am extremely "green" when t comes to Ruby & Rails, I could do this easily in VB, but I want to learn rails....
I have a simple index view which shows all the clients in my database, and I figured out how to replace the index action in the controller to display only the clients who are marked as "active" (a field in the clients table), but my head is swimming trying to figure out routing and/or control actions for switching between the two or three recordsets (in VB terms).
My ultimate goal would be to have radio buttons on the index view where the user chooses between "active", "inactive" or "all".
Currently I have this in the clients controller...
#clients = Client.find(:all, :conditions => { :active => true })
##clients = Client.find(:all, :conditions => { :active => false })
##clients = Client.all.order(sort_column + ' ' + sort_direction)
If I comment out two of the lines, the remaining one does exactly as I want.
4 specific questions: (1) How do I write a conditional statement to make this switching occur, (2) WHERE should this be implemented, controller? Routing? Elsewhere?, (3) can this be implemented with user selectable radio buttons in the index view?, and (4) how do I add my "order" condition back in. (I tried just daisy-chaining it onto the end, but that doesn't work.)
Thanks in advance,
MDS
You've got a couple of strategies.
1) Load all the records onto the page, give them a class based on their active status, and hide some of them. The radio buttons have javascript attached, which shows or hides records with a particular class.
2) Load one set of records, and make the radio buttons trigger a request to the server to reload the contents of the list, passing through different params, eg "active=true" or "active=false" (when params[:active] isn't present you could load all of them). It's nicer if this is done via ajax but you could have it reload the whole page.
I wouldn't do this via routing since it's just a params thing.
You can have a dropdown in your index view that has all 3 statuses (radio buttons probably aren't that different but I don't have an example right now):
<%= form_tag("/clients", method: "get") do %>
<%= select_tag(:active, options_for_select([['Active', true], ['Inactive', false], ['All', '']], params[:active]), :prompt => 'Status') %>
<%= submit_tag("Search") %>
<% end %>
(Assuming your controller index page is at /clients, if not change the value in the form_tag)
You then add a scope to your Client model that takes an argument, this way you're not cluttering up your controller:
scope :active, lambda { |active| where(:active => active)}
In your controller, you then call the scope with the param value if it's set:
#clients = Client.all.order(sort_column + ' ' + sort_direction)
#clients = #clients.active(params[:active]) unless params[:active].blank?
No need to mess with routing since you're just passing a params value.

Add form in sidebar

In my ActiveAdmin app, I need to have a form in the sidebar (only for the show method) that will show a single (for the moment) datepicker. Once user click on the datepicker, the whole page should reload taking into account the date selected (within the show controller).
Any idea on how to add this simple form in a sidebar ?
EDIT
I tried the following but I'm not sure how the form can target a dedicated controller other than the current model.
sidebar :history, :only => :show do
form do |f|
f.input :start_date, :as => :datepicker
end
end
How can a form in a sidebar can target another controller than the one of the current model ?
I presume you have a show controller and don't mean the show action.
make the view into a partial called "whatever" (I call it this)
So, your whatever.erb.html looks like this
<%= render "whatever" %>
If you use Jquery Ui datepicker, you can add a onSelect function
$(".date").datepicker({
dateFormat: "yy-mm-dd",
onSelect: function() {
$('#range_form').submit();
}
}).attr( 'readOnly' , 'true' )
If you want a range add a form tag in your view with two date fields, else you just add one
<%= form_tag('/range', :id => "range_form", :remote => true) do -%>
<%= text_field_tag 'from', Date.now.strftime("%Y-%m-%d"),:class => "date"%>
<%= text_field_tag 'to', Date.now.strftime("%Y-%m-%d"), :class => "date"%>
<% end %>
For this you'll have to add a route in your routes.rb
match "/range/" => "show#todo_range", :as => :range
In your show controller
def range
time_range = ((Date.parse(params[:from]).midnight..Date.parse(params[:to]).midnight)
#whatever = Whatever.where(:date => time_range)
end
Then add a js view to handle the callback
$(".maindiv").empty().append(<%=j(render #whatever)%>)
I have not tested this exact code, so watch out for typos.
Good luck and comment if I need to edit

active_admin dashboard, update_attribute overrides the same attribute in each class member

I need to set just current order's :released as true
section "Recent orders" do
table_for Order.where(:released => false).all do |t|
t.column("Status") {|order| status_tag (order.released ? "Done" : "Pending"), (order.released ? :ok : :error) }
t.column("User"){|order| link_to order.user.username , admin_user_path(order.user)}
t.column("Created"){|order| order.created_at.to_formatted_s(:short)}
t.column("Price") {|order| order.total_price}
t.column("Actions"){|order| button_tag(:type => 'button') do order.update_attribute(:released, true) end}
end
end
After pressing a button all orders in table set :released as true.
What can i do?
I don't know where you saw you could do that. That's evaluated for each order when rendering. Not when the button is pressed.
Anyway, check this page of the docs to understand how you can create different actions like the one you wan't http://activeadmin.info/docs/8-custom-actions.html#member_actions

acts_as_taggable_on Tags added twice

I have a RoR app that allows users to tag items in their collections. I use the tag-it.js Jquery plugin and use Ajax calls to add and remove the tags in the ItemsController. My problem is that each tag is added twice so that when I do #item.tags.each, all the tags are shown twice.
ItemsController:
def add_tag
#collection = current_user.collections.find(params[:collection_id])
#item = #collection.items.find(params[:id])
#item.tag_list.add(params[:tag])
current_user.tag(#item, :with => #item.tag_list.to_s, :on => :tags)
#item.save
render nothing: true
end
def remove_tag
#item = current_user.items.find_by_id(params[:id])
#item.tag_list.remove(params[:tag])
current_user.tag(#item, :with => #item.tag_list.to_s, :on => :tags)
#item.save
render nothing: true
end
Javascript that handles the AJAX tagging with Tag-it.js:
$('#item_tags').tagit({
onTagAdded: function(event, tag) {
var add_url = $('#item_tags').attr("data-add-url");
$.ajax({
url: add_url,
data: {tag: tag.text().substring(0, tag.text().length-1)},
})
},
onTagRemoved: function(event, tag) {
var remove_url = $('#item_tags').attr("data-remove-url");
$.ajax({
url: remove_url,
type: 'DELETE',
data: {tag: tag.text().substring(0, tag.text().length-1)},
})
},
tagSource: function(search, showChoices) {
var autocomplete_url = $('#item_tags').attr("data-auctocomplete-url");
$.ajax({
url: autocomplete_url,
data: {term: search.term},
success: function(choices) {
showChoices(choices);
}
})
}
});
item#_form view where the user adds / removes tags:
<ul id='item_tags' class='tagit' data-remove-url="<%= remove_tag_collection_item_path %>" data-add-url="<%= add_tag_collection_item_path %>" data-auctocomplete-url="/collections/<%=#collection.id %>/items/autocomplete_tag_name">
<% #item.tags.each do |tag| %>
<li><%= tag.name %></li>
<% end %>
</ul>
I must note that it is necessary to have tag ownership (by current_user) so that the Jquery auto complete only completes based on current user's previous tags and not all users. I think the problem is that I have to add the tag to the tag_list and then add the tag_list to the user item tagging. I can't find a way around this because the current_user.tag() method seems to overwrite the previous item tags when current_user.tag() is called so I have to add the new tag to the previous tags to preserve them.
Additionally when I submit the item#_form, I need to somehow have the update method ignore the tags attribute because it's trying to save them to the item but they're already saved with an AJAX call so I get this error:
ActiveRecord::AssociationTypeMismatch in ItemsController#update
ActsAsTaggableOn::Tag(#82082080) expected, got String(#72294010)
Thanks in advance.
PS. Here is how I got the auto complete working in the ItemsController:
def get_autocomplete_items(parameters)
tags = current_user.owned_tags.named_like(parameters[:term])
end
You were right:
I think the problem is that I have to add the tag to the tag_list and
then add the tag_list to the user item tagging. I can't find a way
around this because the current_user.tag() method seems to overwrite
the previous item tags when current_user.tag() is called so I have to
add the new tag to the previous tags to preserve them.
The .tag method over-writes existing tags with the given list. So if you want to add new tags it seems you need to append your new tags to the existing tags, and then pass in that new list. However, .tag_list.add actually also creates tags.
So, when you were doing this:
#item.tag_list.add(params[:tag])
current_user.tag(#item, :with => #item.tag_list.to_s, :on => :tags)
You were indeed adding the new tag twice.
And when I was doing this:
tag_list = profile.tag_list // oops!
tag_list.add(tags)
self.tag(profile, with: tag_list, on: :tags)
I was creating a reference to the tag_list, and then calling add on it. What we needed to do was this:
tag_list = profile.tags.map(&:name)
To make an array of the tag names for the object we are tagging. Then we can call add on the copy of the list, no problem! No more duplicate tags.
I'm glad I encountered your question, as it led me to an answer that worked for me. However, I would be even more pleased if there was just a nice way of doing this provided by the library. I wasn't able to find a good way just by reading the docs.
You were doing all right, but, just one little mistake.
Here tag_list is getting duplicate tags, one with Owner and other without Owner
Your #item.tag_list.add(params[:tag]) line is adding tags without Owner
And current_user.tag(#item, :with => #item.tag_list.to_s, :on => :tags) line is adding same tag with Owner
And later, when you save the object, since tag_list is behaving as reference to un-ownered tags of object, both get saved
Following code shall work fine.
def add_tag
#collection = current_user.collections.find(params[:collection_id])
#item = #collection.items.find(params[:id])
tag_list = #item.all_tag_list.dup # Reference to original tag_list avoided
# Also instead of "tag_list", use "all_tag_list" method, as "tag_list" only return tags without owner
# Link(go to last line og Tag Ownership topic) : "https://github.com/mbleigh/acts-as-taggable-on#tag-ownership"
tag_list.add(params[:tag])
current_user.tag(#item, :with => tag_list.to_s, :on => :tags)
#item.save
render nothing: true
end
def remove_tag
#item = current_user.items.find_by_id(params[:id])
tag_list = #item.all_tag_list.dup # Reference to original tag_list avoided
tag_list.remove(params[:tag])
current_user.tag(#item, :with => tag_list.to_s, :on => :tags)
#item.save
render nothing: true
end
IMPROVED VERSION
def add_owned_tag
#some_item = Item.find(params[:id])
owned_tag_list = #some_item.all_tag_list - #some_item.tag_list
owned_tag_list += [(params[:tag])]
#tag_owner.tag(#some_item, :with => stringify(owned_tag_list), :on => :tags)
#some_item.save
end
def stringify(tag_list)
tag_list.inject('') { |memo, tag| memo += (tag + ',') }[0..-1]
end
def remove_owned_tag
#some_item = Item.find(params[:id])
owned_tag_list = #some_item.all_tag_list - #some_item.tag_list
owned_tag_list -= [(params[:tag])]
#tag_owner.tag(#some_item, :with => stringify(owned_tag_list), :on => :tags)
#some_item.save
end
Take care with onTagAdded event fired on page load. See events sample here http://aehlke.github.com/tag-it/examples.html
//-------------------------------
// Tag events
//-------------------------------
var eventTags = $('#eventTags');
eventTags.tagit({
availableTags: sampleTags,
onTagRemoved: function(evt, tag) {
console.log(evt);
alert('This tag is being removed: ' + eventTags.tagit('tagLabel', tag));
},
onTagClicked: function(evt, tag) {
console.log(tag);
alert('This tag was clicked: ' + eventTags.tagit('tagLabel', tag));
}
}).tagit('option', 'onTagAdded', function(evt, tag) {
// Add this callbackafter we initialize the widget,
// so that onTagAdded doesn't get called on page load.
alert('This tag is being added: ' + eventTags.tagit('tagLabel', tag));
});

Resources