This commit is contained in:
Adam Bradley
2015-12-22 21:45:28 -06:00
parent f409d6add7
commit 98e4336c16
13 changed files with 534 additions and 96 deletions

View File

@ -0,0 +1,129 @@
@import "../../globals.ios";
@import "./alert";
// iOS Alerts
// --------------------------------------------------
$alert-ios-max-width: 270px !default;
$alert-ios-background: rgba(0,0,0,0) !default;
$alert-ios-border-radius: 13px !default;
$alert-ios-background-color: #f8f8f8 !default;
$alert-ios-head-padding: 12px 16px 20px !default;
$alert-ios-head-text-align: center !default;
$alert-ios-title-margin-top: 12px !default;
$alert-ios-title-font-weight: bold !default;
$alert-ios-title-font-size: 17px !default;
$alert-ios-sub-title-font-size: 14px !default;
$alert-ios-sub-title-text-color: #666 !default;
$alert-ios-body-padding: 0px 16px 24px !default;
$alert-ios-body-text-color: inherit !default;
$alert-ios-body-text-align: center !default;
$alert-ios-body-font-size: 13px !default;
$alert-ios-input-padding: 6px !default;
$alert-ios-input-margin-top: 24px !default;
$alert-ios-input-background-color: #fff !default;
$alert-ios-input-border: 1px solid #ccc !default;
$alert-ios-input-border-radius: 4px !default;
$alert-ios-button-min-height: 44px !default;
$alert-ios-button-font-size: 17px !default;
$alert-ios-button-border-color: #c8c7cc !default;
$alert-ios-button-activated-background-color: #e9e9e9 !default;
ion-alert {
background: $alert-ios-background;
}
.alert-wrapper {
border-radius: $alert-ios-border-radius;
background-color: $alert-ios-background-color;
max-width: $alert-ios-max-width;
overflow: hidden;
}
.alert-head {
padding: $alert-ios-head-padding;
text-align: $alert-ios-head-text-align;
}
.alert-title {
margin-top: $alert-ios-title-margin-top;
font-weight: $alert-ios-title-font-weight;
font-size: $alert-ios-title-font-size;
}
.alert-sub-title {
font-size: $alert-ios-sub-title-font-size;
color: $alert-ios-sub-title-text-color;
}
.alert-body {
padding: $alert-ios-body-padding;
color: $alert-ios-body-text-color;
text-align: $alert-ios-body-text-align;
font-size: $alert-ios-body-font-size;
}
.alert-input {
padding: $alert-ios-input-padding;
margin-top: $alert-ios-input-margin-top;
background-color: $alert-ios-input-background-color;
border: $alert-ios-input-border;
border-radius: $alert-ios-input-border-radius;
-webkit-appearance: none;
}
.alert-buttons {
:last-child {
font-weight: bold;
border-right: 0;
}
}
.alert-button {
margin: 0;
flex: 1;
border-radius: 0;
font-size: $alert-ios-button-font-size;
min-height: $alert-ios-button-min-height;
border-right: 1px solid $alert-ios-button-border-color;
&.activated {
opacity: 1;
background-color: $alert-ios-button-activated-background-color;
}
&:hover:not(.disable-hover) {
opacity: 1;
}
&:before {
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1px solid $alert-ios-button-border-color;
content: '';
pointer-events: none;
}
}
&.hairlines {
.alert-input {
border-width: 0.55px;
}
.alert-button {
border-right-width: 0.55px;
&:before {
border-top-width: 0.55px;
}
}
}

View File

@ -0,0 +1,80 @@
@import "../../globals.core";
// Alerts
// --------------------------------------------------
$alert-min-width: 250px !default;
$alert-max-height: 90% !default;
$alert-button-line-height: 20px !default;
$alert-button-font-size: 14px !default;
$alert-button-margin-right: 8px !default;
ion-alert {
position: absolute;
z-index: $z-index-overlay;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
input {
width: 100%;
}
}
.alert-wrapper {
z-index: $z-index-overlay-wrapper;
min-width: $alert-min-width;
max-height: $alert-max-height;
display: flex;
flex-direction: column;
opacity: 0;
}
.alert-title {
margin: 0;
padding: 0;
}
.alert-sub-title {
margin: 5px 0 0 0;
padding: 0;
font-weight: normal;
}
.alert-body {
overflow: auto;
&:empty {
padding: 0;
}
}
.alert-input {
@include placeholder();
border: 0;
background: inherit;
padding: 10px 0;
}
.alert-buttons {
display: flex;
flex-direction: row;
}
.alert-button {
display: block;
margin: 0;
line-height: $alert-button-line-height;
font-size: $alert-button-font-size;
margin-right: $alert-button-margin-right;
}

