Drag and Drop Dynamically added component - angular 7 - javascript

I am adding component dynamic using viewRef and component factory resolver, I could achieve the dynamic creation. Now i need to add a drag and drop feature to the every component added individually
<button type="button" (click)="createComponent()">
Create Widget
</button>
<div cdkDropList (cdkDropListDropped)="drop($event)" [cdkDropListData]="components" >
<div cdkDrag>
<ng-container #container ></ng-container>
</div>
</div>
ts file for adding component
createComponent() {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(
DemoComponent
);
let componentRef: ComponentRef<
DemoComponent
> = this.container.createComponent(componentFactory);
let currentComponent = componentRef.instance;
currentComponent.selfRef = currentComponent;
currentComponent.type = ++this.index;
currentComponent.type1 = ++this.heleloIndex;
currentComponent.index = ++this.index;
// prividing parent Component reference to get access to parent class methods
currentComponent.compInteraction = this;
// add reference for newly created component
this.components.push(componentRef);
}
Code in Child Component
<h1>Alert {{type}}</h1>
<h2>Alert {{type1}}</h2>
<button (click)="change()">change Widget data
</button>
<button (click)="removeMe(index)">Remove Widget
</button>
<hr>
The drop method
public drop(event: CdkDragDrop<any[]>) {
console.log(event);
moveItemInArray(this.components, event.previousIndex, event.currentIndex);
console.log(event.container.data);
}
Now i can drag the component. But that is applying to whole div. I need to drag and drop individual blocks
NOTE: I am not using ngFor
[Example depiction][1]
[1]: https://i.stack.imgur.com/Vh1Q6.jpg

Related

Angular child route elements outside of router-outlet

I was wondering if there is a way to implement a child component element out of the <router-outlet>, like in the example, I wish each component inside of the outlet could show its own buttons on the parent component.
<div class="header">
<h1>Page Title</h1>
<div class="action-buttons">
<!-- child component buttons -->
</div>
</div>
<div id="wrapper">
<router-outlet></router-outlet>
</div>
I'm not sure if it's the best way but what I'm doing is moving the buttons from the child to the parent using Renderer2 every time the route changes.
this.router.navigate(["/"]).then(() => {
this.moveButtons();
});
private moveButtons() : void {
const parent = document.querySelector('.float-buttons');
const buttonsParent = document.getElementById('childButtons');
parent.childNodes.forEach(child => {
this.renderer.removeChild(parent,child);
});
if(!buttonsParent) return;
const children = buttonsParent.childNodes;
children.forEach(child => {
this.renderer.appendChild(parent, child);
});
}

Vuejs - Move header section of template to parent

I am trying to create a template with two parts
the tab (slot) -> could only get the slot to work with using a ref
The content (slot)
This component(tab) is wrapped in a component(tabs aka parent) that organizes the tabs based on certain props.
The overall goal is to create something like so:
https://getbootstrap.com/docs/4.0/components/navs/#tabs
Except with the ability to have custom tabs. For simplicity, I want to keep all the information relating to the tab within the tab component
1 - the header is not rendered in the component itself but pushed to the parent ***
2 - the tab component pushes the $ref to the parent and then the parent renders the HTML and listeners
How can i push(or another method to pass the information to the parent) data to the parent and keep all the listeners and js associated with the components in the tab slot
//tab component
<template>
<div>
<div class="tab" ref="tab">
<slot name="heading"> //-> Only available in setup context.slots if the default content is not used therefore resulted in using ref
//Default content
{{heading}} //-> if I add content to the heading slot via different components, the JS/listeners associated to those components do not work. I assume because I'm only pushing the HTML
</slot>
</div>
<div class="content ">
<slot/>
</div>
<div>
</template>
<script>
import {onMounted, ref} from '#nuxtjs/composition-api'
setup(props, {parent}){
const tab = ref()
onMounted(()=>{
let tab = {
data: tab.value //The entire ref
//data: tab.value.innerHTML => Works for passing the html but no listeners or js work
}
//parent has data.tabs = []
parent.data.tabs.push(tab)
})
return {
tab
}
},
props:{
....
}
</script>
//tab parent component (part to render tab via data.tabs)
<ul
>
<li
v-for="(child, index) in data.tabs"
class="s-tabs--li"
v-bind:key="index"
v-html="child.data"
></li>
</ul>
//Used in action
<s-tabs>
<s-tab heading="Home">
<div>
Home
</div>
</s-tab>
<s-tab heading="Service" icon="flower" tag="music">
<div>
Service
</div>
</s-tab>
</s-tabs>

Get a list of components given the component name regardless they are nested - Vue

