diff --git a/gulpfile.js b/gulpfile.js index c85a99924a..f57c423ebd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,9 +88,20 @@ gulp.task('ng2-rename', function(done) { }); gulp.task('ng2', ['ng2-rename'], function() { var builder = new SystemJsBuilder(); - return builder.loadConfig('jspm-config.js') - .then(function() { - return builder.build('dist/lib/angular2/angular2', 'dist/lib/angular2.js'); + builder.config({ + traceurOptions: { + 'sourceMaps': true, + 'annotations': true, + 'types': true, + 'script': false, + 'memberVariables': true, + 'modules': 'instantiate' + }, + baseURL: 'dist/lib', + map: { + rx: __dirname + '/node_modules/rx' + } }); + return builder.build('angular2/angular2', 'dist/lib/angular2.js'); }); diff --git a/jspm-config.js b/jspm-config.js index 60646a01ac..783ad7cc5d 100644 --- a/jspm-config.js +++ b/jspm-config.js @@ -1,26 +1,23 @@ System.config({ - "paths": { - "*": "*.js", - "dist/*": "/dist", - "node_modules/*": "/node_modules/*", + 'paths': { + '*': '*.js', + 'angular2/*': '/dist/lib/angular2/*.js' }, - "traceurOptions": { - "sourceMaps": true, - "annotations": true, - "types": true, - "script": false, - "memberVariables": true, - "modules": "instantiate" + 'traceurOptions': { + 'sourceMaps': true, + 'annotations': true, + 'types': true, + 'script': false, + 'memberVariables': true, + 'modules': 'instantiate' } }); System.config({ - "map": { - "angular2": "dist/lib/angular2", - "hammer": "node_modules/hammerjs/hammer.js", - "rtts_assert": "dist/lib/rtts_assert", - "rx": "node_modules/rx", - "ionic2": "/src", + 'map': { + 'hammer': '/node_modules/hammerjs/hammer', + 'rx': '/node_modules/rx', + 'ionic2': '/src', } }); diff --git a/src/components/app/examples/figure-it-out/main.js b/src/components/app/examples/figure-it-out/main.js index 0008609175..aad981c5e9 100644 --- a/src/components/app/examples/figure-it-out/main.js +++ b/src/components/app/examples/figure-it-out/main.js @@ -4,12 +4,6 @@ import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_compone import {PrivateComponentLocation} from 'angular2/src/core/compiler/private_component_location'; import {RedBgStyler, BlueTextStyler} from './components/stylers'; -class Testy { - constructor(@Inject() element: NgElement) { - element.domElement.style.border = '3px solid pink;' - } -} - @DynamicComponent({ selector: 'dynamic-component', services: [PrivateComponentLoader, PrivateComponentLocation] @@ -20,12 +14,8 @@ class MyDynamic { loader:PrivateComponentLoader, location:PrivateComponentLocation ) { - // loader.load(RedBgStyler, location); - // loader.load(BlueTextStyler, location); - Testy.annotations = [ - new Component({ selector: 'testy' }), - new Template({ inline: 'testy-template' }) - ]; + loader.load(RedBgStyler, location); + loader.load(BlueTextStyler, location); } } diff --git a/src/components/aside/aside.js b/src/components/aside/aside.js index 9a8eac233f..fd636517f4 100644 --- a/src/components/aside/aside.js +++ b/src/components/aside/aside.js @@ -1,10 +1,10 @@ import {Component, Template, Inject, Parent, NgElement} from 'angular2/angular2'; import {Ion} from '../ion'; -import {IonConfig} from '../../config'; +import {Config} from '../../core/config/config'; import {SlideEdgeGesture} from '../../core/gestures/slide-edge-gesture'; import * as util from '../../util'; -export var asideConfig = new IonConfig('aside'); +export var asideConfig = new Config('aside'); // TODO defaults or bindings? asideConfig.set({ @@ -12,6 +12,13 @@ asideConfig.set({ dragThreshold: 50 }) + +class AndroidAside {} +class IosAside {} + +asideConfig.platform('android').component(AndroidAside); +asideConfig.platform('ios').component(IosAside); + // AsideParent is just a temporary directive @Component({ selector: 'ion-aside-parent' @@ -49,7 +56,9 @@ export class Aside { // TODO: remove this. setTimeout has to be done so the bindings can be applied setTimeout(() => { - // asideConfig.invoke(this); + let Comp = asideConfig.invoke(this); + console.log('using', Comp); + let GestureConstructor = { left: LeftAsideSlideGesture, top: TopAsideSlideGesture, diff --git a/src/components/ion.js b/src/components/ion.js index b6b9406eac..1b93e9655e 100644 --- a/src/components/ion.js +++ b/src/components/ion.js @@ -1,4 +1,3 @@ -import * as Platform from '../platform'; import * as util from '../util'; export class Ion {} diff --git a/src/config.js b/src/config.js deleted file mode 100644 index c27bb46b27..0000000000 --- a/src/config.js +++ /dev/null @@ -1,180 +0,0 @@ -import * as Platform from './platform'; -import * as util from './util'; - -// TODO stop hardcoding platforms and media sizes - -/* - config - .set({ side: 'left' }) - .set('threshold', 50) - .platform('ios') - .set('side', 'top') - .unset('threshold') - .media('lg') - .set('side', 'right') - -config.platform('ios') - .behavior(function() { - do something - }) - .defaults({ - side: 'right' - }) - -config.platform('ios').media('tablet') - .defaults({ - side: 'bottom' - }); -*/ - - -/* - User wants to remove the default behavior for sidemenu, but that's stuck under `.platform('ios').` - -config.platform('ios').media('tablet') === config.media('tablet').platform('ios') -*/ -var QUERIES = { - sm: true, - md: true, - lg: true -}; -var PLATFORMS = { - ios: true, - android: true -}; - -function isPlatform(key = '') { - return key.toLowerCase() in PLATFORMS; -} -function isMedia(key = '') { - return key.toLowerCase() in QUERIES; -} -class ConfigCase { - constructor({ root, parent, path }) { - this._root = root; - this._parent = parent; - this._path = path || []; - this._values = {}; - this.behaviors = []; - } - platform(key = '') { - if (isPlatform(key)) return this._root._addCase(key, this); - return this; - } - media(key = '') { - if (isMedia(key)) return this._root._addCase(key, this); - return this; - } - when(condition = '') { - if (isPlatform(condition) || isMedia(condition)) { - return this._root._addCase(condition, this); - } - return this; - } - behavior(fn) { - this.behaviors.push(fn); - return this; - } - set(a, b) { - if (util.isString(a)) { - this._values[a] = b; - } else { - util.extend(this._values, a || {}); - } - return this; - } - unset(key) { - delete this._values[key]; - return this; - } - get(key) { - return util.isDefined(this._values[key]) ? - this._values[key] : - (this._parent ? this._parent.get(key) : undefined); - } -} - -export class IonConfig extends ConfigCase { - constructor() { - this._root = this; - this._cases = {}; - super({ - root: this, - parent: null, - path: '' - }); - } - invoke(instance) { - return invokeConfig(this, instance); - } - _addCase(key, baseCase) { - var path = baseCase._path.slice(); - path.push(key); - - // Remove empties & duplicates - path = path - .filter((value, index) => { - return value && path.indexOf(value) === index; - }) - .sort(); - - if (path.join(' ') === baseCase._path.join(' ')) { - return baseCase; - } - return this._createCase(path); - } - _createCase(path) { - if (!path.length) return this; - var pathStr = path.join(' '); - var configCase = this._cases[pathStr]; - if (!configCase) { - var parentPath = path.slice(0, path.length - 1); - configCase = this._cases[pathStr] = new ConfigCase({ - root: this, - parent: this._createCase(parentPath), - path: path - }); - } - return configCase; - } -} - -export function invokeConfig(config, object, opts = {}) { - util.defaults(opts, { media: 'lg', platform: 'ios' }); - var { platform, media } = opts; - - var passedCases = [config].concat( - Object.keys(config._cases) - .map(name => config._cases[name]) - .filter(configCasePasses) - .sort(function(a,b) { - return a._path.length < b._path.length ? -1 : 1; - }) - ); - - // Extend the given object with the values of all the passed cases, starting with the - // most specific. - var defaults = [object]; - var behaviors = []; - for (let i = 0, ii = passedCases.length; i < ii; i++) { - defaults.push(passedCases[i]._values); - // Avoid allocating a new array for each passed case's array of behaviors - behaviors.push.apply(behaviors, passedCases[i].behaviors); - } - - util.defaults.apply(null, defaults); - - for (let i = 0, ii = behaviors.length; i < ii; i++) { - behaviors[i].call(object, object); - } - - function configCasePasses(configCase) { - var path = configCase._path; - var key; - for (let i = 0, ii = path.length; i < ii; i++) { - if (!(media === path[i] || platform === path[i])) return false; - } - return true; - } - -} diff --git a/src/config_spec.js b/src/config_spec.js deleted file mode 100644 index 59c6f000c6..0000000000 --- a/src/config_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import {IonConfig} from './config'; - -// TODO stop hardcoding platforms and media sizes -export function main() { - var rootConfig; - beforeEach(() => { - rootConfig = new IonConfig(); - }); - - it('should create a config one level down', () => { - var sub = rootConfig.platform('ios'); - expect(sub._parent).toBe(rootConfig); - expect(sub._path).toEqual(['ios']); - expect(rootConfig._cases.ios).toBe(sub); - }); - - it('should create a config two levels down', () => { - var sub1 = rootConfig.platform('ios'); - var sub2 = sub1.media('lg'); - expect(sub2._parent).toBe(sub1); - expect(sub1._parent).toBe(rootConfig); - expect(rootConfig._cases['ios lg']).toBe(sub2); - expect(rootConfig._cases.ios).toBe(sub1); - }); - - it('set should be chainable', () => { - expect(rootConfig.set()).toBe(rootConfig); - }); - - it('should set values on the root', () => { - rootConfig.set({ - letter: 'a' - }); - expect(rootConfig.get('letter')).toBe('a'); - }); - - it('should always return the same object for the same key', () => { - expect(rootConfig.platform('android')).toBe(rootConfig.platform('android')); - expect(rootConfig.platform('ios')).toBe(rootConfig.platform('ios')); - expect(rootConfig.media('lg')).toBe(rootConfig.media('lg')); - }); - - it('should return the same object when nesting in different order', () => { - var sub1 = rootConfig.platform('ios').media('sm'); - var sub2 = rootConfig.media('sm').platform('ios'); - expect(sub1).toBe(sub2); - }); - - it('should return the same object when nesting in different order for huge queries', () => { - var sub1 = rootConfig.platform('ios').media('sm').platform('android').media('lg'); - var sub2 = rootConfig.media('sm').media('lg').platform('android').platform('ios'); - expect(sub1).toBe(sub2); - }); - - it('should set values one level down and be chainable', () => { - rootConfig.set({ letter: 'a' }); - var sub1 = rootConfig.platform('ios'); - expect(sub1.get('letter')).toBe('a'); - expect( sub1.set({ letter: 'b' }) ).toBe(sub1); - expect(sub1.get('letter')).toBe('b'); - }); - - it('should set values two levels down and be chainable', () => { - rootConfig.set({ letter: 'a' }); - var sub1 = rootConfig.platform('ios'); - sub1.set({ letter: 'b' }); - var sub2 = sub1.media('lg'); - expect(sub2.get('letter')).toBe('b'); - expect( sub2.set({ letter: 'c' }) ).toBe(sub2); - expect(sub2.get('letter')).toBe('c'); - }); - - it('should use parent\'s value if its later set to undefined', () => { - rootConfig.set({ letter: 'a' }); - var sub1 = rootConfig.platform('ios'); - sub1.set({ letter: 'b' }); - expect(sub1.get('letter')).toBe('b'); - expect( sub1.unset('letter') ).toBe(sub1); - expect(sub1.get('letter')).toBe('a'); - }); - - it('when() as alias for media()', () => { - expect(rootConfig.when('lg')).toBe(rootConfig.media('lg')); - expect(rootConfig.when('bad')).toBe(rootConfig); - expect(rootConfig.when('lg')).not.toBe(rootConfig.when('ios')); - }); - - it('when() as alias for platform()', () => { - expect(rootConfig.platform('ios')).toBe(rootConfig.when('ios')); - expect(rootConfig.when('bad')).toBe(rootConfig); - }); - - describe('invokeConfig', function() { - - it('should invoke defaults', () => { - var obj = {}; - rootConfig.set('foo', 'bar'); - rootConfig.invoke(obj); - }); - - it('should invoke defaults in nested whens', () => { - var obj = {}; - rootConfig.set({ a: 'root', b: 'root' }); - rootConfig.when('ios').set({b: 'ios', c: 'ios'}); - rootConfig.when('ios').when('lg').set({ c: 'ios-lg', d: 'ios-lg' }); - - rootConfig.invoke(obj); - expect(obj).toEqual({ - a: 'root', - b: 'ios', - c: 'ios-lg', - d: 'ios-lg' - }); - }); - - it('should run behaviors', () => { - var obj = {}; - rootConfig.behavior(instance => { - instance.foo = 'bar'; - }); - rootConfig.invoke(obj); - expect(obj.foo).toBe('bar'); - }); - - it('should invoke behaviors in nested whens', () => { - var obj = {}; - rootConfig.when('ios') - .behavior(o => o.ios = true) - .when('lg') - .behavior(o => o.lg = true) - rootConfig.invoke(obj); - expect(obj).toEqual({ - ios: true, - lg: true - }); - }); - }); -} diff --git a/src/core/config/config-case.js b/src/core/config/config-case.js new file mode 100644 index 0000000000..6abea2281a --- /dev/null +++ b/src/core/config/config-case.js @@ -0,0 +1,47 @@ +import * as util from '../../util'; +import {platform} from '../platform/platform'; + +function isPlatform(key = '') { + return platform.isRegistered(key.toLowerCase()); +} + +export class ConfigCase { + constructor({ root, parent, path }) { + this._root = root; + this._parent = parent; + this._path = path || []; + this._values = {}; + this._component = null; + } + platform(key = '') { + if (isPlatform(key)) return this._root._addCase(key, this); + return this; + } + // media(key = '') { + // if (isMedia(key)) return this._root._addCase(key, this); + // return this; + // } + when(condition = '') { + if (isPlatform(condition)) { + return this._root._addCase(condition, this); + } + return this; + } + component(Class) { + this._component = Class; + return this; + } + set(obj) { + util.extend(this._values, obj); + return this; + } + unset(key) { + delete this._values[key]; + return this; + } + get(key) { + return util.isDefined(this._values[key]) ? + this._values[key] : + (this._parent ? this._parent.get(key) : undefined); + } +} diff --git a/src/core/config/config.js b/src/core/config/config.js index 2ab6e3c235..cab014d331 100644 --- a/src/core/config/config.js +++ b/src/core/config/config.js @@ -1,100 +1,29 @@ -import getPlatform from '../platform/platform'; +import {platform} from '../platform/platform'; +import {ConfigCase} from './config-case'; import * as util from '../../util'; // TODO stop hardcoding platforms and media sizes /* - config - .set({ side: 'left' }) - .set('threshold', 50) - .platform('ios') - .set('side', 'top') - .unset('threshold') - .media('lg') - .set('side', 'right') +@ConfigPlatform({ + for: AsideBase + platform: 'android', + defaults: { + type: 'reveal' + } +}) +class AndroidAside extends AsideBase {} -config.platform('ios') - .behavior(function() { - do something - }) - .defaults({ - side: 'right' - }) - -config.platform('ios').media('tablet') - .defaults({ - side: 'bottom' - }); +```@ConfigCase({ + for: AsideBase, + condition: instance => instance.type === 'reveal' +}) +class AsideReveal { + constructor(aside: AsideBase) {} +} */ - -/* - User wants to remove the default behavior for sidemenu, but that's stuck under `.platform('ios').` - -config.platform('ios').media('tablet') === config.media('tablet').platform('ios') -*/ -var QUERIES = { - sm: true, - md: true, - lg: true -}; -var PLATFORMS = { - ios: true, - android: true -}; - -function isPlatform(key = '') { - return key.toLowerCase() in PLATFORMS; -} -function isMedia(key = '') { - return key.toLowerCase() in QUERIES; -} -class ConfigCase { - constructor({ root, parent, path }) { - this._root = root; - this._parent = parent; - this._path = path || []; - this._values = {}; - this.behaviors = []; - } - platform(key = '') { - if (isPlatform(key)) return this._root._addCase(key, this); - return this; - } - media(key = '') { - if (isMedia(key)) return this._root._addCase(key, this); - return this; - } - when(condition = '') { - if (isPlatform(condition) || isMedia(condition)) { - return this._root._addCase(condition, this); - } - return this; - } - behavior(fn) { - this.behaviors.push(fn); - return this; - } - set(a, b) { - if (util.isString(a)) { - this._values[a] = b; - } else { - util.extend(this._values, a || {}); - } - return this; - } - unset(key) { - delete this._values[key]; - return this; - } - get(key) { - return util.isDefined(this._values[key]) ? - this._values[key] : - (this._parent ? this._parent.get(key) : undefined); - } -} - -export class IonConfig extends ConfigCase { +export class Config extends ConfigCase { constructor() { this._root = this; this._cases = {}; @@ -108,7 +37,7 @@ export class IonConfig extends ConfigCase { return invokeConfig(this, instance); } _addCase(key, baseCase) { - var path = baseCase._path.slice(); + let path = baseCase._path.slice(); path.push(key); // Remove empties & duplicates @@ -125,10 +54,10 @@ export class IonConfig extends ConfigCase { } _createCase(path) { if (!path.length) return this; - var pathStr = path.join(' '); - var configCase = this._cases[pathStr]; + let pathStr = path.join(' '); + let configCase = this._cases[pathStr]; if (!configCase) { - var parentPath = path.slice(0, path.length - 1); + let parentPath = path.slice(0, path.length - 1); configCase = this._cases[pathStr] = new ConfigCase({ root: this, parent: this._createCase(parentPath), @@ -139,11 +68,10 @@ export class IonConfig extends ConfigCase { } } -export function invokeConfig(config, object, opts = {}) { - util.defaults(opts, { media: 'lg', platform: 'ios' }); - var { platform, media } = opts; +export function invokeConfig(config, object) { + let platformName = platform.get().name; - var passedCases = [config].concat( + let passedCases = [config].concat( Object.keys(config._cases) .map(name => config._cases[name]) .filter(configCasePasses) @@ -154,25 +82,25 @@ export function invokeConfig(config, object, opts = {}) { // Extend the given object with the values of all the passed cases, starting with the // most specific. - var defaults = [object]; - var behaviors = []; + let defaults = [object]; + // Also find the most specific case with a component that we should use. + let ComponentToUse; for (let i = 0, ii = passedCases.length; i < ii; i++) { defaults.push(passedCases[i]._values); - // Avoid allocating a new array for each passed case's array of behaviors - behaviors.push.apply(behaviors, passedCases[i].behaviors); + if (passedCases[i]._component) { + ComponentToUse = passedCases[i]._component; + } } util.defaults.apply(null, defaults); - for (let i = 0, ii = behaviors.length; i < ii; i++) { - behaviors[i].call(object, object); - } + return ComponentToUse; function configCasePasses(configCase) { - var path = configCase._path; - var key; + let path = configCase._path; + let key; for (let i = 0, ii = path.length; i < ii; i++) { - if (!(media === path[i] || platform === path[i])) return false; + if (platformName !== path[i]) return false; } return true; } diff --git a/src/core/platform/platform.js b/src/core/platform/platform.js index 02cfb4751a..3eec9f63e3 100644 --- a/src/core/platform/platform.js +++ b/src/core/platform/platform.js @@ -1,19 +1,13 @@ -var platforms = []; - -// TODO(ajoslin): move this to a facade somewhere else? -var ua = window.navigator.userAgent; +import * as util from '../../util'; class Platform { - constructor({ - name, - matcher - }) { - this.name = name; - this.matcher = matcher; + constructor(options) { + util.extend(this, options); } } class PlatformController { + current: Platform; constructor() { this.registry = []; } @@ -21,11 +15,19 @@ class PlatformController { this.current = platform; } get() { - return platform; + return this.current; } register(platform) { + if (!platform instanceof Platform) { + platform = new Platform(platform); + } this.registry.push(platform); } + isRegistered(platformName) { + return this.registry.some(platform => { + return platform.name === platformName; + }); + } detect() { for (let platform of this.registry) { if (platform.matcher()) { @@ -37,18 +39,23 @@ class PlatformController { export let platform = new PlatformController(); -platform.register(new Platform({ - name: 'android', - matcher: () => { - return /android/i.test(ua) - } -})); -platform.register(new Platform({ - name: 'ios', - matcher: () => { - return /iPhone|iPad|iPod/.test(ua) - } -}) +// TODO(ajoslin): move this to a facade somewhere else? +var ua = window.navigator.userAgent; -function detectPlatform() { -} +// TODO(ajoslin): move these to their own files +platform.register({ + name: 'android', + matcher() { + return /android/i.test(ua); + } +}); +platform.register({ + name: 'ios', + // For now always default to ios + matcher() { + return true; + } +}); + + +platform.set( platform.detect() ); diff --git a/src/platform.js b/src/platform.js deleted file mode 100644 index 0689cc66fb..0000000000 --- a/src/platform.js +++ /dev/null @@ -1,4 +0,0 @@ - -export function getPlatform() { - return 'android'; -}