Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVGElement but not svg tag #313

Closed
NeverGivinUp opened this issue Dec 23, 2018 · 10 comments
Closed

SVGElement but not svg tag #313

NeverGivinUp opened this issue Dec 23, 2018 · 10 comments

Comments

@NeverGivinUp
Copy link

NeverGivinUp commented Dec 23, 2018

I'm considering implementing some svg api, but my rust and stdweb knowledge is insufficient. Help me out, please.

SVGElement is the interface all elements in the svg namespace implement. It provides functionality similar to the INode interface. Notably the ownerSVGElement function returns an Option of a SVGElement similar to the parentElement function of the Node interface. The return type of parent_element in Stdweb is Option< Element >, though "The parent of an element is an Element node, a Document node, or a DocumentFragment node." (Source). This works, I suppose, because Document and DocumentFragment can, for all practical purposes, be considered the html tag element or another element. In the SVG case this is not possible. The svg tag element does have attributes some, but not all other elements share. Thus it cannot be considered a super type of all other tags in the svg namespace. So ownerSVGElement cannot return Option but must return Option.

The function owner_svg_element of the trait ISVGElement: IElement

    fn owner_svg_element(&self) -> Option<ISVGElement> {
        unsafe {
            js!(
                return @{self.as_ref()}.ownerSVGElement;
            ).into_reference_unchecked()
        }
    }

errors as follows

error[E0191]: the value of the associated type `Error` (from the trait `webcore::try_from::TryFrom`) must be specified
  --> src\webapi\svg_element.rs:37:42
   |
