Add Data Dynamically to HTML Button in HTML page - html

I have this button, I need to add Pendo Data that is dynamically working based on which button we chose. Mostly this is making the Button unique. When I have a button that is not changing I add like this:
<button mat-button
 data-pendo="pendo-prospects-send-application"
class='round-button'
color='primary'
type='button'
.....>
</button>
But sometime I need to add this data to one button that is changing based on CSS class. I am not sure how check for that.
For example I need to add to a button when :
if [class.fa-pencil] then data-pendo "Something"
if [class.fa-plus] then data-pendo "Something else"
This is the button that changes base on class:
<button mat-button
class='round-button'
type='button'
[class.disabled-button]='GuidId'
color='primary'
(click)='onAssignLoanOfficer()'>
<i class='fal'
[class.fa-pencil]='GuidId'
[class.fa-plus]='!GuidId'></i>
</button>
How I can do that?

Based on your comments I think this is what you want to do:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
[attr.data-pendo]="GuidId ? 'pendo-edit-loan' : 'pendo-add-loan'"
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
ALTERNATIVE:
As a more sophisticated alternative (I really don't know how you intend to use what you're asking for), you can build a directive to add the attribute you want based on a map of class-to-pendo-data conversion information:
#Directive({
selector: '[addPendoData]'
})
export class AddPendoDataDirective implements AfterViewInit {
constructor(private _el: ElementRef, private _renderer: Renderer2) {}
ngAfterViewInit() {
const pendoData: string | null | undefined = this._getPendoValue();
if (!pendoData) { return;}
const $button: HTMLElement = this._el.nativeElement;
this._renderer.setAttribute($button, 'data-pendo', pendoData);
}
private _getPendoValue() {
const $child: HTMLElement = this._el.nativeElement;
if(!$child) { return null; }
const $i: HTMLElement = $child.querySelector('i');
if(!$i) { return null; }
const listOfClasses: string[] = $i.className.split(' ');
if (!(listOfClasses && listOfClasses.length)) { return null; }
for(const className of listOfClasses) {
if(PENDO_MAP[className]) { return PENDO_MAP[className]; }
}
return null;
}
}
const PENDO_MAP: { [className: string]: string } = {
'fa-pencil': 'pendo-edit-loan',
'fa-plus': 'pendo-add-loan'
// add other mappings here...
};
and you can use it like this:
<button
(click)="onAssignLoanOfficer()"
[class.disabled-button]="GuidId"
addPendoData
class="round-button"
color="primary"
type="button">
<i [class.fa-pencil]="GuidId"
[class.fa-plus]="!GuidId"
class="fall">
</i>
</button>
I've put together this stackblitz demo.

You could define a directive, as julianobrasil suggests.
I want to point a different way to achieve this using Angular templates.
Define two buttons, addition and edition, separately.
Put a default where you want to be rendered
<div class="action-button" *ngIf="GuidId">
<!-- Add button -->
<button mat-button
data-pendo="pendo-add-loan"
(click)="add(...)"
class="round-button"
color="primary">
<i class="fal fa-plus"></i>
</button>
</div>
Then, define a ng-template tag with the other button definition. A completely fresh new.
<ng-template #edit-button>
<div class="action-button">
<!-- Edit button -->
<button mat-button
(click)="edit(...)"
data-pendo="pendo-edit-loan"
class="round-button"
color="primary">
<i class="fal fa-pencil"></i>
</button>
</div>
</ng-template>
Finally, just change the *ngIf statement to render either an addition button or an edition one.
<div class="action-button" *ngIf="!GuidId else edit-button">
<!-- Add button here -->
</div>
This is a way that scales in order to keep components isolated. The buttons are not related anymore, so you can implement w/o having to condition every style, action, etc.
Hope it helps.

Related

Angular How to implement Subject instead of Input and template variable

