class Lebowski::Foundation::ProxyObject

ProxyObject is the root object for all objects that are to proxy an object within a web brower. This provides all of the core functionality allowing you to communicate with a remote object and access other remote objects through the use of relative property paths.

In the case where you have created a custom SproutCore object and want to have a proxy for it then your proxy must inherit from the SCObject class that inherits this class.

Attributes

driver[R]
name[RW]
parent[R]
rel_path[R]

Public Class Methods

new(parent=nil, rel_path=nil, driver=nil) click to toggle source

Creates a new proxy object instance.

Try to refrain from overriding this method. Instead, if you wish to perform some operations during the time an object is being initialized, override the init_ext method

@param parent {Object} parent object of this object. Must inherit from Lebowski::Foundation::ProxyObject @param rel_path {String} a relative path to the remote object (e.g. 'foo', 'foo.bar') @param driver {Object} used to remotely communicate with the remote object.

@see init_ext

# File lib/lebowski/foundation/proxy_object.rb, line 41
def initialize(parent=nil, rel_path=nil, driver=nil)
  
  if not init_expected_parent_type.nil?
    if not parent.kind_of? init_expected_parent_type
      raise ArgumentInvalidTypeError.new "parent", parent, init_expected_parent_type
    end
  end
  
  @parent = parent
  @rel_path = rel_path
  @driver = driver
  @guid = nil
  @cached_proxy_objects = {}
  @defined_proxies = {}
  @name = ""
  
  init_ext()
end

Public Instance Methods

==(obj) click to toggle source

Override the == operator so that a proxy object can be compared to another proxy object via their SproutCore GUIDs

Calls superclass method
# File lib/lebowski/foundation/proxy_object.rb, line 424
def ==(obj)
  return (self.sc_guid == obj.sc_guid) if obj.kind_of?(ProxyObject)
  return super(obj)
end
Also aliased as: eql?
[](rel_path, expected_type=nil) click to toggle source

The primary method used to access a proxied object's properties. Accessing a property is done using a relative property path. The path is a chain of properties connected using dots '.'. The type is automatically determined, but in cases where a particular type is expected, you can optionally supply what the expected type should be.

As an example, to access an object's property called 'foo', you can do the following:

value = object['foo']

If you expect the value's type to be, say, an number, you can do the following:

value = object['foo', :number]

In the case where you expect the value to be a type of object you can do one of the following:

value = object['foo', 'SC.SomeObject']

value = object['foo', SomeObject]

In the first case, you are supply the object type as a string, in the second case you are supplying the expected type with a proxy class. For the second option to work you must first supply the proxy to the proxy factory.

To access a property through a chain of objects, you supply a relative path, like so:

value = object['path.to.some.property']

Remember that the path is relative to the object you passed the path to. The approach is used to work with how you would normally access properties using the SproutCore framework.

The fundamental types detected within the web browser are the following:

Null     - null in JavaScript
Error    - SproutCore error object
String   - string in JavaScript
Number   - number in JavaScript
Boolean  - boolean in JavaScript
Hash     - a JavaScript hash object
Object   - a SproutCore object
Array    - a JavaScript array
Class    - A SproutCore class

Based on the value's type within the browser, this method will translate the value as follows:

Null     -> nil 
Error    -> :error
String   -> standard string
Number   -> standard number
Boolean  -> standard boolean
Hash     -> a generic proxy object
Object   -> closest matching proxy object
Array    -> standard array for basic types; an object array (ObjectArray) for objects
Class    -> a generic proxy object

If the given relative path tries to reference a property that is not defined then :undefined is returned.

The two special cases are when the basic type of the relative path is a SproutCore object or an array. In the case of a SproutCore object, the closest matching object type will be returned based on what proxies have been provided to the proxy factory. For instance, let's say you have custom view that derives from SC.View. If no proxy has been made for the custom view then the next closest proxy will be returned, which would be a View proxy that is already part of the lebowski framework. If your require a proxy to interact with the custom view then you need to add that proxy to the proxy framework.

When the type is an array, the proxy object will check the content of the array to determine their type. If all the content in the array are of the same type then it will return a corresponding array made up content with that type. So, for example, if an object has a property that is an array of strings then a basic array of string will be returned. In the case where the array contains either hash objects or SproutCore objects then an ObjectArray will be returned.

