Make a copy of DOM element then add animation - javascript

I read many articles and documentations about this but none of them seem to take the approach I'm trying to make.
I have an array of category Items and inside it I have a nested array of said category products.
like so.
<ng-container *ngFor="let menu of menuList">
<ng-container *ngFor="let item of menu.catItem" >
<div class="card" #cmp >
just to demonstrate
<button class="animateIt(cmp)"> click to animate </button>
</div>
</ng-container>
</ng-container>
when the user click the button I would like to add some kind of animation to, I can do that without any problem but the issue is I cannot think of a way to this without using any extra JavaScript libraries.
I have tried adding ViewChildren to my component. And a function that print the element
#ViewChildren('cmp') components: QueryList<ElementRef>;
constructor(private renderer: Renderer2){}
animateIt(cmp:ElementRef){
console.log(cmp);
this.renderer.setAttribute(cmp.nativeElement, 'class', 'myClass');
}
second line inside the function gives an error since cmp.nativeElement is not defined which is expected since all it does is to grab the content of the div and just print it without making an ElementRef object.
Is there any way I can achieve this using just angular or just JS?

I think you need to retrieve your element in ngAfterViewInit lifecycle, like below:
ngAfterViewInit() {
this.cmp.changes.subscribe(() => {
console.log('changed');
})
}

did it by using plain JS
here is the code
animateIt(cmp:ElementRef){
this.el.nativeElement = cmp;
let element = document.createElement('div');
element = this.el.nativeElement.cloneNode(true);
element.style.position = "absolute";
element.style.top = this.el.nativeElement.offsetTop + 'px';
element.style.left = this.el.nativeElement.offsetLeft + 'px';
let parent = document.getElementById("parent");
parent.appendChild(element);
}

Related

Angular - Most efficient/elegant way to access component/dom element inside a changing ngb-accordion panel

I have a ngb-accordion where the panels are generated through a for loop. I want to access a specific component/dom element inside the opening/closing panel and hide this element when the panel gets opened and show it when the panel collapses again.
I thought about using the panelChangeEvent because it gets called when the panel opens and collapses. I am now looking for the most elegant way to get the in the current panel. My first thought was using a ViewChildren QueryList for the NgbPanels, iterate through the list and get the changing panel.
How do I get the changing panel? I wanted to compare the panelID of the NgbPanelChange event to the id of the panel in the QueryList. Is there a way to get the id property? Because panel.id returns undefined (id is only an input in the NgbPanel component,correct?)
Afterwards get the instance inside the changing panel. But how do I get the reference to this specific instance? Am I thinking way too complicated here? I am kind of confused by the amount of possibilities. Appreciate any help.
Template
<ngb-accordion #acc="ngbAccordion" (panelChange)="beforeAccordionPanelChange($event)">...
<ngp-panel #accPanel [id]="panel-' + i" *ngFor="let panel of panels; let i= index" >
....
<target-component #target></target-component>
....
</ngp-panel>
</ngb-accordion>
TS
// Hide target component when panel is active
public beforeAccordionPanelChange($event) {
let changedPanelId = $event.panelId;
this.accordionPanelList.toArray().forEach(accordionPanel => {
if ( accordionPanel.id === changedPanelId ) {
// get target-component and call function toggleDisplay() which hides/shows the instance
}
});
}

HTML Update the DIV content in Angular

