class MetaRuby::GUI::HTML::Page
A helper class that gives us easy-to-use page elements on a Qt::WebView
Such a page is managed as a list of sections (called {Fragment}). A new fragment is added or updated with {#push}
Constants
- ASSETS
Assets (CSS, javascript) that are included in every page
- FRAGMENT_TEMPLATE
The ERB template for a page fragment
@see push
- LIST_TEMPLATE
The ERB template for a list
@see
render_list
- PAGE_BODY_TEMPLATE
The ERB template for a page body
@see
html_body
- PAGE_TEMPLATE
The ERB template for a page
@see html
Attributes
Object used to render exceptions in {#push_exception}
It is set by {#enable_exception_rendering}
@return [#render]
List of fragments
@return [Array<Fragment>]
Content to be rendered in the page head
@return [Array<String>]
Static mapping of objects to URIs
@see uri_fo
The underlying page rendering object
@return [Qt::WebPage,HTMLPage]
The content of the <title> tag @return [String,nil]
Scripts to be loaded in the page
@return [Array<String>]
The content of a toplevel <h1> tag @return [String,nil]
Public Class Methods
Copy the assets to a target directory
This can be used to create self-contained HTML
pages using the Page
class, by providing a different ressource dir to e.g. {#html} or {#html_body} and copying the assets to it.
# File lib/metaruby/gui/html/page.rb, line 208 def self.copy_assets_to(target_dir, assets = ASSETS) FileUtils.mkdir_p target_dir assets.each do |file| FileUtils.cp File.join(RESSOURCES_DIR, file), target_dir end end
Converts the given text from markdown to HTML
and generates the necessary <div> context.
@return [String] the HTML
snippet that should be used to render
the given text as main documentation
# File lib/metaruby/gui/html/page.rb, line 176 def self.main_doc(text) "<div class=\"doc-main\">#{Kramdown::Document.new(text).to_html}</div>" end
Creates a new Page
object
@param [Qt::WebPage,HTMLPage] page
# File lib/metaruby/gui/html/page.rb, line 65 def initialize(page) super() @page = page @head = Array.new @scripts = Array.new @fragments = [] @templates = Hash.new @auto_id = 0 if defined?(Qt::WebPage) && page.kind_of?(Qt::WebPage) page.link_delegation_policy = Qt::WebPage::DelegateAllLinks Qt::Object.connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('pageLinkClicked(const QUrl&)')) end @object_uris = Hash.new end
Renders an object into a HTML
page
@param (see to_html_page
) @return [String]
# File lib/metaruby/gui/html/page.rb, line 467 def self.to_html(object, renderer, ressource_dir: RESSOURCES_DIR, **options) to_html_page(object, renderer, **options). html(ressource_dir: ressource_dir) end
Renders an object into a HTML
body
@param (see to_html_page
) @return [String]
# File lib/metaruby/gui/html/page.rb, line 476 def self.to_html_body(object, renderer, ressource_dir: RESSOURCES_DIR, **options) to_html_page(object, renderer, **options). html_body(ressource_dir: ressource_dir) end
Renders an object into a HTML
page
@param [Object] object @param [#render] renderer the object that renders into the page.
The object must accept a {Page} at initialization and its #render method gets called passing the object and rendering options
@return [Page]
# File lib/metaruby/gui/html/page.rb, line 456 def self.to_html_page(object, renderer, **options) webpage = HTMLPage.new page = new(webpage) renderer.new(page).render(object, **options) page end
Public Instance Methods
Add content to {#scripts}
@param [String] html
# File lib/metaruby/gui/html/page.rb, line 124 def add_script(html) scripts << html end
Add content to {#head}
@param [String] html
# File lib/metaruby/gui/html/page.rb, line 117 def add_to_head(html) head << html end
Add content to the page setup (head and scripts)
@param [#head,#scripts] obj the object defining the content to be
added
@see add_to_head
add_scripts
# File lib/metaruby/gui/html/page.rb, line 109 def add_to_setup(obj) add_to_head(obj.head) add_script(obj.scripts) end
Automatic generation of a fragment ID
# File lib/metaruby/gui/html/page.rb, line 393 def auto_id "metaruby-html-page-fragment-#{@auto_id += 1}" end
Removes all existing displays
# File lib/metaruby/gui/html/page.rb, line 244 def clear page.main_frame.html = "" fragments.clear end
Enable rendering of exceptions using the given renderer
@param [ExceptionRendering] renderer
# File lib/metaruby/gui/html/page.rb, line 400 def enable_exception_rendering(renderer = ExceptionRendering.new(self)) add_to_setup(renderer) @exception_rendering = renderer end
# File lib/metaruby/gui/html/page.rb, line 297 def find_first_element(selector) page.main_frame.find_first_element(selector) end
Generate the HTML
@param [String] ressource_dir the path to the ressource directory
that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 264 def html(ressource_dir: RESSOURCES_DIR) load_template(PAGE_TEMPLATE).result(binding) end
Generate the body of the HTML
document
@param [String] ressource_dir the path to the ressource directory
that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 272 def html_body(ressource_dir: RESSOURCES_DIR) load_template(PAGE_BODY_TEMPLATE).result(binding) end
Generate the HTML
of a fragment
@param [String] ressource_dir the path to the ressource directory
that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 280 def html_fragment(fragment, ressource_dir: RESSOURCES_DIR) load_template(FRAGMENT_TEMPLATE).result(binding) end
Helper that generates a HTML
link to a given object
The object URI is resolved using {#uri_for}. If there is no known link to the object, it is returned as text
@param [Object] object the object to create a link to @param [String] text the link text. Defaults to object#name @return [String]
# File lib/metaruby/gui/html/page.rb, line 154 def link_to(object, text = nil, **args) text = HTML.escape_html(text || object.name || "<anonymous>") if uri = uri_for(object) if uri !~ /^\w+:\/\// if uri[0, 1] != '/' uri = "/#{uri}" end uri = Qt::Url.new("link://metaruby#{uri}") else uri = Qt::Url.new(uri) end args.each { |k, v| uri.add_query_item(k.to_s, v.to_s) } "<a href=\"#{uri.to_string}\">#{text}</a>" else text end end
Load a javascript file in the head
# File lib/metaruby/gui/html/page.rb, line 141 def load_javascript(file) add_to_head( "<script type=\"text/javascript\" src=\"#{path_in_resource(file)}\"></script>") end
Lazy loads a template
@return [ERB]
# File lib/metaruby/gui/html/page.rb, line 218 def load_template(*path) path = File.join(*path) @templates[path] ||= ERB.new(File.read(path)) @templates[path].filename = path @templates[path] end
# File lib/metaruby/gui/html/page.rb, line 180 def main_doc(text) self.class.main_doc(text) end
@api private
Slot that catches the page's link-clicked signal and dispatches into the buttonClicked signal (for buttons), fileClicked for files and linkClicked for links
# File lib/metaruby/gui/html/page.rb, line 306 def pageLinkClicked(url) if url.scheme == 'btn' && url.host == 'metaruby' if btn = find_button_by_url(url) new_state = if url.fragment == 'on' then true else false end btn.state = new_state new_text = btn.text element = find_first_element("a##{btn.html_id}") element.replace(btn.render) emit buttonClicked(btn.id, new_state) else MetaRuby.warn "invalid button URI #{url.to_string}: could not find corresponding handler (known buttons are #{fragments.flat_map { |f| f.buttons.map { |btn| btn.id.to_string } }.sort.join(", ")})" end elsif url.scheme == 'link' && url.host == 'metaruby' emit linkClicked(url) elsif url.scheme == "file" emit fileOpenClicked(url) else MetaRuby.warn "MetaRuby::GUI::HTML::Page: ignored link #{url.toString}" end end
Resolves a relative path to a path in the underlying application's resource folder
@return [String]
# File lib/metaruby/gui/html/page.rb, line 132 def path_in_resource(path) if Pathname.new(path).absolute? path else File.join('${RESOURCE_DIR}', path) end end
Adds a fragment to this page, with the given title and HTML
content
The added fragment is enclosed in a div block to allow for dynamic replacement
@option view_options [String] id the ID of the fragment. If given,
and if an existing fragment with the same ID exists, the new fragment replaces the existing one, and the view is updated accordingly.
# File lib/metaruby/gui/html/page.rb, line 373 def push(title, html, id: auto_id, **view_options) if id # Check whether we should replace the existing content or # push it new fragment = fragments.find do |fragment| fragment.id == id end if fragment fragment.html = html element = find_first_element("div##{fragment.id}") element.replace(html_fragment(fragment)) return end end fragments << Fragment.new(title, html, id: id, **view_options) update_html end
Push a fragment that represents the given exception
{#enable_exception_rendering} must have been called first
@param [String] title the fragment title @param [Exception] e the exception to render @param [String] id the fragment ID @param [Hash] options additional options passed to {#push}
# File lib/metaruby/gui/html/page.rb, line 413 def push_exception(title, e, id: auto_id, **options) html = exception_rendering.render(e, nil, id) push(title, html, id: id, **options) end
Create an item for the rendering in tables
# File lib/metaruby/gui/html/page.rb, line 419 def render_item(name, value = nil) if value "<li><b>#{name}</b>: #{value}</li>" else "<li>#{name}</li>" end end
Render a list of objects into HTML
and push it to this page
@param [String,nil] title the section's title. If nil, no new
section is created
@param [Array<Object>,Array<(Object,Hash)>] items the list
items, one item per line. If a hash is provided, it is used as HTML attributes for the lines
@param [Boolean] filter only render the items with the given 'id' @param [String] id the id to filter if filter: is true @param [Hash] push_options options that are passed to
{#push}. The id: option is added to it.
# File lib/metaruby/gui/html/page.rb, line 438 def render_list(title, items, filter: false, id: nil, **push_options) if filter && !id raise ArgumentError, ":filter is true, but no :id has been given" end html = load_template(LIST_TEMPLATE).result(binding) push(title, html, push_options.merge(id: id)) end
Restore the page at the state it was at the last call to {#save}
# File lib/metaruby/gui/html/page.rb, line 340 def restore return if !@saved_state fragments_by_id = Hash.new @saved_state.each do |fragment| fragments_by_id[fragment.id] = fragment end # Delete all fragments that are not in the saved state fragments.delete_if do |fragment| element = find_first_element("div##{fragment.id}") if old_fragment = fragments_by_id[fragment.id] if old_fragment.html != fragment.html element.replace(old_fragment.html) end else element.replace("") true end end end
Save the current state of the page, so that it can be restored by calling {#restore}
# File lib/metaruby/gui/html/page.rb, line 335 def save @saved_state = fragments.map(&:dup) end
# File lib/metaruby/gui/html/page.rb, line 249 def scale_attribute(node, name, scale) node.attributes[name] = node.attributes[name].gsub /[\d\.]+/ do |n| (Float(n) * scale).to_s end end
Generate the HTML
and update the underlying {#page}
# File lib/metaruby/gui/html/page.rb, line 256 def update_html page.main_frame.html = html end
Generate a URI for an object
The method must either return a string that is a URI representing the object, or nil if there is none. The choice of the URI is application-specific, used by the application to recognize links
The default application returns a file:/// URI for a Pathname object, and then uses {#object_uris}
@see link_to
# File lib/metaruby/gui/html/page.rb, line 235 def uri_for(object) if object.kind_of?(Pathname) "file://#{object.expand_path}" else object_uris[object] end end