Class: Scarpe::Webview::Drawable

Inherits:
Shoes::Linkable show all
Includes:
Shoes::Log
Defined in:
lib/scarpe/wv/drawable.rb,
lib/scarpe/wv.rb

Overview

The Webview::Drawable parent class helps connect a Webview drawable with its Shoes equivalent, render itself to the Webview DOM, handle Javascript events and generally keep things working in Webview.

Constant Summary

Constants included from Shoes::Log

Shoes::Log::DEFAULT_COMPONENT, Shoes::Log::DEFAULT_DEBUG_LOG_CONFIG, Shoes::Log::DEFAULT_LOG_CONFIG

Instance Attribute Summary collapse

Attributes inherited from Shoes::Linkable

#linkable_id

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Shoes::Log

configure_logger, #log_init, logger

Methods inherited from Shoes::Linkable

#bind_shoes_event, #send_self_event, #send_shoes_event, #unsub_all_shoes_events, #unsub_shoes_event

Constructor Details

#initialize(properties) ⇒ Drawable

Set instance variables for the Shoes styles of this drawable. Bind Shoes events for changes of parent drawable and changes of property values.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/scarpe/wv/drawable.rb', line 40

def initialize(properties)
  log_init("Webview::Drawable")

  @shoes_style_names = properties.keys.map(&:to_s) - ["shoes_linkable_id"]

  @shoes_linkable_id = properties["shoes_linkable_id"] || properties[:shoes_linkable_id]
  unless @shoes_linkable_id
    raise Scarpe::MissingAttributeError, "Could not find property shoes_linkable_id in #{properties.inspect}!"
  end

  # Set the Shoes styles as instance variables
  properties.each do |k, v|
    next if k == "shoes_linkable_id"

    instance_variable_set("@" + k.to_s, v)
  end

  # Must call this before bind
  super(linkable_id: @shoes_linkable_id)

  # This will only be used if moving a drawable from one parent to another.
  # Shoes doesn't normally do that.
  bind_shoes_event(event_name: "parent", target: shoes_linkable_id) do |new_parent_id|
    display_parent = DisplayService.instance.query_display_drawable_for(new_parent_id)
    if @parent != display_parent
      set_parent(display_parent)
    end
  end

  # When Shoes drawables change properties, we get a change notification here
  bind_shoes_event(event_name: "prop_change", target: shoes_linkable_id) do |prop_changes|
    prop_changes.each do |k, v|
      instance_variable_set("@" + k, v)
    end
    properties_changed(prop_changes)
  end

  bind_shoes_event(event_name: "destroy", target: shoes_linkable_id) do
    destroy_self
  end
end

Instance Attribute Details

#childrenObject (readonly)

An array of Webview::Drawable children (possibly empty) of this drawable



36
37
38
# File 'lib/scarpe/wv/drawable.rb', line 36

def children
  @children
end

#parentObject (readonly)

The Webview::Drawable parent of this drawable



33
34
35
# File 'lib/scarpe/wv/drawable.rb', line 33

def parent
  @parent
end

#shoes_linkable_idObject (readonly)

The Shoes ID corresponding to the Shoes drawable for this Webview drawable



30
31
32
# File 'lib/scarpe/wv/drawable.rb', line 30

def shoes_linkable_id
  @shoes_linkable_id
end

Class Method Details

.display_class_for(scarpe_class_name) ⇒ Object

Return the corresponding Webview class for a particular Shoes class name



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/scarpe/wv/drawable.rb', line 13

def display_class_for(scarpe_class_name)
  scarpe_class = Shoes.const_get(scarpe_class_name)
  unless scarpe_class.ancestors.include?(Shoes::Linkable)
    raise Scarpe::InvalidClassError, "Scarpe Webview can only get display classes for Shoes " +
      "linkable drawables, not #{scarpe_class_name.inspect}!"
  end

  klass = Scarpe::Webview.const_get(scarpe_class_name.split("::")[-1])
  if klass.nil?
    raise Scarpe::MissingClassError, "Couldn't find corresponding Scarpe Webview class for #{scarpe_class_name.inspect}!"
  end

  klass
end

Instance Method Details

#add_child(child) ⇒ Object (protected)

Do not call directly, use set_parent



139
140
141
142
143
144
145
# File 'lib/scarpe/wv/drawable.rb', line 139

def add_child(child)
  @children ||= []
  @children << child

  # If we add a child, we should redraw ourselves
  needs_update!
end

#bind(event) { ... } ⇒ Object

This binds a Scarpe JS callback, handled via a single dispatch point in the app

Parameters:

  • event (String)

    the Scarpe drawable event name

Yields:

  • the block to call when the event occurs

Raises:



189
190
191
192
193
# File 'lib/scarpe/wv/drawable.rb', line 189

def bind(event, &block)
  raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id

  DisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
end

#destroy_selfScarpe::Promise

Removes the element from both the Ruby Drawable tree and the HTML DOM. Unsubscribe from all Shoes events. Return a promise for when that HTML change will be visible.

Returns:

  • (Scarpe::Promise)

    a promise that is fulfilled when the HTML change is complete



200
201
202
203
204
# File 'lib/scarpe/wv/drawable.rb', line 200

def destroy_self
  @parent&.remove_child(self)
  unsub_all_shoes_events
  html_element.remove
end

