Add proxy methods
ScottBreyer96 opened this issue · 7 comments
Hello, I am currently developing a web component and I would like users to be able to interact with it using methods. Can you add the methods to the wrapper? Why didn't you do it originally?
I modified the file generated by the CLI of View to test the addition of the methods, it takes little time but modifying a generated file is a bad practice and I cannot find another solution.
Can you help me by adding the methods? Is there a reason why you didn't add the methods?
Thank you
I don't understand what you want, or mean wqhen you say "adding methods", to be honest.
An example would be helpful.
Sorry, I'm a trainee in a company, I don't always know how to explain things well.
When you convert a view project into a web component, the view-web-component-wrapper used allows you to have props, events etc in proxy. What I want is to also have the methods contained in the parent component of my project seen.
This is very useful for the use of my component.
I'm not sure it's done right, but here is the modified code to add the proxy methods.
function wrap modified :
....
function wrap (Vue, Component) {
const isAsync = typeof Component === 'function' && !Component.cid;
let isInitialized = false;
let hyphenatedPropsList;
let camelizedPropsList;
let camelizedPropsMap;
let hyphenatedMethodsList;
let camelizedMethodsList;
let camelizedMethodsMap;
function initialize (Component) {
if (isInitialized) return
const options = typeof Component === 'function'
? Component
: Component;
let methodsList = Object.keys(options.methods || {});
hyphenatedMethodsList = methodsList.map(hyphenate);
camelizedMethodsList = methodsList.map(camelize);
const originalMethodsAsObject = Array.isArray(options.methods) ? {} : options.methods || {};
camelizedMethodsMap = camelizedMethodsList.reduce((map, key, i) => {
map[key] = originalMethodsAsObject[methodsList[i]];
return map
}, {});
// extract props info
const propsList = Array.isArray(options.props)
? options.props
: Object.keys(options.props || {});
hyphenatedPropsList = propsList.map(hyphenate);
camelizedPropsList = propsList.map(camelize);
const originalPropsAsObject = Array.isArray(options.props) ? {} : options.props || {};
camelizedPropsMap = camelizedPropsList.reduce((map, key, i) => {
map[key] = originalPropsAsObject[propsList[i]];
return map
}, {});
// proxy $emit to native DOM events
injectHook(options, 'beforeCreate', function () {
const emit = this.$emit;
this.$emit = (name, ...args) => {
this.$root.$options.customElement.dispatchEvent(createCustomEvent(name, args));
return emit.call(this, name, ...args)
};
});
injectHook(options, 'created', function () {
// sync default props values to wrapper on created
camelizedPropsList.forEach(key => {
this.$root.props[key] = this[key];
});
camelizedMethodsList.forEach(key => {
this.$root.props[key] = this[key];
});
});
camelizedMethodsList.forEach(key => {
Object.defineProperty(CustomElement.prototype, key, {
get() {
return this._wrapper.props[key]
},
set(newVal) {
this._wrapper.props[key] = newVal;
},
enumerable: false,
configurable: true
});
});
// proxy props as Element properties
camelizedPropsList.forEach(key => {
Object.defineProperty(CustomElement.prototype, key, {
get () {
return this._wrapper.props[key]
},
set (newVal) {
this._wrapper.props[key] = newVal;
},
enumerable: false,
configurable: true
});
});
isInitialized = true;
}
function syncAttribute (el, key) {
const camelized = camelize(key);
const value = el.hasAttribute(key) ? el.getAttribute(key) : undefined;
el._wrapper.props[camelized] = convertAttributeValue(
value,
key,
camelizedPropsMap[camelized]
);
el._wrapper.props[camelized] = convertAttributeValue(
value,
key,
camelizedMethodsMap[camelized]
);
}
class CustomElement extends HTMLElement {
constructor () {
super();
this.attachShadow({ mode: 'open' });
const wrapper = this._wrapper = new Vue({
name: 'shadow-root',
customElement: this,
shadowRoot: this.shadowRoot,
data () {
return {
props: {},
slotChildren: []
}
},
render (h) {
return h(Component, {
ref: 'inner',
props: this.props
}, this.slotChildren)
}
});
// Use MutationObserver to react to future attribute & slot content change
const observer = new MutationObserver(mutations => {
let hasChildrenChange = false;
for (let i = 0; i < mutations.length; i++) {
const m = mutations[i];
if (isInitialized && m.type === 'attributes' && m.target === this) {
syncAttribute(this, m.attributeName);
} else {
hasChildrenChange = true;
}
}
if (hasChildrenChange) {
wrapper.slotChildren = Object.freeze(toVNodes(
wrapper.$createElement,
this.childNodes
));
}
});
observer.observe(this, {
childList: true,
subtree: true,
characterData: true,
attributes: true
});
}
get vueComponent () {
return this._wrapper.$refs.inner
}
connectedCallback () {
const wrapper = this._wrapper;
if (!wrapper._isMounted) {
// initialize attributes
const syncInitialAttributes = () => {
wrapper.props = getInitialProps(camelizedPropsList);
hyphenatedPropsList.forEach(key => {
syncAttribute(this, key);
});
hyphenatedMethodsList.forEach(key => {
syncAttribute(this, key);
});
};
if (isInitialized) {
syncInitialAttributes();
} else {
// async & unresolved
Component().then(resolved => {
if (resolved.__esModule || resolved[Symbol.toStringTag] === 'Module') {
resolved = resolved.default;
}
initialize(resolved);
syncInitialAttributes();
});
}
// initialize children
wrapper.slotChildren = Object.freeze(toVNodes(
wrapper.$createElement,
this.childNodes
));
wrapper.$mount();
this.shadowRoot.appendChild(wrapper.$el);
} else {
callHooks(this.vueComponent, 'activated');
}
}
disconnectedCallback () {
callHooks(this.vueComponent, 'deactivated');
}
}
if (!isAsync) {
initialize(Component);
}
return CustomElement
}
Please format your code properly:
https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks
Also, instead of showing me what piece of code you want to change, show me a usage example. Show me what your change allows you to do, that yoiu can't do today. Show me the problem it solves.
thanks!
An example is that I need to give logs to my component. To do this, I could change a logs property and add a watch on this property.
But I find it more interesting for the use of using a method.
Exemple :
<script src=« ./my-component.js »></script>
<my-component id="myComponent" theme="f"></my-componentt>
<script>
//Here, I change a property and my component reacts to this change
document.querySelector("#myComponent").logs = { cmis_url: "url", cmis_login: "login",
cmis_password: "password"
}
//But i will use a method like this
document.querySelector("#myComponent").setLogs({
cmis_url: "url",cmis_login: "login", cmis_password: "password"
})
</script>
Another example is that my component displays information from a database.
I want the web component to provide a way to refresh
like this:
document.querySelector('my-element').refresh ()
Thank you
Is there a way to bind a view-model variable to the web component properties so that they can be accessed by my application I think that would also help with this issue.
@ScottBreyer96 you probably know it already, but there is a workaround using the webComponent prop like:
document.querySelector('my-element').vueComponent.refresh();
But it would be nice to create those methods on the webComponent directly.
@LinusBorg I hope that the exposition of the vueComponent is good to stay... But it would be very nice to expose the methods directly outside like in this simple example eg. https://developers.google.com/web/fundamentals/web-components/customelements#extendcustomeel
Thank you in advance for your hard work!
I am sorry to add to this old issue, but I stumbled upon this as well when turning a Vue2-component into a WebComponent/CustomElement. For my WebComponent I want to be able to offer a JavaScript-API similar to what HTMLMediaElements like <audio>
offer, such as you can call play()
, seek(10)
, and other methods on the HTMLElement. As described by @stefanovualto there is a workaround in using the vueComponent
property to access the Vue-components methods, but I think it would be nicer to access them directly like it is possible on „normal“ WebComponents.
I already prepared a PR on my fork here https://github.com/wiffbi/vue-web-component-wrapper
I would be more than happy if you consider this a worthy contribution. I built the dist-files with rollup -c
, not sure why the diff in git especially on dist/vue-wc-wrapper.global.js is so big.