28 |     fn owner_svg_element(&self) -> Option<ISVGElement> {
   |                                          ^^^^^^^^^^^ associated type `Error` must be specified
   | 
  ::: src\webcore\try_from.rs:6:5
   |
6  |     type Error;
   |     ----------- `Error` defined here

The same for fn viewport_element(&self) -> Option<ISVGElement>, which is the more relevant, as not all elements have a viewport, but the svg tag element as well as some other elements do.

If I change the signature to

fn owner_svg_element(&self) -> Option<ISVGElement<Error=::stdweb::private::ConversionError>> 

the error changes to

error[E0221]: ambiguous associated type `Error` in bounds of `webapi::svg_element::ISVGElement`
  --> src\webapi\svg_element.rs:28:55
   |
28 |     fn owner_svg_element(&self) -> Option<ISVGElement<Error=::stdweb::private::ConversionError>> {
   |                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ambiguous associated type `Error`
   | 
  ::: src\webcore\try_from.rs:6:5
   |
6  |     type Error;
   |     -----------
   |     |
   |     ambiguous `Error` from `webcore::try_from::TryFrom<webcore::value::Reference>`
   |     ambiguous `Error` from `webcore::try_from::TryFrom<webcore::value::Value>`
@koute
Copy link
Owner

koute commented Dec 23, 2018

In the SVG case this is not possible. The svg tag element does have attributes some, but not all other elements share.

Can you give me some examples?

fn owner_svg_element(&self) -> Option<ISVGElement> {

You probably want it to look like this:

fn owner_svg_element(&self) -> Option<SvgElement> {
    js!(
        return @{self.as_ref()}.ownerSVGElement;
    ).try_into().unwrap()
}

where ISvgElement and SvgElement would be defined as:

pub trait ISvgElement: IElement {
    // ...
}

#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "SVGElement")]
#[reference(subclass_of(EventTarget, Node, Element))]
pub struct SvgElement( Reference );

impl IEventTarget for SvgElement {}
impl INode for SvgElement {}
impl IElement for SvgElement {}
impl ISvgElement for SvgElement {}

Basically, you're not supposed to return traits.

@NeverGivinUp
Copy link
Author

In the SVG case this is not possible. The svg tag element does have attributes some, but not all other elements share.

Can you give me some examples?

If you compare elements - for extreme cases see the svg tag, the title tag and the discard tag, the only attributes all seem to have in common are the core attributes .

Many elements have more attributes in common: styling attributes, global event attributes, presentation attributes and the conditional processing attributes. Graphics elements additionally have graphical event attributes.

Most svg elements have element-specific attributes in addition to common groups of attributes. svg tag-specific attributes are baseProfile, contentScriptType, contentStyleType, height, preserveAspectRatio, version, viewBox, width, x, y. There are other elements that share some of these element-specific attributes. e.g. the view element shares preserveAspectRatio and viewBox.

On a side note: Half of the element-specific attributes of svg element are deprecated. What is stdweb's policy on implementing deprecated and experimental tags and attributes?

Basically, you're not supposed to return traits.

So the alternatives are as follows

  • give all element-specific attributes and attribute groups to all elements in the svg namespace, like in your code fragments above.
  • create an AbstractSVGELement that represents the javascript interface as an stdweb struct
  • change stdweb's restriction to not return traits (which would be a breaking change)
  • others?

@Pauan
Copy link
Contributor

Pauan commented Dec 25, 2018

@NeverGivinUp The reason you can't return traits isn't because of stdweb, it's a limitation in Rust itself.

Rust is statically typed, so everything must have a concrete type which is known at compile time.

Even when you use things like impl Foo or Box<dyn Foo> the Rust compiler must still know the concrete type (so it can convert it into the trait). After it has been converted into a trait, then it no longer needs a concrete type.

So you will need concrete types for the various SVG elements, there is no way around that. As @koute mentioned, it's easy to create such types by using ReferenceType, instance_of, and subclass_of.

Each SVG element should be a distinct struct, using subclass_of to establish class inheritance.

However, even though you can use subclass_of to create class inheritance, that does not give you method or property inheritance, because Rust does not have method/property inheritance.

So instead, in stdweb we use traits to simulate method/property inheritance (though there is a proposal to replace traits with Deref, you can ignore that for now).

Basically, you use traits for methods which are shared between many types, and concrete methods for methods which exist only on a specific type.

Let me give an example, so you can clearly see how we do things. You can then use the same techniques for SVG.

  1. We create an EventTarget struct which is a concrete Rust type used for the EventTarget class in JS.

    It uses instance_of to verify at runtime that it is the correct JS class (EventTarget).

    Technically it is only needed for it to derive ReferenceType, but we also derive other traits such as Clone, Debug, PartialEq, and Eq. This makes it more useful for Rust programmers.

  2. We create an IEventTarget trait, which contains all of the EventTarget methods. As you can see, it has add_event_listener and dispatch_event.

    It defines the methods as default methods, which means they will be automatically provided on all types which impl IEventTarget. This is done for convenience.

  3. We create an impl for IEventTarget. Because we defined the default methods in the trait definition, we do not need to define them here.

That is all that is needed to create a root type (such as EventTarget).

Now let's look at how we can create sub-classes of EventTarget:

  1. We create a new Node struct. It is very similar to EventTarget, except it uses reference(subclass_of(EventTarget)) to specify that it is a sub-class of EventTarget.

    When a type uses subclass_of, it automatically adds in From and TryFrom implementations, so you can use foo.into() and foo.try_into() to convert to/from Node and EventTarget.

    That is all that it does, it does not cause any of the methods to be inherited.

  2. We create a new INode trait which contains all of the Node methods.

    Notice that the INode trait inherits from the IEventTarget trait. This does not cause the methods to be inherited, instead what it means is: if a type implements INode, it must also implement IEventTarget.

  3. We add impls for IEventTarget and INode.

    Because the IEventTarget and INode traits use default methods, we don't need to specify the methods here, so it is much more convenient.

    Because we have implemented both IEventTarget and INode, that means that Node contains the methods for both IEventTarget and INode. This is how we simulate method inheritance.

That's it! As you can see, creating a sub-class is very similar to creating a root class.

Of course we can go deeper as well. Here is how the Element class is defined:

  1. #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
    #[reference(instance_of = "Element")]
    #[reference(subclass_of(EventTarget, Node))]
    pub struct Element( Reference );

  2. pub trait IElement: INode + IParentNode + IChildNode + ISlotable {
    /// The Element.namespaceURI read-only property returns the namespace URI
    /// of the element, or null if the element is not in a namespace.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/namespaceURI)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-namespaceuri
    fn namespace_uri( &self ) -> Option< String > {
    js!(
    return @{self.as_ref()}.namespaceURI;
    ).try_into().unwrap()
    }
    /// The Element.classList is a read-only property which returns a live
    /// [TokenList](struct.TokenList.html) collection of the class attributes
    /// of the element.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-classlist
    fn class_list( &self ) -> TokenList {
    unsafe {
    js!( return @{self.as_ref()}.classList; ).into_reference_unchecked().unwrap()
    }
    }
    /// The Element.hasAttribute() method returns a Boolean value indicating whether
    /// the specified element has the specified attribute or not.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-hasattribute
    fn has_attribute( &self, name: &str ) -> bool {
    js!(
    return @{self.as_ref()}.hasAttribute( @{name} );
    ).try_into().unwrap()
    }
    /// Element.getAttribute() returns the value of a specified attribute on the element.
    /// If the given attribute does not exist, the value returned will either be
    /// null or "" (the empty string);
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-getattribute
    fn get_attribute( &self, name: &str ) -> Option< String > {
    js!(
    return @{self.as_ref()}.getAttribute( @{name} );
    ).try_into().unwrap()
    }
    /// Sets the value of an attribute on the specified element. If the attribute already
    /// exists, the value is updated; otherwise a new attribute is added with the
    /// specified name and value.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-setattribute
    fn set_attribute( &self, name: &str, value: &str ) -> Result< (), InvalidCharacterError > {
    js_try!(
    return @{self.as_ref()}.setAttribute( @{name}, @{value} );
    ).unwrap()
    }
    /// Gets the the number of pixels that an element's content is scrolled vertically.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop)
    // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrolltop%E2%91%A0
    fn scroll_top( &self ) -> f64 {
    js!(
    return @{self.as_ref()}.scrollTop;
    ).try_into().unwrap()
    }
    /// Sets the the number of pixels that an element's content is scrolled vertically.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop)
    // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrolltop%E2%91%A0
    fn set_scroll_top( &self, value: f64 ) {
    js! { @(no_return)
    @{self.as_ref()}.scrollTop = @{value};
    }
    }
    /// Gets the the number of pixels that an element's content is scrolled to the left.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
    // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrollleft%E2%91%A0
    fn scroll_left( &self ) -> f64 {
    js!(
    return @{self.as_ref()}.scrollLeft;
    ).try_into().unwrap()
    }
    /// Sets the the number of pixels that an element's content is scrolled to the left.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft)
    // https://drafts.csswg.org/cssom-view/#ref-for-dom-element-scrollleft%E2%91%A0
    fn set_scroll_left( &self, value: f64 ) {
    js! { @(no_return)
    @{self.as_ref()}.scrollLeft = @{value};
    }
    }
    /// Element.getAttributeNames() returns the attribute names of the element
    /// as an Array of strings. If the element has no attributes it returns an empty array.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNames)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-getattributenames
    fn get_attribute_names( &self ) -> Vec<String> {
    js!(
    return @{self.as_ref()}.getAttributeNames();
    ).try_into().unwrap()
    }
    /// Element.removeAttribute removes an attribute from the specified element.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-removeattribute
    fn remove_attribute( &self, name: &str ) {
    js! { @(no_return)
    @{self.as_ref()}.removeAttribute( @{name} );
    }
    }
    /// The Element.hasAttributes() method returns Boolean value, indicating if
    /// the current element has any attributes or not.
    ///
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttributes)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-hasattributes
    fn has_attributes( &self ) -> bool {
    js!(
    return @{self.as_ref()}.hasAttributes();
    ).try_into().unwrap()
    }
    /// Returns the closest ancestor of the element (or the element itself) which matches the selectors
    /// given in parameter. If there isn't such an ancestor, it returns
    /// `None`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-closest
    fn closest( &self, selectors: &str) -> Result<Option<Element>, SyntaxError> {
    js_try!(
    return @{self.as_ref()}.closest(@{selectors});
    ).unwrap()
    }
    /// Designates a specific element as the capture target of future pointer events.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture)
    // https://w3c.github.io/pointerevents/#dom-element-setpointercapture
    #[inline]
    fn set_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId > {
    js_try!(
    return @{self.as_ref()}.setPointerCapture( @{pointer_id} );
    ).unwrap()
    }
    /// Releases pointer capture that was previously set for a specific pointer
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/releasePointerCapture)
    // https://w3c.github.io/pointerevents/#dom-element-releasepointercapture
    #[inline]
    fn release_pointer_capture( &self, pointer_id: i32 ) -> Result< (), InvalidPointerId > {
    js_try!(
    return @{self.as_ref()}.releasePointerCapture( @{pointer_id} );
    ).unwrap()
    }
    /// Returns a boolean indicating if the element has captured the specified pointer
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/hasPointerCapture)
    // https://w3c.github.io/pointerevents/#dom-element-haspointercapture
    #[inline]
    fn has_pointer_capture( &self, pointer_id: i32 ) -> bool {
    js!( return @{self.as_ref()}.hasPointerCapture( @{pointer_id} ); ).try_into().unwrap()
    }
    /// Insert nodes from HTML fragment into specified position.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
    // https://w3c.github.io/DOM-Parsing/#widl-Element-insertAdjacentHTML-void-DOMString-position-DOMString-text
    fn insert_adjacent_html( &self, position: InsertPosition, html: &str ) -> Result<(), InsertAdjacentError> {
    js_try!( @(no_return)
    @{self.as_ref()}.insertAdjacentHTML( @{position.as_str()}, @{html} );
    ).unwrap()
    }
    /// Insert nodes from HTML fragment before element.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
    fn insert_html_before( &self, html: &str ) -> Result<(), InsertAdjacentError> {
    self.insert_adjacent_html(InsertPosition::BeforeBegin, html)
    }
    /// Insert nodes from HTML fragment as the first children of the element.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
    fn prepend_html( &self, html: &str ) -> Result<(), InsertAdjacentError> {
    self.insert_adjacent_html(InsertPosition::AfterBegin, html)
    }
    /// Insert nodes from HTML fragment as the last children of the element.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
    fn append_html( &self, html: &str ) -> Result<(), InsertAdjacentError> {
    self.insert_adjacent_html(InsertPosition::BeforeEnd, html)
    }
    /// Insert nodes from HTML fragment after element.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML)
    fn insert_html_after( &self, html: &str ) -> Result<(), InsertAdjacentError> {
    self.insert_adjacent_html(InsertPosition::AfterEnd, html)
    }
    /// The slot property of the Element interface returns the name of the shadow DOM
    /// slot the element is inserted in.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/slot)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-slot
    fn slot( &self ) -> String {
    js!(
    return @{self.as_ref()}.slot;
    ).try_into().unwrap()
    }
    /// Attach a shadow DOM tree to the specified element and returns a reference to its `ShadowRoot`.
    /// It returns a shadow root if successfully attached or `None` if the element cannot be attached.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-attachshadow
    fn attach_shadow( &self, mode: ShadowRootMode ) -> Result<ShadowRoot, AttachShadowError> {
    js_try!(
    return @{self.as_ref()}.attachShadow( { mode: @{mode.as_str()}} )
    ).unwrap()
    }
    /// Returns the shadow root of the current element or `None`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot)
    // https://dom.spec.whatwg.org/#ref-for-dom-element-shadowroot
    fn shadow_root( &self ) -> Option<ShadowRoot> {
    unsafe {
    js!(
    return @{self.as_ref()}.shadowRoot;
    ).into_reference_unchecked()
    }
    }
    }

  3. impl IEventTarget for Element {}
    impl INode for Element {}
    impl IElement for Element {}