EDIT:
I implemented some logic to hide my icons and how them only when a specific row from parent component is on mouseover. I need to adjust this logic to my project. Changing global styles is unfortunately unacceptable in my project :(
I tried to implement it with Subject, but now instead of current row, all rows are affaected and all icons are shown. Is it possible to use these Subject Operator logic and show icons only on curretnly hovered row?
This is now my parent component HTML:
<ng-container *ngIf="!emptyList else emptyListTemplate">
<tr requestRow *ngFor="let request of requests"
(mouseenter)="emitEventToChild(true)"
(mouseleave)="emitEventToChild(false)"
[isRequestRowHighlighted]="eventsSubject.asObservable()"
[attr.data-cy-request-id]="request.id"
[request]="request"
[actionable]="true"
[isRequest]="isRequestsList"
(delete)="deleteRequest($event)"
appMemoTooltip>
</tr>
</ng-container>
This is the TS part:
isMouseEnter: boolean
eventsSubject: Subject<boolean> = new Subject<boolean>();
emitEventToChild(e: boolean) {
this.eventsSubject.next(e)
}
private filterSubscription: Subscription
This is my child component HTML (its selector is 'tr[requestRow]' )
<td *ngIf="actionable" class="p-auto text-center actions" (mouseenter)="memoControls.pause()" (mouseleave)="memoControls.resume()">
<div class="d-flex align-items-center justify-content-end" [ngClass]="{'invisible': !isMouseEnter}">
<button
*ngIf="preferredEditType"
type="button" class="btn btn-primary bg-transparent border-0"
title="Edit"
data-cy-id="cy-btn-edit"
(click)="load($event, request, isRequest, preferredEditType)">
<i class="icon-edit-request"></i>
</button>
<button *ngIf="hateoas.supports(request, 'details')"
type="button" class="btn btn-success bg-transparent border-0"
title="Reuse"
data-cy-id="cy-btn-reuse"
(click)="load($event, request, isRequest, requestOperationType.REUSE)">
<i class="icon-copy"></i>
</button>
<button *ngIf="hateoas.supports(request, 'delete')"
type="button" class="btn btn-danger bg-transparent border-0"
title="Delete"
data-cy-id="cy-btn-delete"
(click)="deleteRequest($event)">
<i class="icon-trash"></i>
</button>
</div>
This is the TS file:
#Input() isRequestRowHighlighted: Observable<boolean>
isMouseEnter: boolean
ngOnInit(): void {
this.eventsSubscription = this.isRequestRowHighlighted.subscribe(value => this.isMouseEnter = value)}
enter code here
ngOnDestroy(): void {
this.eventsSubscription.unsubscribe();
}
Expected Result
As is now
Please, let me know how can I achieve displaying icons only for current(on mouseenter) row, not for all of them.
Thank you.
the .css to mannage two differents components should be global, so try in styles.css
tr[requestRow] button {
visibility: hidden;
}
tr[requestRow]:hover button{
visibility: visible;
}
A Simple stackblitz
Update
Well, we can use a ViewEncapsulation.None, who apply all the .css in our component in a way global.
#Component({
selector: [requestRow]
...
encapsulation:ViewEncapsulation.None,
})
But be carefull, if in our component we use also e.g. h1{color:red} this style is also propagate to all our aplication
Another way (but is marked as deprecated) is using some like (in parent.css)
:host ::ng-deep tr[hello] button {
visibility:hidden
}
:host ::ng-deep tr[hello]:hover button{
visibility:visible
}

Angular 4/5 material raised button with input file

I am working on a angular application, currently to upload a file I am using this :
<label class="btn btn-default">
<input type="file" (change)="selectFile($event)">
</label>
<button class="btn btn-success" [disabled]="!selectedFiles"
(click)="upload()">Upload</button>
with methods on my .ts file, it is working well.
I want to upgrade this to material angular components raised button like that right now :
<button mat-raised-button>
<input type="file" (change)="selectFile($event)">
</button>
<button mat-button disabled [disabled]="!selectedFiles" (click)="upload()">Upload</button>
the disabled button is doing well but the input file part doesnt work , it prints baddly and does not open a file folder search window. any ideas?
Won't advise using input field within a button, better you hide the file input and then a button to trigger it. The below example will show a minimal example of it
#Component({
selector: 'my-app',
template: `
<div>
<h2>Hello {{name}} is for Uploading</h2>
</div>
<button mat-raised-button (click)="openInput()">
Select File to Upload
</button>
<input id="fileInput" hidden type="file" (change)="fileChange($event.target.files)" >
<button mat-button [disabled]="!ourFile" (click)="upload()">Upload</button>
`
})
export class App {
name:string;
ourFile: File; // hold our file
constructor() {
this.name = `Angular! v${VERSION.full}`
}
/**
* this is used to trigger the input
*/
openInput(){
// your can use ElementRef for this later
document.getElementById("fileInput").click();
}
fileChange(files: File[]) {
if (files.length > 0) {
this.ourFile = files[0];
}
}
/**
* this is used to perform the actual upload
*/
upload() {
console.log('sending this to server', this.ourFile);
}
}
Check this plnk
With the above example, you should be able to style your button without distorting HTML semantics

