admin_assistant 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README +3 -28
  2. data/Rakefile +18 -10
  3. data/init.rb +1 -0
  4. data/install.rb +5 -1
  5. data/lib/admin_assistant/active_record_column.rb +211 -0
  6. data/lib/admin_assistant/association_target.rb +35 -0
  7. data/lib/admin_assistant/belongs_to_column.rb +186 -0
  8. data/lib/admin_assistant/builder.rb +222 -35
  9. data/lib/admin_assistant/column.rb +266 -297
  10. data/lib/admin_assistant/default_search_column.rb +41 -0
  11. data/lib/admin_assistant/file_column_column.rb +73 -0
  12. data/lib/admin_assistant/form_view.rb +7 -68
  13. data/lib/admin_assistant/helper.rb +4 -2
  14. data/lib/admin_assistant/index.rb +101 -81
  15. data/lib/admin_assistant/paperclip_column.rb +45 -0
  16. data/lib/admin_assistant/polymorphic_belongs_to_column.rb +102 -0
  17. data/lib/admin_assistant/request/autocomplete.rb +47 -0
  18. data/lib/admin_assistant/request/base.rb +176 -0
  19. data/lib/admin_assistant/request/create.rb +27 -0
  20. data/lib/admin_assistant/request/destroy.rb +15 -0
  21. data/lib/admin_assistant/request/edit.rb +11 -0
  22. data/lib/admin_assistant/request/index.rb +26 -0
  23. data/lib/admin_assistant/request/new.rb +19 -0
  24. data/lib/admin_assistant/request/show.rb +24 -0
  25. data/lib/admin_assistant/request/update.rb +44 -0
  26. data/lib/admin_assistant/search.rb +82 -0
  27. data/lib/admin_assistant/show_view.rb +20 -0
  28. data/lib/admin_assistant/virtual_column.rb +61 -0
  29. data/lib/admin_assistant.rb +190 -85
  30. data/lib/javascripts/admin_assistant.js +253 -0
  31. data/lib/stylesheets/activescaffold.css +219 -0
  32. data/lib/stylesheets/default.css +119 -0
  33. data/lib/views/_polymorphic_field_search.html.erb +89 -0
  34. data/lib/views/_restricted_autocompleter.html.erb +53 -0
  35. data/lib/views/autocomplete.html.erb +11 -0
  36. data/lib/views/form.html.erb +6 -3
  37. data/lib/views/index.html.erb +53 -46
  38. data/lib/views/show.html.erb +19 -0
  39. data/vendor/ar_query/MIT-LICENSE +20 -0
  40. data/vendor/ar_query/README +0 -0
  41. data/vendor/ar_query/init.rb +1 -0
  42. data/vendor/ar_query/install.rb +1 -0
  43. data/vendor/ar_query/lib/ar_query.rb +137 -0
  44. data/vendor/ar_query/spec/ar_query_spec.rb +253 -0
  45. data/vendor/ar_query/tasks/ar_query_tasks.rake +0 -0
  46. data/vendor/ar_query/uninstall.rb +1 -0
  47. metadata +39 -16
  48. data/lib/admin_assistant/request.rb +0 -183
  49. data/lib/stylesheets/admin_assistant.css +0 -75
@@ -1,61 +1,39 @@
1
- require 'admin_assistant/builder'
1
+ $: << File.join(File.dirname(__FILE__), '../vendor/ar_query/lib')
2
+ require 'find'
2
3
  require 'admin_assistant/column'
3
- require 'admin_assistant/form_view'
4
- require 'admin_assistant/helper'
5
- require 'admin_assistant/index'
6
- require 'admin_assistant/request'
4
+ Find.find(File.dirname(__FILE__)) do |path|
5
+ if path =~ %r|\.rb$| && path !~ %r|admin_assistant\.rb$| &&
6
+ path !~ %r|admin_assistant/column\.rb$|
7
+ require path
8
+ end
9
+ end
7
10
  require 'will_paginate'
8
11
 
9
12
  class AdminAssistant
10
- attr_reader :custom_column_labels, :form_settings, :index_settings,
11
- :model_class
12
- attr_accessor :actions
13
-
13
+ attr_reader :base_settings, :controller_class, :form_settings,
14
+ :index_settings, :model_class, :show_settings
15
+ attr_accessor :actions, :custom_destroy
16
+ attr_writer :model_class_name
17
+
18
+ def self.template_file(template_name)
19
+ "#{File.dirname(__FILE__)}/views/#{template_name}.html.erb"
20
+ end
21
+
14
22
  def initialize(controller_class, model_class)