I have the following sidebar in my app:
The tree item component code is :
<!-- tree item template -->
<script type="text/x-template" id="tree-item-template">
<div>
<div>
<user-card #toggle-supervisor="toggle" :user_info="item" :key="item.id_user"></user-card>
</div>
<div v-show="isOpen" v-if="isFolder" class="ml-3">
<tree-item
v-for="(child, index) in item.employees"
:key="index"
:item="child"
></tree-item>
</div>
</div>
</script>
This component contains another component called user-card inside it.
What i would like to get is an array with all of the components which name is 'user-card' regardless they are nested inside many other components.
The reason for this is because i want to change the color of the name to blue when a user is selected (clicked) and to black when the element is not selected
I found that using this.$children i can get the list of components inside a component, but not all of them, so i wonder if there is a way to get the list of all elements by the component name
You can use this simple piece of code:
const child = this.$children.slice();
const cards = [];
let cur;
while (child.length > 0)
{
cur = child.shift();
if (cur.$options.name === 'user-card') cards.push(cur);
else
{
cur = cur.$children.slice();
for (let i = 0; i < cur.length; i++) child.push(cur[i]);
}
}
// now you have your user cards inside the "cards" array

Click outside angular component [duplicate]

This question already has answers here:
Detect click outside Angular component
(12 answers)
Closed 7 months ago.
I know there are countless of questions around this, and I tried every solution but nothing suited my needs. Tried directives, tried it straight in my component but it didn't work a single time.
So my situation is like this; I have a tableview and at the end of every tableview you can open a small dropdown by clicking on an icon. The dropdown that pops up is a small component. So in my parent component it looks like this;
<tbody>
<tr *ngFor="let rows of subscriptionTable.data; let i = index;">
<td *ngFor="let cell of subscriptionTable.data[i].data">
<!-- Table actions -->
<div *ngIf="cell.actions && cell.actions.length > 0">
<app-dropdown
[actions] = cell.actions
(onActionClicked) = "handleSubscriptionAction($event, i)"
>
</app-dropdown>
</div>
</td>
</tr>
</tbody>
So I'm rendering multiple child app-dropdown components that look like this;
<div class="fs-action">
<div class="fs-action__dots" (click)="openActionSheet($event)">
<span class="fs-action__dot"></span>
<span class="fs-action__dot"></span>
<span class="fs-action__dot"></span>
</div>
<ul class="fs-action__list">
<li
class="fs-action__item"
*ngFor="let action of actions; let i = index;"
(click)="actionClicked( $event, i )"
[ngClass]="{'fs-action__item--danger': action.type === 'destructive' }"
>
{{ action.label }}
</li>
</ul>
</div>
What I want to do now is, that as soon as I click outside the fs-action element, the dropdown dismisses. I would like to do that inside the app-dropdown component, then everything related to this component is in the same place.
But as soon as I attach a directive or anything else, the hostlistener get's added for every row. Every outside click is then being triggered multiple times. Checking if I was clicking outside the action element then becomes a burden, because it then renders false for all the other hostlisteners and true for the one that's active.
The issue is illustrated inside this stackblitz; https://stackblitz.com/edit/angular-xjdmg4
I commented the section which causes issues inside dropdown.component.ts;
public onClickOutside( event ) {
// It executes 3 times, for each of the items that is added
// Even when clicking inside the dropdown, it calls this function
console.log("click")
}
Just use the (click) event binding, pass a function from the component’s instance and you are done.
Solution:
Declare Directive ClickOutside like below:
#Directive({
selector: '[clickOutside]',
})
export class ClickOutsideDirective {
#Output('onClickOutside') onClickOutside = new EventEmitter<MouseEvent>();
constructor(private _eref: ElementRef) {}
#HostListener('document:click', ['$event', '$event.target'])
onDocumentClicked(event: MouseEvent, targetElement: HTMLElement) {
if (targetElement && document.body.contains(targetElement) && !this._eref.nativeElement.contains(targetElement)) {
this.onClickOutside.emit(event);
}
}
}
}
<div clickOutside (onClickOutside)="onClickOutside()">
...
</div>
And there is directive in an npm module called ng-click-outside.
Installation: npm install --save ng-click-outside
Read full documentation in link below:
ng-click-outside directive
I used a directive to a similar situation.
To install:
npm install --save ng-click-outside
Then import in your module
import { ClickOutsideModule } from 'ng-click-outside';
#NgModule({
declarations: [AppComponent],
imports: [BrowserModule, ClickOutsideModule],
bootstrap: [AppComponent]
})
class AppModule {}
In your component
#Component({
selector: 'app',
template: `
<div (clickOutside)="onClickedOutside($event)">Click outside this</div>
`
})
export class AppComponent {
onClickedOutside(e: Event) {
console.log('Clicked outside:', e);
}
}
It has some options. You can see it in https://www.npmjs.com/package/ng-click-outside
Regards

event.stopPropagation() fails on ReactJS Component inside a native HTMLElement

My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';

Categories