diff --git a/gulpfile.js b/gulpfile.js index af6c010288..c85a99924a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -90,16 +90,7 @@ gulp.task('ng2', ['ng2-rename'], function() { var builder = new SystemJsBuilder(); return builder.loadConfig('jspm-config.js') .then(function() { - builder.config({ - map: { - 'angular2': 'dist/lib/angular2', - 'rtts_assert': 'dist/lib/rtts_assert' - }, - paths: { - dist: undefined, - } - }); - return builder.build('angular2/angular2', 'dist/lib/angular2.js'); + return builder.build('dist/lib/angular2/angular2', 'dist/lib/angular2.js'); }); }); diff --git a/jspm-config.js b/jspm-config.js index a7e2cd5897..60646a01ac 100644 --- a/jspm-config.js +++ b/jspm-config.js @@ -1,7 +1,8 @@ System.config({ "paths": { "*": "*.js", - "dist": "/dist" + "dist/*": "/dist", + "node_modules/*": "/node_modules/*", }, "traceurOptions": { "sourceMaps": true, @@ -16,9 +17,10 @@ System.config({ System.config({ "map": { "angular2": "dist/lib/angular2", - "hammer": "/node_modules/hammerjs/hammer", + "hammer": "node_modules/hammerjs/hammer.js", + "rtts_assert": "dist/lib/rtts_assert", + "rx": "node_modules/rx", "ionic2": "/src", - "rtts_assert": "dist/lib/rtts_assert" } }); diff --git a/package.json b/package.json index dbba5f7257..cec164e029 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "jasmine-core": "^2.2.0", "karma-chrome-launcher": "^0.1.7", "karma-jasmine": "^0.3.5", + "rx": "^2.4.6", "systemjs": "~0.11.0", "traceur": "0.0.87", "zone.js": "0.4.1" diff --git a/src/components.js b/src/components.js index cb90a29601..90f07c9ba0 100644 --- a/src/components.js +++ b/src/components.js @@ -1 +1,8 @@ -export * from './components/aside/aside'; +//BUNDLE ONLY + +import {Aside, AsideParent} from './components/aside/aside'; + +export let ionicComponents = [ + Aside, + AsideParent +]; diff --git a/src/components/app/examples/figure-it-out/components/stylers.js b/src/components/app/examples/figure-it-out/components/stylers.js index 3d8ca55c9d..da9cf0f01f 100644 --- a/src/components/app/examples/figure-it-out/components/stylers.js +++ b/src/components/app/examples/figure-it-out/components/stylers.js @@ -1,12 +1,12 @@ -import {Decorator, NgElement, Template, Component} from 'angular2/angular2'; +import {Component, NgElement, Template} from 'angular2/angular2'; -@Decorator({ +@Component({ selector: '[red-bg]' }) @Template({ - inline: 'test' + inline: 'red template' }) -export class TestRedDecorator { +export class RedBgStyler { constructor( element:NgElement ) { @@ -15,11 +15,11 @@ export class TestRedDecorator { } -@Decorator({ +@Component({ selector: '[blue-bg]' }) @Template({ - inline: 'test' + inline: 'blue template' }) export class BlueTextStyler { constructor( diff --git a/src/components/app/examples/figure-it-out/main.js b/src/components/app/examples/figure-it-out/main.js index 418283d041..0008609175 100644 --- a/src/components/app/examples/figure-it-out/main.js +++ b/src/components/app/examples/figure-it-out/main.js @@ -1,6 +1,14 @@ -import {DynamicComponent, Component, Template, bootstrap} from 'angular2/angular2'; +import {DynamicComponent, Component, Template, bootstrap, NgElement} from 'angular2/angular2'; +import {Inject} from 'angular2/di'; import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader'; 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', @@ -12,8 +20,12 @@ class MyDynamic { loader:PrivateComponentLoader, location:PrivateComponentLocation ) { - loader.load(RedBgStyler, location); - loader.load(BlueTextStyler, location); + // loader.load(RedBgStyler, location); + // loader.load(BlueTextStyler, location); + Testy.annotations = [ + new Component({ selector: 'testy' }), + new Template({ inline: 'testy-template' }) + ]; } } @@ -28,6 +40,9 @@ class MyDynamic { directives: [MyDynamic], }) class MyApp { + constructor() { + console.log('MyApp loaded'); + } } bootstrap(MyApp); diff --git a/src/components/aside/aside.js b/src/components/aside/aside.js index f734c358ab..9a8eac233f 100644 --- a/src/components/aside/aside.js +++ b/src/components/aside/aside.js @@ -89,7 +89,7 @@ class AsideSlideGesture extends SlideEdgeGesture { super(slideElement, { direction: (aside.side === 'left' || aside.side === 'right') ? 'x' : 'y', edge: aside.side || 'left', - threshold: aside.dragThreshold || 100 + threshold: /*aside.dragThreshold || */150 }); } diff --git a/src/components/aside/examples/basic/main.js b/src/components/aside/examples/basic/main.js index 734a853637..90a20cedfc 100644 --- a/src/components/aside/examples/basic/main.js +++ b/src/components/aside/examples/basic/main.js @@ -1,14 +1,12 @@ -import {Aside, AsideParent} from 'ionic2/components/aside/aside'; +import {ionicComponents} from 'ionic2/components'; import {Template, Component, bootstrap} from 'angular2/angular2'; - @Component({ selector: 'aside-app' }) @Template({ - // Inlined version of main.html - inline: ` - + directives: ionicComponents, + inline: ` LEFT

...

@@ -42,13 +40,9 @@ import {Template, Component, bootstrap} from 'angular2/angular2'; - - `, - directives: [Aside, AsideParent] + ` }) class AsideApp { - constructor() { - } openLeft() { } } diff --git a/src/core/config/config.js b/src/core/config/config.js new file mode 100644 index 0000000000..2ab6e3c235 --- /dev/null +++ b/src/core/config/config.js @@ -0,0 +1,180 @@ +import getPlatform from '../platform/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/core/config/config_spec.js b/src/core/config/config_spec.js new file mode 100644 index 0000000000..e98a27e310 --- /dev/null +++ b/src/core/config/config_spec.js @@ -0,0 +1,138 @@ +import {Config} from './config'; + +// TODO stop hardcoding platforms and media sizes +export function main() { + var rootConfig; + beforeEach(() => { + rootConfig = new Config(); + }); + + 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/media/media.js b/src/core/media/media.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/core/platform/detect.js b/src/core/platform/detect.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/core/platform/platform.js b/src/core/platform/platform.js new file mode 100644 index 0000000000..02cfb4751a --- /dev/null +++ b/src/core/platform/platform.js @@ -0,0 +1,54 @@ +var platforms = []; + +// TODO(ajoslin): move this to a facade somewhere else? +var ua = window.navigator.userAgent; + +class Platform { + constructor({ + name, + matcher + }) { + this.name = name; + this.matcher = matcher; + } +} + +class PlatformController { + constructor() { + this.registry = []; + } + set(platform) { + this.current = platform; + } + get() { + return platform; + } + register(platform) { + this.registry.push(platform); + } + detect() { + for (let platform of this.registry) { + if (platform.matcher()) { + return platform; + } + } + } +} + +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) + } +}) + +function detectPlatform() { +}