lib/scarpe/wv/widget.rb in scarpe-0.2.1 vs lib/scarpe/wv/widget.rb in scarpe-0.2.2
- old
+ new
@@ -1,16 +1,20 @@
# frozen_string_literal: true
class Scarpe
- class WebviewWidget < DisplayService::Linkable
- include Scarpe::Log
+ # The WebviewWidget parent class helps connect a Webview widget with
+ # its Shoes equivalent, render itself to the Webview DOM, handle
+ # Javascript events and generally keep things working in Webview.
+ class WebviewWidget < Shoes::Linkable
+ include Shoes::Log
class << self
+ # Return the corresponding Webview class for a particular Shoes class name
def display_class_for(scarpe_class_name)
- scarpe_class = Scarpe.const_get(scarpe_class_name)
- unless scarpe_class.ancestors.include?(Scarpe::DisplayService::Linkable)
- raise "Scarpe Webview can only get display classes for Scarpe " +
+ scarpe_class = Shoes.const_get(scarpe_class_name)
+ unless scarpe_class.ancestors.include?(Shoes::Linkable)
+ raise "Scarpe Webview can only get display classes for Shoes " +
"linkable widgets, not #{scarpe_class_name.inspect}!"
end
klass = Scarpe.const_get("Webview" + scarpe_class_name.split("::")[-1])
if klass.nil?
@@ -19,14 +23,21 @@
klass
end
end
+ # The Shoes ID corresponding to the Shoes widget for this Webview widget
attr_reader :shoes_linkable_id
+
+ # The WebviewWidget parent of this widget
attr_reader :parent
+
+ # An array of WebviewWidget children (possibly empty) of this widget
attr_reader :children
+ # Set instance variables for the display properties of this widget. Bind Shoes
+ # events for changes of parent widget and changes of property values.
def initialize(properties)
log_init("WV::Widget")
# Call method, which looks up the parent
@shoes_linkable_id = properties["shoes_linkable_id"] || properties[:shoes_linkable_id]
@@ -62,21 +73,46 @@
end
super(linkable_id: @shoes_linkable_id)
end
- # This exists to be overridden by children watching for changes
+ # Properties_changed will be called automatically when properties change.
+ # The widget 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.
+ #
+ # @param changes [Hash] a Hash of new values for properties that have changed
def properties_changed(changes)
+ # If a widget 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
+ new_style = style # Get current display CSS property, which may vary by subclass
+ disp = new_style[:display]
+ html_element.set_style("display", disp || "block")
+ end
+ end
+
needs_update! unless changes.empty?
end
+ # Give this widget a new parent, including managing the appropriate child lists for parent widgets.
def set_parent(new_parent)
@parent&.remove_child(self)
new_parent&.add_child(self)
@parent = new_parent
end
+ # A shorter inspect text for prettier irb output
+ def inspect
+ "#<#{self.class}:#{self.object_id} @shoes_linkable_id=#{@shoes_linkable_id} @parent=#{@parent.inspect} @children=#{@children.inspect}>"
+ end
+
protected
# Do not call directly, use set_parent
def remove_child(child)
@children ||= []
@@ -120,58 +156,91 @@
b_int = (b_float * 255.0).to_i.clamp(0, 255)
"#%0.2X%0.2X%0.2X" % [r_int, g_int, b_int]
end
+ # CSS styles
+ def style
+ styles = {}
+ if @hidden
+ styles[:display] = "none"
+ end
+ styles
+ end
+
public
- # This gets a mini-webview for just this element and its children, if any
+ # This gets a mini-webview for just this element and its children, if any.
+ # It is normally called by the widget itself to do its DOM management.
+ #
+ # @return [Scarpe::WebWrangler::ElementWrangler] a DOM object manager
def html_element
- @elt_wrangler ||= WebviewDisplayService.instance.doc_root.get_element_wrangler(html_id)
+ @elt_wrangler ||= Scarpe::WebWrangler::ElementWrangler.new(html_id)
end
# Return a promise that guarantees all currently-requested changes have completed
+ #
+ # @return [Scarpe::Promise] a promise that will be fulfilled when all pending changes have finished
def promise_update
html_element.promise_update
end
+ # Get the object's HTML ID
+ #
+ # @return [String] the HTML ID
def html_id
object_id.to_s
end
# 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.
+ #
+ # @return [String] the rendered HTML
def to_html
@children ||= []
child_markup = @children.map(&:to_html).join
if respond_to?(:element)
element { child_markup }
else
child_markup
end
end
- # This binds a Scarpe JS callback, handled via a single dispatch point in the document root
+ # This binds a Scarpe JS callback, handled via a single dispatch point in the app
+ #
+ # @param event [String] the Scarpe widget event name
+ # @yield the block to call when the event occurs
def bind(event, &block)
raise("Widget has no linkable_id! #{inspect}") unless linkable_id
- WebviewDisplayService.instance.doc_root.bind("#{linkable_id}-#{event}", &block)
+ WebviewDisplayService.instance.app.bind("#{linkable_id}-#{event}", &block)
end
# Removes the element from both the Ruby Widget tree and the HTML DOM.
# Return a promise for when that HTML change will be visible.
+ #
+ # @return [Scarpe::Promise] a promise that is fulfilled when the HTML change is complete
def destroy_self
@parent&.remove_child(self)
html_element.remove
end
+ # Request a full redraw of all widgets.
+ #
# It's really hard to do dirty-tracking here because the redraws are fully asynchronous.
# And so we can't easily cancel one "in flight," and we can't easily pick up the latest
# changes... And we probably don't want to, because we may be halfway through a batch.
+ #
+ # @return [void]
def needs_update!
- WebviewDisplayService.instance.doc_root.request_redraw!
+ WebviewDisplayService.instance.app.request_redraw!
end
+ # Generate JS code to trigger a specific event name on this widget with the supplies arguments.
+ #
+ # @param handler_function_name [String] the event name - @see #bind
+ # @param args [Array] additional arguments that will be passed to the event in the generated JS
+ # @return [String] the generated JS code
def handler_js_code(handler_function_name, *args)
raise("Widget has no linkable_id! #{inspect}") unless linkable_id
js_args = ["'#{linkable_id}-#{handler_function_name}'", *args].join(", ")
"scarpeHandler(#{js_args})"