View File

@ -1,25 +1,217 @@
import {Component, ElementRef, Injectable, Renderer} from 'angular2/core';
import {Component, ElementRef, Renderer} from 'angular2/core';
import {NgClass, NgIf, NgFor, FORM_DIRECTIVES} from 'angular2/common';
import {OverlayController} from '../overlay/overlay-controller';
import {Config} from '../../config/config';
import {Animation} from '../../animations/animation';
import {NavParams} from '../nav/nav-controller';
import {ViewController} from '../nav/view-controller';
import {Animation} from '../../animations/animation';
import {Button} from '../button/button';
import {extend} from '../../util/util';
import {extend, isDefined} from '../../util/util';
@Injectable()
export class Alert {
export class Alert extends ViewController {
constructor(private _ctrl: OverlayController, private _config: Config) {
constructor(opts={}) {
super(null, AlertCmp, opts);
this.data.inputs = this.data.inputs || [];
let buttons = this.data.buttons || [];
this.data.buttons = [];
for (let button of buttons) {
this.addButton(button);
}
this.enterAnimationKey = 'alertEnter';
this.leaveAnimationKey = 'alertLeave';
}
static create() {
let alert = new Alert();
setTitle(title) {
this.data.title = title;
}
return alert;
setSubTitle(subTitle) {
this.data.subTitle = subTitle;
}
setBodyText(text) {
this.data.text = text;
}
addInput(input) {
input.value = isDefined(input.value) ? input.value : '';
this.data.inputs.push(input);
}
addButton(button) {
if (typeof button === 'string') {
button = {
text: button
};
}
this.data.buttons.push(button);
}
close() {
let index = this._nav.indexOf(this);
this._nav.remove(index, { animateFirst: true });
}
onClose(handler) {
this.data.onClose = handler;
}
static create(opts={}) {
return new Alert(opts);
}
}
@Component({
selector: 'ion-alert',
template:
'<div (click)="close()" tappable disable-activated class="backdrop" role="presentation"></div>' +
'<div class="alert-wrapper">' +
'<div class="alert-head">' +
'<h2 class="alert-title" *ngIf="d.title">{{d.title}}</h2>' +
'<h3 class="alert-sub-title" *ngIf="d.subTitle">{{d.subTitle}}</h3>' +
'</div>' +
'<div class="alert-body" *ngIf="d.text">{{d.text}}</div>' +
'<div class="alert-body alert-inputs" *ngIf="d.inputs.length">' +
'<div class="alert-input-wrapper" *ngFor="#i of d.inputs">' +
'<div class="alert-input-title" *ngIf="i.title">{{i.title}}</div>' +
'<input [placeholder]="i.placeholder" [type]="i.input" [value]="i.value" class="alert-input">' +
'</div>' +
'</div>' +
'<div class="alert-buttons">' +
'<button *ngFor="#b of d.buttons" (click)="click(b)" [ngClass]="b.cssClass" class="alert-button">' +
'{{b.text}}' +
'</button>' +
'</div>' +
'</div>',
host: {
'role': 'dialog'
},
directives: [NgClass, NgIf, NgFor]
})
class AlertCmp {
constructor(
private _viewCtrl: ViewController,
elementRef: ElementRef,
params: NavParams,
renderer: Renderer
) {
this.d = params.data;
if (this.d.cssClass) {
renderer.setElementClass(elementRef, this.d.cssClass, true);
}
}
click(button, ev) {
let shouldClose = true;
if (button.handler) {
// a handler has been provided, run it
if (button.handler() === false) {
// if the return value is a false then do not close
shouldClose = false;
}
}
if (shouldClose) {
this.close();
}
}
close() {
this._viewCtrl.close();
}
onPageDidLeave() {
let values = this.d.inputs.map(i => i.value);
this.d.onClose && this.d.onClose(values);
}
}
/**
* Animations for alerts
*/
class AlertPopIn extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = enteringView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.1', '1');
backdrop.fromTo('opacity', '0.01', '0.3');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-pop-in', AlertPopIn);
class AlertPopOut extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = leavingView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '0.9');
backdrop.fromTo('opacity', '0.3', '0');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-pop-out', AlertPopOut);
class AlertMdPopIn extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = enteringView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.1', '1');
backdrop.fromTo('opacity', '0.01', '0.5');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-md-pop-in', AlertMdPopIn);
class AlertMdPopOut extends Animation {
constructor(enteringView, leavingView, opts) {
super(null, opts);
let ele = leavingView.pageRef().nativeElement;
let backdrop = new Animation(ele.querySelector('.backdrop'));
let wrapper = new Animation(ele.querySelector('.alert-wrapper'));
wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '0.9');
backdrop.fromTo('opacity', '0.5', '0');
this
.easing('ease-in-out')
.duration(200)
.add(backdrop, wrapper);
}
}
Animation.register('alert-md-pop-out', AlertMdPopOut);

View File

@ -1,4 +1,4 @@
import {App, Alert} from 'ionic/ionic';
import {App, Alert, OverlayController} from 'ionic/ionic';
@App({
@ -6,7 +6,7 @@ import {App, Alert} from 'ionic/ionic';
})
class E2EApp {
constructor() {
constructor(private overlay: OverlayController) {
this.alertOpen = false;
this.confirmOpen = false;
this.confirmResult = '';
@ -15,12 +15,56 @@ class E2EApp {
}
doAlert() {
debugger;
let alert = Alert.create();
let alert = Alert.create({
title: 'Alert!',
subTitle: 'My alert subtitle',
bodyText: 'My alert body text',
buttons: ['Ok']
});
alert.onClose(() => {
this.alertOpen = false;
});
this.overlay.push(alert).then(() => {
this.alertOpen = true;
});
}
doPrompt() {
let alert = Alert.create();
alert.setTitle('Prompt!');
alert.addInput({
label: 'Input Label',
placeholder: 'Placeholder'
});
alert.addButton({
text: 'Cancel',
handler: () => {
console.log('500ms delayed prompt close');
setTimeout(() => {
console.log('Prompt close');
alert.close();
}, 500);
return false;
}
});
alert.addButton({
text: 'Ok',
handler: () => {
console.log('Prompt Ok');
}
});
alert.onClose(data => {
this.promptOpen = false;
});
this.overlay.push(alert).then(() => {
this.promptOpen = true;
});
}
doConfirm() {

View File

@ -230,10 +230,10 @@ export class NavController extends Ion {
* @param {Object} [opts={}] Any options you want to use pass to transtion
* @returns {Promise} Returns a promise when the transition is completed
*/
push(componentType, params = {}, opts = {}, callback) {
push(componentType, params={}, opts={}, callback) {
if (!componentType) {
let errMsg = 'invalid componentType to push';
console.error(errMsg)
console.error(errMsg);
return Promise.reject(errMsg);
}
@ -245,13 +245,27 @@ export class NavController extends Ion {
return Promise.reject('nav controller actively transitioning');
}
this.setTransitioning(true, 500);
let promise = null;
if (!callback) {
promise = new Promise(res => { callback = res; });
}
// create a new ViewController
let enteringView = new ViewController(this, componentType, params);
enteringView.pageType = opts.pageType;
enteringView.handle = opts.handle || null;
this.pushView(enteringView, opts, callback);
return promise;
}
/**
* @private
*/
pushView(enteringView, opts, callback) {
this.setTransitioning(true, 500);
// do not animate if this is the first in the stack
if (!this._views.length && !opts.animateFirst) {
opts.animate = false;
@ -260,6 +274,10 @@ export class NavController extends Ion {
// default the direction to "forward"
opts.direction = opts.direction || 'forward';
if (!opts.animation) {
opts.animation = this.config.get(enteringView.enterAnimationKey);
}
// the active view is going to be the leaving one (if one exists)
let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true);
@ -268,25 +286,16 @@ export class NavController extends Ion {
leavingView.willUnload();
}
// create a new ViewController
let enteringView = new ViewController(this, componentType, params);
enteringView.shouldDestroy = false;
enteringView.shouldCache = false;
enteringView.pageType = opts.pageType;
enteringView.handle = opts.handle || null;
// add the view to the stack
this._add(enteringView);
if (this.router) {
// notify router of the state change
this.router.stateChange('push', enteringView, params);
this.router.stateChange('push', enteringView, enteringView.params);
}
// start the transition
this._transition(enteringView, leavingView, opts, callback);
return promise;
}
/**
@ -333,6 +342,10 @@ export class NavController extends Ion {
leavingView.willUnload();
}
if (!opts.animation) {
opts.animation = this.config.get(leavingView.leaveAnimationKey);
}
// the entering view is now the new last view
// Note: we might not have an entering view if this is the
// only view on the history stack.
@ -655,9 +668,6 @@ export class NavController extends Ion {
return done(enteringView);
}
if (!opts.animation) {
opts.animation = this.config.get('pageTransition');
}
if (this.config.get('animate') === false) {
opts.animate = false;
}
@ -909,7 +919,7 @@ export class NavController extends Ion {
let providers = this.providers.concat(Injector.resolve([
provide(ViewController, {useValue: viewCtrl}),
provide(NavParams, {useValue: viewCtrl.params})
provide(NavParams, {useValue: viewCtrl.getNavParams()})
]));
let location = this.elementRef;

View File

@ -1,6 +1,5 @@
import {NavParams} from './nav-controller';
/**
* @name ViewController
* @description
@ -18,14 +17,26 @@ import {NavParams} from './nav-controller';
*/
export class ViewController {
constructor(navCtrl, componentType, params = {}) {
this.navCtrl = navCtrl;
constructor(navCtrl, componentType, data={}) {
this.setNav(navCtrl);
this.componentType = componentType;
this.params = new NavParams(params);
this.data = data;
this.instance = {};
this.state = 0;
this._destroys = [];
this._loaded = false;
this.shouldDestroy = false;
this.shouldCache = false;
this.enterAnimationKey = 'pageTransition';
this.leaveAnimationKey = 'pageTransition';
}
setNav(navCtrl) {
this._nav = navCtrl;
}
getNavParams() {
return new NavParams(this.data);
}
/**
@ -35,8 +46,8 @@ export class ViewController {
*/
enableBack() {
// update if it's possible to go back from this nav item
if (this.navCtrl) {
let previousItem = this.navCtrl.getPrevious(this);
if (this._nav) {
let previousItem = this._nav.getPrevious(this);
// the previous view may exist, but if it's about to be destroyed
// it shouldn't be able to go back to
return !!(previousItem && !previousItem.shouldDestroy);
@ -74,7 +85,7 @@ export class ViewController {
* @returns {Number} Returns the index of this page within its NavController.
*/
get index() {
return (this.navCtrl ? this.navCtrl.indexOf(this) : -1);
return (this._nav ? this._nav.indexOf(this) : -1);
}
/**

View File

@ -1,63 +1,31 @@
import {Animation} from '../../animations/animation';
import {extend} from '../../util';
import {Injectable} from 'angular2/core';
import {ViewController} from '../nav/view-controller';
import {Config} from '../../config/config';
import {IonicApp} from '../app/app';
/**
* @private
*/
@Injectable()
export class OverlayController {
open(componentType, params = {}, opts = {}) {
if (!this.nav) {
console.error('<ion-overlay></ion-overlay> required in root template (app.html) to use: ' + opts.pageType);
return Promise.reject();
}
constructor(private _config: Config) {}
let resolve, reject;
let promise = new Promise((res, rej) => { resolve = res; reject = rej; });
opts.animation = opts.enterAnimation;
push(overlayView, opts={}) {
overlayView.setNav(this._nav);
opts.animateFirst = true;
this.nav.push(componentType, params, opts).then(viewCtrl => {
if (viewCtrl && viewCtrl.instance) {
let self = this;
function escape(ev) {
if (ev.keyCode == 27 && self.nav.last() === viewCtrl) {
viewCtrl.instance.close();
}
}
viewCtrl.instance.close = (data, closeOpts={}) => {
extend(opts, closeOpts);
opts.animation = opts.leaveAnimation;
viewCtrl.instance.onClose && viewCtrl.instance.onClose(data);
this.nav.pop(opts);
document.removeEventListener('keyup', escape, true);
};
document.addEventListener('keyup', escape, true);
resolve(viewCtrl.instance);
} else {
reject();
}
}, rejectReason => {
console.error(rejectReason);
return new Promise(resolve => {
this._nav.pushView(overlayView, opts, resolve);
});
return promise;
}
getByType(overlayType) {
let overlay = this.nav.getByType(overlayType);
return overlay && overlay.instance;
pop(opts={}) {
opts.animateFirst = true;
return this._nav.pop(opts);
}
getByHandle(handle, overlayType) {
let overlay = this.nav.getByHandle(handle);
return overlay && overlay.instance;
setNav(nav) {
this._nav = nav;
}
}

View File

@ -35,7 +35,7 @@ export class OverlayNav extends NavController {
}
this.initZIndex = 1000;
overlayCtrl.nav = this;
overlayCtrl.setNav(this);
}
}