recordselect 3.2.5 → 3.2.6

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