opal / opal-browser

Browser support for Opal.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Consider supporting WebComponents

brodock opened this issue · comments

WebComponents is made from few APIs supported natively on the browser. The two most relevant are Custom Elements and Shadow Dom.

The v0 specification for Custom Elements require just calling a bunch of functions to register elements and define attributes to it:

https://www.html5rocks.com/en/tutorials/webcomponents/customelements/

I believe some of them show be part of the opal-browser gem.

The next iteration on WebComponents is the CustomElements v1: https://developers.google.com/web/fundamentals/web-components/customelements

In order to support that, we need to be able to extend HTMLElement and interact with the constructor.

AFAIK this two are not support on Opal right now (hope we can have a workaround in the future).

commented

Right now we wrap the DOM objects inside our own Ruby class, but I made some effort to make sure those are deduplicated. That looks like a good start, maybe, but also maybe it would be a better idea to use the DOM objects directly as Ruby objects (not wrap them), but I would need to do further research on how Opal does things. And I'm afraid this could cause some incompatibilities (my earlier work was careful not to break anything). I will be working on the object model further.

Regarding the API, what I would suggest (or do) is to do something like this: create a Browser::DOM::Element::Custom class which would dispatch the custom elements. Then, if someone wanted to create a new "ob-custom" element, he would write a code like this:

class OBCustom < Browser::DOM::Element::Custom
  construct_element "ob-custom" do
    self << DOM { div "Hello world" }
  end

  def hello
    123
  end
end

elem = OBCustom.create
$document.body << elem
$document.at_css('ob-custom').class # should be OBCustom
$document.at_css('ob-custom').hash == elem.hash # not another wrapper, but the same object
elem.hello # 123
$document.body << DOM("<ob-custom id='obc'>Test</ob-custom>")
$document['obc'].class # should be OBCustom

(this DSL is just a crude idea)

Under the hood, construct_element would both register "ob-custom" to some custom JS class which would create bindings and generate a constructor for the new class from its block (we may allow user to access it too, or create some helper methods to expose Ruby methods to the JS world, but I see it currently mostly as a wrapper). Would it allow polyfills to work? Certainly.

Supporting Shadow DOM in opal-browser should be a walk in the park, but polyfilling that would border on being impossible I think. I would do that by implementing a shadow method on DOM::Element which would either return a shadow DOM interface if it already exists or create one. It could be removed with shadow.remove maybe.

What would you all think? I'm new to this API so my idea may not be especially correct.

commented

The above commits should support most of the functionality of WebComponents. What remains is custom DOM classes which can be useful for initializing and maybe interacting with other JS code (and it's actually the core of WebComponents). This warrants quite a careful interface design at this stage and if someone has some good idea - feel free to chime in.

commented

After some more reading and understanding the spec I think an API like this may be a good idea:

class MySuperImage < Browser::DOM::Element::Image
  # Under the hood this does def_selector, create a new JS class inheriting from HTMLImageElement
  # and setting 'img' as a base tag. Two last parts are optional - the JS class could be deduced from the
  # Ruby class like Browser::DOM::Element::Image. Also it would include a module like
  # Browser::DOM::Element::Custom which would provide the necessary API methods.
  def_custom_element "my-super-image", `HTMLImageElement`, 'img'

  # Part of the API - called just after createElement (or when we approach the element in the DOM). Properties won't work yet
  # but we can set up the shadow DOM. Can we just name it initialize? Possibly, I will explore that possibility.
  def custom_initialize
    shadow << CSS {
      rule ":host" do
        display "block"
      end
    }
  end

  # Part of the API - called when this element is attached to DOM
  def custom_attach
  end

  # Part of the API - called when this element is detached from DOM
  def custom_detach
  end
end

Other possible ideas I have are like, adding a template hash to Browser::DOM::Element::Custom which would basically store the template contents (easing out a few calls and sparing the DOM parsing overhead). Eg.

class TestElement < Browser::DOM::Element
  # ...

  template[:root] = DOM {
    slot name: "item" do
      p "Hello!"
    end
  }

  def custom_initialize
    shadow << template[:root]
  end
end

The idea is open and the names to be used are not set in stone. I believe we can make a robust API.