rolemodel_tables 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4ee2f9c219c3b794961065e8e2ad79bd6de362fc4bbc9c7f7ca5fe76f5231c9
4
- data.tar.gz: bc7c188b7a7b666089a6e0a6a0076d1d47e37be70fd6ce870b4b03842db47070
3
+ metadata.gz: 6f1a42253a5583547dba681eda52f60b16127b513500d0ae4b05257e5dbd6945
4
+ data.tar.gz: 0b8b0c4ffe45ff940cde6041013b2b2fafb39e440cfd3294ca8648a5096961c6
5
5
  SHA512:
6
- metadata.gz: 98bce5a12bf187e90d38e1e4fedbbfca5a23dd5f4fa6d46c57baa23e323d21fb94483cae90b348941037343c12956a47cee506a37808f9a4a3bb6abba3abf4e9
7
- data.tar.gz: 8bd04a2300f93ea63de60772fed8ff6e21949786ab49a95ab2cf0f095dcd5deef7fe578183e43cad9374c35102dc392f537f3037315a19339dc9373ed085438a
6
+ metadata.gz: 06556643aff1a83c64d7c9e443009a2e0c835d6d6921331dfd5c0c29cbbc473f3744129a616964f08f59ddd454d9d8c8d98f358e0d508376bd55c9c16c99f9ba
7
+ data.tar.gz: b040bfe87341bda6a691467f5b582d4b16b149b503d0bad845e90cf0295c64cd39214af49c6b7cf503e5450fe9e6d73c31a5a49d84c7777996348bb72ab59f65
@@ -0,0 +1,53 @@
1
+ const { Controller } = require("@hotwired/stimulus")
2
+
3
+ class AdvancedTableSearchController extends Controller {
4
+ static values = {
5
+ advancedLabel: String,
6
+ }
7
+
8
+ static targets = [
9
+ 'showSearchHiddenInput',
10
+ 'toggleButton',
11
+ 'controlsContainer',
12
+ 'keywordSearchButton',
13
+ 'toggleableArea',
14
+ ]
15
+
16
+ connect() {
17
+ /* global window */
18
+ // Did the user initiate a search via the form? If so, reveal the form
19
+ // on page load. The form is initially hidden.
20
+ const searchParams = new URLSearchParams(window.location.search)
21
+ if (searchParams.get('show_advanced_search') === 'true') {
22
+ this.toggle()
23
+ }
24
+ }
25
+
26
+ toggle() {
27
+ this.toggleableAreaTarget.classList.toggle('hidden')
28
+
29
+ this.keywordSearchButtonTarget.classList.toggle('hidden')
30
+
31
+ const text = this.toggleableAreaTarget.classList.contains('hidden') ? 'Show' : 'Hide'
32
+ this.toggleButtonTarget.innerText = `${text} ${this.advancedLabelValue}`
33
+
34
+ const advancedSearchState = this.toggleableAreaTarget.classList.contains('hidden') ? 'false' : 'true'
35
+ this.showSearchHiddenInputTarget.value = advancedSearchState
36
+
37
+ const searchParams = new URLSearchParams(window.location.search)
38
+ searchParams.set('show_advanced_search', advancedSearchState)
39
+ const newRelativePathQuery = this._newRelativePathQuery(searchParams)
40
+ window.history.pushState(null, '', newRelativePathQuery)
41
+ }
42
+
43
+ clear() {
44
+ const searchParams = new URLSearchParams({ show_advanced_search: 'true' })
45
+ window.location = this._newRelativePathQuery(searchParams)
46
+ }
47
+
48
+ _newRelativePathQuery(searchParams) {
49
+ return `${window.location.pathname}?${searchParams.toString()}`
50
+ }
51
+ }
52
+
53
+ export default AdvancedTableSearchController
@@ -0,0 +1,10 @@
1
+ / Link to the "First" page
2
+ / - available local variables
3
+ / url: url to the first page
4
+ / current_page: a page object for the currently displayed page
5
+ / total_pages: total number of pages
6
+ / per_page: number of items to fetch per page
7
+ / remote: data-remote
8
+
9
+ span
10
+ = link_to_unless current_page.first?, icon('first_page'), url, remote: remote, class: 'btn btn--outline btn--small'
@@ -0,0 +1,9 @@
1
+ / Non-link tag that stands for skipped pages...
2
+ / - available local variables
3
+ / current_page: a page object for the currently displayed page
4
+ / total_pages: total number of pages
5
+ / per_page: number of items to fetch per page
6
+ / remote: data-remote
7
+
8
+ span
9
+ .btn.btn--small.pagination__gap = t('views.pagination.truncate').html_safe
@@ -0,0 +1,10 @@
1
+ / Link to the "Last" page
2
+ / - available local variables
3
+ / url: url to the last page
4
+ / current_page: a page object for the currently displayed page
5
+ / total_pages: total number of pages
6
+ / per_page: number of items to fetch per page
7
+ / remote: data-remote
8
+
9
+ span
10
+ = link_to_unless current_page.last?, icon('last_page'), url, remote: remote, class: 'btn btn--outline btn--small'
@@ -0,0 +1,10 @@
1
+ / Link to the "Next" page
2
+ / - available local variables
3
+ / url: url to the next page
4
+ / current_page: a page object for the currently displayed page
5
+ / total_pages: total number of pages
6
+ / per_page: number of items to fetch per page
7
+ / remote: data-remote
8
+
9
+ span
10
+ = link_to_unless current_page.last?, icon('chevron_right'), url, rel: 'next', remote: remote, class: 'btn btn--outline btn--small'
@@ -0,0 +1,14 @@
1
+ / Link showing page number
2
+ / - available local variables
3
+ / page: a page object for "this" page
4
+ / url: url to this page
5
+ / current_page: a page object for the currently displayed page
6
+ / total_pages: total number of pages
7
+ / per_page: number of items to fetch per page
8
+ / remote: data-remote
9
+
10
+ span.page
11
+ - if page.current?
12
+ .btn.btn--small.pagination__current = page
13
+ - else
14
+ = link_to page, url, remote: remote, rel: page.rel, class: 'btn btn--small btn--outline'
@@ -0,0 +1,20 @@
1
+ / The container tag
2
+ / - available local variables
3
+ / current_page: a page object for the currently displayed page
4
+ / total_pages: total number of pages
5
+ / per_page: number of items to fetch per page
6
+ / remote: data-remote
7
+ / paginator: the paginator that renders the pagination tags inside
8
+
9
+ == paginator.render do
10
+ nav.pagination role="navigation" aria-label="pager"
11
+ == first_page_tag unless current_page.first?
12
+ == prev_page_tag unless current_page.first?
13
+ - each_page do |page|
14
+ - if page.display_tag?
15
+ == page_tag page
16
+ - elsif !page.was_truncated?
17
+ == gap_tag
18
+ - unless current_page.out_of_range?
19
+ == next_page_tag unless current_page.last?
20
+ == last_page_tag unless current_page.last?
@@ -0,0 +1,10 @@
1
+ / Link to the "Previous" page
2
+ / - available local variables
3
+ / url: url to the previous page
4
+ / current_page: a page object for the currently displayed page
5
+ / total_pages: total number of pages
6
+ / per_page: number of items to fetch per page
7
+ / remote: data-remote
8
+
9
+ span
10
+ = link_to_unless current_page.first?, icon('chevron_left'), url, rel: 'prev', remote: remote, class: 'btn btn--outline btn--small'
@@ -0,0 +1,21 @@
1
+ .margin-y-md data-controller='advanced-table-search submit-on-select' data-advanced-table-search-advanced-label-value='Advanced Filters'
2
+ = search_form_for filter_view.ransack, url: request.path, html: { class: 'simple_form' , data: { 'submit-on-select-target' => 'form' }} do |f|
3
+ = hidden_field_tag :show_advanced_search, params.fetch(:show_advanced_search, false), data: { 'advanced-table-search-target': 'showSearchHiddenInput' }
4
+
5
+ .card.margin-bottom-md data-advanced-table-search-target='controlsContainer'
6
+ .flex.items-center.gap-sm
7
+ / TODO: INLINE STYLES BAD
8
+ .flex.items-center.gap-sm style='width: 300px'
9
+ = f.search_field filter_view.quick_search, placeholder: 'Keyword', class: 'form__input'
10
+ = f.button :search, class: 'btn btn--primary', data: { 'advanced-table-search-target': 'keywordSearchButton' }
11
+
12
+ button.project-search-toggle data-advanced-table-search-target='toggleButton' type='button' data-action='click->advanced-table-search#toggle' Show Advanced Filters
13
+
14
+ .margin-top-md.hidden data-advanced-table-search-target='toggleableArea'
15
+ .flex.flex-wrap
16
+ - filter_view.advanced_filters.each do |filter|
17
+ = filter.form_row(f).html_safe
18
+
19
+ .flex.justify-end.items-center.gap-sm.margin-top-md
20
+ button.btn.btn--outline type='button' data-action='click->advanced-table-search#clear' Clear
21
+ = f.button :search, class: 'btn btn--primary'
@@ -0,0 +1,4 @@
1
+ module RoleModelTables
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ # Parent class for all filter views.
5
+ class FilterView
6
+ class_attribute :default_sort, :quick_search, :advanced_filters
7
+ attr_reader :ransack
8
+
9
+ def initialize(scoped_collection)
10
+ @scoped_collection = scoped_collection
11
+ end
12
+
13
+ def self.default_sort(sort_option)
14
+ self.default_sort = sort_option
15
+ end
16
+
17
+ def self.quick_search(ransack_matcher)
18
+ self.quick_search = ransack_matcher
19
+ end
20
+
21
+ def self.advanced_filter(ransack_matcher, klass_name, form_options = {})
22
+ self.advanced_filters ||= {}
23
+ self.advanced_filters[ransack_matcher] = "RoleModelTables::Filters::#{klass_name}".constantize.new(ransack_matcher, form_options)
24
+ end
25
+
26
+ def advanced_filters
27
+ self.class.advanced_filters.map do |_, filter_instance|
28
+ filter_instance.filter = self
29
+ filter_instance
30
+ end
31
+ end
32
+
33
+ def search(params)
34
+ @ransack = @scoped_collection.ransack(params[:query])
35
+ @ransack.sorts = default_sort if @ransack.sorts.empty?
36
+ @ransack.result.page(params[:page])
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Handle collection dropdowns.
6
+ class CollectionFilter < RansackFilter
7
+ def form_row(form)
8
+ content_tag(:div, class: 'form__group') do
9
+ [
10
+ label(form),
11
+ collection_select(form)
12
+ ].join(' ').html_safe
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def collection_select(form)
19
+ form.collection_select(
20
+ ransack_matcher,
21
+ form_options.fetch(:collection, -> { [] }).call(filter),
22
+ form_options.fetch(:option_value, ''),
23
+ form_options.fetch(:option_label, ''),
24
+ { include_blank: 'Any' },
25
+ { class: 'form__dropdown' }
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Handles a single date field filter.
6
+ class DateFilter < RansackFilter
7
+ def form_row(form)
8
+ content_tag(:div, class: 'form__group') do
9
+ [
10
+ label(form),
11
+ date_field(form)
12
+ ].join(' ').html_safe
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def date_field(form)
19
+ form.date_field(ransack_matcher, { class: 'form__input' })
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Handles a date range filter.
6
+ class DateRangeFilter < RansackFilter
7
+ def form_row(form)
8
+ content_tag(:div, class: 'form__group') do
9
+ [
10
+ label(form),
11
+ content_tag(:div, class: 'flex items-center gap-sm') do
12
+ [
13
+ date_field(form, :gteq),
14
+ content_tag(:span, '-'),
15
+ date_field(form, :lteq)
16
+ ].join(' ').html_safe
17
+ end
18
+ ].join(' ').html_safe
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def date_field(form, predicate)
25
+ form.date_field("#{ransack_matcher}_#{predicate}", { class: 'form__input' })
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Handles a number range filter.
6
+ class NumberRangeFilter < RansackFilter
7
+ def form_row(form)
8
+ content_tag(:div, class: 'form__group') do
9
+ [
10
+ label(form),
11
+ content_tag(:div, class: 'flex items-center gap-sm') do
12
+ [
13
+ search_field(form, :gteq),
14
+ content_tag(:span, '-'),
15
+ search_field(form, :lteq)
16
+ ].join(' ').html_safe
17
+ end
18
+ ].join(' ').html_safe
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def search_field(form, predicate)
25
+ form.search_field(
26
+ "#{ransack_matcher}_#{predicate}",
27
+ class: 'form__input',
28
+ placeholder: form_options.fetch(:range_begin_placeholder, '')
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Handles a text field filter.
6
+ class TextFilter < RansackFilter
7
+ def form_row(form)
8
+ content_tag(:div, class: 'form__group') do
9
+ [
10
+ label(form),
11
+ search_field(form)
12
+ ].join(' ').html_safe
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def search_field(form)
19
+ form.search_field(
20
+ ransack_matcher,
21
+ class: 'form__input',
22
+ placeholder: form_options.fetch(:placeholder, '')
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ module Filters
5
+ # Parent class for all custom filters.
6
+ class RansackFilter
7
+ include ActionView::Context
8
+ include ActionView::Helpers::TagHelper
9
+
10
+ attr_reader :ransack_matcher, :form_options
11
+ attr_accessor :filter
12
+
13
+ def initialize(ransack_matcher, form_options = {})
14
+ @ransack_matcher = ransack_matcher
15
+ @form_options = form_options
16
+ end
17
+
18
+ def form_row(_form)
19
+ throw 'Subclass must override'
20
+ end
21
+
22
+ private
23
+
24
+ def label(form)
25
+ form.label(
26
+ ransack_matcher,
27
+ form_options.fetch(:label, ransack_matcher.to_s.humanize),
28
+ class: 'form__label'
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ VERSION = '0.0.2'
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RoleModelTables
4
+ class Error < StandardError; end
5
+ end
6
+
7
+ require 'rolemodel_tables/engine'
8
+ require 'rolemodel_tables/models/filter_view'
9
+ require 'rolemodel_tables/models/ransack_filter'
10
+
11
+ Dir.chdir(__dir__) do
12
+ Dir['rolemodel_tables/models/filters/*.rb'].each { |file| require file }
13
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'rolemodel_tables/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'rolemodel_tables'
9
+ s.version = RoleModelTables::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Jeremy Walton']
12
+ s.email = 'consult@rolemodelsoftware.com'
13
+
14
+ s.summary = 'Rolemodel Tables!'
15
+ s.description = 'Helpers for advanced table functionality.'
16
+ s.homepage = 'https://github.com/RoleModel/rolemodel_tables'
17
+
18
+ s.license = 'MIT'
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ s.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ s.require_paths = ['lib']
26
+
27
+ s.required_ruby_version = '>= 2.1.0'
28
+
29
+ s.add_dependency 'kaminari', '~> 1.2'
30
+ s.add_dependency 'ransack', '~> 2.5'
31
+ s.add_dependency 'slim', '~> 4.1'
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rolemodel_tables
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Walton
@@ -61,6 +61,26 @@ files:
61
61
  - ".gitignore"
62
62
  - LICENSE
63
63
  - README.md
64
+ - app/javascript/controllers/advanced_table_search_controller.js
65
+ - app/views/kaminari/_first_page.html.slim
66
+ - app/views/kaminari/_gap.html.slim
67
+ - app/views/kaminari/_last_page.html.slim
68
+ - app/views/kaminari/_next_page.html.slim
69
+ - app/views/kaminari/_page.html.slim
70
+ - app/views/kaminari/_paginator.html.slim
71
+ - app/views/kaminari/_prev_page.html.slim
72
+ - app/views/rolemodel_tables/_advanced_search_controls.html.slim
73
+ - lib/rolemodel_tables.rb
74
+ - lib/rolemodel_tables/engine.rb
75
+ - lib/rolemodel_tables/models/filter_view.rb
76
+ - lib/rolemodel_tables/models/filters/collection_filter.rb
77
+ - lib/rolemodel_tables/models/filters/date_filter.rb
78
+ - lib/rolemodel_tables/models/filters/date_range_filter.rb
79
+ - lib/rolemodel_tables/models/filters/number_range_filter.rb
80
+ - lib/rolemodel_tables/models/filters/text_filter.rb
81
+ - lib/rolemodel_tables/models/ransack_filter.rb
82
+ - lib/rolemodel_tables/version.rb
83
+ - rolemodel_tables.gemspec
64
84
  homepage: https://github.com/RoleModel/rolemodel_tables
65
85
  licenses:
66
86
  - MIT