livelist-rails 0.0.10 → 0.0.11

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.
@@ -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