# File lib/lebowski/foundation/proxy_object.rb, line 386
def [](rel_path, expected_type=nil)
  if (not rel_path.kind_of?(String)) or rel_path.empty?
    raise ArgumentError.new "rel_path must be a valid string"
  end
  
  if @cached_proxy_objects.has_key? rel_path
    return @cached_proxy_objects[rel_path]
  end
  
  defined_proxy = @defined_proxies.has_key?(rel_path) ? @defined_proxies[rel_path] : nil
    
  path_defined = path_defined? rel_path
  
  if path_defined
    path = defined_path rel_path
    expected_type = path.expected_type if (path.has_expected_type? and expected_type.nil?)
  end

  unraveled_rel_path = unravel_relative_path rel_path
  value = fetch_rel_path_value unraveled_rel_path, expected_type
  
  if value.kind_of? ProxyObject
    value = value.represent_as(defined_proxy) if (not defined_proxy.nil?)
    @cached_proxy_objects[rel_path] = value if (path_defined or not defined_proxy.nil?) 
  end
  
  return value
end
__eql?(obj)

Retain a reference to the original eql? method so we can still use it

Alias for: eql?
abs_path() click to toggle source

Returns the absolute path of this object based on the parent object heirarchy. As an example, this object's parent has an absolute path of 'foo' and this object has relative path of 'bar', then the absolute path will be 'foo.bar'

# File lib/lebowski/foundation/proxy_object.rb, line 142
def abs_path()   
  if not @abs_path.nil? 
    return @abs_path
  end

  if @parent.nil? or @parent.abs_path.nil?
    return @rel_path
  end

  @abs_path = "#{@parent.abs_path}.#{rel_path}"

  return @abs_path
end
abs_path_with(rel_path) click to toggle source

Returns the absolute path given a relative path. Say, for example, that a proxy object has an absolute path of 'mainPage.mainPane.someView'. When given a relative path of 'foo.bar', the returned value would be:

'mainPage.mainPane.someView.foo.bar'
# File lib/lebowski/foundation/proxy_object.rb, line 163
def abs_path_with(rel_path)
  path = abs_path
  return rel_path if path.nil?
  return "#{path}.#{rel_path}"
end
define(path, rel_path=nil, expected_type=nil) click to toggle source

DEPRECATED

# File lib/lebowski/foundation/proxy_object.rb, line 249
def define(path, rel_path=nil, expected_type=nil)
  puts "DEPRECATED: define is deprecated. use define_path instead"
  puts "... path = #{path}"
  puts "... rel_path = #{rel_path}"
  puts "... expected_type = #{expected_type}"
  define_path(path, rel_path, expected_type)
end
define_proxy(klass, rel_path) click to toggle source

Defines a path proxy for a relative path on this proxy object. The path proxy will be loaded only when actually requested for use.

@param klass The klass to use as the path proxy @param rel_path The relative path agaist this proxy object

# File lib/lebowski/foundation/proxy_object.rb, line 264
def define_proxy(klass, rel_path)
  if (not rel_path.kind_of?(String)) or rel_path.empty?
    raise ArgumentError.new "rel_path must be a valid string"
  end
  
  if not (klass.kind_of?(Class) and klass.ancestors.member?(ProxyObject))
    raise ArgumentInvalidTypeError.new "klass", klass, 'class < ProxyObject'
  end

  @defined_proxies[rel_path] = klass
end
eql?(obj)
Also aliased as: __eql?
Alias for: ==
init_ext() click to toggle source

Override this method for any initialization procedures during the time the object is being inialized

@see initialize

# File lib/lebowski/foundation/proxy_object.rb, line 66
def init_ext()

end
method_missing(sym, *args, &block) click to toggle source

Override method_missing so that we can access a proxied object's properties using a more conventional Ruby approach. So instead of accessing an object's property using the [] convention, we can instead do the following:

value = proxied_object.foo # compared to proxied_object['foo']

This will also translate the name of property into camel case that is normally used in JavaScript. So, if an object in JavaScript has a property with the name 'fooBar', you can access that property using the standard Ruby convention like so:

value = proxied_object.foo_bar

It will be converted back into 'fooBar'. If the property does not exist on the proxied object then an exception will be thrown. If you want to access property without an exception being thrown then use the [] convention using a relative property path string.

Calls superclass method
# File lib/lebowski/foundation/proxy_object.rb, line 450
def method_missing(sym, *args, &block)
  if (not sym.to_s =~ /\?$/) and (args.length == 0)
    camel_case = Util.to_camel_case(sym.to_s)
    return self[camel_case] if sc_path_defined?(camel_case)  
  end
  super
end
none?(rel_path) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 230
def none?(rel_path)
  type = sc_type_of(rel_path)
  return (type == SC_T_UNDEFINED or type == SC_T_NULL) 
end
object?(rel_path) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 235
def object?(rel_path)
  type = sc_type_of(rel_path)
  return (type == SC_T_OBJECT or type == SC_T_HASH)
end
proxy(klass, rel_path) click to toggle source

DEPRECATED

# File lib/lebowski/foundation/proxy_object.rb, line 241
def proxy(klass, rel_path)
  puts "DEPRECATED: proxy is deprecated. use define_proxy instead"
  puts "... klass = #{klass}"
  puts "... rel_path = #{rel_path}"
  define_proxy(klass, rel_path)
end
represent_as(type) click to toggle source

Use when you wish to represent a proxied object as something else other then the proxy returned by this object when accessed with a relative path using []. This is useful in cases where you have a view that is simply composed of other views but itself is not custom view inherited from SC.View. As an example, it is common in SproutCore to create a complex view that lives only within an SC.Page, like so:

MyApp.mainPage = SC.Page.create({

  composedView: SC.View.design({
    layout: { top: 0, bottom: 0, left: 0, right: 0 },
    childViews: 'buttonOne buttonTwo statusLabel'.w(),

    buttonOne: SC.ButtonView.design({
      ...
    }),

    buttonTwo: SC.ButtonView.design({
      ...
    }),

    statusLabel: SC.LabelView.design({
      ...
    })
  })

})

Since the root view (composedView) is just a basic SC.View, accessing it using the proxy's [] convention would just give you back a basic View proxy. This then means you have to access the child views explicitly every time you want to interact with the composed view. This can be brittle since the view's structure can change. Instead you can make a proxy to abstract away the interal structure and make it easier to work with the view. Therefore, we could make a proxy as follows:

ComposedView < Lebowski::Foundation::Views::View

  def click_button_one()
    self['buttonOne'].click
  end

  def click_button_two()
    self['buttonTwo'].click
  end   

  def status()
    return self['statusLabel.value']
  end

end

With the proxy above, you can then do the following:

view = App['mainPage.composedView', View]
view = view.represent_as(ComposedView) 
view.click_button_one
status = view.status
# File lib/lebowski/foundation/proxy_object.rb, line 128
def represent_as(type)
  if not (type.kind_of?(Class) and type.ancestors.member?(ProxyObject))
    raise ArgumentInvalidTypeError.new "type", type, 'class < ProxyObject'
  end
  
  obj = type.new @parent, @rel_path, @driver
  return obj
end
sc_all_classes() click to toggle source

Gets all the remote SproutCore classes that the proxied object derives from. This will return an array of strings representing the names of the classes. As an example, if the proxy was communicating with an object that was of type SC.ButtonView then the result would be the following:

['SC.ButtonView', 'SC.View', 'SC.Object']

The last item in the array is always 'SC.Object' since that is the root object for all SproutCore objects.

# File lib/lebowski/foundation/proxy_object.rb, line 198
def sc_all_classes()
  @all_class_names = @driver.get_sc_object_class_names(abs_path) if @all_class_names.nil?
  return @all_class_names
end
sc_class() click to toggle source

Gets the remote SC class name for this object

# File lib/lebowski/foundation/proxy_object.rb, line 181
def sc_class()
  # We only need to fetch the remote SC class name once since it never changes for a given instance
  @class_name = @driver.get_sc_object_class_name(abs_path) if @class_name.nil?
  return @class_name
end
sc_guid() click to toggle source

Gets the remote SproutCore GUID for this object