It is the same as how we created Node.

The end result is:

  • Users can use IEventTarget methods with EventTarget, Node, and Element.

  • Users can use INode methods with Node and Element.

  • Users can use IElement methods with Element.

  • Users can use From and TryFrom to safely convert between EventTarget, Node, and Element.

  • We only need to define the methods once, so it avoids code duplication.

  • Everything is statically type-safe, so users cannot use INode or IElement methods with EventTarget.

If there are some methods which exist only on one specific type, we just use normal inherent methods:

stdweb/src/webapi/node.rs

Lines 366 to 396 in ac96613

impl Node {
/// Attempt to create the `Node` from raw html. The html string must contain **exactly one**
/// root node.
///
/// Returns a `SyntaxError` if:
///
/// - There is not **exactly one** root node.
/// - The html syntax is wrong. However, on most browsers the html parsing algorighm is
/// _unbelievably_ forgiving and will just turn your html into text or maybe even an empty
/// string.
///
/// It is recommended to have control over the html being given to this function as not
/// having control is a security concern.
///
/// For more details, see information about setting `innerHTML`:
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML>
pub fn from_html(html: &str) -> Result<Node, SyntaxError> {
js_try!(
var span = document.createElement("span");
span.innerHTML = @{html};
if( span.childNodes.length != 1 ) {
throw new DOMException(
"Node::from_html requires a single root node but has: "
+ span.childNodes.length,
"SyntaxError");
}
return span.childNodes[0];
).unwrap()
}
}