15
23
  @controller_class, @model_class = controller_class, model_class
16
- @actions = [:index, :create, :update]
24
+ @model = Model.new model_class
25
+ @actions = [:index, :create, :update, :show]
17
26
  @form_settings = FormSettings.new self
18
27
  @index_settings = IndexSettings.new self
19
- @custom_column_labels = {}
20
- end
21
-
22
- def belongs_to_assoc(name)
23
- model_class.reflect_on_all_associations.detect { |assoc|
24
- assoc.macro == :belongs_to && assoc.name.to_s == name.to_s
25
- }
26
- end
27
-
28
- def column(name)
29
- column = if file_columns.include?(name)
30
- FileColumnColumn.new name
31
- elsif (ar_column = model_class.columns_hash[name.to_s])
32
- ActiveRecordColumn.new(ar_column)
33
- elsif belongs_to_assoc = belongs_to_assoc(name)
34
- BelongsToColumn.new(belongs_to_assoc)
35
- else
36
- AdminAssistantColumn.new(name)
37
- end
38
- if column && (custom = custom_column_labels[name.to_s])
39
- column.custom_label = custom
40
- end
41
- column
28
+ @show_settings = ShowSettings.new self
29
+ @base_settings = BaseSettings.new self
42
30
  end
43
31
 
44
- def column_name_or_assoc_name(name)
45
- result = name
46
- ar_column = model_class.columns_hash[name.to_s]
47
- if ar_column
48
- associations = model_class.reflect_on_all_associations
49
- if belongs_to_assoc = associations.detect { |assoc|
50
- assoc.macro == :belongs_to && assoc.association_foreign_key == name
51
- }
52
- result = belongs_to_assoc.name.to_s
53
- end
54
- end
55
- result
32
+ def [](name)
33
+ @base_settings[name]
56
34
  end
57
35
 
58
- def columns(names)
36
+ def accumulate_columns(names)
59
37
  columns = paperclip_attachments.map { |paperclip_attachment|
60
38
  PaperclipColumn.new paperclip_attachment
61
39
  }
@@ -68,10 +46,62 @@ class AdminAssistant
68
46
  columns
69
47
  end
70
48
 
49
+ def accumulate_belongs_to_columns(names)
50
+ accumulate_columns(names).select { |column| column.is_a?(BelongsToColumn) }
51
+ end
52
+
53
+ def autocomplete_actions
54
+ ac_actions = []
55
+ if [:new, :create, :edit, :update].any? { |action|
56
+ actions.include?(action)
57
+ }
58
+ accumulate_belongs_to_columns(default_column_names).each { |column|
59
+ ac_actions << "autocomplete_#{column.name}".to_sym
60
+ }
61
+ end
62
+ if actions.include?(:index)
63
+ base_settings.all_polymorphic_types.each do |p_type|
64
+ ac_actions << "autocomplete_#{p_type.name.underscore.downcase}".to_sym
65
+ end
66
+ end
67
+ ac_actions.uniq
68
+ end
69
+
70
+ def column(name)
71
+ if file_columns.include?(name.to_s)
72
+ FileColumnColumn.new name
73
+ elsif paperclip_attachments.include?(name)
74
+ PaperclipColumn.new name
75
+ elsif (belongs_to_assoc = belongs_to_assoc(name) or
76
+ belongs_to_assoc = belongs_to_assoc_by_foreign_key(name))
77
+ column_based_on_belongs_to_assoc name, belongs_to_assoc
78
+ elsif belongs_to_assoc = belongs_to_assoc_by_polymorphic_type(name)
79
+ # skip it, actually
80
+ elsif (ar_column = @model_class.columns_hash[name.to_s])
81
+ ActiveRecordColumn.new ar_column
82
+ else
83
+ VirtualColumn.new name, @model_class, self
84
+ end
85
+ end
86
+
87
+ def column_based_on_belongs_to_assoc(name, belongs_to_assoc)
88
+ if belongs_to_assoc.options[:polymorphic]
89
+ PolymorphicBelongsToColumn.new belongs_to_assoc
90
+ else
91
+ BelongsToColumn.new(
92
+ belongs_to_assoc,
93
+ :match_text_fields_in_search =>
94
+ search_settings[name].match_text_fields_for_association?,
95
+ :sort_by => index_settings[name].sort_by
96
+ )
97
+ end
98
+ end
99
+
71
100
  def controller_actions
72
101
  c_actions = actions.clone
