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
Public Class Methods
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
Override the == operator so that a proxy object can be compared to another proxy object via their SproutCore GUIDs
# 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
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
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
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
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
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
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
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.
# 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
# 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
# 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
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
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
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
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
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
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
# 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
# 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
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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# File lib/lebowski/foundation/proxy_object.rb, line 460 def init_expected_parent_type() return nil end
# 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
# 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