Class: Lebowski::Foundation::ProxyObject

Inherits:
Object
  • Object
show all
Includes:
Lebowski::Foundation, Mixins::DefinePathsSupport, Mixins::WaitActions
Defined in:
lib/lebowski/foundation/proxy_object.rb

Overview

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.

Direct Known Subclasses

Application, SCObject

Constant Summary

Constants included from Lebowski::Foundation

SC_BRANCH_CLOSED, SC_BRANCH_OPEN, SC_BUTTON1_STATUS, SC_BUTTON2_STATUS, SC_BUTTON3_STATUS, SC_LEAF_NODE, SC_MIXED_STATE, SC_PICKER_FIXED, SC_PICKER_MENU, SC_PICKER_POINTER, SC_T_ARRAY, SC_T_BOOL, SC_T_CLASS, SC_T_ERROR, SC_T_FUNCTION, SC_T_HASH, SC_T_NULL, SC_T_NUMBER, SC_T_OBJECT, SC_T_STRING, SC_T_UNDEFINED

Constants included from Mixins::WaitActions

Mixins::WaitActions::DEFAULT_TIMEOUT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Mixins::DefinePathsSupport

#define_path, #define_paths_for, #defined_path, #defined_paths, #path_defined?, #root_defined_path_part, #root_defined_path_part=

Methods included from Mixins::WaitActions

#wait_until

Constructor Details

#initialize(parent = nil, rel_path = nil, driver = nil) ⇒ ProxyObject

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

Parameters:

  • parent (Object) (defaults to: nil)

    parent object of this object. Must inherit from Lebowski::Foundation::ProxyObject

  • rel_path (String) (defaults to: nil)

    a relative path to the remote object (e.g. ‘foo’, ‘foo.bar’)

  • driver (Object) (defaults to: nil)

    used to remotely communicate with the remote object.

See Also:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

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.



450
451
452
453
454
455
456
# 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

Instance Attribute Details

#driverObject (readonly)

The parent object of this object. Must derive from Lebowski::Foundation::ProxyObject



23
24
25
# File 'lib/lebowski/foundation/proxy_object.rb', line 23

def driver
  @driver
end

#nameObject

A name for this object. Useful for printing out statements and debugging



27
28
29
# File 'lib/lebowski/foundation/proxy_object.rb', line 27

def name
  @name
end

#parentObject (readonly)

The parent object of this object. Must derive from Lebowski::Foundation::ProxyObject



23
24
25
# File 'lib/lebowski/foundation/proxy_object.rb', line 23

def parent
  @parent
end

#rel_pathObject (readonly)

The parent object of this object. Must derive from Lebowski::Foundation::ProxyObject



23
24
25
# File 'lib/lebowski/foundation/proxy_object.rb', line 23

def rel_path
  @rel_path
end

Instance Method Details

#==(obj) ⇒ Object Also known as: eql?

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



424
425
426
427
# 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

#[](rel_path, expected_type = nil) ⇒ Object

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.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# 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

#abs_pathObject

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’



142
143
144
145
146
147
148
149
150
151
152
153
154
# 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) ⇒ Object

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'


163
164
165
166
167
# 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) ⇒ Object

DEPRECATED



249
250
251
252
253
254
255
# 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) ⇒ Object

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

Parameters:

  • klass

    The klass to use as the path proxy

  • rel_path

    The relative path agaist this proxy object



264
265
266
267
268
269
270
271
272
273
274
# 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

#init_extObject

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

See Also:



66
67
68
# File 'lib/lebowski/foundation/proxy_object.rb', line 66

def init_ext()

end

#none?(rel_path) ⇒ Boolean

Returns:

  • (Boolean)


230
231
232
233
# 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) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
238
# 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) ⇒ Object

DEPRECATED



241
242
243
244
245
246
# 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) ⇒ Object

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


128
129
130
131
132
133
134
135
# 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_classesObject

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.



198
199
200
201
# 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_classObject

Gets the remote SC class name for this object



181
182
183
184
185
# 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_guidObject

Gets the remote SproutCore GUID for this object



172
173
174
175
176
# 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) ⇒ Boolean

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

Returns:

  • (Boolean)


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# 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) ⇒ Boolean

Returns:

  • (Boolean)


226
227
228
# 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) ⇒ Object



222
223
224
# 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) ⇒ Object

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.



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# 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