Skip to content

Commit

Permalink
Fixed #9677 - Splitter Component
Browse files Browse the repository at this point in the history
  • Loading branch information
yigitfindikli committed Dec 21, 2020
1 parent d495ce3 commit 1a788eb
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 35 deletions.
4 changes: 4 additions & 0 deletions src/app/components/splitter/splitter.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
display: flex;
}

.p-splitter-panel p-splitter {
flex-grow: 1;
}

.p-splitter-panel .p-splitter {
flex-grow: 1;
border: 0 none;
Expand Down
266 changes: 231 additions & 35 deletions src/app/components/splitter/splitter.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,56 @@
import { NgModule, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, ContentChildren, QueryList, ElementRef, Inject, forwardRef, ChangeDetectorRef, TemplateRef, Directive } from '@angular/core';
import { NgModule, Component, ChangeDetectionStrategy, ViewEncapsulation, Input, ContentChildren, QueryList, ElementRef, ChangeDetectorRef, TemplateRef, ViewChild, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DomHandler } from '../dom/domhandler';
import { Subscription } from 'rxjs';
import { DomHandler } from 'primeng/dom';

@Component({
selector: 'p-splitter',
template: `
<div [ngClass]="containerClass()" [class]="styleClass" [ngStyle]="style">
<ng-content></ng-content>
<div #container [ngClass]="containerClass()" [class]="styleClass" [ngStyle]="style">
<ng-template ngFor let-panel let-i="index" [ngForOf]="panels">
<div [ngClass]="panelContainerClass()" [class]="panelStyleClass">
<ng-container *ngTemplateOutlet="panel"></ng-container>
</div>
<div class="p-splitter-gutter" *ngIf="i !== (panels.length - 1)" [ngStyle]="gutterStyle()"
(mousedown)="onGutterMouseDown($event, i)" (touchstart)="onGutterTouchStart($event, i)" (touchmove)="onGutterTouchMove($event, i)" (touchend)="onGutterTouchEnd($event, i)">
<div class="p-splitter-gutter-handle"></div>
</div>
</ng-template>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
styleUrls: ['./splitter.css']
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./splitter.css'],
host: {
'[class.p-splitter-panel-nested]': 'nested'
}
})
export class Splitter {

@Input() styleClass: string;

@Input() panelStyleClass: string;

@Input() style: any;

@Input() stateStorage: string = "session";

@Input() stateKey: string;
@Input() stateKey: string = null;

@Input() layout: string = "horizontal";

@Input() gutterSize: number = 4;

// @ContentChildren(SplitterPanel) panelList: QueryList<SplitterPanel>;
@Input() panelSizes: number[] = [];

@Input() minSizes: number[] = [];

// panels: SplitterPanel[] = [];
@Output() onResizeEnd: EventEmitter<any> = new EventEmitter();

@ContentChildren(TemplateRef) panels: QueryList<any>;

@ViewChild('container', { static: false }) containerViewChild: ElementRef;

nested = false;

dragging = false;

Expand All @@ -52,39 +72,203 @@ export class Splitter {

prevPanelSize = null;

panelSizes = null;
_panelSizes = null;

prevPanelIndex = null;

panelListSubscription: Subscription;

constructor(public cd: ChangeDetectorRef, private el: ElementRef) { }

ngOnInit() {
this.nested = this.isNested();
}

ngAfterContentInit() {
// this.panels = this.panelList.toArray();
ngAfterViewInit() {
if (this.panels && this.panels.length) {
let initialized = false;
if (this.isStateful()) {
initialized = this.restoreState();
}

if (!initialized) {
let children = [...this.el.nativeElement.children[0].children].filter(child => DomHandler.hasClass(child, 'p-splitter-panel'));
let _panelSizes = [];

this.panels.map((panel, i) => {
let panelInitialSize = this.panelSizes.length -1 >= i ? this.panelSizes[i]: null;
let panelSize = panelInitialSize || (100 / this.panels.length);
_panelSizes[i] = panelSize;
children[i].style.flexBasis = 'calc(' + panelSize + '% - ' + ((this.panels.length - 1) * this.gutterSize) + 'px)';
});

this._panelSizes = _panelSizes;
}
}
}

onResizeStart(event, index) {
this.gutterElement = event.currentTarget;
this.size = this.horizontal() ? DomHandler.getWidth(this.containerViewChild.nativeElement) : DomHandler.getHeight(this.containerViewChild.nativeElement);
this.dragging = true;
this.startPos = this.horizontal() ? event.pageX : event.pageY;
this.prevPanelElement = this.gutterElement.previousElementSibling;
this.nextPanelElement = this.gutterElement.nextElementSibling;
this.prevPanelSize = 100 * (this.horizontal() ? DomHandler.getOuterWidth(this.prevPanelElement, true): DomHandler.getOuterHeight(this.prevPanelElement, true)) / this.size;
this.nextPanelSize = 100 * (this.horizontal() ? DomHandler.getOuterWidth(this.nextPanelElement, true): DomHandler.getOuterHeight(this.nextPanelElement, true)) / this.size;
this.prevPanelIndex = index;
DomHandler.addClass(this.gutterElement, 'p-splitter-gutter-resizing');
DomHandler.addClass(this.containerViewChild.nativeElement, 'p-splitter-resizing');
}

// if (this.panels && this.panels.length) {
// let initialized = false;
// if (!initialized) {
// let children = [...this.panels].filter(child => DomHandler.hasClass(child.el.nativeElement.children[0], 'p-splitter-panel'));
// let _panelSizes = [];
onResize(event) {
let newPos;
if (this.horizontal())
newPos = (event.pageX * 100 / this.size) - (this.startPos * 100 / this.size);
else
newPos = (event.pageY * 100 / this.size) - (this.startPos * 100 / this.size);

let newPrevPanelSize = this.prevPanelSize + newPos;
let newNextPanelSize = this.nextPanelSize - newPos;

if (this.validateResize(newPrevPanelSize, newNextPanelSize)) {
this.prevPanelElement.style.flexBasis = 'calc(' + newPrevPanelSize + '% - ' + ((this.panels.length - 1) * this.gutterSize) + 'px)';
this.nextPanelElement.style.flexBasis = 'calc(' + newNextPanelSize + '% - ' + ((this.panels.length - 1) * this.gutterSize) + 'px)';
this._panelSizes[this.prevPanelIndex] = newPrevPanelSize;
this._panelSizes[this.prevPanelIndex + 1] = newNextPanelSize;
}
}

// this.panels.map((panel, i) => {
// let panelInitialSize = panel && panel.size ? panel.size : null;
// let panelSize = panelInitialSize || (100 / this.panels.length);
// _panelSizes[i] = panelSize;
// children[i].style.flexBasis = 'calc(' + panelSize + '% - ' + ((this.panels.length - 1) * this.gutterSize) + 'px)';
// children[i];
// });
resizeEnd(event) {
if (this.isStateful()) {
this.saveState();
}

this.onResizeEnd.emit({originalEvent: event, sizes: this._panelSizes})
DomHandler.removeClass(this.gutterElement, 'p-splitter-gutter-resizing');
DomHandler.removeClass(this.containerViewChild.nativeElement, 'p-splitter-resizing');
this.clear();
}

// this.panelSizes = _panelSizes;
// }
// }
onGutterMouseDown(event, index) {
this.onResizeStart(event, index);
this.bindMouseListeners();
}

this.cd.markForCheck();
onGutterTouchStart(event, index) {
this.onResizeStart(event, index);
event.preventDefault();
}

onGutterTouchMove(event) {
this.onResize(event);
event.preventDefault();
}

onGutterTouchEnd(event) {
this.resizeEnd(event);
event.preventDefault();
}

validateResize(newPrevPanelSize, newNextPanelSize) {
if (this.minSizes.length >= 1 && this.minSizes[0] && this.minSizes[0] > newPrevPanelSize) {
return false;
}

if (this.minSizes.length > 1 && this.minSizes[1] && this.minSizes[1] > newNextPanelSize) {
return false;
}

return true;
}

bindMouseListeners() {
if (!this.mouseMoveListener) {
this.mouseMoveListener = event => this.onResize(event)
document.addEventListener('mousemove', this.mouseMoveListener);
}

if (!this.mouseUpListener) {
this.mouseUpListener = event => {
this.resizeEnd(event);
this.unbindMouseListeners();
}
document.addEventListener('mouseup', this.mouseUpListener);
}
}

unbindMouseListeners() {
if (this.mouseMoveListener) {
document.removeEventListener('mousemove', this.mouseMoveListener);
this.mouseMoveListener = null;
}

if (this.mouseUpListener) {
document.removeEventListener('mouseup', this.mouseUpListener);
this.mouseUpListener = null;
}
}

clear() {
this.dragging = false;
this.size = null;
this.startPos = null;
this.prevPanelElement = null;
this.nextPanelElement = null;
this.prevPanelSize = null;
this.nextPanelSize = null;
this.gutterElement = null;
this.prevPanelIndex = null;
}

isNested() {
if (this.el.nativeElement) {
let parent = this.el.nativeElement.parentElement;
while (parent && !DomHandler.hasClass(parent, 'p-splitter')) {
parent = parent.parentElement;
}

return parent !== null;
}
else {
return false;
}
}

isStateful() {
return this.stateKey != null;
}

getStorage() {
switch(this.stateStorage) {
case 'local':
return window.localStorage;

case 'session':
return window.sessionStorage;

default:
throw new Error(this.stateStorage + ' is not a valid value for the state storage, supported values are "local" and "session".');
}
}

saveState() {
this.getStorage().setItem(this.stateKey, JSON.stringify(this._panelSizes));
}

restoreState() {
const storage = this.getStorage();
const stateString = storage.getItem(this.stateKey);

if (stateString) {
this._panelSizes = JSON.parse(stateString);
let children = [...this.containerViewChild.nativeElement.children].filter(child => DomHandler.hasClass(child, 'p-splitter-panel'));
children.forEach((child, i) => {
child.style.flexBasis = 'calc(' + this._panelSizes[i] + '% - ' + ((this.panels.length - 1) * this.gutterSize) + 'px)';
});

return true;
}

return false;
}

containerClass() {
Expand All @@ -95,10 +279,22 @@ export class Splitter {
};
}

ngOnDestroy() {
if (this.panelListSubscription) {
this.panelListSubscription.unsubscribe();
}
panelContainerClass() {
return {
'p-splitter-panel': true,
'p-splitter-panel-nested': true
};
}

gutterStyle() {
if (this.horizontal())
return {width: this.gutterSize + 'px'};
else
return {height: this.gutterSize + 'px'};
}

horizontal() {
return this.layout === 'horizontal';
}
}

Expand Down
61 changes: 61 additions & 0 deletions src/app/showcase/components/splitter/splitterdemo.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,68 @@ <h1>Splitter</h1>

<div class="content-section implementation">
<div class="card">
<h5>Horizontal</h5>
<p-splitter [style]="{'height': '300px'}" styleClass="p-mb-5">
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 1
</div>
</ng-template>
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 2
</div>
</ng-template>
</p-splitter>
</div>

<div class="card">
<h5>Vertical</h5>
<p-splitter [style]="{'height': '300px'}" styleClass="p-mb-5" layout="vertical">
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 1
</div>
</ng-template>
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 2
</div>
</ng-template>
</p-splitter>
</div>

<div class="card">
<h5>Nested</h5>
<p-splitter [style]="{'height': '300px'}" [panelSizes]="[20,80]" [minSizes]="[20,0]" styleClass="p-mb-5">
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 1
</div>
</ng-template>
<ng-template>
<p-splitter layout="vertical" [panelSizes]="[15,85]">
<ng-template>
<div style="flex-grow: 1;" class="p-d-flex p-ai-center p-jc-center">
Panel 2
</div>
</ng-template>
<ng-template>
<p-splitter [panelSizes]="[20,80]">
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 3
</div>
</ng-template>
<ng-template>
<div class="p-col p-d-flex p-ai-center p-jc-center">
Panel 4
</div>
</ng-template>
</p-splitter>
</ng-template>
</p-splitter>
</ng-template>
</p-splitter>
</div>
</div>
Expand Down

0 comments on commit 1a788eb

Please sign in to comment.