Vue.js: Communicate with components that are down the DOM - javascript

I'm just trying out Vue.js and I'm struggling with the key concept of passing information up and down the DOM from one component to the other.
Consider this example in which a container-toggle button should toggle all components within the container, or say, set them all to "true" or "false".
<div id="app">
<p>
<strong>Welcome!</strong>
Click the "true/false" buttons to toggle them.
Click the "Toggle all" button to toggle them all!
</p>
<app-toggle-container>
<app-toggle></app-toggle>
<app-toggle></app-toggle>
</app-toggle-container>
<app-toggle-container>
<app-toggle></app-toggle>
<app-toggle></app-toggle>
<app-toggle></app-toggle>
</app-toggle-container>
</div>
In this code pen, I've defined app-toggle and app-toggle-container as components: https://codepen.io/fiedl/pen/mmqLMN?editors=1010
But I can't find a good way to pass the information down from the container to the separate toggles.
Also, in a second step, when trying the other way round, for example, to have the "Toggle all" button just show "true" if all toggles are true, or to show "false" when all toggles are false, I can't find a way to pass the information of the current state of the toggles up to the container.
This doesn't seem like an uncommon problem. What is the proper way to do this in Vue.js? Or am I thinking about this it in the wrong way?
Quickly, I've found $broadcast and $dispatch. But as they are dropped in Vue.js 2, I'm most probably thinking about it in the wrong way :)

I forked your pen http://codepen.io/nicooga/pen/wdPXvJ.
Turns out theres a $children property for Vue components that contains your children components [controllers]. You can iterate over them and do stuff with them.
this.$children.forEach(c => c.toggle());
See
https://v2.vuejs.org/v2/api/#vm-children
VueJs Calling method in Child components.

Related

How to emit a custom event from meta-data in Vue.js

I am trying to emit a custom event from a very rare situation.
I have a tab component where every slot inside has a name with 'row' and 'col' and each one corrisponds to <td>content here</td>, i must check inside the component if the slot is empty.
I am trying to emit a custom event 'no-content' only when the slot is actually empty.
My example
<my-component :meta-data="!$slots['td-'+row+'-'+col] ? $emit('no-content', 'td-'+row+'-'+col) : ''"
<slot :name="'td-'+row+'-'+col">
</slot>
</my-component>
is it actually possible to achieve this result with Vue.js? i tried myself and also searched for a case similar to mine but i found nothing.
PS: I actually spent a little more time trying to make it work but i had a problem with the component itself, so i started all over again but insted of using slots I work with an array of objects in input, now i reached the result I wanted to.
I'm gonna leave this here just in case someone else thinks about a solution with meta-data.

How to open a modal component through an onClick from another component

This question is related to this one. I am rewording to make it simple.
I have a main navigation component (MainNavigation.js) which is siblings with a Secondary Navigation Component(SecNavigation.js) under the Header component(Header.js).
What I want is when I click on a specific Link in the MainNavigation.js the whole SecNavigation.js to be displayed in a modal.
My problem here is that I don't quite grasp how should I go using useState, useContext or even if I need any of this.
Hope this will clear something for you
What you need is a parent that handles the state and then it will pass it down to its children. Because the parent is keeping track of the children and what they are doing.
So what I have done it this:
App.js is the parent and I handle the state here.
Then I just pass down the setIsModalShowing setState function to the MainNav that will call it when I press a button.
Then in the App.js I will show or hide the modal if the setIsModalShowing is true or false
inside of the modal I am showing SecNav and the modal also has the setIsModalShowing passed down so that you can click on the button to close is and that will set the state to false.
Does that makes sense to you ? or else I can try to explain it in another way
Code example

Angular Directive not working with *ngIf. Calling directive from component