# File lib/lebowski/foundation/proxy_object.rb, line 172
def sc_guid()
  # We only need to fetch the remote GUID once since it never changes for a given instance
  @guid = @driver.get_sc_guid(abs_path) if @guid.nil?
  return @guid
end
sc_kind_of?(type) click to toggle source

Checks if the remote proxied object is a kind of given SC class

# File lib/lebowski/foundation/proxy_object.rb, line 206
def sc_kind_of?(type)
  if not (type.kind_of?(Class) or type.kind_of?(String))
    raise ArgumentInvalidTypeError.new "type", type, 'class < SCObject', String
  end
  
  if type.kind_of?(Class) and type.ancestors.member?(SCObject)
    type = type.represented_sc_class
  end
  
  type = type.downcase
  result = sc_all_classes.detect do |val| 
    val.downcase == type
  end
  return (not result.nil?)
end
sc_path_defined?(rel_path) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 226
def sc_path_defined?(rel_path)
  return (not sc_type_of(rel_path) == SC_T_UNDEFINED)
end
sc_type_of(rel_path) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 222
def sc_type_of(rel_path)
  return @driver.get_sc_type_of(abs_path_with(rel_path))
end
unravel_relative_path(rel_path) click to toggle source

Given a relative path, unravel it to access an object. Unraveling means to take any defined path in the given relative path and convert the entire path back into a full relative path without definitions.

# File lib/lebowski/foundation/proxy_object.rb, line 281
def unravel_relative_path(rel_path)
  path_parts = rel_path.split '.'
  
  full_rel_path = ""
  defined_path = nil
  counter = path_parts.length
  
  for path_part in path_parts do
    path = defined_path.nil? ? path_part : "#{defined_path}.#{path_part}"
    if path_defined? path
      defined_path = path
    else
      break
    end
    counter = counter - 1
  end
  
  full_rel_path << self.defined_path(defined_path).full_rel_path if (not defined_path.nil?)
  if (counter > 0)
    full_rel_path << "." if (not defined_path.nil?)
    full_rel_path << path_parts.last(counter).join('.')
  end
  
  return full_rel_path
end

Private Instance Methods

