How to get class of the element in stimulus.js - javascript

I want to toggle elements and I need a class names for that.
How can I get a class name of the nested element in stimulus.js and change it?
F.I, I need to toggle the "ul" element that is initially hidden.
div data-controller="my_controller"
a data-action="click->my_controller#toggle_my_elements"
| Click
ul.is-hidden id="my-id" data-target="my_controller.mytext"
li
| Text to be toggled.
and in stimulus controller I have:
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ["mytext"]
toggle_my_elements(){
console.log("debuggin") //Ok
const class_name = this.mytextTarget.className
}
}
I tried to call a js function className but it seems js functions don't work in the way they used to.
I just can't figure out how to get it.

As StimulusJS target is a HTML element, you can use its classList property
this.mytextTarget.classList.remove('is-hidden')

You could do the following to get the ul class:
static targets = [ "mytext" ]
connect() {
this.mytextClass = this.data.get("class") || "is-hidden"
}
Then use the following action descriptor to toggle your ul element
toggle(event) {
event.preventDefault();
this.mytextTargets.forEach(target => {
target.classList.toggle(this.mytextClass)
})
}

Have you tried element[:class]?
That's how I access the class of the html element from a Stimulus Reflex in ruby since element.class returns the class of the element (a StimulusReflex::Element) instead of the "btn btn-primary" String I was expecting.

Related

Prototypes on Custom Elements

Suppose I have a custom HTML element tag <custom-table> with an attribute tableVal="10".
I would like to easily fetch the tableVal without using .getAttribute() (since I'm creating a public API that is easy-to-use).
Here's what I'm trying to do:
var customElement = document.querySelector('custom-table');
console.log(customElement.val)
Output: undefined
Expected Output: 10
This is my current try:
Object.setPrototypeOf(customElements.prototype, val.prototype)
function val(){
return this.getAttribute("tableVal")
}
Any idea or approaches through which I can achieve this?
There are a couple of ways to do this (see the MDN documentation on writing custom elements), but one is to create a class for your custom element with an accessor val property, and use that class when registering your custom element:
class CustomTable extends HTMLElement {
get val() {
return this.getAttribute("tableVal");
}
}
customElements.define("custom-table", CustomTable);
Live Example:
<!-- Defining the custom element -->
<script>
class CustomTable extends HTMLElement {
get val() {
return this.getAttribute("tableVal");
}
}
customElements.define("custom-table", CustomTable);
</script>
<!-- Using it in HTML -->
<custom-table tableVal="10"></custom-table>
<!-- Testing the property -->
<script>
const table = document.querySelector("custom-table");
console.log(table.val); // 10
</script>

Adding a custom class to blockquote in QuillJS

I'm trying to figure out how to add a custom class when the user clicks the blockquote toolbar button. At the moment, when blockquote is clicked, the element is created as so:
<blockquote class="ql-align-justify">this is my quoted text</blockquote>
I would like to add .blockquote to the class as so:
<blockquote class="ql-align-justify blockquote">this is my quoted text</blockquote>
I'm currently looking at adding a handler, but there doesn't appear to be much documentation on how this works:
this.editor = new Quill(this.$refs.editor, this.editorOptions)
// Handlers can also be added post initialization
var toolbar = this.editor.getModule('toolbar');
toolbar.addHandler('blockquote', function(value) {
//todo: working on adding the blockquote class to blockquotes.
console.log('blockquote called');
console.log('value:');
console.log(value);
if (value) {
this.quill.format('blockquote');
}
});
I don't think handlers can do this (as far as I know)
But you can extend the BlockQuote format, by doing so, you will have full control of the node, you can add classes, or even click handler.
The format that is responsible for blockquote is:
const BlockQuote = Quill.import('formats/blockquote');
So simply you can do:
const BlockQuote = Quill.import('formats/blockquote');
class CustomBlockQuote extends BlockQuote {
static create(value) {
const node = super.create(value);
node.classList.add('test');
return node;
}
}
And you can update the tag name and the blot name like:
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
Or even simpler for only changing the class:
class CustomBlockQuote extends BlockQuote {}
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
CustomBlockQuote.className = 'custom-class-name';
Quill.register(CustomBlockQuote, true);
I have just created a jsfiddle as an example, I hope it works for you:
https://jsfiddle.net/hassansalem/095mh3fn/4/

How to change display by document.getElementById inAngular

I have a scenario where I am generating dynamic elements with the data from backend some what like
<tr *ngFor="let data of datas" (click)="display(data.id)">
<div id="'full'+data.id" [style.display]=" active? 'block': 'none'">
</div>
</tr>
My Ts File
export class Component{
active=false;
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
What I want to do is something like above. I tried something like below but that doesn't work it throws error
Property 'display' does not exist on type 'HTMLInputElement'
import { DOCUMENT } from '#angular/common';
import { Inject } from '#angular/core';
export class Component{
active=false;
constructor(#Inject(DOCUMENT) document) {
}
display(id)
{
document.getElementById(`full${id}`).display="block";
}
}
any suggestions ,how to do this .Thanks
Property 'display' does not exist on type 'HTMLInputElement'
That's because HTML elements do not have a property display. What you're looking for is:
document.getElementById(`full${id}`).style.display='block';
Rather than directly manipulating the DOM, the more Angular way of doing this would be to track the visibility state of each row and drive visibility through NgIf
NgIf: A structural directive that conditionally includes a template based on the value of an expression coerced to Boolean. When the expression evaluates to true, Angular renders the template provided in a then clause, and when false or null, Angular renders the template provided in an optional else clause. The default template for the else clause is blank.
Here is an example with a single boolean driving the toggle of a single div, but you could do something similar with a map on your data.
#Component({
selector: 'ng-if-simple',
template: `
<button (click)="show = !show">{{show ? 'hide' : 'show'}}</button>
show = {{show}}
<br>
<div *ngIf="show">Text to show</div>
`
})
export class NgIfSimple {
show: boolean = true;
}
I've solve problem like this:
let fullId = document.getElementById(`full${id}`) as HTMLElement;
fullId.style.display='block';

