livelist-rails 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -1 +1 @@
1
- ((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a};window.Utilities=function(){function b(){this.setOptions=a(this.setOptions,this)}return b.prototype.setOptions=function(a,b){var c=this;return b==null&&(b=this),_.each(a,function(a,c){return b[c]=a})},b}(),window.LiveList=function(a){function b(a){this.globalOptions.listSelector=a.list.renderTo,this.globalOptions.eventName="livelist:"+a.global.resourceName,this.globalOptions.urlPrefix="/"+a.global.resourceName,this.setOptions(a.global,this.globalOptions),this.search=new Search(this.globalOptions,a.search),this.filters=new Filters(this.globalOptions,a.filters),this.pagination=new Pagination(this.globalOptions,a.pagination),this.list=new List(this.search,this.filters,this.pagination,this.globalOptions,a.list)}return c(b,a),b.prototype.globalOptions={data:null,resourceName:"items",resourceNameSingular:"item"},b}(Utilities),window.List=function(b){function d(b,c,d,e,f){var g,h=this;f==null&&(f={}),this.renderIndex=a(this.renderIndex,this),this.removeFetchingIndication=a(this.removeFetchingIndication,this),this.displayFetchingIndication=a(this.displayFetchingIndication,this),this.data=e.data,this.fetchRequest=null,this.search=b,this.filters=c,this.pagination=d,this.setOptions(e),this.listTemplate="{{#"+this.resourceName+"}}{{>"+this.resourceNameSingular+"}}{{/"+this.resourceName+"}}",this.listItemTemplate="<li>{{id}}</li>",this.fetchingIndicationClass="updating",this.setOptions(f),$(this.renderTo).bind(this.eventName,function(a,b){return h.fetch({presets:null,page:b!=null?b.page:void 0})}),g=this.filters.presets(),this.fetch({presets:g})}return c(d,b),d.prototype.displayFetchingIndication=function(){return $(this.renderTo).addClass(this.fetchingIndicationClass)},d.prototype.removeFetchingIndication=function(){return $(this.renderTo).removeClass(this.fetchingIndicationClass)},d.prototype.renderIndex=function(a,b,c){return this.data=a,this.render(),this.pagination.render(this.data),this.filters.render(this.data)},d.prototype.fetch=function(a){var b,c;return this.fetchRequest&&this.fetchRequest.abort(),c=this.search.searchTerm(),b={},b.filters=this.filters.setPresets(a.presets),c&&(b.q=c),a.page&&(b.page=a.page),this.fetchRequest=$.ajax({url:this.urlPrefix,dataType:"json",data:b,type:this.httpMethod,beforeSend:this.displayFetchingIndication,success:this.renderIndex})},d.prototype.render=function(){var a,b;return b={},b[this.resourceNameSingular]=this.listItemTemplate,a=Mustache.to_html(this.listTemplate,this.data,b),$(this.renderTo).html(a),this.removeFetchingIndication()},d}(Utilities),window.LiveList.version="0.0.5",window.Filters=function(b){function d(b,c){var d=this;c==null&&(c={}),this.handleAdvancedOptionsClick=a(this.handleAdvancedOptionsClick,this),this.setOptions(b),this.filters=c.presets?_.keys(c.presets):[],this.initializeCookies(),this.setOptions(c),$("input.filter_option",this.renderTo).live("change",function(){return $(d.listSelector).trigger(d.eventName)}),$(this.advancedOptionsToggleSelector).click(this.handleAdvancedOptionsClick)}return c(d,b),d.prototype.initializeCookies=function(){if(jQuery.cookie&&this.useCookies&&this.cookieName)return this.cookieName="livelist_filter_presets"},d.prototype.presets=function(){var a;return jQuery.cookie&&this.useCookies&&(a=jQuery.cookie(this.cookieName)),this.useCookies&&a?JSON.parse(a):this.presets},d.prototype.setPresets=function(a){var b;return b={},jQuery.isEmptyObject(a)?(b=this.selections(),jQuery.cookie&&this.setCookie()):b=a,b},d.prototype.setCookie=function(a){if(!jQuery.isEmptyObject(a))return jQuery.cookie(this.cookieName,JSON.stringify(a))},d.prototype.template="{{#filters}}\n<div class='filter'>\n <h3>\n {{name}}\n </h3>\n <ul id='{{filter_slug}}_filter_options'>\n {{#options}}\n <label>\n <li>\n <input {{#selected}}checked='checked'{{/selected}}\n class='left filter_option'\n id='filter_{{slug}}'\n name='filters[]'\n type='checkbox'\n value='{{value}}' />\n <div class='left filter_name'>{{name}}</div>\n <div class='right filter_count'>{{count}}</div>\n <div class='clear'></div>\n </li>\n </label>\n {{/options}}\n </ul>\n</div>\n{{/filters}}",d.prototype.selections=function(){var a,b=this;return a={},_.each(this.filters,function(b){return a[b]=_.pluck($("#"+b+"_filter_options input.filter_option:checked"),"value")}),a},d.prototype.noFiltersSelected=function(a){return _.all(a.filters,function(a){return _.all(a.options,function(a){return!a.selected})})},d.prototype.render=function(a){var b;this.filters=_.pluck(a.filters,"filter_slug"),b=Mustache.to_html(this.template,a),$(this.renderTo).html(b);if(this.noFiltersSelected(a)&&a[this.resourceName].length>0)return $('input[type="checkbox"]',this.renderTo).attr("checked","checked")},d.prototype.handleAdvancedOptionsClick=function(a){return a.preventDefault(),$(this.renderTo).slideToggle()},d}(Utilities),window.Pagination=function(b){function d(b,c){c==null&&(c={}),this.handlePaginationLinkClick=a(this.handlePaginationLinkClick,this),this.pagination=null,this.maxPages=30,this.setOptions(b),this.emptyListMessage="<p>No "+this.resourceName+" matched your filter criteria</p>",this.setOptions(c),$(""+this.renderTo+" a").live("click",function(a){return a.preventDefault()}),$(""+this.renderTo+" li:not(.disabled) a").live("click",this.handlePaginationLinkClick)}return c(d,b),d.prototype.template="{{#isEmpty}}\n {{{emptyListMessage}}}\n{{/isEmpty}}\n{{^isEmpty}}\n<div class=\"pagination\">\n <ul>\n <li class=\"{{^previousPage}}disabled{{/previousPage}}\">\n <a href='{{urlPrefix}}?page={{previousPage}}' data-page='{{previousPage}}'>← Previous</a>\n </li>\n\n {{#pages}}\n <li class=\"{{#currentPage}}active disabled{{/currentPage}}\">\n <a href='{{urlPrefix}}?page={{page}}' data-page='{{page}}'>{{page}}</a>\n </li>\n {{/pages}}\n\n <li class=\"{{^nextPage}}disabled{{/nextPage}}\">\n <a href='{{urlPrefix}}?page={{nextPage}}' data-page='{{nextPage}}'>Next →</a>\n </li>\n </ul>\n</div>\n{{/isEmpty}}",d.prototype.pagesJSON=function(a,b){var c,d,e,f,g,h;return d=this.maxPages/2,c=a<d?1:a-d,f=c+d*2-1,e=f>=b?b:f,_.map(function(){h=[];for(var a=c;c<=e?a<=e:a>=e;c<=e?a++:a--)h.push(a);return h}.apply(this),function(b){return{page:b,currentPage:function(){return a===b}}})},d.prototype.paginationJSON=function(a){return{isEmpty:a.total_pages===0,emptyListMessage:this.emptyListMessage,currentPage:a.current_page,nextPage:a.next_page,previousPage:a.previous_page,urlPrefix:this.urlPrefix,pages:this.pagesJSON(a.current_page,a.total_pages)}},d.prototype.render=function(a){var b;return this.pagination=this.paginationJSON(a.pagination),b=Mustache.to_html(this.template,this.pagination),$(this.renderTo).html(b)},d.prototype.handlePaginationLinkClick=function(a){return a.preventDefault(),$(this.listSelector).trigger(this.eventName,{page:$(a.target).data("page")})},d}(Utilities),window.Search=function(b){function d(b,c){var d=this;c==null&&(c={}),this.handleSearchFormSubmit=a(this.handleSearchFormSubmit,this),this.setOptions(b),this.setOptions(c),$(this.formSelector).submit(function(a){return d.handleSearchFormSubmit(a)})}return c(d,b),d.prototype.searchTerm=function(){var a;return a=$(this.searchTextInputSelector).val(),!a||a===""?null:a},d.prototype.handleSearchFormSubmit=function(a){return a.preventDefault(),$(this.listSelector).trigger(this.eventName)},d}(Utilities)})).call(this);
1
+ ((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a};window.Utilities=function(){function b(){this.setOptions=a(this.setOptions,this)}return b.prototype.setOptions=function(a,b){var c=this;return b==null&&(b=this),_.each(a,function(a,c){return b[c]=a})},b}(),window.LiveList=function(a){function b(a){this.listSelector=a.list.renderTo,this.resourceName=a.global.resourceName,this.resourceNameSingular=a.global.resourceNameSingular,this.eventName="livelist:"+this.resourceName,this.urlPrefix="/"+this.resourceName,this.search=new Search(a.search,this),this.filters=new Filters(a.filters,this),this.pagination=new Pagination(a.pagination,this),this.list=new List(a.list,this)}return c(b,a),b}(Utilities),window.List=function(b){function d(b,c){this.renderIndex=a(this.renderIndex,this),this.removeFetchingIndication=a(this.removeFetchingIndication,this),this.displayFetchingIndication=a(this.displayFetchingIndication,this);var d=this;this.fetchRequest=null,this.livelist=c,this.listTemplate="{{#"+this.livelist.resourceName+"}}{{>"+this.livelist.resourceNameSingular+"}}{{/"+this.livelist.resourceName+"}}",this.listItemTemplate="<li>{{id}}</li>",this.fetchingIndicationClass="updating",this.renderTo="ul#"+this.livelist.resourceName,this.setOptions(b),$(this.renderTo).bind(this.livelist.eventName,function(a,b){return d.fetch({presets:null,page:b!=null?b.page:void 0})}),this.fetch({presets:this.livelist.filters.getPresets()})}return c(d,b),d.prototype.displayFetchingIndication=function(){return $(this.renderTo).addClass(this.fetchingIndicationClass)},d.prototype.removeFetchingIndication=function(){return $(this.renderTo).removeClass(this.fetchingIndicationClass)},d.prototype.renderIndex=function(a,b,c){return this.livelist.data=a,this.render(),this.livelist.pagination.render(this.livelist.data),this.livelist.filters.render(this.livelist.data)},d.prototype.fetch=function(a){var b,c;return this.fetchRequest&&this.fetchRequest.abort(),c=this.livelist.search.searchTerm(),b={},b.filters=this.livelist.filters.setPresets(a.presets),c&&(b.q=c),a.page&&(b.page=a.page),this.fetchRequest=$.ajax({url:this.livelist.urlPrefix,type:this.livelist.httpMethod,dataType:"json",data:b,beforeSend:this.displayFetchingIndication,success:this.renderIndex})},d.prototype.render=function(){var a,b;return b={},b[this.livelist.resourceNameSingular]=this.listItemTemplate,a=Mustache.to_html(this.listTemplate,this.livelist.data,b),$(this.renderTo).html(a),this.removeFetchingIndication()},d}(Utilities),window.LiveList.version="0.0.5",window.Filters=function(b){function d(b,c){this.handleAdvancedOptionsClick=a(this.handleAdvancedOptionsClick,this);var d=this;this.livelist=c,this.filters=b.presets?_.keys(b.presets):[],this.initializeCookies(),this.setOptions(b),$("input.filter_option",this.renderTo).live("change",function(){return $(d.livelist.listSelector).trigger(d.livelist.eventName)}),$(this.advancedOptionsToggleSelector).click(this.handleAdvancedOptionsClick)}return c(d,b),d.prototype.initializeCookies=function(){if(jQuery.cookie&&this.useCookies&&this.cookieName)return this.cookieName="livelist_filter_presets"},d.prototype.getPresets=function(){var a;return jQuery.cookie&&this.useCookies&&(a=jQuery.cookie(this.cookieName)),this.useCookies&&a?JSON.parse(a):this.presets},d.prototype.setPresets=function(a){var b;return b={},jQuery.isEmptyObject(a)?(b=this.selections(),jQuery.cookie&&this.setCookie(b)):b=a,b},d.prototype.setCookie=function(a){if(!jQuery.isEmptyObject(a))return jQuery.cookie(this.cookieName,JSON.stringify(a))},d.prototype.template="{{#filters}}\n<div class='filter'>\n <h3>\n {{name}}\n </h3>\n <ul id='{{filter_slug}}_filter_options'>\n {{#options}}\n <label>\n <li>\n <input {{#selected}}checked='checked'{{/selected}}\n class='left filter_option'\n id='filter_{{slug}}'\n name='filters[]'\n type='checkbox'\n value='{{value}}' />\n <div class='left filter_name'>{{name}}</div>\n <div class='right filter_count'>{{count}}</div>\n <div class='clear'></div>\n </li>\n </label>\n {{/options}}\n </ul>\n</div>\n{{/filters}}",d.prototype.selections=function(){var a,b=this;return a={},_.each(this.filters,function(b){return a[b]=_.pluck($("#"+b+"_filter_options input.filter_option:checked"),"value")}),a},d.prototype.noFiltersSelected=function(a){return _.all(a.filters,function(a){return _.all(a.options,function(a){return!a.selected})})},d.prototype.sortOptions=function(a){return _.map(a,function(a){return a.options=_.sortBy(a.options,function(a){return a.name}),a})},d.prototype.sort=function(a){return _.sortBy(a,function(a){return a.name})},d.prototype.render=function(a){var b;this.filters=_.pluck(a.filters,"filter_slug"),this.sort(a.filters),this.sortOptions(a.filters),b=Mustache.to_html(this.template,a),$(this.renderTo).html(b);if(this.noFiltersSelected(a)&&a[this.livelist.resourceName].length>0)return $('input[type="checkbox"]',this.renderTo).attr("checked","checked")},d.prototype.handleAdvancedOptionsClick=function(a){return a.preventDefault(),$(this.renderTo).slideToggle()},d}(Utilities),window.Pagination=function(b){function d(b,c){this.handlePaginationLinkClick=a(this.handlePaginationLinkClick,this),this.livelist=c,this.pagination=null,this.maxPages=30,this.emptyListMessage="<p>No "+this.livelist.resourceName+" matched your filter criteria</p>",this.setOptions(b),$(""+this.renderTo+" a").live("click",function(a){return a.preventDefault()}),$(""+this.renderTo+" li:not(.disabled) a").live("click",this.handlePaginationLinkClick)}return c(d,b),d.prototype.template="{{#isEmpty}}\n {{{emptyListMessage}}}\n{{/isEmpty}}\n{{^isEmpty}}\n<div class=\"pagination\">\n <ul>\n <li class=\"{{^previousPage}}disabled{{/previousPage}}\">\n <a href='{{urlPrefix}}?page={{previousPage}}' data-page='{{previousPage}}'>← Previous</a>\n </li>\n\n {{#pages}}\n <li class=\"{{#currentPage}}active disabled{{/currentPage}}\">\n <a href='{{urlPrefix}}?page={{page}}' data-page='{{page}}'>{{page}}</a>\n </li>\n {{/pages}}\n\n <li class=\"{{^nextPage}}disabled{{/nextPage}}\">\n <a href='{{urlPrefix}}?page={{nextPage}}' data-page='{{nextPage}}'>Next →</a>\n </li>\n </ul>\n</div>\n{{/isEmpty}}",d.prototype.pagesJSON=function(a,b){var c,d,e,f,g,h;return d=Math.floor(this.maxPages/2),c=a<=d?1:a-d,f=c+d*2-1,e=f>=b?b:f,_.map(function(){h=[];for(var a=c;c<=e?a<=e:a>=e;c<=e?a++:a--)h.push(a);return h}.apply(this),function(b){return{page:b,currentPage:a===b}})},d.prototype.paginationJSON=function(a){return{isEmpty:a.total_pages===0,emptyListMessage:this.emptyListMessage,currentPage:a.current_page,nextPage:a.next_page,previousPage:a.previous_page,urlPrefix:this.livelist.urlPrefix,pages:this.pagesJSON(a.current_page,a.total_pages)}},d.prototype.render=function(a){var b;return this.pagination=this.paginationJSON(a.pagination),b=Mustache.to_html(this.template,this.pagination),$(this.renderTo).html(b)},d.prototype.handlePaginationLinkClick=function(a){return a.preventDefault(),$(this.livelist.listSelector).trigger(this.livelist.eventName,{page:$(a.target).data("page")})},d}(Utilities),window.Search=function(b){function d(b,c){this.handleSearchFormSubmit=a(this.handleSearchFormSubmit,this);var d=this;this.livelist=c,this.setOptions(b),$(this.formSelector).submit(function(a){return d.handleSearchFormSubmit(a)})}return c(d,b),d.prototype.searchTerm=function(){var a;return a=$(this.searchTextInputSelector).val(),!a||a===""?null:a},d.prototype.handleSearchFormSubmit=function(a){return a.preventDefault(),$(this.livelist.listSelector).trigger(this.livelist.eventName)},d}(Utilities)})).call(this);
@@ -1,7 +1,8 @@
1
+ require 'livelist/rails/railtie'
2
+ require 'livelist/rails/engine'
3
+ require 'livelist/rails/version'
4
+
1
5
  module Livelist
