diff --git a/ionic/components/app/app.js b/ionic/components/app/app.js index 8b1b174528..f032b0a798 100644 --- a/ionic/components/app/app.js +++ b/ionic/components/app/app.js @@ -1,21 +1,13 @@ import {bootstrap} from 'angular2/angular2'; import {AppViewManager} from 'angular2/src/core/compiler/view_manager'; -import {Component, Directive, onInit} from 'angular2/src/core/annotations_impl/annotations'; -import {View} from 'angular2/src/core/annotations_impl/view'; -import {ComponentRef, onDestroy, DomRenderer, ApplicationRef} from 'angular2/angular2'; -import {Promise} from 'angular2/src/facade/async'; -import {isPresent, Type} from 'angular2/src/facade/lang'; import {Compiler} from 'angular2/angular2'; import {ElementRef} from 'angular2/src/core/compiler/element_ref'; -import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; -import {Injector} from 'angular2/di'; -import {Parent} from 'angular2/src/core/annotations_impl/visibility'; import {bind} from 'angular2/di'; -import {Injectable} from 'angular2/src/di/decorators'; import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {IonicConfig} from '../../config/config'; -import {ViewController} from '../view/view-controller'; +import {Platform} from '../../platform/platform'; +import * as util from '../../util/util'; export class IonicApp { @@ -24,6 +16,72 @@ export class IonicApp { this.overlays = []; } + config(val) { + if (arguments.length) { + this._config = val; + } + return this._config; + } + + url(val) { + if (arguments.length) { + this._url = val; + this._qs = util.getQuerystring(val); + } + return this._url; + } + + query(key) { + return (this._qs || {})[key]; + } + + userAgent(val) { + if (arguments.length) { + this._ua = val; + } + return this._ua; + } + + matchesQuery(queryValue) { + let val = this.query('ionicplatform'); + if (val) { + let valueSplit = val.toLowerCase().split(';'); + for (let i = 0; i < valueSplit.length; i++) { + if (valueSplit[i] == queryValue) { + return true; + } + } + } + return false; + } + + matchesUserAgent(userAgentExpression) { + let rx = new RegExp(userAgentExpression, 'i'); + return rx.test(this._ua); + } + + matchesPlatform(queryValue, userAgentExpression) { + if (!userAgentExpression) { + userAgentExpression = queryValue; + } + return this.matchesQuery(queryValue) || + this.matchesUserAgent(userAgentExpression); + } + + width(val) { + if (arguments.length) { + this._w = val; + } + return this._w || 0; + } + + height(val) { + if (arguments.length) { + this._h = val; + } + return this._h || 0; + } + /** * Create and append the given component into the root * element of the app. @@ -67,9 +125,9 @@ export class IonicApp { }); } - ref() { + ref(val) { if (arguments.length) { - this._ref = arguments[0]; + this._ref = val; } return this._ref; } @@ -78,26 +136,55 @@ export class IonicApp { export function ionicBootstrap(ComponentType, config) { return new Promise((resolve, reject) => { - let app = new IonicApp(); - config = config || new IonicConfig(); + try { + let app = new IonicApp(); + app.url(window.location.href); + app.userAgent(window.navigator.userAgent); + app.width(window.innerWidth); + app.height(window.innerHeight); - let componentInjectableBindings = [ - bind(IonicApp).toValue(app), - bind(IonicConfig).toValue(config) - ]; + let platform = Platform.create(app); - bootstrap(ComponentType, componentInjectableBindings).then(appRef => { - app.ref(appRef); - resolve(app); + config = config || new IonicConfig(); - }).catch(err => { + // copy default platform settings into the user config platform settings + // user config platform settings should override default platform settings + config.setPlatform(platform); + + + GlobalIonicConfig = config; + + let injectableBindings = [ + bind(IonicApp).toValue(app), + bind(Platform).toValue(platform), + bind(IonicConfig).toValue(config) + ]; + + bootstrap(ComponentType, injectableBindings).then(appRef => { + app.ref(appRef); + + platform.run(); + + resolve({ + app, + config, + platform + }); + + }).catch(err => { + console.error('ionicBootstrap', err); + reject(err); + }); + + } catch (err) { console.error('ionicBootstrap', err); reject(err); - }); - + } }); } +export let GlobalIonicConfig = null; + export function load(app) { if (!app) { console.error('Invalid app module'); diff --git a/ionic/components/modal/test/basic/index.js b/ionic/components/modal/test/basic/index.js index 384999342e..5eae9363a3 100644 --- a/ionic/components/modal/test/basic/index.js +++ b/ionic/components/modal/test/basic/index.js @@ -149,12 +149,27 @@ export class ModalSecondPage { } export function main(ionicBootstrap) { - // crazy config + let myConfig = new IonicConfig(); - ionicBootstrap(MyApp, myConfig).then(app => { - // crazy run - console.log('ionicBootstrap', app); + //myConfig.setting('someKey', 'userConfig'); + // myConfig.setting('ios', 'someKey', 'iosConfig'); + // myConfig.setting('ipad', 'someKey', 'ipadConfig'); + + ionicBootstrap(MyApp, myConfig).then(root => { + + console.log('someKey', myConfig.setting('someKey')); + console.log(myConfig.setting('mode')); + + console.log('mobile', root.platform.is('mobile')) + console.log('ipad', root.platform.is('ipad')) + console.log('iphone', root.platform.is('iphone')) + console.log('phablet', root.platform.is('phablet')) + console.log('tablet', root.platform.is('tablet')) + console.log('ios', root.platform.is('ios')) + console.log('android', root.platform.is('android')) + console.log('windows phone', root.platform.is('windowsphone')) + }); } diff --git a/ionic/config/component.js b/ionic/config/component.js index a887bcb768..8c6812ce9d 100644 --- a/ionic/config/component.js +++ b/ionic/config/component.js @@ -3,8 +3,7 @@ import {DirectiveMetadata} from 'angular2/src/render/api'; import * as util from 'ionic/util'; import {Platform} from 'ionic/platform/platform'; - -const platformMode = Platform.getMode(); +import {GlobalIonicConfig} from '../components/app/app'; export class IonicDirective extends Directive { constructor(ComponentType) { @@ -25,8 +24,6 @@ function appendModeConfig(ComponentType) { let config = ComponentType.config; config.host = config.host || {}; -// let host = DirectiveMetadata.parseHostConfig(config.host); - const defaultProperties = config.defaultProperties; config.properties = config.properties || []; @@ -58,20 +55,13 @@ function appendModeConfig(ComponentType) { continue; } - // get the property values from a global user config - var globalPropertyValue = null; - if (globalPropertyValue) { + // get the property values from a global user/platform config + let configVal = GlobalIonicConfig.setting(prop); + if (configVal) { instance[prop] = globalPropertyValue; continue; } - // get the property values provided by this mode/platform - var modePropertyValue = null; - if (modePropertyValue) { - instance[prop] = modePropertyValue; - continue; - } - // wasn't set yet, so go with property's default value instance[prop] = defaultProperties[prop]; } @@ -94,8 +84,14 @@ function appendModeConfig(ComponentType) { }; } + if (!platformMode) { + platformMode = GlobalIonicConfig.setting('mode'); + } + let id = config.classId || (config.selector && config.selector.replace('ion-', '')); config.host['class'] = (id + ' ' + id + '-' + platformMode); return config; } + +let platformMode = null; diff --git a/ionic/config/config.js b/ionic/config/config.js index 05e4e4f19b..c494a8c542 100644 --- a/ionic/config/config.js +++ b/ionic/config/config.js @@ -1,9 +1,116 @@ +import {isString, isObject, isDefined, extend} from '../util/util'; export class IonicConfig { - constructor() { - this.canWe = true; + constructor(settings) { + this.setting(settings || {}); + } + + setting() { + const args = arguments; + const arg0 = args[0]; + const arg1 = args[1]; + + let s = this._settings; + + switch (args.length) { + + case 0: + // setting() = get settings object + return s; + + + case 1: + // setting({...}) = set settings object + // setting('key') = get value + + if (isObject(arg0)) { + // setting({...}) = set settings object + // arg0 = setting object + this._settings = arg0; + return this; + } + + // time for the big show, get the value + // setting('key') = get value + // arg0 = key + + if (!isDefined(s[arg0])) { + // if the value was already set this will all be skipped + // if there was no user config then it'll check each of + // the user config's platforms, which already contains + // settings from default platform configs + s[arg0] = null; + + // check the platform settings object for this value + // loop though each of the active platforms + let activePlatformKeys = this._platforms; + let platformSettings = s.platforms; + let platformObj = null; + if (platformSettings) { + let platformValue = undefined; + for (let i = 0; i < activePlatformKeys.length; i++) { + platformObj = platformSettings[ activePlatformKeys[i] ]; + if (platformObj && isDefined(platformObj[arg0])) { + platformValue = platformObj[arg0]; + } + } + if (isDefined(platformValue)) { + s[arg0] = platformValue; + } + } + } + + // return key's value + // either it came directly from the user config + // or it was from the users platform configs + // or it was from the default platform configs + // in that order + return s[arg0]; + + + case 2: + // setting('ios', {...}) = set platform config object + // setting('key', 'value') = set key/value pair + if (isObject(arg1)) { + // setting('ios', {...}) = set platform config object + // arg0 = platform + // arg1 = platform config object + s.platforms = s.platforms || {}; + s.platforms[arg0] = arg1; + + } else { + // setting('key', 'value') = set key/value pair + // arg0 = key + // arg1 = value + s[arg0] = arg1; + } + return this; + + + case 3: + // setting('ios', 'key', 'value') = set key/value pair for platform + // arg0 = platform + // arg1 = key + // arg2 = value + s.platforms = s.platforms || {}; + s.platforms[arg0] = s.platforms[arg0] || {}; + s.platforms[arg0][arg1] = args[2]; + return this; + + } + + } + + setPlatform(platform) { + // get the array of active platforms, which also knows the hierarchy, + // with the last one the most important + this._platforms = platform.platforms(); + + // copy default platform settings into the user config platform settings + // user config platform settings should override default platform settings + this._settings.platforms = extend(platform.settings(), this._settings.platforms || {}); } } diff --git a/ionic/ionic.js b/ionic/ionic.js index bca8fd6a5b..07aab1277c 100644 --- a/ionic/ionic.js +++ b/ionic/ionic.js @@ -4,7 +4,10 @@ export * from 'ionic/config/component' export * from 'ionic/config/ionic-view' export * from 'ionic/components' + export * from 'ionic/platform/platform' +export * from 'ionic/platform/registry' + export * from 'ionic/routing/router' export * from 'ionic/util/click-block' diff --git a/ionic/platform/platform.js b/ionic/platform/platform.js index 9a11f5e966..0289332ce3 100644 --- a/ionic/platform/platform.js +++ b/ionic/platform/platform.js @@ -1,144 +1,220 @@ import * as util from '../util/util'; -import {Tap} from '../util/tap'; -let registry = {}; -let defaultPlatform; -let activePlatform; -class PlatformController { +export class Platform { - constructor(platformQuerystring, userAgent) { - this.pqs = platformQuerystring; - this.ua = userAgent; + constructor() { + this._settings = {}; + this._platforms = []; } - get() { - if (util.isUndefined(activePlatform)) { - this.set(this.detect()); + is(platformName) { + return (this._platforms.indexOf(platformName) > -1); + } + + settings(val) { + if (arguments.length) { + this._settings = val; } - return activePlatform || defaultPlatform; - } - - getName() { - return this.get().name; - } - - getMode() { - let plt = this.get(); - return plt.mode || plt.name; - } - - register(platform) { - registry[platform.name] = platform; - } - - getPlatform(name) { - return registry[name]; - } - - set(platform) { - activePlatform = platform; - - this._applyBodyClasses(); - } - - setDefault(platform) { - defaultPlatform = platform; - } - - isRegistered(platformName) { - return registry.some(platform => { - return platform.name === platformName; - }) - } - - detect() { - for (let name in registry) { - if (registry[name].isMatch(this.pqs, this.ua)) { - return registry[name]; - } - } - return null; - } - - _applyBodyClasses() { - if(!activePlatform) { - return; - } - - document.body.classList.add('platform-' + activePlatform.name); + return this._settings; } run() { - activePlatform && activePlatform.run(); + let config = null; + + for (var i = 0; i < this._platforms.length; i++) { + config = Platform.get(this._platforms[i]); + config.run && config.run(); + } } - /** - * Check if the platform matches the provided one. - */ - is(platform) { - if(!activePlatform) { return false; } - - return activePlatform.name === platform; + add(platformName) { + this._platforms.push(platformName); } - /** - * Check if the loaded device matches the provided one. - */ - isDevice(device) { - if(!activePlatform) { return false; } - return activePlatform.getDevice() === device; + platforms() { + // get the array of active platforms, which also knows the hierarchy, + // with the last one the most important + return this._platforms; + } + + + /* Static Methods */ + static create(app) { + let rootNode = null; + + function matchPlatform(platformConfig) { + let platformNode = new PlatformNode(); + platformNode.config(platformConfig); + let tmpPlatform = platformNode.getRoot(app, 0); + + if (tmpPlatform) { + tmpPlatform.depth = 0; + let childPlatform = tmpPlatform.child(); + while(childPlatform) { + tmpPlatform.depth++ + childPlatform = childPlatform.child(); + } + + if (!rootNode || tmpPlatform.depth > rootNode.depth) { + rootNode = tmpPlatform; + } + } + } + + function insertSuperset(platformNode) { + let supersetPlaformName = platformNode.superset(); + if (supersetPlaformName) { + let supersetPlatform = new PlatformNode(); + supersetPlatform.load(supersetPlaformName); + supersetPlatform.parent(platformNode.parent()); + supersetPlatform.child(platformNode); + supersetPlatform.parent().child(supersetPlatform); + platformNode.parent(supersetPlatform); + } + } + + for (let platformName in platformRegistry) { + matchPlatform( platformRegistry[platformName] ); + } + + let platform = new Platform(); + if (rootNode) { + let platformNode = rootNode; + while (platformNode) { + insertSuperset(platformNode); + platformNode = platformNode.child(); + } + + platformNode = rootNode; + let settings = {}; + while (platformNode) { + // set the array of active platforms with + // the last one in the array the most important + platform.add(platformNode.name()); + + // copy default platform settings into this platform settings obj + settings[platformNode.name()] = util.extend({}, platformNode.settings()); + + // go to the next child + platformNode = platformNode.child(); + } + + platform.settings(settings); + } + + return platform; + } + + static register(platform) { + platformRegistry[platform.name] = platform; + } + + static get(platformName) { + return platformRegistry[platformName] || {}; + } + + static getSubsetParents(subsetPlatformName) { + let parentPlatformNames = []; + let platform = null; + + for (let platformName in platformRegistry) { + platform = platformRegistry[platformName]; + if (platform.subsets && platform.subsets.indexOf(subsetPlatformName) > -1) { + parentPlatformNames.push(platformName); + } + } + return parentPlatformNames; } } -export let Platform = new PlatformController((util.getQuerystring('ionicplatform')).toLowerCase(), window.navigator.userAgent); +class PlatformNode { -Platform.register({ - name: 'android', - mode: 'md', - isMatch(platformQuerystring, userAgent) { - if (platformQuerystring) { - return platformQuerystring == 'android'; - } - return /android/i.test(userAgent); - }, - getDevice: function() { - return 'android'; - }, - run() { + load(platformName) { + this._c = Platform.get(platformName); } -}); -Platform.register({ - name: 'ios', - isMatch(platformQuerystring, userAgent) { - if (platformQuerystring) { - return platformQuerystring == 'ios'; - } - return /ipad|iphone|ipod/i.test(userAgent); - }, - getDevice: function() { - if(/ipad/i.test(userAgent)) { - return 'ipad'; - } - if(/iphone/i.test(userAgent)) { - return 'iphone'; - } - }, - run() { - Tap.run(); + config(val) { + this._c = val; } -}); -// Last case is a catch-all -// TODO(mlynch): don't default to iOS, default to core, -// also make sure to remove getPlatform and set to detect() -Platform.setDefault({ - name: 'ios' -}); -Platform.set( Platform.getPlatform('ios') );//Platform.detect() ); + settings() { + return this._c.settings || {}; + } + + name() { + return this._c.name; + } + + superset() { + return this._c.superset; + } + + runAll() { + let platform = this; + while (platform) { + platform.run(); + platform = platform.child(); + } + return false; + } + + parent(val) { + if (arguments.length) { + this._parent = val; + } + return this._parent; + } + + child(val) { + if (arguments.length) { + this._child = val; + } + return this._child; + } + + isMatch(app) { + if (typeof this._c._isMatched !== 'boolean') { + // only do the actual check once + if (!this._c.isMatch) { + this._c._isMatched = true; + } else { + this._c._isMatched = this._c.isMatch(app); + } + } + return this._c._isMatched; + } + + getRoot(app) { + if (this.isMatch(app)) { + + let parents = Platform.getSubsetParents(this.name()); + + if (!parents.length) { + return this; + } + + let platform = null; + let rootPlatform = null; + + for (let i = 0; i < parents.length; i++) { + platform = new PlatformNode(); + platform.load(parents[i]); + platform.child(this); + + rootPlatform = platform.getRoot(app); + if (rootPlatform) { + this.parent(platform); + return rootPlatform; + } + } + } + + return null; + } + +} + +let platformRegistry = {}; -// If the platform needs to do some initialization (like load a custom -// tap strategy), run it now -Platform.run(); diff --git a/ionic/platform/registry.js b/ionic/platform/registry.js new file mode 100644 index 0000000000..99210326a6 --- /dev/null +++ b/ionic/platform/registry.js @@ -0,0 +1,116 @@ +import {Platform} from './platform'; +import {Tap} from '../util/tap'; + + +Platform.register({ + name: 'core', + subsets: [ + 'android', + 'ios', + 'windowsphone' + ], + settings: { + mode: 'core' + } +}); + + +Platform.register({ + name: 'mobile' +}); + + +Platform.register({ + name: 'phablet', + isMatch(app) { + let smallest = Math.min(app.width(), app.height()); + let largest = Math.max(app.width(), app.height()); + // http://www.mydevice.io/devices/ + return (smallest > 390 && smallest < 520) && + (largest > 620 && largest < 800); + } +}); + + +Platform.register({ + name: 'tablet', + isMatch(app) { + let smallest = Math.min(app.width(), app.height()); + let largest = Math.max(app.width(), app.height()); + // http://www.mydevice.io/devices/ + return (smallest > 460 && smallest < 820) && + (largest > 780 && largest < 1400); + } +}); + + +Platform.register({ + name: 'android', + superset: 'mobile', + subsets: [ + 'phablet', + 'tablet' + ], + settings: { + mode: 'md' + }, + isMatch(app) { + return app.matchesPlatform('android'); + } +}); + + + +Platform.register({ + name: 'ios', + superset: 'mobile', + subsets: [ + 'ipad', + 'iphone' + ], + settings: { + mode: 'ios' + }, + isMatch(app) { + return app.matchesPlatform('ios', 'iphone|ipad|ipod'); + }, + run() { + Tap.run(); + } +}); + + +Platform.register({ + name: 'ipad', + superset: 'tablet', + isMatch(app) { + return app.matchesPlatform('ipad'); + } +}); + + +Platform.register({ + name: 'iphone', + subsets: [ + 'phablet' + ], + isMatch(app) { + return app.matchesPlatform('iphone'); + } +}); + + +Platform.register({ + name: 'windowsphone', + superset: 'mobile', + subsets: [ + 'phablet', + 'tablet' + ], + settings: { + mode: 'wp' + }, + isMatch(app) { + return app.matchesPlatform('windowsphone', 'windows phone'); + } +}); diff --git a/ionic/util/util.js b/ionic/util/util.js index 7b22ac5718..2f49ad864d 100644 --- a/ionic/util/util.js +++ b/ionic/util/util.js @@ -134,20 +134,22 @@ export const array = { * Grab the query string param value for the given key. * @param key the key to look for */ -export function getQuerystring(key) { +export function getQuerystring(url, key) { var queryParams = {}; - const startIndex = window.location.href.indexOf('?'); - if (startIndex !== -1) { - const queries = window.location.href.slice(startIndex + 1).split('&'); - if (queries.length) { - queries.forEach((param) => { - var split = param.split('='); - queryParams[split[0]] = split[1]; - }); + if (url) { + const startIndex = url.indexOf('?'); + if (startIndex !== -1) { + const queries = url.slice(startIndex + 1).split('&'); + if (queries.length) { + queries.forEach((param) => { + var split = param.split('='); + queryParams[split[0]] = split[1]; + }); + } + } + if (key) { + return queryParams[key] || ''; } - } - if (key) { - return queryParams[key] || ''; } return queryParams; }