zable 0.0.1
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +8 -0
- data/Rakefile +29 -0
- data/lib/zable/active_record.rb +59 -0
- data/lib/zable/engine.rb +11 -0
- data/lib/zable/hash.rb +5 -0
- data/lib/zable/html.rb +154 -0
- data/lib/zable/search.rb +53 -0
- data/lib/zable/sort.rb +47 -0
- data/lib/zable/will_paginate.rb +19 -0
- data/lib/zable/zable_test_helper.rb +11 -0
- data/lib/zable.rb +11 -0
- metadata +72 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2010 YOURNAME
|
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.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'bundler/setup'
|
5
|
+
rescue LoadError
|
6
|
+
$stderr.puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rake'
|
10
|
+
require 'rdoc/task'
|
11
|
+
|
12
|
+
require 'rake/testtask'
|
13
|
+
|
14
|
+
Rake::TestTask.new(:test) do |t|
|
15
|
+
t.libs << 'lib'
|
16
|
+
t.libs << 'test'
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = false
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :test
|
22
|
+
|
23
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
24
|
+
rdoc.rdoc_dir = 'rdoc'
|
25
|
+
rdoc.title = 'Zable'
|
26
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
27
|
+
rdoc.rdoc_files.include('README.rdoc')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Zable
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
PAGE_DEFAULTS = { 'num' => 1, 'size' => 2 }
|
6
|
+
|
7
|
+
def scoped_for_sort(params, scoped_object)
|
8
|
+
hash = (params[:sort] || {}).stringify_keys
|
9
|
+
scoped_object = scoped_object.for_sort_params(hash) unless hash.empty?
|
10
|
+
scoped_object
|
11
|
+
end
|
12
|
+
|
13
|
+
def scoped_for_search(params, scoped_object)
|
14
|
+
hash = (params[:search] || {}).stringify_keys
|
15
|
+
scoped_object = scoped_object.for_search_params(hash) unless hash.empty?
|
16
|
+
scoped_object
|
17
|
+
end
|
18
|
+
|
19
|
+
def scoped_for_paginate(params, scoped_object)
|
20
|
+
page = (PAGE_DEFAULTS.merge(params[:page] || {})).stringify_keys
|
21
|
+
scoped_object.paginate :page => page['num'], :per_page => page['size']
|
22
|
+
end
|
23
|
+
|
24
|
+
def populate(params={})
|
25
|
+
obj = scoped_for_sort(params, self)
|
26
|
+
obj = scoped_for_search(params, obj)
|
27
|
+
scoped_for_paginate(params, obj)
|
28
|
+
end
|
29
|
+
|
30
|
+
module Helpers
|
31
|
+
def attribute_columns_only
|
32
|
+
self.column_names.reject { |c|
|
33
|
+
is_foreign_key?(c) || is_rails_column?(c)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def is_foreign_key?(column_name)
|
39
|
+
!(self.reflect_on_all_associations(:belongs_to).detect do |e|
|
40
|
+
if (e.options.has_key? :foreign_key)
|
41
|
+
e.options[:foreign_key] == column_name
|
42
|
+
else
|
43
|
+
"#{e.name}_id" == column_name
|
44
|
+
end
|
45
|
+
end.nil?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def is_rails_column?(column_name)
|
49
|
+
%w{id created_at updated_at}.include?(column_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
ActiveRecord::Base.send :extend, Zable::ActiveRecord::ClassMethods
|
59
|
+
ActiveRecord::Base.send :extend, Zable::ActiveRecord::ClassMethods::Helpers
|
data/lib/zable/engine.rb
ADDED
data/lib/zable/hash.rb
ADDED
data/lib/zable/html.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
module Zable
|
2
|
+
module Html
|
3
|
+
|
4
|
+
## Table header methods
|
5
|
+
def table_header(klass, columns)
|
6
|
+
content_tag :thead do
|
7
|
+
table_header_row(klass, columns)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def table_header_row(klass, columns)
|
12
|
+
content_tag :tr do
|
13
|
+
table_header_cells(klass, columns)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def table_header_cells(klass, columns)
|
18
|
+
columns.inject("".html_safe) do |str, attr|
|
19
|
+
str << table_header_cell(klass, attr, columns)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def table_header_cell(klass, attr, columns)
|
24
|
+
content_tag :th, :id => header_cell_id(klass, attr) do
|
25
|
+
header_cell_content(attr, columns)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def header_cell_content(attr, columns)
|
30
|
+
str = link_to_if attr[:sort], header_cell_link_text(attr), header_cell_href(attr, columns)
|
31
|
+
str << header_cell_sort_image(attr)
|
32
|
+
end
|
33
|
+
|
34
|
+
def header_cell_id(klass, attr)
|
35
|
+
"#{idify klass.name}-#{idify attr[:name]}".html_safe
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_url
|
39
|
+
"#{controller.request.fullpath.split("?")[0]}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def param(param_type, param_key, attr)
|
43
|
+
"#{param_type}[#{param_key}]=#{attr}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def sort_params(attr)
|
47
|
+
params = []
|
48
|
+
params << param(:sort, "attr", attr[:name])
|
49
|
+
params << param(:sort, "order", attr[:sort_order]) if attr[:sorted?]
|
50
|
+
params.join("&".html_safe)
|
51
|
+
end
|
52
|
+
|
53
|
+
def search_params(params)
|
54
|
+
params.to_a.collect do |param|
|
55
|
+
param(:search, param.first, param.second)
|
56
|
+
end.join("&".html_safe)
|
57
|
+
end
|
58
|
+
|
59
|
+
def sort_arrow_image_file(attr)
|
60
|
+
attr[:sort_order] == :desc ? "common/ascending.gif" : "common/descending.gif"
|
61
|
+
end
|
62
|
+
|
63
|
+
def header_cell_href(attr, columns)
|
64
|
+
search = columns.instance_variable_get :@search_params
|
65
|
+
extra_params = columns.instance_variable_get(:@_extra_params) || {}
|
66
|
+
all_params = [sort_params(attr), search_params(search), extra_params.to_query.html_safe].reject(&:blank?).join("&".html_safe)
|
67
|
+
current_url << "?".html_safe << all_params
|
68
|
+
end
|
69
|
+
|
70
|
+
def header_cell_link_text(attr)
|
71
|
+
(attr[:title] || attr[:name].to_s.titleize).html_safe
|
72
|
+
end
|
73
|
+
|
74
|
+
def header_cell_sort_image(attr)
|
75
|
+
return ''.html_safe unless attr[:sort] && attr[:sorted?]
|
76
|
+
arrow = sort_arrow_image_file(attr)
|
77
|
+
image_tag arrow
|
78
|
+
end
|
79
|
+
|
80
|
+
## Table body methods
|
81
|
+
def table_body(collection, columns, args)
|
82
|
+
content_tag :tbody do
|
83
|
+
return empty_table_body_row(columns,args) if collection.empty?
|
84
|
+
(table_body_rows(collection, columns) || "".html_safe) + (args[:append] || "".html_safe)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def empty_table_body_row(columns, args)
|
89
|
+
content_tag :tr, :class => 'empty', :id=> "zable-empty-set" do
|
90
|
+
content_tag :td, :colspan => columns.size do
|
91
|
+
(args[:empty_message] || "No items found.").html_safe
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def table_body_rows(collection, columns)
|
97
|
+
collection.inject("".html_safe) do |str, elem|
|
98
|
+
str << table_body_row(elem, columns)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def table_body_row(elem, columns)
|
103
|
+
content_tag :tr, :id => body_row_id(elem), :class => body_row_class do
|
104
|
+
table_body_row_cells(elem, columns)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def table_body_row_cells(elem, columns)
|
109
|
+
columns.inject("".html_safe) do |str, ac|
|
110
|
+
block = ac[:block] if ac.has_key?(:block)
|
111
|
+
str << table_body_row_cell(ac, elem, &block)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def table_body_row_cell(ac, elem)
|
116
|
+
content_tag(:td, :id => body_cell_id(ac, elem)) do
|
117
|
+
if block_given?
|
118
|
+
yield elem
|
119
|
+
else
|
120
|
+
val = elem.send(ac[:name])
|
121
|
+
val.respond_to?(:strftime) ? val.strftime("%m/%d/%Y") : val.to_s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def body_row_id(elem)
|
127
|
+
"#{idify_class_name(elem)}-#{elem.id}".html_safe
|
128
|
+
end
|
129
|
+
|
130
|
+
def body_cell_id(ac, elem)
|
131
|
+
"#{idify_class_name(elem)}-#{elem.id}-#{idify ac[:name]}".html_safe
|
132
|
+
end
|
133
|
+
|
134
|
+
def body_row_class
|
135
|
+
cycle("odd", "even", name: "zable_cycle")
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
def idify_class_name(elem)
|
140
|
+
idify elem.class.name
|
141
|
+
end
|
142
|
+
|
143
|
+
def idify(val)
|
144
|
+
val.to_s.demodulize.underscore.dasherize
|
145
|
+
end
|
146
|
+
|
147
|
+
def tag_args(args)
|
148
|
+
tag_args = {}
|
149
|
+
tag_args[:class] = args[:table_class].join(" ".html_safe) if args[:table_class] and !args[:table_class].empty?
|
150
|
+
tag_args
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
data/lib/zable/search.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Zable
|
2
|
+
module Search
|
3
|
+
module ActiveRecord
|
4
|
+
module ClassMethods
|
5
|
+
def inherited(subclass)
|
6
|
+
subclass.class_eval do
|
7
|
+
scope :for_search_params, -> search_params { inject_search_scopes(search_params) }
|
8
|
+
end
|
9
|
+
super(subclass)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Allows +Model.for_search_params+ to do equality-based searching for the given +attr_names+.
|
13
|
+
# Date attributes (ending in "_on") will take Ruby-parseable date strings as well as the common
|
14
|
+
# US case "mm/dd/yyyy".
|
15
|
+
#
|
16
|
+
# searchable :first_name, :last_name, :born_on
|
17
|
+
def searchable(*attr_names)
|
18
|
+
attr_names.each do |attr_name|
|
19
|
+
scope "search_#{attr_name}", -> attr_value do
|
20
|
+
where(attr_name => (attr_name =~ /_on$/ ? parse_date_string(attr_value) : attr_value))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def inject_search_scopes(search_params)
|
28
|
+
non_empty_search_params = search_params.try(:reject_empty_values) || {}
|
29
|
+
|
30
|
+
non_empty_search_params.to_a.inject(self) { |result,value|
|
31
|
+
scope_for_search_attribute(result, value)
|
32
|
+
} unless non_empty_search_params.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_date_string(str)
|
36
|
+
str =~ /\d\d\/\d\d\/\d{4}/ ? Date.strptime(str, '%m/%d/%Y') : str.to_date
|
37
|
+
end
|
38
|
+
|
39
|
+
def scope_name_for_attribute(type, attr)
|
40
|
+
"#{type}_#{attr}".to_sym
|
41
|
+
end
|
42
|
+
|
43
|
+
def scope_for_search_attribute(target, tuple)
|
44
|
+
attr = tuple.first
|
45
|
+
target.send scope_name_for_attribute(:search, attr), tuple.second
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
ActiveRecord::Base.send :extend, Zable::Search::ActiveRecord::ClassMethods
|
data/lib/zable/sort.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Zable
|
2
|
+
module Sort
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def inherited(subclass)
|
8
|
+
subclass.class_eval do
|
9
|
+
scope :for_sort_params, -> sort_params { inject_sort_scope(sort_params) }
|
10
|
+
end
|
11
|
+
super(subclass)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Allows +Model.for_sort_params+ to sort for the given +attr_names+ for the common sorting
|
15
|
+
# case (i.e. "attr_name ASC" or "attr_name DESC")
|
16
|
+
#
|
17
|
+
# sortable :last_name, :created_at
|
18
|
+
def sortable(*attr_names)
|
19
|
+
attr_names.each do |attr_name|
|
20
|
+
scope "sort_#{attr_name}", -> options { order("#{table_name}.#{attr_name} #{options[:order]}") }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def inject_sort_scope(sort_params)
|
27
|
+
return unless sort_params
|
28
|
+
self.send scope_for_sort_attribute(sort_params), sort_params
|
29
|
+
end
|
30
|
+
|
31
|
+
def scope_name_for_attribute(type, attr)
|
32
|
+
"#{type}_#{attr}".to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
def scope_for_sort_attribute(sort_params)
|
36
|
+
sort_params.stringify_keys!
|
37
|
+
sort_params['order'] = 'ASC' if sort_params['order'].nil? || sort_params['order'].upcase != "DESC"
|
38
|
+
scope_name_for_attribute(:sort, sort_params['attr'])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ActiveRecord::Base.send :extend, Zable::Sort::ActiveRecord::ClassMethods
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'will_paginate/view_helpers/link_renderer'
|
2
|
+
|
3
|
+
module Zable
|
4
|
+
module WillPaginate
|
5
|
+
class LinkWithParamsRenderer < ::WillPaginate::ViewHelpers::LinkRenderer
|
6
|
+
|
7
|
+
def initialize(params = {})
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def default_url_params
|
14
|
+
super.merge(@params)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/zable.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Derek Croft
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: will_paginate
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: HTML searching, sorting and pagination made dead simple
|
31
|
+
email:
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- lib/zable/active_record.rb
|
37
|
+
- lib/zable/engine.rb
|
38
|
+
- lib/zable/hash.rb
|
39
|
+
- lib/zable/html.rb
|
40
|
+
- lib/zable/search.rb
|
41
|
+
- lib/zable/sort.rb
|
42
|
+
- lib/zable/will_paginate.rb
|
43
|
+
- lib/zable/zable_test_helper.rb
|
44
|
+
- lib/zable.rb
|
45
|
+
- MIT-LICENSE
|
46
|
+
- Rakefile
|
47
|
+
- README.rdoc
|
48
|
+
homepage:
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.21
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: HTML tables
|
72
|
+
test_files: []
|