Skip to content

Vue.js and Web Components #332

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

Closed
AlessandroEmm opened this issue Jun 30, 2014 · 24 comments
Closed

Vue.js and Web Components #332

AlessandroEmm opened this issue Jun 30, 2014 · 24 comments

Comments

@AlessandroEmm
Copy link

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

@AlessandroEmm
Copy link
Author

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

@yyx990803
Copy link
Member

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',
    // ...
})

@AlessandroEmm
Copy link
Author

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!

@yyx990803
Copy link
Member

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/

@AlessandroEmm
Copy link
Author

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

@AlessandroEmm
Copy link
Author

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.

@yyx990803
Copy link
Member

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

@AlessandroEmm
Copy link
Author

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.

@AlessandroEmm
Copy link
Author

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?

@yyx990803
Copy link
Member

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

@AlessandroEmm
Copy link
Author

Exactly.

@yyx990803
Copy link
Member

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

@AlessandroEmm
Copy link
Author

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

@yyx990803
Copy link
Member

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.

@AlessandroEmm
Copy link
Author

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?

@yyx990803
Copy link
Member

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.

@AlessandroEmm
Copy link
Author

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".

@yyx990803
Copy link
Member

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

@AlessandroEmm
Copy link
Author

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?

@AlessandroEmm
Copy link
Author

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.

@yyx990803
Copy link
Member

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.

@AlessandroEmm
Copy link
Author

@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

@yyx990803
Copy link
Member

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

@kristianmandrup
Copy link

kristianmandrup commented Jan 22, 2017

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!

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