Stimulus Framework - connect() not work when change with javascript - javascript

I create a stimulus controller and use on a div element.
Now when change innerHTML of this div element with click on a button, text change but color not change to red and connect() not work.
<div data-controller="test">
<span>This text color change to red</span>
<button type="button" data-action="test#changeColor">click</button>
</div>
test_controller.js
...
connect() {
this.element.style.color = "red";
}
changeColor(event) {
this.element.innerHTML = "<span>HelloWorld!</span>"
}
...

Two issues
You need to target the span, as this.element will be the element with the data-controller on it. See https://stimulus.hotwired.dev/reference/targets
You will need to set the target's color in the connect method AND the controller's changeColor method for it to change when the element connects (DOM loads) and when the button is clicked. See https://stimulus.hotwired.dev/reference/lifecycle-callbacks
import { Controller } from '#hotwired/stimulus';
export default class extends Controller {
static targets = ['label'];
connect() {
this.changeColor();
}
changeColor() {
const element = this.labelTarget;
element.innerText = 'HelloWorld!';
element.style.color = 'red';
}
}
<div data-controller="test">
<span data-test-target="label">This text color change to red</span>
<button type="button" data-action="test#changeColor">click</button>
</div>

Related

How to click on an element that is in the body pragmatically from inside of an angular component?

I have a website on which I have crisp chat service running and I'm trying to click on the chat box from one of my component's typescript files.
The crisp chat box is a div with the id of crisp-client inside body tag. How do I click on it from inside my typescript?
Inject Renderer2 in constructor -
constructor(private renderer: Renderer2) {}
Get the crisp-client element in your component -
let crispClient = this.renderer.selectRootElement('#crisp-client');
Perform click() event -
crispClient.click();
Working demo here
Added a test method on div to check -
<div id="crisp-client" onClick="console.log('crisp client clicked')"></div>
You can use the ViewChild decorator and pass a template reference variable as a string, and manipulate the event. Here's a simple example of clicking Button 1, which programmatically clicks Button 2 and due to that click, the text of Button 2 is changed:
<button #button1 (click)="clickButton2()">
Click to change the text of Button 2
</button>
<button #button2 (click)="changeText()">{{ buttonText }}</button>
buttonText = 'Button 2';
#ViewChild('button2') button2: ElementRef<HTMLElement>;
clickButton2() {
let el: HTMLElement = this.button2.nativeElement;
el.click();
}
changeText() {
this.buttonText="Changed!"
}
Here's a Stackblitz demo
UPDATE
You can access elements of body using document object in Angular. This way, you can achieve the click of the element which is inside body. Here's one more example where I am clicking the button which is inside body from a component.
<button (click)="handleClick()">Programatically click the button inside body</button>
ngOnInit() {
let btn = document.getElementById('main');
btn.addEventListener('click', (e: Event) => btn.innerHTML = "Clicked!");
}
handleClick() {
document.getElementById('main').click();
}
index.html
<html>
<body>
<my-app>loading</my-app>
<button id="main">Click</button>
</body>
</html>
Here's a Stackblitz demo.

Getting index of an element from his parent

