Suppose I have a custom element in which I iterate over an array of data and render custom subelements.
<my-list action-handler.bind="actionHandler">
<my-element item.bind="element" repeat.for="element of data"></my-element>
</my-list>
In my-list.html I render the contents with <contents></contents> and in general the real code is a bit more complex and has replaceable template parts, but that's not the problem.
I want the actionHandler that I bind to MyList to be available in all of the children. For the sake of elegancy, we did this in my-list.js:
#children('*:not(div)') listItems;
//...
attached(){
this.listItems.forEach((e) => {
e.actionHandler = this.actionHandler;
});
}
And everything worked just fine until we started to load data dynamically. In this case listItems is empty since the element is initialized before the child elements re-rendered.
So, the question is:
How would I make #children be re-calculated?
I understand that we could bind the actionHandler to all children when we were repeating over them, but this would start to look really ugly when we add some more logic like
Thanks!
If <my-element> has a dependency on it's parent <my-list> element's actionHandler, you can express that declaratively and let Aurelia's dependency injection system do the rest. Here's a couple of ways to do that...
If <my-element> will always be within a <my-list> (doesn't necessarily need to be directly within):
import {inject} from 'aurelia-dependency-injection';
#inject(MyList)
export class MyElement {
constructor(list) {
this.actionHandler = list.actionHandler;
}
}
If you need access <my-element>'s closest parent custom element, regardless of what it is:
import {inject, Parent} from 'aurelia-dependency-injection';
#inject(Parent.of(Element))
export class MyElement {
constructor(parent) {
this.actionHandler = parent.actionHandler;
}
}
I know this wasn't exactly what you were asking but I'm proposing this in case it's a simpler way to meet your requirements.
The Aurelia Cheat Sheet is telling that:
#children(selector) - Decorates a property to create an array on your class that has its items automatically synchronized based on a query selector against the element's immediate child content.
so i guess it is not possible to use #children with dynamic data.
You might should go with the Parent approach, like the previous answer is suggesting.
Related
I have a page which is being generated by my app in java (SSR) depending on some data (e.g. a publisher for some entity). I would like to create some sort of a reusable Vue component that would call an API method and request some data about the entity that is currently opened. Also in some cases there could be more than one such component on one page.
The only thing I cannot really figure out being a most-of-the-time backend developer - is how to tell a component which entity I'm trying to get. The only solution that comes to my mind is to generate the parent <div class="my-vue-component"><div> with an additional attribute, e.g. <div class="my-vue-component" publisher-id="123"><div>.
But I cannot find if there is a way to access that attribute from inside the Vue instance. (Please note that I don't have a fixed id for this div as there can be many such components on the same page referring to different data).
Any kind of advice is appreciated.
As stated in the previous answer, you will need to use props. Although since you will pass down data to multiple components and the data can change, there should be a way to respond to those changes.
For that, you will have to bind the prop with a reactive variable in your page/parent component.
So your SSR code should look like
<my-vue-component :publisher-id="openId"></blog-post>
And inside your page/parent component will reside the openId, which you can change as needed, and your component will re-render if prop passed to it changes.
export default {
data(){
return {
openId:1
}
}
}
It seems like you are looking for components-props.
You can define a prop like
Vue.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
Your SSR code should then be generating:
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>
Inside the component methods you can access the passed in value using this.postTitle
I'm working on a Vue application.
It has a header and then the main content.
Nesting and structure as below
TheHeader.vue -> TheLogin.vue
MainContent.vue -> ShoppingCart.vue -> OrderSummary.vue
I need to access an element in TheLogin.vue from OrderSummary.vue
this.$refs.loginPopover.$emit('open')
gives me an error "Cannot read property '$emit' of undefined" so obviously I am not able to access $refs from other components.
The question is how do I get hold of refs from other components?
Thanks in advance!
Edit 1 - Found out $refs works with only child components.
How do I access elements across components in different level?
You definitely don't want to be reaching through the hierarchy like that. You are breaking encapsulation. You want a global event bus.
And here's a secret: there's one built in, called $root. Have your OrderSummary do
this.$root.emit('openPopup');
and set up a listener in your TheLogin's created hook:
this.$root.on('openPopup', () => this.$emit('open'));
In general, you should try to avoid using refs.
For anyone who comes here later and wants to access $refs in parent component, not in this particular case for emitting events since event bus or a store would suffice but let's just say you want to access some element in parent to get it's attributes like clientHeight, classList etc. then you could access them like:
this.$parent.$parent.$refs //you can traverse through multiple levels like this to access $ref property at the required level
You can put a function like this on your component to do this. I put mine in a Mixin:
public findRefByName(refName) {
let obj = this
while (obj) {
if (obj.$refs[refName]) {
return obj.$refs[refName]
}
obj = obj.$parent
}
return undefined
}
I also added some accessors to help:
get mycomponent() {
return this.findRefByName('mycomponent')
}
And once that exists, you can access your component by simply doing:
this.mycomponent
Thanks for that tip Abdullah!
In my case I was looking for a sibling, so in case someone comes looking for that, here's an example:
var RefName='MyCoolReferenceName';
var MyRef,x;
for(x=0;x<this.$parent.$children.length;x++)
{
if(typeof this.$parent.$children[x].$refs[RefName] !='undefined')
MyRef=this.$parent.$children[x].$refs['LABEL_'+bGroupReady.ChildID];
}
if(typeof MyRef !='undefined')
MyRef.error=true;
PS - The reason I'm doing MyRef.error=true is because I was having ZERO luck with Quasar inputs and lazy-rules="ondemand". Turns out you can just set .error=true to activate the error message and the red highlighting and .clearValidation() event to clear it back out. In case someone is trying to do that as well!
When using HyperHTMLElement it's possible to access the contents of the component by simply using this.children or this.querySelector(), since it's an element.
But how would I achieve similar behavior when using hyper.Component?
The hypothetical example I have in mind is from React docs: https://facebook.github.io/react/docs/refs-and-the-dom.html - I'd like to focus a specific node inside my DOM.
I have a codepen sandbox where I'm trying to solve this: https://codepen.io/asapach/pen/oGvdBd?editors=0010
The idea is that render() returns the same Node every time, so I could save it before returning and access it later as this.node:
render() {
this.node = this.html`
<div>
<input type="text" />
<input type="button" value="Focus the text input" onclick=${this} />
</div>
`;
return this.node;
}
But that doesn't look clean to me. Is there a better way to do this?
The handleEvent pattern is there to help you. The idea behind that pattern is that you never need to retain DOM references when the behavior is event-driven, 'cause you can always retrieve nodes via event.currentTarget, always pointing at the element that had the listener attached, or event.target, suitable for clicks happened in other places too within a generic click handler attached to the wrap element, in your demo case the div one.
If you'd like to use these information, you can enrich your components using an attribute to recognize them, like a data-is="custom-text-input" on the root element could be, and reach it to do any other thing you need.
onclick(e) {
var node = e.target.closest('[data-is=custom-text-input]');
node.querySelector('[type=text]').focus();
}
You can see a working example in a fork of your code pen:
https://codepen.io/WebReflection/pen/RLNyjy?editors=0010
As alternative, you could render your component and address its content once as shown in this other fork:
https://codepen.io/WebReflection/pen/LzEmgO?editors=0010
constructor() {
super().node = this.render();
}
at the end of the day, if you are not using custom elements but just basic, good'ol DOM nodes, you can initialize / render them whenever you want, you don't need to wait for any upgrade mechanism.
What is both nice and hopefully secure here, is that there's no way, unless you explicitly expose it, to address/change/mutate the instance related to the DOM element.
I hope these possibilities answered your question.
This is something I've worked on in the past via https://github.com/joshgillies/hypercomponent
The implementation is actually quite trivial.
class ElementalComponent extends hyper.Component {
constructor () {
super()
const _html = super.html
this.html = (...args) => {
this.node = _html.apply(this, args)
return this.node
}
}
}
class HelloWorld extends ElementalComponent {
render () {
return this.html`<div>Hello World!</div>`
}
}
This works really well and is inline with your question. However, it's worth noting hyperHTML can render not only a single node but also multiple nodes. As an example:
hyper`<div>Hello World!</div>` // returns a single DOM Node
hyper`<div>Hello</div> <div>World!</div>` // returns multiple DOM Nodes as an Array.
So this.node in the above ElementalComponent can be either a DOM Node, or Array based on what the renderer is doing.
I'm executing the following method when the user clicks a button.
#ViewChild("privs") privs: ElementRef;
addPrivs() {
this.privs.nativeElement
.insertAdjacentHTML('beforeend', '<generic1>yey!</generic1>');
}
My markup looks like this.
<generic1>woosh</generic1>
<div #privs></div>
The subcomponent named generic1 is declared like this and, of course, present in imports of the module.
import { Component } from "#angular/core";
#Component({
selector: "generic1",
template: "<div>mamba...</div>"
})
export class Generic1 { }
The behavior I'm getting is that the statistically created one in the markup shows as supposed to. The dynamically appended don't. According to the DOM as I investigate, a tag generic1 is added but it's not rendered by Angular (I see the text yey! and the tag but not the rendition of the component).
What am I missing?
I've googled for examples but didn't see anything specifically wrong with my set up. Must be something outside of my scope...
You are not actually creating a dynamic component. You are inserting additional HTML that is not rendered by Angular. Html and text are sanitized when inserting them into a template like that and thus you won't get a rendered component.
This answer has a very detailed explanation of setting up dynamic components. However, it is not a copy and paste solution for what you need. The documentation on Angular.io has a good example of a dynamic component loader. Essentially, you will need a ComponentFactoryResolver to resolve and build an Angular component. In the example from the Angular.io docs, the real magic happens in this function:
loadComponent() {
this.currentAddIndex = (this.currentAddIndex + 1) % this.ads.length;
let adItem = this.ads[this.currentAddIndex];
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(adItem.component);
let viewContainerRef = this.adHost.viewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent(componentFactory);
(<AdComponent>componentRef.instance).data = adItem.data;
}
It retrieves a component from a list of stored components in this.ads, uses a componentFactoryResolver to resolve and build the component. It retrieves the viewContainerRef and creates the component on it.
Hope that helps point you in the right direction.
I'd like to know if there is a way to get a component by using some type of id, or by type, similar as you would do in DOM manipulation. Something like:
var Avatar = React.createClass({
render: function () {
...
}
});
React.renderComponent(Avatar({id:'avatar'}), ...);
...
...
var avatar = React.getComponentById('avatar');
avatar.setProps({url = 'http://...'});
// or
var avatars = React.getComponentByType('Avatar');
if (avatars.length) {
avatars[0].setProps({url = 'http://...'});
}
I don't want to keep references of components instances...
setProps is something that you should use sparingly. In fact storing references to "rendered" components in general might indicate that you can structure your code differently. We also limit your uses of setProps to top level components.
var avatar = React.renderComponent(<Avatar .../>, node);
avatar.setProps({ foo: '1' });
is equivalent to this, which fits in a bit better with the declarative model:
React.renderComponent(<Avatar .../>, node);
React.renderComponent(<Avatar ... foo="1" />, node);
You could wrap that render up inside a function call so you could call it at will.
Sorry, there's no (publicly exposed) global registry of mounted React components. If you need to send messages to a component after mounting it, the best way is to save a reference to it. Iterating through the list of all Avatar components seems like the wrong solution to me anyway because it wrecks the composability aspect of components, where each parent component can specify its child's props and trust that outside forces won't change them -- changing this makes your page harder to reason about.
If you provide a jsfiddle of what you're trying to do, perhaps I can be of more help.