Skip to content

Commit fab90aa

Browse files
authored
Fix/component imports railtie (#3)
* fix module names * add turbo-rails dependency * add main_component * cap view_component version * add require statements * add railtie * remove comments from railtie.rb * remove unnecessary requires * happy rubocop * various updates * Update category_component.rb * remove commented requires * update heroicon to 2.0 * setup js * add tailwindcss-rails gem dependency * add view require * Revert "add view require" This reverts commit 45d2ec7. * white -> medium gray * fix indentation * Revert "fix indentation" This reverts commit 2b478c0. * add log statements * Delete .vscode/diff directory * add require * remove logs * remove comment and whitespace * use ruby platform * typo * Create sn_filterable-0.1.0.gem * add view_component require to railtie.rb * remove view_component require * add view_component require * require view_component/base * add /all requirement * Revert "add /all requirement" This reverts commit 3e2b4af. * move require into SnFItlerable module * remove /base require * reduce spec.files scope * Update version.rb * Update Gemfile.lock * add require view_component to railtie.rb * Fix/replicate polaris (#7) * move components to app * Move component loading to engine * add view_component/engine require to engine * Revert "add view_component/engine require to engine" This reverts commit 797d977. * Revert "Merge branch 'fix/replicate-polaris' of https://github.com/ibm-skills-network/sn_filterable into fix/replicate-polaris" This reverts commit 3213ee4, reversing changes made to 797d977. * Revert "Revert "add view_component/engine require to engine"" This reverts commit 2e3e9b7. * add comoponents/sn_filterable sub folder * fix namespace mismatches * remove BaseComponents namespace from FilterButtonComponent * precompile js * Revert "precompile js" This reverts commit 90020e0. * add js to autoload paths * Revert "add js to autoload paths" This reverts commit 06d81e7. * add load_js * load_js -> self.load_js * Update README.md * Add implementation steps * rubocop adjustments * Add testing details to readme * adjust readme code format * update readme gemfile install instructions * Update README.md * add demo gif * change bundle install format * grammatical readme adjustments * Add search bar usage instructions * update table rendering in readme * Add sort too readme usage * Update README.md * Update README.md
1 parent 95397c6 commit fab90aa

26 files changed

+413
-89
lines changed

.vscode/diff/vulsCount.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

Gemfile.lock

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
PATH
22
remote: .
33
specs:
4-
sn_filterable (0.1.0)
4+
sn_filterable (0.1.1)
55
heroicon
66
kaminari
77
pg
88
pg_search
9+
tailwindcss-rails
10+
turbo-rails
911
view_component
1012

1113
GEM
@@ -74,6 +76,7 @@ GEM
7476
builder (3.2.4)
7577
concurrent-ruby (1.1.10)
7678
crass (1.0.6)
79+
date (3.3.1)
7780
diff-lcs (1.5.0)
7881
erubi (1.11.0)
7982
factory_bot (6.2.1)
@@ -113,19 +116,20 @@ GEM
113116
marcel (1.0.2)
114117
method_source (1.0.0)
115118
mini_mime (1.1.2)
119+
mini_portile2 (2.8.0)
116120
minitest (5.16.3)
117-
net-imap (0.3.1)
121+
net-imap (0.3.2)
122+
date
118123
net-protocol
119124
net-pop (0.1.2)
120125
net-protocol
121-
net-protocol (0.2.0)
126+
net-protocol (0.2.1)
122127
timeout
123128
net-smtp (0.3.3)
124129
net-protocol
125130
nio4r (2.5.8)
126-
nokogiri (1.13.10-x86_64-darwin)
127-
racc (~> 1.4)
128-
nokogiri (1.13.10-x86_64-linux)
131+
nokogiri (1.13.10)
132+
mini_portile2 (~> 2.8.0)
129133
racc (~> 1.4)
130134
parallel (1.22.1)
131135
parser (3.1.3.0)
@@ -218,12 +222,18 @@ GEM
218222
actionpack (>= 5.2)
219223
activesupport (>= 5.2)
220224
sprockets (>= 3.0.0)
225+
tailwindcss-rails (2.0.21)
226+
railties (>= 6.0.0)
221227
thor (1.2.1)
222228
timeout (0.3.1)
229+
turbo-rails (1.3.2)
230+
actionpack (>= 6.0.0)
231+
activejob (>= 6.0.0)
232+
railties (>= 6.0.0)
223233
tzinfo (2.0.5)
224234
concurrent-ruby (~> 1.0)
225235
unicode-display_width (2.3.0)
226-
view_component (2.78.0)
236+
view_component (2.79.0)
227237
activesupport (>= 5.0.0, < 8.0)
228238
concurrent-ruby (~> 1.0)
229239
method_source (~> 1.0)
@@ -235,9 +245,7 @@ GEM
235245
zeitwerk (2.6.6)
236246

237247
PLATFORMS
238-
x86_64-darwin-20
239-
x86_64-darwin-21
240-
x86_64-linux
248+
ruby
241249

242250
DEPENDENCIES
243251
factory_bot_rails

README.md

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,148 @@
11
# SnFilterable
22

3-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sn_filterable`. To experiment with that code, run `bin/console` for an interactive prompt.
3+
Welcome to the Skills Network Filterable gem!
44

5-
TODO: Delete this and the text above, and describe your gem
5+
This gem provides a method for developers to quickly implement a customizable search and filter for their data with live-reloading.
6+
7+
Live examples of the gem's use can be viewed at [Skills Network's Author Workbench](https://author.skills.network), primarily under the [organizations tab](https://author.skills.network/organizations)
8+
9+
![](sn_filterable_demo.gif)
10+
11+
## Requirements
12+
13+
There are a couple key requirements for your app to be compatible with this gem:
14+
15+
1. You need to have [AlpineJS](https://alpinejs.dev/essentials/installation) loaded into the page where you plan to use SnFilterable
16+
2. Your app needs to be running [TailwindCSS](https://tailwindcss.com/docs/guides/ruby-on-rails)
617

718
## Installation
819

920
Add this line to your application's Gemfile:
1021

1122
```ruby
12-
gem 'sn_filterable'
23+
gem "sn_filterable", git: "https://github.com/ibm-skills-network/sn_filterable.git"
1324
```
1425

1526
And then execute:
27+
```bash
28+
bundle install
29+
```
30+
31+
##### Make the following adjustments to your codebase
32+
33+
1. Add the necessary translations and customize as desired
34+
```yaml
35+
# en.yml
36+
en:
37+
# Other translations
38+
shared:
39+
filterable:
40+
view_filter_button: "View filters"
41+
results_per_page: "Results per page"
42+
clear_all: "Clear all"
43+
pagination:
44+
previous_page: "Previous"
45+
next_page: "Next"
46+
```
1647
17-
$ bundle install
48+
2. Require the necessary JavaScript (dependent on AlpineJS being included with your App)
49+
```javascript
50+
// application.js converted to application.js.erb
1851

19-
Or install it yourself as:
52+
// other imports
53+
<%= SnFilterable.load_js %>
54+
```
2055

21-
$ gem install sn_filterable
56+
3. Configure your app's Tailwind to scan the gem
57+
```javascript
58+
// tailwind.config.js
59+
const execSync = require('child_process').execSync;
60+
const output = execSync('bundle show sn_filterable', { encoding: 'utf-8' });
61+
62+
module.exports = {
63+
// other config settings
64+
content: [
65+
// other content
66+
output.trim() + '/app/**/*.{erb,rb}'
67+
]
68+
// other config settings
69+
};
70+
```
2271

2372
## Usage
2473

25-
TODO: Write usage instructions here
2674

27-
## Development
75+
##### The MainComponent: Search Bar and sidebar
76+
77+
The MainComponent is what is demo'd in the introduction. It consists of the search bar and a sidebar for filters.
78+
79+
If you only wish to use the Search bar an optional `show_sidebar: false` parameter can be passed to `SnFilterable::MainComponent` in the view.
80+
81+
There are three components which work to provide the text search functionality:
82+
83+
1. Filters in the given model:
84+
```ruby
85+
# model.rb
86+
class Model < ApplicationRecord
87+
include SnFilterable::Filterable
88+
89+
FILTER_SCOPE_MAPPINGS = {
90+
"search_name": :filter_by_name
91+
# 'search_name' will be referenced from the view
92+
}.freeze
2893

29-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
3094

31-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
95+
SORT_SCOPE_MAPPINGS = {
96+
"sort_name": :sort_by_name
97+
# 'sort_name' will be referenced from the controller
98+
}.freeze
99+
100+
scope :filter_by_name, ->(search) { where(SnFilterable::WhereHelper.contains("name", search)) }
101+
scope :sort_by_name, -> { order :name }
102+
# 'name' is a string column defined on the Model
103+
104+
# Model code...
105+
end
106+
```
107+
108+
2. Setting up the controller
109+
* While `:default_sort` is an optional parameter it is recommended
110+
```ruby
111+
# models_controller.rb
112+
@search = Model.filter(params:, default_sort: ["sort_name", :asc].freeze)
113+
@models = @search.items
114+
```
115+
116+
3. Rendering the ViewComponent
117+
```html
118+
<%= render SnFilterable::MainComponent.new(frame_id: "some_unique_id", filtered: @search, filters: [], search_filter_name: "search_name") do %>
119+
<% @models.each do |model| %>
120+
<%= model.name %>
121+
<% end %>
122+
<%= filtered_paginate @search %> # Kaminari Gem Pagination
123+
<% end %>
124+
```
125+
126+
## Testing / Development
127+
128+
This gem using [RSpec](https://rspec.info) for testing. Tests can be running locally by first setting up the dummy database/app as follows:
129+
130+
```bash
131+
docker compose up -d
132+
cd spec/dummy
133+
rails db:create
134+
rails db:schema:load
135+
```
136+
137+
138+
Now the test suite can be run from the project root using
139+
```bash
140+
bundle exec rspec
141+
```
32142

33143
## Contributing
34144

35-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sn_filterable.
145+
Bug reports and pull requests are welcome on [GitHub](https://github.com/ibm-skills-network/sn_filterable).
36146

37147
## License
38148

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
document.addEventListener("alpine:init", () => {
2+
Alpine.data("filteringSearchInput", (searchKey) => ({
3+
currentTimeout: null,
4+
hasValue: false,
5+
onInit: function () {
6+
this.onValueUpdate()
7+
},
8+
onUpdate: function (force) {
9+
this.clearTimeout();
10+
this.onValueUpdate();
11+
12+
this.$data.currentTimeout = setTimeout(() => {
13+
this.$data.currentTimeout = null;
14+
15+
this.$data.filteringForm.requestSubmit();
16+
}, force ? 0 : 500);
17+
},
18+
onValueUpdate: function () {
19+
this.$data.hasValue = this.$el.value.length > 0;
20+
},
21+
clearTimeout: function () {
22+
if (this.$data.currentTimeout != null) {
23+
clearInterval(this.$data.currentTimeout);
24+
this.$data.currentTimeout = null;
25+
}
26+
}
27+
}));
28+
Alpine.data("filteringChipContainer", () => ({
29+
updateSidebarGap: function () {
30+
this.$data.sidebarGapTarget.style.paddingTop = `${this.$el.clientHeight}px`;
31+
}
32+
}));
33+
Alpine.data("filteringChip", (filter) => ({
34+
onClick: function (chipElem) {
35+
const inputElem = this.$data.filteringForm.querySelector(`input[data-filter-name=${JSON.stringify(filter.parent)}][value=${JSON.stringify(filter.value)}]`);
36+
if (filter.multi) {
37+
inputElem.click()
38+
} else {
39+
inputElem.checked = false;
40+
this.$data.filteringForm.requestSubmit();
41+
}
42+
43+
if (this.$el.closest(".app-filter-chips-container").children.length == 1) {
44+
this.$el.closest(".app-filter-chips-content").remove();
45+
} else {
46+
this.$el.closest(".app-filter-chip").remove();
47+
}
48+
49+
this.$data.updateSidebarGap();
50+
}
51+
}));
52+
Alpine.data("filteringClear", (filter) => ({
53+
onClick: function (chipElem) {
54+
const chips = Array.from(this.$data.entireComponenet.querySelector(".app-filter-chips-container").querySelectorAll(".app-filter-chip"));
55+
56+
for (const chip of chips) {
57+
chip.querySelector("a").click();
58+
}
59+
60+
this.$el.closest(".app-filter-chips-content").remove();
61+
}
62+
}));
63+
});

lib/sn_filterable/base_components/base_component.rb renamed to app/components/sn_filterable/base_components/base_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
require "view_component"
33
require "rails"
44

5-
class BaseComponent < ViewComponent::Base
5+
class SnFilterable::BaseComponents::BaseComponent < ViewComponent::Base
66
include ClassNameHelper
77
include FetchOrFallbackHelper
88

lib/sn_filterable/base_components/button_component.rb renamed to app/components/sn_filterable/base_components/button_component.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22
require_relative "base_component"
33

4-
class ButtonComponent < BaseComponent
4+
class SnFilterable::BaseComponents::ButtonComponent < SnFilterable::BaseComponents::BaseComponent
55
DEFAULT_BUTTON_TYPE = :default
66
BUTTON_TYPE_MAPPINGS = {
77
DEFAULT_BUTTON_TYPE => "shadow-sm border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-indigo-500",
@@ -66,6 +66,6 @@ def initialize(
6666
end
6767

6868
def call
69-
render(BaseComponent.new(**@arguments)) { content }
69+
render(SnFilterable::BaseComponents::BaseComponent.new(**@arguments)) { content }
7070
end
7171
end

lib/sn_filterable/category_component.rb renamed to app/components/sn_filterable/category_component.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
module Filterable
1+
require_relative "filter_category_component"
2+
3+
module SnFilterable
24
# Renders a category to be displayed in the filtering sidebar/popup.
35
# Simple view component, logic to display filters should render a [:filter] (see [Filterable::FilterCategoryComponent])
46
class CategoryComponent < ViewComponent::Base
5-
include HeroiconHelper
7+
include Heroicon::Engine.helpers
68

7-
renders_one :filter, Filterable::FilterCategoryComponent
9+
renders_one :filter, SnFilterable::FilterCategoryComponent
810

911
# @param [String] title Optional, the title of the category, will default to the filter's title (if specified)
1012
# @param [Boolean] open Optional, determines if the category should be opened by default

lib/sn_filterable/chips_component.rb renamed to app/components/sn_filterable/chips_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Filterable
1+
module SnFilterable
22
# Handles rendering of the chips for the filters
33
class ChipsComponent < ViewComponent::Base
44
include FilteredHelper
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= render(SnFilterable::BaseComponents::ButtonComponent.new("@click": "filtersPopupOpen = !filtersPopupOpen", "class": "after:content-[attr(data-active)] before:block", "data-active": @count)) { t("shared.filterable.view_filter_button") } %>

lib/sn_filterable/filter_button_component.rb renamed to app/components/sn_filterable/filter_button_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module Filterable
1+
module SnFilterable
22
# Handles rendering of the `View filters` button for mobile layouts
33
class FilterButtonComponent < ViewComponent::Base
44
# @param [Filtered] filtered The filtered instance

lib/sn_filterable/filter_category_component.rb renamed to app/components/sn_filterable/filter_category_component.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
module Filterable
1+
module SnFilterable
22
# Component for a filter's category for the filters sidebar
33
class FilterCategoryComponent < ViewComponent::Base
4-
include HeroiconHelper
4+
include Heroicon::Engine.helpers
55
include FilteredHelper
66

77
# @param [Filtered] filtered The filtered instance

0 commit comments

Comments
 (0)