When i click on edit give me inputfield and 2 buttons

<div class="fa fa-edit clickable edit-icon" (click)="editProject()"></div>
{{project.name}}
This is the code I want, when I click on this icon, the project.name needs to turn into an input field and give me 2 buttons in the same page
I am working with html and typescript.
You can use ngIf and a property binding
In your component's class:
public isInputVisible : boolean = false;
public editProject(): void {
this.isInputVisible = true;
}
In your template:
<div class="fa fa-edit clickable edit-icon" (click)="editProject()"></div>
<ng-container *ngIf="!isInputVisible">{{project.name}}</ng-container>
<input [(ngModel)]=project.name"" *ngIf="isInputVisible" />

How to stop property binding to every element in Angular within *ngFor

In my html, I want to apply property binding to every element.
I have a click and hover event that I want to do whenever the user
hovers or clicks on an individual element. But right now the hover or
click happens to every element within the *ngFor. I want it to only
happen on the element they are selecting/hovering over. What do I need
to change?
I saw another stackoverflow answer and they simply applied the name
within the for loop (ex: *ngFor="let article of articles" and they
used article) in front of the boolean/variable they were setting.
Like my boolean is favorite so they did article.favorite within
the element and it apparently worked, but that method doesn't work for
me.
Code:
<div class="row">
<!--/span-->
<div class="col-6 col-sm-6 col-lg-4"
*ngFor="let article of articles">
<h2>{{article.title}}</h2>
<h4>By: {{article.author}}</h4>
<p>{{article.body}}</p>
<div class="col-lg-4">
<button type="button" class="btn btn-default" (click)="addFavorite()"
(mouseenter)="hoverFavorite()"
(mouseleave)="removeHoverFavorite()">
<span
class="glyphicon"
[class.glyphicon-heart]="favorite"
[class.glyphicon-heart-empty]="!favorite"
aria-hidden="true"></span> Favorite
</button>
</div>
<div class="col-lg-4">
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Comment
</button>
</div>
<div class="col-lg-4">
<button type="button" class="btn btn-info pull-left" [routerLink]="['/articles', article.articleId]">Read More »
</button>
</div>
</div>
</div>
<!--/row-->
Adding component
import { Component, OnInit } from '#angular/core';
import {ArticlesService} from "./articles.service";
import {Article} from "./article.model";
import {Router} from "#angular/router";
#Component({
selector: 'app-articles',
templateUrl: './articles.component.html',
styleUrls: ['./articles.component.css']
})
export class ArticlesComponent implements OnInit {
articles: Article[];
// if favorite = false then full heart is not shown. if true then heart shown
favorite: boolean = false;
// clicked will be used to determine if we should keep hovering effect on
clicked: boolean = false;
constructor(private router: Router, private articleService: ArticlesService) { }
ngOnInit() {
this.articleService.getArticles()
.subscribe(
(articles: Article[]) => {
this.articles = articles;
}
);
}
addFavorite(){
// toggle full and empty heart
this.clicked = !this.clicked;
if (this.clicked === true){
// if clicked then add to database and show full heart
this.favorite = true;
} else { // if false then remove from database and show empty heart
this.favorite = false;
}
}
hoverFavorite(){
// if clicked is false then show hover effect, else dont
if (this.clicked === false){
this.favorite = true;
}
}
removeHoverFavorite(){
// if clicked is false then show hover effect, else dont
if (this.clicked === false){
this.favorite = this.favorite = false;
}
}
}
You can use the index
*ngFor="let article of articles; let i=index"
(click)="addFavorite(i)"
// or
(click)="addFavorite(article)"
(mouseenter)="hoverFavorite = i"
(mouseleave)="hoverFavorite = -1"
[class.glyphicon-heart]="favorite === i"
[class.glyphicon-heart-empty]="favorite !== i"
The reason you're not getting the desired result is because defined your boolean variables in the ArticlesComponent, which is the component rendering the list of articles. Thus, if the variables become true, it would be true for all the articles, instead of a single one. To fix it you should define all of the content within the ngFor loop as its own component, and in that component you would set those boolean variables. That way, each instance of an ArticleComponent would have its own variables and they wouldn't interfere with each other.

