recordselect-custom 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/CHANGELOG +25 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/MIT-LICENSE +20 -0
- data/README +11 -0
- data/README.md +0 -0
- data/Rakefile +23 -0
- data/app/helpers/record_select_helper.rb +187 -0
- data/app/views/record_select/_browse.html.erb +8 -0
- data/app/views/record_select/_list.html.erb +29 -0
- data/app/views/record_select/_search.html.erb +14 -0
- data/app/views/record_select/browse.js.rjs +4 -0
- data/assets/images/cross.gif +0 -0
- data/assets/images/next.gif +0 -0
- data/assets/images/previous.gif +0 -0
- data/assets/javascripts/record_select.js +327 -0
- data/assets/stylesheets/record_select.css +129 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/zh.yml +5 -0
- data/init.rb +16 -0
- data/install.rb +1 -0
- data/lib/extensions/active_record.rb +9 -0
- data/lib/localization.rb +8 -0
- data/lib/record_select.rb +33 -0
- data/lib/record_select/actions.rb +60 -0
- data/lib/record_select/conditions.rb +88 -0
- data/lib/record_select/config.rb +84 -0
- data/lib/record_select/form_builder.rb +25 -0
- data/lib/record_select/helpers.rb +187 -0
- data/lib/recordselect-custom.rb +7 -0
- data/lib/recordselect-custom/version.rb +5 -0
- data/lib/views/_browse.html +1 -0
- data/lib/views/_browse.rhtml +8 -0
- data/lib/views/_list.rhtml +34 -0
- data/lib/views/_search.rhtml +28 -0
- data/lib/views/browse.rjs +4 -0
- data/recordselect-custom.gemspec +19 -0
- data/test/recordselect_test.rb +8 -0
- data/uninstall.rb +4 -0
- metadata +107 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
1.0
|
2
|
+
* record_select_field always searches on the latest string
|
3
|
+
* R.S. dialog closes on mousedown instead of click (because sometimes clicks are intercepted)
|
4
|
+
* R.S. layers over <select> boxes in IE 6
|
5
|
+
* record_select_field now opens R.S. even when the field loads with focus
|
6
|
+
* new :onchange option for record_select_field, to observe selection events
|
7
|
+
|
8
|
+
1.0rc1
|
9
|
+
* helpers complain if the controller they're configured for doesn't use recordselect
|
10
|
+
* when using keyboard navigation, hitting "enter" to select a record no longer submits the form
|
11
|
+
* if text field is empty when dialog is closed, then recordselect will empty the hidden field as well (lets you deselect)
|
12
|
+
* support for multiple selections (record_multi_select_helper)
|
13
|
+
* param-based search conditions are smarter - can search numeric fields as well
|
14
|
+
* using record_select_conditions_from_controller instead of conditions_for_collection. also added a new record_select_includes override method.
|
15
|
+
* helpers now accept a :class option
|
16
|
+
* fixed bug with url escaping when helpers were configured with multiple parameters
|
17
|
+
* new :label configuration option. it's a proc - the default one calls record.to_label (for backwards compatibility).
|
18
|
+
* cleaned up merge_conditions to use activerecord's sanitize_sql.
|
19
|
+
* div.record-select-container now has a high z-index so it will by default be visible above other absolutely positioned elements.
|
20
|
+
|
21
|
+
0.9
|
22
|
+
stuff
|
23
|
+
|
24
|
+
0.1
|
25
|
+
stuff
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 lion
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Lance Ivy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
RecordSelect
|
2
|
+
============
|
3
|
+
|
4
|
+
(c) 2007 - Lance Ivy
|
5
|
+
|
6
|
+
RecordSelect is a Rails widget to help you pick one record out of many. I designed it as a more usable and performant alternative to generating a massive dropdown list. It relies on AJAX for the cooler uses (all the provided view helpers rely on JavaScript?) but can also function in a pure-http fashion (although multi-select support is provided in a pure-JavaScript implementation).
|
7
|
+
|
8
|
+
Please see the ActionView::Helpers::RecordSelectHelpers for the most common API. More documentation (and HOWTOs) can be found online at http://code.google.com/p/recordselect, and a demo application is available at http://recordselect.googlecode.com/svn/demo.
|
9
|
+
|
10
|
+
= DEPENDENCIES
|
11
|
+
This depends on the excellent Paginator gem by Bruce Williams. This simple gem is available at paginator.rubyforge.org.
|
data/README.md
ADDED
File without changes
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the recordselect plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the recordselect plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Recordselect'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
rdoc.rdoc_files.include('assets/**/*.js')
|
23
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module RecordSelectHelper
|
2
|
+
# Print this from your layout to include everything necessary for RecordSelect to work.
|
3
|
+
# Well, not everything. You need Prototype too.
|
4
|
+
def record_select_includes
|
5
|
+
includes = ''
|
6
|
+
includes << stylesheet_link_tag('record_select/record_select')
|
7
|
+
includes << javascript_include_tag('record_select/record_select')
|
8
|
+
includes
|
9
|
+
end
|
10
|
+
|
11
|
+
# Adds a link on the page that toggles a RecordSelect widget from the given controller.
|
12
|
+
#
|
13
|
+
# *Options*
|
14
|
+
# +onselect+:: JavaScript code to handle selections client-side. This code has access to two variables: id, label. If the code returns false, the dialog will *not* close automatically.
|
15
|
+
# +params+:: Extra URL parameters. If any parameter is a column name, the parameter will be used as a search term to filter the result set.
|
16
|
+
def link_to_record_select(name, controller, options = {})
|
17
|
+
options[:params] ||= {}
|
18
|
+
options[:params].merge!(:controller => controller, :action => :browse)
|
19
|
+
options[:onselect] = "function(id, label) {#{options[:onselect]}}" if options[:onselect]
|
20
|
+
options[:html] ||= {}
|
21
|
+
options[:html][:id] ||= "rs_#{rand(9999)}"
|
22
|
+
|
23
|
+
assert_controller_responds(options[:params][:controller])
|
24
|
+
|
25
|
+
html = link_to_function(name, '', options[:html])
|
26
|
+
html << javascript_tag("new RecordSelect.Dialog(#{options[:html][:id].to_json}, #{url_for(options[:params].merge(:escape => false)).to_json}, {onselect: #{options[:onselect] || ''}})")
|
27
|
+
|
28
|
+
return html
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds a RecordSelect-based form field. The field submits the record's id using a hidden input.
|
32
|
+
#
|
33
|
+
# *Arguments*
|
34
|
+
# +name+:: the input name that will be used to submit the selected record's id.
|
35
|
+
# +current+:: the currently selected object. provide a new record if there're none currently selected and you have not passed the optional :controller argument.
|
36
|
+
#
|
37
|
+
# *Options*
|
38
|
+
# +controller+:: The controller configured to provide the result set. Optional if you have standard resource controllers (e.g. UsersController for the User model), in which case the controller will be inferred from the class of +current+ (the second argument)
|
39
|
+
# +params+:: A hash of extra URL parameters
|
40
|
+
# +id+:: The id to use for the input. Defaults based on the input's name.
|
41
|
+
# +onchange+:: A JavaScript function that will be called whenever something new is selected. It should accept the new id as the first argument, and the new label as the second argument. For example, you could set onchange to be "function(id, label) {alert(id);}", or you could create a JavaScript function somewhere else and set onchange to be "my_function" (without the parantheses!).
|
42
|
+
def record_select_field(name, current, options = {})
|
43
|
+
options[:controller] ||= current.class.to_s.pluralize.underscore
|
44
|
+
options[:params] ||= {}
|
45
|
+
options[:id] ||= name.gsub(/[\[\]]/, '_')
|
46
|
+
|
47
|
+
controller = assert_controller_responds(options[:controller])
|
48
|
+
|
49
|
+
id = label = ''
|
50
|
+
if current and not current.new_record?
|
51
|
+
id = current.id
|
52
|
+
label = label_for_field(current, controller)
|
53
|
+
end
|
54
|
+
|
55
|
+
url = url_for({:action => :browse, :controller => options[:controller], :escape => false}.merge(options[:params]))
|
56
|
+
|
57
|
+
html = text_field_tag(name, nil, :autocomplete => 'off', :id => options[:id], :class => options[:class], :onfocus => "this.focused=true", :onblur => "this.focused=false")
|
58
|
+
html << javascript_tag("$(#{options[:id].to_json}).singleRecordSelect(#{url.to_json}, {id: #{id.to_json}, label: #{label.to_json}, onchange: #{options[:onchange] || ''.to_json}});")
|
59
|
+
|
60
|
+
return html
|
61
|
+
end
|
62
|
+
|
63
|
+
# Assists with the creation of an observer for the :onchange option of the record_select_field method.
|
64
|
+
# Currently only supports building an Ajax.Request based on the id of the selected record.
|
65
|
+
#
|
66
|
+
# options[:url] should be a hash with all the necessary options *except* :id. that parameter
|
67
|
+
# will be provided based on the selected record.
|
68
|
+
#
|
69
|
+
# Question: if selecting users, what's more likely?
|
70
|
+
# /users/5/categories
|
71
|
+
# /categories?user_id=5
|
72
|
+
def record_select_observer(options = {})
|
73
|
+
fn = ""
|
74
|
+
fn << "function(id, value) {"
|
75
|
+
fn << "var url = #{url_for(options[:url].merge(:id => ":id:")).to_json}.replace(/:id:/, id);"
|
76
|
+
fn << "jQuery.get(url);"
|
77
|
+
fn << "}"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds a RecordSelect-based form field for multiple selections. The values submit using a list of hidden inputs.
|
81
|
+
#
|
82
|
+
# *Arguments*
|
83
|
+
# +name+:: the input name that will be used to submit the selected records' ids. empty brackets will be appended to the name.
|
84
|
+
# +current+:: pass a collection of existing associated records
|
85
|
+
#
|
86
|
+
# *Options*
|
87
|
+
# +controller+:: The controller configured to provide the result set.
|
88
|
+
# +params+:: A hash of extra URL parameters
|
89
|
+
# +id+:: The id to use for the input. Defaults based on the input's name.
|
90
|
+
def record_multi_select_field(name, current, options = {})
|
91
|
+
options[:controller] ||= current.first.class.to_s.pluralize.underscore
|
92
|
+
options[:params] ||= {}
|
93
|
+
options[:id] ||= name.gsub(/[\[\]]/, '_')
|
94
|
+
|
95
|
+
controller = assert_controller_responds(options[:controller])
|
96
|
+
|
97
|
+
current = current.inject([]) { |memo, record| memo.push({:id => record.id, :label => label_for_field(record, controller)}) }
|
98
|
+
|
99
|
+
url = url_for({:action => :browse, :controller => options[:controller], :escape => false}.merge(options[:params]))
|
100
|
+
|
101
|
+
html = text_field_tag("#{name}[]", nil, :autocomplete => 'off', :id => options[:id], :class => options[:class], :onfocus => "this.focused=true", :onblur => "this.focused=false")
|
102
|
+
html << content_tag('ul', '', :class => 'record-select-list');
|
103
|
+
html << javascript_tag("new RecordSelect.Multiple(#{options[:id].to_json}, #{url.to_json}, {current: #{current.to_json}});")
|
104
|
+
|
105
|
+
return html
|
106
|
+
end
|
107
|
+
|
108
|
+
# A helper to render RecordSelect partials
|
109
|
+
def render_record_select(options = {}) #:nodoc:
|
110
|
+
controller.send(:render_record_select, options) do |options|
|
111
|
+
render options
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Provides view access to the RecordSelect configuration
|
116
|
+
def record_select_config #:nodoc:
|
117
|
+
controller.send :record_select_config
|
118
|
+
end
|
119
|
+
|
120
|
+
# The id of the RecordSelect widget for the given controller.
|
121
|
+
def record_select_id(controller = nil) #:nodoc:
|
122
|
+
controller ||= params[:controller]
|
123
|
+
"record-select-#{controller.gsub('/', '_')}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def record_select_search_id(controller = nil) #:nodoc:
|
127
|
+
"#{record_select_id(controller)}-search"
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
# render the record using the renderer and add a link to select the record
|
132
|
+
def render_record_in_list(record, controller_path)
|
133
|
+
text = render_record_from_config(record)
|
134
|
+
if record_select_config.link?
|
135
|
+
url_params = {:controller => controller_path, :action => :select, :id => record.id, :escape => false}
|
136
|
+
link_to_remote text, {
|
137
|
+
:url => url_params,
|
138
|
+
:method => :post,
|
139
|
+
:before => 'jQuery(this).toggleClass("selected")',
|
140
|
+
:condition => "jQuery(this).recordSelect_notify()"
|
141
|
+
}, :href => url_params
|
142
|
+
else
|
143
|
+
text
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# uses renderer (defaults to record_select_config.label) to determine how the given record renders.
|
149
|
+
def render_record_from_config(record, renderer = record_select_config.label)
|
150
|
+
case renderer
|
151
|
+
when Symbol, String
|
152
|
+
# return full-html from the named partial
|
153
|
+
render :partial => renderer.to_s, :locals => {:record => record}
|
154
|
+
|
155
|
+
when Proc
|
156
|
+
# return an html-cleaned descriptive string
|
157
|
+
h renderer.call(record)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# uses the result of render_record_from_config to snag an appropriate record label
|
162
|
+
# to display in a field.
|
163
|
+
#
|
164
|
+
# if given a controller, searches for a partial in its views path
|
165
|
+
def label_for_field(record, controller = self.controller)
|
166
|
+
renderer = controller.record_select_config.label
|
167
|
+
case renderer
|
168
|
+
when Symbol, String
|
169
|
+
# find the <label> element and grab its innerHTML
|
170
|
+
description = render_record_from_config(record, File.join(controller.controller_path, renderer.to_s))
|
171
|
+
description.match(/<label[^>]*>(.*)<\/label>/)[1]
|
172
|
+
|
173
|
+
when Proc
|
174
|
+
# just return the string
|
175
|
+
render_record_from_config(record, renderer)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def assert_controller_responds(controller_name)
|
180
|
+
controller_name = "#{controller_name.camelize}Controller"
|
181
|
+
controller = controller_name.constantize
|
182
|
+
unless controller.uses_record_select?
|
183
|
+
raise "#{controller_name} has not been configured to use RecordSelect."
|
184
|
+
end
|
185
|
+
controller
|
186
|
+
end
|
187
|
+
end
|
@@ -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,29 @@
|
|
1
|
+
<%- controller ||= params[:controller] -%>
|
2
|
+
<%- pagination_url_params = params.merge(:controller => controller, :action => :browse, :search => params[:search], :update => 1) if page.prev? || page.next? -%>
|
3
|
+
|
4
|
+
<ol>
|
5
|
+
<li class="found">
|
6
|
+
<%= rs_("%d %s found", page.pager.count, record_select_config.model.to_s.pluralize.titleize.downcase) %>
|
7
|
+
</li>
|
8
|
+
<% if page.prev? -%>
|
9
|
+
<%- prev_url = url_for(pagination_url_params.merge(:page => page.prev.number, :escape => false)) %>
|
10
|
+
<li class="pagination previous">
|
11
|
+
<%- previous_text = image_tag('record_select/previous.gif', :alt => rs_('Previous')) + " " + rs_("Previous %d", page.pager.per_page) %>
|
12
|
+
<%- previous_text = "<div>#{previous_text}</div>" %>
|
13
|
+
<%= link_to_remote previous_text, {:url => prev_url, :method => :get}, {:href => prev_url} %>
|
14
|
+
</li>
|
15
|
+
<% end -%>
|
16
|
+
<% page.items.each do |record| -%>
|
17
|
+
<li class="record <%= cycle 'odd', 'even' %>" id="rs<%= record.id -%>">
|
18
|
+
<%= render_record_in_list(record, controller) %>
|
19
|
+
</li>
|
20
|
+
<% end -%>
|
21
|
+
<% if page.next? -%>
|
22
|
+
<%- next_url = url_for(pagination_url_params.merge(:page => page.next.number, :escape => false)) %>
|
23
|
+
<li class="pagination next">
|
24
|
+
<%- next_text = rs_("Next %d", page.pager.per_page) + " " + image_tag('record_select/next.gif', :alt => rs_('Next')) %>
|
25
|
+
<%- next_text = "<div>#{next_text}</div>" %>
|
26
|
+
<%= link_to_remote next_text, {:url => next_url, :method => :get}, {:href => next_url} %>
|
27
|
+
</li>
|
28
|
+
<% end -%>
|
29
|
+
</ol>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% search_url = url_for(params.merge(:controller => controller, :action => :browse, :page => 1, :update => 1, :escape => false)) -%>
|
2
|
+
<% form_remote_tag :url => search_url, :method => :get,
|
3
|
+
:html => { :action => search_url, :id => record_select_search_id } do %>
|
4
|
+
<%= text_field_tag 'search', params[:search], :autocomplete => 'off', :class => 'text-input' %>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<script type="text/javascript">
|
8
|
+
//<![CDATA[
|
9
|
+
var i = $(<%= record_select_search_id.to_json %>).down('input.text-input');
|
10
|
+
Form.Element.AfterActivity(i, function() {
|
11
|
+
$(<%= record_select_search_id.to_json %>).onsubmit();
|
12
|
+
}, 0.35);
|
13
|
+
//]]>
|
14
|
+
</script>
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,327 @@
|
|
1
|
+
jQuery(window).bind("load", function() { RecordSelect.document_loaded = true });
|
2
|
+
|
3
|
+
jQuery.fn.afterActivity = function(callback, delay, _this) {
|
4
|
+
var element = jQuery(this);
|
5
|
+
if (!delay) delay = 0.35;
|
6
|
+
|
7
|
+
var elem;
|
8
|
+
jQuery(this).bind(("keyup"), function(event) {
|
9
|
+
|
10
|
+
// TODO 用中文输入法输英文时(输入asdf,回车),有问题
|
11
|
+
var keyCode = event.keyCode;
|
12
|
+
|
13
|
+
switch( event.keyCode ) {
|
14
|
+
case 0://Event.KEY_SPACE:
|
15
|
+
case 13://keyCode.ENTER
|
16
|
+
_this.clearActivityTimer();
|
17
|
+
_this.abortRequestXhr("ajaxGetSearh");
|
18
|
+
_this.abortRequestXhr("ajaxGetOpen");
|
19
|
+
case 16://keyCode.SHIFT:
|
20
|
+
case 17://keyCode.CTRL:
|
21
|
+
case 18://keyCode.ALT:
|
22
|
+
case 20://keyCode.CAPSLOCK:
|
23
|
+
case 27://Event.KEY_ESC:
|
24
|
+
case 33://keyCode.PAGEUP:
|
25
|
+
case 34://keyCode.PAGEDOWN:
|
26
|
+
case 35://keyCode.END:
|
27
|
+
case 36://keyCode.HOME:
|
28
|
+
case 37://Event.KEY_LEFT:
|
29
|
+
case 38://keyCode.UP:
|
30
|
+
case 39://Event.KEY_RIGHT:
|
31
|
+
case 40://keyCode.DOWN:
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
if(event.isDefaultPrevented()) return;
|
36
|
+
|
37
|
+
_this.clearActivityTimer();
|
38
|
+
_this.abortRequestXhr("ajaxGetSearh");
|
39
|
+
_this.abortRequestXhr("ajaxGetOpen");
|
40
|
+
_this.activity_timer = setTimeout(function() {
|
41
|
+
if(!_this.is_open()) _this.open();
|
42
|
+
form = element.nextAll('div.record-select-container').find('form');
|
43
|
+
callback = function(){form[0] && form[0].onsubmit(_this)};
|
44
|
+
// TODO is close auto open
|
45
|
+
callback(element.value);
|
46
|
+
}, delay * 1000 + 50);
|
47
|
+
});
|
48
|
+
jQuery(this).bind(("keydown"), function(event) {
|
49
|
+
|
50
|
+
var keyCode = event.keyCode;
|
51
|
+
|
52
|
+
// hot keys
|
53
|
+
if(event.shiftKey && event.altKey && keyCode == 78){
|
54
|
+
_this.clearActivityTimer();
|
55
|
+
_this.abortRequestXhr("ajaxGetSearh");
|
56
|
+
_this.abortRequestXhr("ajaxGetOpen");
|
57
|
+
_this.close();
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
switch( event.keyCode ) {
|
61
|
+
case 38://keyCode.UP:
|
62
|
+
elem = _this.has_current() && _this.current.parent('.record-select') && _this.current.prev(":not(.found)");
|
63
|
+
if (elem && elem.length == 0) elem = _this.container.find("ol li:not(.found):last");
|
64
|
+
_this.highlight(elem);
|
65
|
+
event.preventDefault();
|
66
|
+
return;
|
67
|
+
case 40://keyCode.DOWN:
|
68
|
+
if(!_this.is_open()) _this.open();
|
69
|
+
if (_this.has_current() && _this.current.parent('.record-select')) elem = _this.current.next(":not(.found)");
|
70
|
+
if (elem && elem.length == 0) elem = _this.container.find("ol li:not(.found):first");
|
71
|
+
// prevent moving cursor to end of text field in some browsers
|
72
|
+
_this.highlight(elem);
|
73
|
+
event.preventDefault();
|
74
|
+
return;
|
75
|
+
case 0://Event.KEY_SPACE:
|
76
|
+
case 13://keyCode.ENTER:
|
77
|
+
if (_this.has_current()){
|
78
|
+
elem = _this.current.find('a')[0];
|
79
|
+
switch(elem){
|
80
|
+
case _this.getPrevPageBtn()[0]: _this.doPrevPage(); break;
|
81
|
+
case _this.getNextPageBtn()[0]: _this.doNextPage(); break;
|
82
|
+
default : elem.onclick();
|
83
|
+
}
|
84
|
+
}
|
85
|
+
event.preventDefault();
|
86
|
+
return;
|
87
|
+
case 39://Event.KEY_RIGHT:
|
88
|
+
if(_this.is_open()){
|
89
|
+
_this.doNextPage();
|
90
|
+
event.preventDefault();
|
91
|
+
}
|
92
|
+
return;
|
93
|
+
case 37://Event.KEY_LEFT:
|
94
|
+
if(_this.is_open()){
|
95
|
+
_this.doPrevPage();
|
96
|
+
event.preventDefault();
|
97
|
+
}
|
98
|
+
return;
|
99
|
+
case 9://keyCode.TAB:
|
100
|
+
case 27://Event.KEY_ESC:
|
101
|
+
_this.close();
|
102
|
+
return;
|
103
|
+
case 16://keyCode.SHIFT:
|
104
|
+
case 17://keyCode.CTRL:
|
105
|
+
case 18://keyCode.ALT:
|
106
|
+
case 20://keyCode.CAPSLOCK:
|
107
|
+
case 33://keyCode.PAGEUP:
|
108
|
+
case 34://keyCode.PAGEDOWN:
|
109
|
+
case 35://keyCode.END:
|
110
|
+
case 36://keyCode.HOME:
|
111
|
+
return;
|
112
|
+
}
|
113
|
+
});
|
114
|
+
}
|
115
|
+
|
116
|
+
var RecordSelect = new Object();
|
117
|
+
RecordSelect.document_loaded = false;
|
118
|
+
|
119
|
+
$.fn.recordSelect_notify = function() {
|
120
|
+
var item = $(this);
|
121
|
+
var e = item.parents(".record-select-handler");
|
122
|
+
var my_onselect = e.my_onselect || e.attr('my_onselect') || e.prop('my_onselect');
|
123
|
+
if (typeof my_onselect != 'function') my_onselect = eval(my_onselect);
|
124
|
+
if (my_onselect) {
|
125
|
+
try {
|
126
|
+
par = item.parent();
|
127
|
+
my_onselect(par.attr("id").substring(2), item.find('label').text() || item.text(), e);
|
128
|
+
} catch(e) {
|
129
|
+
alert(e);
|
130
|
+
}
|
131
|
+
return false;
|
132
|
+
}
|
133
|
+
else return true;
|
134
|
+
return false;
|
135
|
+
}
|
136
|
+
|
137
|
+
jQuery.fn.recordSelectSingle = function(url, options) {
|
138
|
+
var obj = $(this);
|
139
|
+
var _this = {};
|
140
|
+
if(!options.openEvents) options.openEvents = "focus click";
|
141
|
+
|
142
|
+
var initialize = function(obj, url, options) {
|
143
|
+
_this.obj = obj;
|
144
|
+
_this.url = url;
|
145
|
+
_this.options = options;
|
146
|
+
_this.js_params = _this.options && _this.options.js_params;
|
147
|
+
_this.container;
|
148
|
+
|
149
|
+
if (RecordSelect.document_loaded) {
|
150
|
+
_this.my_onload();
|
151
|
+
} else {
|
152
|
+
jQuery(window).bind('load', _this.my_onload);
|
153
|
+
}
|
154
|
+
|
155
|
+
jQuery(document.body).bind('mousedown', _this.onbodyclick);
|
156
|
+
};
|
157
|
+
|
158
|
+
_this.my_onload = function() {
|
159
|
+
// initialize the container
|
160
|
+
_this.container = _this.create_container();
|
161
|
+
_this.container.addClass('record-select-autocomplete');
|
162
|
+
|
163
|
+
// create the hidden input
|
164
|
+
_this.obj.after('<input type="hidden" name="" value="" class="value"/>');
|
165
|
+
_this.hidden_input = _this.obj.next("input.value:last");
|
166
|
+
|
167
|
+
// transfer the input name from the text input to the hidden input
|
168
|
+
_this.hidden_input.attr("name", _this.obj.attr("name"));
|
169
|
+
_this.obj.attr("name", '');
|
170
|
+
|
171
|
+
// initialize the values
|
172
|
+
_this.set(_this.options.id, _this.options.label);
|
173
|
+
_this._respond_to_text_field(_this.obj);
|
174
|
+
if (_this.obj.focused) _this.open(); // if it was focused before we could attach observers
|
175
|
+
};
|
176
|
+
|
177
|
+
_this.close = function() {
|
178
|
+
_this.clearActivityTimer();
|
179
|
+
_this.abortRequestXhr("ajaxGetSearh");
|
180
|
+
_this.abortRequestXhr("ajaxGetOpen");
|
181
|
+
// if they close the dialog with the text field empty, then delete the id value
|
182
|
+
if (_this.obj.attr("value") == '') _this.set('', '');
|
183
|
+
|
184
|
+
if (_this._use_iframe_mask()) { _this.container.next('iframe').remove(); }
|
185
|
+
|
186
|
+
_this.container.hide();
|
187
|
+
// hopefully by using remove() instead of innerHTML we won't leak memory
|
188
|
+
_this.container.children().remove();
|
189
|
+
};
|
190
|
+
|
191
|
+
_this.my_onselect = function(id, value, e) {
|
192
|
+
if (_this.options.onchange) _this.options.onchange(_this.obj, id, value);
|
193
|
+
_this.set(id, value);
|
194
|
+
_this.obj.focus();
|
195
|
+
_this.close();
|
196
|
+
};
|
197
|
+
|
198
|
+
_this.open = function(ev) {
|
199
|
+
var elem;
|
200
|
+
if (_this.is_open()) return;
|
201
|
+
if (_this.ajaxGetOpen) return;
|
202
|
+
var _params = {search: _this.obj.val()};
|
203
|
+
if (_this.js_params) _params = $.extend(_params, _this.js_params());
|
204
|
+
|
205
|
+
_this.ajaxGetOpen = jQuery.get(_this.url, _params, function(data, b) {
|
206
|
+
_this.ajaxGetOpen = null;
|
207
|
+
_this.container.html(data);
|
208
|
+
_this.show();
|
209
|
+
_this.highlightFirst();
|
210
|
+
// needs to be mousedown so the event doesn't get canceled by other code (see issue #26)
|
211
|
+
});
|
212
|
+
};
|
213
|
+
|
214
|
+
_this.highlightFirst = function() {
|
215
|
+
var elem = _this.container.find('ol li.record:first');
|
216
|
+
// prevent moving cursor to end of text field in some browsers
|
217
|
+
_this.highlight(elem);
|
218
|
+
};
|
219
|
+
|
220
|
+
_this.abortRequestXhr = function(reqXhrName) {
|
221
|
+
if (_this[reqXhrName]) {
|
222
|
+
_this[reqXhrName].abort();
|
223
|
+
_this[reqXhrName] = null;
|
224
|
+
}
|
225
|
+
};
|
226
|
+
|
227
|
+
_this.clearActivityTimer = function() {
|
228
|
+
if (_this.activity_timer) clearTimeout(_this.activity_timer);
|
229
|
+
};
|
230
|
+
|
231
|
+
_this.show = function() {
|
232
|
+
var offset = _this.obj.position();
|
233
|
+
_this.container.css("left", offset.left + 'px');
|
234
|
+
_this.container.css("top", (_this.obj.outerHeight() + offset.top) + 'px');
|
235
|
+
|
236
|
+
if (_this._use_iframe_mask()) {
|
237
|
+
_this.container.append('<iframe src="javascript:false;" class="record-select-mask" />');
|
238
|
+
var mask = _this.container.next('iframe');
|
239
|
+
mask.css("left", _this.container.position().left);
|
240
|
+
mask.css("top", _this.container.position().top);
|
241
|
+
}
|
242
|
+
|
243
|
+
_this.container.show();
|
244
|
+
if (_this._use_iframe_mask()) {
|
245
|
+
var dimensions = _this.container.children();
|
246
|
+
mask.css("width", dimensions.outerWidth() + 'px');
|
247
|
+
mask.css("height", dimensions.outerHeight() + 'px');
|
248
|
+
}
|
249
|
+
};
|
250
|
+
|
251
|
+
_this.is_open = function() { return (!!_this.container.html()); };
|
252
|
+
|
253
|
+
_this.set = function(id, label) {
|
254
|
+
var text = jQuery("<div>" + label + "</div>").text();
|
255
|
+
_this.obj.val(text);
|
256
|
+
_this.hidden_input.val(_this.options.ass_value ? id : text);
|
257
|
+
};
|
258
|
+
|
259
|
+
_this.create_container = function() {
|
260
|
+
box = obj.parent();
|
261
|
+
box.append('<div class="record-select-container record-select-handler"></div>');
|
262
|
+
e = box.children()[box.children().length - 1];
|
263
|
+
e.my_onselect = _this.my_onselect;
|
264
|
+
e = jQuery(e);
|
265
|
+
e.hide();
|
266
|
+
|
267
|
+
return jQuery(e);
|
268
|
+
};
|
269
|
+
|
270
|
+
_this.onbodyclick = function(ev) {
|
271
|
+
var elem = jQuery(ev.target);
|
272
|
+
if (elem.parents("li.pagination.previous").children("a")[0] == _this.getPrevPageBtn()[0]) _this.doPrevPage();
|
273
|
+
if (elem.parents("li.pagination.next").children("a")[0] == _this.getNextPageBtn()[0]) _this.doNextPage();
|
274
|
+
if (elem.parents("div.record-select-container").length > 0 || elem[0] == _this.obj[0]) return;
|
275
|
+
_this.clearActivityTimer();
|
276
|
+
_this.abortRequestXhr("ajaxGetSearh");
|
277
|
+
_this.abortRequestXhr("ajaxGetOpen");
|
278
|
+
_this.close();
|
279
|
+
};
|
280
|
+
|
281
|
+
_this._respond_to_text_field = function(text_field) {
|
282
|
+
// attach the events to start _this party
|
283
|
+
text_field.bind(options.openEvents, _this.open);
|
284
|
+
|
285
|
+
// the autosearch event - needs to happen slightly late (keyup is later than keypress)
|
286
|
+
text_field.bind('keyup', function() {
|
287
|
+
if (!_this.is_open()) return;
|
288
|
+
_this.container.find('.text-input').attr("value", text_field.attr("value"));
|
289
|
+
});
|
290
|
+
|
291
|
+
// keyboard navigation, if available
|
292
|
+
if (_this.onkeypress) { text_field.bind((jQuery.browser.opera? "keypress" : "keydown"), _this.onkeypress); }
|
293
|
+
};
|
294
|
+
_this.current = null;
|
295
|
+
|
296
|
+
_this.onkeypress = function(event) { };
|
297
|
+
|
298
|
+
_this.highlight = function(obj) {
|
299
|
+
if (_this.has_current()) _this.current.removeClass('current');
|
300
|
+
if (!obj) return;
|
301
|
+
_this.current = jQuery(obj);
|
302
|
+
obj.addClass('current');
|
303
|
+
};
|
304
|
+
|
305
|
+
_this.getPrevPageBtn = function() { return _this.container.find("li.pagination.previous a"); };
|
306
|
+
_this.getNextPageBtn = function() { return _this.container.find("li.pagination.next a"); };
|
307
|
+
|
308
|
+
_this.doPrevPage = function(){ if(_this.getPrevPageBtn().length > 0) _this.doPage(_this.getPrevPageBtn().attr("href")); }
|
309
|
+
_this.doNextPage = function(){ if(_this.getNextPageBtn().length > 0) _this.doPage(_this.getNextPageBtn().attr("href")); }
|
310
|
+
|
311
|
+
_this.doPage = function(url) {
|
312
|
+
jQuery.ajax({
|
313
|
+
url: url,
|
314
|
+
data: _this.js_params && _this.js_params(),
|
315
|
+
success: function(data) {
|
316
|
+
eval(data);
|
317
|
+
_this.highlightFirst();
|
318
|
+
}
|
319
|
+
});
|
320
|
+
};
|
321
|
+
|
322
|
+
_this._use_iframe_mask = function() { return false; };
|
323
|
+
_this.has_current = function() { return _this.current && _this.current.length > 0; };
|
324
|
+
|
325
|
+
obj.afterActivity(null, 0.35, _this);
|
326
|
+
initialize(obj, url, options);
|
327
|
+
};
|