listalicious 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jeremy Jackson
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.
@@ -0,0 +1,260 @@
1
+ h1. Listalicious
2
+
3
+ Semantic listing; a semantic way to build datagrid structures in Rails.
4
+
5
+ h2. The Story
6
+
7
+ Ok, so I really liked what came of my navigasmic gem, and it's proved itself useful and flexible. I'm also working on unifying some aspects of a CMS and decided that datagrids should be unified to some degree so I can simplify the generators, make broad changes if I need to, and have a potentially more efficient way to create them.
8
+
9
+ So I started this project to do just that. I quickly learned that datagrids aren't especially easy to make amazing, and there's a lot of things that one might want to do with them... grouping (with a row that creates a separator), sorting, ordering, additional informational rows, footers, ajax pagination, etc. etc. etc.. The list is actually pretty long, and I wrote a lot of those things, but it's nowhere near perfect. I sort of don't think it's possible with how I've approached it this time around. Anyhow, I thought I could get something to a point that simplified the datagrid process, and I believe I've succeeded in providing a good start. I opted out of adding several features for the time being until I need them -- at which point I might have a better understanding of how I could approach it better. I cover some of my thoughts below in this document.
10
+
11
+ So, as always, I set up some requirements for what the project should do, and here's what I came up with (a lot of these are standard):
12
+
13
+ * Should be simple
14
+ * Should be easily customizable
15
+ * Should handle sorting
16
+ * Should handle grouping
17
+ * Should handle ordering columns
18
+ * Should handle additional informational rows
19
+ * Should be pleasant to use
20
+ * Should use less code to create than it generates
21
+
22
+ I wrote a DSL that met those requirements:
23
+
24
+ <pre>
25
+ <% semantic_list_for @users do |l| %>
26
+ <%= l.head do %>
27
+ <%= l.column 'Name', :sort => 'last_name' %>
28
+ <%= l.column 'Email Address' %>
29
+ <% end %>
30
+ <%= l.columns do |user, index| %>
31
+ <%= l.column "#{user.first_name} #{user.last_name}" %>
32
+ <%= l.column link_to(user.email, "mailto:#{user.email}") %>
33
+ <%= l.controls link_to('edit', edit_user_path(user)) %>
34
+ <% end %>
35
+ <%= l.foot do %>
36
+ <%= l.full_column will_paginate(@users) %>
37
+ <% end %>
38
+ <% end %>
39
+ </pre>
40
+
41
+ I liked this, but could it be reduced further if the use case allowed? I came up with the following, which simplifies it, but comes with a minor snag. Note the line that instantiates a new User object. This is needed if there's no objects in the collection you pass it, because it needs to know about the object while it's building the columns.
42
+
43
+ <pre>
44
+ <% semantic_list_for @users do |l| %>
45
+ <%= l.columns [:head, :body] do |user, index| %>
46
+ <% user ||= User.new # this DSL requires a user object to be here %>
47
+ <%= l.column user.login, :title => 'Login', :sort => 'login' %>
48
+ <%= l.column user.email, :title => 'Email Address', :sort => 'email' %>
49
+ <%= l.controls link_to('edit', edit_user_path(user)) %>
50
+ <% end %>
51
+ <% end %>
52
+ </pre>
53
+
54
+ Clearly there's some ups and downs here, so I implemented both DSLs and leave it to your discretion to figure out which one is best to use in your case.
55
+
56
+ Since using builders on previous projects has worked out fairly well, I ventured down that path again. There's a single builder provided for now, TableBuilder (which inherits from GenericBuilder). GenericBuilder provides some basic functionality (ordering links for example), and isn't intended to be used as a builder by itself. And of course, the builders can always be extended or replaced if you need more custom markup.
57
+
58
+ h2. Installation
59
+
60
+ The gem is hosted on gemcutter, so *if you haven't already*, add it as a gem source:
61
+
62
+ <pre>
63
+ sudo gem sources -a http://gemcutter.org/
64
+ </pre>
65
+
66
+ Then install the listalicious gem:
67
+
68
+ <pre>
69
+ sudo gem install listalicious
70
+ </pre>
71
+
72
+ If you would like to get the default JS and CSS files you can use the generator after installing:
73
+
74
+ <pre>
75
+ [no javascript or css provided yet]
76
+ </pre>
77
+
78
+ h2. Usage
79
+
80
+ Listalicious works fine just as a view helper, but it also handles ordering of the lists. If you plan on using ordering in the lists you'll have to add a bit to the model and controller. All of the methods are added to the model by using sortable_fields, and the :order option on finders can be generated by using sort_order_from in the controller.
81
+
82
+ h3. Models
83
+
84
+ <pre>
85
+ class User < ActiveRecord::Base
86
+ # communicates to Listalicious how a list of this model can be ordered
87
+ sortable_fields :first_name, :last_name, :login, :email, :default => :login
88
+ end
89
+ </pre>
90
+
91
+ h3. Controllers
92
+
93
+ <pre>
94
+ def index
95
+ @users = User.paginate :page => params[:page], :per_page => 2, :order => User.sort_order_from(params)
96
+ respond_to do |format|
97
+ format.html # index.html.erb
98
+ format.xml { render :xml => @users }
99
+ end
100
+ end
101
+ </pre>
102
+
103
+ h3. Views (I prefer HAML, so no ERB examples, but it should work with ERB fine as well)
104
+
105
+ It's important to note that all the methods take blocks or content strings. For instance, look at the extra and controls methods in the simple usage example.
106
+
107
+ It's also important to notice that the controls cell is not being counted in the columns. This is because I position these absolutely and do a hover event on them.. I'll likely take this out shortly so it doesn't effect anyone else.
108
+
109
+ *Simple Usage*
110
+
111
+ <pre>
112
+ - semantic_list_for @users, :html => {:class => 'list'} do |l|
113
+ = l.columns [:head, :body] do |user, index|
114
+ - user ||= User.new # this method requires a user object
115
+ = l.column user.login, :title => 'Login', :sort => 'login', :width => '20%'
116
+ = l.column user.email, :title => 'Email Address', :sort => 'email'
117
+ = l.extra do
118
+ = "You can add more information about #{user.first_name} here."
119
+ = l.controls link_to('edit', edit_user_path(user))
120
+ </pre>
121
+ ...produces...
122
+ <pre>
123
+ <table class="list semantic-list" id="user_list">
124
+ <thead>
125
+ <tr class="header">
126
+ <th width="20%"><a class="sort-ascending" href="?user_sort_desc=login">Login</a></th>
127
+ <th><a href="?user_sort_asc=email">Email Address</a></th>
128
+ </tr>
129
+ </thead>
130
+ <tbody>
131
+ <tr class="even">
132
+ <td>jejacks0n</td>
133
+ <td>jeremy@email.com</td>
134
+ <td class="controls"><a href="/users/1/edit">edit</a></td>
135
+ </tr>
136
+ <tr class="even">
137
+ <td colspan="2">You can add more information about Jeremy here.</td>
138
+ </tr>
139
+ <tr class="odd">
140
+ <td>user1</td>
141
+ <td>user1@email.com</td>
142
+ <td class="controls"><a href="/users/2/edit">edit</a></td>
143
+ </tr>
144
+ <tr class="odd">
145
+ <td colspan="2">You can add more information about User here.</td>
146
+ </tr>
147
+ </tbody>
148
+ </table>
149
+ </pre>
150
+
151
+ *Extended Usage*
152
+
153
+ <pre>
154
+ - semantic_list_for @users do |l|
155
+ = l.head do
156
+ = l.column 'Name', :sort => 'last_name', :width => '20%'
157
+ = l.column 'Email Address'
158
+ = l.columns do |user, index|
159
+ = l.column "#{user.first_name} #{user.last_name}"
160
+ = l.column :html => {:class => 'email'} do
161
+ = link_to(user.email, "mailto:#{user.email}")
162
+ = l.controls link_to('edit', edit_protosite_user_path(user))
163
+ = l.foot do
164
+ = l.full_column will_paginate(l.collection)
165
+ </pre>
166
+ ...produces...
167
+ <pre>
168
+ <table class="semantic-list" id="user_list">
169
+ <thead>
170
+ <tr class="header">
171
+ <th width="20%"><a class="sort-ascending" href="?user_sort_asc=last_name">Name</a></th>
172
+ <th>Email Address</th>
173
+ </tr>
174
+ </thead>
175
+ <tbody>
176
+ <tr class="even">
177
+ <td>Jeremy Jackson</td>
178
+ <td class="email"><a href="mailto:jeremy@email.com">jeremy@email.com</a></td>
179
+ <td class="controls"><a href="/users/1/edit">edit</a></td>
180
+ </tr>
181
+ <tr class="odd">
182
+ <td>User 1</td>
183
+ <td class="email"><a href="mailto:user1@email.com">user1@email.com</a></td>
184
+ <td class="controls"><a href="/users/2/edit">edit</a></td>
185
+ </tr>
186
+ </tbody>
187
+ <tfoot>
188
+ <tr>
189
+ <th colspan="2">
190
+ <div class="pagination">[removed for your sanity]</div>
191
+ </th>
192
+ </tr>
193
+ </tfoot>
194
+ </table>
195
+ </pre>
196
+
197
+ *Grouping* -- You can group lists, and this will add extra header rows as separators. This will include sort links if those were provided as well.
198
+
199
+ <pre>
200
+ - semantic_list_for @users, :group_by => :login do |l|
201
+ </pre>
202
+
203
+ *Sorting* -- Sorting requires javascript. It's part of the javascript code that comes with Listalicious and requires Prototype.js. If you want to add sorting to the list, just provide a url for a sort action and it will put that into the HTML5 data-sorturl attribute. You can use your own javascript if you would like.
204
+
205
+ <pre>
206
+ - semantic_list_for @users, :sort_url => { :action => 'sort' } do |l|
207
+ </pre>
208
+
209
+ *Additional JS Functionality* -- There a few functions that are javascript specific. You can add any additional functionality this way as well, since we're using unobtrusive JS to get the following features accomplished.
210
+
211
+ You can make the list multi-selectable. There isn't any pre-bundled functionality to do actions on the selected items, but you can fetch the list of selected items by using JS.
212
+
213
+ <pre>
214
+ - semantic_list_for @users, :selectable => true do |l|
215
+ </pre>
216
+
217
+ You can make the "extra" information rows expandable. If you add this, the extra rows won't show unless the row they belong to is highlighted.
218
+
219
+ <pre>
220
+ - semantic_list_for @users, :expandable => true do |l|
221
+ </pre>
222
+
223
+ h3. UL / OL Datagrids
224
+
225
+ The nature of doing a UL / OL based datagrid requires a certain level of CSS, and I haven't had time or reason to provide that level yet. When I finish up this project and include the JS and CSS needed for it (in a generator) I may add a builder for this, but there isn't one currently.
226
+
227
+ h3. Other
228
+
229
+ It's important to note that a lot of the functionality of these lists do not play nicely with one another -- I don't believe this is a shortcoming, and consider it more an effort to avoid overkill. A good example of this is the sortable features. For example, if you need the extra information and sorting together, you should consider using the :expandable => true option. Grouping and sorting don't play together nicely for obvious reasons as well. The javascript handles moving the extra container, in sorting, but doesn't attempt to do it gracefully. If I do the ListBuilder (UL/OL) to compliment the TableBuilder, it would likely handle these things somewhat better, but I haven't had a need for it yet.
230
+
231
+ I'm kicking around plans for how to make it more featureful without the annoying conflicts mentioned above. If you have any thoughts let me know, but I expect my solution will come by breaking up the builders by features.. So, instead of having a single TableBuilder, there would be a SortableTableBuilder, and an InformationalTableBuilder, and a MultiSelectTableBuilder, etc.. not sure how that would play out, but that's what I'm thinking might resolve some of the annoyances mentioned above.
232
+
233
+ And, as always, you can create your own builder by extending one of the existing ones, or by creating one from scratch.
234
+
235
+ Then just specify your builder, or do it as a configuration.
236
+
237
+ <pre>
238
+ semantic_list_for @users, :builder => MyCustomBuilder do
239
+ </pre>
240
+
241
+ <pre>
242
+ Listalicious::SemanticListHelper.builder = MyCustomBuilder
243
+ </pre>
244
+
245
+ h2. Documentation
246
+
247
+ RDoc documentation _should_ be automatically generated after each commit and made available on the "rdoc.info website":http://rdoc.info/projects/jejacks0n/listalicious.
248
+
249
+ Documentation is pretty sparse right now.
250
+
251
+ h2. Compatibility
252
+
253
+ I'm only testing with the latest Rails 2.4.x stable release, and it should work under Rails 2.3.x as well. Feel free to add backwards compatibility if you need it.
254
+
255
+ h2. Project Info
256
+
257
+ Listalicious is hosted on Github: "http://github.com/jejacks0n/listalicious":http://github.com/jejacks0n/listalicious, and the gem is available on Gemcutter: "http://gemcutter.org/gems/listalicious":http://gemcutter.org/gems/listalicious
258
+
259
+
260
+ Copyright (c) Jeremy Jackson, released under the MIT license.
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "listalicious"
8
+ gem.summary = %Q{Semantic lists (datagrids) for Rails}
9
+ gem.description = %Q{Semantic listing; a semantic way to build datagrid structures in Rails.}
10
+ gem.email = "jejacks0n@gmail.com"
11
+ gem.homepage = "http://github.com/jejacks0n/listalicious"
12
+ gem.authors = ["Jeremy Jackson"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "listalicious #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,32 @@
1
+ module Listalicious
2
+ class GenericBuilder
3
+
4
+ attr_accessor :template, :collection
5
+
6
+ def initialize(template, collection, options = {}, &proc)
7
+ @template, @collection, @options = template, collection, options
8
+ @object_name = collection.first.class.name.underscore unless collection.empty?
9
+
10
+ render(options.delete(:html), &proc)
11
+ end
12
+
13
+ def render(options, &proc); end
14
+
15
+ def sortable_link(contents, field)
16
+ sort_url, sort_direction = sortable_params(field)
17
+ template.content_tag(:a, contents, :href => "?#{sort_url}", :class => "sort-#{sort_direction == 'descending' ? 'ascending' : 'descending'}")
18
+ end
19
+
20
+ def sortable_params(field)
21
+ object_name = @object_name.nil? ? '' : "#{@object_name}_"
22
+
23
+ params = template.params.reject { |param, value| ['action', 'controller', "#{object_name}sort_desc"].include?(param) }
24
+ direction = params.delete("#{object_name}sort_asc") == field.to_s ? 'descending' : 'ascending'
25
+ method = direction == 'descending' ? "#{object_name}sort_desc" : "#{object_name}sort_asc"
26
+ params[method] = field
27
+
28
+ [params.to_query, direction]
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,148 @@
1
+ module Listalicious
2
+ class TableBuilder < GenericBuilder
3
+
4
+ attr_accessor :template, :collection
5
+
6
+ def render(options, &proc)
7
+ buffer = template.capture(self, &proc)
8
+ template.concat(template.content_tag(:table, buffer, options))
9
+ end
10
+
11
+ def head(options = {}, &proc)
12
+ column_group(:head, options, &proc)
13
+ end
14
+
15
+ def body(options = {}, &proc)
16
+ column_group(:body, options, &proc)
17
+ end
18
+
19
+ def foot(options = {}, &proc)
20
+ column_group(:foot, options, &proc)
21
+ end
22
+
23
+ def columns(scope = :body, options = {}, &proc)
24
+ raise ArgumentError, "Missing block" unless block_given?
25
+
26
+ if scope.kind_of? Array
27
+ scope.collect { |scope| column_group(scope, options, &proc) }
28
+ else
29
+ column_group(scope, options, &proc)
30
+ end
31
+ end
32
+
33
+ def column_group(scope, options = {}, &proc)
34
+ @current_scope = scope
35
+
36
+ options[:html] ||= {}
37
+
38
+ self.send("column_group_#{scope}", options, &proc)
39
+ end
40
+
41
+ def column_group_head(options = {}, &proc)
42
+ @column_count = 0
43
+
44
+ @head_wrapper = template.content_tag(:tr, template.capture(collection.first, 0, &proc),
45
+ options[:html].merge({:class => template.add_class(options[:html][:class], 'header')}))
46
+ template.content_tag(:thead, @head_wrapper, options.delete(:wrapper_html))
47
+ end
48
+
49
+ def column_group_body(options = {}, &proc)
50
+ buffer = ''
51
+ return unless collection.first.present?
52
+ collection.each_with_index do |record, index|
53
+ @column_count = 0
54
+
55
+ if @options[:grouped_by]
56
+ buffer << @head_wrapper if record[@options[:grouped_by]] != @last_row_grouping
57
+ @last_row_grouping = record[@options[:grouped_by]]
58
+ end
59
+
60
+ @cycle = template.cycle('even', 'odd');
61
+ buffer << template.content_tag(:tr, template.capture(record, index, &proc),
62
+ options[:html].merge({:class => template.add_class(options[:html][:class], @cycle)}))
63
+ buffer << template.content_tag(:tr, @extra,
64
+ options[:html].merge({:class => template.add_class(options[:html][:class], @cycle)})) if @extra.present?
65
+ @extra = nil
66
+ end
67
+ template.content_tag(:tbody, buffer, options.delete(:wrapper_html))
68
+ end
69
+
70
+ def column_group_foot(options = {}, &proc)
71
+ buffer = template.content_tag(:tr, template.capture(collection.first, 0, &proc), options[:html])
72
+ template.content_tag(:tfoot, buffer, options.delete(:wrapper_html))
73
+ end
74
+
75
+ def column(*args, &proc)
76
+ options = args.extract_options!
77
+ contents = options == args.first ? nil : args.first
78
+
79
+ if @current_scope == :body
80
+ contents = template.capture(self, &proc) if block_given? && collection.first.present?
81
+ else
82
+ contents = options[:title] || contents
83
+ end
84
+
85
+ @column_count = @column_count + 1
86
+ self.send("#{@current_scope}_column", contents, options)
87
+ end
88
+
89
+ def full_column(contents = nil, options = {}, &proc)
90
+ raise ArgumentError, "Must provide a string or a block" if !block_given? && contents.nil?
91
+ contents ||= template.capture(self, &proc)
92
+
93
+ options[:html] ||= {}
94
+ options[:html][:colspan] ||= @column_count
95
+
96
+ options[:wrapper_html] ||= {}
97
+ options[:wrapper_html][:class] = template.add_class(options[:wrapper_html][:class], 'full-column')
98
+
99
+ @extra = self.send("#{@current_scope}_column", contents, options)
100
+ end
101
+
102
+ def controls(contents = nil, options = {}, &proc)
103
+ return unless @current_scope == :body
104
+ raise ArgumentError, "Must provide a string or a block" if !block_given? && contents.nil?
105
+ contents ||= template.capture(self, &proc)
106
+
107
+ options[:html] ||= {}
108
+ options[:html][:class] = template.add_class(options[:html][:class], 'controls')
109
+
110
+ self.send("#{@current_scope}_column", contents, options)
111
+ end
112
+
113
+ def extra(contents = nil, options = {}, &proc)
114
+ return unless @current_scope == :body
115
+ raise ArgumentError, "Must provide a string or a block" if !block_given? && contents.nil?
116
+ contents ||= template.capture(self, &proc)
117
+
118
+ options[:html] ||= {}
119
+ options[:html][:colspan] ||= @column_count
120
+
121
+ options[:wrapper_html] ||= {}
122
+ options[:wrapper_html][:class] = template.add_class(options[:wrapper_html][:class], 'extra')
123
+
124
+ @extra = self.send("#{@current_scope}_column", contents, options)
125
+ ''
126
+ end
127
+
128
+ def head_column(contents, options = {})
129
+ options[:html] ||= {}
130
+ options[:html][:width] ||= options[:width]
131
+
132
+ contents = sortable_link(contents, options[:sort]) if options[:sort]
133
+
134
+ template.content_tag(:th, contents, options.delete(:html))
135
+ end
136
+
137
+ def body_column(contents, options = {})
138
+ template.content_tag(:td, contents, options.delete(:html))
139
+ end
140
+
141
+ def foot_column(contents, options = {})
142
+ contents = sortable_link(contents, options[:sort]) if options[:sort]
143
+
144
+ template.content_tag(:th, contents, options.delete(:html))
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,82 @@
1
+ # coding: utf-8
2
+ require File.join(File.dirname(__FILE__), *%w[builders generic_builder])
3
+ require File.join(File.dirname(__FILE__), *%w[builders table_builder])
4
+
5
+ module Listalicious #:nodoc:
6
+
7
+ # Semantic list helper methods
8
+ #
9
+ # Example Usage:
10
+ #
11
+ module SemanticListHelper
12
+
13
+ @@builder = ::Listalicious::TableBuilder
14
+ mattr_accessor :builder
15
+
16
+ def semantic_list_for(collection, *args, &proc)
17
+ raise ArgumentError, "Missing block" unless block_given?
18
+
19
+ options = args.extract_options!
20
+
21
+ options[:html] ||= {}
22
+ options[:html][:class] = add_class(options[:html][:class], 'semantic-list')
23
+ options[:html][:id] ||= collection.first ? "#{collection.first.class.name.underscore}_list" : 'semantic_list'
24
+
25
+ options[:html][:class] = add_class(options[:html][:class], 'actionable') if options[:actionable]
26
+ options[:html][:class] = add_class(options[:html][:class], 'selectable') if options[:selectable]
27
+
28
+ if options[:sort_url]
29
+ options[:html][:class] = add_class(options[:html][:class], 'sortable')
30
+ options[:html]['data-sorturl'] = url_for(options[:sort_url])
31
+ end
32
+
33
+ builder = options[:builder] || TableBuilder
34
+ builder.new(@template, collection, options, &proc)
35
+ end
36
+
37
+ def add_class(classnames, classname)
38
+ out = (classnames.is_a?(String) ? classnames.split(' ') : []) << classname
39
+ out.join(' ')
40
+ end
41
+
42
+ end
43
+
44
+ module ActiveRecordExtensions # :nodoc:
45
+
46
+ def self.included(base) # :nodoc:
47
+ return if base.kind_of?(::Listalicious::ActiveRecordExtensions::ClassMethods)
48
+ base.class_eval do
49
+ extend ClassMethods
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+
55
+ attr_accessor :default_sort_field
56
+
57
+ def sortable_fields(*args)
58
+ options = args.extract_options!
59
+ @acceptable_sort_fields = args
60
+ @default_sort_field = options[:default]
61
+ end
62
+
63
+ def acceptable_sort_field?(column)
64
+ column.present? ? @acceptable_sort_fields.include?(column.to_sym) : false
65
+ end
66
+
67
+ def sort_order_from(params)
68
+ field = params["#{self.name.underscore}_sort_asc"] || params["#{self.name.underscore}_sort_desc"]
69
+ field = @default_sort_field.to_s unless acceptable_sort_field?(field)
70
+
71
+ method = (params["#{self.name.underscore}_sort_desc".to_sym] == field) ? 'DESC' : 'ASC'
72
+ "#{field} #{method}" unless field.blank?
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ ActionController::Base.helper Listalicious::SemanticListHelper
82
+ ActiveRecord::Base.class_eval { include ::Listalicious::ActiveRecordExtensions }
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{listalicious}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeremy Jackson"]
12
+ s.date = %q{2010-01-24}
13
+ s.description = %q{Semantic listing; a semantic way to build datagrid structures in Rails.}
14
+ s.email = %q{jejacks0n@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.textile",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/builders/generic_builder.rb",
27
+ "lib/builders/table_builder.rb",
28
+ "lib/listalicious.rb",
29
+ "listalicious.gemspec",
30
+ "test/helper.rb",
31
+ "test/test_listalicious.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/jejacks0n/listalicious}
34
+ s.rdoc_options = ["--charset=UTF-8"]
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = %q{1.3.5}
37
+ s.summary = %q{Semantic lists (datagrids) for Rails}
38
+ s.test_files = [
39
+ "test/helper.rb",
40
+ "test/test_listalicious.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
49
+ else
50
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
54
+ end
55
+ end
56
+
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'listalicious'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestListalicious < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: listalicious
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Jackson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-24 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Semantic listing; a semantic way to build datagrid structures in Rails.
26
+ email: jejacks0n@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.textile
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.textile
39
+ - Rakefile
40
+ - VERSION
41
+ - lib/builders/generic_builder.rb
42
+ - lib/builders/table_builder.rb
43
+ - lib/listalicious.rb
44
+ - listalicious.gemspec
45
+ - test/helper.rb
46
+ - test/test_listalicious.rb
47
+ has_rdoc: true
48
+ homepage: http://github.com/jejacks0n/listalicious
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.5
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Semantic lists (datagrids) for Rails
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/test_listalicious.rb