I have a DIV inside my HTML page like this in my angular application,
<div id="graph" *ngIf="newRequired == 0"></div>
I want to load a graph inside the container, my Container code is like this
Element createContainer() {
var e = new DivElement()
..style.height = '300px'
..style.maxWidth = '70%'
..style.marginBottom = '50px';
document.body.append(e);
return e;
}
This working fine... the problem I am facing since the element is dynamic one.. so the graph loads at the bottom of the page.
Instead I want to load the graph inside the DIV id="graph" (already inside the HTML page).
I feel to do this we have to change the code here document.body.append(e)... can anyone help me how to bring the graph since the DIV id="graph"
If you're trying to add code inside your div, replace document.body.append(e) with document.getElementById('graph').innerHTML = e.
Alternatively since it looks like you're using jQuery you can try $('#graph').append(e).
DOM manipulation - especially injection is completely incoherent with what Angular is trying to achieve with its framework. In the last couple years I've spent working with Angular, I've not had a single occasion where I have needed to do something like: document.body.append(e);.
What you should be doing instead is using property binding in html.
So...
Element createContainer() {
var e = new DivElement()
..style.height = '300px'
..style.maxWidth = '70%'
..style.marginBottom = '50px';
document.body.append(e);
return e;
}
Will transform into:
In component:
export class ExampleComponent implements OnInit {
public height: number;
public maxWidth: number;
public marginBottom: number;
...
}
In html:
<div id="graph" *ngIf="newRequired == 0">
<div [ngStyle]="{'height': height, 'max-width': maxWidth, 'margin-bottom': marginBottom}"></div>
</div>
With this approach if you need to change your height for whatever reason, all you have to do is change the properties in the component and the html will update. If you go with your original approach of document.body.append(e) you would have to manually update these values.

Vue add class to element without v-model:class?

I have two elements, that are in two completely different parts of the DOM. And I'm trying to connect the two, so when you hover one element, the other one shows an effect.
The first element is in a component and is being displayed with code like this inside a component:
<button #mouseover="$emit( 'event-hovered', event.id )" #mouseout="$emit( 'event-not-hovered' )">
Button text
</button>
The second element is being generated using code from an AmCharts-demo:
createCustomMarker: function (image) {
var element = document.createElement('button');
element.className = 'map-marker the-id-' + image.id;
element.title = image.title;
element.style.position = 'absolute';
element.dataset.target = "#id" + image.id;
element.setAttribute('data-toggle', 'modal');
image.chart.chartDiv.appendChild(element);
return element;
}
As can be seen, then I've made a #mouseover that emits the id back to he main instance. In the main instance, then I have a method that finds the second element. But I do that using regular javascript, since I've had issues rewriting that createCustomMarker-function, so both Vue and AmCharts grasps what's going on. But this means that I can't add a class to the second elements (generated by createCustomMarker), the conventional v-model:class-way. I tried doing this in a method in the main instance:
eventHovered: function( elementId ){
let markerWithId = document.getElementById( 'id' + elementId );
markerWithId.classList.add("event-hovered");
console.log( markerWithId );
}
When I console.log the markerWithId, then I can see that that has the added class (event-hovered). But that doesn't appear on the screen, and I assume that's because Vue is controlling the DOM.
So how do I submit the element back to the DOM?
And is this a stupid way of doing this?
Try calling the validateNow() function after you edit the class on the marker.
As seen here:
https://docs.amcharts.com/3/javascriptcharts/AmCoordinateChart
From the Docs:
This method should be called after you changed one or more properties of any class. The chart will redraw after this method is called.Both attributes, validateData and skipEvents are optional (false by default).

Angular 2 - Dynamically create/inject component into specific element inside *ngFor loop

I am looping through a list of metrics and inside each loop there is a button that would be clicked to render a graph at a specific div within the HTML of the loop. The graph is a separate component, however I cannot seem to figure out how to target the correct HTML element to load the graph component into. I started by creating a component rendering function and call it like this:
loadMetricGraph(metric) {
let selector = "[id=metricGraph-" + metric.id + "]";
this.renderComponent(LineGraphHorizontalComponent, selector, metric.data);
}
renderComponent(componentClass, selector, data) {
return this.compiler
.compileComponentAsync(componentClass)
.then((factory:ComponentFactory<any>) => {
let cmpRef:ComponentRef<any> =
factory.create(this.injector, null, selector);
cmpRef.instance.inputData = data;
(<any>this.appRef)._loadComponent(cmpRef);
return cmpRef;
});
}
In the loop I have a div that would be the target for the loaded component, however I am stuck on how to pass the correct selector to the renderComponent() function. I have attempted to use
id="graphData-{{ metric.id }}" And then a selector of "[id=graphData-" + metric.id + "]". I know this is not the correct way to do this, but I am not sure how to proceed.
You can't dynamically add HTML and get components or directives created for this HTML. If you want to dynamically add components then you need to use ViewContainerRef.createComonent.
See Angular 2 dynamic tabs with user-click chosen components shows how to do this declaratively using a helper component.