I found this code as the answer of a question:
function getNodeIndex(elm){
return [...elm.parentNode.children].indexOf(elm)
}
I made something so when you click on the document, it logs the target of the click;
If the target is myClass, I want it logs the index of it.
The myClass elements are buttons, that are added when the user clicks on a button.
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target))
}
})
But, that's weird:
Even if we click on 1st button, 4th button or 35th button, it will always log 2.
What's the problem there?
Thanks.
Edit:
The full code is here: http://pasted.co/6e55109a
And it is executable on http://zombs.io/
It's due to the structure of your DOM which probably looks something like
<div>
<div>Some Text: <button>Button 1</button></div>
<div>Some Text: <button>Button 2</button></div>
<div>Some Text: <button>Button 3</button></div>
</div>
Each of those buttons is the second child of its parent, i.e. one of the inner divs
Here's how to modify getNodeIndex to get it to work with DOM in this shape. If this still doesn't work, post your DOM.
function getNodeIndex(elm) {
return [...elm.parentNode.parentNode.children].indexOf(elm.parentNode)
}
$('button').on('click', e => {
console.log(getNodeIndex(e.target))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<div>Some Text: <button>Button 1</button></div>
<div>Some Text: <button>Button 2</button></div>
<div>Some Text: <button>Button 3</button></div>
</div>
I don't know if your actual code is exactly the same, but the code you posted has 2 errors, you're missing 2 brackets:
/*
Yours:
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target)
}
}
*/
// With the brackets
document.addEventListener("click", function(e) {
if(e.target.classList.value == "myClass") {
console.log(getNodeIndex(e.target))
}
})
I've run your code in jsbin and I had no problem, check the brackets and if you´re still having a problem please post your html
You are adding your eventListener to the document. Then you are checking for the classList. The classList may have another class and break your code. You should use classList.contains('some-class') instead.
I would add the click events directly to the 'some-class'-items you want them to be trigger by. This should work as long as you don't add more items to the DOM later. If you do, make sure to add the eventListener too.
// wait for all the html is loaded
window.addEventListener('load', () => {
// get all buttons with the .some-class
const someClassElements = document.querySelectorAll('.some-class');
// iterate over the 'array like' elements
someClassElements.forEach( element => {
// add a click event to the someClassElements
element.addEventListener('click', () => {
// log the nodeIndex
const nodeIndex = getNodeIndex(element);
console.log( nodeIndex );
});
});
}); // end on load
function getNodeIndex(elm){
return [...elm.parentNode.children].indexOf(elm)
}
div{
margin-top: 50px;
}
<div>
<button class='some-class'>some class (0)</button>
<button class='some-class'>some class (1)</button>
<button class='some-class'>some class (2)</button>
<button>no class (3)</button>
<button class='some-class'>some class (4)</button>
<button class='some-class'>some class (5)</button>
<button>no class (6)</button>
<button class='some-class'>some class (7)</button>
</div>
<div>
<button>no class (0)</button>
<button class='some-class'>some class (1)</button>
<button>no class (2)</button>
<button class='some-class'>some class (3)</button>
<button class='some-class'>some class (4)</button>
<button>no class (5)</button>
<button class='some-class'>some class (6)</button>
</div>
The actual answer in your situation:
It seems like you want to know the index of the parent div, not the actual element.
use
console.log(getNodeIndex(e.target.parentNode));
instead of
console.log(getNodeIndex(e.target));
PS: You are always getting 2 as result without your html, you may actually always be clicking the third child of the parent element. I verified that this is the case from your code.
PSII: an extra.. In the code you linked you removed a parent node of an element. Later you try to use that element to make some unsuccessful console.log's, which won't work because you just removed the element.

How apply styles from Material Angular

I need to add a button with the Material Angular 2 style. So I have a button that does this:
<button mat-button (click)="addElement($event, 'button')">Add button</button>
Where addElement is defined:
addElement(ev, tag) {
const htmlElem = ev.currentTarget;
const el = this.renderer.createElement(tag);
this.renderer.setAttribute(el, 'mat-button', '');
el.textContent = 'New Button';
htmlElem.parentNode.insertBefore(el, null);
}
Clicking on the button then creates a new element in my HTML, shown as:
<button mat-button">Add button</button>
The function code correctly generates the button, however it doesn't apply all children as shown in the original button code from the material code
So do I have a way to "refresh" the button so that the everything from the standard Mat-button applies to the button added via the JS code?
What went wrong?
You are right that you have added the mat-button attribute but since you are creating a button dynamically you did not be able to achieve to generate the whole structure of mat-button.
Below is the whole style and structure of mat-button
<button class="mat-button" mat-button="">
<span class="mat-button-wrapper">Add button</span>
<div class="mat-button-ripple mat-ripple"></div>
<div class="mat-button-focus-overlay"></div>
</button>
As you can see there are other html elements in the mat-button which includes ripple effects and focus overlay.
The Solution to your question
We are going to use Angular Renderer to create html element, set the attribute to the element and to append it on an element.
So what we did
create the button that will trigger the append
<button id="clickBtn" (click)="onClick()">Click here to add Button</button>
import the import Directive, ElementRef, Renderer2 in the component.
{ Component, Directive, ElementRef, Renderer2 } from '#angular/core';
add a directive that will target the html element where the button will get appended (#clickBtn [is the id tag of the button that we created)
#Directive({
selector: '#clickBtn'
})
create a constructor to inject renderer and elementref
constructor(private renderer: Renderer2,private elRef: ElementRef) {
}
trigger the click event to append the button
onClick() {
const btn = this.renderer.createElement('button');
const span = this.renderer.createElement('span');
const div1 = this.renderer.createElement('div');
const div2 = this.renderer.createElement('div');
const text = this.renderer.createText('I am a Generated Button');
const attrBtn = this.renderer.setAttribute(btn, 'class', 'mat-button');
const attrSpan = this.renderer.setAttribute(span, 'class', 'mat-button-wrapper');
const attrDiv1 = this.renderer.setAttribute(div1, 'class', 'mat-button-ripple mat-ripple');
const attrDiv2 = this.renderer.setAttribute(div2, 'class', 'mat-button-focus-overlay');
this.renderer.appendChild(span, text);
this.renderer.appendChild(btn, span);
this.renderer.appendChild(btn, div1);
this.renderer.appendChild(btn, div2);
this.renderer.appendChild(this.elRef.nativeElement, btn);
}
Woah so what going on here. as you can see we generate here all the structure of the mat-button
To know more about Renderer2 Please visit this link.
https://alligator.io/angular/using-renderer2/
Please see the link of live code on stackblitz
https://stackblitz.com/edit/dmgrave-ng-so-answer-dom?file=app%2Fapp.component.ts
Hope this helps.
I'm not sure why you want to generate the buttons dynamically but I would do it something like this
In your template:
<button mat-raised-button (click)="addNewButton()">Add new </button>
<div *ngFor="let btn of buttonsList">
<button mat-raised-button color="primary">
{{btn+1}}
</button>
</div>
In your component:
export class AppComponent {
buttonsList = [];
addNewButton():void{
const newId = this.buttonsList.length;
this.buttonsList.push(newId);
}
}
You just have to loop through your array and display the buttons. Every time you add a new button, you just have to push a new value to your array and let the framework do the heavy lifting.
Here's the stackblitz demo : https://stackblitz.com/edit/angular-7rfwrx?file=app%2Fapp.component.ts
Hope this helps.

Remove all occurances of class in angular 2 +

I have just started coding in angular5 and I came across need of removing all class occurances on click event.
Something like below we have in Jquery
$('.m-active').removeClass('m-active');
I am looking for alternative of this in angular2 + (Typescript)
You could use document.querySelector all to remove the class - in the following - I have two divs - iniitally set to be red / green text, but using querySelectorAll - I am removing the red class from the divs.
function toggleRedClass() {
var redDivs = document.querySelectorAll('.red');
if (redDivs.length) {
for(i=0;i<redDivs.length;i++) {
redDivs[i].classList.remove('red');
redDivs[i].classList.add('black')
}
} else {
var blackDivs = document.querySelectorAll('.black');
for(i=0;i<blackDivs.length;i++) {
blackDivs[i].classList.remove('black')
blackDivs[i].classList.add('red')
}
}
}
.red {color:red}
.green {color:green}
<div class="red">test</div>
<div class="green">test1</div>
<button type="button" onclick="toggleRedClass()">Click to toggle the red class</button>
In Angular 2+ better use bindings instead of jQuery
<div [class.my-class]="isMyClass">div 1</div>
<div [class.my-class]="isMyClass">div 2</div>
<button (click)="isMyClass = !isMyClass">toggle</button>
export class MyComponent {
isMyClass:boolean = true;
}
You can create a directive like this :
https://plnkr.co/edit/eKokX0IrsIWIuY9ACUZ4?p=preview
#Directive({
selector: '[class]'
})
export class ClassDirective {
#Input('class') claz;
private _claz;
public set claz(claz){
this._claz = claz;
}
public get claz(){
return this._claz;
}
#HostBinding('class') get hostClass(){
return this.claz;
}
constructor(){
console.log('***');
}
ngOnInit(){
console.log('this.classz',this.claz);
setTimeout(()=>{
this.claz= this.claz.replace('milad','');
},2000)
}
}
I know it doesn't do exactly what you want, but the idea is to create a Directive which has a selector called class and then you have access to all the classes in your application (obviously this component should be declared in your modules).
Then you can do whatever you'd like inside that directive, you can use host binding to override the classes and whatnot.
You can create an event listener to some button, pass the event listener's call back to this directive and let it do whatever you want.

