Module: UserEvents

Included in:
Element
Defined in:
lib/source/redshift/user_events.rb

Overview

The UserEvents module mixes in methods for handling user-generated events such as mouse gestures and keystrokes. UserEvents is also responsible for defining new event types based on native events.

Only Document, Window, and objects of class Element respond to UserEvents methods. See module CodeEvents for information on defining and handling custom callback events.

Constant Summary collapse

NATIVE_EVENTS =
{
# mouse buttons
  :click            => 2,
  :dblclick         => 2,
  :mouseup          => 2,
  :mousedown        => 2,
  :contextmenu      => 2,
# mouse wheel
  :mousewheel       => 2,
  :DOMMouseScroll   => 2,
# mouse movement
  :mouseover        => 2,
  :mouseout         => 2,
  :mousemove        => 2,
  :selectstart      => 2,
  :selectend        => 2,
# keyboard
  :keydown          => 2,
  :keypress         => 2,
  :keyup            => 2,
# form elements
  :focus            => 2,
  :blur             => 2,
  :change           => 2,
  :reset            => 2,
  :select           => 2,
  :submit           => 2,
# window
  :load             => 1,
  :unload           => 1,
  :beforeunload     => 1,
  :resize           => 1,
  :move             => 1,
  :DOMContentLoaded => 1,
  :readystatechange => 1,
# misc
  :error            => 1,
  :abort            => 1,
  :scroll           => 1
}
DEFINED_EVENTS =
{
  :mouse_enter => {:base => 'mouseover', :condition => proc(`c$UserEvents.mousecheck`) },
  :mouse_leave => {:base => 'mouseout',  :condition => proc(`c$UserEvents.mousecheck`) },
  :mouse_wheel => {:base => gecko? ? 'DOMMouseScroll' : 'mousewheel' }
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.define(sym, hash = {}) ⇒ Object

call-seq:

UserEvents.define(sym, { :base => symbol [, ...] }) -> true

Adds a new event type sym to the UserEvents::DEFINED_EVENTS hash with the given options. The following options can be set:

Required

base

A symbol representing the native event type upon which sym will be based.

Optional

condition

A Proc object with parameters |element, event| which, if returning false when evaluated for a given event, kills the event.

on_listen

A Proc object with parameters |element, listener_proc| that is evaluated when sym is passed to UserEvents#listen.

on_unlisten

A Proc object with parameters |element, listener_proc| that is evaluated when sym is passed to UserEvents#unlisten.


condition = proc {|element,event| event.shift? }                                                                            #=> #<Proc:0x393825>
on_listen = proc {|element,listener_proc| puts "%s responds to shift-click with %s" % [element.inspect, listener_proc] }    #=> #<Proc:0x3935da>

UserEvents.define(:shift_click, :base => 'click', :condition => condition, :on_listen => on_listen)                         #=> true

Document['#example'].listen :shift_click do |element,event|
  puts "%s was shift-clicked" % element.inspect
end

produces:

#<Element: DIV id="example"> responds to shift-click with #<Proc:0x3962ae>

shift-clicking element ‘#example’ produces:

#<Element: DIV id="example"> was shift-clicked


106
107
108
109
# File 'lib/source/redshift/user_events.rb', line 106

def self.define(sym, hash = {})
  DEFINED_EVENTS[sym.to_sym] = hash
  return true
end

.extended(base) ⇒ Object

:nodoc:

Raises:



115
116
117
# File 'lib/source/redshift/user_events.rb', line 115

def self.extended(base) # :nodoc:
  raise(TypeError, 'only Document and Window may be extended with UserEvents; use CodeEvents instead') unless [`c$Document`, `c$Window`].include?(base)
end

.included(base) ⇒ Object

:nodoc:

Raises:



111
112
113
# File 'lib/source/redshift/user_events.rb', line 111

def self.included(base) # :nodoc:
  raise(TypeError, 'only class Element and the singleton objects Window and Document may include UserEvents; use CodeEvents instead') unless base == `c$Element`
end

Instance Method Details

#add_listener(sym, &block) ⇒ Object

:nodoc:



168
169
170
171
172
173
# File 'lib/source/redshift/user_events.rb', line 168

def add_listener(sym, &block) # :nodoc:
  `var el=this.__native__,type=sym.__value__,fn=block.__block__`
  `if(type==='unload'){var old=fn,that=this;fn=function(){that.m$remove_listener($q('unload'),fn);old();};}else{var collected = {};collected[this.__id__]=this}` # TODO: put this element into "collected"
  `if(el.addEventListener){el.addEventListener(type,fn,false);}else{el.attachEvent('on'+type,fn);}`
  return self
end

#listen(sym, &block) ⇒ Object

call-seq:

obj.listen(sym) { |element,event| block } -> obj

Adds a listener to obj for a native or defined user event type sym, then returns obj.

Document['#example'].listen :click do |element, event|
  puts "%s was clicked" % element.inspect
end

clicking element ‘#example’ produces:

#<Element: DIV id="example"> was clicked


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/source/redshift/user_events.rb', line 133

def listen(sym, &block)
  type = sym.to_sym
  events = @events ||= {}
  events[type]     ||= {}
  return self if events[type][block]
  
  custom    = DEFINED_EVENTS[type]
  condition = block
  real_type = type
  
  if custom
    custom[:on_listen].call(self, block) if custom[:on_listen]
    if custom[:condition]
      condition = lambda {|element,event| custom[:condition].call(element,event) ? block.call(element,event) : true }
    end
    real_type = (custom[:base] || real_type).to_sym
  end
  
  listener     = lambda { block.call(self,nil); }
  native_event = NATIVE_EVENTS[real_type]
  
  if native_event
    if native_event == 2
      listener = lambda do |native_event|
        event = `$v(native_event)`
        event.kill! if condition.call(self,event) == false
      end
    end
    self.add_listener(real_type, &listener)
  end
  
  events[type][block] = listener
  return self
end

#remove_listener(sym, &block) ⇒ Object

:nodoc:



210
211
212
213
214
# File 'lib/source/redshift/user_events.rb', line 210

def remove_listener(sym, &block) # :nodoc:
  `var el=this.__native__,type=sym.__value__,fn=block.__block__`
  `if(this.removeEventListener){this.removeEventListener(type,fn,false);}else{this.detachEvent('on'+type,fn);}`
  return self
end

#unlisten(sym, &block) ⇒ Object

call-seq:

obj.unlisten(sym, &proc) -> obj

Unsets the function proc as a listener for event type sym, then returns obj. proc must be the self-same object originally assigned as a listener.

proc_1 = proc {|element,event| puts "%s was clicked" % element.inspect }
proc_2 = proc {|element,event| puts "%s has two listeners" % element.inspect }
elem = Document['#example']

elem.listen(:click, proc_1).listen(:click, proc_2)    #=> #<Element: DIV id="example">
elem.unlisten(:click, proc_2)                         #=> #<Element: DIV id="example">

clicking element ‘#example’ produces:

#<Element: DIV id="example"> was clicked


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/source/redshift/user_events.rb', line 193

def unlisten(sym, &block)
  type   = sym.to_sym
  events = @events
  return self unless events && events[type] && events[type][block]
  
  listener = events[type].delete(block)
  custom   = DEFINED_EVENTS[type]
  
  if custom
    custom[:on_unlisten].call(self, block) if custom[:on_unlisten]
    type = (custom[:base] || type).to_sym
  end
  
  self.remove_listener(type, &listener) if NATIVE_EVENTS[type]
  return self
end