refactor(overlay): actionsheet/popup

This commit is contained in:
Adam Bradley
2015-10-04 22:35:54 -05:00
parent dfe19a962d
commit 3324f3cc90
13 changed files with 264 additions and 262 deletions

View File

@ -6,10 +6,11 @@
* The ActionSheet is a modal menu with options to select based on an action.
*/
import {View, Injectable, NgFor, NgIf} from 'angular2/angular2';
import {Component, View, Injectable, NgFor, NgIf} from 'angular2/angular2';
import {OverlayController} from '../overlay/overlay-controller';
import {IonicConfig} from '../../config/config';
import {Icon} from '../icon/icon';
import {Overlay} from '../overlay/overlay';
import {Animation} from '../../animations/animation';
import * as util from 'ionic/util';
@ -52,6 +53,9 @@ import * as util from 'ionic/util';
* }
* ```
*/
@Component({
selector: 'ion-action-sheet'
})
@View({
template:
'<backdrop (click)="_cancel()" tappable disable-activated></backdrop>' +
@ -76,30 +80,42 @@ import * as util from 'ionic/util';
'</action-sheet-wrapper>',
directives: [NgFor, NgIf, Icon]
})
class ActionSheetDirective {
class ActionSheetCmp {
_cancel() {
this.cancel && this.cancel();
return this.overlayRef.close();
return this.close();
}
_destructive() {
let shouldClose = this.destructiveButtonClicked();
if (shouldClose === true) {
return this.overlayRef.close();
return this.close();
}
}
_buttonClicked(index) {
let shouldClose = this.buttonClicked(index);
if (shouldClose === true) {
return this.overlayRef.close();
return this.close();
}
}
}
@Injectable()
export class ActionSheet extends Overlay {
export class ActionSheet {
constructor(ctrl: OverlayController, config: IonicConfig) {
this.ctrl = ctrl;
this._defaults = {
enterAnimation: config.get('actionSheetEnter'),
leaveAnimation: config.get('actionSheetLeave'),
cancelIcon: config.get('actionSheetCancelIcon'),
destructiveIcon: config.get('actionSheetDestructiveIcon')
};
}
/**
* Create and open a new Action Sheet. This is the
* public API, and most often you will only use ActionSheet.open()
@ -108,25 +124,18 @@ export class ActionSheet extends Overlay {
* @return {Promise} Promise that resolves when the action sheet is open.
*/
open(opts={}) {
let config = this.config;
let defaults = {
enterAnimation: config.get('actionSheetEnter'),
leaveAnimation: config.get('actionSheetLeave'),
cancelIcon: config.get('actionSheetCancelIcon'),
destructiveIcon: config.get('actionSheetDestructiveIcon')
};
let context = util.extend(defaults, opts);
return this.create(OVERLAY_TYPE, ActionSheetDirective, context, context);
return this.ctrl.open(OVERLAY_TYPE, ActionSheetCmp, util.extend(this._defaults, opts));
}
/**
* TODO
* @returns {TODO} TODO
*/
get() {
return this.getByType(OVERLAY_TYPE);
get(handle) {
if (handle) {
return this.ctrl.getByHandle(handle, OVERLAY_TYPE);
}
return this.ctrl.getByType(OVERLAY_TYPE);
}
}

View File

@ -1,3 +1,5 @@
<ion-content padding>
<button (click)="openMenu()">Open Action Sheet</button>
</ion-content>
<ion-overlay></ion-overlay>

View File

@ -129,18 +129,4 @@ export class IonicApp {
return this.components[id];
}
/**
* Create and append the given component into the root
* element of the app.
*
* @param {TODO} componentType the component to create and insert
* @return {Promise} Promise that resolves with the ContainerRef created
*/
appendOverlay(componentType: Type) {
if (!this.overlayAnchor) {
throw ('<ion-overlays></ion-overlays> must be added to your root component\'s template');
}
return this.overlayAnchor.append(componentType);
}
}

View File

@ -1,6 +1,8 @@
import {Injectable} from 'angular2/angular2';
import {Overlay} from '../overlay/overlay';
import {IonicApp} from '../app/app';
import {IonicConfig} from '../../config/config';
import {OverlayController} from '../overlay/overlay-controller';
import {Animation} from '../../animations/animation';
import * as util from 'ionic/util';
@ -28,7 +30,14 @@ import * as util from 'ionic/util';
* ```
*/
@Injectable()
export class Modal extends Overlay {
export class Modal {
constructor(app: IonicApp, config: IonicConfig) {
// super(app, {
// enterAnimation: config.get('modalEnter') || 'modal-slide-in',
// leaveAnimation: config.get('modalLeave') || 'modal-slide-out',
// })
}
/**
* TODO
@ -37,12 +46,7 @@ export class Modal extends Overlay {
* @returns {TODO} TODO
*/
open(ComponentType: Type, opts={}) {
let defaults = {
enterAnimation: this.config.get('modalEnter') || 'modal-slide-in',
leaveAnimation: this.config.get('modalLeave') || 'modal-slide-out',
};
return this.create(OVERLAY_TYPE, ComponentType, util.extend(defaults, opts));
return this.create(OVERLAY_TYPE, ComponentType, opts);
}
/**

View File

@ -171,7 +171,7 @@ export class NavController extends Ion {
// the active view is going to be the leaving one (if one exists)
let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : true);
leavingView.shouldCache = (util.isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true);
leavingView.shouldDestroy = !leavingView.shouldCache;
if (leavingView.shouldDestroy) {
leavingView.willUnload();
@ -215,7 +215,7 @@ export class NavController extends Ion {
// get the active view and set that it is staged to be leaving
// was probably the one popped from the stack
let leavingView = this.getActive() || new ViewController();
leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : false);
leavingView.shouldCache = (util.isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : false);
leavingView.shouldDestroy = !leavingView.shouldCache;
if (leavingView.shouldDestroy) {
leavingView.willUnload();
@ -341,7 +341,7 @@ export class NavController extends Ion {
opts.animate = opts.animate || false;
// ensure leaving views are not cached, and should be destroyed
opts.cacheleavingView = false;
opts.cacheLeavingView = false;
// get the views to auto remove without having to do a transiton for each
// the last view (the currently active one) will do a normal transition out

View File

@ -14,8 +14,6 @@ export class ViewController {
this.state = 0;
this.disposals = [];
this._nbItms = [];
this.navbarTemplateRef = null;
}

View File

@ -0,0 +1,156 @@
import {Component, View, NgZone, Injectable, Renderer} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {Animation} from '../../animations/animation';
import * as util from 'ionic/util';
@Injectable()
export class OverlayController {
constructor(app: IonicApp, zone: NgZone, renderer: Renderer) {
this.app = app;
this.zone = zone;
this.renderer = renderer;
this.refs = [];
}
open(overlayType, componentType: Type, opts={}) {
let resolve;
let promise = new Promise(res => { resolve = res; });
if (!this.anchor) {
console.error('<ion-overlay></ion-overlay> required in root component template to use: ' + overlayType);
return Promise.reject();
}
try {
this.anchor.append(componentType).then(ref => {
let instance = ref.instance;
ref._z = ROOT_Z_INDEX;
for (let i = 0; i < this.refs.length; i++) {
if (this.refs[i]._z >= ref._z) {
ref._z = this.refs[i]._z + 1;
}
}
this.renderer.setElementStyle(ref.location, 'zIndex', ref._z);
util.extend(instance, opts);
ref._type = overlayType;
ref._opts = opts;
ref._handle = opts.handle || (overlayType + instance.zIndex);
this.add(ref);
instance.close = (opts={}) => {
this.close(ref, opts);
};
instance.onViewLoaded && instance.onViewLoaded();
instance.onViewWillEnter && instance.onViewWillEnter();
let animation = Animation.create(ref.location.nativeElement, opts.enterAnimation);
animation.before.addClass('show-overlay');
this.app.setEnabled(false, animation.duration());
this.app.setTransitioning(true, animation.duration());
this.zone.runOutsideAngular(() => {
animation.play().then(() => {
animation.dispose();
this.zone.run(() => {
this.app.setEnabled(true);
this.app.setTransitioning(false);
instance.onViewDidEnter && instance.onViewDidEnter();
resolve();
});
});
});
}).catch(err => {
console.error(err);
});
} catch (e) {
console.error(e);
}
return promise;
}
close(ref, opts) {
let resolve;
let promise = new Promise(res => { resolve = res; });
let instance = ref.instance;
instance.onViewWillLeave && instance.onViewWillLeave();
instance.onViewWillUnload && instance.onViewWillUnload();
opts = util.extend(ref._opts, opts);
let animation = Animation.create(ref.location.nativeElement, opts.leaveAnimation);
animation.after.removeClass('show-overlay');
this.app.setEnabled(false, animation.duration());
this.app.setTransitioning(true, animation.duration());
this.zone.runOutsideAngular(() => {
animation.play().then(() => {
animation.dispose();
this.zone.run(() => {
instance.onViewDidLeave && instance.onViewDidLeave();
instance.onViewDidUnload && instance.onViewDidUnload();
this.app.setEnabled(true);
this.app.setTransitioning(false);
this.remove(ref);
resolve();
});
});
});
return promise;
}
add(ref) {
this.refs.push(ref);
}
remove(ref) {
util.array.remove(this.refs, ref);
ref.dispose && ref.dispose();
}
getByType(overlayType) {
for (let i = this.overlays.length - 1; i >= 0; i--) {
if (overlayType === this.overlays[i]._type) {
return this.overlays[i];
}
}
return null;
}
getByHandle(handle, overlayType) {
for (let i = this.overlays.length - 1; i >= 0; i--) {
if (handle === this.overlays[i]._handle && overlayType === this.overlays[i]._type) {
return this.overlays[i];
}
}
return null;
}
}
const ROOT_Z_INDEX = 1000;

View File

@ -1,195 +1,27 @@
import {Component, View, DirectiveBinding} from 'angular2/angular2';
import {Component, View, ElementRef, DynamicComponentLoader} from 'angular2/angular2';
import {IonicApp} from '../app/app';
import {Animation} from '../../animations/animation';
import * as util from 'ionic/util';
export class Overlay {
constructor(app: IonicApp) {
this.app = app;
}
create(overlayType, componentType: Type, opts={}, context=null) {
return new Promise((resolve, reject) => {
let app = this.app;
let annotation = new Component({
selector: 'ion-' + overlayType,
host: {
'[style.z-index]': 'zIndex',
'class': overlayType
}
});
let overlayComponentType = DirectiveBinding.createFromType(componentType, annotation);
// create a unique token that works as a cache key
overlayComponentType.token = overlayType + componentType.name;
app.appendOverlay(overlayComponentType).then(ref => {
let overlayRef = new OverlayRef(app, overlayType, opts, ref, context);
overlayRef._open(opts).then(() => {
resolve(overlayRef);
});
}).catch(err => {
console.error('Overlay appendOverlay:', err);
reject(err);
});
}).catch(err => {
console.error('Overlay create:', err);
});
}
getByType(overlayType) {
if (this.app) {
for (let i = this.app.overlays.length - 1; i >= 0; i--) {
if (overlayType === this.app.overlays[i]._type) {
return this.app.overlays[i];
}
}
}
return null;
}
getByHandle(handle, overlayType) {
if (this.app) {
for (let i = this.app.overlays.length - 1; i >= 0; i--) {
if (handle === this.app.overlays[i]._handle &&
overlayType === this.app.overlays[i]._type) {
return this.app.overlays[i];
}
}
}
return null;
}
}
export class OverlayRef {
constructor(app, overlayType, opts, ref, context) {
this.app = app;
let overlayInstance = (ref && ref.instance);
if (!overlayInstance) return;
if (context) {
util.extend(ref.instance, context);
}
this._instance = overlayInstance;
overlayInstance.onViewLoaded && overlayInstance.onViewLoaded();
this.zIndex = ROOT_Z_INDEX;
for (let i = 0; i < app.overlays.length; i++) {
if (app.overlays[i].zIndex >= this.zIndex) {
this.zIndex = app.overlays[i].zIndex + 1;
}
}
overlayInstance.zIndex = this.zIndex;
overlayInstance.overlayRef = this;
overlayInstance.close = (instanceOpts) => {
this.close(instanceOpts);
};
this._elementRef = ref.location;
this._type = overlayType;
this._opts = opts;
this._handle = opts.handle || this.zIndex;
this._dispose = () => {
this._instance = null;
ref.dispose && ref.dispose();
util.array.remove(app.overlays, this);
};
app.overlays.push(this);
}
getElementRef() {
return this._elementRef;
}
_open(opts={}) {
return new Promise(resolve => {
let instance = this._instance || {};
instance.onViewWillEnter && instance.onViewWillEnter();
let animationName = (opts && opts.animation) || this._opts.enterAnimation;
let animation = Animation.create(this._elementRef.nativeElement, animationName);
animation.before.addClass('show-overlay');
this.app.setEnabled(false, animation.duration());
this.app.setTransitioning(true, animation.duration());
this.app.zoneRunOutside(() => {
animation.play().then(() => {
this.app.zoneRun(() => {
this.app.setEnabled(true);
this.app.setTransitioning(false);
animation.dispose();
instance.onViewDidEnter && instance.onViewDidEnter();
resolve();
});
});
});
}).catch(err => {
console.error(err);
});
}
close(opts={}) {
return new Promise(resolve => {
let instance = this._instance || {};
instance.onViewWillLeave && instance.onViewWillLeave();
instance.onViewWillUnload && instance.onViewWillUnload();
let animationName = (opts && opts.animation) || this._opts.leaveAnimation;
let animation = Animation.create(this._elementRef.nativeElement, animationName);
animation.after.removeClass('show-overlay');
this.app.setEnabled(false, animation.duration());
this.app.setTransitioning(true, animation.duration());
animation.play().then(() => {
instance.onViewDidLeave && instance.onViewDidLeave();
instance.onViewDidUnload && instance.onViewDidUnload();
this._dispose();
this.app.setEnabled(true);
this.app.setTransitioning(false);
animation.dispose();
resolve();
})
}).catch(err => {
console.error(err);
});
}
}
import {OverlayController} from './overlay-controller';
@Component({
selector: 'ion-overlays'
selector: 'ion-overlay'
})
@View({
template: ''
})
export class OverlaysContainer {
constructor(app: IonicApp, elementRef: ElementRef, loader: DynamicComponentLoader) {
export class OverlayAnchor {
constructor(
overlayCtrl: OverlayController,
elementRef: ElementRef,
loader: DynamicComponentLoader
) {
if (overlayCtrl.anchor) {
throw ('An app should only have one <ion-overlays></ion-overlays>');
}
this.elementRef = elementRef;
this.loader = loader;
app.overlayAnchor = this;
overlayCtrl.anchor = this;
}
append(componentType) {
@ -198,6 +30,3 @@ export class OverlaysContainer {
});
}
}
const ROOT_Z_INDEX = 1000;

View File

@ -1,9 +1,9 @@
import {FORM_DIRECTIVES, NgControl, NgControlGroup,
Component, View, Injectable, NgClass, NgIf, NgFor} from 'angular2/angular2';
Component, View, ElementRef, Injectable, NgClass, NgIf, NgFor} from 'angular2/angular2';
import {Overlay} from '../overlay/overlay';
import {OverlayController} from '../overlay/overlay-controller';
import {IonicConfig} from '../../config/config';
import {Animation} from '../../animations/animation';
import {Ion} from '../ion';
import * as util from 'ionic/util';
@ -63,25 +63,27 @@ import * as util from 'ionic/util';
* ```
*/
@Injectable()
export class Popup extends Overlay {
export class Popup {
constructor(ctrl: OverlayController, config: IonicConfig) {
this.ctrl = ctrl;
this._defaults = {
enterAnimation: config.get('popupPopIn'),
leaveAnimation: config.get('popupPopOut'),
};
}
/**
* TODO
* @param {TODO} opts TODO
* @returns {object} A promise
*/
popup(opts) {
open(opts) {
return new Promise((resolve, reject)=> {
let config = this.config;
let defaults = {
enterAnimation: config.get('popupPopIn'),
leaveAnimation: config.get('popupPopOut'),
};
opts.promiseResolve = resolve;
opts.promiseReject = reject;
return this.create(OVERLAY_TYPE, StandardPopup, defaults, opts);
return this.ctrl.open(OVERLAY_TYPE, PopupCmp, util.extend(this._defaults, opts));
});
}
@ -128,7 +130,7 @@ export class Popup extends Overlay {
]
}, opts);
return this.popup(opts);
return this.open(opts);
}
/**
@ -182,7 +184,7 @@ export class Popup extends Overlay {
cancelButton, okButton
]
}, opts);
return this.popup(opts);
return this.open(opts);
}
/**
@ -243,7 +245,7 @@ export class Popup extends Overlay {
]
}, opts);
return this.popup(opts);
return this.open(opts);
}
/**
@ -264,7 +266,7 @@ const OVERLAY_TYPE = 'popup';
@Component({
selector: 'ion-popup-default'
selector: 'ion-popup'
})
@View({
template:
@ -285,19 +287,22 @@ const OVERLAY_TYPE = 'popup';
directives: [FORM_DIRECTIVES, NgClass, NgIf, NgFor]
})
class StandardPopup {
constructor(popup: Popup) {
this.popup = popup;
class PopupCmp {
constructor(elementRef: ElementRef) {
this.elementRef = elementRef;
}
onInit() {
setTimeout(() => {
this.element = this.overlayRef.getElementRef().nativeElement;
this.promptInput = this.element.querySelector('input');
// TODO: make more better, no DOM BS
this.promptInput = this.elementRef.nativeElement.querySelector('input');
if (this.promptInput) {
this.promptInput.value = '';
}
});
}
buttonTapped(button, event) {
let promptValue = this.promptInput && this.promptInput.value;
@ -314,20 +319,22 @@ class StandardPopup {
// Resolve with the prompt value
this.promiseResolve(promptValue);
}
return this.overlayRef.close();
return this.close();
}
}
_cancel(event) {
this.cancel && this.cancel(event);
if (!event.defaultPrevented) {
this.promiseReject();
return this.overlayRef.close();
return this.close();
}
}
}
class PopupAnimation extends Animation {
constructor(element) {
super(element);
@ -342,13 +349,14 @@ class PopupAnimation extends Animation {
}
}
/**
* Animations for modals
* Animations for popups
*/
class PopupPopIn extends PopupAnimation {
constructor(element) {
super(element);
this.wrapper.fromTo('opacity', '0', '1')
this.wrapper.fromTo('opacity', '0.01', '1')
this.wrapper.fromTo('scale', '1.1', '1');
this.backdrop.fromTo('opacity', '0', '0.3')
@ -370,7 +378,7 @@ Animation.register('popup-pop-out', PopupPopOut);
class PopupMdPopIn extends PopupPopIn {
constructor(element) {
super(element);
this.backdrop.fromTo('opacity', '0', '0.5')
this.backdrop.fromTo('opacity', '0.01', '0.5')
}
}
Animation.register('popup-md-pop-in', PopupMdPopIn);

View File

@ -14,3 +14,5 @@
</pre>
</ion-content>
<ion-overlay></ion-overlay>

View File

@ -4,6 +4,7 @@ import {ROUTER_BINDINGS, HashLocationStrategy, LocationStrategy} from 'angular2/
import {IonicApp} from '../components/app/app';
import {IonicConfig} from './config';
import {IonicPlatform} from '../platform/platform';
import {OverlayController} from '../components/overlay/overlay-controller';
import {ActionSheet} from '../components/action-sheet/action-sheet';
import {Modal} from '../components/modal/modal';
import {Popup} from '../components/popup/popup';
@ -40,6 +41,7 @@ export function ionicBindings(configSettings) {
bind(IonicPlatform).toValue(platform),
bind(TapClick).toValue(tapClick),
bind(Events).toValue(events),
OverlayController,
ActionSheet,
Modal,
Popup,

View File

@ -49,12 +49,16 @@ export function IonicDirective(config) {
*/
export function IonicComponent(config) {
return function(cls) {
return makeComponent(cls, appendConfig(cls, config));
}
}
export function makeComponent(cls, config) {
var annotations = Reflect.getMetadata('annotations', cls) || [];
annotations.push(new Component(appendConfig(cls, config)));
annotations.push(new Component(config));
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
}
}
function appendConfig(cls, config) {
config.host = config.host || {};

View File

@ -1,6 +1,7 @@
import {CORE_DIRECTIVES, FORM_DIRECTIVES, forwardRef} from 'angular2/angular2'
import {
OverlayAnchor,
Menu, MenuToggle, MenuClose,
Button, Content, Scroll, Refresher,
Slides, Slide, SlideLazy,
@ -29,6 +30,7 @@ export const IONIC_DIRECTIVES = [
FORM_DIRECTIVES,
// Content
forwardRef(() => OverlayAnchor),
forwardRef(() => Menu),
forwardRef(() => MenuToggle),
forwardRef(() => MenuClose),