sortable_by 1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +70 -0
- data/Rakefile +32 -0
- data/app/controllers/concerns/sortable_by/params.rb +83 -0
- data/app/helpers/sortable_by/table_helper.rb +37 -0
- data/lib/sortable_by.rb +6 -0
- data/lib/sortable_by/configuration.rb +11 -0
- data/lib/sortable_by/engine.rb +7 -0
- data/lib/sortable_by/table_header.rb +107 -0
- data/lib/sortable_by/version.rb +3 -0
- data/lib/tasks/sortable_by_tasks.rake +4 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b73a5ddc646d2e3c337439264cb4ca123aa3e30e
|
4
|
+
data.tar.gz: 5790ac2d41f873ebaffe8e2a20a7329ee667f180
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd6f579e3c5b90e976c23be7f5c03192d5fa816e1dbf7ee466d7df1ea02b13ab3eae36275fec8b6fa2278c791c68b16511f706ac36a0105415f2c62e12317d02
|
7
|
+
data.tar.gz: ec9f53443ec60403476953b6f8beabf3982ea7342f5134adf725f1ce3c247d07ce4af80fdb29379308fc22470022676ba6f007f2a4f4a93385bd526247b504cb
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2017 Greg Woods
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# SortableBy
|
2
|
+
|
3
|
+
Add sortable table headers to your views and connect them to ActiveRecord queries in your controller.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'sortable_by'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
```bash
|
15
|
+
$ bundle install
|
16
|
+
```
|
17
|
+
|
18
|
+
### Example Usage
|
19
|
+
|
20
|
+
In your view, the `sortable_table_header` helper method will generate a `<thead>` element with header tags.
|
21
|
+
|
22
|
+
```erb
|
23
|
+
<%= sortable_table_header :users_path do |t| %>
|
24
|
+
<%= t.sortable :full_name %>
|
25
|
+
<%= t.sortable :email %>
|
26
|
+
<%= t.sortable :birth_date %>
|
27
|
+
<%= t.header :favorite_color %>
|
28
|
+
<th>Plain HTML is fine too</th>
|
29
|
+
<% end %>
|
30
|
+
```
|
31
|
+
|
32
|
+
See [table_helper.rb](./app/helpers/sortable_by/table_helper.rb) for options.
|
33
|
+
|
34
|
+
In your controller, use the `sortable_by` method to configure sorting attributes.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class MyController < ApplicationController
|
38
|
+
# Configure the sortable attributes
|
39
|
+
sortable_by :email, # A basic sort
|
40
|
+
full_name: [:last_name, :first_name], # This will sort on two columns
|
41
|
+
birth_date: 'birth_dates.date :dir', # This will sort on a joined table
|
42
|
+
default: :email # The sort to use when none is passed
|
43
|
+
direction: :desc # Initial sort direction (defaults to :asc)
|
44
|
+
|
45
|
+
def index
|
46
|
+
# Call #sortable_query to produce an ActiveRecord compatible sorting hash
|
47
|
+
MyModel.where(...).order(sortable_query)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
See [params.rb](./app/controllers/concerns/sortable_by/params.rb) for options.
|
53
|
+
|
54
|
+
### Icon Support
|
55
|
+
|
56
|
+
[Font Awesome](http://fontawesome.io) and [Glyphicons](http://getbootstrap.com/components/#glyphicons) are both supported by default. You can also add your own icon strategy.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
module SortableBy::IconStrategy
|
60
|
+
def self.custom(context, dir)
|
61
|
+
"test: #{dir}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
```erb
|
67
|
+
<%= sortable_table_header :users_path, icon: :custom do |t| %>
|
68
|
+
...
|
69
|
+
<% end %>
|
70
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'SortableBy'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module SortableBy
|
2
|
+
module Params
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
attr_accessor :default_sort_attribute
|
7
|
+
attr_accessor :default_sort_dir
|
8
|
+
attr_accessor :sortable_mapping
|
9
|
+
|
10
|
+
# Define attributes you wish to sort by
|
11
|
+
#
|
12
|
+
# This optional configuration method tells the the sortable
|
13
|
+
# helpers how they should map attributes to queries. For
|
14
|
+
# example a "name" sort might need to sort on first and last
|
15
|
+
# name attributes
|
16
|
+
#
|
17
|
+
# The `default` argument, if passed, will determine the
|
18
|
+
# attribute used when no sort param has been passed
|
19
|
+
#
|
20
|
+
# Usage:
|
21
|
+
#
|
22
|
+
# sortable_by :email, name: [:last_name, :first_name]
|
23
|
+
# default: :email,
|
24
|
+
# direction: :asc
|
25
|
+
#
|
26
|
+
#
|
27
|
+
def sortable_by(*attributes, **options)
|
28
|
+
@default_sort_attribute = options.delete(:default)
|
29
|
+
@default_sort_dir = options.delete(:direction) || :asc
|
30
|
+
@sortable_mapping = options.merge(attributes.map { |att| [att, att] }.to_h)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return the current sort direction
|
35
|
+
#
|
36
|
+
# Defaults to :default_sort_dir for all values other than 'asc' or 'desc'
|
37
|
+
#
|
38
|
+
def sort_direction
|
39
|
+
if ['asc', 'desc'].include?(params[:dir])
|
40
|
+
params[:dir].to_sym
|
41
|
+
else
|
42
|
+
self.class.default_sort_dir
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the current sort param
|
47
|
+
#
|
48
|
+
def sort_attribute
|
49
|
+
params[:sort].try(:to_sym) || self.class.default_sort_attribute
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return a hash that can be used in an ActiveRecord order query
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
# MyModel.where(...).order(sortable_query).paginate(...)
|
56
|
+
#
|
57
|
+
def sortable_query
|
58
|
+
return unless sort_attribute
|
59
|
+
normalize_sort_value(
|
60
|
+
sort_attribute,
|
61
|
+
sort_direction
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Translate the sort_by and direction into a sortable hash
|
66
|
+
#
|
67
|
+
def normalize_sort_value(sort_by, direction)
|
68
|
+
mapping = self.class.sortable_mapping[sort_by]
|
69
|
+
if mapping.is_a? Symbol
|
70
|
+
{ mapping => sort_direction }
|
71
|
+
elsif mapping.is_a? String
|
72
|
+
mapping.gsub(':dir', sort_direction.to_s)
|
73
|
+
elsif mapping.is_a? Array
|
74
|
+
mapping.map { |att| [att, direction] }.to_h
|
75
|
+
elsif mapping.is_a? Proc
|
76
|
+
mapping.call(direction)
|
77
|
+
else
|
78
|
+
logger.debug("WARNING: Sort attribute '#{sort_by}' is not recognized. Did you mean to pass it to sortable_by?")
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SortableBy
|
2
|
+
module TableHelper
|
3
|
+
|
4
|
+
# Build a table header for a model
|
5
|
+
#
|
6
|
+
# * path_helper: The helper method you want to use to generate URLs.
|
7
|
+
# * model: The class we should use for translations (optional)
|
8
|
+
# * permit: Array of request params that are forwarded to the sort links
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# <%= sortable_header :admin_users_path, model: User do |t| %>
|
13
|
+
# <%= t.sortable :name %>
|
14
|
+
# <%= t.sortable :email %>
|
15
|
+
# <%= t.header :last_login %>
|
16
|
+
# <th></th>
|
17
|
+
# <% end %>
|
18
|
+
#
|
19
|
+
# Header labels will be pulled from en.yml. To provide a different
|
20
|
+
# label pass the label: option
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# <%= t.header :name, label: 'Full Name' %>
|
25
|
+
#
|
26
|
+
def sortable_table_header(path_helper, model: nil, permit: [], icon: SortableBy.icon_strategy, &block)
|
27
|
+
header = SortableBy::TableHeader.new(
|
28
|
+
path_helper: path_helper,
|
29
|
+
model: model,
|
30
|
+
params: params.permit(permit.concat(SortableBy.params_list)),
|
31
|
+
context: self,
|
32
|
+
icon: icon)
|
33
|
+
header.capture(block) if block
|
34
|
+
header.to_html
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/sortable_by.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module SortableBy
|
2
|
+
include ActiveSupport::Configurable
|
3
|
+
|
4
|
+
config_accessor :params_list, :icon_strategy
|
5
|
+
|
6
|
+
# List of params to white list
|
7
|
+
self.params_list = [:sort, :dir, :search, :tab]
|
8
|
+
|
9
|
+
# Default icon strategy to be used
|
10
|
+
self.icon_strategy = :glyph
|
11
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SortableBy
|
4
|
+
class TableHeader
|
5
|
+
attr_reader :context
|
6
|
+
attr_accessor :header_html
|
7
|
+
|
8
|
+
# Forward view helper methods to the view context
|
9
|
+
delegate :concat, :content_tag, :link_to, to: :context
|
10
|
+
|
11
|
+
def initialize(path_helper:, model: nil, params: {}, context:, icon:)
|
12
|
+
@path_helper = path_helper
|
13
|
+
@model = model
|
14
|
+
@params = params
|
15
|
+
@context = context
|
16
|
+
@icon = icon.to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
def capture(block)
|
20
|
+
@html = context.capture do
|
21
|
+
block.call(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def sortable(attribute, label: nil)
|
26
|
+
content_tag :th, class: classes_for_attribute(attribute) do
|
27
|
+
concat(link_to_for_attribute(attribute, label))
|
28
|
+
concat(sort_arrow_for_attribute(attribute))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def header(attribute, label: nil)
|
33
|
+
label ||= translated_attribute(attribute)
|
34
|
+
content_tag :th, label
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_row
|
38
|
+
content_tag(:tr, @html)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_html
|
42
|
+
content_tag :thead, to_row, class: 'tb-table-header'
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def translated_attribute(attribute)
|
48
|
+
if @model
|
49
|
+
@model.human_attribute_name(attribute)
|
50
|
+
else
|
51
|
+
context.t(attribute)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_direction
|
56
|
+
@params[:dir] || 'asc'
|
57
|
+
end
|
58
|
+
|
59
|
+
def inverted_direction
|
60
|
+
current_direction == 'asc' ? 'desc' : 'asc'
|
61
|
+
end
|
62
|
+
|
63
|
+
def determine_direction(attribute)
|
64
|
+
if @params[:sort].nil? || @params[:sort] != attribute.to_s
|
65
|
+
'asc'
|
66
|
+
else
|
67
|
+
inverted_direction
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def classes_for_attribute(attribute)
|
72
|
+
classes = ['sortable']
|
73
|
+
if @params[:sort] == attribute.to_s
|
74
|
+
classes << 'sortable-active'
|
75
|
+
classes << "sortable-#{current_direction}"
|
76
|
+
end
|
77
|
+
classes.join(' ')
|
78
|
+
end
|
79
|
+
|
80
|
+
def link_to_for_attribute(attribute, label)
|
81
|
+
label ||= translated_attribute(attribute)
|
82
|
+
path = context.send(
|
83
|
+
@path_helper,
|
84
|
+
@params.merge(sort: attribute, dir: determine_direction(attribute))
|
85
|
+
)
|
86
|
+
link_to(label, path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def sort_arrow_for_attribute(attribute)
|
90
|
+
return '' unless @params[:sort] == attribute.to_s
|
91
|
+
IconStrategy.send(@icon, context, current_direction) if IconStrategy.respond_to?(@icon)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module IconStrategy
|
96
|
+
|
97
|
+
def self.fontawesome(context, dir)
|
98
|
+
icon_class = dir == 'asc' ? 'fa-caret-up' : 'fa-caret-down'
|
99
|
+
context.content_tag :i, '', class: "fa #{icon_class}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.glyph(context, dir)
|
103
|
+
icon_class = dir == 'asc' ? 'up' : 'down'
|
104
|
+
context.content_tag(:span, '', class: "glyphicon glyphicon-arrow-#{icon_class}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sortable_by
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Woods
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: database_cleaner
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: This gem adds capabilities to create sortable table headersconnect those
|
98
|
+
headers to a back end ActiveRecord sort query
|
99
|
+
email:
|
100
|
+
- greg.woods@moserit.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- MIT-LICENSE
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- app/controllers/concerns/sortable_by/params.rb
|
109
|
+
- app/helpers/sortable_by/table_helper.rb
|
110
|
+
- lib/sortable_by.rb
|
111
|
+
- lib/sortable_by/configuration.rb
|
112
|
+
- lib/sortable_by/engine.rb
|
113
|
+
- lib/sortable_by/table_header.rb
|
114
|
+
- lib/sortable_by/version.rb
|
115
|
+
- lib/tasks/sortable_by_tasks.rake
|
116
|
+
homepage: https://github.com/moser-inc/sortable_by
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubyforge_project:
|
136
|
+
rubygems_version: 2.6.12
|
137
|
+
signing_key:
|
138
|
+
specification_version: 4
|
139
|
+
summary: Simple tool for sorting tables of data in rails
|
140
|
+
test_files: []
|