#full_window_redraw!void

This method returns an undefined value.

Request a full redraw of the entire window, including the entire tree of drawables and the outer "empty page" frame.



210
211
212
# File 'lib/scarpe/wv/drawable.rb', line 210

def full_window_redraw!
  DisplayService.instance.app.request_redraw!
end

#handler_js_code(handler_function_name, *args) ⇒ String

Generate JS code to trigger a specific event name on this drawable with the supplies arguments.

Parameters:

  • handler_function_name (String)

    the event name - @see #bind

  • args (Array)

    additional arguments that will be passed to the event in the generated JS

Returns:

  • (String)

    the generated JS code

Raises:



232
233
234
235
236
237
# File 'lib/scarpe/wv/drawable.rb', line 232

def handler_js_code(handler_function_name, *args)
  raise(Scarpe::MissingAttributeError, "Drawable has no linkable_id! #{inspect}") unless linkable_id

  js_args = ["'#{linkable_id}-#{handler_function_name}'", *args].join(", ")
  "scarpeHandler(#{js_args})"
end

#html_elementScarpe::WebWrangler::ElementWrangler

This gets an accessor for just this element's HTML ID. It is normally called by the drawable itself to do its DOM management. Drawables are required to use their html_id for their outermost element, to make sure that remove(), hidden() etc. affect every part of the drawable.

Returns:

  • (Scarpe::WebWrangler::ElementWrangler)

    a DOM object manager



155
156
157
# File 'lib/scarpe/wv/drawable.rb', line 155

def html_element
  @elt_wrangler ||= Scarpe::Webview::WebWrangler::ElementWrangler.new(html_id:)
end

#html_idString

Get the object's HTML ID

Returns:

  • (String)

    the HTML ID



171
172
173
# File 'lib/scarpe/wv/drawable.rb', line 171

def html_id
  @linkable_id.to_s
end

#inspectObject

A shorter inspect text for prettier irb output



122
123
124
# File 'lib/scarpe/wv/drawable.rb', line 122

def inspect
  "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @children=#{@children.inspect}>"
end

#needs_update!void

This method returns an undefined value.

Request a full redraw of this drawable, including all its children. Can be overridden in drawable subclasses if needed. An override would normally only be needed if re-rendering the element with the given html_id wasn't enough (and then remove would also need to be overridden.)

This occurs by default if a property is changed and the drawable doesn't remove its change in property_changed.



223
224
225
# File 'lib/scarpe/wv/drawable.rb', line 223

def needs_update!
  html_element.outer_html = to_html
end

#promise_updateScarpe::Promise

Return a promise that guarantees all currently-requested changes have completed

Returns:

  • (Scarpe::Promise)

    a promise that will be fulfilled when all pending changes have finished



162
163
164
165
166
# File 'lib/scarpe/wv/drawable.rb', line 162

def promise_update
  # Doesn't matter what ElementWrangler we use -- they all return an update promise
  # that includes all pending updates, no matter who they're for.
  html_element.promise_update
end

#properties_changed(changes) ⇒ Object

Properties_changed will be called automatically when properties change. The drawable should delete any changes from the Hash that it knows how to incrementally handle, and pass the rest to super. If any changes go entirely un-handled, a full redraw will be scheduled. This exists to be overridden by children watching for changes.

Parameters:

  • changes (Hash)

    a Hash of new values for properties that have changed



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/scarpe/wv/drawable.rb', line 97

def properties_changed(changes)
  # If a drawable does something really nonstandard with its html_id or element, it will
  # need to override to prevent this from happening. That's easy enough, though.
  if changes.key?("hidden")
    hidden = changes.delete("hidden")
    if hidden
      html_element.set_style("display", "none")
    else
      # With Calzini we can't easily tell what the display property should be.
      # Could be flex or inline, not only block or none. Re-render this drawable.
      needs_update!
    end
  end

  needs_update! unless changes.empty?
end

#remove_child(child) ⇒ Object (protected)

Do not call directly, use set_parent



129
130
131
132
133
134
135
136
# File 'lib/scarpe/wv/drawable.rb', line 129

def remove_child(child)
  @children ||= []
  unless @children.include?(child)
    @log.error("remove_child: no such child(#{child.inspect}) for"\
      " parent(#{parent.inspect})!")
  end
  @children.delete(child)
end

#set_parent(new_parent) ⇒ Object

Give this drawable a new parent, including managing the appropriate child lists for parent drawables.



115
116
117
118
119
# File 'lib/scarpe/wv/drawable.rb', line 115

def set_parent(new_parent)
  @parent&.remove_child(self)
  new_parent&.add_child(self)
  @parent = new_parent
end

#shoes_stylesObject



82
83
84
85
86
87
88
# File 'lib/scarpe/wv/drawable.rb', line 82

def shoes_styles
  p = {}
  @shoes_style_names.each do |prop_name|
    p[prop_name] = instance_variable_get("@#{prop_name}")
  end
  p
end

#to_htmlString

to_html is intended to get the HTML DOM rendering of this object and its children. Calling it should be side-effect-free and NOT update the webview.

Returns:

  • (String)

    the rendered HTML



179
180
181
182
183
# File 'lib/scarpe/wv/drawable.rb', line 179

def to_html
  @children ||= []
  child_markup = @children.map(&:to_html).join
  element { child_markup }
end