Permalink
Browse files

First commit of Active Scaffold List Filters for official Rails 2.1

  • Loading branch information...
lightningdb committed Jul 24, 2008
0 parents commit 0a186de48fbba927fe8fbdf5da6765b8f5bdb183
@@ -0,0 +1 @@
+.svn
@@ -0,0 +1,11 @@
+v0.31 (April 8, 2008)
+Fixed small bug in loading the plugin
+
+v0.3 (March 8, 2008)
+Re-factored of how custom filters work. They are now plugin base and are mix'ed in with the list_filter data type giving far more flexibility.
+
+v0.2 (March 2, 2008)
+* Fixed filters to use per scaffold sessions so they don't interfere with each other.
+
+v0.1 (Feb 28, 2008)
+* Initial Release
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Tys von Gaza (tys@gotoybox.com)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
90 README
@@ -0,0 +1,90 @@
+=ActiveScaffoldListFilters=
+----
+ Copyright (c) 2008 Tys von Gaza (tys@gotoybox.com)
+
+ MIT License use as you wish, see MIT-LICENSE file
+
+ Version 0.31
+
+[http://activescaffoldlistfilters.googlecode.com/files/Picture%201.png Preview]
+
+Add a filter menu at the top of the ActiveScaffold List view. The filter can be whatever you dream up. Code inspiration: activescaffoldexport plugin.
+
+==Todo/Know Bugs (Please Help!):==
+ * If you have activescaffoldexport installed, please see this kb issue: http://code.google.com/p/activescaffoldexport/issues/detail?id=4
+ * Have the reset link not close the filter, but actually reset it.
+ * Ability to save filters (and table orderings!) to the database, this will be a separate plugin.
+ * Association filter may not work with has_and_belongs_to_many associations.
+ * Tests need to be created! I don't have much experience here, help or advice welcome.
+ * Ability to get a count of rows in the _current_ list for each association in the association filter. Ie color the options different if there is results or not. Tricky, need to update the filter along with the list. Thoughts?
+ * Doesn't javascript degrade... not an issue in our project so won't fix.
+
+==Ideas for filters:==
+ * Map, 100km's from point (http://en.wikipedia.org/wiki/Haversine_formula)
+ * iTunes style browser (update categories as you filter)
+ * Dates (On a year, month, day or between two dates)
+
+==Installing:==
+
+ # Copy into plugin directory
+ # Add to your application.rb default action list or to an individual controller:
+ app/controllers/application.rb
+ {{{
+class ApplicationController < ActionController::Base
+ ActiveScaffold.set_defaults do |config|
+ config.actions.add :list_filter
+ end
+end
+ }}}
+ or an individual controller
+ {{{
+active_scaffold "Model" do |config|
+ config.actions.add :list_filter
+end
+ }}}
+ # Add a filter
+ The form is:
+ * config.list_filter.add(:filter_type, :filter_name, {:label => "Filter Label", :other => "options"})
+ * :filter_type is the type of filter
+ * :filter_name is the name you want to use for the filter, should be unique in your scaffold
+ * {} is the options hash, :label is on the base filters, but you can set whatever you like to be used by your filter
+ ie: Add an Association (checkboxes) filter, named district, with the label Districts and method chain of model.unit.district:
+ {{{
+active_scaffold "Model" do |config|
+ config.list_filter.add(:association, :district, {:label => "District", :association => [ :unit, :district ] })
+end
+ }}}
+
+----
+
+==Creating your own filters:==
+It is simple to create your own filter.
+
+ # Choose a name to use, ie "association".
+ # Create a new view:
+ {{{
+in active_scaffold_list_filters/frontends/default/views/, ie:
+active_scaffold_list_filters/default/views/_association.rhtml
+ }}}
+ # Use this to create a way for your user to interact with your filter. You have access to a ListFilter object (see lib/data_structures/list_filter.rb), which you'll later extend with your own methods. This object allows you get your filters type, name, and options set in the controller.
+ Your filter view must set form fields. Use the list_filter_input_name(filter) to get a base name to use. If you're setting more then one field then use form array's, ie: list_filter_input_name(filter)['field2'], or if you're using checkboxes or multi select menu's you can use an array like so:
+ {{{
+list_filter_input_name(filter)[]
+ }}}
+ # Create a new List Filter in active_scaffold_list_filters/list_filters, ie: active_scaffold_list_filters/list_filters/association.rb
+ This file must have the following layout:
+ {{{
+class Association < ActiveScaffold::DataStructures::ListFilter
+
+ # Return a list of conditions based on the params
+ def conditions(params)
+ association = association_tree[association_tree.size - 1]
+ column = [association.active_record.table_name, association.primary_key_name].join('.')
+ return ["#{column} IN (?)", params]
+ end
+end
+ }}}
+ You must then define the method conditions(params) to return a list of filter conditions. These will be used to filter your table's data.
+ You have access to your the ListFilter that you're extending again with the same properties as above. You also have a list of parameters from your view.
+ This file can also contain custom methods used in your view or conditions, sky is the limit!
+ # Upload your filter object and view for others to use and improve!
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the active_scaffold_filters plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the active_scaffold_filters plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'ActiveScaffoldFilters'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
Binary file not shown.
@@ -0,0 +1,6 @@
+/* IE hacks
+ ==================================== */
+.active-scaffold-header div.actions a.list_filter {
+ background-image: none;
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../images/active_scaffold/default/filter.png', sizingMethod='crop');
+}
@@ -0,0 +1,27 @@
+.active-scaffold-header div.actions a.list_filter {
+ background-image: url(../../../images/active_scaffold/default/filter.png);
+ background-position: 1px 50%;
+ background-repeat: no-repeat;
+ padding-left: 19px;
+}
+.active-scaffold div.list_filter-view h3 {
+ font-weight: bold;
+}
+.active-scaffold div.list_filter-view div.separator {
+ clear: both;
+ padding: .5em;
+}
+.active-scaffold div.list_filter-view div.options div.separator {
+ padding: 0;
+}
+.active-scaffold div.list_filter-view div.options div.checkbox-wrapper {
+ margin-bottom: .5em;
+}
+.active-scaffold div.list_filter-view div div.checkbox-wrapper {
+ min-width: 20%;
+ margin-bottom: .25em;
+ float: left;
+}
+.active-scaffold div.list_filter-view label #delimiter {
+ font-family: monospace;
+}
@@ -0,0 +1,21 @@
+<%
+ ar_class = filter.association_tree.last.class_name.constantize
+ associated_values = ar_class.find(:all).sort {|a,b| a.to_label <=> b.to_label }
+-%>
+
+<div style='margin-bottom: 1em;'>
+<ol style='width: 100%'>
+<% associated_values.each do |value| -%>
+ <%
+ session_value = nil
+ session_value = filter_session.include? value.id.to_s unless filter_session.nil?
+ -%>
+ <li style='float: left; width: 20%;'>
+ <%= content_tag(:label,
+ check_box_tag(list_filter_input_name(filter) + "[]",
+ "#{value.id}",session_value) + "&nbsp;#{value.to_label}") %>
+ </li>
+<% end -%>
+</ol>
+<br style='clear: left;'>
+</div>
@@ -0,0 +1,38 @@
+<%
+ url_options = params_for(:action => :list_filter)
+-%>
+<% href = url_for(params_for(:action => :update_table, :escape => false).delete_if{|k,v| k == 'list_filter'}) -%>
+<%= form_remote_tag :url => href,
+ :method => :get,
+ :before => "addActiveScaffoldPageToHistory('#{href}', '#{params[:controller]}')",
+ :after => "$('#{loading_indicator_id(:action => :list_filter, :id => params[:id])}').style.visibility = 'visible';",
+ :complete => "$('#{loading_indicator_id(:action => :list_filter, :id => params[:id])}').style.visibility = 'hidden';",
+ :update => active_scaffold_content_id,
+ :html => { :href => href, :id => list_filter_form_id, :class => 'list_filter' } %>
+
+<h3><%=as_('Filters')%></h3>
+
+<% filter_config.filters.each do |filter|
+ id = params[:eid] || params[:controller]
+ session_index = "as:#{id}"
+ filter_session = session[session_index]["list_filter"] unless session[session_index]["list_filter"].nil?
+ filter_session = filter_session[filter.filter_type] unless filter_session.nil?
+ filter_session = filter_session[filter.name] unless filter_session.nil?
+%>
+ <label><%=as_(filter.label)%></label>
+ <%= render :partial => filter.filter_type, :locals => { :filter => filter, :filter_session => filter_session } -%>
+<% end %>
+
+<%= hidden_field_tag('list_filter[input]','filter') %>
+
+<a href="#" onclick="$('<%= list_filter_form_id %>').reset();$('<%= list_filter_form_id %>').onsubmit();">Reset</a>
+<%= loading_indicator_tag(:action => :list_filter) %>
+</form>
+<script type="text/javascript">
+//<![CDATA[
+ new Form.EventObserver('<%= list_filter_form_id %>', function(element, value) {
+ if (!$(element.id)) return false; // because the element may have been destroyed
+ $(element).onsubmit();
+ });
+//]]>
+</script>
@@ -0,0 +1,5 @@
+<div class="active-scaffold">
+ <div class="list_filter view">
+ <%= render :partial => 'list_filter', :locals => { :filter_config => filter_config } -%>
+ </div>
+</div>
27 init.rb
@@ -0,0 +1,27 @@
+# Include hook code here
+ActiveScaffold rescue throw "should have included ActiveScaffold plug in first. Please make sure that this plug-in comes alphabetically after the ActiveScaffold plug-in"
+
+$:.unshift(File.dirname(__FILE__))
+
+require "#{File.dirname(__FILE__)}/lib/pluginfactory.rb"
+require "#{File.dirname(__FILE__)}/lib/actions/list_filter.rb"
+require "#{File.dirname(__FILE__)}/lib/config/core.rb"
+require "#{File.dirname(__FILE__)}/lib/config/list_filter.rb"
+require "#{File.dirname(__FILE__)}/lib/data_structures/list_filter.rb"
+require "#{File.dirname(__FILE__)}/lib/data_structures/list_filters.rb"
+require "#{File.dirname(__FILE__)}/lib/helpers/id_helpers_override.rb"
+require "#{File.dirname(__FILE__)}/lib/helpers/view_helpers_override.rb"
+
+# custom filters
+#Kernel::load 'lib/list_filters/base.rb'
+#Kernel::load 'lib/list_filters/association.rb'
+
+##
+## Run the install script, too, just to make sure
+## But at least rescue the action in production
+##
+begin
+ require 'install'
+rescue
+ raise $! unless RAILS_ENV == 'production'
+end
@@ -0,0 +1,29 @@
+# Workaround a problem with script/plugin and http-based repos.
+# See http://dev.rubyonrails.org/ticket/8189
+Dir.chdir(Dir.getwd.sub(/vendor.*/, '')) do
+ ##
+ ## Copy over asset files (javascript/css/images) from the plugin directory to public/
+ ##
+
+ def copy_files(source_path, destination_path, directory)
+ source, destination = File.join(directory, source_path), File.join(RAILS_ROOT, destination_path)
+ FileUtils.mkdir(destination) unless File.exist?(destination)
+ FileUtils.cp_r(Dir.glob(source+'/*.*'), destination)
+ end
+
+ directory = File.dirname(__FILE__)
+
+ copy_files("/public", "/public", directory)
+
+ available_frontends = Dir[File.join(directory, 'frontends', '*')].collect { |d| File.basename d }
+ [ :stylesheets, :javascripts, :images].each do |asset_type|
+ path = "/public/#{asset_type}/active_scaffold"
+ copy_files(path, path, directory)
+
+ available_frontends.each do |frontend|
+ source = "/frontends/#{frontend}/#{asset_type}/"
+ destination = "/public/#{asset_type}/active_scaffold/#{frontend}"
+ copy_files(source, destination, directory)
+ end
+ end
+end
@@ -0,0 +1,76 @@
+module ActiveScaffold::Actions
+ module ListFilter
+
+ def self.included(base)
+ base.before_filter :list_filter_authorized?, :only => [:list_filter]
+ base.before_filter :init_filter_session_var
+ base.before_filter :do_list_filter
+
+ as_list_filter_plugin_path = File.join(RAILS_ROOT, 'vendor', 'plugins', as_list_filter_plugin_name, 'frontends', 'default', 'views')
+ if base.respond_to?(:generic_view_paths) && ! base.generic_view_paths.empty?
+ base.generic_view_paths.insert(0, as_list_filter_plugin_path)
+ else
+ config.inherited_view_paths << as_list_filter_plugin_path
+ end
+ end
+
+ def self.as_list_filter_plugin_name
+ # extract the name of the plugin as installed
+ /.+vendor\/plugins\/(.+)\/lib/.match(__FILE__)
+ plugin_name = $1
+ end
+
+ def init_filter_session_var
+ if !params["list_filter"].nil?
+ if params["list_filter"]["input"] == "filter"
+ active_scaffold_session_storage["list_filter"] = params["list_filter"]
+ elsif params["list_filter"]["input"] == "reset"
+ active_scaffold_session_storage["list_filter"] = nil
+ end
+ end
+ end
+
+ def list_filter
+ filter_config = active_scaffold_config.list_filter
+ respond_to do |wants|
+ wants.html do
+ if successful?
+ render(:partial => 'list_filter', :locals => { :filter_config => filter_config }, :layout => true)
+ else
+ return_to_main
+ end
+ end
+ wants.js do
+ render(:partial => 'list_filter', :locals => { :filter_config => filter_config }, :layout => false)
+ end
+ end
+ end
+
+
+ protected
+
+ def do_list_filter
+ list_session = active_scaffold_session_storage["list_filter"]
+ unless list_session.nil?
+ active_scaffold_config.list_filter.filters.each do |filter|
+ unless list_session[filter.filter_type].nil? || list_session[filter.filter_type][filter.name].nil?
+ conditions = filter.conditions(list_session[filter.filter_type][filter.name])
+ self.active_scaffold_conditions = merge_conditions(self.active_scaffold_conditions, conditions)
+ active_scaffold_config.list.user.page = nil
+ end
+ end
+ end
+ end
+
+ def clear_list_filter
+ active_scaffold_session_storage[:list_filter] = nil?
+ active_scaffold_config.list.user.page = nil
+ end
+
+ # The default security delegates to ActiveRecordPermissions.
+ # You may override the method to customize.
+ def list_filter_authorized?
+ authorized_for?(:action => :read)
+ end
+ end
+end
@@ -0,0 +1,5 @@
+module ActiveScaffold::Config
+ class Core
+ ActionController::Resources::Resource::ACTIVE_SCAFFOLD_ROUTING[:collection][:list_filter] = :get
+ end
+end
Oops, something went wrong.

0 comments on commit 0a186de

Please sign in to comment.