73
102
  c_actions << :new if c_actions.include?(:create)
74
103
  c_actions << :edit if c_actions.include?(:update)
104
+ c_actions.concat(autocomplete_actions) if autocomplete_actions
75
105
  c_actions
76
106
  end
77
107
 
@@ -79,58 +109,50 @@ class AdminAssistant
79
109
  controller.controller_path.gsub(%r|/|, '_')
80
110
  end
81
111
 
82
- def dispatch_to_request_method(request_method, controller)
112
+ def dispatch_to_request_method(request_class, controller)
83
113
  controller.instance_variable_set :@admin_assistant, self
84
- klass = Request.const_get request_method.to_s.capitalize
85
- @request = klass.new(self, controller)
114
+ @request = request_class.new(self, controller)
86
115
  @request.call
87
116
  @request = nil
88
117
  end
89
118
 
90
- def file_columns
91
- fc = []
92
- if model_class.respond_to?(:file_column)
93
- model_class.columns.each do |column|
94
- suffixes = %w( relative_path dir relative_dir temp )
95
- if suffixes.all? { |suffix|
96
- model_class.method_defined? "#{column.name}_#{suffix}".to_sym
97
- }
98
- fc << column.name
99
- end
100
- end
101
- end
102
- fc
103
- end
104
-
105
119
  def method_missing(meth, *args)
106
- request_methods = [:create, :destroy, :edit, :index, :new, :update]
107
- if request_methods.include?(meth) and args.size == 1
108
- dispatch_to_request_method meth, args.first
120
+ if @model.respond_to?(meth)
121
+ @model.send meth, *args
109
122
  else
110
- if meth.to_s =~ /(.*)\?/ && request_methods.include?($1.to_sym)
111
- @actions.include?($1.to_sym)
112
- elsif @request.respond_to?(meth)
113
- @request.send meth, *args
123
+ request_methods = [
124
+ :create, :destroy, :edit, :index, :new, :update, :show
125
+ ]
126
+ if request_methods.include?(meth) and args.size == 1
127
+ request_class = Request.const_get meth.to_s.capitalize
128
+ dispatch_to_request_method request_class, args.first
129
+ elsif autocomplete_actions && autocomplete_actions.include?(meth)
130
+ dispatch_to_request_method Request::Autocomplete, args.first
114
131
  else
115
- super
132
+ if meth.to_s =~ /(.*)\?/ && request_methods.include?($1.to_sym)
133
+ @controller_class.public_instance_methods.include?($1)
134
+ elsif @request.respond_to?(meth)
135
+ @request.send meth, *args
136
+ else
137
+ super
138
+ end
116
139
  end
117
140
  end
118
141
  end
119
142
 
120
143
  def model_class_name
121
- model_class.name.gsub(/([A-Z])/, ' \1')[1..-1].downcase
144
+ @model_class_name ||
145
+ @model_class.name.gsub(/([A-Z])/, ' \1')[1..-1].downcase
122
146
  end
123
-
124
- def paperclip_attachments
125
- pa = []
126
- if model_class.respond_to?(:attachment_definitions)
127
- if model_class.attachment_definitions
128
- pa = model_class.attachment_definitions.map { |name, definition|
129
- name
130
- }
131
- end
147
+
148
+ def profile(msg)
149
+ if @request_start_time
150
+ puts "#{msg}: #{Time.now - @request_start_time}"
132
151
  end
133
- pa
152
+ end
153
+
154
+ def search_settings
155
+ @index_settings.search_settings
134
156
  end
135
157
 
136
158
  def url_params(a = action)
@@ -159,13 +181,96 @@ class AdminAssistant
159
181
  end
160
182
  end
161
183
  end