As for deprecated methods, we generally don't add them in unless there is a very good reason to do so.

We're more accepting of experimental methods, though it needs to be clear in the documentation where they are supported (and where they're not supported).

Does that answer your questions? Is there anything which is still not clear?

@Pauan
Copy link
Contributor

Pauan commented Dec 25, 2018

P.S. There are some methods which are defined as mixins, so they don't belong to a particular class. In that case we use this technique:

  1. First we define the mixin as a standalone trait:

    pub trait IParentNode: ReferenceType {
    /// Returns the first element that is a descendant of the element on which it is
    /// invoked that matches the specified group of selectors.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector)
    // https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselector
    fn query_selector( &self, selector: &str ) -> Result< Option< Element >, SyntaxError > {
    js_try!(
    return @{self.as_ref()}.querySelector(@{selector});
    ).unwrap()
    }
    /// Returns a non-live [NodeList](struct.NodeList.html) of all elements descended
    /// from the element on which it is invoked that matches the specified group of CSS selectors.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll)
    // https://dom.spec.whatwg.org/#ref-for-dom-parentnode-queryselectorall
    fn query_selector_all( &self, selector: &str ) -> Result< NodeList, SyntaxError > {
    js_try!(
    return @{self.as_ref()}.querySelectorAll(@{selector});
    ).unwrap()
    }
    }

  2. For each type which uses the mixin, we add it as a parent to the trait:

    pub trait IElement: INode + IParentNode + IChildNode + ISlotable {

    Notice how IElement inherits from IParentNode. This works because Rust traits support multiple inheritance, unlike JavaScript which only supports single inheritance.

  3. We add an impl so that every IElement automatically implements IParentNode:

    impl< T: IElement > IParentNode for T {}

@NeverGivinUp
Copy link
Author

Thank you @Pauan, for the detailed explanation. It has been very helpful. And thank you @Pauan, too. And merry Christmas, to you (if you are christian or just like the festivities).

It seems my confusion mainly stemmed from naively equalizing Javascript interfaces with Rust traits. Instead in StdWeb I need to represent a Javascript Interface it by a Rust struct, which references that Interface. A corresponding trait is purely for the convenient default implementations. It has nothing to do with the Javascript Interface. To represent a Javascript Interface separately from a Javascript Class I need to create separate Rust structs for each of them.

Another confusion of mine was between SVGElement and SVGSVGElement. The former is the Javascript Interface from which all SVG elements inherit. The latter is the Javascript Interface to the svg tag element. I'm obviously a rookie, when it comes to modern web development.

Most developers that currently use StdWeb have a solid Javascript and WebApi background. In time more developers will use StdWeb, that are not as familiar with them. Suppose they try to instantiate of SVGElement or HTMLElement. What is the idiomatic way to tell them to use SVGSVGElement or HTMLHtmlElement instead? Documentation? An explicit check (and error) in document().create_element() and document().create_element_ns()?

@Pauan
Copy link
Contributor

Pauan commented Dec 25, 2018

@NeverGivinUp And merry Christmas, to you

Yeah, you too.

It seems my confusion mainly stemmed from naively equalizing Javascript interfaces with Rust traits.

WebIDL interfaces are actually the same as JavaScript classes (i.e. concrete types). They're not interfaces at all (even though they're called interfaces).

