From d4f45b5a072b6c286b35aedba3f669269897b5d8 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Thu, 18 May 2017 13:56:47 -0500 Subject: [PATCH] fix(): ensure that bindings fore core are being loaded. --- .../angular/components/boolean-input.ts | 124 ++++++++++++++++++ src/bindings/angular/providers/ionic-core.ts | 122 +++++++++++++++++ src/index.ts | 1 + src/module.ts | 2 + 4 files changed, 249 insertions(+) create mode 100644 src/bindings/angular/components/boolean-input.ts create mode 100644 src/bindings/angular/providers/ionic-core.ts diff --git a/src/bindings/angular/components/boolean-input.ts b/src/bindings/angular/components/boolean-input.ts new file mode 100644 index 0000000000..4dd0f724da --- /dev/null +++ b/src/bindings/angular/components/boolean-input.ts @@ -0,0 +1,124 @@ +import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; + + +/** + * @hidden + */ +@Directive({ + selector: 'ion-toggle', + providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: BooleanInput, multi: true }], +}) +export class BooleanInput { + id: string; + _labelId: string; + _chg: Function; + _tch: Function; + _dis: Function; + + @Output() ionChange: EventEmitter = new EventEmitter(); + @Output() ionFocus: EventEmitter = new EventEmitter(); + @Output() ionBlur: EventEmitter = new EventEmitter(); + + + constructor(public _el: ElementRef) {} + + /** + * @input {boolean} If true, the element is selected. + */ + @Input() + get checked(): boolean { + return this._el.nativeElement.checked; + } + set checked(val: boolean) { + this._el.nativeElement.checked = val; + } + + /** + * @input {boolean} If true, the user cannot interact with this element. + */ + @Input() + get disabled(): boolean { + return this._el.nativeElement.disabled; + } + set disabled(val: boolean) { + this._el.nativeElement.disabled = val; + } + + /** + * @input {string} + */ + @Input() + get value(): string { + return this._el.nativeElement.value; + } + set value(val: string) { + this._el.nativeElement.value = val; + } + + /** + * @hidden + */ + @HostListener('$ionChange', ['$event']) + _onChange(ev: any) { + ev.stopPropagation(); + this._chg && this._chg(ev.detail.checked); + this._tch && this._tch(); + this.ionChange.emit(this); + } + + /** + * @hidden + */ + @HostListener('$ionFocus', ['$event']) + _onFocus(ev: any) { + ev.stopPropagation(); + this.ionFocus.emit(this); + } + + /** + * @hidden + */ + @HostListener('$ionBlur', ['$event']) + _onBlur(ev: any) { + ev.stopPropagation(); + this.ionBlur.emit(this); + } + + /** + * @hidden + */ + writeValue(val: any) { + if (this.checked !== val) { + this.checked = val; + } + } + + /** + * @hidden + */ + registerOnChange(fn: any) { + this._chg = fn; + } + + /** + * @hidden + */ + registerOnTouched(fn: any) { + this._tch = fn; + } + + /** + * @hidden + */ + registerOnDisabledChange(fn: any) { + this._dis = fn; + } + + /** + * @hidden + */ + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + } +} diff --git a/src/bindings/angular/providers/ionic-core.ts b/src/bindings/angular/providers/ionic-core.ts new file mode 100644 index 0000000000..4d84fc9867 --- /dev/null +++ b/src/bindings/angular/providers/ionic-core.ts @@ -0,0 +1,122 @@ +import { Config } from '../../../config/config'; +import { DomController } from '../../../platform/dom-controller'; +import { NgZone } from '@angular/core'; +import { Platform } from '../../../platform/platform'; + + +/** + * @hidden + */ +export function setupCore(config: Config, plt: Platform, domCtrl: DomController, zone: NgZone) { + return function() { + const win: any = plt.win(); + const doc = plt.doc(); + + const ionic: any = win['Ionic']; + + if (!ionic || !ionic['staticDir']) { + // window.Ionic should already exist and + // window.Ionic.staticDir should have been added to the + // main bundle referencing the static www build directory + return; + } + + // same controllers are used by ionic core + ionic['ConfigCtrl'] = config; + + // keep core and angular dom reads/writes *nsync + ionic['DomCtrl'] = domCtrl; + + // next tick controller created here so that it can + // be created to run outside of angular + ionic['QueueCtrl'] = getQueueController(win, zone); + + // keep core and angular dom reads/writes *nsync + ionic['eventNameFn'] = function(eventName: string) { + return '$' + eventName; + }; + + // build up a path for the exact ionic core javascript file this browser needs + var pathItems: string[] = ['core']; + + if (!('attachShadow' in Element.prototype)) { + // browser requires the shadow dom polyfill + pathItems.push('sd'); + } + + if (!win.customElements) { + // browser requires the custom elements polyfill + pathItems.push('ce'); + } + + // request the ionic core file this browser needs + var s = doc.createElement('script'); + s.src = `${ionic['staticDir']}ionic.${pathItems.join('.')}.js`; + doc.head.appendChild(s); + + return {}; + }; +} + + +function getQueueController(win: any, zone: NgZone) { + const hostScheduleDefer: Function = win['requestIdleCallback']; + const callbacks: Function[] = []; + let pending = false; + + function doWork(deadlineObj: any) { + // let's see if we've got time to take care of things + while (deadlineObj.timeRemaining() > 1 && callbacks.length > 0) { + // do some work while within the allowed time + // shift the array and fire off the callbacks from the beginning + // once we run out of time or callbacks we'll stop + callbacks.shift()(); + } + + // check to see if we still have work to do + if (pending = (callbacks.length > 0)) { + // everyone just settle down now + // we already don't have time to do anything in this callback + // let's throw the next one in a requestAnimationFrame + // so we can just simmer down for a bit + zone.runOutsideAngular(() => { + requestAnimationFrame(flush); + }); + } + } + + function flush() { + // always force a bunch of callbacks to run, but still have + // a throttle on how many can run in a certain time + const start = performance.now(); + while (callbacks.length > 0 && (performance.now() - start < 4)) { + callbacks.shift()(); + } + + if (pending = (callbacks.length > 0)) { + // still more to do yet, but we've run out of time + // let's let thing cool off and try again after a raf + zone.runOutsideAngular(() => { + hostScheduleDefer(doWork); + }); + } + } + + function add(cb: Function) { + // add the work to the end of the callbacks + callbacks.push(cb); + + if (!pending) { + // not already pending work to do, so let's tee it up + pending = true; + zone.runOutsideAngular(() => { + hostScheduleDefer(doWork); + }); + } + } + + return { + add: add, + flush: flush + }; +} diff --git a/src/index.ts b/src/index.ts index b6e98733b4..f74b264658 100644 --- a/src/index.ts +++ b/src/index.ts @@ -122,6 +122,7 @@ export { NavController } from './navigation/nav-controller'; export { NavControllerBase } from './navigation/nav-controller-base'; export { NavParams } from './navigation/nav-params'; export { NavLink, NavOptions, DeepLinkConfig, DeepLinkMetadata, DeepLinkMetadataFactory } from './navigation/nav-util'; +export { setupCore } from './bindings/angular/providers/ionic-core'; export { TapClick, setupTapClick, isActivatable } from './tap-click/tap-click'; export { UrlSerializer, DeepLinkConfigToken } from './navigation/url-serializer'; export { ViewController } from './navigation/view-controller'; diff --git a/src/module.ts b/src/module.ts index 4e995259e1..f056641959 100644 --- a/src/module.ts +++ b/src/module.ts @@ -31,6 +31,7 @@ import { ModuleLoader, provideModuleLoader, setupPreloading, LAZY_LOADED_TOKEN } import { NgModuleLoader } from './util/ng-module-loader'; import { Platform, setupPlatform } from './platform/platform'; import { PlatformConfigToken, providePlatformConfigs } from './platform/platform-registry'; +import { setupCore } from './bindings/angular/providers/ionic-core'; import { TapClick, setupTapClick } from './tap-click/tap-click'; import { registerModeConfigs } from './config/mode-registry'; import { TransitionController } from './transitions/transition-controller'; @@ -395,6 +396,7 @@ export class IonicModule { // useFactory: ionic app initializers { provide: APP_INITIALIZER, useFactory: registerModeConfigs, deps: [ Config ], multi: true }, + { provide: APP_INITIALIZER, useFactory: setupCore, deps: [ Config, Platform, DomController, NgZone ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupProvideEvents, deps: [ Platform, DomController ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupTapClick, deps: [ Config, Platform, DomController, App, GestureController ], multi: true }, { provide: APP_INITIALIZER, useFactory: setupPreloading, deps: [ Config, DeepLinkConfigToken, ModuleLoader, NgZone ], multi: true },