rolemodel_tables 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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