2
6
  module Rails
3
- require 'livelist/rails/railtie'
4
- require 'livelist/rails/engine'
5
- require 'livelist/rails/version'
6
7
  end
7
8
  end
@@ -1,159 +1,27 @@
1
1
  require 'active_record'
2
+ require 'livelist/rails/filter_collection'
2
3
 
3
4
  module Livelist
4
5
  module Rails
5
6
  module ActiveRecord
6
- @@filter_slugs = []
7
- @@filter_collections = {}
8
7
 
9
- def filter_for(filter_slug, options = {})
10
- @@filter_slugs << filter_slug unless @@filter_slugs.include?(filter_slug)
11
- @@filter_collections[filter_slug] = options[:collection] || lambda { select("distinct #{filter_slug}") }
8
+ def filter_for(slug, options = {})
9
+ @filters ||= FilterCollection.new
10
+ @filters.create_filter(
11
+ :reference_criteria => options[:reference_criteria],
12
+ :model_name => model_name,
13
+ :slug => slug
14
+ )
12
15
 
13
- define_class_methods(filter_slug)
14
-
15
- def filters_as_json(filter_params)
16
- @counts = {}
17
- @filter_params = filter_params || {}
18
- filters = @@filter_slugs.map do |filter|
19
- filter_options = send("#{filter}_filters", filter)
20
- send("#{filter}_filter", filter_options)
21
- end
22
- end
23
-
24
- def filter_relation(filter_params)
25
- query = scoped
26
- @@filter_slugs.each do |filter_slug|
27
- values = filter_params[filter_slug.to_s]
28
- query = query.send("#{filter_slug}_where", filter_collection(filter_slug))
29
- query = query.send("#{filter_slug}_relation", values) unless filter_params.empty?
30
- end
31
- query
32
- end
33
-
34
- def filter(filter_params)
35
- filter_params ||= {}
36
- @filter_relation = filter_relation(filter_params)
37
- end
38
-
39
- def filter_option_count(filter_slug, option)
40
- @counts[filter_slug] ||= send("#{filter_slug}_filter_counts")
41
- @counts[filter_slug][option.to_s] || 0
42
- end
43
-
44
- def filter_collection(filter_slug)
45
- collection = @@filter_collections[filter_slug.to_sym]
46
- if collection.respond_to?(:call)
47
- collection.call.map(&filter_slug.to_sym)
48
- else
49
- collection
50
- end
51
- end
52
-
53
- def exclude_filter_relation?(filter_slug, slug)
54
- @filter_params[filter_slug].nil? || (slug.to_s == filter_slug)
55
- end
56
-
57
- def counts_relation(filter_slug, slug)
58
- query = scoped
59
- query = query.send("#{slug}_join")
60
- query = query.send("#{slug}_where", filter_collection(slug))
61
- query = query.send("#{slug}_where", @filter_params[slug]) unless exclude_filter_relation?(filter_slug, slug)
62
- query
16
+ def filters_as_json(params)
17
+ @filters.as_json(scoped, params)
63
18
  end