WebIDL mixins are more like interfaces (and they map cleanly to Rust traits). Yes, it's confusing.

A corresponding trait is purely for the convenient default implementations. It has nothing to do with the Javascript Interface.

Yes, though it's more precise to say that the Rust struct corresponds to the JavaScript class while the Rust trait corresponds to the JavaScript methods.

The other reason is to allow for method/property inheritance (e.g. you can create a method which accepts IElement and it will then work with many different types).

To represent a Javascript Interface separately from a Javascript Class I need to create separate Rust structs for each of them.

Yes, each interface corresponds to a separate Rust struct.

Except a WebIDL interface is the same as a JavaScript class, they're not separate things. So a Rust struct corresponds to a JavaScript class (which is the same as a WebIDL interface).

Another confusion of mine was between SVGElement and SVGSVGElement. The former is the Javascript Interface from which all SVG elements inherit. The latter is the Javascript Interface to the svg tag element.

Yes, it's quite confusing. As you figured out, SVGElement is a generic class for all SVG elements, and SVGSVGElement is specifically for the <svg> tag.

I'm obviously a rookie, when it comes to modern web development.

That's not a problem, we can help fill in any missing gaps and answer questions.

Suppose they try to instantiate of SVGElement or HTMLElement. What is the idiomatic way to tell them to use SVGSVGElement or HTMLHtmlElement instead? Documentation? An explicit check (and error) in document().create_element() and document().create_element_ns()?