Why do bootstrap tooltips not work in bootstrap modals?

My tooltips are working on the mainpage perfectly. In a modal which is generated later by an ajax call the tooltips won't work.
I have included the following code inside the generated modal (result of the ajax call).
To re-ini the tooltips
<script>
$('.tooltips').tooltip();
</script>
In the html of the modal
<button class="btn btn-lg default tooltips blue-madison" type="submit"
name="O" data-container="body" data-placement="top"
data-original-title="THIS TEXT FOR TOOLTIPS">
<i class="fa fa-industry blue-madison"></i> BUTTON1
</button>
<button class="btn btn-lg default tooltips green-jungle" type="submit"
name="P" data-container="body" data-placement="top"
data-original-title="THIS TEXT FOR TOOLTIPS">
<i class="fa fa-user green-jungle "></i> BUTTON2
</button>
Why don't the tooltips show- what I'm doing wrong?
The issue is because of the z-index of modal and tooltip. You can try this,
.tooltip {
z-index: 100000000;
}
Probably it's because you should call $('.tooltips').tooltip(); after the modal's content have been inserted in the document.
Otherwise, please post a fiddle with your current code where we can test it.
Another solution is to bind the tooltip to the modal using the container option:
$('#modal').on('shown.bs.modal', function() {
console.log("modal show");
$('.tooltips').tooltip({
container: $(this)
});
});
Maybe this helps someone: I had a case when needed with ajax to populate & display a bootstrap modal (render view) on click (calling showModal(url, event) below); bootstrap tooltip and also fengyuanchen/datepicker were unresponsive, so I managed to trigger them after detecting modal loading, like this:
function showModal(url, event) {
$.when(
$.ajax({
url: url,
method: 'GET',
success: function (data) {
$('#modal-wrapper').html(data);
}
})
).then(function() {
$('.loaded_modal').modal().on('shown.bs.modal', function() {
$('[data-toggle="datepicker"]').datepicker({
format: "dd/mm/yyyy",
autoclose: true,
todayHighlight: true,
zIndex: 1070,
container: '.modal'
});
$('.modal [data-toggle="tooltip"]').tooltip({trigger: 'hover'});
// could also be on click {trigger: 'hover click'}
});
});
If you are using react I had success with a different answer. All you have to
do is give the parent container a ref and then in the overlayTrigger component you just have to pass in the ref as a param to the container.
import React, { Component } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
class random extends Component {
constructor(props) {
super(props);
this.ref= React.createRef()
}
render() {
return (
<div ref={this.ref}>
<OverlayTrigger
placement="top"
container={this.ref}
overlay={
<Tooltip data-container="body">Some text</Tooltip>
}
>
<span className="d-inline-block">
<i className="mdi mdi-help-circle pointer"></i>
</span>
</OverlayTrigger>
</div>
);
}
}
Best way to fix this behaviour is by adding this prop inside your md-tooltip:
md-z-index="9999"
or a different z-index.
No need to hardcode this in the css.
You can also define the z-index in your scope variable inside your controller as the following
// in controller
$scope.btnOptions = {
isOpen:false,
label: 'test button',
class: 'md-scale',
zIndex: 99999
};
in you html ( normally I would use {{}} to print the variable, but I'm on laravel so I used <% %> instead
<md-button aria-label="Édit" class="md-fab md-raised md-mini">
<md-tooltip md-direction="top" md-z-index="<% btnOptions.zIndex %>">Mode édition</md-tooltip>
<i class="far fa-edit"></i>
</md-button>