184
+
185
+ class Model
186
+ def initialize(ar_model)
187
+ @ar_model = ar_model
188
+ end
189
+
190
+ def belongs_to_associations
191
+ @ar_model.reflect_on_all_associations.select { |assoc|
192
+ assoc.macro == :belongs_to
193
+ }
194
+ end
195
+
196
+ def belongs_to_assoc(association_name)
197
+ belongs_to_associations.detect { |assoc|
198
+ assoc.name.to_s == association_name.to_s
199
+ }
200
+ end
201
+
202
+ def belongs_to_assoc_by_foreign_key(foreign_key)
203
+ belongs_to_associations.detect { |assoc|
204
+ assoc.association_foreign_key == foreign_key
205
+ }
206
+ end
207
+
208
+ def belongs_to_assoc_by_polymorphic_type(name)
209
+ if name =~ /^(.*)_type/
210
+ belongs_to_associations.detect { |assoc|
211
+ assoc.options[:polymorphic] && $1 == assoc.name.to_s
212
+ }
213
+ end
214
+ end
215
+
216
+ def default_column_names
217
+ @ar_model.columns.reject { |ar_column|
218
+ %w(id created_at updated_at).include?(ar_column.name)
219
+ }.map { |ar_column| ar_column.name }
220
+ end
221
+
222
+ def file_columns
223
+ unless @file_columns
224
+ @file_columns = []
225
+ if @ar_model.respond_to?(:file_column)
226
+ names_to_check = @ar_model.columns.map &:name
227
+ names_to_check.concat(
228
+ @ar_model.instance_methods.
229
+ select { |m| m =~ /=$/ }.
230
+ map { |m| m.gsub(/=/, '')}.
231
+ select { |m| @ar_model.instance_methods.include?(m) }
232
+ )
233
+ names_to_check.uniq.each do |name|
234
+ suffixes = %w( relative_path dir relative_dir temp )
235
+ if suffixes.all? { |suffix|
236
+ @ar_model.method_defined? "#{name}_#{suffix}".to_sym
237
+ }
238
+ @file_columns << name
239
+ end
240
+ end
241
+ end
242
+ end
243
+ @file_columns
244
+ end
245
+
246
+ def paperclip_attachments
247
+ pa = []
248
+ if @ar_model.respond_to?(:attachment_definitions)
249
+ if @ar_model.attachment_definitions
250
+ pa = @ar_model.attachment_definitions.map { |name, definition|
251
+ name
252
+ }
253
+ end
254
+ end
255
+ pa
256
+ end
257
+
258
+ def searchable_columns
259
+ @ar_model.columns.select { |column|
260
+ [:string, :text].include?(column.type)
261
+ }
262
+ end
263
+ end
162
264
  end
163
265
 
164
266
  ActionController::Base.send :include, AdminAssistant::ControllerMethods
165
267
 
268
+ css_dir = "#{RAILS_ROOT}/public/stylesheets/admin_assistant"
269
+ FileUtils.mkdir(css_dir) unless File.exist?(css_dir)
270
+ FileUtils.cp_r(Dir.glob("#{File.dirname(__FILE__)}/stylesheets/*"), css_dir)
166
271
  FileUtils.copy(
167
- "#{File.dirname(__FILE__)}/stylesheets/admin_assistant.css",
168
- "#{RAILS_ROOT}/public/stylesheets/admin_assistant.css"
272
+ "#{File.dirname(__FILE__)}/javascripts/admin_assistant.js",
273
+ "#{RAILS_ROOT}/public/javascripts/admin_assistant.js"
169
274
  )
170
275
  images_dir = "#{RAILS_ROOT}/public/images/admin_assistant"
171
276
  FileUtils.mkdir(images_dir) unless File.exist?(images_dir)
