Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #960: remote_autocomplete_v3.diff

File remote_autocomplete_v3.diff, 25.4 kB (added by madrobby, 3 years ago)

Fixes a problem with decodeHTML. Also, added Effect.Fade (not used by autocompleter)

  • actionpack/lib/action_view/helpers/javascripts/prototype.js

    old new  
    452452    element.style.backgroundColor = "#ffff" + current.toColorPart(); 
    453453  } 
    454454} 
     455 
     456Effect.Fade = Class.create(); 
     457Effect.Fade.prototype = { 
     458  initialize: function(element) { 
     459    this.element = $(element); 
     460    this.start  = 100; 
     461    this.finish = 0; 
     462    this.current = this.start; 
     463    this.fade(); 
     464  }, 
     465   
     466  fade: function() { 
     467    if (this.isFinished()) return; 
     468    if (this.timer) clearTimeout(this.timer); 
     469    this.setOpacity(this.element, this.current); 
     470    this.current -= 10; 
     471    this.timer = setTimeout(this.fade.bind(this), 100); 
     472  }, 
     473   
     474  isFinished: function() { 
     475    return this.current <= this.finish; 
     476  }, 
     477   
     478  setOpacity: function(element, opacity) { 
     479    opacity = (opacity == 100) ? 99.999 : opacity; 
     480    element.style.filter = "alpha(opacity:"+opacity+")"; 
     481    element.style.opacity = opacity/100; 
     482  } 
     483} 
     484 
     485/*--------------------------------------------------------------------------*/ 
     486 
     487Event = { 
     488  KEY_BACKSPACE: 8, 
     489  KEY_TAB:       9, 
     490  KEY_RETURN:   13, 
     491  KEY_ESC:      27, 
     492  KEY_LEFT:     37, 
     493  KEY_UP:       38, 
     494  KEY_RIGHT:    39, 
     495  KEY_DOWN:     40, 
     496  KEY_DELETE:   46, 
     497 
     498  element: function(event) { 
     499    return event.srcElement || event.currentTarget; 
     500  }, 
     501   
     502  stop: function(event) { 
     503    if(event.preventDefault)  
     504      { event.preventDefault(); event.stopPropagation(); } 
     505    else  
     506      event.returnValue = false; 
     507  }, 
     508   
     509  getParentNodeOrSelfByName: function(event, nodeName) { 
     510    element = Event.element(event); 
     511      while(element.nodeName != nodeName && element.parentNode)  
     512        element = element.parentNode;  
     513    return element; 
     514  }, 
     515   
     516  observeKeypress: function(element, observer) { 
     517    if(navigator.appVersion.indexOf('AppleWebKit')>0)  
     518      { $(element).addEventListener("keydown",observer,false); return; } 
     519    if($(element).addEventListener) $(element).addEventListener("keypress",observer,false) 
     520      else if($(element).attachEvent) $(element).attachEvent("onkeydown",observer); 
     521  }, 
     522   
     523  observeBlur: function(element, observer) { 
     524    if($(element).addEventListener) $(element).addEventListener("blur",observer,false) 
     525      else if($(element).attachEvent) $(element).attachEvent("onblur",observer); 
     526  }, 
     527   
     528  observeClick: function(element, observer) { 
     529    if($(element).addEventListener) $(element).addEventListener("click",observer,false) 
     530      else if($(element).attachEvent) $(element).attachEvent("onclick",observer); 
     531  }, 
     532   
     533  observeHover: function(element, observer) { 
     534    if($(element).addEventListener) $(element).addEventListener("mouseover",observer,false) 
     535      else if($(element).attachEvent) $(element).attachEvent("onmouseover",observer); 
     536  } 
     537   
     538} 
     539 
     540Text = { 
     541  stripTags: function(htmlstr) { 
     542    return htmlstr.replace(/<\/?[^>]+>/gi,""); 
     543  }, 
     544  decodeHTML: function(htmlstr) { 
     545    return htmlstr.replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&apos;/gi,"'").replace(/&amp;/gi,"&").replace(/[\n\r]/gi,""); 
     546  } 
     547} 
     548 
     549Element = { 
     550  show: function(element) { 
     551    $(element).style.display = ''; 
     552  }, 
     553  hide: function(element) { 
     554    $(element).style.display = 'none'; 
     555  }, 
     556  samePositionAs: function(element, aselement) { 
     557    if(navigator.appVersion.indexOf('MSIE')>0) { 
     558      $(element).style.top      = $(aselement).style.top; 
     559      $(element).style.left     = $(aselement).style.left; 
     560      $(element).style.width    = $(aselement).offsetWidth; 
     561      $(element).style.height   = $(aselement).offsetHeight; 
     562    } 
     563  } 
     564} 
     565                      
     566Ajax.Autocomplete = Class.create(); 
     567Ajax.Autocomplete.prototype = (new Ajax.Base()).extend({ 
     568  initialize: function(element, update, url, options) { 
     569    this.element     = $(element);  
     570    this.update      = $(update);   
     571    this.has_focus   = false;  
     572    this.changed     = false;  
     573    this.active      = false;  
     574    this.index       = 0;      
     575    this.entry_count = 0;     
     576    this.url         = url; 
     577 
     578    this.setOptions(options); 
     579    this.options.asynchronous = true; 
     580    this.options.onComplete   = this.onComplete.bind(this) 
     581    this.options.frequency    = this.options.frequency || 0.4; 
     582    this.options.min_chars    = this.options.min_chars || 1; 
     583    this.options.method       = 'post'; 
     584     
     585    if(this.options.indicator) 
     586      this.indicator = $(this.options.indicator); 
     587        
     588    this.observer = null; 
     589     
     590    Event.observeKeypress (this.element, this.onKeyPress.bindAsEventListener(this)); 
     591    Event.observeClick    (document, this.onBlur.bindAsEventListener(this)); 
     592  }, 
     593   
     594  show: function() { 
     595    Element.show(this.update); 
     596    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0)) { 
     597      new Insertion.Before(this.update,  
     598       '<iframe id="' + this.update.id + '_iefix" style="display:none;" src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 
     599      this.iefix = $(this.update.id+'_iefix'); 
     600      this.iefix.style.position = 'absolute'; 
     601      this.iefix.style.zIndex = 1; 
     602      this.update.style.zIndex = 2; 
     603    } 
     604    if(this.iefix) { 
     605      Element.samePositionAs(this.iefix, this.update); 
     606      Element.show(this.iefix); 
     607    } 
     608  }, 
     609   
     610  hide: function() { 
     611    if(this.iefix) Element.hide(this.iefix); 
     612    Element.hide(this.update); 
     613  }, 
     614   
     615  startIndicator: function() { 
     616    if(this.indicator) Element.show(this.indicator); 
     617  }, 
     618   
     619  stopIndicator: function() { 
     620    if(this.indicator) Element.hide(this.indicator); 
     621  }, 
     622   
     623  onObserverEvent: function() { 
     624    this.changed = false;    
     625    if(this.element.value.length>=this.options.min_chars) { 
     626      this.startIndicator(); 
     627      this.options.parameters = this.options.callback ? 
     628        this.options.callback(this.element, Form.Element.getValue(this.element)) : 
     629          Form.Element.getValue(this.element); 
     630      new Ajax.Request(this.url, this.options); 
     631    } else { 
     632      this.active = false; 
     633      this.hide(); 
     634    } 
     635  }, 
     636   
     637  onComplete: function(request) { 
     638    if(!this.changed) { 
     639      this.update.innerHTML = request.responseText; 
     640 
     641      if(this.update.firstChild && this.update.firstChild.childNodes) { 
     642        this.entry_count =  
     643          this.update.firstChild.childNodes.length; 
     644        for (var i = 0; i < this.entry_count; i++) { 
     645          entry = this.get_entry(i); 
     646          entry.autocompleteIndex = i; 
     647          Event.observeHover(entry, this.onHover.bindAsEventListener(this)); 
     648          Event.observeClick(entry, this.onClick.bindAsEventListener(this)); 
     649        } 
     650      } else {  
     651        this.entry_count = 0; 
     652      } 
     653       
     654      this.stopIndicator(); 
     655       
     656      this.index = 0; 
     657      this.render(); 
     658    } 
     659  }, 
     660   
     661  onKeyPress: function(event) { 
     662    if(this.active) 
     663      switch(event.keyCode) { 
     664       case Event.KEY_TAB: 
     665       case Event.KEY_RETURN: 
     666         this.select_entry(); 
     667         Event.stop(event); 
     668       case Event.KEY_ESC: 
     669         this.hide(); 
     670         this.active = false; 
     671         return; 
     672       case Event.KEY_LEFT: 
     673       case Event.KEY_RIGHT: 
     674         return; 
     675       case Event.KEY_UP: 
     676         this.mark_previous(); 
     677         this.render(); 
     678         return; 
     679       case Event.KEY_DOWN: 
     680         this.mark_next(); 
     681         this.render(); 
     682         return; 
     683      } 
     684     else  
     685      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)  
     686        return; 
     687     
     688    this.changed = true; 
     689    this.has_focus = true; 
     690     
     691    if(this.observer) clearTimeout(this.observer); 
     692      this.observer =  
     693        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 
     694  }, 
     695   
     696  onHover: function(event) { 
     697    element = Event.getParentNodeOrSelfByName(event, 'LI'); 
     698    if(this.index != element.autocompleteIndex)  
     699    { 
     700        this.index = element.autocompleteIndex; 
     701        this.render(); 
     702    } 
     703  }, 
     704   
     705  onClick: function(event) { 
     706    element = Event.getParentNodeOrSelfByName(event, 'LI'); 
     707    this.index = element.autocompleteIndex; 
     708    this.select_entry(); 
     709  }, 
     710   
     711  onBlur: function(event) { 
     712    element = Event.element(event); 
     713    if(element==this.update) return; 
     714    while(element.parentNode)  
     715      { element = element.parentNode; if(element==this.update) return; } 
     716    this.hide(); 
     717    this.active = false; 
     718  },  
     719   
     720  render: function() { 
     721    if(this.entry_count > 0) { 
     722      for (var i = 0; i < this.entry_count; i++) 
     723        this.get_entry(i).className = 
     724          this.index==i ? 'selected' : ''; 
     725         
     726      if(this.has_focus) {  
     727        if(this.get_current_entry().scrollIntoView)  
     728          this.get_current_entry().scrollIntoView(false); 
     729         
     730        this.show(); 
     731        this.active = true; 
     732      } 
     733    } else this.hide(); 
     734  }, 
     735   
     736  mark_previous: function() { 
     737    if(this.index > 0) this.index-- 
     738      else this.index = this.entry_count-1; 
     739  }, 
     740   
     741  mark_next: function() { 
     742    if(this.index < this.entry_count-1) this.index++ 
     743      else this.index = 0; 
     744  }, 
     745   
     746  get_entry: function(index) { 
     747    return this.update.firstChild.childNodes[index]; 
     748  }, 
     749   
     750  get_current_entry: function() { 
     751    return this.get_entry(this.index); 
     752  }, 
     753   
     754  select_entry: function() { 
     755    this.hide(); 
     756    this.active = false; 
     757    value = Text.decodeHTML(Text.stripTags(this.get_current_entry().innerHTML)); 
     758    this.element.value = value; 
     759    this.element.focus(); 
     760  } 
     761}); 
  • actionpack/lib/action_view/helpers/form_helper.rb

    old new  
    11require 'cgi' 
    22require File.dirname(__FILE__) + '/date_helper' 
    33require File.dirname(__FILE__) + '/tag_helper' 
     4require File.dirname(__FILE__) + '/javascript_helper' 
     5require File.dirname(__FILE__) + '/url_helper' 
    46 
    57module ActionView 
    68  module Helpers 
     
    141143 
    142144    class InstanceTag #:nodoc: 
    143145      include Helpers::TagHelper 
     146      include Helpers::JavascriptHelper 
     147      include Helpers::UrlHelper 
    144148 
    145149      attr_reader :method_name, :object_name 
    146150 
     
    151155      def initialize(object_name, method_name, template_object, local_binding = nil) 
    152156        @object_name, @method_name = object_name, method_name 
    153157        @template_object, @local_binding = template_object, local_binding 
     158        @controller = @template_object.controller # url_for in :remote_autocomplete 
    154159        if @object_name.sub!(/\[\]$/,"") 
    155160          @auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast 
    156161        end 
     
    166171        options["type"] = field_type 
    167172        options["value"] ||= value_before_type_cast unless field_type == "file" 
    168173        add_default_name_and_id(options) 
    169         tag("input", options) 
     174        autocomplete_text = "" 
     175        if options["remote_autocomplete"] 
     176           if options["indicator"] 
     177             autocomplete_text <<  
     178               tag("img", { :id => "#{options['id']}_autocomplete_indicator", :style => 'display:none;', :src => options["indicator"] }) 
     179             options.delete("indicator") 
     180           end 
     181           autocomplete_text <<      # content_tag needed to have <div></div> instead of <div/> 
     182             content_tag("div", "", { :id => "#{options['id']}_autocomplete", :class => "autocomplete", :style => "display:none;" }) 
     183           autocomplete_text <<  
     184             autocomplete_for(options['id'],  
     185               { :url => options["remote_autocomplete"], :with => "'for=' + escape(value)", :indicator => "#{options['id']}_autocomplete_indicator" }) 
     186           options["autocomplete"] = "off" 
     187           options.delete("remote_autocomplete") 
     188        end 
     189        tag_text =  tag("input", options) 
     190        tag_text << autocomplete_text 
     191        return tag_text 
    170192      end 
    171193 
    172194      def to_radio_button_tag(tag_value, options = {}) 
  • actionpack/lib/action_view/helpers/javascript_helper.rb

    old new  
    147147      def observe_form(form_id, options = {}) 
    148148        build_observer('Form.Observer', form_id, options) 
    149149      end 
     150       
     151      # Adds Ajax autocomplete functionality to the text input field with the  
     152      # DOM ID specified by +field_id+. 
     153      # 
     154      # This function expects that the called action returns a HTML <ul> list, 
     155      # or nothing if no entries should be displayed for autocompletion. 
     156      # Note: There must be no whitespace between the returned elements.  
     157      #  
     158      # Required +options+ are: 
     159      # <tt>:url</tt>::       Specifies the DOM ID of the element whose 
     160      #                       innerHTML should be updated with the autocomplete 
     161      #                       entries returned by XMLHttpRequest. 
     162      #  
     163      # Addtional +options+ are: 
     164      # <tt>:update</tt>::    Specifies the DOM ID of the element whose  
     165      #                       innerHTML should be updated with the autocomplete 
     166      #                       entries returned by the Ajax request.  
     167      #                       Defaults to field_id + '_autocomplete' 
     168      # <tt>:with</tt>::      A Javascript expression specifying the 
     169      #                       parameters for the XMLHttpRequest. This defaults 
     170      #                       to 'value', which in the evaluated context  
     171      #                       refers to the new field value. 
     172      # <tt>:indicator</tt>:: Specifies the DOM ID of an elment which will be 
     173      #                       displayed while autocomplete is running.  
     174      # 
     175      def autocomplete_for(field_id, options = {}) 
     176        function =  "new Ajax.Autocomplete(" 
     177        function << "'#{field_id}', " 
     178        function << "'" + (options[:update] || "#{field_id}_autocomplete") + "', " 
     179        function << "'#{url_for(options[:url])}'" 
     180        js_options = {} 
     181        js_options[:callback] = "function(element, value) {return #{options[:with]}}" if options[:with] 
     182        js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] 
     183        function << (', {' + js_options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}') 
     184        function << ")" 
     185               
     186        "<script type=\"text/javascript\">#{function}</script>"         
     187      end 
     188       
     189      # Use this method in your view to generate a return for the Ajax automplete requests. 
     190      # 
     191      # Example Action: 
     192      #   @items = Item.find_all([ 'LOWER(description) LIKE ?',  
     193      #     '%' + @params["for"].downcase + '%' ], 'description ASC') 
     194      #   render_without_layout 
     195      #    
     196      # Example View: 
     197      #   <%= autocomplete_responder @items, 'description' %> 
     198      # 
     199      def autocomplete_responder(entries, field, phrase = nil) 
     200        "<ul>#{entries.map { |entry| '<li>' + (phrase ? highlight(entry[field],phrase) : h(entry[field])) + '</li>' }.join}</ul>" if entries 
     201      end 
    150202 
    151203      # Escape carrier returns and single and double quotes for Javascript segments. 
    152204      def escape_javascript(javascript) 
  • railties/html/javascripts/prototype.js

    old new  
    452452    element.style.backgroundColor = "#ffff" + current.toColorPart(); 
    453453  } 
    454454} 
     455 
     456Effect.Fade = Class.create(); 
     457Effect.Fade.prototype = { 
     458  initialize: function(element) { 
     459    this.element = $(element); 
     460    this.start  = 100; 
     461    this.finish = 0; 
     462    this.current = this.start; 
     463    this.fade(); 
     464  }, 
     465   
     466  fade: function() { 
     467    if (this.isFinished()) return; 
     468    if (this.timer) clearTimeout(this.timer); 
     469    this.setOpacity(this.element, this.current); 
     470    this.current -= 10; 
     471    this.timer = setTimeout(this.fade.bind(this), 100); 
     472  }, 
     473   
     474  isFinished: function() { 
     475    return this.current <= this.finish; 
     476  }, 
     477   
     478  setOpacity: function(element, opacity) { 
     479    opacity = (opacity == 100) ? 99.999 : opacity; 
     480    element.style.filter = "alpha(opacity:"+opacity+")"; 
     481    element.style.opacity = opacity/100; 
     482  } 
     483} 
     484 
     485/*--------------------------------------------------------------------------*/ 
     486 
     487Event = { 
     488  KEY_BACKSPACE: 8, 
     489  KEY_TAB:       9, 
     490  KEY_RETURN:   13, 
     491  KEY_ESC:      27, 
     492  KEY_LEFT:     37, 
     493  KEY_UP:       38, 
     494  KEY_RIGHT:    39, 
     495  KEY_DOWN:     40, 
     496  KEY_DELETE:   46, 
     497 
     498  element: function(event) { 
     499    return event.srcElement || event.currentTarget; 
     500  }, 
     501   
     502  stop: function(event) { 
     503    if(event.preventDefault)  
     504      { event.preventDefault(); event.stopPropagation(); } 
     505    else  
     506      event.returnValue = false; 
     507  }, 
     508   
     509  getParentNodeOrSelfByName: function(event, nodeName) { 
     510    element = Event.element(event); 
     511      while(element.nodeName != nodeName && element.parentNode)  
     512        element = element.parentNode;  
     513    return element; 
     514  }, 
     515   
     516  observeKeypress: function(element, observer) { 
     517    if(navigator.appVersion.indexOf('AppleWebKit')>0)  
     518      { $(element).addEventListener("keydown",observer,false); return; } 
     519    if($(element).addEventListener) $(element).addEventListener("keypress",observer,false) 
     520      else if($(element).attachEvent) $(element).attachEvent("onkeydown",observer); 
     521  }, 
     522   
     523  observeBlur: function(element, observer) { 
     524    if($(element).addEventListener) $(element).addEventListener("blur",observer,false) 
     525      else if($(element).attachEvent) $(element).attachEvent("onblur",observer); 
     526  }, 
     527   
     528  observeClick: function(element, observer) { 
     529    if($(element).addEventListener) $(element).addEventListener("click",observer,false) 
     530      else if($(element).attachEvent) $(element).attachEvent("onclick",observer); 
     531  }, 
     532   
     533  observeHover: function(element, observer) { 
     534    if($(element).addEventListener) $(element).addEventListener("mouseover",observer,false) 
     535      else if($(element).attachEvent) $(element).attachEvent("onmouseover",observer); 
     536  } 
     537   
     538} 
     539 
     540Text = { 
     541  stripTags: function(htmlstr) { 
     542    return htmlstr.replace(/<\/?[^>]+>/gi,""); 
     543  }, 
     544  decodeHTML: function(htmlstr) { 
     545    return htmlstr.replace(/&lt;/gi,"<").replace(/&gt;/gi,">").replace(/&quot;/gi,'"').replace(/&apos;/gi,"'").replace(/&amp;/gi,"&").replace(/[\n\r]/gi,""); 
     546  } 
     547} 
     548 
     549Element = { 
     550  show: function(element) { 
     551    $(element).style.display = ''; 
     552  }, 
     553  hide: function(element) { 
     554    $(element).style.display = 'none'; 
     555  }, 
     556  samePositionAs: function(element, aselement) { 
     557    if(navigator.appVersion.indexOf('MSIE')>0) { 
     558      $(element).style.top      = $(aselement).style.top; 
     559      $(element).style.left     = $(aselement).style.left; 
     560      $(element).style.width    = $(aselement).offsetWidth; 
     561      $(element).style.height   = $(aselement).offsetHeight; 
     562    } 
     563  } 
     564} 
     565                      
     566Ajax.Autocomplete = Class.create(); 
     567Ajax.Autocomplete.prototype = (new Ajax.Base()).extend({ 
     568  initialize: function(element, update, url, options) { 
     569    this.element     = $(element);  
     570    this.update      = $(update);   
     571    this.has_focus   = false;  
     572    this.changed     = false;  
     573    this.active      = false;  
     574    this.index       = 0;      
     575    this.entry_count = 0;     
     576    this.url         = url; 
     577 
     578    this.setOptions(options); 
     579    this.options.asynchronous = true; 
     580    this.options.onComplete   = this.onComplete.bind(this) 
     581    this.options.frequency    = this.options.frequency || 0.4; 
     582    this.options.min_chars    = this.options.min_chars || 1; 
     583    this.options.method       = 'post'; 
     584     
     585    if(this.options.indicator) 
     586      this.indicator = $(this.options.indicator); 
     587        
     588    this.observer = null; 
     589     
     590    Event.observeKeypress (this.element, this.onKeyPress.bindAsEventListener(this)); 
     591    Event.observeClick    (document, this.onBlur.bindAsEventListener(this)); 
     592  }, 
     593   
     594  show: function() { 
     595    Element.show(this.update); 
     596    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0)) { 
     597      new Insertion.Before(this.update,  
     598       '<iframe id="' + this.update.id + '_iefix" style="display:none;" src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); 
     599      this.iefix = $(this.update.id+'_iefix'); 
     600      this.iefix.style.position = 'absolute'; 
     601      this.iefix.style.zIndex = 1; 
     602      this.update.style.zIndex = 2; 
     603    } 
     604    if(this.iefix) { 
     605      Element.samePositionAs(this.iefix, this.update); 
     606      Element.show(this.iefix); 
     607    } 
     608  }, 
     609   
     610  hide: function() { 
     611    if(this.iefix) Element.hide(this.iefix); 
     612    Element.hide(this.update); 
     613  }, 
     614   
     615  startIndicator: function() { 
     616    if(this.indicator) Element.show(this.indicator); 
     617  }, 
     618   
     619  stopIndicator: function() { 
     620    if(this.indicator) Element.hide(this.indicator); 
     621  }, 
     622   
     623  onObserverEvent: function() { 
     624    this.changed = false;    
     625    if(this.element.value.length>=this.options.min_chars) { 
     626      this.startIndicator(); 
     627      this.options.parameters = this.options.callback ? 
     628        this.options.callback(this.element, Form.Element.getValue(this.element)) : 
     629          Form.Element.getValue(this.element); 
     630      new Ajax.Request(this.url, this.options); 
     631    } else { 
     632      this.active = false; 
     633      this.hide(); 
     634    } 
     635  }, 
     636   
     637  onComplete: function(request) { 
     638    if(!this.changed) { 
     639      this.update.innerHTML = request.responseText; 
     640 
     641      if(this.update.firstChild && this.update.firstChild.childNodes) { 
     642        this.entry_count =  
     643          this.update.firstChild.childNodes.length; 
     644        for (var i = 0; i < this.entry_count; i++) { 
     645          entry = this.get_entry(i); 
     646          entry.autocompleteIndex = i; 
     647          Event.observeHover(entry, this.onHover.bindAsEventListener(this)); 
     648          Event.observeClick(entry, this.onClick.bindAsEventListener(this)); 
     649        } 
     650      } else {  
     651        this.entry_count = 0; 
     652      } 
     653       
     654      this.stopIndicator(); 
     655       
     656      this.index = 0; 
     657      this.render(); 
     658    } 
     659  }, 
     660   
     661  onKeyPress: function(event) { 
     662    if(this.active) 
     663      switch(event.keyCode) { 
     664       case Event.KEY_TAB: 
     665       case Event.KEY_RETURN: 
     666         this.select_entry(); 
     667         Event.stop(event); 
     668       case Event.KEY_ESC: 
     669         this.hide(); 
     670         this.active = false; 
     671         return; 
     672       case Event.KEY_LEFT: 
     673       case Event.KEY_RIGHT: 
     674         return; 
     675       case Event.KEY_UP: 
     676         this.mark_previous(); 
     677         this.render(); 
     678         return; 
     679       case Event.KEY_DOWN: 
     680         this.mark_next(); 
     681         this.render(); 
     682         return; 
     683      } 
     684     else  
     685      if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)  
     686        return; 
     687     
     688    this.changed = true; 
     689    this.has_focus = true; 
     690     
     691    if(this.observer) clearTimeout(this.observer); 
     692      this.observer =  
     693        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); 
     694  }, 
     695   
     696  onHover: function(event) { 
     697    element = Event.getParentNodeOrSelfByName(event, 'LI'); 
     698    if(this.index != element.autocompleteIndex)  
     699    { 
     700        this.index = element.autocompleteIndex; 
     701        this.render(); 
     702    } 
     703  }, 
     704   
     705  onClick: function(event) { 
     706    element = Event.getParentNodeOrSelfByName(event, 'LI'); 
     707    this.index = element.autocompleteIndex; 
     708    this.select_entry(); 
     709  }, 
     710   
     711  onBlur: function(event) { 
     712    element = Event.element(event); 
     713    if(element==this.update) return; 
     714    while(element.parentNode)  
     715      { element = element.parentNode; if(element==this.update) return; } 
     716    this.hide(); 
     717    this.active = false; 
     718  },  
     719   
     720  render: function() { 
     721    if(this.entry_count > 0) { 
     722      for (var i = 0; i < this.entry_count; i++) 
     723        this.get_entry(i).className = 
     724          this.index==i ? 'selected' : ''; 
     725         
     726      if(this.has_focus) {  
     727        if(this.get_current_entry().scrollIntoView)  
     728          this.get_current_entry().scrollIntoView(false); 
     729         
     730        this.show(); 
     731        this.active = true; 
     732      } 
     733    } else this.hide(); 
     734  }, 
     735   
     736  mark_previous: function() { 
     737    if(this.index > 0) this.index-- 
     738      else this.index = this.entry_count-1; 
     739  }, 
     740   
     741  mark_next: function() { 
     742    if(this.index < this.entry_count-1) this.index++ 
     743      else this.index = 0; 
     744  }, 
     745   
     746  get_entry: function(index) { 
     747    return this.update.firstChild.childNodes[index]; 
     748  }, 
     749   
     750  get_current_entry: function() { 
     751    return this.get_entry(this.index); 
     752  }, 
     753   
     754  select_entry: function() { 
     755    this.hide(); 
     756    this.active = false; 
     757    value = Text.decodeHTML(Text.stripTags(this.get_current_entry().innerHTML)); 
     758    this.element.value = value; 
     759    this.element.focus(); 
     760  } 
     761}); 
  • railties/lib/rails_generator/generators/components/scaffold/templates/style.css

    old new  
    1616a:visited { color: #666; } 
    1717a:hover { color: #fff; background-color:#000; } 
    1818 
     19.autocomplete {  
     20  position:absolute;  
     21  z-index:10;  
     22  border:1px solid #aaa;  
     23  background-color:#fff;  
     24} 
     25.autocomplete ul, .autocomplete li { margin:0; padding:0; border: 0; } 
     26.autocomplete li { padding:2px; display:block; cursor:pointer; } 
     27.autocomplete .selected { background-color: #ffa; } 
     28 
    1929.fieldWithErrors { 
    2030  padding: 2px; 
    2131  background-color: red;