Getting HTML element by Id and switch its CSS through React

I have some files that load into my react components, which have HTML code.
As it is now, the pure HTML code renders just fine, however there is some 'hidden' code that appears whenever you click certain buttons in other parts of the application or on the text above (think of it like panels that expand when you click on it).
The HTML is hidden just using the good old <div id="someId" style="display:none">.
Anyway I am trying to get the correct panel to expand upon clicking their respective buttons.
So in theory, what I need to do is find the element by id, and switch it's display to block whenever needed, and then switch it back when the parent is clicked again.
Unfortunately I have no idea how to do this and so far have gotten nowhere. As it is now, I have access to the component's ids. What I want to know is how in the world can I access that and get to change whatever is rendering?
Create your function:
function element_do(my_element, what_to_do) {
document.getElementById(my_element).style.display = what_to_do;
}
and latter in code you can append wherever you want through javascript onclick or not depends what do you need:
element_do("someId", "none"); // to hide
element_do("someId", "block"); // to show
or create yourself toggle:
function toggle_element(element_id) {
var element = document.getElementById(element_id);
element.style.display = (element.style.display != 'none' ? 'none' : 'block' );
}
// and you can just call it
<button onClick="toggle_element('some_id')">toggle some element</button>
The react way to do it would be with states. Assuming that you know how to use states I'd do something like this:
class ShowHide extends React.Component {
constructor() {
super();
this.state = {myState: true};
this.onClick = this.onClick.bind(this)
}
onClick() {
this.setState({myState: !this.state.myState}) //set the opposite of true/false
}
render() {
const style = {myState ? "display: none" : "display:block"} //if myState is true/false it will set the style
return (<div>
<button onClick={this.onClick}>Click me to hide/show me </button>
<div id="myDiv" style={style}> Here you will hide/show div on click </div>
</div>)
}
}

Categories

Resources