DOM Manipulations in Angular 5

For example I have:
<div class="btn-wrapper-bt1">
<button>AAA</button>
</div>
This button is on the 3rd party element that exists in node_modules/somebt
I would like to do some simple class change within Angular environment.
Is there a simple way to change it in ngOnInit? Or I need to fork the source and change it within the source?
Thanks in advance.
In the html, add a #ref reference to the element containing your 3rd party component
yourComponent.html
<div #ref >
<your-3rd-party-component></your-3rd-party-component>
</div>
Then, in your component, retrieve the children of the containing element
yourComponent.ts
import { Component,Renderer2, ViewChild,ElementRef } from '#angular/core';
export class YourParentComponent {
#ViewChild('ref') containerEltRef: ElementRef;
constructor(private renderer: Renderer2)
{
}
ngAfterViewInit()
{
// retrieves element by class
let elt = this.containerEltRef.nativeElement.querySelector('.btn-wrapper-bt1');
this.renderer.addClass(elt, 'newClass'); //Adds new class to element
}
}
Here is a stacklblitz demo
Note: If you just want to change the 3rd party component's appearance, you could just override the class in your own component
yourComponent.scss
:host ::ng-deep .btn-wrapper-bt1
{
color: red;
}
Add a reference :
<div #myRef class="btn-wrapper-bt1">
<button>AAA</button>
</div>
And in your TS :
#ViewChild('myRef') myElement: ElementRef;
myFunc(){
// do whatever you want with it AFTER you third party module finished its job (that's your call)
//this.myElement.nativeElement.querySelector()
//this.myElement.nativeElement.classList.remove('toto')
}

Extending HTML elements in Web components

From the custom elements page, I see that to extend an element you do:
var XFooButtonPrototype = Object.create(HTMLButtonElement.prototype);
XFooButtonPrototype.createdCallback = function() {
this.textContent = "I'm an x-foo button!";
};
var XFooButton = document.registerElement('x-foo-button', {
prototype: XFooButtonPrototype,
extends: 'button'
});
Then later in the guide it says that you can make an element by writing either:
<x-foo></x-foo>
Or:
<button is="x-foo-button"></button>
Questions:
Why is it important to specify extends: 'button' when the element is obviously_ inheriting from HTMLButtonElement (since it has HTMLButtonElement.prototype in its proto chain)
How is the link between button and x-foo-button established? Does x-foo-button become a possible option of button in terms of is="x-foo-button" thanks to that extends: 'button' ? What happens "internally", so to speak?
Why would you pick <button is="x-foo-button"></button> over <x-foo></x-foo>...?
[ADDENDUM]
Polymer saves us from this duplication:
MyInput = Polymer({
is: 'my-input',
extends: 'input',
created: function() {
this.style.border = '1px solid red';
}
});
If extends is there, Polymer will put the right prototype in the chain with Object.getPrototypeOf(document.createElement(tag));.
So, corollary question:
Why the duplication in the first place? If there is an extends, shouldn't the browser automatically do this?
You totally misunderstood how extending web components work.
Create simple elements
First of all, this is how you register a new element:
var XFoo = document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype)
});
To create an element you can do one of these:
<x-foo></x-foo>
var xFoo = new XFoo();
document.body.appendChild(xFoo);
var xFoo = document.createElement( 'x-foo')
document.body.appendChild(xFoo);
Create extended elements
This is how you extend an existing element:
var XFooButton = document.registerElement('x-foo-button', {
prototype: Object.create(HTMLButtonElement.prototype),
extends: 'button'
});
To create one you can do one of these:
<button is="x-foo-button"></button>
var xFooButton = new XFooButton();
document.body.appendChild(xFoo);
var xFooButton = document.createElement('button', 'x-foo-button');
document.body.appendChild(xFooButton);
Note that in case of extended custom elements, when registering them you have to specify both the prototype (set to HTMLButtonElement.prototype rather than HTMLElement.prototype), and the extended tag's name (extends: 'button').
Also, when you create an extended element using markup or createElement(), you need to also specify the basic element (button) and the extended one (x-foo-button),
(Note: I am aware I am answering myself)
I think its Importent to Say here:
WARNING DEPRECATED Browser API METHOD
Here in this Question a .registerElement is Used it got Replaced by .defineElement and the Api has changed
current way to define a element
class AppDrawer extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH</h1>'
}
}
window.customElements.define('app-drawer', AppDrawer);
// Or use an anonymous class if you don't want a named constructor in current scope.
window.customElements.define('app-drawer-noname', class extends HTMLElement {
constructor() {
super()
this.innerHTML = '<h1>UH AH</h1>'
}
});
Example - defining a mobile drawer panel, < app - drawer >:
Example usage:
<app-drawer></app-drawer>
<app-drawer-noname></app-drawer-noname>
```

Categories