multiple iron-collapse not working, expands only first

I'm trying to create multiple iron-collapse elements with content inside. I want the iron-collapse to expand when user clicks on a button. The problem is that I can't get each element expanded individually. The script catches only first element and does not affect the others. I've tried many code samples but without success. Can someone help me? My code is below:
var expandContent = document.getElementById('mybutton');
expandContent.onclick = function(event) {
var moreInfo = document.getElementById('moreinfo');
var iconButton = Polymer.dom(event).localTarget;
iconButton.icon = moreInfo.opened ? 'hardware:keyboard-arrow-down'
: 'hardware:keyboard-arrow-up';
/*moreInfo.toggle();*/ /* This one works to, don't know which is better */
event.currentTarget.parentElement.querySelector('iron-collapse').toggle();
};
<paper-card style="width:100%;line-height:56px;font-size:16px;font-weight:400;">
<paper-icon-button class="mybutton" id="mybutton" icon="hardware:keyboard-arrow-up" style="float:right;margin:8px;" >
</paper-icon-button>
<iron-icon icon="communication:forum"></iron-icon>
<iron-collapse id="moreinfo" class="moreinfo" style="width:100%;">
<paper-item>
<iron-icon icon="info"></iron-icon>
<paper-item-body two-line>
<div>Primary text</div>
<div secondary>Secondary text</div>
</paper-item-body>
</paper-item>
<paper-item>Second item</paper-item>
<paper-item>Third item</paper-item>
</iron-collapse>
</paper-card>
I want only one collapse to expand after it's corresponding button is being pressed. Is there way to change this code to achieve what I need, without complete rewrite, because only with this code iron-collapse works properly and changes its attribute expanded="yes/no", which I later use with cookies?
The problem is here:
var expandContent = document.getElementById('mybutton');
Why are you looking for "mybutton" on entire document? You don´t need to do that because Polymer´s encapsulate each component so you can use it in multiples situations.
As documentation say´s:
Polymer automatically builds a map of statically created instance nodes in its local DOM, to provide convenient access to frequently used nodes without the need to query for them manually. Any node specified in the element’s template with an id is stored on the this.$ hash by id.
So, you need to change document.getElementById('mybutton') to this.$.mybutton to refer the local dom button. On this way, should work.
Edit
Using your code and not doing it as it should in Polymer, maybe this will help you:
var idArray =["mybutton","mybutton2","mybutton3"];
idArray.forEach(function(item,index,array){
var expandContent = document.getElementById(item);
expandContent.onclick = function(event) {
var moreInfo = document.getElementById('moreinfo');
var iconButton = Polymer.dom(event).localTarget;
iconButton.icon = moreInfo.opened ? 'hardware:keyboard-arrow-down'
: 'hardware:keyboard-arrow-up';
/*moreInfo.toggle();*/ /* This one works to, don't know which is better */
event.currentTarget.parentElement.querySelector('iron-collapse').toggle();
};
}.bind(this));
Update
Ok, i found proper solution after reading some forums and js docs, i will write it down, maybe someone needs it too.
var elems = document.getElementsByClassName('mybutton'); // Getting all button with same class name
for(var i = 0; i < elems.length; i++) { // Adding count indexes to buttons starting from first is 0
elems[i].onclick = function(event){ //And here is function to do expand/collapse, it can be any other function too, main idea was to get work/trigger all items with same class
var expandContent = event.currentTarget.parentElement.querySelector('iron-collapse');
var iButton = Polymer.dom(event).localTarget;
iButton.icon = expandContent.opened ? 'hardware:keyboard-arrow-down' : 'hardware:keyboard-arrow-up';
event.currentTarget.parentElement.querySelector('iron-collapse').toggle();
console.log("Works ! Great !!!");
}
}

Categories

Resources