@@ -0,0 +1,253 @@
1
+ /******************************************************************************
2
+ Don't edit this file: It gets re-copied every time the server starts.
3
+ ******************************************************************************/
4
+
5
+ var AdminAssistant = {};
6
+ AdminAssistant.clear_datetime_select = function(name) {
7
+ $R(1,5).each(function(index) {
8
+ $(name + '_' + index + 'i').value = '';
9
+ });
10
+ };
11
+
12
+ AdminAssistant.show_search_form = function() {
13
+ $('search_form').show();
14
+ };
15
+
16
+ AdminAssistant.PolymorphicFieldSearch = Class.create();
17
+ AdminAssistant.PolymorphicFieldSearch.prototype = {
18
+ initialize: function(name, types) {
19
+ this.name = name;
20
+ this.hiddenFieldId = 'search_' + name + '_id';
21
+ this.hiddenTypeFieldId = 'search_' + name + '_type';
22
+ this.autocompleteTypes = types.autocompleteTypes;
23
+ this.autocompleters = {};
24
+ this.autocompleteTypes.each(function(autocompleteType) {
25
+ autocompleter = this.newAutocompleter(autocompleteType);
26
+ this.autocompleters[autocompleteType.name] = autocompleter;
27
+ }, this);
28
+ this.idTypes = types.idTypes;
29
+ this.idTypes.each(function(idType) {
30
+ Event.observe(
31
+ this.name + '_' + idType.name + "_id", 'keyup',
32
+ this.setHiddenFieldsFromIdField.bind(this)
33
+ );
34
+ }, this);
35
+ this.selectTypes = types.selectTypes;
36
+ this.selectTypes.each(function(selectType) {
37
+ Event.observe(
38
+ this.name + '_' + selectType.name + "_id", 'change',
39
+ this.setHiddenFieldsFromSelectField.bind(this)
40
+ );
41
+ }, this);
42
+ },
43
+
44
+ clearHiddenFields: function() {
45
+ this.hiddenField().value = '';
46
+ this.hiddenTypeField().value = '';
47
+ },
48
+
49
+ clearInputsExceptFor: function(name_to_not_clear) {
50
+ this.autocompleteTypes.each(function(autocompleteType) {
51
+ if (autocompleteType.name != name_to_not_clear) {
52
+ var autocompleter = this.autocompleters[autocompleteType.name]
53
+ autocompleter.clearTextField();
54
+ autocompleter.setClearLinkVisibility();
55
+ }
56
+ }, this);
57
+ this.idTypes.each(function(idType) {
58
+ if (idType.name != name_to_not_clear) {
59
+ $(this.name + '_' + idType.name + '_id').value = '';
60
+ }
61
+ }, this);
62
+ this.selectTypes.each(function(selectType) {
63
+ if (selectType.name != name_to_not_clear) {
64
+ $(this.name + '_' + selectType.name + '_id').value = '';
65
+ }
66
+ }, this);
67
+ },
68
+
69
+ hiddenField: function() {
70
+ return $(this.hiddenFieldId);
71
+ },
72
+
73
+ hiddenTypeField: function() {
74
+ return $(this.hiddenTypeFieldId);
75
+ },
76
+
77
+ newAutocompleter: function(autocompleteType) {
78
+ options = {
79
+ clearLink: autocompleteType.clearLink,
80
+ includeBlank: true,
81
+ modelName: autocompleteType.name,
82
+ palette: autocompleteType.palette,
83
+ paletteClonesInputWidth: false,
84
+ parameters: autocompleteType.parameters,
85
+ paramName: autocompleteType.name + '_autocomplete_input',
86
+ textField: autocompleteType.textField
87
+ };
88
+ options.afterAutocomplete = function(value) {
89
+ this.hiddenTypeField().value = autocompleteType.type;
90
+ this.clearInputsExceptFor(autocompleteType.name);
91
+ }.bind(this);
92
+ options.afterClearSelected = function(value) {
93
+ this.hiddenTypeField().value = '';
94
+ }.bind(this);
95
+ return new AdminAssistant.RestrictedAutocompleter(
96
+ autocompleteType.name, this.hiddenFieldId, autocompleteType.url,
97
+ options
98
+ );
99
+ },
100
+
101
+ setHiddenFieldsFromIdField: function(event) {
102
+ input = event.findElement();
103
+ idType = this.idTypes.detect(function(idt) {
104
+ return (this.name + '_' + idt.name + '_id' == input.id);
105
+ }, this);
106
+ if (input.value == '') {
107
+ if (this.hiddenTypeField().value == idType.type) {
108
+ this.clearHiddenFields();
109
+ }
110
+ } else {
111
+ this.hiddenField().value = input.value;
112
+ this.hiddenTypeField().value = idType.type;
113
+ this.clearInputsExceptFor(idType.name);
114
+ }
115
+ },
116
+
117
+ setHiddenFieldsFromSelectField: function(event) {
118
+ select = event.findElement();
119
+ selectType = this.selectTypes.detect(function(st) {
120
+ return (this.name + '_' + st.name + '_id' == select.id);
121
+ }, this);
122
+ if (select.value == '') {
123
+ if (this.hiddenTypeField().value == selectType.type) {
124
+ this.clearHiddenFields();
125
+ }
126
+ } else {
127
+ this.hiddenField().value = select.value;
128
+ this.hiddenTypeField().value = selectType.type;
129
+ this.clearInputsExceptFor(selectType.name);
130
+ }
131
+ }
132
+ }
133
+
134
+ /*
135
+ This autocompleter restricts based on a list of names and matching IDs; the IDs
136
+ are set in a hidden field. Arguments
137
+ */
138
+ AdminAssistant.RestrictedAutocompleter = Class.create();
139
+ AdminAssistant.RestrictedAutocompleter.prototype = {
140
+ initialize: function(name, hiddenField, url, options) {
141
+ this.name = name;
142
+ this.textField = options.textField || (this.name + '_autocomplete_input');
143
+ this.palette = options.palette || (this.name + '_autocomplete_palette');
144
+ this.selectedTextFieldValue = $(this.textField).value;
145
+ this.hiddenField = hiddenField;
146
+ this.includeBlank = options.includeBlank;
147
+ this.modelName = options.modelName || this.name;
148
+ this.clearLink = options.clearLink || ('clear_' + this.name + '_link');
149
+ ajaxAutocompleterOptions = {
150
+ afterUpdateElement: this.autocompleteAfterUpdateElement.bind(this),
151
+ fullSearch: true,
152
+ onHide: this.autocompleteOnHide,
153
+ parameters: options.parameters,
154
+ paramName: options.paramName,
155
+ partialChars: 1,
156
+ };
157
+ if (!options.paletteClonesInputWidth) {
158
+ ajaxAutocompleterOptions.onShow = this.onShowWithoutCloningInputWidth;
159
+ }
160
+ var ac = new Ajax.Autocompleter(
161
+ this.textField, this.palette, url, ajaxAutocompleterOptions
162
+ );
163
+ this.changeArrowBehavior(ac);
164
+ this.setClearLinkVisibility();
165
+ Event.observe(this.clearLink, 'click', function(event) {
166
+ this.clearSelected();
167
+ }.bind(this));
168
+ this.afterAutocomplete = options.afterAutocomplete;
169
+ this.afterClearSelected = options.afterClearSelected;
170
+ },
171
+
172
+ /*
173
+ This is the callback that fires after a selection is made from the
174
+ autocompleter results.
175
+
176
+ In addition to setting hidden id fields, it will place the text label of the
177
+ selected item in the search field. It assumes that your selectedElement is
178
+ one with simple text contents or some markup structure with an element of
179
+ class "title". In the latter case, it will use the text of the "title"
180
+ element.
181
+ */
182
+ autocompleteAfterUpdateElement: function(element, selectedElement) {
183
+ var id_name = element.id;
184
+ var input_id = id_name.substr(id_name.length - 1)
185
+ var selected_value = selectedElement.id.substr(this.modelName.length)
186
+
187
+ // If they have more complex markup inside the selection, get the "title" element
188
+ var title_element = selectedElement.down(".title")
189
+ if (title_element) selectedElement = title_element
190
+
191
+ $(this.hiddenField).value = selected_value
192
+ this.selectedTextFieldValue =
193
+ selectedElement.innerHTML.unescapeHTML().strip();
194
+ this.setClearLinkVisibility();
195
+ if (this.afterAutocomplete) { this.afterAutocomplete(selected_value); }
196
+ },
197
+
198
+ /*
199
+ Refresh the text fill-in field to the value reflected in the underlying
200
+ hidden input fields
201
+ */
202
+ autocompleteOnHide: function( element, update ) {
203
+ if (this.selectedTextFieldValue) {
204
+ element.value = this.selectedTextFieldValue;
205
+ }
206
+ new Effect.Fade(update,{duration:0.15});
207
+ },
208
+
209
+ /*
210
+ Overriding totally wierd arrow-up and arrow-down scrolling behavior built
211
+ into the autocompleter in scriptaculous 1.7.0
212
+ */
213
+ changeArrowBehavior: function(ac) {
214
+ ac.markPrevious = function() {
215
+ if(this.index > 0) this.index--
216
+ else this.index = this.entryCount-1;
217
+ };
218
+ ac.markNext = function() {
219
+ if(this.index < this.entryCount-1) this.index++
220
+ else this.index = 0;
221
+ };
222
+ },
223
+
224
+ clearSelected: function() {
225
+ $(this.hiddenField).value = '';
226
+ this.clearTextField();
227
+ this.setClearLinkVisibility();
228
+ if (this.afterClearSelected) { this.afterClearSelected(); }
229
+ },
230
+
231
+ onShowWithoutCloningInputWidth: function(element, update){
232
+ if(!update.style.position || update.style.position=='absolute') {
233
+ update.style.position = 'absolute';
234
+ Position.clone(element, update, {
235
+ setHeight: false, setWidth: false, offsetTop: element.offsetHeight
236
+ });
237
+ }
238
+ Effect.Appear(update,{duration:0.15});
239
+ },
240
+
241
+ clearTextField: function() {
242
+ $(this.textField).value = '';
243
+ },
244
+
245
+ setClearLinkVisibility: function() {
246
+ if ($(this.textField).value == '') {
247
+ $(this.clearLink).hide();
248
+ } else {
249
+ if (this.includeBlank) { $(this.clearLink).show(); }
250
+ }
251
+ }
252
+ }
253
+