Files
2015-09-16 12:32:17 -05:00

368 lines
9.5 KiB
TypeScript

import {Component, View, bootstrap, ElementRef, NgZone, bind, DynamicComponentLoader, Injector} from 'angular2/angular2';
import {ROUTER_BINDINGS, HashLocationStrategy, LocationStrategy, Router} from 'angular2/router';
import {IonicConfig} from '../../config/config';
import {IonicPlatform, Platform} from '../../platform/platform';
import * as util from '../../util/util';
// injectables
import {Activator} from './activator';
import {ActionMenu} from '../action-menu/action-menu';
import {Modal} from '../modal/modal';
import {Popup} from '../popup/popup';
import {FocusHolder} from '../form/focus-holder';
/**
* @name IonicApp
* @description
* TODO(adamdbradley): IonicApp is injected, not inherited from now
* The base Ionic class that your app inherits from. By inheriting from this class, you will have access to the Ionic API.
*
* @usage
* ```js
* @App({
* templateUrl: '/app/app.html',
* })
* class MyApp {
*
* constructor(app: IonicApp) {
* this.app = app;
* }
* }
* ```
* Note: Ionic sets `ion-app` as the selector for the app. Setting a custom selector will override this and cause CSS problems.
*
*/
export class IonicApp {
/**
* TODO
*/
constructor() {
this.overlays = [];
this._transDone = 0;
// Our component registry map
this.components = {};
}
/**
* TODO
* @param {Object} appRef TODO
*/
load(appRef) {
this.ref(appRef);
this._zone = appRef.injector.get(NgZone);
}
/**
* TODO
* @param {TODO=} val TODO
* @return {TODO} TODO
*/
focusHolder(val) {
if (arguments.length) {
this._focusHolder = val;
}
return this._focusHolder;
}
/**
* Sets the document title.
* @param {string} val Value to set the document title to.
*/
title(val) {
// TODO: User angular service
document.title = val;
}
/**
* Sets if the app is currently transitioning or not. For example
* this is set to `true` while views transition, a modal slides up, an action-menu
* slides up, etc. After the transition completes it is set back to `false`.
* @param {bool} isTransitioning
*/
setTransitioning(isTransitioning, msTilDone=800) {
this._transDone = (isTransitioning ? Date.now() + msTilDone : 0);
}
/**
* Boolean if the app is actively transitioning or not.
* @return {bool}
*/
isTransitioning() {
return (this._transDone > Date.now());
}
/**
* TODO
* @param {TODO=} val TODO
* @return TODO
*/
ref(val) {
if (arguments.length) {
this._ref = val;
}
return this._ref;
}
/**
* TODO
* @return TODO
*/
get injector() {
return this._ref.injector;
}
/**
* TODO
* @param {Function} fn TODO
*/
zoneRun(fn) {
this._zone.run(fn);
}
/**
* TODO
* @param {Function} fn TODO
*/
zoneRunOutside(fn) {
this._zone.runOutsideAngular(fn);
}
/**
* Register a known component with a key, for easy lookups later.
* @param {TODO} id The id to use to register the component
* @param {TODO} component The component to register
*/
register(id, component) {
if (this.components[id] && this.components[id] !== component) {
console.error('Component id "' + id + '" already registered.');
}
this.components[id] = component;
}
/**
* Unregister a known component with a key.
* @param {TODO} id The id to use to unregister
*/
unregister(id) {
delete this.components[id];
}
/**
* Get a registered component with the given type (returns the first)
* @param {Object} cls the type to search for
* @return the matching component, or undefined if none was found
*/
getRegisteredComponent(cls) {
for(let component of this.components) {
if(component instanceof cls) {
return component;
}
}
}
/**
* Get the component for the given key.
* @param {TODO} key TODO
* @return {TODO} TODO
*/
getComponent(id) {
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
*/
appendComponent(componentType: Type) {
return this.rootAnchor.append(componentType);
}
/**
* If val is defined, specifies whether app text is RTL. If val is undefined
* returns whether app text is RTL.
*
* @param {boolean=} val Boolean specifying whether text is RTL or not.
* @returns {boolean} true if app text is RTL, false if otherwise.
*/
isRTL(val) {
if (arguments.length) {
this._rtl = val;
}
return this._rtl;
}
}
@Component({
selector: 'root-anchor'
})
@View({
template: ''
})
class RootAnchor {
constructor(app: IonicApp, elementRef: ElementRef, loader: DynamicComponentLoader) {
this.elementRef = elementRef;
this.loader = loader;
app.rootAnchor = this;
}
append(componentType) {
return this.loader.loadNextToLocation(componentType, this.elementRef).catch(err => {
console.error(err)
});
}
}
function initApp(window, document, config, platform) {
// create the base IonicApp
let app = new IonicApp();
app.isRTL(document.dir == 'rtl');
// load all platform data
platform.url(window.location.href);
platform.userAgent(window.navigator.userAgent);
platform.navigatorPlatform(window.navigator.platform);
platform.load(config);
// copy default platform settings into the user config platform settings
// user config platform settings should override default platform settings
config.setPlatform(platform);
// config and platform settings have been figured out
// apply the correct CSS to the app
applyBodyCss(document.body, config, platform);
// prepare the ready promise to fire....when ready
platform.prepareReady(config);
setTimeout(function() {
// start listening for resizes XXms after the app starts
window.addEventListener('resize', function() {
platform.windowResize();
});
}, 2500);
return app;
}
/**
* TODO
*
* @param {TODO} rootComponentType TODO
* @param {TODO} config TODO
* @return {Promise} TODO
*/
export function ionicBootstrap(rootComponentType, config) {
return new Promise(resolve => {
try {
// get the user config, or create one if wasn't passed in
if (typeof config !== IonicConfig) {
config = new IonicConfig(config);
}
let platform = new IonicPlatform();
// create the base IonicApp
let app = initApp(window, document, config, platform);
// TODO: probs need a better way to inject global injectables
let activator = new Activator(app, config, window, document);
let actionMenu = new ActionMenu(app, config);
let modal = new Modal(app, config);
let popup = new Popup(app, config);
// add injectables that will be available to all child components
let appBindings = Injector.resolve([
bind(IonicApp).toValue(app),
bind(IonicConfig).toValue(config),
bind(IonicPlatform).toValue(platform),
bind(Activator).toValue(activator),
bind(ActionMenu).toValue(actionMenu),
bind(Modal).toValue(modal),
bind(Popup).toValue(popup),
ROUTER_BINDINGS,
bind(LocationStrategy).toClass(HashLocationStrategy)
]);
bootstrap(rootComponentType, appBindings).then(appRef => {
app.load(appRef);
// Adding a anchor to add overlays off of...huh??
let elementRefs = appRef._hostComponent.hostView._view.elementRefs;
let lastElementRef = elementRefs[1];
let injector = lastElementRef.parentView._view.rootElementInjectors[0]._injector;
let loader = injector.get(DynamicComponentLoader);
loader.loadNextToLocation(RootAnchor, lastElementRef).then(() => {
// append the focus holder if its needed
if (config.setting('keyboardScrollAssist')) {
app.appendComponent(FocusHolder).then(ref => {
app.focusHolder(ref.instance);
});
}
}).catch(err => {
console.error(err)
});
resolve(app);
}).catch(err => {
console.error('ionicBootstrap', err);
});
} catch (err) {
console.error(err);
}
});
}
function applyBodyCss(bodyEle, config, platform) {
let versions = platform.versions();
platform.platforms().forEach(platformName => {
// platform-ios
let platformClass = 'platform-' + platformName;
bodyEle.classList.add(platformClass);
let platformVersion = versions[platformName];
if (platformVersion) {
// platform-ios9
platformClass += platformVersion.major;
bodyEle.classList.add(platformClass);
// platform-ios9_3
bodyEle.classList.add(platformClass + '_' + platformVersion.minor);
}
});
// set the mode class name
// ios
bodyEle.classList.add(config.setting('mode'));
// touch devices should not use :hover CSS pseudo
// enable :hover CSS when the "hoverCSS" setting is not false
if (config.setting('hoverCSS') !== false) {
bodyEle.classList.add('enable-hover');
}
/**
* Hairline Shim
* Add the "hairline" CSS class name to the body tag
* if the browser supports subpixels.
*/
if (window.devicePixelRatio >= 2) {
var hairlineEle = document.createElement('div');
hairlineEle.style.border = '.5px solid transparent';
bodyEle.appendChild(hairlineEle);
if (hairlineEle.offsetHeight === 1) {
bodyEle.classList.add('hairlines');
}
bodyEle.removeChild(hairlineEle);
}
}