64
19
 
65
- def filter_slug_filter_counts(filter_slug)
66
- query = scoped.except(:order)
67
- @@filter_slugs.each do |slug|
68
- query = query.counts_relation(filter_slug, slug)
69
- end
70
- group_by = send("#{filter_slug}_counts_group_by")
71
- query.group(group_by).count
20
+ def filter(params)
21
+ @filters.relation(scoped, params)
72
22
  end
73
23
  end
74
24
 
75
- def define_class_methods(filter_slug)
76
- filter_slug = filter_slug.to_s
77
- metaclass = class << self; self; end
78
-
79
- metaclass.instance_eval do
80
- define_method("#{filter_slug}_filter_name") { filter_slug.capitalize }
81
- define_method("#{filter_slug}_filter_slug") { filter_slug }
82
- define_method("#{filter_slug}_counts_group_by") { filter_slug }
83
- define_method("#{filter_slug}_join") { scoped }
84
- define_method("#{filter_slug}_where") { |values| where(model_name.to_s.tableize => { filter_slug => values }) }
85
- define_method("#{filter_slug}_filter_counts") { filter_slug_filter_counts(filter_slug) }
86
- define_method("#{filter_slug}_filter_option_key_name") { new.respond_to?(filter_slug.to_sym) ? filter_slug.to_sym : :id }
87
- define_method("#{filter_slug}_relation") { |values| send("#{filter_slug}_join").send("#{filter_slug}_where", values) }
88
- define_method("#{filter_slug}_filter_option_count") { |option| filter_option_count(filter_slug, option) }
89
-
90
- define_method("#{filter_slug}_filter_option_value") do |option|
91
- [String, Integer].any?{|klass| option.kind_of?(klass)} ? option.to_s : option.send(:id).to_s
92
- end
93
-
94
- define_method("#{filter_slug}_filter_option_selected?") do |filter, option|
95
- @filter_params[filter].nil? ? false : @filter_params[filter].include?(option.to_s)
96
- end
97
-
98
- define_method "#{filter_slug}_filter" do |options|
99
- {
100
- :filter_slug => send("#{filter_slug}_filter_slug"),
101
- :name => send("#{filter_slug}_filter_name"),
102
- :options => options
103
- }
104
- end
105
-
106
- define_method "#{filter_slug}_filter_values" do
107
- key = send("#{filter_slug}_filter_option_key_name")
108
- collection = filter_collection(filter_slug)
109
- if collection.any?{|object| object.kind_of?(Hash) && object.has_key?(key)}
110
- collection.map{|object| object[key]}
111
- elsif collection.any?{|object| object.respond_to?(key)}
112
- collection.map(&key)
113
- elsif collection.kind_of?(Array)
114
- collection
115
- end
116
- end
117
-
118
- define_method "#{filter_slug}_filter_option_slug" do |option|
119
- if [String, Integer].any?{|klass| option.kind_of?(klass)}
120
- option.to_s
121
- else
122
- option.send(:id).to_s
123
- end
124
- end
125
-
126
- define_method "#{filter_slug}_filter_option_name" do |option|
127
- collection = filter_collection(filter_slug)
128
- if collection.any?{|object| object.kind_of?(Hash) && object.has_key?(:name)}
129
- option_object = collection.detect{|object| object[:state] == option.to_s}
130
- option_object[:name]
131
- elsif collection.any?{|object| object.respond_to?(:name)}
132
- option_object = collection.detect{|object| object.send(:id).to_s == option.to_s}
133
- option_object.send(:name)
134
- else
135
- option.to_s
136
- end
137
- end
138
-
139
- define_method "#{filter_slug}_filter_option" do |option, selected|
140
- {
141
- :slug => send("#{filter_slug}_filter_option_slug", option),
142
- :name => send("#{filter_slug}_filter_option_name", option),
143
- :value => send("#{filter_slug}_filter_option_value", option),
144
- :count => send("#{filter_slug}_filter_option_count", option),
145
- :selected => selected
146
- }
147
- end
148
-
149
- define_method "#{filter_slug}_filters" do |filter|
150
- send("#{filter_slug}_filter_values").map do |option|
151
- selected = send("#{filter_slug}_filter_option_selected?", filter, option)
152
- send("#{filter_slug}_filter_option", option, selected)
153
- end
154
- end
155
- end
156
- end
157
25
  end
