I am not quite sure that the title is the appropriate, because I donĀ“t know whether 'repaint' is correct. Nevertheless I think is clear what it means.
I am using the plugin '#proplugin/nativescript-platform-css' and followed Tiago's Alves excellent blog.
As I am using angular I require the plugin in the main.tns.ts
import { platformNativeScriptDynamic } from 'nativescript-angular/platform';
import { AppModule } from '#src/app/app.module';
require( '#proplugins/nativescript-platform-css' );
platformNativeScriptDynamic().bootstrapModule(AppModule);
I am dynamically changing the rows and columns depending on the device width:
HTML
<GridLayout (layoutChanged)="updateLayout($event)" horizontalAlignment="center" verticalAlignment="center" class="wrap-login100"
[class.phone]="gridLayout.isPhone" [class.tablet]="!gridLayout.isPhone"
row="1"
[rows]="gridLayout.rows"
[columns]="gridLayout.columns">
<StackLayout marginBottom="50" [col]="gridLayout.image.col" [row]="gridLayout.image.row" verticalAlignment="center" class="login100-pic">
UPDATE LAYOUT EVENT
updateLayout(value) {
this.zone.run(() => {
this.gridLayout = this.platformService.updateLayout('login');
});
}
UPDATE METHOD
updateLayout(route) {
let width;
width = screen.mainScreen.widthDIPs;
let gridLayout;
if (width < 1000) {
gridLayout = {
isPhone: true,
columns: '*',
rows: '*,2*',
image: {
row: 0,
col: 0
},
form: {
row: 1,
col: 0
}
};
} else {
gridLayout = {
isPhone: false,
columns: '*,2*',
rows: '*',
image: {
row: 0,
col: 0
},
form: {
row: 0,
col: 1
}
};
}
return gridLayout;
}
When the app started I triggered the updateLayout method and the pp will paint correctly the layout, either tablet or phone. When I change then the orientation, the updateLayout method is also triggered returning the appropriate gridLayout object but nothing happens on the screen.
As you can see in the code I tried to use the angular zone, but nothing changed.
Any ideas will be appreciated as I don't really know where to look...
Related
I'm currently developing an app using Ionic V6 and i'm figured out if it was possible to make an ion-modal-sheet snap the header when it fully swiped like the Uber eats application ?
Thanks in advance for your answers and have a nice day !
For those who wondering how to do it, here's how i managed to get something similar:
first of all i've added an id to my header (my ion-header is wrapped inside a component called header) if your header is always in the same page of where you're creating the modal, use a template reference variable instead of an id:
<header
id="getHeight"
[title]="'Solutions disponibles'"
[displayGoBack]="true"></header>
then i've created a helper to:
Get the height of the header (can be different between iOS and Android)
Get the body height
Use the body height and the header height to return the height my modal should have in px and in %
export const getContentHeight = (): any => {
const header = document.querySelector('#getHeight');
const body = document.querySelector('body');
if (body && header) {
const percent = (100 - (header.clientHeight * 100 / body.clientHeight));
return {
height: body.clientHeight - header.clientHeight,
percent: percent
}
}
}
After that i've created another helper to change the visual aspect of my modal when breakpoint reach 0.9:
export const setModalSheetContentHeight = (modal: HTMLIonModalElement, breakpoint: number, expand: boolean): any => {
if (breakpoint >= 0.9 && !expand) {
modal.classList.add('no-radius');
modal.setCurrentBreakpoint(1);
expand = true;
} else if (breakpoint <= 0.9 && expand) {
modal.classList.remove('no-radius');
expand = false;
}
return expand;
}
The boolean "expand" avoid the helper to always set the modal breakpoint to 1.
Then i've created a method that add some listeners on the modal:
initModalListener(): void {
let expand = false;
// Define if the modal is fully expand
const content = getContentHeight();
// Return an object with content height in px and in %
addEventListener('ionModalWillPresent', () => {
this.modal.setAttribute('style', `--height: ${content.height}px`);
// Set the modal height before it shown to avoid visual bug
});
addEventListener('ionBreakpointDidChange', (e: any) => {
const breakpoint = e.detail.breakpoint;
expand = setModalSheetContentHeight(this.modal, breakpoint, expand);
});
}
And after all of that i call this method inside of the method that create my modal:
private async presentModal(): Promise<void> {
this.modal = await this.modalCtrl.create({
component: YourModalComponent,
breakpoints: [0.3, 0.65, 1],
initialBreakpoint: 0.3,
backdropBreakpoint: 1,
showBackdrop: false,
canDismiss: false,
keyboardClose: true,
id: 'modalSheet',
}
});
this.initModalListener();
await this.modal.present();
}
This is a first shot so i'm sure that there is plenty of thing to adjust but for now, it work pretty well with Ionic 6.
After a lot of searches in SO without any particular solution, I am compelled to ask this question.
What I want is to hide a row group icon on a single group row. Like in the below picture I have a group row that has only one record, which is already shown in the top row. I want to hide that collapse icon on that single record. Only collapse/expand icon shown when group rows are more than one.
For reference see AG-Grid Master-Detail Section, here they specify which rows to expand. Same functionality I needed here.
I'm using the below versions of AG-Grid Angular (v9)
"#ag-grid-community/core": "^25.3.0",
"#ag-grid-enterprise/row-grouping": "^26.0.0",
"#ag-grid-enterprise/server-side-row-model": "^25.3.0",
"ag-grid-angular": "^25.3.0",
"ag-grid-community": "^25.3.0",
Here is my code:
this.rowModelType = 'serverSide';
this.serverSideStoreType = 'partial';
this.cacheBlockSize = 20;
this.gridOptions = {
rowData: this.loanlist,
columnDefs: this.generateColumns(),
getNodeChildDetails: function(rowItem) {
if (rowItem.orderCount > 1) {
return {
expanded: true
}
} else {
return null;
}
}
}
The issue is the getNodeChildDetails is not accessible. Browser console showing me the below warning and my above code is not working.
This is simple to achieve using a cellRendererSelector on the autoGroupColumnDef. You can specify whether to show the default agGroupCellRenderer or simply return another renderer (or, just return null):
this.autoGroupColumnDef = {
cellRendererSelector: (params) => {
if (params.value == 'United States') {
return null;
} else {
return {
component: 'agGroupCellRenderer',
};
}
},
};
In the example below, we are disabling the row group expand functionality on the United States row.
See this implemented in the following plunkr.
The solution isn't that hard - but could be tough, agreed (one day faced with the same case)
So - the answer is custom cell renderer.
It would look a little bit different (separate column for collapse\expande action) - but you would get all control of it.
Custom rendeder component for this action would look like :
template: `
<em
[ngClass]="{'icon-arrow-down':params.node.expanded, 'icon-arrow-right': !params.node.expanded}"
*ngIf="yourFunctionHere()"
(click)="toggleClick()">
</em>`,
export class MasterDetailActionComponent implements ICellRendererAngularComp {
private params: any;
agInit(params: any): void {
this.params = params;
}
public toggleClick(): void {
this.params.node.setExpanded(!this.params.node.expanded);
}
public yourFunctionHere(): boolean {
// so here you are able to access grid api via params.api
// but anyway params.node - would give you everything related to row also
}
refresh(): boolean {
return false;
}
}
in [ngClass] - you are able to handle the visual part (icons) - modify\customize
and don't forget to add this component in the gridOptions:
frameworkComponents: {
'masterDetailActionCellRenderer': MasterDetailActionComponent,
}
and include this column in your columnDef:
columnDefs: [
headerName: "",
width: 75,
field: "expand",
cellRenderer: "masterDetailActionCellRenderer",
filter: false,
resizable: true,
suppressMenu: true,
sortable: false,
suppressMovable: false,
lockVisible: true,
getQuickFilterText: (params) => { return '' }
]
Can't find the exact answer.
If i decide to opt-in for vanilla JavaScript (non-Angular & Co) ag-Grid-community edition, can i have easy to add my own custom context menu an other custom extensions?
As i seen their docs, context menu is only enterprise level feature.
I seen some treads that there is some caveats, but i personally did not dig deeper.
In general, how easy is to implement self-built features in ag-Grid-community. Or it is better to write own grid?
We have a custom context menu component in our Angular project with ag-grid community, so it's definitely possible.
How it works:
We define all grid columns in templates. If you want a context menu, you put an empty column into the column set and put a special directive on it. The directive accepts a context menu template, which is passed into a custom cellRendererFramework (a menu trigger button, basically). The directive also configures the column to ensure consistent look across grid instances.
This might be not what you've been looking for if you require for menu to open with right mouse click anywhere in a row, but I suppose it shouldn't be that hard to trigger the menu from a different event (check out ag-grid events, there might something suitable).
The snippets below should be straightforward to adapt for your framework of choice. Given you opted into vanilla JS, you'll have to use regular functions to do the same, something like this:
const grid = withContextMenu(new Grid(element, gridOptions), menuOptions).
Here's an example of how we use it:
<ag-grid-angular>
<ag-grid-column headerName='ID' field='id'></ag-grid-column>
<ag-grid-column [contextMenu]='menu'>
<mat-menu #menu='matMenu'>
<ng-template matMenuContent let-item='data'>
<button mat-menu-item (click)='restoreSnapshot(item.id)'>Restore From Snapshot</button>
<a mat-menu-item [routerLink]='[item.id, "remove"]'>Remove</a>
</ng-template>
</mat-menu>
</ag-grid-column>
</ag-grid-angular>
The directive that applies the menu:
const WIDTH = 42;
export const CONTEXT_MENU_COLID = 'context-menu';
#Directive({
selector: '[agGridContextMenu]'
})
export class AgGridContextMenuDirective implements AfterViewInit {
constructor(private gridComponent: AgGridAngular) {}
#Input()
agGridContextMenu!: ElementRef<MatMenu>;
ngAfterViewInit() {
if (!this.agGridContextMenu) return;
setTimeout(() => {
this.gridComponent.api.setColumnDefs([
...this.gridComponent.columnDefs,
{
colId: CONTEXT_MENU_COLID,
cellRendererFramework: CellRendererContextMenuComponent,
width: WIDTH,
maxWidth: WIDTH,
minWidth: WIDTH,
cellStyle: {padding: 0},
pinned: 'right',
resizable: false,
cellRendererParams: {
suppressHide: true,
contextMenu: {
menu: this.agGridContextMenu
}
}
}
]);
});
}
}
The cell renderer component:
#Component({
selector: 'cell-renderer-context-menu',
template: `
<ng-container *ngIf='params.data && params.colDef.cellRendererParams.contextMenu.menu'>
<button
type='button'
mat-icon-button
[matMenuTriggerFor]='params.colDef.cellRendererParams.contextMenu.menu'
[matMenuTriggerData]='{data: params.data}'
>
<mat-icon svgIcon='fas:ellipsis-v'></mat-icon>
</button>
</ng-container>
`,
styleUrls: ['./cell-renderer-context-menu.component.scss']
})
export class CellRendererContextMenuComponent implements ICellRendererAngularComp {
params!: ICellRendererParams;
agInit(params: ICellRendererParams) {
this.params = params;
}
refresh() {
return false;
}
}
A screenshot:
I followed this blogpost, using community edition ag-grid, and it worked! I was surprised because previously I had the experience that cell renderers didn't allow content outside of the cell boundaries to be shown, but somehow popper/tippy is getting around that (I think it adds itself to the top of the DOM with this section of code appendTo: document.body).
https://blog.ag-grid.com/creating-popups-in-ag-grid/
basically, in my javascript CellRenderer:
class MyCellRenderer{
// https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/
init(e){
this.isOpen = false;
this.container = document.createElement("span");
let menubutton = document.createElement("button");
menubutton.innerHTML="🠋"; //downward arrow
this.tippyInstance = tippy(menubutton);
this.tippyInstance.disable();
this.container.appendChild(menubutton);
menubutton.addEventListener('click', that.togglePopup.bind(this));
}
getGui() {
return this.container;
}
togglePopup() {
this.isOpen = !this.isOpen;
if (this.isOpen) {
this.configureTippyInstance();
this.eMenu = this.createMenuComponent();
this.tippyInstance.setContent(this.eMenu);
} else {
this.tippyInstance.unmount();
}
}
configureTippyInstance() {
this.tippyInstance.enable();
this.tippyInstance.show();
this.tippyInstance.setProps({
trigger: 'manual',
placement: 'bottom-start',
arrow: false,
interactive: true,
appendTo: document.body,
hideOnClick: true,
onShow: (instance) => {
tippy.hideAll({ exclude: instance });
},
onClickOutside: (instance, event) => {
this.isOpen = false;
instance.unmount();
},
});
}
createMenuComponent() {
let menu = document.createElement('div');
menu.classList.add('menu-container');
let options = {};
options['Delete Row'] = this.menuItemClickHandler.bind(this);
options['Popup an Alert!'] = function(){alert("hello!");};
options['Popup an Alert 2!'] = this.menuItemClickHandler.bind(this);
for (const [key, value] of Object.entries(options)) {
let item = document.createElement('div');
item.classList.add('menu-item');
item.setAttribute('data-action', key.toLowerCase());
item.classList.add('hover_changes_color');
item.innerText = `${key}`; // string formatting example
item.addEventListener('click', value);
menu.appendChild(item);
}
return menu;
}
menuItemClickHandler(event) {
this.togglePopup();
const action = event.target.dataset.action;
if (action === 'delete row') {
this.params.api.applyTransaction({ remove: [this.params.data] });
}
if (action === 'popup an alert 2!') {
alert("2");
}
}
}
and in styles.css:
.hover_changes_color:hover {
background-color: dimgrey;
cursor: pointer;
}
I have the following plotly code:
var element = document.getElementById(scope.changeid);
function getData(division,redraw) {
var employeeData = [];
if (!division) {
$http.get(api.getUrl('competenceUserAverageByMyDivisions', null)).success(function (response) {
processData(response,redraw);
});
}
else {
$http.get(api.getUrl('competenceUserAverageByDivision', division)).success(function (response) {
processData(response,redraw);
})
}
}
function processData(data,redraw) {
var y = [],
x1 = [],
x2 = [];
data.forEach(function (item) {
y.push(item.user.profile.firstname);
x1.push(item.current_level);
x2.push(item.expected);
});
var charData = [{
x: y,
y: x1,
type: 'bar',
name: $filter('translate')('COMPETENCES.GRAPH.CURRENT'),
marker: {
color: '#23b7e5'
}
}, {
x:y,
y:x2,
type: 'bar',
marker: {
color: '#f05050'
},
name: $filter('translate')('COMPETENCES.GRAPH.EXPECTED')
}],
layout = {
title: $filter('translate')('USERMANAGEMENT.USERS'),
barmode: 'stack',
legend: {
traceorder: 'reversed'
}
};
Plotly.newPlot(element,charData,layout);
}
scope.$watch('divisionId', function (newValue, oldValue) {
if (newValue) {
getData(newValue.id,true);
}
}, true);
getData(null,false);
This generates the following chart:
<div class="panel-body">
<h4 class="text-center">{{'COMPETENCES.GRAPH_TITLES.OVERVIEW_GAP' | translate}}</h4>
<vertical-bar-chart id="chartArea" goto="competence.titleCompetenceDetails"
changeid="chartArea" xcolumn="xColumn" y-column="yColumn"
dataset="dataSet"
has-addition="true"
style="width: 80%; text-align: center"></vertical-bar-chart>
</div>
As you might be able to tell the text (x column) is being unintentionally cut off. So my question is how can i avoid this? i have attempted to increase the height of the element however without any luck :(
AS you can see here:
(oh you cant tell because of the white background but the height of panel body is 1000 px however it still cuts it off.)
Try increasing the bottom margin in layout.margin.b (more info in the plotlyjs reference page.
For reference, I had the same issue, margin bottom didn't help, but after the graph was created, I ran the following JQuery which revealed the hidden text:
var heightincrease = $('#yourID').find('.svg-container').height() + 100;
$('#yourID').find('.svg-container').height(heightincrease).find('.main-svg').height(heightincrease);
Obviously adjust as required to reveal your whole graph. Will probably break resizing so will need work if that's a concern.
I am able to take screenshot of the page using the example code below:
html2canvas(document.body, {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
Now there are certain div's i dont want to be part of the page when I take the screenshot?
How can i prevent them from being part of the screenshot.
One way I thought was to clone the element and then remove the elements, but taking a screenshot of the clone gives a white screen. Here is the code I used:
html2canvas($(document.body).clone()[0], {
onrendered: function(canvas) {
document.body.appendChild(canvas);
}
});
Add this attribute: data-html2canvas-ignore to any element you don't want to be taken when the screenshot is processed.
Hopefully this will help the next guy.
When I used this library I faced a problem that the lib download all the images in my application, that cause the application to run slowly. I resolved the problem using the ignoreElements option.
This is my code:
var DropAreaElement= document.getElementById("123");
var config= {
useCORS: true,
ignoreElements: function (element) {
if (element.contains(DropAreaElement) || element.parentElement.nodeName =="HTML" || element == DropAreaElement || element.parentNode == DropAreaElement) {
console.log("elements that should be taken: ", element)
return false;
}else {
return true;
}
}
};
html2canvas(DropAreaElement, config).then(function (canvas){
var imgBase64 = canvas.toDataURL('image/jpeg', 0.1);
console.log("imgBase64:", imgBase64);
var imgURL = "data:image/" + imgBase64;
var triggerDownload = $("<a>").attr("href", imgURL).attr("download", "layout_" + new Date().getTime() + ".jpeg").appendTo("body");
triggerDownload[0].click();
triggerDownload.remove();
}).catch(Delegate.create(this, function (e){
console.error("getLayoutImageBase64 Exception:", e);
});
If you don't want to use an attribute, html2canvas does provide a method to remove elements. For example:
html2canvas( document.body, {
ignoreElements: function( element ) {
/* Remove element with id="MyElementIdHere" */
if( 'MyElementIdHere' == element.id ) {
return true;
}
/* Remove all elements with class="MyClassNameHere" */
if( element.classList.contains( 'MyClassNameHere' ) ) {
return true;
}
}
} ).then( function( canvas ) {
document.body.appendChild( canvas );
} );
For more information, see html2canvas options.
You can create HOC for <Printable/> and <NonPrintable/> , you can wrap your component with <NonPrintable><YourCoolComponent/></NonPrintable>
those children components would be excluded.
import React from "react"
interface INonPrintable {
children: React.ReactChildren
}
/*
HOC - Printable which injects the printId to the React component
which gets us Printable Context to html2canvas => jsPDF
eg:
<Printable printId="about-you-print">
<PersonalInfo badEmail={badEmail} />
<IdentityInfo />
<AdditonalInfo />
<AddressInfo
serviceAddress={serviceAddress}
billingAddress={this.state.billingAddress}
setBillingAddress={this.setBillingAddress}
/>
</Printable>
*/
export default function Printable({ printId = "", children, ...restProps }) {
return <div print-id={printId} {...restProps}>{children}</div>
}
/*
HOC - NONPrintable which injects the data-html2canvas-ignore to the React component
which gets us Printable Context to html2canvas => jsPDF
eg:
<NonPrintable style={{display:"flex",justifyContent:'space-around'}}>
<Button
text="Print PDF using Own utility"
onClick={this.handlePrintPdf}
/>
<Button
text="Print PDF using html2canvas + jsPDF"
onClick={this.handlePrintwithPDFjs}
/>
</NonPrintable>
*/
export const NonPrintable = ({ children, ...restProps }) => {
return <div data-html2canvas-ignore {...restProps}>{children}</div>
}