fetch_rel_path_value(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 476
def fetch_rel_path_value(rel_path, expected_type)
  type = sc_type_of(rel_path)
  
  case type
  when SC_T_NULL
    return handle_type_null(rel_path, expected_type)
  
  when SC_T_UNDEFINED
    return handle_type_undefined(rel_path, expected_type)
  
  when SC_T_ERROR
    return handle_type_error(rel_path, expected_type)

  when SC_T_STRING
    return handle_type_string(rel_path, expected_type)
  
  when SC_T_NUMBER
    return handle_type_number(rel_path, expected_type)
  
  when SC_T_BOOL
    return handle_type_bool(rel_path, expected_type)
  
  when SC_T_ARRAY
    return handle_type_array(rel_path, expected_type)
  
  when SC_T_HASH
    return handle_type_hash(rel_path, expected_type)
  
  when SC_T_OBJECT
    return handle_type_object(rel_path, expected_type)
    
  when SC_T_CLASS
    return handle_type_class(rel_path, expected_type)
  
  else
    raise StandardError.new "Unrecognized returned type '#{type}' for path #{abs_path_with(rel_path)}"
  end
end
handle_type_array(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 613
def handle_type_array(rel_path, expected_type)
  content_type = @driver.get_sc_type_of_array_content(abs_path_with(rel_path))
  
  got_expected_type = false
  
  # TODO: This needs a better solution
  case content_type
  when SC_T_NUMBER
    got_expected_type = (expected_type == :array or expected_type == :number_array)
    value = @driver.get_sc_path_number_array_value(abs_path_with(rel_path))
  when SC_T_STRING
    got_expected_type = (expected_type == :array or expected_type == :string_array)
    value = @driver.get_sc_path_string_array_value(abs_path_with(rel_path))
  when SC_T_BOOL
    got_expected_type = (expected_type == :array or expected_type == :boolean_array or expected_type == :bool_array)
    value = @driver.get_sc_path_boolean_array_value(abs_path_with(rel_path))
  when SC_T_HASH
    got_expected_type = (expected_type == :array or expected_type == :object_array or expected_type == :hash_array)
    value = ObjectArray.new self, rel_path
  when SC_T_OBJECT
    got_expected_type = (expected_type == :array or expected_type == :object_array)
    value = ObjectArray.new self, rel_path
  when "empty"
    got_expected_type = (expected_type == :array)
    value = []
  else
    got_expected_type = (expected_type == :array)
    # TODO: Replace with correct logic. Temporary for now
    value = []
  end
  
  if (not expected_type.nil?) and (not got_expected_type)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ARRAY, value)
  end
  
  return value
end
handle_type_bool(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 539
def handle_type_bool(rel_path, expected_type)
  value = @driver.get_sc_path_boolean_value(abs_path_with(rel_path))
  
  if (not expected_type.nil?) and not (expected_type == :boolean or expected_type == :bool)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_BOOL, value)
  end
  
  return value
end
handle_type_class(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 569
def handle_type_class(rel_path, expected_type)
  # TODO: Need to handle this case better
  value = ProxyObject.new self, rel_path, @driver
  
  if (not expected_type.nil?) and not (expected_type == :class)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_CLASS, value)
  end
  
  return value
end
handle_type_error(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 531
def handle_type_error(rel_path, expected_type)
  if (not expected_type.nil?) and not (expected_type == :error)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_ERROR, :error)
  end

  return :error
end
handle_type_hash(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 580
def handle_type_hash(rel_path, expected_type)
  value = ProxyObject.new self, rel_path, @driver
  
  if (not expected_type.nil?) and not (expected_type == :object or expected_type == :hash)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_HASH, value)
  end
  
  return value
end
handle_type_null(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 515
def handle_type_null(rel_path, expected_type)
  if (not expected_type.nil?) and not (expected_type == :null)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NULL, :null)
  end

  return nil
end
handle_type_number(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 559
def handle_type_number(rel_path, expected_type)
  value = @driver.get_sc_path_number_value(abs_path_with(rel_path))
  
  if (not expected_type.nil?) and not (expected_type == :number)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_NUMBER, value)
  end
  
  return value
end
handle_type_object(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 590
def handle_type_object(rel_path, expected_type)
  
  value = nil
  
  class_names = @driver.get_sc_object_class_names(abs_path_with(rel_path))
  matching_class = class_names.detect { |name| ProxyFactory.has_key?(name) }
  if matching_class.nil?
    value = ProxyFactory.create_proxy(Lebowski::Foundation::SCObject, self, rel_path)
  else
    value = ProxyFactory.create_proxy(matching_class, self, rel_path)
  end
  
  if not expected_type.nil?
    got_expected_type = (expected_type == :object or value.sc_kind_of?(expected_type))
    if not got_expected_type
      raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_OBJECT, value)
    end
  end

  return value
  
end
handle_type_string(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 549
def handle_type_string(rel_path, expected_type)
  value = @driver.get_sc_path_string_value(abs_path_with(rel_path))
  
  if (not expected_type.nil?) and not (expected_type == :string)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_STRING, value)
  end
  
  return value
end
handle_type_undefined(rel_path, expected_type) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 523
def handle_type_undefined(rel_path, expected_type)
  if (not expected_type.nil?) and not (expected_type == :undefined)
    raise UnexpectedTypeError.new(abs_path_with(rel_path), expected_type, SC_T_UNDEFINED, :undefined)
  end

  return :undefined
end
init_expected_parent_type() click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 460
def init_expected_parent_type()
  return nil
end
rel_path_first_part(rel_path) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 464
def rel_path_first_part(rel_path)
  first_path_part = rel_path.match(/^((\w|-)*)\./)
  first_path_part = rel_path if first_path_part.nil?
  first_path_part = first_path_part[1] if first_path_part.kind_of?(MatchData)   
  return first_path_part    
end
rel_path_sub_path(rel_path, first_path_part) click to toggle source
# File lib/lebowski/foundation/proxy_object.rb, line 471
def rel_path_sub_path(rel_path, first_path_part)
  sub_path = (first_path_part != rel_path) ? rel_path.sub(/^(\w|-)*\./, "") : ""
  return sub_path
end