158
26
  end
159
27
  end
@@ -0,0 +1,109 @@
1
+ require 'active_record'
2
+ require 'livelist/rails/filter_criteria'
3
+
4
+ module Livelist
5
+ module Rails
6
+
7
+ class Filter
8
+ attr_accessor :slug,
9
+ :name,
10
+ :key_name,
11
+ :model_name,
12
+ :model_class,
13
+ :join,
14
+ :type,
15
+ :criteria
16
+
17
+ # slug should always be a symbol
18
+ def initialize(options = {})
19
+ @filter_collection = options[:filter_collection]
20
+ @slug = options[:slug].to_sym
21
+ @name = options[:name] || @slug.to_s.capitalize
22
+ @model_name = options[:model_name]
23
+ @type = options[:type] || initialize_type
24
+ @key_name = options[:key_name] || default_key_name
25
+ @criteria = FilterCriteria.new(
26
+ :filter => self,
27
+ :reference_criteria => options[:reference_criteria],
28
+ :slug => @key_name
29
+ )
30
+ end
31
+
32
+ def group_by
33
+ case @type
34
+ when :attribute then "#{model_name.tableize}.#{@slug}"
35
+ when :association then "#{@slug}.id"
36
+ end
37
+ end
38
+
39
+ def exclude_filter_relation?(matching_filter, params)
40
+ params.nil? || (self == matching_filter)
41
+ end
42
+
43
+ def set_criteria_counts(query, params)
44
+ @criteria.counts = counts(query, params)
45
+ end
46
+
47
+ def counts_relation(query, filter, params)
48
+ exclude_params_relation = exclude_filter_relation?(filter, params[@slug])
49
+ counts_scope = filter.relation(query, params[filter.slug], exclude_params_relation)
50
+ query.merge(counts_scope)
51
+ end
52
+
53
+ def counts(query, params)
54
+ @filter_collection.filters.each do |filter|
55
+ query = counts_relation(query, filter, params)
56
+ end
57
+ query.except(:order).group(group_by).count.stringify_keys
58
+ end
59
+
60
+ def relation(query, params, exclude_params_relation)
61
+ query = query.includes(@slug) if @type == :association
62
+ query = query.where(where(@criteria.slugs))
63
+ query = query.where(where(params)) unless exclude_params_relation
64
+ query
65
+ end
66
+
67
+ def default_key_name
68
+ case @type
69
+ when :association then :id
70
+ when :attribute then @slug
71
+ end
72
+ end
73
+
74
+ def table_name
75
+ case @type
76
+ when :association then @slug
77
+ when :attribute then model_name.tableize
78
+ end
79
+ end
80
+
81
+ def model_class
82
+ @model_name.classify.constantize
83
+ end
84
+
85
+ def where(slug_params)
86
+ { table_name => { @key_name => slug_params } }
87
+ end
88
+
89
+ def as_json(params)
90
+ {
91
+ :filter_slug => @slug,
92
+ :name => @name,
93
+ :options => @criteria.as_json(params)
94
+ }
95
+ end
96
+
97
+ def initialize_type
98
+ if model_class.column_names.include?(@slug.to_s)
99
+ :attribute
100
+ elsif model_class.reflect_on_all_associations.map(&:name).include?(@slug)
101
+ :association
102
+ else
103
+ nil
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'livelist/rails/filter'
3
+
4
+ module Livelist
5
+ module Rails
6
+
7
+ class FilterCollection < HashWithIndifferentAccess
8
+ alias :filters :values
9
+ alias :find_filter :[]
10
+
11
+ def create_filter(options)
12
+ options.merge!(:filter_collection => self)
13
+ self[options[:slug]] = Filter.new(options)
14
+ end
15
+
16
+ def relation(query, params)
17
+ params ||= {}
18
+ filters.each do |filter|
19
+ query = filter.relation(query, params[filter.slug.to_s], params.empty?)
20
+ end
21
+ query
22
+ end
23
+
24
+ def as_json(query, params)
25
+ params ||= {}
26
+ filters.map do |filter|
27
+ filter.set_criteria_counts(query, params)
28
+ filter.as_json(params[filter.slug])
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ require 'active_support/hash_with_indifferent_access'
2
+ require 'livelist/rails/filter_criterion'
3
+
4
+ module Livelist
5
+ module Rails
6
+
7
+ class FilterCriteria < HashWithIndifferentAccess
8
+ alias :criteria :values
9
+ alias :find_criteria :[]
10
+ attr_reader :slug
11
+
12
+ def initialize(options)
13
+ @filter = options[:filter]
14
+ @reference_criteria = options[:reference_criteria] || default_reference_criteria
15
+ @reference_criteria = @reference_criteria.call if @reference_criteria.respond_to?(:call)
16
+ @slug = options[:slug]
17
+
18
+ @reference_criteria.each do |reference|
19
+ create_criterion(reference)
20
+ end
21
+ end
22
+
23
+ def default_reference_criteria
24
+ @filter.model_class.select("distinct #{@filter.slug}")
25
+ end
26
+
27
+ def create_criterion(reference)
28
+ filter_criterion = FilterCriterion.new(:filter => @filter, :criteria => self, :reference => reference)
29
+ self[filter_criterion.slug] = filter_criterion
30
+ end
31
+
32
+ def slugs
33
+ criteria.map(&:slug)
34
+ end
35
+
36
+ def counts=(counts_hash)
37
+ criteria.each do |criterion|
38
+ criterion.count = counts_hash[criterion.slug.to_s] || 0
39
+ end
40
+ end
41
+
42
+ def as_json(params)
43
+ criteria.map do |criterion|
44
+ criterion.as_json(params)
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,81 @@
1
+ require 'active_record'
2
+
3
+ module Livelist
4
+ module Rails
5
+
6
+ class FilterCriterion
7
+ attr_accessor :slug,
8
+ :name,
9
+ :count,
10
+ :value,
11
+ :type,
12
+ :name_key
13
+
14
+ def initialize(options = {})
15
+ @filter = options[:filter]
16
+ @criteria = options[:criteria]
17
+ @reference = options[:reference]
18
+ @type = infer_type
19
+ @name_key = options[:name_key] || infer_name_key
20
+ @slug = infer_slug
21
+ @name = infer_name
22
+ end
23
+
24
+ def selected?(params)
25
+ params.nil? ? false : params.include?(slug.to_s)
26
+ end
27
+
28
+ def as_json(params)
29
+ {
30
+ :slug => @filter.slug,
31
+ :name => @name,
32
+ :value => @slug.to_s,
33
+ :count => @count,
34
+ :selected => selected?(params)
35
+ }
36
+ end
37
+
38
+ private
39
+
40
+ def infer_type
41
+ raise ArgumentError, "reference is not valid \n #{@reference.inspect}" if @reference.nil?
42
+ if [String, Symbol, Integer].any?{|klass| @reference.kind_of?(klass)}
43
+ :scalar
44
+ elsif @reference.kind_of?(Hash)
45
+ :hash
46
+ else
47
+ :model
48
+ end
49
+ end
50
+
51
+ def infer_name_key
52
+ case @type
53
+ when :scalar then nil
54
+ when :hash then :name
55
+ when :model
56
+ if @reference.respond_to?(:name)
57
+ :name
58
+ elsif @reference.respond_to?(@criteria.slug)
59
+ @criteria.slug
60
+ end
61
+ end
62
+ end
63
+
64
+ def infer_slug
65
+ case @type
66
+ when :scalar then @reference
67
+ when :hash, :model then @reference[@criteria.slug]
68
+ end
69
+ end
70
+
71
+ def infer_name
72
+ case @type
73
+ when :scalar then @reference
74
+ when :hash then @reference[@name_key]
75
+ when :model then @reference.send(@name_key)
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end