recordselect 3.2.5 → 3.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ <%
2
+ controller ||= params[:controller]
3
+ record_select_id = record_select_id(controller)
4
+ -%>
5
+ <div class="record-select" id="<%= record_select_id -%>">
6
+ <%= render_record_select :partial => 'search', :locals => {:controller => controller, :record_select_id => record_select_id} %>
7
+ <%= render_record_select :partial => 'list', :locals => {:controller => controller, :page => @page, :record_select_id => record_select_id} %>
8
+ </div>
@@ -0,0 +1,31 @@
1
+ <%
2
+ controller ||= params[:controller]
3
+
4
+ pagination_url_params = params.merge(:controller => controller, :action => :browse, :search => params[:search], :update => 1)
5
+ prev_url = url_for(pagination_url_params.merge(:page => page.prev.number, :escape => false)) if page.prev?
6
+ next_url = url_for(pagination_url_params.merge(:page => page.next.number, :escape => false)) if page.next?
7
+ -%>
8
+ <ol>
9
+ <li class="found"><%= rs_(:records_found, :count => page.pager.count,
10
+ :model => record_select_config.model.model_name.human(:count => page.pager.count).downcase) %></li>
11
+ <% if page.prev? -%>
12
+ <li class="pagination previous">
13
+ <%= link_to image_tag('record_select/previous.gif', :alt => rs_(:previous)) + " " + rs_(:previous_items,
14
+ :count => page.pager.per_page),
15
+ {:url => prev_url},
16
+ {:href => prev_url, :method => :get, :remote => true} %>
17
+ </li>
18
+ <% end -%>
19
+ <% page.items.each do |record| -%>
20
+ <li class="record <%= cycle 'odd', 'even' %>" id="rs<%= record.id -%>">
21
+ <%= render_record_in_list(record, controller) %>
22
+ </li>
23
+ <% end -%>
24
+ <% if page.next? -%>
25
+ <li class="pagination next">
26
+ <%= link_to (rs_(:next_items, :count => page.pager.per_page) + " " + image_tag('record_select/next.gif', :alt => rs_(:next))).html_safe,
27
+ {:url => next_url},
28
+ {:href => next_url, :method => :get, :remote => true} %>
29
+ </li>
30
+ <% end -%>
31
+ </ol>
@@ -0,0 +1,20 @@
1
+ <% url_options = params.merge(:controller => controller, :action => :browse, :page => 1, :update => 1, :escape => false) -%>
2
+ <%= form_tag url_options, {:method => :get, :remote => true, :id => record_select_search_id} -%>
3
+ <%= text_field_tag 'search', params[:search], :autocomplete => 'off', :class => 'text-input' %>
4
+ <%= submit_tag 'search', :class => "search_submit" %>
5
+ </form>
6
+
7
+ <script type="text/javascript">
8
+ //<![CDATA[
9
+ <% if RecordSelect::Config.js_framework == :prototype %>
10
+ var i = $(<%= record_select_search_id.to_json.html_safe %>).down('input.text-input');
11
+ Form.Element.AfterActivity(i, function() {
12
+ $(<%= record_select_search_id.to_json.html_safe -%>).down('input.search_submit').click();
13
+ }, 0.35);
14
+ <% elsif RecordSelect::Config.js_framework == :jquery %>
15
+ jQuery(<%= "##{record_select_search_id}".to_json.html_safe %>).find('input.text-input').delayedObserver(0.35, function() {
16
+ jQuery(<%= "##{record_select_search_id}".to_json.html_safe %>).trigger("submit");});
17
+ <% end %>
18
+ //]]>
19
+ </script>
20
+
@@ -0,0 +1 @@
1
+ RecordSelect.render_page('<%= record_select_id %>', '<%= escape_javascript(render_record_select(:partial => 'list', :locals => {:page => @page})) %>');
@@ -0,0 +1,9 @@
1
+ en:
2
+ record_select:
3
+ next: "Next"
4
+ next_items: "Next %{count}"
5
+ previous: "Previous"
6
+ previous_items: "Previous %{count}"
7
+ records_found:
8
+ one: "1 %{model} found"
9
+ other: "%{count} %{model} found"
@@ -0,0 +1,13 @@
1
+ es:
2
+ record_select:
3
+ next: "Siguiente"
4
+ next_items:
5
+ one: "Siguente"
6
+ other: "%{count} siguentes"
7
+ previous: "Anterior"
8
+ previous_items:
9
+ one: "Anterior"
10
+ other: "%{count} anteriores"
11
+ records_found:
12
+ one: "1 %{model} encontrado"
13
+ other: "%{count} %{model} encontrados"
@@ -0,0 +1,36 @@
1
+ module RecordSelect
2
+ def self.included(base)
3
+ base.send :extend, ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ # Enables and configures RecordSelect on your controller.
8
+ #
9
+ # *Options*
10
+ # +model+:: defaults based on the name of the controller
11
+ # +per_page+:: records to show per page when browsing
12
+ # +notify+:: a method name to invoke when a record has been selected.
13
+ # +order_by+:: a SQL string to order the search results
14
+ # +search_on+:: an array of searchable fields
15
+ # +full_text_search+:: a boolean for whether to use a %?% search pattern or not. default is false.
16
+ # +label+:: a proc that accepts a record as argument and returns an option label. default is to call record.to_label instead.
17
+ # +include+:: as for ActiveRecord::Base#find. can help with search conditions or just help optimize rendering the results.
18
+ # +link+:: a boolean for whether wrap the text returned by label in a link or not. default is true. set to false when
19
+ # label returns html code which can't be inside a tag. You can use record_select_link_to_select in your proc
20
+ # or partial to add a link to select action
21
+ #
22
+ # You may also pass a block, which will be used as options[:notify].
23
+ def record_select(options = {})
24
+ options[:model] ||= self.to_s.split('::').last.sub(/Controller$/, '').pluralize.singularize.underscore
25
+ @record_select_config = RecordSelect::Config.new(options.delete(:model), options)
26
+ self.send :include, RecordSelect::Actions
27
+ self.send :include, RecordSelect::Conditions
28
+ end
29
+
30
+ attr_reader :record_select_config
31
+
32
+ def uses_record_select?
33
+ !record_select_config.nil?
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,68 @@
1
+ module RecordSelect
2
+ module Actions
3
+ # :method => :get
4
+ # params => [:page, :search]
5
+ def browse
6
+ conditions = record_select_conditions
7
+ klass = record_select_model.where(conditions).includes(record_select_includes)
8
+ @count = klass.count
9
+ @count = @count.length if @count.is_a? ActiveSupport::OrderedHash
10
+ pager = ::Paginator.new(@count, record_select_config.per_page) do |offset, per_page|
11
+ klass.select(record_select_select).includes(record_select_config.include).order(record_select_config.order_by).limit(per_page).offset(offset).all
12
+ end
13
+ @page = pager.page(params[:page] || 1)
14
+
15
+ respond_to do |wants|
16
+ wants.html { render_record_select :partial => 'browse'}
17
+ wants.js {
18
+ if params[:update]
19
+ render_record_select :template => 'browse.js', :layout => false
20
+ else
21
+ render_record_select :partial => 'browse'
22
+ end
23
+ }
24
+ wants.yaml {}
25
+ wants.xml {}
26
+ wants.json {}
27
+ end
28
+ end
29
+
30
+ # :method => :post
31
+ # params => [:id]
32
+ def select
33
+ klass = record_select_model
34
+ record = klass.find(params[:id])
35
+ if record_select_config.notify.is_a? Proc
36
+ record_select_config.notify.call(record)
37
+ elsif record_select_config.notify
38
+ send(record_select_config.notify, record)
39
+ end
40
+ render :nothing => true
41
+ end
42
+
43
+ protected
44
+
45
+ def record_select_config #:nodoc:
46
+ self.class.record_select_config
47
+ end
48
+
49
+ def render_record_select(options = {}) #:nodoc:
50
+ [:template,:partial].each do |template_name|
51
+ if options[template_name] then
52
+ options[template_name] = File.join(record_select_views_path, options[template_name])
53
+ end
54
+ end
55
+ if block_given? then yield options else render options end
56
+ end
57
+
58
+ private
59
+
60
+ def record_select_views_path
61
+ "record_select"
62
+ end
63
+ end
64
+
65
+ def record_select_model
66
+ record_select_config.model
67
+ end
68
+ end
@@ -0,0 +1,100 @@
1
+ module RecordSelect
2
+ module Conditions
3
+ protected
4
+ # returns the combination of all conditions.
5
+ # conditions come from:
6
+ # * current search (params[:search])
7
+ # * intelligent url params (e.g. params[:first_name] if first_name is a model column)
8
+ # * specific conditions supplied by the developer
9
+ def record_select_conditions
10
+ conditions = []
11
+
12
+ merge_conditions(
13
+ record_select_conditions_from_search,
14
+ record_select_conditions_from_params,
15
+ record_select_conditions_from_controller
16
+ )
17
+ end
18
+
19
+ # an override method.
20
+ # here you can provide custom conditions to define the selectable records. useful for situational restrictions.
21
+ def record_select_conditions_from_controller; end
22
+
23
+ # another override method.
24
+ # define any association includes you want for the finder search.
25
+ def record_select_includes; end
26
+
27
+ def record_select_like_operator
28
+ @like_operator ||= ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
29
+ end
30
+
31
+ # define special list of selected fields,
32
+ # mainly to define extra fields that can be used for
33
+ # specialized sorting.
34
+ def record_select_select; end
35
+
36
+ # generate conditions from params[:search]
37
+ # override this if you want to customize the search routine
38
+ def record_select_conditions_from_search
39
+ search_pattern = record_select_config.full_text_search? ? '%?%' : '?%'
40
+
41
+ if params[:search] and !params[:search].strip.empty?
42
+ if record_select_config.full_text_search?
43
+ tokens = params[:search].strip.split(' ')
44
+ else
45
+ tokens = []
46
+ tokens << params[:search].strip
47
+ end
48
+
49
+ where_clauses = record_select_config.search_on.collect { |sql| "#{sql} #{record_select_like_operator} ?" }
50
+ phrase = "(#{where_clauses.join(' OR ')})"
51
+
52
+ sql = ([phrase] * tokens.length).join(' AND ')
53
+ tokens = tokens.collect{ |value| [search_pattern.sub('?', value)] * record_select_config.search_on.length }.flatten
54
+
55
+ conditions = [sql, *tokens]
56
+ end
57
+ end
58
+
59
+ # instead of a shotgun approach, this assumes the user is
60
+ # searching vs some SQL field (possibly built with CONCAT())
61
+ # similar to the record labels.
62
+ # def record_select_simple_conditions_from_search
63
+ # return unless params[:search] and not params[:search].empty?
64
+ #
65
+ # search_pattern = record_select_config.full_text_search? ? '%?%' : '?%'
66
+ # search_string = search_pattern.sub('?', value.downcase)
67
+ #
68
+ # ["LOWER(#{record_select_config.search_on})", search_pattern.sub('?', value.downcase)]
69
+ # end
70
+
71
+ # generate conditions from the url parameters (e.g. users/browse?group_id=5)
72
+ def record_select_conditions_from_params
73
+ conditions = nil
74
+ params.each do |field, value|
75
+ next unless column = record_select_config.model.columns_hash[field]
76
+ conditions = merge_conditions(
77
+ conditions,
78
+ record_select_condition_for_column(column, value)
79
+ )
80
+ end
81
+ conditions
82
+ end
83
+
84
+ # generates an SQL condition for the given column/value
85
+ def record_select_condition_for_column(column, value)
86
+ if value.blank? and column.null
87
+ "#{column.name} IS NULL"
88
+ elsif column.text?
89
+ ["LOWER(#{column.name}) LIKE ?", value]
90
+ else
91
+ ["#{column.name} = ?", column.type_cast(value)]
92
+ end
93
+ end
94
+
95
+ def merge_conditions(*conditions) #:nodoc:
96
+ c = conditions.find_all {|c| not c.nil? and not c.empty? }
97
+ c.empty? ? nil : c.collect{|c| record_select_config.model.send(:sanitize_sql, c)}.join(' AND ')
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,103 @@
1
+ module RecordSelect
2
+ # a write-once configuration object
3
+ class Config
4
+ def initialize(klass, options = {})
5
+ @klass = klass
6
+
7
+ @notify = block_given? ? proc : options[:notify]
8
+
9
+ @per_page = options[:per_page]
10
+
11
+ @search_on = [options[:search_on]].flatten unless options[:search_on].nil?
12
+
13
+ @order_by = options[:order_by]
14
+
15
+ @full_text_search = options[:full_text_search]
16
+
17
+ @label = options[:label]
18
+
19
+ @include = options[:include]
20
+
21
+ @link = options[:link]
22
+ end
23
+
24
+ def self.js_framework=(framework)
25
+ @@js_framework = framework
26
+ end
27
+
28
+ def self.js_framework
29
+ @@js_framework ||= if defined? Jquery
30
+ :jquery
31
+ elsif defined? PrototypeRails
32
+ :prototype
33
+ end
34
+ end
35
+
36
+ # The model object we're browsing
37
+ def model
38
+ @model ||= klass.to_s.camelcase.constantize
39
+ end
40
+
41
+ # Records to show on a page
42
+ def per_page
43
+ @per_page ||= 10
44
+ end
45
+
46
+ # The method name or proc to notify of a selection event.
47
+ # May not matter if the selection event is intercepted client-side.
48
+ def notify
49
+ @notify
50
+ end
51
+
52
+ # A collection of fields to search. This is essentially raw SQL, so you could search on "CONCAT(first_name, ' ', last_name)" if you wanted to.
53
+ # NOTE: this does *NO* default transforms (such as LOWER()), that's left entirely up to you.
54
+ def search_on
55
+ @search_on ||= self.model.columns.collect{|c| c.name if [:text, :string].include? c.type}.compact
56
+ end
57
+
58
+ def order_by
59
+ @order_by ||= "#{model.table_name}.#{model.primary_key} ASC" unless @order_by == false
60
+ end
61
+
62
+ def full_text_search?
63
+ @full_text_search ? true : false
64
+ end
65
+
66
+ def include
67
+ @include
68
+ end
69
+
70
+ # If a proc, must accept the record as an argument and return a descriptive string.
71
+ #
72
+ # If a symbol or string, must name a partial that renders a representation of the
73
+ # record. The partial should assume a local "record" variable, and should include a
74
+ # <label> tag, even if it's not visible. The contents of the <label> tag will be used
75
+ # to represent the record once it has been selected. For example:
76
+ #
77
+ # record_select_config.label = :user_description
78
+ #
79
+ # > app/views/users/_user_description.erb
80
+ #
81
+ # <div class="user_description">
82
+ # <%= image_tag url_for_file_column(record, 'avatar') %>
83
+ # <label><%= record.username %></label>
84
+ # <p><%= record.quote %></p>
85
+ # </div>
86
+ #
87
+ def label
88
+ @label ||= proc {|r| r.to_label}
89
+ end
90
+
91
+ # whether wrap the text returned by label in a link or not
92
+ def link?
93
+ @link.nil? ? true : @link
94
+ end
95
+
96
+ protected
97
+
98
+ # A singularized underscored version of the model we're browsing
99
+ def klass
100
+ @klass
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,4 @@
1
+ module RecordSelect
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveRecord # :nodoc:
2
+ class Base # :nodoc:
3
+ unless method_defined? :to_label
4
+ def to_label
5
+ self.to_s
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # Provides a simple pass-through localizer for RecordSelect. If you want
2
+ # to localize RS, you need to override this method and route it to your
3
+ # own system.
4
+ class Object
5
+ def rs_(key, options = {})
6
+ unless key.blank?
7
+ text = I18n.translate "#{key}", {:scope => [:record_select], :default => key.is_a?(String) ? key : key.to_s.titleize}.merge(options)
8
+ # text = nil if text.include?('translation missing:')
9
+ end
10
+ text ||= key
11
+ text
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module ActionDispatch
2
+ module Routing
3
+ RECORD_SELECT_ROUTING = {
4
+ :collection => {:browse => :get},
5
+ :member => {:select => :post}
6
+ }
7
+ class Mapper
8
+ module Base
9
+ def record_select_routes
10
+ collection do
11
+ ActionDispatch::Routing::RECORD_SELECT_ROUTING[:collection].each {|name, type| send(type, name)}
12
+ end
13
+ member do
14
+ ActionDispatch::Routing::RECORD_SELECT_ROUTING[:member].each {|name, type| send(type, name)}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end