This is done using TryInto, like this:

let x: InputElement = document().create_element("input").unwrap().try_into().unwrap();

Or for SVG elements:

let x: SvgPathElement = document().create_element_ns("http://www.w3.org/2000/svg", "path").unwrap().try_into().unwrap();

The type (InputElement or SvgPathElement) must match the tag (input or path).

If you create an app, I recommend creating helper functions to make the above easier:

fn create_element<A>(tag: &str) -> A where A: TryFrom<Element> {
    document().create_element(tag).unwrap().try_into().unwrap()
}
let x: InputElement = create_element("input");

Or perhaps using a DOM framework like dominator.

There's actually quite a few DOM frameworks available for Rust (including Virtual DOM ones like yew)

@Pauan
Copy link
Contributor

Pauan commented Dec 25, 2018

Oh, I just now realized I didn't really answer your question.

Suppose they try to instantiate of SVGElement or HTMLElement. What is the idiomatic way to tell them to use SVGSVGElement or HTMLHtmlElement instead? Documentation? An explicit check (and error) in document().create_element() and document().create_element_ns()?

Since the JavaScript runtime class is determined by the tag (not the type), it is perfectly valid to create an SvgElement from an svg tag.

It won't give an error, but of course you'll only be able to use the SvgElement methods (if you try to use an SvgSvgElement method it will give you a compile error).

If you want to use SvgSvgElement methods, all you have to do is change the type to SvgSvgElement (the tag can stay the same).

Maybe we could use some documentation for that.

Or perhaps instead we could create something similar to ConcreteEvent, and make it impossible to mess up:

let x: SvgSvgElement = document().create_element().unwrap();

I like that idea a lot. I should implement that in dominator.

@NeverGivinUp
Copy link
Author

Thanks again @Pauan! I managed to get a stub implementation of SVGSVGElement working.

When I create it like this (I am aware, that in practice I'll need something like a namespace handle and can't just write an attribute):

    let svg_element: SVGSVGElement = document().create_element_ns("http://www.w3.org/2000/svg", "svg").unwrap().try_into().unwrap();
    svg_element.set_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink").unwrap();

I get

<svg xmlns:xlink="http://www.w3.org/1999/xlink"></svg>

The xlink namespace, i added manually, is there. The svg namespace is missing.

Do you happen to know if this is

  • me using create_element_ns in a wrong way
  • intended behaviour, as the svg namespace is known in the browser
  • a bug in create_element_ns

@Pauan
Copy link
Contributor

Pauan commented Dec 27, 2018

@NeverGivinUp In the browser debugger tools it doesn't show the namespace, but it really is there.

You can verify this by selecting the <svg> tag, and then running $0.namespaceURI in the console.

@NeverGivinUp
Copy link
Author

I'm just going to believe you. Thanks @koute !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants