From a485cd00ed7f0923293f18d4bb1110321d7a2b37 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 28 Mar 2016 17:35:49 -0400 Subject: [PATCH 1/9] feat(loading): add loading indicator component and styles this is the initial commit - still needs work references #5426 --- ionic/components.core.scss | 1 + ionic/components.ios.scss | 1 + ionic/components.md.scss | 1 + ionic/components.ts | 1 + ionic/components.wp.scss | 1 + ionic/components/loading/loading.ios.scss | 71 +++++ ionic/components/loading/loading.md.scss | 69 +++++ ionic/components/loading/loading.scss | 35 +++ ionic/components/loading/loading.ts | 278 ++++++++++++++++++ ionic/components/loading/loading.wp.scss | 65 ++++ ionic/components/loading/test/basic/e2e.ts | 4 + ionic/components/loading/test/basic/index.ts | 115 ++++++++ ionic/components/loading/test/basic/main.html | 24 ++ .../components/loading/test/basic/styles.css | 56 ++++ ionic/config/modes.ts | 11 + 15 files changed, 733 insertions(+) create mode 100644 ionic/components/loading/loading.ios.scss create mode 100644 ionic/components/loading/loading.md.scss create mode 100644 ionic/components/loading/loading.scss create mode 100644 ionic/components/loading/loading.ts create mode 100644 ionic/components/loading/loading.wp.scss create mode 100644 ionic/components/loading/test/basic/e2e.ts create mode 100644 ionic/components/loading/test/basic/index.ts create mode 100644 ionic/components/loading/test/basic/main.html create mode 100644 ionic/components/loading/test/basic/styles.css diff --git a/ionic/components.core.scss b/ionic/components.core.scss index 7d1105d6cb..1d97ed8540 100644 --- a/ionic/components.core.scss +++ b/ionic/components.core.scss @@ -17,6 +17,7 @@ "components/grid/grid", "components/icon/icon", "components/infinite-scroll/infinite-scroll", + "components/loading/loading", "components/menu/menu", "components/modal/modal", "components/refresher/refresher", diff --git a/ionic/components.ios.scss b/ionic/components.ios.scss index c75859bb2d..769e795b89 100644 --- a/ionic/components.ios.scss +++ b/ionic/components.ios.scss @@ -18,6 +18,7 @@ "components/item/item.ios", "components/label/label.ios", "components/list/list.ios", + "components/loading/loading.ios", "components/menu/menu.ios", "components/modal/modal.ios", "components/radio/radio.ios", diff --git a/ionic/components.md.scss b/ionic/components.md.scss index 82d53a8f34..3e3c695d29 100644 --- a/ionic/components.md.scss +++ b/ionic/components.md.scss @@ -18,6 +18,7 @@ "components/item/item.md", "components/label/label.md", "components/list/list.md", + "components/loading/loading.md", "components/menu/menu.md", "components/modal/modal.md", "components/radio/radio.md", diff --git a/ionic/components.ts b/ionic/components.ts index 362c737a3b..2a3d11b835 100644 --- a/ionic/components.ts +++ b/ionic/components.ts @@ -19,6 +19,7 @@ export * from './components/menu/menu-toggle' export * from './components/menu/menu-close' export * from './components/label/label' export * from './components/list/list' +export * from './components/loading/loading' export * from './components/show-hide-when/show-hide-when' export * from './components/modal/modal' export * from './components/nav/nav' diff --git a/ionic/components.wp.scss b/ionic/components.wp.scss index 5f5f5e7a42..962b6a62a5 100644 --- a/ionic/components.wp.scss +++ b/ionic/components.wp.scss @@ -18,6 +18,7 @@ "components/item/item.wp", "components/label/label.wp", "components/list/list.wp", + "components/loading/loading.wp", "components/menu/menu.wp", "components/modal/modal.wp", "components/radio/radio.wp", diff --git a/ionic/components/loading/loading.ios.scss b/ionic/components/loading/loading.ios.scss new file mode 100644 index 0000000000..ae2b691a86 --- /dev/null +++ b/ionic/components/loading/loading.ios.scss @@ -0,0 +1,71 @@ +@import "../../globals.core"; +@import "./loading"; + +// iOS Loading Indicator +// -------------------------------------------------- + +$loading-ios-padding: 24px 34px !default; +$loading-ios-max-height: 90% !default; +$loading-ios-border-radius: 8px !default; +$loading-ios-text-color: #000 !default; +$loading-ios-background: #f8f8f8 !default; + +$loading-ios-content-font-weight: bold !default; + +$loading-ios-spinner-color: #69717d !default; + +$loading-ios-spinner-ios-color: $loading-ios-spinner-color !default; +$loading-ios-spinner-bubbles-color: $loading-ios-spinner-color !default; +$loading-ios-spinner-circles-color: $loading-ios-spinner-color !default; +$loading-ios-spinner-crescent-color: $loading-ios-spinner-color !default; +$loading-ios-spinner-dots-color: $loading-ios-spinner-color !default; + + +.loading-wrapper { + padding: $loading-ios-padding; + + max-height: $loading-ios-max-height; + + border-radius: $loading-ios-border-radius; + color: $loading-ios-text-color; + background: $loading-ios-background; +} + + +// iOS Loading Content +// ----------------------------------------- + +.loading-content { + font-weight: $loading-ios-content-font-weight; +} + +.loading-spinner + .loading-content { + margin-left: 16px; +} + + +// iOS Loading Spinner fill colors +// ----------------------------------------- + +.loading-spinner { + .spinner-ios line, + .spinner-ios-small line { + stroke: $loading-ios-spinner-ios-color; + } + + .spinner-bubbles circle { + fill: $loading-ios-spinner-bubbles-color; + } + + .spinner-circles circle { + fill: $loading-ios-spinner-circles-color; + } + + .spinner-crescent circle { + stroke: $loading-ios-spinner-crescent-color; + } + + .spinner-dots circle { + fill: $loading-ios-spinner-dots-color; + } +} diff --git a/ionic/components/loading/loading.md.scss b/ionic/components/loading/loading.md.scss new file mode 100644 index 0000000000..4af60b2dc8 --- /dev/null +++ b/ionic/components/loading/loading.md.scss @@ -0,0 +1,69 @@ +@import "../../globals.core"; +@import "./loading"; + +// Material Design Loading Indicator +// -------------------------------------------------- + +$loading-md-padding: 24px !default; +$loading-md-max-height: 90% !default; +$loading-md-border-radius: 2px !default; +$loading-md-text-color: rgba(0, 0, 0, .5) !default; +$loading-md-background: #fafafa !default; +$loading-md-box-shadow-color: rgba(0, 0, 0, .4) !default; +$loading-md-box-shadow: 0 16px 20px $loading-md-box-shadow-color !default; + +$loading-md-spinner-color: color($colors-md, primary) !default; + +$loading-md-spinner-ios-color: $loading-md-spinner-color !default; +$loading-md-spinner-bubbles-color: $loading-md-spinner-color !default; +$loading-md-spinner-circles-color: $loading-md-spinner-color !default; +$loading-md-spinner-crescent-color: $loading-md-spinner-color !default; +$loading-md-spinner-dots-color: $loading-md-spinner-color !default; + + +.loading-wrapper { + padding: $loading-md-padding; + + max-height: $loading-md-max-height; + + border-radius: $loading-md-border-radius; + color: $loading-md-text-color; + background: $loading-md-background; + + box-shadow: $loading-md-box-shadow; +} + + +// Material Design Loading Content +// ----------------------------------------- + +.loading-spinner + .loading-content { + margin-left: 16px; +} + + +// Material Design Loading Spinner fill colors +// ----------------------------------------- + +.loading-spinner { + .spinner-ios line, + .spinner-ios-small line { + stroke: $loading-md-spinner-ios-color; + } + + .spinner-bubbles circle { + fill: $loading-md-spinner-bubbles-color; + } + + .spinner-circles circle { + fill: $loading-md-spinner-circles-color; + } + + .spinner-crescent circle { + stroke: $loading-md-spinner-crescent-color; + } + + .spinner-dots circle { + fill: $loading-md-spinner-dots-color; + } +} diff --git a/ionic/components/loading/loading.scss b/ionic/components/loading/loading.scss new file mode 100644 index 0000000000..4a49505038 --- /dev/null +++ b/ionic/components/loading/loading.scss @@ -0,0 +1,35 @@ +@import "../../globals.core"; + +// Loading Indicator +// -------------------------------------------------- + + +ion-loading { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: $z-index-overlay; + + display: flex; + + align-items: center; + justify-content: center; +} + +.loading-wrapper { + z-index: $z-index-overlay-wrapper; + display: flex; + + align-items: center; + + opacity: 0; +} + +// Loading Backdrop +// ----------------------------------------- + +.hide-backdrop { + display: none; +} diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts new file mode 100644 index 0000000000..3a0c3e4299 --- /dev/null +++ b/ionic/components/loading/loading.ts @@ -0,0 +1,278 @@ +import {Component, Renderer, ElementRef, HostListener} from 'angular2/core'; +import {NgFor, NgIf} from 'angular2/common'; + +import {Animation} from '../../animations/animation'; +import {Transition, TransitionOptions} from '../../transitions/transition'; +import {Config} from '../../config/config'; +import {Spinner} from '../spinner/spinner'; +import {isPresent} from '../../util/util'; +import {NavParams} from '../nav/nav-params'; +import {ViewController} from '../nav/view-controller'; + + +/** + * @name Loading + * @description + */ +export class Loading extends ViewController { + + constructor(opts: LoadingOptions = {}) { + opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : false; + opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true; + + super(LoadingCmp, opts); + this.viewType = 'loading'; + this.isOverlay = true; + + // by default, loading indicators should not fire lifecycle events of other views + // for example, when an loading indicators enters, the current active view should + // not fire its lifecycle events because it's not conceptually leaving + this.fireOtherLifecycles = false; + } + + /** + * @private + */ + getTransitionName(direction: string) { + let key = (direction === 'back' ? 'loadingLeave' : 'loadingEnter'); + return this._nav && this._nav.config.get(key); + } + + /** + * Open a loading indicator with the following options + * + * | Option | Type | Description | + * |-----------------------|------------|------------------------------------------------------------------------------------------------------------------| + * | icon |`string` | The spinner icon for the loading indicator. | + * | content |`string` | The html content for the loading indicator. | + * | cssClass |`string` | An additional class for custom styles. | + * | showBackdrop |`boolean` | Whether to show the backdrop. Default true. | + * | dismissOnPageChange |`boolean` | Whether to dismiss the indicator when navigating to a new page. Default false. | + * | enableBackdropDismiss |`boolean` | If the loading should close when the user taps the backdrop. Default false. | + * | delay |`number` | How many milliseconds to delay showing the indicator. Default 0. | + * | duration |`number` | How many milliseconds to wait before hiding the indicator. By default, it will show until `hide()` is called. | + * + * + * @param {object} opts Loading options + */ + static create(opts: LoadingOptions = {}) { + return new Loading(opts); + } + + } + +/** +* @private +*/ +@Component({ + selector: 'ion-loading', + template: + '' + + '
' + + '
' + + '' + + '
' + + '
' + + '
', + host: { + 'role': 'dialog' + }, + directives: [NgIf, Spinner] +}) +class LoadingCmp { + private d: any; + private id: number; + private created: number; + + constructor( + private _viewCtrl: ViewController, + private _config: Config, + private _elementRef: ElementRef, + params: NavParams, + renderer: Renderer + ) { + this.d = params.data; + this.created = Date.now(); + + if (this.d.cssClass) { + renderer.setElementClass(_elementRef.nativeElement, this.d.cssClass, true); + } + + this.id = (++loadingIds); + } + + onPageDidEnter() { + let activeElement: any = document.activeElement; + if (document.activeElement) { + activeElement.blur(); + } + + // If there is a duration, dismiss after that amount of time + this.d.duration ? setTimeout(() => this.dismiss('backdrop'), this.d.duration) : null; + } + + @HostListener('body:keyup', ['$event']) + private _keyUp(ev: KeyboardEvent) { + if (this.isEnabled() && this._viewCtrl.isLast()) { + if (ev.keyCode === 27) { + console.debug('loading, escape button'); + this.bdClick(); + } + } + } + + bdClick() { + if (this.isEnabled() && this.d.enableBackdropDismiss) { + this.dismiss('backdrop'); + } + } + + dismiss(role): Promise { + return this._viewCtrl.dismiss(null, role); + } + + isEnabled() { + let tm = this._config.getNumber('overlayCreatedDiff', 750); + return (this.created + tm < Date.now()); + } +} + +export interface LoadingOptions { + icon?: string; + content?: string; + showBackdrop?: boolean; + dismissOnPageChange?: boolean; + enableBackdropDismiss?: boolean; + delay?: number; + duration?: number; +} + +/** + * Animations for loading + */ + class LoadingPopIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = enteringView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-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) + .add(wrapper); + } + } + Transition.register('loading-pop-in', LoadingPopIn); + + + class LoadingPopOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = leavingView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-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) + .add(wrapper); + } + } + Transition.register('loading-pop-out', LoadingPopOut); + + + class LoadingMdPopIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = enteringView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-wrapper')); + + wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.1', '1'); + backdrop.fromTo('opacity', '0.01', '0.50'); + + this + .easing('ease-in-out') + .duration(200) + .add(backdrop) + .add(wrapper); + } + } + Transition.register('loading-md-pop-in', LoadingMdPopIn); + + + class LoadingMdPopOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = leavingView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-wrapper')); + + wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '0.9'); + backdrop.fromTo('opacity', '0.50', '0'); + + this + .easing('ease-in-out') + .duration(200) + .add(backdrop) + .add(wrapper); + } + } + Transition.register('loading-md-pop-out', LoadingMdPopOut); + + + class LoadingWpPopIn extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = enteringView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-wrapper')); + + wrapper.fromTo('opacity', '0.01', '1').fromTo('scale', '1.3', '1'); + backdrop.fromTo('opacity', '0.01', '0.16'); + + this + .easing('cubic-bezier(0,0 0.05,1)') + .duration(200) + .add(backdrop) + .add(wrapper); + } + } + Transition.register('loading-wp-pop-in', LoadingWpPopIn); + + + class LoadingWpPopOut extends Transition { + constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { + super(opts); + + let ele = leavingView.pageRef().nativeElement; + let backdrop = new Animation(ele.querySelector('.backdrop')); + let wrapper = new Animation(ele.querySelector('.loading-wrapper')); + + wrapper.fromTo('opacity', '1', '0').fromTo('scale', '1', '1.3'); + backdrop.fromTo('opacity', '0.16', '0'); + + this + .easing('ease-out') + .duration(150) + .add(backdrop) + .add(wrapper); + } + } + Transition.register('loading-wp-pop-out', LoadingWpPopOut); + +let loadingIds = -1; diff --git a/ionic/components/loading/loading.wp.scss b/ionic/components/loading/loading.wp.scss new file mode 100644 index 0000000000..495996af1c --- /dev/null +++ b/ionic/components/loading/loading.wp.scss @@ -0,0 +1,65 @@ +@import "../../globals.core"; +@import "./loading"; + +// Windows Loading Indicator +// -------------------------------------------------- + +$loading-wp-padding: 20px !default; +$loading-wp-max-height: 90% !default; +$loading-wp-border-radius: 2px !default; +$loading-wp-text-color: #fff !default; +$loading-wp-background: #000 !default; + +$loading-wp-spinner-color: $loading-wp-text-color !default; + +$loading-wp-spinner-ios-color: $loading-wp-spinner-color !default; +$loading-wp-spinner-bubbles-color: $loading-wp-spinner-color !default; +$loading-wp-spinner-circles-color: $loading-wp-spinner-color !default; +$loading-wp-spinner-crescent-color: $loading-wp-spinner-color !default; +$loading-wp-spinner-dots-color: $loading-wp-spinner-color !default; + + +.loading-wrapper { + padding: $loading-wp-padding; + + max-height: $loading-wp-max-height; + + border-radius: $loading-wp-border-radius; + color: $loading-wp-text-color; + background: $loading-wp-background; +} + + +// Windows Loading Content +// ----------------------------------------- + +.loading-spinner + .loading-content { + margin-left: 16px; +} + + +// Windows Loading Spinner fill colors +// ----------------------------------------- + +.loading-spinner { + .spinner-ios line, + .spinner-ios-small line { + stroke: $loading-wp-spinner-ios-color; + } + + .spinner-bubbles circle { + fill: $loading-wp-spinner-bubbles-color; + } + + .spinner-circles circle { + fill: $loading-wp-spinner-circles-color; + } + + .spinner-crescent circle { + stroke: $loading-wp-spinner-crescent-color; + } + + .spinner-dots circle { + fill: $loading-wp-spinner-dots-color; + } +} diff --git a/ionic/components/loading/test/basic/e2e.ts b/ionic/components/loading/test/basic/e2e.ts new file mode 100644 index 0000000000..338516c2fb --- /dev/null +++ b/ionic/components/loading/test/basic/e2e.ts @@ -0,0 +1,4 @@ + +it('should open default spinner', function() { + element(by.css('.e2eLoadingDefaultSpinner')).click(); +}); diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts new file mode 100644 index 0000000000..717ac2366d --- /dev/null +++ b/ionic/components/loading/test/basic/index.ts @@ -0,0 +1,115 @@ +import {App, Page, ActionSheet, Loading, NavController, ViewController, Platform} from 'ionic-angular'; + + +@Page({ + templateUrl: 'main.html' +}) +class E2EPage { + constructor(private nav: NavController, private platform: Platform) {} + + showLoadingIos() { + let loading = Loading.create({ + icon: 'ios', + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + showLoadingDots() { + let loading = Loading.create({ + icon: 'dots', + content: 'Loading...', + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + showLoadingBubbles() { + let loading = Loading.create({ + icon: 'bubbles', + content: 'Loading...', + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + showLoadingCircles() { + let loading = Loading.create({ + icon: 'circles', + content: 'Loading...', + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + showLoadingCrescent() { + let loading = Loading.create({ + icon: 'crescent', + content: 'Please wait...', + enableBackdropDismiss: true, + duration: 1500 + }); + + this.nav.present(loading); + } + + showLoadingDefault() { + let loading = Loading.create({ + icon: 'platform', + content: 'Please wait...', + enableBackdropDismiss: true, + }); + + this.nav.present(loading); + } + + showLoadingCustom() { + let loading = Loading.create({ + content: ` +
+
+
`, + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + showLoadingText() { + let loading = Loading.create({ + content: 'Loading Please Wait...', + enableBackdropDismiss: true + }); + + this.nav.present(loading); + } + + goToPage2() { + this.nav.push(Page2); + } +} + +@Page({ + template: ` + + Page 2 + + Some content + ` +}) +class Page2 { + constructor(private nav: NavController, private platform: Platform) {} +} + +@App({ + template: '' +}) +class E2EApp { + root = E2EPage; +} + +document.body.innerHTML += '' diff --git a/ionic/components/loading/test/basic/main.html b/ionic/components/loading/test/basic/main.html new file mode 100644 index 0000000000..d07fe33602 --- /dev/null +++ b/ionic/components/loading/test/basic/main.html @@ -0,0 +1,24 @@ + + + Loading + + + + + + + + + + + + + + + + + + diff --git a/ionic/components/loading/test/basic/styles.css b/ionic/components/loading/test/basic/styles.css new file mode 100644 index 0000000000..5291c34716 --- /dev/null +++ b/ionic/components/loading/test/basic/styles.css @@ -0,0 +1,56 @@ +.custom-spinner-container { + position: relative; + display: inline-block; + box-sizing: border-box; +} + +.custom-spinner-box { + position: relative; + box-sizing: border-box; + border: 4px solid #000; + width: 60px; + height: 60px; + animation: spin 3s infinite linear; +} + +.custom-spinner-box:before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + box-sizing: border-box; + border: 4px solid #000; + width: 40px; + height: 40px; + animation: pulse 1.5s infinite ease; +} + +.wp .custom-spinner-box, +.wp .custom-spinner-box:before { + border-color: #fff; +} + +@-webkit-keyframes pulse { + 50% { + border-width: 20px; + } +} +@keyframes pulse { + 50% { + border-width: 20px; + } +} + +@-webkit-keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} diff --git a/ionic/config/modes.ts b/ionic/config/modes.ts index 4e96d83a48..590952479a 100644 --- a/ionic/config/modes.ts +++ b/ionic/config/modes.ts @@ -17,6 +17,9 @@ Config.setModeConfig('ios', { iconMode: 'ios', + loadingEnter: 'loading-pop-in', + loadingLeave: 'loading-pop-out', + menuType: 'reveal', modalEnter: 'modal-slide-in', @@ -46,6 +49,9 @@ Config.setModeConfig('md', { iconMode: 'md', + loadingEnter: 'loading-md-pop-in', + loadingLeave: 'loading-md-pop-out', + menuType: 'overlay', modalEnter: 'modal-md-slide-in', @@ -78,6 +84,9 @@ Config.setModeConfig('wp', { iconMode: 'ios', + loadingEnter: 'loading-wp-pop-in', + loadingLeave: 'loading-wp-pop-out', + menuType: 'overlay', modalEnter: 'modal-md-slide-in', @@ -86,6 +95,8 @@ Config.setModeConfig('wp', { pageTransition: 'wp-transition', pageTransitionDelay: 96, + spinner: 'circles', + tabbarPlacement: 'top', tabSubPages: true, From dae37e7d9cbb47f7d771b32de018a1414d994921 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 1 Apr 2016 13:13:43 -0400 Subject: [PATCH 2/9] feat(loading): add ability to hide spinner in the config or options tweak css to add a max width to the loading indicator references #5426 --- ionic/components/loading/loading.ios.scss | 2 + ionic/components/loading/loading.md.scss | 2 + ionic/components/loading/loading.ts | 109 +++++++++++++++++- ionic/components/loading/loading.wp.scss | 2 + ionic/components/loading/test/basic/index.ts | 44 ++++--- ionic/components/loading/test/basic/main.html | 16 +-- 6 files changed, 145 insertions(+), 30 deletions(-) diff --git a/ionic/components/loading/loading.ios.scss b/ionic/components/loading/loading.ios.scss index ae2b691a86..7360fabba9 100644 --- a/ionic/components/loading/loading.ios.scss +++ b/ionic/components/loading/loading.ios.scss @@ -5,6 +5,7 @@ // -------------------------------------------------- $loading-ios-padding: 24px 34px !default; +$loading-ios-max-width: 270px !default; $loading-ios-max-height: 90% !default; $loading-ios-border-radius: 8px !default; $loading-ios-text-color: #000 !default; @@ -24,6 +25,7 @@ $loading-ios-spinner-dots-color: $loading-ios-spinner-color !default; .loading-wrapper { padding: $loading-ios-padding; + max-width: $loading-ios-max-width; max-height: $loading-ios-max-height; border-radius: $loading-ios-border-radius; diff --git a/ionic/components/loading/loading.md.scss b/ionic/components/loading/loading.md.scss index 4af60b2dc8..bc21e1febf 100644 --- a/ionic/components/loading/loading.md.scss +++ b/ionic/components/loading/loading.md.scss @@ -5,6 +5,7 @@ // -------------------------------------------------- $loading-md-padding: 24px !default; +$loading-md-max-width: 280px !default; $loading-md-max-height: 90% !default; $loading-md-border-radius: 2px !default; $loading-md-text-color: rgba(0, 0, 0, .5) !default; @@ -24,6 +25,7 @@ $loading-md-spinner-dots-color: $loading-md-spinner-color !default; .loading-wrapper { padding: $loading-md-padding; + max-width: $loading-md-max-width; max-height: $loading-md-max-height; border-radius: $loading-md-border-radius; diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts index 3a0c3e4299..68f74cc7be 100644 --- a/ionic/components/loading/loading.ts +++ b/ionic/components/loading/loading.ts @@ -5,7 +5,7 @@ import {Animation} from '../../animations/animation'; import {Transition, TransitionOptions} from '../../transitions/transition'; import {Config} from '../../config/config'; import {Spinner} from '../spinner/spinner'; -import {isPresent} from '../../util/util'; +import {isPresent, isUndefined, isDefined} from '../../util/util'; import {NavParams} from '../nav/nav-params'; import {ViewController} from '../nav/view-controller'; @@ -13,6 +13,91 @@ import {ViewController} from '../nav/view-controller'; /** * @name Loading * @description + * An overlay that can be used to indicate activity while blocking user + * interaction. The loading indicator appears on top of the app's content, + * and can be automatically dismissed by the app or manually dismissed by + * the user to resume interaction with the app. It includes an optional + * backdrop, which can optionally be clicked to dismiss the loading + * indicator. + * + * ### Creating + * You can pass all of the loading options in the first argument of + * the create method: `Loading.create(opts)`. The spinner name should be + * passed in the `spinner` property, and any optional HTML can be passed + * in the `content` property. If you do not pass a value to `spinner` + * the loading indicator will use the spinner specified by the mode. To + * set the spinner name across the app, set the value of `loadingSpinner` + * in your app's config. To hide the spinner, you can set + * `loadingSpinner: 'hide'` or pass `spinner: 'hide'` in the loading + * options. See the create method below for all available options. + * + * ### Dismissing + * The loading indicator can be dismissed automatically after a specific + * amount of time by passing the number of milliseconds to display it in + * the `duration` of the loading options. It can also be dismissed by + * clicking on the backdrop or pressing the escape key if + * `enableBackdropDismiss` is set to `true` in the loading options. By + * default the loading indicator will show even during page changes, + * but this can be disabled by setting `dismissOnPageChange` to `true`. + * To dismiss the loading indicator after creation, call the `dismiss()` + * method on the Loading instance. + * + * ### Limitations + * The element is styled to appear on top of other content by setting its + * `z-index` property. You must ensure no element has a stacking context with + * a higher `z-index` than this element. + * + * @usage + * ```ts + * constructor(nav: NavController) { + * this.nav = nav; + * } + * + * presentLoadingDefault() { + * let loading = Loading.create({ + * content: 'Please wait...' + * }); + * + * this.nav.present(loading); + * + * setTimeout(() => { + * loading.dismiss(); + * }, 5000); + * } + * + * presentLoadingCustom() { + * let loading = Loading.create({ + * spinner: 'hide', + * content: ` + *
+ *
+ *
`, + * duration: 5000 + * }); + * + * this.nav.present(loading); + * } + * + * presentLoadingText() { + * let loading = Loading.create({ + * spinner: 'hide', + * content: 'Loading Please Wait...' + * }); + * + * this.nav.present(loading); + * + * setTimeout(() => { + * this.nav.push(Page2); + * }, 1000); + * + * setTimeout(() => { + * loading.dismiss(); + * }, 5000); + * } + * ``` + * + * @demo /docs/v2/demos/loading/ + * @see {@link /docs/v2/api/components/spinner/Spinner Spinner API Docs} */ export class Loading extends ViewController { @@ -39,11 +124,11 @@ export class Loading extends ViewController { } /** - * Open a loading indicator with the following options + * Create a loading indicator with the following options * * | Option | Type | Description | * |-----------------------|------------|------------------------------------------------------------------------------------------------------------------| - * | icon |`string` | The spinner icon for the loading indicator. | + * | spinner |`string` | The name of the SVG spinner for the loading indicator. | * | content |`string` | The html content for the loading indicator. | * | cssClass |`string` | An additional class for custom styles. | * | showBackdrop |`boolean` | Whether to show the backdrop. Default true. | @@ -69,8 +154,8 @@ export class Loading extends ViewController { template: '' + '
' + - '
' + - '' + + '
' + + '' + '
' + '
' + '
', @@ -83,6 +168,7 @@ class LoadingCmp { private d: any; private id: number; private created: number; + private showSpinner: boolean; constructor( private _viewCtrl: ViewController, @@ -101,6 +187,17 @@ class LoadingCmp { this.id = (++loadingIds); } + ngOnInit() { + // If no spinner was passed in loading options we need to fall back + // to the loadingSpinner in the app's config, then the mode spinner + if (isUndefined(this.d.spinner)) { + this.d.spinner = this._config.get('loadingSpinner', this._config.get('spinner', 'ios')); + } + + // If the user passed hide to the spinner we don't want to show it + this.showSpinner = isDefined(this.d.spinner) && this.d.spinner !== 'hide'; + } + onPageDidEnter() { let activeElement: any = document.activeElement; if (document.activeElement) { @@ -138,7 +235,7 @@ class LoadingCmp { } export interface LoadingOptions { - icon?: string; + spinner?: string; content?: string; showBackdrop?: boolean; dismissOnPageChange?: boolean; diff --git a/ionic/components/loading/loading.wp.scss b/ionic/components/loading/loading.wp.scss index 495996af1c..50bd066273 100644 --- a/ionic/components/loading/loading.wp.scss +++ b/ionic/components/loading/loading.wp.scss @@ -5,6 +5,7 @@ // -------------------------------------------------- $loading-wp-padding: 20px !default; +$loading-wp-max-width: 280px !default; $loading-wp-max-height: 90% !default; $loading-wp-border-radius: 2px !default; $loading-wp-text-color: #fff !default; @@ -22,6 +23,7 @@ $loading-wp-spinner-dots-color: $loading-wp-spinner-color !default; .loading-wrapper { padding: $loading-wp-padding; + max-width: $loading-wp-max-width; max-height: $loading-wp-max-height; border-radius: $loading-wp-border-radius; diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts index 717ac2366d..c159a1c3d9 100644 --- a/ionic/components/loading/test/basic/index.ts +++ b/ionic/components/loading/test/basic/index.ts @@ -7,18 +7,18 @@ import {App, Page, ActionSheet, Loading, NavController, ViewController, Platform class E2EPage { constructor(private nav: NavController, private platform: Platform) {} - showLoadingIos() { + presentLoadingIos() { let loading = Loading.create({ - icon: 'ios', + spinner: 'ios', enableBackdropDismiss: true }); this.nav.present(loading); } - showLoadingDots() { + presentLoadingDots() { let loading = Loading.create({ - icon: 'dots', + spinner: 'dots', content: 'Loading...', enableBackdropDismiss: true }); @@ -26,9 +26,9 @@ class E2EPage { this.nav.present(loading); } - showLoadingBubbles() { + presentLoadingBubbles() { let loading = Loading.create({ - icon: 'bubbles', + spinner: 'bubbles', content: 'Loading...', enableBackdropDismiss: true }); @@ -36,9 +36,9 @@ class E2EPage { this.nav.present(loading); } - showLoadingCircles() { + presentLoadingCircles() { let loading = Loading.create({ - icon: 'circles', + spinner: 'circles', content: 'Loading...', enableBackdropDismiss: true }); @@ -46,9 +46,9 @@ class E2EPage { this.nav.present(loading); } - showLoadingCrescent() { + presentLoadingCrescent() { let loading = Loading.create({ - icon: 'crescent', + spinner: 'crescent', content: 'Please wait...', enableBackdropDismiss: true, duration: 1500 @@ -57,18 +57,22 @@ class E2EPage { this.nav.present(loading); } - showLoadingDefault() { + presentLoadingDefault() { let loading = Loading.create({ - icon: 'platform', content: 'Please wait...', enableBackdropDismiss: true, }); this.nav.present(loading); + + setTimeout(() => { + loading.dismiss(); + }, 5000); } - showLoadingCustom() { + presentLoadingCustom() { let loading = Loading.create({ + spinner: 'hide', content: `
@@ -79,13 +83,21 @@ class E2EPage { this.nav.present(loading); } - showLoadingText() { + presentLoadingText() { let loading = Loading.create({ - content: 'Loading Please Wait...', - enableBackdropDismiss: true + spinner: 'hide', + content: 'Loading Please Wait...' }); this.nav.present(loading); + + setTimeout(() => { + this.goToPage2(); + }, 1000); + + setTimeout(() => { + loading.dismiss(); + }, 5000); } goToPage2() { diff --git a/ionic/components/loading/test/basic/main.html b/ionic/components/loading/test/basic/main.html index d07fe33602..b84373f339 100644 --- a/ionic/components/loading/test/basic/main.html +++ b/ionic/components/loading/test/basic/main.html @@ -4,14 +4,14 @@ - - - - - - - - + + + + + + + + From 3bce7ba2dc28202b09c0f15eda519dcf27de6899 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 1 Apr 2016 13:14:06 -0400 Subject: [PATCH 3/9] docs(demos): add API demos for loading indicator references #5426 --- demos/loading/index.ts | 125 +++++++++++++++++++++++++++++++++++++++ demos/loading/main.html | 23 +++++++ demos/loading/styles.css | 59 ++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 demos/loading/index.ts create mode 100644 demos/loading/main.html create mode 100644 demos/loading/styles.css diff --git a/demos/loading/index.ts b/demos/loading/index.ts new file mode 100644 index 0000000000..c6cb025a93 --- /dev/null +++ b/demos/loading/index.ts @@ -0,0 +1,125 @@ +import {App, Page, ActionSheet, Loading, NavController, ViewController, Platform} from 'ionic-angular'; + + +@Page({ + templateUrl: 'main.html' +}) +class E2EPage { + constructor(private nav: NavController, private platform: Platform) {} + + presentLoadingIos() { + let loading = Loading.create({ + spinner: 'ios', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingDots() { + let loading = Loading.create({ + spinner: 'dots', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingBubbles() { + let loading = Loading.create({ + spinner: 'bubbles', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingCircles() { + let loading = Loading.create({ + spinner: 'circles', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingCrescent() { + let loading = Loading.create({ + spinner: 'crescent', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingDefault() { + let loading = Loading.create({ + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingCustom() { + let loading = Loading.create({ + spinner: 'hide', + content: ` +
+
+
+
This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.
`, + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + presentLoadingText() { + let loading = Loading.create({ + spinner: 'hide', + content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', + enableBackdropDismiss: true, + duration: 5000 + }); + + this.nav.present(loading); + } + + goToPage2() { + this.nav.push(Page2); + } +} + +@Page({ + template: ` + + Page 2 + + Some content + ` +}) +class Page2 { + constructor(private nav: NavController, private platform: Platform) {} +} + +@App({ + template: '' +}) +class E2EApp { + root = E2EPage; +} + +document.body.innerHTML += '' diff --git a/demos/loading/main.html b/demos/loading/main.html new file mode 100644 index 0000000000..1292080be4 --- /dev/null +++ b/demos/loading/main.html @@ -0,0 +1,23 @@ + + Loading + + + + + + + + + + + + + + + + + + diff --git a/demos/loading/styles.css b/demos/loading/styles.css new file mode 100644 index 0000000000..48888095ba --- /dev/null +++ b/demos/loading/styles.css @@ -0,0 +1,59 @@ +.custom-spinner-container { + position: relative; + display: inline-block; + box-sizing: border-box; + width: 100%; + margin-bottom: 20px; +} + +.custom-spinner-box { + margin: 0 auto; + position: relative; + box-sizing: border-box; + border: 4px solid #000; + width: 60px; + height: 60px; + animation: spin 3s infinite linear; +} + +.custom-spinner-box:before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + box-sizing: border-box; + border: 4px solid #000; + width: 40px; + height: 40px; + animation: pulse 1.5s infinite ease; +} + +.wp .custom-spinner-box, +.wp .custom-spinner-box:before { + border-color: #fff; +} + +@-webkit-keyframes pulse { + 50% { + border-width: 20px; + } +} +@keyframes pulse { + 50% { + border-width: 20px; + } +} + +@-webkit-keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} From d21a682bad37ad192f60c097aa4d2ed44ca7d6db Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 1 Apr 2016 13:37:47 -0400 Subject: [PATCH 4/9] docs(loading): remove the enableBackdropDismiss and update demo references #5426 --- demos/loading/index.ts | 40 ++++++++---------- ionic/components/loading/loading.ts | 43 +++++--------------- ionic/components/loading/test/basic/index.ts | 14 +++---- 3 files changed, 32 insertions(+), 65 deletions(-) diff --git a/demos/loading/index.ts b/demos/loading/index.ts index c6cb025a93..2cd8d824e2 100644 --- a/demos/loading/index.ts +++ b/demos/loading/index.ts @@ -10,9 +10,8 @@ class E2EPage { presentLoadingIos() { let loading = Loading.create({ spinner: 'ios', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the "ios" spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -21,9 +20,8 @@ class E2EPage { presentLoadingDots() { let loading = Loading.create({ spinner: 'dots', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the "dots" spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -32,9 +30,8 @@ class E2EPage { presentLoadingBubbles() { let loading = Loading.create({ spinner: 'bubbles', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the "bubbles" spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -43,9 +40,8 @@ class E2EPage { presentLoadingCircles() { let loading = Loading.create({ spinner: 'circles', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the "circles" spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -54,9 +50,8 @@ class E2EPage { presentLoadingCrescent() { let loading = Loading.create({ spinner: 'crescent', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the "crescent" spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -64,9 +59,8 @@ class E2EPage { presentLoadingDefault() { let loading = Loading.create({ - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This is the mode specific spinner. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); @@ -79,9 +73,8 @@ class E2EPage {
-
This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.
`, - enableBackdropDismiss: true, - duration: 5000 +
This is a custom spinner. It will dismiss after 3 seconds.
`, + duration: 3000 }); this.nav.present(loading); @@ -90,9 +83,8 @@ class E2EPage { presentLoadingText() { let loading = Loading.create({ spinner: 'hide', - content: 'This will dismiss after 5 seconds, or you can click the backdrop to dismiss it now.', - enableBackdropDismiss: true, - duration: 5000 + content: 'This has no spinner, only text. It will dismiss after 3 seconds.', + duration: 3000 }); this.nav.present(loading); diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts index 68f74cc7be..2024d35654 100644 --- a/ionic/components/loading/loading.ts +++ b/ionic/components/loading/loading.ts @@ -15,10 +15,9 @@ import {ViewController} from '../nav/view-controller'; * @description * An overlay that can be used to indicate activity while blocking user * interaction. The loading indicator appears on top of the app's content, - * and can be automatically dismissed by the app or manually dismissed by - * the user to resume interaction with the app. It includes an optional - * backdrop, which can optionally be clicked to dismiss the loading - * indicator. + * and can be dismissed by the app to resume user interaction with + * the app. It includes an optional backdrop, which can be disabled + * by setting `showBackdrop: false` upon creation. * * ### Creating * You can pass all of the loading options in the first argument of @@ -27,20 +26,17 @@ import {ViewController} from '../nav/view-controller'; * in the `content` property. If you do not pass a value to `spinner` * the loading indicator will use the spinner specified by the mode. To * set the spinner name across the app, set the value of `loadingSpinner` - * in your app's config. To hide the spinner, you can set - * `loadingSpinner: 'hide'` or pass `spinner: 'hide'` in the loading + * in your app's config. To hide the spinner, set `loadingSpinner: 'hide'` + * in the apps' config or pass `spinner: 'hide'` in the loading * options. See the create method below for all available options. * * ### Dismissing * The loading indicator can be dismissed automatically after a specific * amount of time by passing the number of milliseconds to display it in - * the `duration` of the loading options. It can also be dismissed by - * clicking on the backdrop or pressing the escape key if - * `enableBackdropDismiss` is set to `true` in the loading options. By - * default the loading indicator will show even during page changes, - * but this can be disabled by setting `dismissOnPageChange` to `true`. - * To dismiss the loading indicator after creation, call the `dismiss()` - * method on the Loading instance. + * the `duration` of the loading options. By default the loading indicator + * will show even during page changes, but this can be disabled by setting + * `dismissOnPageChange` to `true`. To dismiss the loading indicator after + * creation, call the `dismiss()` method on the Loading instance. * * ### Limitations * The element is styled to appear on top of other content by setting its @@ -102,7 +98,6 @@ import {ViewController} from '../nav/view-controller'; export class Loading extends ViewController { constructor(opts: LoadingOptions = {}) { - opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : false; opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true; super(LoadingCmp, opts); @@ -133,7 +128,6 @@ export class Loading extends ViewController { * | cssClass |`string` | An additional class for custom styles. | * | showBackdrop |`boolean` | Whether to show the backdrop. Default true. | * | dismissOnPageChange |`boolean` | Whether to dismiss the indicator when navigating to a new page. Default false. | - * | enableBackdropDismiss |`boolean` | If the loading should close when the user taps the backdrop. Default false. | * | delay |`number` | How many milliseconds to delay showing the indicator. Default 0. | * | duration |`number` | How many milliseconds to wait before hiding the indicator. By default, it will show until `hide()` is called. | * @@ -152,7 +146,7 @@ export class Loading extends ViewController { @Component({ selector: 'ion-loading', template: - '' + + '' + '
' + '
' + '' + @@ -208,22 +202,6 @@ class LoadingCmp { this.d.duration ? setTimeout(() => this.dismiss('backdrop'), this.d.duration) : null; } - @HostListener('body:keyup', ['$event']) - private _keyUp(ev: KeyboardEvent) { - if (this.isEnabled() && this._viewCtrl.isLast()) { - if (ev.keyCode === 27) { - console.debug('loading, escape button'); - this.bdClick(); - } - } - } - - bdClick() { - if (this.isEnabled() && this.d.enableBackdropDismiss) { - this.dismiss('backdrop'); - } - } - dismiss(role): Promise { return this._viewCtrl.dismiss(null, role); } @@ -239,7 +217,6 @@ export interface LoadingOptions { content?: string; showBackdrop?: boolean; dismissOnPageChange?: boolean; - enableBackdropDismiss?: boolean; delay?: number; duration?: number; } diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts index c159a1c3d9..0624cefac7 100644 --- a/ionic/components/loading/test/basic/index.ts +++ b/ionic/components/loading/test/basic/index.ts @@ -10,7 +10,7 @@ class E2EPage { presentLoadingIos() { let loading = Loading.create({ spinner: 'ios', - enableBackdropDismiss: true + duration: 2000 }); this.nav.present(loading); @@ -20,7 +20,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'dots', content: 'Loading...', - enableBackdropDismiss: true + duration: 2000 }); this.nav.present(loading); @@ -30,7 +30,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'bubbles', content: 'Loading...', - enableBackdropDismiss: true + duration: 2000 }); this.nav.present(loading); @@ -40,7 +40,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'circles', content: 'Loading...', - enableBackdropDismiss: true + duration: 2000 }); this.nav.present(loading); @@ -50,7 +50,6 @@ class E2EPage { let loading = Loading.create({ spinner: 'crescent', content: 'Please wait...', - enableBackdropDismiss: true, duration: 1500 }); @@ -60,7 +59,6 @@ class E2EPage { presentLoadingDefault() { let loading = Loading.create({ content: 'Please wait...', - enableBackdropDismiss: true, }); this.nav.present(loading); @@ -77,7 +75,7 @@ class E2EPage {
`, - enableBackdropDismiss: true + duration: 2000 }); this.nav.present(loading); @@ -97,7 +95,7 @@ class E2EPage { setTimeout(() => { loading.dismiss(); - }, 5000); + }, 2000); } goToPage2() { From 128a8342e1ca1b22c4664c4dbd950690e94d4aff Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 1 Apr 2016 14:00:36 -0400 Subject: [PATCH 5/9] test(loading): add another page to the loading test --- ionic/components/loading/test/basic/index.ts | 32 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts index 0624cefac7..15ae5a244f 100644 --- a/ionic/components/loading/test/basic/index.ts +++ b/ionic/components/loading/test/basic/index.ts @@ -90,8 +90,12 @@ class E2EPage { this.nav.present(loading); setTimeout(() => { - this.goToPage2(); - }, 1000); + this.nav.push(Page2); + }, 3000); + + setTimeout(() => { + this.nav.push(Page3); + }, 3000); setTimeout(() => { loading.dismiss(); @@ -109,10 +113,34 @@ class E2EPage { Page 2 Some content + + + + + ` }) class Page2 { constructor(private nav: NavController, private platform: Platform) {} + + goToPage3() { + this.nav.push(Page3); + } +} + +@Page({ + template: ` + + Page 3 + + Some content + ` +}) +class Page3 { + constructor(private nav: NavController, private platform: Platform) {} } @App({ From 53bebc74ea744745aa99204fb69f81bdcce7841c Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 1 Apr 2016 15:05:40 -0400 Subject: [PATCH 6/9] test(loading): add another page for navigation --- ionic/components/loading/test/basic/index.ts | 14 ++++++++------ ionic/components/loading/test/basic/main.html | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts index 15ae5a244f..bc4fcbef34 100644 --- a/ionic/components/loading/test/basic/index.ts +++ b/ionic/components/loading/test/basic/index.ts @@ -91,15 +91,11 @@ class E2EPage { setTimeout(() => { this.nav.push(Page2); - }, 3000); - - setTimeout(() => { - this.nav.push(Page3); - }, 3000); + }, 1000); setTimeout(() => { loading.dismiss(); - }, 2000); + }, 5000); } goToPage2() { @@ -126,6 +122,12 @@ class E2EPage { class Page2 { constructor(private nav: NavController, private platform: Platform) {} + onPageLoaded() { + setTimeout(() => { + this.nav.push(Page3); + }, 1000); + } + goToPage3() { this.nav.push(Page3); } diff --git a/ionic/components/loading/test/basic/main.html b/ionic/components/loading/test/basic/main.html index b84373f339..29ad44dfaf 100644 --- a/ionic/components/loading/test/basic/main.html +++ b/ionic/components/loading/test/basic/main.html @@ -11,7 +11,7 @@ - + From f096cf656f35449e4a1ca916b937067e1a811f70 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 4 Apr 2016 23:25:42 -0400 Subject: [PATCH 7/9] docs(demos): update loading indicator demo and e2e references #5426 --- demos/loading/main.html | 52 +++++++++-- ionic/components/loading/test/basic/index.ts | 86 +++++++++++++++++-- ionic/components/loading/test/basic/main.html | 6 +- 3 files changed, 127 insertions(+), 17 deletions(-) diff --git a/demos/loading/main.html b/demos/loading/main.html index 1292080be4..76aaf2165c 100644 --- a/demos/loading/main.html +++ b/demos/loading/main.html @@ -2,15 +2,49 @@ Loading - - - - - - - - - + + + + Spinner Loading Indicators + + + + + + + + + + + + Custom Loading Indicators + + + + + diff --git a/ionic/components/loading/test/basic/index.ts b/ionic/components/loading/test/basic/index.ts index bc4fcbef34..3766769972 100644 --- a/ionic/components/loading/test/basic/index.ts +++ b/ionic/components/loading/test/basic/index.ts @@ -10,7 +10,7 @@ class E2EPage { presentLoadingIos() { let loading = Loading.create({ spinner: 'ios', - duration: 2000 + duration: 1000 }); this.nav.present(loading); @@ -20,7 +20,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'dots', content: 'Loading...', - duration: 2000 + duration: 1000 }); this.nav.present(loading); @@ -30,7 +30,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'bubbles', content: 'Loading...', - duration: 2000 + duration: 1000 }); this.nav.present(loading); @@ -40,7 +40,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'circles', content: 'Loading...', - duration: 2000 + duration: 1000 }); this.nav.present(loading); @@ -50,7 +50,7 @@ class E2EPage { let loading = Loading.create({ spinner: 'crescent', content: 'Please wait...', - duration: 1500 + duration: 1000 }); this.nav.present(loading); @@ -75,7 +75,7 @@ class E2EPage {
`, - duration: 2000 + duration: 1000 }); this.nav.present(loading); @@ -101,6 +101,80 @@ class E2EPage { goToPage2() { this.nav.push(Page2); } + + presentLoadingMultiple() { + let loading = Loading.create({ + spinner: 'hide', + content: 'Loading 1 Please Wait...' + }); + + this.nav.present(loading); + + let loading2 = Loading.create({ + spinner: 'hide', + content: 'Loading 2 Please Wait...' + }); + + setTimeout(() => { + this.nav.present(loading2); + }, 1000); + + let loading3 = Loading.create({ + spinner: 'hide', + content: 'Loading 3 Please Wait...' + }); + + setTimeout(() => { + this.nav.present(loading3); + + setTimeout(() => { + loading3.dismiss(); + }, 1000); + + setTimeout(() => { + loading2.dismiss(); + }, 2000); + + setTimeout(() => { + loading.dismiss(); + }, 3000); + }, 2000); + + } + + presentLoadingMultipleNav() { + let loading = Loading.create({ + spinner: 'hide', + content: 'Loading 1 Please Wait...', + dismissOnPageChange: true + }); + + this.nav.present(loading); + + let loading2 = Loading.create({ + spinner: 'hide', + content: 'Loading 2 Please Wait...', + dismissOnPageChange: true + }); + + setTimeout(() => { + this.nav.present(loading2); + }, 500); + + let loading3 = Loading.create({ + spinner: 'hide', + content: 'Loading 3 Please Wait...', + dismissOnPageChange: true + }); + + setTimeout(() => { + this.nav.present(loading3); + + setTimeout(() => { + this.nav.push(Page2); + }, 1000); + }, 1000); + } } @Page({ diff --git a/ionic/components/loading/test/basic/main.html b/ionic/components/loading/test/basic/main.html index 29ad44dfaf..a25d3819b9 100644 --- a/ionic/components/loading/test/basic/main.html +++ b/ionic/components/loading/test/basic/main.html @@ -8,10 +8,12 @@ - + - + + +
From d3fa29fe95d6eccfe61ba577af57c7719cea89bf Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 4 Apr 2016 23:38:58 -0400 Subject: [PATCH 8/9] feat(loading): add internal stack for the loading service this allows the user to push multiple loading indicators on top of each other without interfering with the page stack, as well as navigation between pages behind the loading indicator. references #5426 --- ionic/components/loading/loading.ts | 2 ++ ionic/components/nav/nav-controller.ts | 32 +++++++++++++++---- ionic/components/nav/nav-portal.ts | 30 +++++++++++++++++ ionic/components/nav/nav.ts | 12 +++++-- .../nav/test/nav-controller.spec.ts | 2 ++ ionic/components/nav/view-controller.ts | 5 +++ 6 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 ionic/components/nav/nav-portal.ts diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts index 2024d35654..7c7f39f134 100644 --- a/ionic/components/loading/loading.ts +++ b/ionic/components/loading/loading.ts @@ -99,10 +99,12 @@ export class Loading extends ViewController { constructor(opts: LoadingOptions = {}) { opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true; + opts.dismissOnPageChange = isPresent(opts.dismissOnPageChange) ? !!opts.dismissOnPageChange : false; super(LoadingCmp, opts); this.viewType = 'loading'; this.isOverlay = true; + this.usePortal = true; // by default, loading indicators should not fire lifecycle events of other views // for example, when an loading indicators enters, the current active view should diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index 4e3477006b..e91ebaadb3 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -8,6 +8,7 @@ import {Keyboard} from '../../util/keyboard'; import {NavParams} from './nav-params'; import {NavRouter} from './nav-router'; import {pascalCaseToDashCase, isTrueProperty, isBlank} from '../../util/util'; +import {Portal} from './nav-portal'; import {raf} from '../../util/dom'; import {SwipeBackGesture} from './swipe-back'; import {Transition} from '../../transitions/transition'; @@ -109,6 +110,7 @@ export class NavController extends Ion { private _trans: Transition; private _sbGesture: SwipeBackGesture; private _sbThreshold: number; + private _portal: Portal; protected _sbEnabled: boolean; protected _ids: number = -1; @@ -170,6 +172,10 @@ export class NavController extends Ion { provide(NavController, {useValue: this}) ]); } + + setPortal(val: Portal) { + this._portal = val; + } /** * Set the root for the current navigation stack @@ -412,7 +418,7 @@ export class NavController extends Ion { if (rootNav['_tabs']) { // TODO: must have until this goes in // https://github.com/angular/angular/issues/5481 - console.error('A parent is required for ActionSheet/Alert/Modal'); + console.error('A parent is required for ActionSheet/Alert/Modal/Loading'); return; } @@ -433,7 +439,12 @@ export class NavController extends Ion { keyboardClose: false, direction: 'back', animation: enteringView.getTransitionName('back') - }); + }); + + if (enteringView.usePortal && this._portal) { + this._portal.present(enteringView); + return; + } // start the transition return rootNav._insertViews(-1, [enteringView], opts); @@ -534,7 +545,7 @@ export class NavController extends Ion { if (this._views[i] === enteringView) { // cool, so the last valid view is also our entering view!! - // this means we should animate that bad boy in so its the active view + // this means we should animate that bad boy in so it's the active view // return a promise and resolve when the transition has completed // get the leaving view which the _insert() already set @@ -730,8 +741,8 @@ export class NavController extends Ion { // get the view thats ready to enter let enteringView = this.getByState(STATE_INIT_ENTER); - if (!enteringView) { - // oh knows! no entering view to go to! + if (!enteringView && this._portal) { + // oh nos! no entering view to go to! // if there is no previous view that would enter in this nav stack // and the option is set to climb up the nav parent looking // for the next nav we could transition to instead @@ -1248,6 +1259,14 @@ export class NavController extends Ion { // see if we should add the swipe back gesture listeners or not this._sbCheck(); + + if (this._portal) { + this._portal._views.forEach(view => { + if (view.data && view.data.dismissOnPageChange) { + view.dismiss(); + } + }); + } } else { // darn, so this wasn't the most recent transition @@ -1640,7 +1659,7 @@ export class NavController extends Ion { } else { // this is the initial view - enteringView.setZIndex(INIT_ZINDEX, this._renderer); + enteringView.setZIndex(this._portal ? INIT_ZINDEX : PORTAL_ZINDEX, this._renderer); } } else if (direction === 'back') { @@ -1680,5 +1699,6 @@ const STATE_REMOVE = 'remove'; const STATE_REMOVE_AFTER_TRANS = 'remove_after_trans'; const STATE_FORCE_ACTIVE = 'force_active'; const INIT_ZINDEX = 100; +const PORTAL_ZINDEX = 9999; let ctrlIds = -1; diff --git a/ionic/components/nav/nav-portal.ts b/ionic/components/nav/nav-portal.ts new file mode 100644 index 0000000000..6e6786ab4a --- /dev/null +++ b/ionic/components/nav/nav-portal.ts @@ -0,0 +1,30 @@ +import {Directive, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type, ContentChild} from 'angular2/core'; + +import {IonicApp} from '../app/app'; +import {Config} from '../../config/config'; +import {Keyboard} from '../../util/keyboard'; +import {NavController} from './nav-controller'; +import {ViewController} from './view-controller'; + +/** + * @private + */ +@Directive({ + selector: '[portal]' +}) +export class Portal extends NavController { + constructor( + @Optional() hostNavCtrl: NavController, + @Optional() viewCtrl: ViewController, + app: IonicApp, + config: Config, + keyboard: Keyboard, + elementRef: ElementRef, + compiler: Compiler, + viewManager: AppViewManager, + zone: NgZone, + renderer: Renderer + ) { + super(hostNavCtrl, app, config, keyboard, elementRef, null, compiler, viewManager, zone, renderer); + } +} diff --git a/ionic/components/nav/nav.ts b/ionic/components/nav/nav.ts index 86ceb92549..b808f57dcb 100644 --- a/ionic/components/nav/nav.ts +++ b/ionic/components/nav/nav.ts @@ -1,10 +1,11 @@ -import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type} from 'angular2/core'; +import {Component, ElementRef, Input, Optional, NgZone, Compiler, AppViewManager, Renderer, Type, ViewChild} from 'angular2/core'; import {IonicApp} from '../app/app'; import {Config} from '../../config/config'; import {Keyboard} from '../../util/keyboard'; import {isTrueProperty} from '../../util/util'; import {NavController} from './nav-controller'; +import {Portal} from './nav-portal'; import {ViewController} from './view-controller'; /** @@ -104,7 +105,8 @@ import {ViewController} from './view-controller'; */ @Component({ selector: 'ion-nav', - template: '
' + template: '
', + directives: [Portal] }) export class Nav extends NavController { private _root: Type; @@ -172,5 +174,9 @@ export class Nav extends NavController { this.push(this._root); } } - + + @ViewChild(Portal) + private set _navPortal(val: Portal) { + this.setPortal(val); + } } diff --git a/ionic/components/nav/test/nav-controller.spec.ts b/ionic/components/nav/test/nav-controller.spec.ts index 7a960b1288..7c7bcbb0ce 100644 --- a/ionic/components/nav/test/nav-controller.spec.ts +++ b/ionic/components/nav/test/nav-controller.spec.ts @@ -1236,6 +1236,8 @@ export function run() { setElementClass: function(){}, setElementStyle: function(){} }; + + nav._portal = new NavController(null, null, config, null, elementRef, null, null, null, null, null); return nav; } diff --git a/ionic/components/nav/view-controller.ts b/ionic/components/nav/view-controller.ts index b974b69fde..30b1f11e7a 100644 --- a/ionic/components/nav/view-controller.ts +++ b/ionic/components/nav/view-controller.ts @@ -77,6 +77,11 @@ export class ViewController { */ isOverlay: boolean = false; + /** + * @private + */ + usePortal: boolean = false; + /** * @private */ From 5d587b0418cc64a5d852f5c77c346a17d8983b7b Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Tue, 5 Apr 2016 10:45:52 -0400 Subject: [PATCH 9/9] docs: remove delay from docs for now from loading --- ionic/components/loading/loading.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ionic/components/loading/loading.ts b/ionic/components/loading/loading.ts index 7c7f39f134..d13a298230 100644 --- a/ionic/components/loading/loading.ts +++ b/ionic/components/loading/loading.ts @@ -130,7 +130,6 @@ export class Loading extends ViewController { * | cssClass |`string` | An additional class for custom styles. | * | showBackdrop |`boolean` | Whether to show the backdrop. Default true. | * | dismissOnPageChange |`boolean` | Whether to dismiss the indicator when navigating to a new page. Default false. | - * | delay |`number` | How many milliseconds to delay showing the indicator. Default 0. | * | duration |`number` | How many milliseconds to wait before hiding the indicator. By default, it will show until `hide()` is called. | * *