I have two DIVs - one, that is always visible, and another one, that gets displayed after I click a button (it is toggable):
<div>
<div>
<small>ADDRESS</small>
<p [appQueryHighlight]="query">{{ address}}</p>
</div>
</div>
<div *ngIf="showDetails">
<div>
<small>NAME</small>
<p [appQueryHighlight]="query">{{ name}}</p>
</div>
</div>
My custom directive works well on the first DIV (it changes the color of letters inside the paragraph that match the query entered in an "input"), but does not work on the second DIV.
Here is an example of how "Test address" looks like when query is "addr":
Test addresswhere bold text is for example red-colored text.
But when I have name John, and query is "Joh", it should also be colored once shown with the button, but it is not.
As I understand, I need to re-run the directive after I click and toggle the second div. Also, probably I have to do this with delay, so it has time to be shown. How can I do this?
UPDATE
Problem with my directive is related to *ngFor - both DIV are located withing ngFor. I have to call directive ngOnChanges or ngOnInit only after the new list get propagated based on the query. I have no idea how to do this, and currently, directives get loaded before ngFor fully loads - this cause problems.
Here is my previous answer:
I finally managed to solve this problem, thanks to #iHazCode.
So firstly, this is probably a duplication of problem described here: Angular 4 call directive method from component
Because my directive hightlights the specific letters in paragraph based on input query, each time the query changes, the directive should fire, thus I am using ngOnChanges withing the directive.
This is not working with *ngIf, because when ngOnChanges fires, the second div and paragraph is not visible.
As a workaround, we can access the directive from out component using #ViewChildren:
#ViewChildren(QueryHighlightDirective) dirs;
And then call it's ngOnChanges method, once div is toggled. In my case, the problem was related with last occurence of the directive within the component:
toggleDetails() {
...
this.dirs.last.ngOnChanges();
}
That was exactly what I was looking for, I hope it will help someone.
In my case I also encountered another problem - the defined DIVs are within *ngFor, which is also propagated based on the query, so I have to make sure that the list will get propagated before calling ngOnChanges. I haven't find a solution for this yet.
ngIf does not show/hide elements. ngIf determines if the element exists on the DOM. You can verify this by looking at the source of your rendered page. Change the code to use ngHide/Show instead.

Using (click) event in one html template to control [hidden] element in a separate template Angular 2

I have 2 TypeScript files, both with HTML templates in the #Component. Let's say one template displays info cards that can be collapsed or expanded with [hidden]="collapsed". This function exists in either file:
public collapsed : boolean = false;
toggleCollapsed(){
this.collapsed = !this.collapsed;
}
Then in a separate template I have a button to trigger this event
<button clear (click)="toggleCollapsed()">
<ion-icon name="expand"></ion-icon>
Expand All
</button>
Basically the button and info cards are in different templates/files but displayed on the same page in the view. I need help with making the button expand/collapse the info cards even though they're in separate templates.
I figured this topic is general enough to help others as well if answered :)
Just short sample:
<panel #panel>
<button (click)="panel.collapsed=!panel.collapsed">
Create component reference #panel and use it to change state.
It depends on the relationship (if any) between the two components. If there is a parent-child relationship, you can use an input property or an event (depending on which direction the communication needs to flow).
If there is no relationship, use a service with a Subject. One component will subscribe() to the Subject in order to listen for events, and the other will generate events via the Subject.
The Component Interaction Cookbook has examples of all three.

How to hide/show adf component automatically

Hi I want to hide an adf component automatically.I have a selectOneChoice that contain some number (from 1 to 12).
Example when I select 2, it show's two field automatically without clicking any button..
i used this function to hide a declared componenet but just when i click a button..
function enableField(actionEvent) {
var nameInputText = actionEvent.getSource().findComponent("soc1");
nameInputText.setProperty("visible", true);
actionEvent.cancel();
}
i set the componement "soc1" visible = true than through the javascript function i change it..
SO the probleme here is how to read the number from the selectonechoise and how to set the component visible directly without clicking any button.
Actually Rendered won't do what you want. You want to use Visible property instead. Rendered causes the actual markup for the component not to be rendered on the page, so a Partial Refresh will not cause it to appear. Rendered is reserved, usually, to hide items that are secure. We set rendered property to false on the item(s), but then refresh the parent containing component - usually a layout manager - then it works. So either refresh the layout manager that contains the item or use Visible. I demonstrated this exact use case last week in class and it works as described.
Basicaly, you don't need javascript solution or any programming to achieve this.
You should set attributes rendered(this way component won't be rendered at the page at all) and partialTriggers that points to selectOneChoice component for components you want to show or hide. Also you have to set autoSubmit="true" for your selectOneChoice component.
<af:selectOneChoice id="soc1" autoSubmit="true" .../>
<af:panelGroupLayout id="plg1" partialTriggers="soc1">
<af:outputText id="ot1" rendered="#{bindings.lov.inputValue le 1}" inputValue="text1"/>
</af:panelGroupLayout>
Note: its not working code, just a sample
It will work following way, on valueChange event at selectOneChoice component value is submitted and partialRefresh fires for components that have it in partialTriggers specified. And rendered attribute will either render or remove component depending on it's EL expression. Components that should be managed wrapped into another component to make sure PPR reach it when they ain't rendered.

Categories