Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:

jobs:
test:
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }}
name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / ${{ matrix.layout }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand All @@ -19,17 +19,22 @@ jobs:
ruby: ['3.3', '3.4', '4.0']
# Rails 7.2 went eol 2026-08-09; 8.2 is not released yet.
rails: ['8.0.0', '8.1.0']
# Every activeadmin 3.2+ resolves and passes against Rails 8.0 and 8.1,
# so no exclude rules are needed.
activeadmin: ['3.2.0', '3.3.0', '3.4.0', '3.5.0']
# Stock ActiveAdmin 4, and ActiveAdmin 4 with active_admin_theme,
# which wraps the filters sidebar in a panel.
layout: ['stock', 'theme']
include:
- layout: stock
gemfile: Gemfile
- layout: theme
gemfile: gemfiles/active_admin_theme.gemfile
env:
RAILS: ${{ matrix.rails }}
AA: ${{ matrix.activeadmin }}
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run tests
run: bundle exec rspec spec
run: bundle exec rspec
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/.bundle/
/bin/
/Gemfile.lock
/gemfiles/*.lock
/pkg/
/tmp/
spec/rails/rails-*
/coverage
/src/
/node_modules/
/spec/dummy/tmp/
/spec/dummy/log/
/spec/dummy/app/assets/builds/*
!/spec/dummy/app/assets/builds/.keep
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require spec_helper
13 changes: 7 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ source 'https://rubygems.org'
gemspec

default_rails_version = '8.1.0'
default_activeadmin_version = '3.5.0'
default_activeadmin_version = '4.0.0.beta23'

gem 'rails', "~> #{ENV['RAILS'] || default_rails_version}"
gem 'activeadmin', "~> #{ENV['AA'] || default_activeadmin_version}"
gem 'sprockets-rails'
gem 'sass-rails'
gem 'activeadmin', ENV['AA'] || default_activeadmin_version

group :test do
gem 'simplecov', require: false
gem 'rspec-rails'
gem 'sqlite3', '~> 2.0'
gem 'database_cleaner'
gem 'propshaft'
gem 'importmap-rails'
# Builds the dummy app's ActiveAdmin stylesheet with the plugin CSS.
gem 'tailwindcss-ruby', '~> 4.0'
gem 'capybara'
gem 'cuprite'
gem 'webrick', require: false
gem 'puma'
end
229 changes: 110 additions & 119 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,174 +4,165 @@

# ActiveAdminFiltersVisibility

ActiveAdmin plugin allows to hide any filter from Filters Sidebar.
Useful when page has many filters, but admin user needs only some of them.
Every filter saves its state using browser's LocalStorage.
Lets an admin hide filters in the ActiveAdmin filters sidebar, and optionally reorder them. Useful when a page has many filters and each admin needs only some of them. The choice is saved per page in the browser's localStorage.

![Demonstration](https://raw.githubusercontent.com/activeadmin-plugins/active_admin_filters_visibility/master/screen/example_aa_filters_visibility.gif "Visibility example")
![Filters visibility panel](https://raw.githubusercontent.com/activeadmin-plugins/active_admin_filters_visibility/master/screen/filters_visibility.png "Stock ActiveAdmin 4")
![Filters visibility panel with active_admin_theme, dark mode](https://raw.githubusercontent.com/activeadmin-plugins/active_admin_filters_visibility/master/screen/filters_visibility_theme_dark.png "active_admin_theme 4, dark mode")

Also you can use drag&drop to change filters order
Version 2 supports ActiveAdmin 4 only. For ActiveAdmin 3, use version 1.x. See [Upgrading from 1.x](#upgrading-from-1x).

![Demonstration](https://raw.githubusercontent.com/activeadmin-plugins/active_admin_filters_visibility/master/screen/example_aa_filters_ordering.gif "Ordering example")
It works with stock ActiveAdmin 4 and with [active_admin_theme](https://github.com/activeadmin-plugins/active_admin_theme) 4, which wraps the filters sidebar in a panel: the toggle goes into the panel header.

## Install

In Gemfile add

```ruby
gem 'active_admin_filters_visibility', git: 'https://github.com/activeadmin-plugins/active_admin_filters_visibility'
```

##### Using assets via Sprockets
in the
```
app/assets/javascript/active_admin.coffee
gem 'active_admin_filters_visibility'
```

and
### JavaScript

```coffeescript
#= require active_admin_filters_visibility
```
The gem pins the `active_admin_filters_visibility` module into `ActiveAdmin.importmap`, the import map ActiveAdmin pages render. ActiveAdmin pages then need to import it. Pick the route that matches your app:

##### Using assets via Webpacker (or any other assets bundler) as a NPM module (Yarn package)
- **ActiveAdmin with the `javascript_modules` setting** ([activeadmin/activeadmin#9148](https://github.com/activeadmin/activeadmin/pull/9148)): nothing to do. The gem adds itself to that setting.
- **active_admin_theme 4**: add the module to the theme's list in an initializer:

Execute:
```ruby
ActiveAdminTheme.javascripts += %w[active_admin_filters_visibility]
```

$ npm i @activeadmin-plugins/active_admin_filters_visibility
- **Otherwise**: copy `app/views/active_admin/_html_head.html.erb` from ActiveAdmin into your app and add this line after `javascript_importmap_tags`:

Or
```erb
<%= javascript_import_module_tag "active_admin_filters_visibility" %>
```

$ yarn add @activeadmin-plugins/active_admin_filters_visibility
**jsbundling hosts** install the npm package and import it from the ActiveAdmin entry file:

Or add manually to `package.json`:
```sh
npm install @activeadmin-plugins/active_admin_filters_visibility
```

```json
"dependencies": {
"@activeadmin-plugins/active_admin_filters_visibility": "1.2.0"
}
```javascript
import "@activeadmin-plugins/active_admin_filters_visibility"
```
and execute:

$ yarn
The module sets itself up on `#filters_sidebar_section` when the page loads.

Add the following line into `app/assets/javascripts/active_admin.js`:
### CSS

```javascript
import '@activeadmin-plugins/active_admin_filters_visibility';
```
The styles are plain CSS. Import them into the Tailwind entry file that builds ActiveAdmin's stylesheet (usually `app/assets/stylesheets/active_admin.css`), after `@import "tailwindcss"`.

##### Initialization:
From the npm package:

```javascript
$(document).ready(function() {
$('#filters_sidebar_section').activeAdminFiltersVisibility();
});
```css
@import "@activeadmin-plugins/active_admin_filters_visibility/css";
```

## Customization
Or from the installed gem. Its path differs between machines, so write it into a gitignored file before each CSS build instead of committing it:

```coffeescript
$('.jquery-selector').activeAdminFiltersVisibility(options)
```sh
echo "@import \"$(bundle show active_admin_filters_visibility)/app/assets/stylesheets/active_admin_filters_visibility.css\";" > app/assets/stylesheets/active_admin_filters_visibility.generated.css
```

ActiveAdminFiltersVisibility is a standard jQuery Plugin, and accepts some "options" as a hash.
Default is:

```javascript
{
sidebarUniqId: function() {
return window.location.pathname;
},
icon: '&#9782;',
iconClass: '',
iconStyle: '',
skipDefaultCss: false,
title: 'Visibility:',
ordering: false,
orderHint: 'Drag&Drop to reorder filters',
resetButtonTitle: 'Reset'
}
```css
@import "./active_admin_filters_visibility.generated.css";
```

You can change icon - this is a HTML text or symbol. You can pass empty string and customize it with your CSS.
Or you can set class("iconClass") for icon or inline styles("iconStyle").
Dark mode follows the `.dark` class on `<html>`, as ActiveAdmin 4 does. With active_admin_theme the colors follow the theme's `--aat-*` custom properties.

This plugin has minimal CSS styling.
In case you want to use custom CSS, default styling can be ignored:
set ```skipDefaultCss``` to ```true```
## Configuration

Change `defaults` from a module that ActiveAdmin pages import. The plugin sets itself up on `DOMContentLoaded`, after every module has run, so the new values apply:

### Ordering
```javascript
import { defaults } from "active_admin_filters_visibility"

By default ordering is disabled. You can turn it: set option ```ordering``` to ```true```.
defaults.ordering = true
defaults.title = "Filtres visibles"
```

| Option | Default | Description |
| --- | --- | --- |
| `autoInit` | `true` | Set up the filters sidebar on page load. Set to `false` to call `setup()` yourself. |
| `selector` | `"#filters_sidebar_section"` | The sidebar section to set up on page load. |
| `storageKey` | `() => window.location.pathname` | The key the state is saved under. One key per page by default. |
| `ordering` | `false` | Let admins reorder filters by drag and drop. |
| `icon` | sliders SVG | HTML for the toggle button. |
| `buttonLabel` | `"Choose visible filters"` | The toggle's `aria-label` and tooltip. |
| `title` | `"Visible filters"` | The panel title. |
| `resetLabel` | `"Reset"` | The reset button text. |
| `orderHint` | `"Drag filters to reorder them"` | The hint shown when ordering is on. |

### Texts
Change text in options: ```title```, ```orderHint``` and ```resetButtonTitle```
To set up a section by hand, pass options for that call only:

```javascript
import { defaults, setup } from "active_admin_filters_visibility"

### Dont hide filters which has selected value
defaults.autoInit = false
document.addEventListener("DOMContentLoaded", () => {
setup(document.getElementById("filters_sidebar_section"), { ordering: true })
})
```

### Filters with a value stay visible

If filter has selected value - that filter will not be hidden on page reload. Even if "visibility checkbox" is unchecked.
This was made for preventing unpredictable filtering: for example when user came to page by some link with predefined filtering params.
All default filters types(string, numeric, date range, etc.) are implemented.
But if you have some specific filter, you need to add custom handler.
A hidden filter that has a value when the page loads stays visible, for example when an admin follows a link with filter params. Otherwise the list would be filtered by something the admin cannot see.

For example you have customized filter by ID.
And you want to consider "1" as default value and allow hiding it.
In this case you should add uniq class to wrapper.
The plugin detects a value by the filter's type, which is a class on the filter wrapper (`select`, `string`, `date_range`, ...). Select and boolean filters count a selected option with a value. Every other type counts a filled text field or a checked box. To change that for one filter, give its wrapper a class and register a detector for it. A detector you register wins over the built-in ones, whatever the class order:

```ruby
filter :id, wrapper_html: { class: 'filter_customized_id' }
filter :id, wrapper_html: { class: 'customized-id' }
```

```javascript
// return TRUE if this filter has value
// elWrapper is jQuery element "div.filter_form_field.filter_customized_id"
// if not "1" then consider this filter as active, and return TRUE to prevent hiding
$.fn.activeAdminFiltersVisibility.registerFilterType('customized_id', function(elWrapper) {
var inputValue = elWrapper.find('input').val();
return inputValue != '1';
});
```
import { registerFilterType } from "active_admin_filters_visibility"

// Return true when the filter has a value. "1" counts as empty here.
registerFilterType("customized-id", (wrapper) => {
const value = wrapper.querySelector("input:not([type=hidden])").value
return value !== "" && value !== "1"
})
```

### Saving state

Plugin saves list of hidden filters in LocalStorage, using jQuery plugin "Lockr" https://github.com/tsironis/lockr
If you need to save this in cookies or user profile, you should write your own implementation - rewrite "$.fn.activeAdminFiltersVisibility.storage".
For example:
The state is saved in localStorage under `active_admin_filters_visibility:<storageKey>`, as JSON: `{"hidden": ["q_name_input"], "order": [...]}`. Filters are identified by the id of their wrapper (`q_<attribute>_input`). To save it elsewhere, replace the `storage` methods:

```javascript
$.fn.activeAdminFiltersVisibility.storage = function(storageUniqId) {
// initialize storage with "storageUniqId"
// every page(sidebar filters) must has its uniq storageUniqId
var myStorage = new MyCustomizedStorage(storageUniqId);

return {
add: function(labelText) {
// add hidden filter "labelText" to storage myStorage
myStorage.add(labelText);
},
remove: function(labelText) {
// drop hidden filter "labelText" to storage myStorage
// makes filter visible again
myStorage.remove(labelText);
},
has: function(labelText) {
// check if labelText already hidden(in storage)
// should return true if exists
return myStorage.find(labelText) ? true : false;
},
all: function() {
// return array of hidden labels(filters)
return myStorage.getAll(); // ['Id', 'Name', 'Created At']
},
any: function() {
// check if current Sidebar Filter has some hidden elements
return this.all().length > 0;
}
}
};
import { storage } from "active_admin_filters_visibility"

storage.load = (key) => myStore.read(key) || {}
storage.save = (key, state) => myStore.write(key, state)
storage.clear = (key) => myStore.delete(key)
```

### Test hooks

The markup the plugin adds carries `data-*` attributes, so tests do not depend on CSS classes:

- `data-filters-visibility` on the sidebar section once it is set up.
- `data-filters-visibility-header` on the header that holds the toggle.
- `data-filters-visibility-toggle` on the toggle button. It also has `aria-expanded`, and `data-filters-visibility-active` while any filter is hidden.
- `data-filters-visibility-panel` on the panel.
- `data-filters-visibility-item="<wrapper id>"` on each row and `data-filters-visibility-checkbox="<wrapper id>"` on its checkbox.
- `data-filters-visibility-reset` on the reset button.
- `data-filters-visibility-hidden` on a hidden filter wrapper.

## Upgrading from 1.x

- ActiveAdmin 4 only. ActiveAdmin 3 apps stay on 1.x.
- No jQuery and no Sprockets. The asset is an ES module loaded through ActiveAdmin's import map or the npm package, and it sets itself up: remove the `$('#filters_sidebar_section').activeAdminFiltersVisibility()` call.
- Options moved from the jQuery call to `defaults`. `iconClass`, `iconStyle` and `skipDefaultCss` are gone: style the toggle with CSS, or leave the stylesheet out. `sidebarUniqId` is now `storageKey`. `$.fn.activeAdminFiltersVisibility.registerFilterType` and `.storage` are now the `registerFilterType` export and the `storage` object.
- The styles are a stylesheet you import into the ActiveAdmin Tailwind build. They are no longer injected at runtime.
- Saved state uses a new key and format, and identifies filters by wrapper id instead of label text. Hidden filters and custom orders saved by 1.x are not carried over.
- Reset is always shown and no longer reloads the page.
- Filter type detectors are keyed by any class on the wrapper, such as Formtastic's type (`select`) or a class you set, instead of a `filter_<type>` class.

## Development

```sh
bundle exec rspec
BUNDLE_GEMFILE=gemfiles/active_admin_theme.gemfile bundle exec rspec
```

The second run uses active_admin_theme from git. Set `ACTIVE_ADMIN_THEME_PATH` to use a local checkout. The specs build the dummy app's stylesheet with the standalone Tailwind CLI from `tailwindcss-ruby`.
7 changes: 5 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
require "bundler"
require 'bundler'
require 'rake'
Bundler.setup
Bundler::GemHelper.install_tasks
FileList['tasks/**/*.rake'].each { |task| import task }

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task default: :spec
4 changes: 2 additions & 2 deletions active_admin_filters_visibility.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Gem::Specification.new do |s|
s.email = ['workgena@gmail.com']
s.homepage = 'https://github.com/workgena/active_admin_filters_visibility'
s.summary = %q{active_admin_filters_visibility gem}
s.description = %q{extension for activeadmin gem to hide any filters from sidebar-filters panel}
s.description = %q{extension for activeadmin gem to hide and reorder filters in the ActiveAdmin 4 filters sidebar}

s.required_ruby_version = '>= 3.3'

s.add_dependency 'activeadmin', '>= 3.2', '< 4.0'
s.add_dependency 'activeadmin', '>= 4.0.0.beta1', '< 5.0.0.a'

# Whitelist, not a reject list: a new directory in the repo does not
# reach consumers until it is named here. The reject form needs a new
Expand Down
Loading
Loading