Vue: Vue.js and Web Components

Created on 30 Jun 2014  路  24Comments  路  Source: vuejs/vue

Hey there

I've been trying to to get vue.js running with a custom Element (Web Component) and I cant seem to get it running. I tried to hand it a { el: 'template', data: { test:" test"}} but it did not work (no errors).

Where as the template consists of

         <template>
         <h1> {{ test }}</h1>
          </template>

While the template can be resolved by vue the problem is that template is no ordinary HTML element and doesn't have the H1 as direct child. It has however a document fragment as an attribute which itself has the children in it. Passing vue this fragment didnt work either unfortunately. A document fragment is obviously not the same as full blown element so could that be the issue?

Thank you!
Alessandro

Most helpful comment

How can I package a Vue component as a web component? Couldn't find any examples of that so far... Oh, I see the gist you provided shows how. Would be nice if this recipe was more accessible via official docs or real life examples. Cheers!

All 24 comments

I was able to get around the issue by boxing everything in the template tag into a div and referencing that. Is there a easy way to found out what the template aka documentfragment has been lacking that it wouldn't work with it?

Thanks again.
Alessandro

You should use the template tag with the template option instead of el.

e.g.

<template id="my-template">
    <h1> {{ test }}</h1>
</template>
new Vue({
    template: '#my-template',
    // ...
})

Unfortunately that didn't work. What DOM Node would vue be doing the ID Lookup based on anyway? Can I use template and pass it the actuall innerHTML that its supposed to parse?

Thanks!

The catch here is template is used to created a cached DocumentFragment for reuse, while el is the actual element that Vue will use to render stuff. On the other hand, <template> tags cannot be used as a render node (because browsers won't render it at all, it only parses its content as a DocumentFragment). You need both template and el options for it to work.

Working example: http://jsfiddle.net/fNY6J/

My configuration now is like
{ el: 'div', template: this.shadowRoot, data: {test:"input"}} // this.shadowRoot pointing to the template

And I'm getting a:
screen shot 2014-06-30 at 20 18 56

So as an alternative i tried to modify the div lookup slightly by doing a
{ el: this.shadowRoot.querySelector('div'), template: this.shadowRoot, data: {input:"input"}}

I got a:
Uncaught DataCloneError: Failed to execute 'cloneNode' on 'ShadowRoot': ShadowRoot nodes are not clonable.

So thats probably where the evil lies.

Why would you use a shadowRoot as your template? Can you post a fiddle so I can see your setup?

I'm not quite sure how to implement that with a fiddle to be honest otherwise I would have done that in the first place. :-) I'm going to give it a try.

My template is getting shadowRooted so that its content can be encapsulated/hidden from the rest of the DOM. Have you ever had a look at Polymer? (polymer-project.org) They have similar two-way binding functionality with shadowRoot in place. Thing is that I wanted something similar with regular web components. That's why I was on the look for an "independent" solution like yours.

Sorry to bring up another point: Is it possible to invert the relationship from data to the vue instance? So that I can hand vue a reference to an object, but they would only get reference and watched when they are being used in a directive/bind?

Do you mean only watch data that is actually used in templates?

Exactly.

Unfortunately that's not possible at the moment...

Okay. Is it a limitation of the current implementation or hasn't it just come up as an requirement?

The idea is that if something is in vm.$data, it is meant to be observed. However you can simply do vm.someThing = {} after the vm has been created, these late additions will never get watched.

I see your point. Thing is my $data is pointing to fairly large object that not only holds simple object like Arrays, String and the like but also DOM elements, well stuff that should most likely not be watched but should reside in the same object for convenience. Would it be a big effort to adapt that or rather make that configurable?

You can prefix properties with _ and those properties will be ignored by the observer. Also anything that is not pure JS object will be ignored too.

Yeah, I noticed the escaping with the prefix, but this would ultimately the break the mixing of things in the object as every entry would have to adhere to that rule, which it shouldn't IMHO.

How does the detection of non-standard JS Objects work? All I have currently are just some functions, some HTML Nodes and plain values in the watched object, and I still run into
"RangeError: Maximum call stack size exceeded".

I've found out that shadowRoot nodes do not implement the full HTMLElement interface, so you cannot use them as template or el in Vue. Here's a rough test of using Vue with custom elements: https://github.com/yyx990803/hello-world-element

Thanks for giving it a try yourself.

As a workaround I'm currently wrapping a span around the shadomDOMs content. Is there anything specific that you need from the HTMLElement that you'd miss on the shadowRoot node?

Oh by the way, I'm using Chrome's 35 native ShadowRoot which might as well differ quite a bit from the polyfill that platform.js provides.

I'm testing in Chrome 35 too. It seems from the spec draft that shadowRoot does not implement the full Element interface, e,g hasAttributes, setAttribute etc.

@yyx990803
So I have finally figured what caused the infinite loop within the data binding. It wasn't properly distinguishing between regular objects and Node/Document Like Objects and thus was indexing those forever.
I have extend the convertKey function to also check for special objects through their specific fields:

function convertKey (obj, key, propagate) {
    var keyPrefix = key.charAt(0)
    if (keyPrefix === '$' || keyPrefix === '_') {
        return
    }
    if('documentURI' in obj || "styleSheets" in obj)
        return

DocumentURI is for Documents and styleSheets is used in shadowRoots objects.
I don't really know how to implement that in a less fuzzy way because the shadowRoots are really lacking an identifying attribute.

I do think that you'd never want to watch either of those, what do you think?

Thanks
Alessandro

Closing due to inactivity, but check this out: (currently a proof of concept) https://gist.github.com/yyx990803/fa9b369661ab04c87af1

How can I package a Vue component as a web component? Couldn't find any examples of that so far... Oh, I see the gist you provided shows how. Would be nice if this recipe was more accessible via official docs or real life examples. Cheers!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

julianxhokaxhiu picture julianxhokaxhiu  路  3Comments

aviggngyv picture aviggngyv  路  3Comments

finico picture finico  路  3Comments

robertleeplummerjr picture robertleeplummerjr  路  3Comments

gkiely picture gkiely  路  3Comments