feat(popover): change template in popover to a page similar to modal

add ability to pass event in the transition, so on present of the
popover, clean up the positioning

references #5420
This commit is contained in:
Brandy Carney
2016-05-23 14:15:17 -04:00
parent 53fd3c3973
commit a96e36aa0e
9 changed files with 133 additions and 114 deletions

View File

@ -1157,7 +1157,8 @@ export class NavController extends Ion {
duration: opts.duration, duration: opts.duration,
easing: opts.easing, easing: opts.easing,
renderDelay: opts.transitionDelay || this._trnsDelay, renderDelay: opts.transitionDelay || this._trnsDelay,
isRTL: this.config.platform.isRTL() isRTL: this.config.platform.isRTL(),
ev: opts.ev,
}; };
let transAnimation = Transition.createTransition(enteringView, let transAnimation = Transition.createTransition(enteringView,
@ -1779,6 +1780,7 @@ export interface NavOptions {
postLoad?: Function; postLoad?: Function;
progressAnimation?: boolean; progressAnimation?: boolean;
climbNav?: boolean; climbNav?: boolean;
ev?: any;
} }
const STATE_ACTIVE = 'active'; const STATE_ACTIVE = 'active';

View File

@ -4,7 +4,6 @@
// iOS Popover // iOS Popover
// -------------------------------------------------- // --------------------------------------------------
$popover-ios-padding: 24px 34px !default;
$popover-ios-min-width: 150px !default; $popover-ios-min-width: 150px !default;
$popover-ios-max-width: 270px !default; $popover-ios-max-width: 270px !default;
$popover-ios-max-height: 90% !default; $popover-ios-max-height: 90% !default;
@ -14,8 +13,6 @@ $popover-ios-background: #f3f3f3 !default;
.popover-wrapper { .popover-wrapper {
padding: $popover-ios-padding;
min-width: $popover-ios-min-width; min-width: $popover-ios-min-width;
max-width: $popover-ios-max-width; max-width: $popover-ios-max-width;
@ -34,8 +31,8 @@ $popover-ios-background: #f3f3f3 !default;
position: absolute; position: absolute;
display: block; display: block;
top: -20px; top: -20px;
width: 30px; width: 20px;
height: 19px; height: 10px;
overflow: hidden; overflow: hidden;
&:after { &:after {
@ -43,14 +40,15 @@ $popover-ios-background: #f3f3f3 !default;
z-index: $z-index-overlay-wrapper; z-index: $z-index-overlay-wrapper;
top: 12px; top: 3px;
left: 5px; left: 3px;
width: 20px; width: 14px;
height: 20px; height: 14px;
background-color: $popover-ios-background; background-color: $popover-ios-background;
border-radius: 3px; border-radius: 3px;
content: ''; content: '';
transform: rotate(-45deg); transform: rotate(45deg);
} }
} }

View File

@ -4,7 +4,6 @@
// Material Design Popover // Material Design Popover
// -------------------------------------------------- // --------------------------------------------------
$popover-md-padding: 24px 34px !default;
$popover-md-min-width: 150px !default; $popover-md-min-width: 150px !default;
$popover-md-max-width: 270px !default; $popover-md-max-width: 270px !default;
$popover-md-max-height: 90% !default; $popover-md-max-height: 90% !default;
@ -14,8 +13,6 @@ $popover-md-background: #fafafa !default;
.popover-wrapper { .popover-wrapper {
padding: $popover-md-padding;
min-width: $popover-md-min-width; min-width: $popover-md-min-width;
max-width: $popover-md-max-width; max-width: $popover-md-max-width;
@ -25,11 +22,3 @@ $popover-md-background: #fafafa !default;
color: $popover-md-text-color; color: $popover-md-text-color;
background: $popover-md-background; background: $popover-md-background;
} }
// Material Design Popover Template
// -----------------------------------------
.popover-template {
}

View File

@ -3,8 +3,8 @@
// Popover // Popover
// -------------------------------------------------- // --------------------------------------------------
$popover-min-width: 150px !default; $popover-width: 200px !default;
$popover-max-height: 90% !default; $popover-height: 250px !default;
ion-popover { ion-popover {
@ -29,10 +29,20 @@ ion-popover {
flex-direction: column; flex-direction: column;
min-width: $popover-min-width; width: $popover-width;
max-height: $popover-max-height; height: $popover-height;
min-width: $popover-width;
max-height: $popover-height;
overflow: hidden;
opacity: 0; opacity: 0;
ion-page {
display: flex;
overflow: auto;
}
} }

View File

@ -1,13 +1,15 @@
import {Component, Renderer, ElementRef, HostListener, ViewEncapsulation} from '@angular/core'; import {Component, ViewChild, ViewContainerRef, DynamicComponentLoader} from '@angular/core';
import {Renderer, ElementRef} from '@angular/core';
import {Animation} from '../../animations/animation'; import {Animation} from '../../animations/animation';
import {Transition, TransitionOptions} from '../../transitions/transition'; import {Transition, TransitionOptions} from '../../transitions/transition';
import {Config} from '../../config/config'; import {Config} from '../../config/config';
import {NavParams} from '../nav/nav-params'; import {NavParams} from '../nav/nav-params';
import {Platform} from '../../platform/platform'
import {isPresent, isUndefined, isDefined} from '../../util/util'; import {isPresent, isUndefined, isDefined} from '../../util/util';
import {ViewController} from '../nav/view-controller'; import {ViewController} from '../nav/view-controller';
const POPOVER_BODY_PADDING = 6; const POPOVER_BODY_PADDING = 2;
/** /**
* @name Popover * @name Popover
@ -16,14 +18,15 @@ const POPOVER_BODY_PADDING = 6;
*/ */
export class Popover extends ViewController { export class Popover extends ViewController {
constructor(opts: PopoverOptions = {}) { constructor(componentType, data: any = {}, opts: PopoverOptions = {}) {
opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true; opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true;
opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true; opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true;
super(PopoverCmp, opts); data.componentType = componentType;
data.opts = opts;
super(PopoverCmp, data);
this.viewType = 'popover'; this.viewType = 'popover';
this.isOverlay = true; this.isOverlay = true;
this.usePortal = false;
// by default, popovers should not fire lifecycle events of other views // by default, popovers should not fire lifecycle events of other views
// for example, when a popover enters, the current active view should // for example, when a popover enters, the current active view should
@ -44,16 +47,16 @@ export class Popover extends ViewController {
* *
* | Option | Type | Description | * | Option | Type | Description |
* |-----------------------|------------|------------------------------------------------------------------------------------------------------------------| * |-----------------------|------------|------------------------------------------------------------------------------------------------------------------|
* | template |`string` | The html content for the popover. |
* | cssClass |`string` | An additional class for custom styles. | * | cssClass |`string` | An additional class for custom styles. |
* | showBackdrop |`boolean` | Whether to show the backdrop. Default true. | * | showBackdrop |`boolean` | Whether to show the backdrop. Default true. |
* | enableBackdropDismiss |`boolean` | Wheather the popover should be dismissed by tapping the backdrop. Default true. | * | enableBackdropDismiss |`boolean` | Wheather the popover should be dismissed by tapping the backdrop. Default true. |
* *
* *
* @param {object} data Any data to pass to the popover view
* @param {object} opts Popover options * @param {object} opts Popover options
*/ */
static create(opts: PopoverOptions = {}) { static create(componentType, data = {}, opts: PopoverOptions = {}) {
return new Popover(opts); return new Popover(componentType, data, opts);
} }
} }
@ -64,30 +67,29 @@ export class Popover extends ViewController {
@Component({ @Component({
selector: 'ion-popover', selector: 'ion-popover',
template: template:
'<div disable-activated class="backdrop" (click)="bdClick()" [class.hide-backdrop]="!d.showBackdrop" role="presentation"></div>' + '<div disable-activated class="backdrop" (click)="bdClick()" [class.hide-backdrop]="!d.showBackdrop"></div>' +
'<div class="popover-arrow"></div>' + '<div class="popover-arrow"></div>' +
'<div class="popover-wrapper">' + '<div class="popover-wrapper">' +
'<div *ngIf="d.template" [innerHTML]="d.template" class="popover-template"></div>' + '<div #viewport></div>' +
'</div>', '</div>'
host: {
'role': 'dialog'
},
encapsulation: ViewEncapsulation.None,
}) })
class PopoverCmp { class PopoverCmp {
@ViewChild('viewport', {read: ViewContainerRef}) viewport: ViewContainerRef;
private d: any; private d: any;
private id: number; private id: number;
private created: number; private created: number;
private showSpinner: boolean; private showSpinner: boolean;
constructor( constructor(private _loader: DynamicComponentLoader,
private _viewCtrl: ViewController,
private _config: Config,
private _elementRef: ElementRef, private _elementRef: ElementRef,
private _renderer: Renderer, private _renderer: Renderer,
params: NavParams private _config: Config,
private _navParams: NavParams,
private _viewCtrl: ViewController,
private _platform: Platform
) { ) {
this.d = params.data; this.d = _navParams.data.opts;
this.created = Date.now(); this.created = Date.now();
if (this.d.cssClass) { if (this.d.cssClass) {
@ -97,9 +99,18 @@ class PopoverCmp {
this.id = (++popoverIds); this.id = (++popoverIds);
} }
onPageWillEnter() {
this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => {
this._viewCtrl.setInstance(componentRef.instance);
// manually fire onPageWillEnter() since ModalCmp's onPageWillEnter already happened
this._viewCtrl.willEnter();
});
}
ngOnInit() { ngOnInit() {
if (this.d.element && this.d.event) { if (this.d.event) {
this.positionView(this.d.element, this.d.event); this.positionView(this.d.event);
} }
} }
@ -110,71 +121,64 @@ class PopoverCmp {
} }
} }
positionView(targetEle, ev) { positionView(ev) {
let popoverEle = this._elementRef.nativeElement; let nativeEle = this._elementRef.nativeElement;
let popoverWrapperEle = popoverEle.querySelector('.popover-wrapper');
// Popover width and height // Popover wrapper width and height
let popoverWidth = popoverWrapperEle.offsetWidth; let popoverEle = nativeEle.querySelector('.popover-wrapper');
let popoverHeight = popoverWrapperEle.offsetHeight; let popoverDim = popoverEle.getBoundingClientRect();
let popoverWidth = popoverDim.width;
let popoverHeight = popoverDim.height;
// Window body width and height // Window body width and height
let bodyWidth = window.innerWidth; let bodyWidth = this._platform.width();
let bodyHeight = window.innerHeight; let bodyHeight = this._platform.height();
// Clicked element width and height // Target element width and height
targetEle = targetEle._elementRef.nativeElement; let targetDim = ev.target.getBoundingClientRect();
let targetWidth = targetEle.offsetWidth; let targetTop = targetDim.top;
let targetHeight = targetEle.offsetHeight; let targetLeft = targetDim.left;
let targetWidth = targetDim.width;
// console.log("Popover Wrapper Element", popoverWrapperEle); let targetHeight = targetDim.height;
// console.log("Popover Wrapper Width & Height", popoverWidth, popoverHeight);
// console.log("Body Width & Height", bodyWidth, bodyHeight);
// console.log("Target", targetEle);
// console.log("Target Width & Height", targetWidth, targetHeight);
let popoverCSS = {
top: ev.clientY + targetHeight - (popoverHeight / 2),
left: ev.clientX - popoverWidth / 2
};
// The arrow that shows above the popover on iOS // The arrow that shows above the popover on iOS
var arrowEle = popoverEle.querySelector('.popover-arrow'); var arrowEle = nativeEle.querySelector('.popover-arrow');
var arrowWidth = arrowEle.offsetWidth; let arrowDim = arrowEle.getBoundingClientRect();
var arrowHeight = arrowEle.offsetHeight; var arrowWidth = arrowDim.width;
var arrowHeight = arrowDim.height;
let arrowLeft = targetWidth + targetWidth / 2 -
arrowEle.offsetWidth / 2 - popoverCSS.left;
let arrowCSS = { let arrowCSS = {
top: ev.clientY + targetHeight - (popoverHeight / 2) - arrowHeight, top: targetTop + targetHeight,
left: ev.clientX - (arrowWidth / 2) left: targetLeft + (targetWidth / 2) - (arrowWidth / 2)
} }
let popoverCSS = {
top: targetTop + targetHeight + (arrowHeight - 1),
left: targetLeft + (targetWidth / 2) - (popoverWidth / 2)
};
// If the popover left is less than the padding it is off screen // If the popover left is less than the padding it is off screen
// to the left so adjust it, else if the width of the popover // to the left so adjust it, else if the width of the popover
// exceeds the body width it is off screen to the right so adjust // exceeds the body width it is off screen to the right so adjust
if (popoverCSS.left < POPOVER_BODY_PADDING) { if (popoverCSS.left < POPOVER_BODY_PADDING) {
popoverCSS.left = POPOVER_BODY_PADDING; popoverCSS.left = POPOVER_BODY_PADDING;
arrowCSS.left = (POPOVER_BODY_PADDING * 2);
} else if (popoverWidth + POPOVER_BODY_PADDING + popoverCSS.left > bodyWidth) { } else if (popoverWidth + POPOVER_BODY_PADDING + popoverCSS.left > bodyWidth) {
popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING; popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
arrowCSS.left = bodyWidth - (POPOVER_BODY_PADDING * 2) - arrowWidth;
} }
// If the popover when popped down stretches past bottom of screen, // If the popover when popped down stretches past bottom of screen,
// make it pop up if there's room above // make it pop up if there's room above
if (popoverCSS.top + POPOVER_BODY_PADDING + popoverHeight > bodyHeight && if (popoverCSS.top + POPOVER_BODY_PADDING + popoverHeight > bodyHeight && popoverCSS.top - popoverHeight > 0) {
popoverCSS.top - popoverHeight > 0) { arrowCSS.top = targetTop - (arrowHeight + 1);
popoverCSS.top = popoverCSS.top - targetHeight - popoverHeight; popoverCSS.top = targetTop - popoverHeight - (arrowHeight - 1);
this._renderer.setElementClass(this._elementRef.nativeElement, 'popover-bottom', true); this._renderer.setElementClass(this._elementRef.nativeElement, 'popover-bottom', true);
} }
this._renderer.setElementStyle(arrowEle, 'top', arrowCSS.top + 'px'); this._renderer.setElementStyle(arrowEle, 'top', arrowCSS.top + 'px');
this._renderer.setElementStyle(arrowEle, 'left', arrowCSS.left + 'px'); this._renderer.setElementStyle(arrowEle, 'left', arrowCSS.left + 'px');
this._renderer.setElementStyle(popoverWrapperEle, 'top', popoverCSS.top + 'px'); this._renderer.setElementStyle(popoverEle, 'top', popoverCSS.top + 'px');
this._renderer.setElementStyle(popoverWrapperEle, 'left', popoverCSS.left + 'px'); this._renderer.setElementStyle(popoverEle, 'left', popoverCSS.left + 'px');
} }
dismiss(role): Promise<any> { dismiss(role): Promise<any> {
@ -194,8 +198,6 @@ class PopoverCmp {
} }
export interface PopoverOptions { export interface PopoverOptions {
template?: string;
element?: any;
event?: any; event?: any;
cssClass?: string; cssClass?: string;
showBackdrop?: boolean; showBackdrop?: boolean;
@ -209,6 +211,8 @@ class PopoverPopIn extends Transition {
constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
super(opts); super(opts);
console.log(opts);
let ele = enteringView.pageRef().nativeElement; let ele = enteringView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop')); let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.popover-wrapper')); let wrapper = new Animation(ele.querySelector('.popover-wrapper'));

View File

@ -4,18 +4,15 @@
// Windows Popover // Windows Popover
// -------------------------------------------------- // --------------------------------------------------
$popover-wp-padding: 24px 34px !default;
$popover-wp-min-width: 150px !default; $popover-wp-min-width: 150px !default;
$popover-wp-max-width: 270px !default; $popover-wp-max-width: 270px !default;
$popover-wp-max-height: 90% !default; $popover-wp-max-height: 90% !default;
$popover-wp-border-radius: 2px !default; $popover-wp-border-radius: 2px !default;
$popover-wp-text-color: #fff !default; $popover-wp-text-color: #000 !default;
$popover-wp-background: #000 !default; $popover-wp-background: #fff !default;
.popover-wrapper { .popover-wrapper {
padding: $popover-wp-padding;
min-width: $popover-wp-min-width; min-width: $popover-wp-min-width;
max-width: $popover-wp-max-width; max-width: $popover-wp-max-width;
@ -25,11 +22,3 @@ $popover-wp-background: #000 !default;
color: $popover-wp-text-color; color: $popover-wp-text-color;
background: $popover-wp-background; background: $popover-wp-background;
} }
// Windows Popover Template
// -----------------------------------------
.popover-template {
}

View File

@ -1,24 +1,39 @@
import {App, Page, Popover, NavController} from '../../../../../src'; import {App, Page, Popover, NavController} from '../../../../../src';
@Page({
template: `
<ion-list>
<ion-list-header>Ionic</ion-list-header>
<button ion-item>Learn Ionic</button>
<button ion-item>Documentation</button>
<button ion-item>Showcase</button>
<button ion-item>GitHub Repo</button>
</ion-list>
`
})
class PopoverPage {
}
@Page({ @Page({
templateUrl: 'main.html' templateUrl: 'main.html'
}) })
class E2EPage { class E2EPage {
constructor(private nav: NavController) {} constructor(private nav: NavController) {}
presentPopover(ele, ev) { presentPopover(ev) {
console.log(ev); let popover = Popover.create(PopoverPage, {}, {
let popover = Popover.create({
template: `
My Popover
`,
element: ele,
event: ev event: ev
}); });
this.nav.present(popover); this.nav.present(popover);
this.nav.present(popover, {
ev: ev
});
} }
} }

View File

@ -1,7 +1,18 @@
<ion-navbar *navbar> <ion-navbar *navbar primary>
<ion-buttons start>
<button (click)="presentPopover($event)">
<ion-icon name="options"></ion-icon>
</button>
<button (click)="presentPopover($event)">
<ion-icon name="more"></ion-icon>
</button>
</ion-buttons>
<ion-title>Popover</ion-title> <ion-title>Popover</ion-title>
<ion-buttons end> <ion-buttons end>
<button #popover (click)="presentPopover(popover, $event)"> <button (click)="presentPopover($event)">
<ion-icon name="options"></ion-icon>
</button>
<button (click)="presentPopover($event)">
<ion-icon name="more"></ion-icon> <ion-icon name="more"></ion-icon>
</button> </button>
</ion-buttons> </ion-buttons>
@ -9,7 +20,7 @@
<ion-content padding> <ion-content padding>
<button #element block (click)="presentPopover(element, $event)"> <button block (click)="presentPopover($event)">
Present Popover Present Popover
</button> </button>
@ -18,7 +29,7 @@
<ion-toolbar position="bottom"> <ion-toolbar position="bottom">
<ion-title>Popover</ion-title> <ion-title>Popover</ion-title>
<ion-buttons start> <ion-buttons start>
<button #bottomPopover (click)="presentPopover(bottomPopover, $event)"> <button (click)="presentPopover($event)">
<ion-icon name="more"></ion-icon> <ion-icon name="more"></ion-icon>
</button> </button>
</ion-buttons> </ion-buttons>

View File

@ -36,6 +36,7 @@ export interface TransitionOptions {
direction: string; direction: string;
renderDelay?: number; renderDelay?: number;
isRTL?: boolean; isRTL?: boolean;
ev?: any;
} }
let TransitionRegistry = {}; let TransitionRegistry = {};