From 7f23b84b74322fa46c3647414495d83074a57186 Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Fri, 16 Jun 2017 15:54:15 +0200 Subject: [PATCH 1/8] chore(deps): update React Native to 0.45.1 (#104) --- package.json | 2 +- sample/samples/ios/widgets.ts | 6 - src/components/ios/map_view.ts | 217 --------------------------- src/ios.ts | 1 - src/modules/ios_module.ts | 5 +- src/renderer/node.ts | 2 +- src/test_helpers/utils.ts | 3 +- src/testing.ts | 1 - src/wrapper/wrapper_impl.ts | 1 - test/components/ios/map_view_spec.ts | 61 -------- 10 files changed, 5 insertions(+), 294 deletions(-) delete mode 100644 src/components/ios/map_view.ts delete mode 100644 test/components/ios/map_view_spec.ts diff --git a/package.json b/package.json index 0fc549f..48fa110 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@angular/platform-server": "2.4.1", "@angular/router": "3.4.1", "hammerjs": "2.0.6", - "react-native": "0.39.2", + "react-native": "0.45.1", "reflect-metadata": "0.1.8", "rxjs": "5.0.1", "zone.js": "0.7.4" diff --git a/sample/samples/ios/widgets.ts b/sample/samples/ios/widgets.ts index 05f91c4..9b89ea0 100644 --- a/sample/samples/ios/widgets.ts +++ b/sample/samples/ios/widgets.ts @@ -50,12 +50,6 @@ import {StyleSheet} from 'react-native'; Date picker ({{selectedDate}}) - - - Map view - - ` }) diff --git a/src/components/ios/map_view.ts b/src/components/ios/map_view.ts deleted file mode 100644 index 8b9b859..0000000 --- a/src/components/ios/map_view.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {Component, Inject, Output, EventEmitter} from "@angular/core"; -import {HighLevelComponent, GENERIC_INPUTS, GENERIC_BINDINGS} from "../common/component"; -import {REACT_NATIVE_WRAPPER} from "./../../renderer/renderer"; -import {ReactNativeWrapper} from "../../wrapper/wrapper"; - -/** - * A component for displaying a map. - * - * ``` - @Component({ - selector: 'sample', - template: `` -}) - export class Sample {} - * ``` - * @style https://facebook.github.io/react-native/docs/view.html#style - * @platform ios - */ -@Component({ - selector: 'MapView', - inputs: [ - 'pitchEnabled', 'region', 'rotateEnabled', 'scrollEnabled', 'showsUserLocation', 'zoomEnabled', - 'annotations', 'followUserLocation', 'legalLabelInsets', 'mapType', 'maxDelta', 'minDelta', - 'overlays', 'showsCompass', 'showsPointsOfInterest' - ].concat(GENERIC_INPUTS), - template: `` -}) -export class MapView extends HighLevelComponent { - constructor(@Inject(REACT_NATIVE_WRAPPER) wrapper: ReactNativeWrapper) { - super(wrapper); - } - - //Events - /** - * To be documented - */ - @Output() change: EventEmitter = new EventEmitter(); - /** - * To be documented - * {annotation: Annotation, action: "annotation-click"} - */ - @Output() annotationPress: EventEmitter = new EventEmitter(); - /** - * To be documented - * {annotation: Annotation} - */ - @Output() annotationFocus: EventEmitter = new EventEmitter(); - /** - * To be documented - * {annotation: Annotation} - */ - @Output() annotationBlur: EventEmitter = new EventEmitter(); - /** - * To be documented - * {annotation: Annotation} - */ - @Output() annotationDragStateChange: EventEmitter = new EventEmitter(); - - //Properties - public _pitchEnabled: boolean; - public _region: any; - public _rotateEnabled: boolean; - public _scrollEnabled: boolean; - public _showsUserLocation: boolean; - public _zoomEnabled: boolean; - /** - * To be documented - */ - set pitchEnabled(value: any) {this._pitchEnabled = this.processBoolean(value);} - /** - * To be documented - * {latitude: number, longitude: number, latitudeDelta: number, longitudeDelta: number} - */ - set region(value: any) {this._region = value;} - /** - * To be documented - */ - set rotateEnabled(value: any) {this._rotateEnabled = this.processBoolean(value);} - /** - * To be documented - */ - set scrollEnabled(value: any) {this._scrollEnabled = this.processBoolean(value);} - /** - * To be documented - */ - set showsUserLocation(value: any) {this._showsUserLocation = this.processBoolean(value);} - /** - * To be documented - */ - set zoomEnabled(value: any) {this._zoomEnabled = this.processBoolean(value);} - - //TODO: manage views and events on annotations - public _annotations: Array; - public _annotationEventsEnabled: boolean = false; - public _annotationMap: Map; - public _followUserLocation: boolean; - public _legalLabelInsets: any; - public _mapType: string; - public _maxDelta: number; - public _minDelta: number; - public _overlays: Array; - public _showsCompass: boolean; - public _showsPointsOfInterest: boolean; - /** - * To be documented - * [{latitude: number, longitude: number, animateDrop: bool, draggable: bool, title: string, subtitle: string, - * tintColor: color, image: source, id: string}] - * Custom views in annotations are not yet managed. - */ - set annotations(value: Array) { - if(value) { - this._annotationEventsEnabled = true; - this._annotationMap = new Map(); - this._annotations = value.map((annotation) => { - var res = annotation; - res['id'] = annotation.id || encodeURIComponent(JSON.stringify(annotation)); - this._annotationMap.set(res['id'], res); - if (annotation.tintColor) { - res['tintColor'] = this.processColor(annotation.tintColor); - } - if (annotation.image) { - res['image'] = this.resolveAssetSource(annotation.image); - } - return res; - }); - } else { - this._annotationEventsEnabled = false; - delete this._annotations; - } - } - /** - * To be documented - */ - set followUserLocation(value: any) {this._followUserLocation = this.processBoolean(value);} - /** - * To be documented - * {top: number, left: number, bottom: number, right: number} - */ - set legalLabelInsets(value: any) {this._legalLabelInsets = value;} - /** - * To be documented - */ - set mapType(value: string) {this._mapType = this.processEnum(value, ['standard', 'satellite', 'hybrid']);} - /** - * To be documented - */ - set maxDelta(value: any) {this._maxDelta = this.processNumber(value);} - /** - * To be documented - */ - set minDelta(value: any) {this._minDelta = this.processNumber(value);} - /** - * To be documented - * [{coordinates: [{latitude: number, longitude: number}], lineWidth: number, strokeColor: color, fillColor: color, id: string}] - */ - set overlays(value: Array) { - if(value) { - this._overlays = value.map((overlay) => { - var res = overlay; - res['id'] = overlay.id || encodeURIComponent(JSON.stringify(overlay)); - this._annotationMap.set(res['id'], res); - if (overlay.strokeColor) { - res['strokeColor'] = this.processColor(overlay.strokeColor); - } - if (overlay.fillColor) { - res['fillColor'] = this.processColor(overlay.fillColor); - } - return res; - }); - } else { - delete this._overlays; - } - } - /** - * To be documented - */ - set showsCompass(value: any) {this._showsCompass = this.processBoolean(value);} - /** - * To be documented - */ - set showsPointsOfInterest(value: any) {this._showsPointsOfInterest = this.processBoolean(value);} - - - _handleChange(event: any) { - //Event example: {region: {latitude: 45, longitude: 0, latitudeDelta: 9, longitudeDelta: 73}, continuous: true} - this.change.emit(event); - } - - _handlePress(event: any) { - //Event example: {annotation: Object, action: "annotation-click", target: 22} - this.annotationPress.emit({annotation: event.annotation, action: event.action}) - } - - _handleAnnotationFocus(event: any) { - //Event example: {target: 22, annotationId: "fdsfs"} - this.annotationFocus.emit({annotation: this._annotationMap.get(event.annotationId)}); - } - - _handleAnnotationBlur(event: any) { - //Event example: {target: 22, annotationId: "fdsfs"} - this.annotationFocus.emit({annotation: this._annotationMap.get(event.annotationId)}); - } - - _handleAnnotationDragStateChange(event: any) { - //Event example: {target: 22, annotationId: "fdsfs"} - this.annotationDragStateChange.emit({annotation: this._annotationMap.get(event.annotationId)}); - } -} diff --git a/src/ios.ts b/src/ios.ts index a50fab4..c99e68f 100644 --- a/src/ios.ts +++ b/src/ios.ts @@ -11,7 +11,6 @@ export {View} from './components/ios/_view'; export {WebView} from './components/ios/_webview'; export {DatePicker} from './components/ios/date_picker'; -export {MapView} from './components/ios/map_view'; export {Navigator} from './components/ios/navigator'; export {ProgressView} from './components/ios/progress_view'; export {SegmentedControl} from './components/ios/segmented_control'; diff --git a/src/modules/ios_module.ts b/src/modules/ios_module.ts index af258c4..fd25283 100644 --- a/src/modules/ios_module.ts +++ b/src/modules/ios_module.ts @@ -15,7 +15,6 @@ import {View} from "../components/ios/_view"; import {WebView} from "../components/ios/_webview"; import {DatePicker} from "../components/ios/date_picker"; -import {MapView} from "../components/ios/map_view"; import {Navigator, NavigatorItem} from "../components/ios/navigator"; import {ProgressView} from "../components/ios/progress_view"; import {SegmentedControl} from "../components/ios/segmented_control"; @@ -24,9 +23,9 @@ import {TabBarItem} from "../components/ios/tabbar_item"; @NgModule({ declarations: [ActivityIndicator, Image, Picker, RefreshControl, ScrollView, Slider, Switch, Text, TextInput, View, WebView, - DatePicker, MapView, Navigator, NavigatorItem, ProgressView, SegmentedControl, TabBar, TabBarItem], + DatePicker, Navigator, NavigatorItem, ProgressView, SegmentedControl, TabBar, TabBarItem], exports: [ActivityIndicator, Image, Picker, RefreshControl, ScrollView, Slider, Switch, Text, TextInput, View, WebView, - DatePicker, MapView, Navigator, ProgressView, SegmentedControl, TabBar, TabBarItem], + DatePicker, Navigator, ProgressView, SegmentedControl, TabBar, TabBarItem], imports: [CommonModule, ApplicationModule], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/src/renderer/node.ts b/src/renderer/node.ts index 4c1e5c4..09b62b4 100644 --- a/src/renderer/node.ts +++ b/src/renderer/node.ts @@ -274,7 +274,7 @@ export class ElementNode extends Node { //TODO: generalize the mechanism (list? regexp? meta data?) if (['dummy-anchor-for-dynamic-loader', 'View', 'Text', 'Switch', 'TextInput', 'WebView', 'Image', 'ProgressBar', 'PagerLayout', 'Picker', 'ScrollView', 'DrawerLayout', 'DrawerLayoutSide', 'DrawerLayoutContent', 'RefreshControl', 'Toolbar', - 'ActivityIndicator', 'DatePicker', 'MapView', 'Navigator', 'NavigatorItem', 'ProgressView', 'SegmentedControl', 'Slider', 'TabBar', 'TabBarItem'].indexOf(tagName) > -1) { + 'ActivityIndicator', 'DatePicker', 'Navigator', 'NavigatorItem', 'ProgressView', 'SegmentedControl', 'Slider', 'TabBar', 'TabBarItem'].indexOf(tagName) > -1) { this.isVirtual = true; } } diff --git a/src/test_helpers/utils.ts b/src/test_helpers/utils.ts index 5e29ffd..7cf725b 100644 --- a/src/test_helpers/utils.ts +++ b/src/test_helpers/utils.ts @@ -15,7 +15,6 @@ import {ReactNativeWrapper} from "../wrapper/wrapper"; import {Node} from "../renderer/node"; import {ReactNativeAndroidModule} from "../modules/android_module"; import {DatePicker} from "./../components/ios/date_picker"; -import {MapView} from "./../components/ios/map_view"; import {Navigator, NavigatorItem} from "./../components/ios/navigator"; import {ProgressView} from "./../components/ios/progress_view"; import {SegmentedControl} from "./../components/ios/segmented_control"; @@ -27,7 +26,7 @@ export function configureTestingModule(mock: ReactNativeWrapper, testCpt: any, d imports: [ReactNativeAndroidModule].concat(modules), providers: getTestingProviders(mock, testCpt), schemas: [CUSTOM_ELEMENTS_SCHEMA], - declarations: [DatePicker, MapView, Navigator, NavigatorItem, ProgressView, SegmentedControl, TabBar, TabBarItem, testCpt].concat(declarations) + declarations: [DatePicker, Navigator, NavigatorItem, ProgressView, SegmentedControl, TabBar, TabBarItem, testCpt].concat(declarations) }); // return tb; diff --git a/src/testing.ts b/src/testing.ts index e5c7b2e..da56385 100644 --- a/src/testing.ts +++ b/src/testing.ts @@ -21,7 +21,6 @@ export * from './components/android/progress_bar'; export * from './components/android/toolbar'; export * from './components/ios/date_picker'; -export * from './components/ios/map_view'; export * from './components/ios/navigator'; export * from './components/ios/progress_view'; export * from './components/ios/segmented_control'; diff --git a/src/wrapper/wrapper_impl.ts b/src/wrapper/wrapper_impl.ts index be30ec8..b882a67 100644 --- a/src/wrapper/wrapper_impl.ts +++ b/src/wrapper/wrapper_impl.ts @@ -53,7 +53,6 @@ const RCT_VIEW_NAMES: { [s: string]: string } = ReactNative.Platform.OS == 'andr 'native-dialogpicker': 'RCTPicker', 'native-activityindicator': 'RCTActivityIndicatorView', 'native-datepicker': 'RCTDatePicker', - 'native-mapview': 'RCTMap', 'native-navigator': 'RCTNavigator', 'native-navitem': 'RCTNavItem', 'native-progressview': 'RCTProgressView', diff --git a/test/components/ios/map_view_spec.ts b/test/components/ios/map_view_spec.ts deleted file mode 100644 index 01ba25a..0000000 --- a/test/components/ios/map_view_spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {Component, ViewChild} from "@angular/core"; -import {MockReactNativeWrapper} from "./../../../src/wrapper/wrapper_mock"; -import {fireFunctionalEvent, configureTestingModule, initTest} from "../../../src/test_helpers/utils"; -import {MapView} from "../../../src/components/ios/map_view"; - -describe('DatePicker component (iOS)', () => { - const mock: MockReactNativeWrapper = new MockReactNativeWrapper(); - beforeEach(() => { - mock.reset(); - configureTestingModule(mock, TestComponent); - }); - - it('should render', () => { - initTest(TestComponent, ``); - expect(mock.commandLogs.toString()).toEqual( - 'CREATE+2+test-cmp+{},CREATE+3+native-mapview+{"onChange":true,"onPress":false,"onAnnotationDragStateChange":false,"onAnnotationFocus":false,"onAnnotationBlur":false},' + - 'ATTACH+1+2+0,ATTACH+2+3+0'); - }); - - it('should render with properties', () => { - initTest(TestComponent, ``); - expect(mock.commandLogs.toString()).toEqual( - 'CREATE+2+test-cmp+{},CREATE+3+native-mapview+{"onChange":true,' + - '"annotations":[{"longitude":1,"latitude":2,"tintColor":42,"id":"%7B%22longitude%22%3A1%2C%22latitude%22%3A2%2C%22tintColor%22%3A%22%23123456%22%7D"}],' + - '"onPress":true,"onAnnotationDragStateChange":true,"onAnnotationFocus":true,"onAnnotationBlur":true,"accessible":true,"testID":"foo"},ATTACH+1+2+0,ATTACH+2+3+0'); - }); - - it('should render with styles', () => { - initTest(TestComponent, ``); - expect(mock.commandLogs.toString()).toEqual( - 'CREATE+2+test-cmp+{},CREATE+3+native-mapview+{"onChange":true,"onPress":false,"onAnnotationDragStateChange":false,' + - '"onAnnotationFocus":false,"onAnnotationBlur":false,"flex":1,"collapse":true,"margin":42},ATTACH+1+2+0,ATTACH+2+3+0'); - }); - - it('should fire change event', () => { - const {fixture, rootRenderer} = initTest(TestComponent, ``); - mock.clearLogs(); - - const target = fixture.elementRef.nativeElement.children[0].children[0]; - fireFunctionalEvent('topChange', target, {annotation: {}, action: 'click'}); - - fixture.whenStable().then(() => { - expect(fixture.componentInstance.log.join(',')).toEqual('click'); - }); - - }); - -}); - -@Component({ - selector: 'test-cmp', - template: `to be overriden` -}) -class TestComponent { - @ViewChild(MapView) mapView: MapView; - log: Array = []; - - handleChange(event: any) { - this.log.push(event.action); - } -} \ No newline at end of file From 40f8e097d79df0ae5641f4c111278b428f2909dc Mon Sep 17 00:00:00 2001 From: Marc Laval Date: Thu, 22 Jun 2017 11:14:51 +0200 Subject: [PATCH 2/8] feat: migrate to Angular 4.x (#105) --- README.md | 2 +- doc/parser.js | 8 +- gulpfile.js | 23 +- karma-test-shim.js | 18 +- package.json | 45 +-- sample/tsconfig.json | 1 - src/components/ios/navigator.ts | 14 +- src/events/hammer.ts | 11 +- src/renderer/bootstrap_aot.ts | 37 +-- src/renderer/bootstrap_jit.ts | 36 +-- src/renderer/node.ts | 2 +- src/renderer/renderer.ts | 277 ++++++++---------- src/router/router_outlet.ts | 105 +++++-- src/test_helpers/router_testing_module.ts | 10 +- src/test_helpers/utils.ts | 7 +- test/components/android/drawer_layout_spec.ts | 4 +- test/components/ios/navigator_spec.ts | 10 +- test/renderer/component_without_host_spec.ts | 10 +- test/renderer/element_spec.ts | 10 +- tools/copy-dependencies.js | 12 - typings.json | 8 - 21 files changed, 316 insertions(+), 334 deletions(-) delete mode 100644 tools/copy-dependencies.js delete mode 100644 typings.json diff --git a/README.md b/README.md index c882ea8..004f06d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ http://angular.github.io/react-native-renderer/ ### Preparing your environment * Set up React Native for iOS and/or Android following [Getting started](https://facebook.github.io/react-native/docs/getting-started.html) and [Android setup](https://facebook.github.io/react-native/docs/android-setup.htmlt) * Clone this repository or a fork of it -* Install Gulp, React Native CLI and Typings globally: `npm install -g gulp react-native-cli typings` +* Install Gulp and React Native CLI globally: `npm install -g gulp react-native-cli` * Install local npm modules: `npm install` ### Running scripts diff --git a/doc/parser.js b/doc/parser.js index f74a66d..fc87c9a 100644 --- a/doc/parser.js +++ b/doc/parser.js @@ -35,11 +35,11 @@ function visit(sourceFile) { return data; function visitNode(node) { - var commentRanges = ts.getJsDocComments(node, sourceFile); - if (commentRanges) { - commentRanges.forEach(function (commentRange) { + var jsDocs = ts.getJSDocs(node); + if (jsDocs) { + jsDocs.forEach(function (jsDoc) { var content = sourceFile.text - .substring(commentRange.pos + 3, commentRange.end - 2) + .substring(jsDoc.pos + 3, jsDoc.end - 2) .replace(LEADING_STAR_REGEX, '') .trim(); if (content) { diff --git a/gulpfile.js b/gulpfile.js index 6a2705c..1f34a12 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,7 +1,6 @@ var fs = require('fs'); var gulp = require('gulp'); -const babel = require('gulp-babel'); var jade = require('gulp-jade'); var rename = require('gulp-rename'); var typescript = require('gulp-typescript'); @@ -34,20 +33,11 @@ var PATHS = { tmp: 'dist/tmp', publish: 'dist/publish', modules: [ + 'node_modules/@angular/**/*', 'node_modules/hammerjs/**/*', 'node_modules/reflect-metadata/**/*', 'node_modules/rxjs/**/*', 'node_modules/zone.js/**/*' - ], - es6Modules: [ - 'node_modules/@angular/common/**/*.js', '!node_modules/@angular/common/bundles/*', - 'node_modules/@angular/compiler/**/*.js', '!node_modules/@angular/compiler/bundles/*', - 'node_modules/@angular/core/**/*.js', '!node_modules/@angular/core/bundles/*', - 'node_modules/@angular/http/**/*.js', '!node_modules/@angular/http/bundles/*', - 'node_modules/@angular/platform-browser/**/*.js', '!node_modules/@angular/platform-browser/bundles/*', - 'node_modules/@angular/platform-browser-dynamic/**/*.js', '!node_modules/@angular/platform-browser-dynamic/bundles/*', - 'node_modules/@angular/platform-server/**/*.js', '!node_modules/@angular/platform-server/bundles/*', - 'node_modules/@angular/router/**/*.js', '!node_modules/@angular/router/bundles/*', ] }; @@ -69,17 +59,10 @@ gulp.task('!postcreate', ['!create'], function() { .pipe(gulp.dest(PATHS.app + '/' + APP_NAME + '/android/app/src/main/')); }); -gulp.task('!ng2commonjs', ['!postcreate'], function() { - return gulp.src(PATHS.es6Modules, {base: 'node_modules'}) - .pipe(babel({ - presets: ['es2015'] - })) +gulp.task('init', ['!postcreate'], function() { + return gulp.src(PATHS.modules, { base: './node_modules/' }) .pipe(gulp.dest(PATHS.app + '/' + APP_NAME + '/node_modules')); }); -gulp.task('init', ['!ng2commonjs'], function() { - var copier = require('./tools/copy-dependencies'); - return copier.doCopy(PATHS.modules, PATHS.app + '/' + APP_NAME + '/node_modules'); -}); //--------------------------- JIT compilation--------------------------- gulp.task('!assets', function () { diff --git a/karma-test-shim.js b/karma-test-shim.js index d987e36..5d0e3cb 100644 --- a/karma-test-shim.js +++ b/karma-test-shim.js @@ -37,15 +37,19 @@ System.config( }, map: { // angular bundles + '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', + '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js', '@angular/core': 'npm:@angular/core/bundles/core.umd.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '@angular/http': 'npm:@angular/http/bundles/http.umd.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.js', // angular testing umd bundles + '@angular/animations/browser/testing': 'npm:@angular/animations/bundles/animations-browser-testing.umd.js', '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', @@ -66,11 +70,15 @@ System.config( System.import('@angular/core/testing') .then(function(coreTesting){ - return System.import('@angular/platform-browser-dynamic/testing') - .then(function(browserTesting) { + return Promise + .all([ + System.import('@angular/platform-browser-dynamic/testing'), + System.import('@angular/platform-browser/animations') + ]) + .then(function(mods) { coreTesting.TestBed.initTestEnvironment( - browserTesting.BrowserDynamicTestingModule, - browserTesting.platformBrowserDynamicTesting()); + [mods[0].BrowserDynamicTestingModule, mods[1].NoopAnimationsModule], + mods[0].platformBrowserDynamicTesting()); }); }) .then(function() { @@ -80,4 +88,4 @@ System.import('@angular/core/testing') allSpecFiles.map(function (moduleName) { return System.import(moduleName); })); -}).then(__karma__.start, __karma__.error); \ No newline at end of file +}).then(function() { __karma__.start(); }, function(error) { console.error(error); }); \ No newline at end of file diff --git a/package.json b/package.json index 48fa110..46314b8 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,7 @@ "version": "0.0.1-alpha.20", "description": "A React Native renderer for Angular 2", "scripts": { - "test": "gulp test.browser/ci", - "postinstall": "typings install" + "test": "gulp test.browser/ci" }, "repository": { "type": "git", @@ -25,45 +24,47 @@ }, "homepage": "https://github.com/angular/react-native-renderer/", "devDependencies": { - "@angular/compiler-cli": "2.4.1", - "babel-preset-es2015": "^6.14.0", + "@angular/compiler-cli": "4.2.4", "del": "^1.2.0", "glob": "^4.0.6", "gulp": "^3.9.0", - "gulp-babel": "^6.1.2", "gulp-filter": "^3.0.1", "gulp-jade": "^1.1.0", "gulp-rename": "^1.2.2", "gulp-strip-comments": "^2.2.1", "gulp-typescript": "^2.12.2", "gulp-watch": "~4.3.5", - "jasmine": "2.4.1", + "jasmine": "2.6.0", "jstransformer-marked": "^1.0.1", "karma": "^0.13.21", "karma-chrome-launcher": "^0.2.2", "karma-jasmine": "^0.3.6", "marked": "^0.3.5", "merge2": "1.0.2", - "parse5": "1.5.0", + "parse5": "3.0.2", "run-sequence": "~1.1.2", - "systemjs": "^0.19.9", - "systemjs-builder": "^0.15.14", + "systemjs": "0.19.41", + "systemjs-builder": "0.15.35", "through2": "~0.6.3", - "typescript": "2.0.10" + "typescript": "2.3.4" }, "dependencies": { - "@angular/common": "2.4.1", - "@angular/compiler": "2.4.1", - "@angular/core": "2.4.1", - "@angular/http": "2.4.1", - "@angular/platform-browser": "2.4.1", - "@angular/platform-browser-dynamic": "2.4.1", - "@angular/platform-server": "2.4.1", - "@angular/router": "3.4.1", - "hammerjs": "2.0.6", + "@angular/animations": "4.2.4", + "@angular/common": "4.2.4", + "@angular/compiler": "4.2.4", + "@angular/core": "4.2.4", + "@angular/http": "4.2.4", + "@angular/platform-browser": "4.2.4", + "@angular/platform-browser-dynamic": "4.2.4", + "@angular/platform-server": "4.2.4", + "@angular/router": "4.2.4", + "@types/jasmine": "^2.5.52", + "@types/node": "^8.0.1", + "@types/react-native": "^0.44.10", + "hammerjs": "2.0.8", "react-native": "0.45.1", - "reflect-metadata": "0.1.8", - "rxjs": "5.0.1", - "zone.js": "0.7.4" + "reflect-metadata": "0.1.9", + "rxjs": "5.4.1", + "zone.js": "0.8.12" } } diff --git a/sample/tsconfig.json b/sample/tsconfig.json index 5c7bb80..3596332 100644 --- a/sample/tsconfig.json +++ b/sample/tsconfig.json @@ -17,7 +17,6 @@ "index.android.ts", "index.ios.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts", - "../../../typings/globals/node/index.d.ts", "../../../fake-react-native.d.ts" ], diff --git a/src/components/ios/navigator.ts b/src/components/ios/navigator.ts index 9dd631b..3de87fd 100644 --- a/src/components/ios/navigator.ts +++ b/src/components/ios/navigator.ts @@ -13,7 +13,7 @@ import { AfterViewInit, OnDestroy, ResolvedReflectiveProvider } from "@angular/core"; import {LocationStrategy} from "@angular/common"; -import {Router, ActivatedRoute, RouterOutletMap, PRIMARY_OUTLET} from "@angular/router"; +import {Router, ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET} from "@angular/router"; import {HighLevelComponent, GENERIC_INPUTS, GENERIC_BINDINGS} from "../common/component"; import {REACT_NATIVE_WRAPPER} from "./../../renderer/renderer"; import {ReactNativeWrapper} from "../../wrapper/wrapper"; @@ -183,20 +183,18 @@ export class Navigator extends HighLevelComponent implements OnDestroy { private __wrapper: ReactNativeWrapper; constructor(private router: Router, private zone: NgZone, private locationStrategy: LocationStrategy, private elementRef: ElementRef, - private parentOutletMap: RouterOutletMap, @Inject(REACT_NATIVE_WRAPPER) wrapper: ReactNativeWrapper) { + private parentContexts: ChildrenOutletContexts, @Inject(REACT_NATIVE_WRAPPER) wrapper: ReactNativeWrapper) { super(wrapper); this.__wrapper = wrapper; - parentOutletMap.registerOutlet(PRIMARY_OUTLET, this); + parentContexts.onChildOutletCreated(PRIMARY_OUTLET, this); this.setDefaultStyle({flex: 1}); } - activate(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver, - loadedInjector: Injector, providers: ResolvedReflectiveProvider[], - outletMap: RouterOutletMap): void { + activateWith(activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver): void { if (this._stack.length == 0) { this._stack.push(activatedRoute); } else { - var navigator: Node = this.elementRef.nativeElement.children[1]; + var navigator: Node = this.elementRef.nativeElement.children[0]; setTimeout(()=> { this.__wrapper.requestNavigatorLock(navigator.nativeTag, (lockAcquired) => this._handleNavigation(lockAcquired, navigator, activatedRoute)); }, 0) @@ -205,7 +203,7 @@ export class Navigator extends HighLevelComponent implements OnDestroy { deactivate(): void {} - ngOnDestroy(): void { this.parentOutletMap.removeOutlet(PRIMARY_OUTLET); } + ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(PRIMARY_OUTLET); } _handleNavigation(lockAcquired: boolean, navigator: Node, activatedRoute: ActivatedRoute): void { this.zone.run(() => { diff --git a/src/events/hammer.ts b/src/events/hammer.ts index b22ebba..02233cb 100644 --- a/src/events/hammer.ts +++ b/src/events/hammer.ts @@ -1,9 +1,6 @@ -declare var global: any; -declare var require: any; - -if (typeof global.window === "undefined") global.window = global; -if (typeof global.document === "undefined") global.document = {createElement: () => {return {style: {}};}}; -var hammer = (typeof global.window.Hammer === "undefined") ? require('hammerjs') : global.window.Hammer; +if (typeof global['window'] === "undefined") global['window'] = global; +if (typeof global['document'] === "undefined") global['document'] = {createElement: () => {return {style: {}};}}; +var hammer = (typeof global['window'].Hammer === "undefined") ? require('hammerjs') : global['window'].Hammer; var EVENT_RECOGNIZER_MAP: {[s: string]: string } = { //doubletap @@ -155,7 +152,7 @@ var NATIVE_INPUT_MAP: {[s: string]: any } = { var NATIVE_TARGET_EVENTS = 'topTouchStart topTouchMove topTouchEnd topTouchCancel'; -function NativeInput() { +export function NativeInput() { this.evTarget = NATIVE_TARGET_EVENTS; this.targetIds = {}; diff --git a/src/renderer/bootstrap_aot.ts b/src/renderer/bootstrap_aot.ts index a603ca6..5ddbb70 100644 --- a/src/renderer/bootstrap_aot.ts +++ b/src/renderer/bootstrap_aot.ts @@ -6,26 +6,21 @@ import {ReactNativeWrapperImpl} from "./../wrapper/wrapper_impl"; //Dependencies import "reflect-metadata"; -//Zone.js, patching RN's polyfill of XMLHttpRequest is needed to make it compatible with Zone.js -var onreadystatechangeGetter = function() {return this._onreadystatechange;}; -var onreadystatechangeSetter = function(v: any) {this._onreadystatechange = v;}; -Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', { - get: onreadystatechangeGetter, - set: onreadystatechangeSetter, - configurable: true -}); +//Zone.js, working around some patches +if (!global.hasOwnProperty) { + global.hasOwnProperty = () => false; +} +var oldSend = XMLHttpRequest.prototype.send; require("zone.js/dist/zone.js"); -Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', { - get: onreadystatechangeGetter, -}); +require("zone.js/dist/long-stack-trace-zone.js") +XMLHttpRequest.prototype.send = oldSend; // Finally, define the bootstrap -import {RootRenderer, NgZone, enableProdMode, NgModuleRef, Sanitizer, ErrorHandler} from "@angular/core"; +import {RendererFactory2, NgZone, enableProdMode, NgModuleRef, Sanitizer, ErrorHandler} from "@angular/core"; import { platformBrowser } from '@angular/platform-browser'; -import {ElementSchemaRegistry} from "@angular/compiler"; +import {DomElementSchemaRegistry} from "@angular/compiler"; import { ReactNativeRootRenderer, - ReactNativeRootRenderer_, ReactNativeElementSchemaRegistry, ReactNativeSanitizer, REACT_NATIVE_WRAPPER @@ -39,19 +34,19 @@ export function bootstrapReactNativeAOT(appName:string, factory: any, customProv [ReactNativeWrapperImpl], {provide: REACT_NATIVE_WRAPPER, useExisting: ReactNativeWrapperImpl}, [ReactNativeElementSchemaRegistry], - {provide: ElementSchemaRegistry, useExisting: ReactNativeElementSchemaRegistry}, + {provide: DomElementSchemaRegistry, useExisting: ReactNativeElementSchemaRegistry}, ReactNativeSanitizer, {provide: Sanitizer, useExisting: ReactNativeSanitizer}, - {provide: ReactNativeRootRenderer, useClass: ReactNativeRootRenderer_}, - {provide: RootRenderer, useExisting: ReactNativeRootRenderer} + [ReactNativeRootRenderer], + {provide: RendererFactory2, useExisting: ReactNativeRootRenderer} ].concat(customProviders || [])). bootstrapModuleFactory(factory). then((ngModuleRef: NgModuleRef) => { var zone: NgZone = ngModuleRef.injector.get(NgZone); - var rootRenderer = ngModuleRef.injector.get(RootRenderer); - rootRenderer.zone = zone; - rootRenderer.executeCommands(); - zone.onStable.subscribe(() => { rootRenderer.executeCommands(); }); + var rendererFactory = ngModuleRef.injector.get(RendererFactory2); + rendererFactory.zone = zone; + rendererFactory.executeCommands(); + zone.onStable.subscribe(() => { rendererFactory.executeCommands(); }); ngModuleRef.injector.get(ReactNativeWrapperImpl).patchReactNativeWithZone(zone); }); }); diff --git a/src/renderer/bootstrap_jit.ts b/src/renderer/bootstrap_jit.ts index f332b5f..b5202c2 100644 --- a/src/renderer/bootstrap_jit.ts +++ b/src/renderer/bootstrap_jit.ts @@ -6,26 +6,20 @@ import {ReactNativeWrapperImpl} from "./../wrapper/wrapper_impl"; //Dependencies import "reflect-metadata"; -//Zone.js, patching RN's polyfill of XMLHttpRequest is needed to make it compatible with Zone.js -var onreadystatechangeGetter = function() {return this._onreadystatechange;}; -var onreadystatechangeSetter = function(v: any) {this._onreadystatechange = v;}; -Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', { - get: onreadystatechangeGetter, - set: onreadystatechangeSetter, - configurable: true -}); +//Zone.js, working around some patches +if (!global.hasOwnProperty) { + global.hasOwnProperty = () => false; +} +var oldSend = XMLHttpRequest.prototype.send; require("zone.js/dist/zone.js"); -Object.defineProperty(XMLHttpRequest.prototype, 'onreadystatechange', { - get: onreadystatechangeGetter, -}); +XMLHttpRequest.prototype.send = oldSend; // Finally, define the bootstrap -import {RootRenderer, NgZone, enableProdMode, NgModuleRef, Sanitizer, ErrorHandler} from "@angular/core"; +import {RendererFactory2, NgZone, enableProdMode, NgModuleRef, Sanitizer, ErrorHandler} from "@angular/core"; import {platformBrowserDynamic} from "@angular/platform-browser-dynamic"; -import {ElementSchemaRegistry} from "@angular/compiler"; +import {DomElementSchemaRegistry} from "@angular/compiler"; import { ReactNativeRootRenderer, - ReactNativeRootRenderer_, ReactNativeElementSchemaRegistry, ReactNativeSanitizer, REACT_NATIVE_WRAPPER @@ -39,19 +33,19 @@ export function bootstrapReactNativeJIT(appName:string, module: any, customProvi [ReactNativeWrapperImpl], {provide: REACT_NATIVE_WRAPPER, useExisting: ReactNativeWrapperImpl}, [ReactNativeElementSchemaRegistry], - {provide: ElementSchemaRegistry, useExisting: ReactNativeElementSchemaRegistry}, + {provide: DomElementSchemaRegistry, useExisting: ReactNativeElementSchemaRegistry}, ReactNativeSanitizer, {provide: Sanitizer, useExisting: ReactNativeSanitizer}, - {provide: ReactNativeRootRenderer, useClass: ReactNativeRootRenderer_}, - {provide: RootRenderer, useExisting: ReactNativeRootRenderer} + [ReactNativeRootRenderer], + {provide: RendererFactory2, useExisting: ReactNativeRootRenderer} ].concat(customProviders || [])). bootstrapModule(module). then((ngModuleRef: NgModuleRef) => { var zone: NgZone = ngModuleRef.injector.get(NgZone); - var rootRenderer = ngModuleRef.injector.get(RootRenderer); - rootRenderer.zone = zone; - rootRenderer.executeCommands(); - zone.onStable.subscribe(() => { rootRenderer.executeCommands(); }); + var rendererFactory = ngModuleRef.injector.get(RendererFactory2); + rendererFactory.zone = zone; + rendererFactory.executeCommands(); + zone.onStable.subscribe(() => { rendererFactory.executeCommands(); }); ngModuleRef.injector.get(ReactNativeWrapperImpl).patchReactNativeWithZone(zone); }); }); diff --git a/src/renderer/node.ts b/src/renderer/node.ts index 09b62b4..482b0dd 100644 --- a/src/renderer/node.ts +++ b/src/renderer/node.ts @@ -294,6 +294,6 @@ export class TextNode extends Node { } } -export class AnchorNode extends Node { +export class CommentNode extends Node { constructor(wrapper: ReactNativeWrapper, zoneHolder: any) { super(wrapper, zoneHolder); this.isVirtual = true;} } diff --git a/src/renderer/renderer.ts b/src/renderer/renderer.ts index 914c400..b80e4a4 100644 --- a/src/renderer/renderer.ts +++ b/src/renderer/renderer.ts @@ -1,18 +1,20 @@ import { - RootRenderer, - Renderer, - RenderComponentType, - OpaqueToken, + RendererFactory2, + Renderer2, + RendererType2, + RendererStyleFlags2, + InjectionToken, Inject, Injectable, NgZone, Sanitizer, SecurityContext, AnimationPlayer, - SchemaMetadata + SchemaMetadata, + Compiler } from "@angular/core"; import {ElementSchemaRegistry} from "@angular/compiler"; -import {Node, ElementNode, AnchorNode, TextNode, nodeMap} from "./node"; +import {Node, ElementNode, TextNode, nodeMap, CommentNode} from "./node"; import {ReactNativeWrapper} from "./../wrapper/wrapper"; import { NativeCommand, @@ -23,7 +25,7 @@ import { NativeCommandAttachAfter } from "./native_command"; -export const REACT_NATIVE_WRAPPER: OpaqueToken = new OpaqueToken("ReactNativeWrapper"); +export const REACT_NATIVE_WRAPPER: InjectionToken = new InjectionToken("ReactNativeWrapper"); export class ReactNativeElementSchemaRegistry extends ElementSchemaRegistry { getDefaultComponentElementName(): string { @@ -66,8 +68,10 @@ export class ReactNativeSanitizer implements Sanitizer { } } -export class ReactNativeRootRenderer implements RootRenderer { +@Injectable() +export class ReactNativeRootRenderer implements RendererFactory2 { public zone: NgZone; + public wrapper: ReactNativeWrapper; private _registeredComponents: Map = new Map(); @@ -77,17 +81,13 @@ export class ReactNativeRootRenderer implements RootRenderer { private _attachAfterCommands: Map = new Map(); private _detachCommands: Map = new Map(); - constructor(public wrapper: ReactNativeWrapper) { - wrapper.patchReactNativeEventEmitter(nodeMap); + constructor(@Inject(REACT_NATIVE_WRAPPER) _wrapper: ReactNativeWrapper) { + this.wrapper = _wrapper; + this.wrapper.patchReactNativeEventEmitter(nodeMap); } - renderComponent(componentType: RenderComponentType): Renderer { - var renderer = this._registeredComponents.get(componentType.id); - if (renderer == null) { - renderer = new ReactNativeRenderer(this, componentType); - this._registeredComponents.set(componentType.id, renderer); - } - return renderer; + createRenderer(hostElement: any, type: RendererType2 | any): Renderer2 { + return new ReactNativeRenderer(this); } addCreateCommand(node: Node, props: {[s: string]: any } = null) { @@ -138,190 +138,167 @@ export class ReactNativeRootRenderer implements RootRenderer { } } -@Injectable() -export class ReactNativeRootRenderer_ extends ReactNativeRootRenderer { - constructor(@Inject(REACT_NATIVE_WRAPPER) _wrapper: ReactNativeWrapper) { - super(_wrapper); - } -} - -export class ReactNativeRenderer implements Renderer { +export class ReactNativeRenderer implements Renderer2 { + data: {[key: string]: any;} = {}; - constructor(private _rootRenderer: ReactNativeRootRenderer, private componentProto: RenderComponentType) { } - - renderComponent(componentType: RenderComponentType):Renderer { - return this._rootRenderer.renderComponent(componentType); - } + constructor(private _rootRenderer: ReactNativeRootRenderer) {} selectRootElement(selector: string): Node { - var root = this.createElement(null, selector.startsWith('#root') ? 'test-cmp' : selector); + const root = this.createElement(selector.startsWith('#root') ? 'test-cmp' : selector); this._createElementCommand(root); this._rootRenderer.addAttachCommand(root, true); return root; } - createElement(parentElement: Node, name: string): Node { - var node = new ElementNode(name, this._rootRenderer.wrapper, this._rootRenderer); - node.attachTo(parentElement); - if (!node.isVirtual && node.getAncestorWithNativeCreated()) { - this._createElementCommand(node); - this._rootRenderer.addAttachCommand(node, false); - } - return node; + createElement(name: string, namespace?: string | any): Node { + //console.log('createElement:' + name); + return new ElementNode(name, this._rootRenderer.wrapper, this._rootRenderer); } - private _createElementCommand(node: Node): void { - this._rootRenderer.addCreateCommand(node); - node.isCreated = true; + createComment(value: string): any { + return new CommentNode(this._rootRenderer.wrapper, this._rootRenderer); } - createViewRoot(hostElement: Node): Node { - return hostElement; + createText(value: string): Node { + //console.log('createText:' + value); + return new TextNode(value, this._rootRenderer.wrapper, this._rootRenderer); } - createTemplateAnchor(parentElement: Node): Node { - var node = new AnchorNode(this._rootRenderer.wrapper, this._rootRenderer); - node.attachTo(parentElement); - return node; + destroyNode(node: Node): any { + node.toBeDestroyed = true; } - createText(parentElement: Node, value: string): Node { - var node = new TextNode(value, this._rootRenderer.wrapper, this._rootRenderer); - if (parentElement && parentElement.isCreated) { - this._createTextCommand(node); - this._rootRenderer.addAttachCommand(node, false); - } - node.attachTo(parentElement); - return node; + destroy(): void { + //console.log('NOT IMPLEMENTED: destroy', arguments); } - private _createTextCommand(node: TextNode): void { - this._rootRenderer.addCreateCommand(node, {text: node.properties['text']}); - var cmd = new NativeCommandCreate(node); - node.isCreated = true; - } - - projectNodes(parentElement: Node, nodes: Node[]): void { - if (parentElement) { - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - node.attachTo(parentElement); - if (node.getAncestorWithNativeCreated()) { - if (this._createNativeRecursively(node)) { - this._rootRenderer.addAttachCommand(node, false); - } - } + appendChild(parent: Node, newChild: Node): void { + //console.log('appendChild'); + newChild.attachTo(parent); + if (newChild.getAncestorWithNativeCreated()) { + if (this._createNativeRecursively(newChild)) { + this._rootRenderer.addAttachCommand(newChild, false); } } } - attachViewAfter(node: Node, viewRootNodes: Node[]): void { - if (viewRootNodes.length > 0) { - var index = node.parent.children.indexOf(node); - for (var i = 0; i < viewRootNodes.length; i++) { - var viewRootNode = viewRootNodes[i]; - viewRootNode.attachToAt(node.parent, index + i + 1); - if (viewRootNode.getAncestorWithNativeCreated()) { - if (this._createNativeRecursively(viewRootNode)) { - this._rootRenderer.addAttachAfterCommand(viewRootNode, node); - } - } + insertBefore(parent: any, newChild: any, refChild: any): void { + //console.log('insertBefore'); + const index = parent.children.indexOf(refChild); + newChild.attachToAt(parent, index); + if (newChild.getAncestorWithNativeCreated()) { + if (this._createNativeRecursively(newChild)) { + this._rootRenderer.addAttachCommand(newChild, false); } } } - private _createNativeRecursively(node: Node, isRoot: boolean = true): boolean { - var didCreate: boolean = false; - if (!node.isCreated) { - if (!node.isVirtual) { - node instanceof TextNode ? this._createTextCommand(node) : this._createElementCommand(node); - didCreate = !(node instanceof TextNode) || isRoot; - } - for (var i = 0; i < node.children.length; i++) { - var child = node.children[i]; - didCreate = this._createNativeRecursively(child, false) || didCreate; - if (!child.isVirtual && !(isRoot && node.isVirtual)) { - this._rootRenderer.addAttachCommand(child, false); - } - } - } - return didCreate; + removeChild(parent: any, oldChild: any): void { + const index = parent.children.indexOf(oldChild); + parent.children.splice(index, 1); + this._rootRenderer.addDetachCommand(oldChild); } - detachView(viewRootNodes: Node[]): void { - for (var i = 0; i < viewRootNodes.length; i++) { - var node = viewRootNodes[i]; - var parent = node.parent; - if (parent) { - var index = parent.children.indexOf(node); - parent.children.splice(index, 1); - this._rootRenderer.addDetachCommand(node); - } - } + parentNode(node: Node): Node { + return node.parent; } - destroyView(hostElement: any, viewAllNodes: Node[]): void { - for (var i = 0; i < viewAllNodes.length; i++) { - var node = viewAllNodes[i]; - node.toBeDestroyed = true; + nextSibling(node: Node): any { + let res = null; + const parent = node.parent; + if (parent) { + const index = parent.children.indexOf(node) + 1; + if (parent.children.length > index) { + res = parent.children[index]; + } } + return res; } - listen(renderElement: Node, name: string, callback: Function): Function { - renderElement.addEventListener(name, callback); - return () => {renderElement.removeEventListener(name, callback);}; + setAttribute(el: Node, name: string, value: string, namespace?: string | any): void { + var val: any = value; + if (name == "ng-version") return; + if (value == "false") val = false; + if (value == "true") val = true; + if (value == "null") val = null; + if (!isNaN(parseInt(val))) val = parseInt(val); + if (value.startsWith('#')) val = this._rootRenderer.wrapper.processColor(value); + this.setProperty(el, name, val); } - listenGlobal(target: string, name: string, callback: Function): Function { - console.error('NOT IMPLEMENTED: listenGlobal', arguments); - return () => {}; + removeAttribute(el: any, name: string, namespace?: string | any): void { } - setElementProperty(renderElement: Node, propertyName: string, propertyValue: any): void { - if (typeof propertyValue !== 'undefined') { - const cleanPropertyName = propertyName.startsWith('_on') ? propertyName.substr(1) : propertyName; - renderElement.setProperty(cleanPropertyName, propertyValue, false); - if (renderElement.isCreated) { - this._rootRenderer.addUpdateCommand(renderElement, cleanPropertyName, propertyValue); - } - } + addClass(el: any, name: string): void { + console.error('NOT IMPLEMENTED: addClass', arguments); } - setElementAttribute(renderElement: Node, attributeName: string, attributeValue: string): void { - var val: any = attributeValue; - if (attributeName == "ng-version") return; - if (attributeValue == "false") val = false; - if (attributeValue == "true") val = true; - if (attributeValue == "null") val = null; - if (!isNaN(parseInt(val))) val = parseInt(val); - if (attributeValue.startsWith('#')) val = this._rootRenderer.wrapper.processColor(attributeValue); - this.setElementProperty(renderElement, attributeName, val); + removeClass(el: any, name: string): void { + console.error('NOT IMPLEMENTED: removeClass', arguments); } - setBindingDebugInfo(renderElement: Node, propertyName: string, propertyValue: string): void { - //this.setElementProperty(renderElement, propertyName, propertyValue); + setStyle(el: any, style: string, value: any, flags?: RendererStyleFlags2): void { + console.error('NOT IMPLEMENTED: setStyle', arguments); } - setElementClass(renderElement:any, className:string, isAdd:boolean):any { - console.error('NOT IMPLEMENTED: setElementClass', arguments); + removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void { + console.error('NOT IMPLEMENTED: removeStyle', arguments); } - setElementStyle(renderElement:any, styleName:string, styleValue:string):any { - console.error('NOT IMPLEMENTED: setElementStyle', arguments); + setProperty(el: Node, name: string, value: any): void { + if (typeof value !== 'undefined') { + const cleanPropertyName = name.startsWith('_on') ? name.substr(1) : name; + el.setProperty(cleanPropertyName, value, false); + if (el.isCreated) { + this._rootRenderer.addUpdateCommand(el, cleanPropertyName, value); + } + } } - invokeElementMethod(renderElement: Node, methodName: string, args: any[]): void { - renderElement.dispatchCommand(methodName, args); + setValue(node: Node, value: string): void { + if (node instanceof TextNode) { + const trimedText = node.setText(value); + this.setProperty(node, 'text', trimedText); + } } - setText(renderNode: Node, text: string): void { - if (renderNode instanceof TextNode) { - var trimedText = renderNode.setText(text); - this.setElementProperty(renderNode, 'text', trimedText); + listen(target: 'window' | 'document' | 'body' | Node, eventName: string, callback: (event: any) => boolean | void): () => void { + if (target === 'window' || target == 'document' || target == 'body') { + console.error('NOT IMPLEMENTED: listen on ' + target, arguments); + return () => {}; + } else { + target.addEventListener(eventName, callback); + return () => {target.removeEventListener(eventName, callback);}; } + + } + + private _createElementCommand(node: Node): void { + this._rootRenderer.addCreateCommand(node); + node.isCreated = true; } - animate(element:any, startingStyles: any, keyframes: any[], duration: number, delay: number, easing: string): AnimationPlayer { - return undefined; + private _createTextCommand(node: TextNode): void { + this._rootRenderer.addCreateCommand(node, {text: node.properties['text']}); + var cmd = new NativeCommandCreate(node); + node.isCreated = true; + } + + private _createNativeRecursively(node: Node, isRoot: boolean = true): boolean { + var didCreate: boolean = false; + if (!node.isCreated) { + if (!node.isVirtual) { + node instanceof TextNode ? this._createTextCommand(node) : this._createElementCommand(node); + didCreate = !(node instanceof TextNode) || isRoot; + } + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + didCreate = this._createNativeRecursively(child, false) || didCreate; + if (!child.isVirtual && !(isRoot && node.isVirtual)) { + this._rootRenderer.addAttachCommand(child, false); + } + } + } + return didCreate; } } \ No newline at end of file diff --git a/src/router/router_outlet.ts b/src/router/router_outlet.ts index 259797b..ba81cfc 100644 --- a/src/router/router_outlet.ts +++ b/src/router/router_outlet.ts @@ -1,31 +1,72 @@ -import {Attribute, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, Output, ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef} from '@angular/core'; -import {RouterOutletMap, ActivatedRoute, PRIMARY_OUTLET} from '@angular/router'; +import {Attribute, ComponentFactoryResolver, ComponentRef, Directive, EventEmitter, Injector, OnDestroy, OnInit, Output, ViewContainerRef} from '@angular/core'; +import {ChildrenOutletContexts, ActivatedRoute, PRIMARY_OUTLET} from '@angular/router'; @Directive({selector: 'router-outlet'}) -export class RouterOutlet implements OnDestroy { - private activated: ComponentRef; - private _activatedRoute: ActivatedRoute; - public outletMap: RouterOutletMap; +export class RouterOutlet implements OnDestroy, OnInit { + private activated: ComponentRef = null; + private _activatedRoute: ActivatedRoute = null; + private name: string; @Output('activate') activateEvents = new EventEmitter(); @Output('deactivate') deactivateEvents = new EventEmitter(); constructor( - private parentOutletMap: RouterOutletMap, private location: ViewContainerRef, - private resolver: ComponentFactoryResolver, @Attribute('name') private name: string) { - parentOutletMap.registerOutlet(name ? name : PRIMARY_OUTLET, this); + private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef, + private resolver: ComponentFactoryResolver, @Attribute('name') name: string) { + this.name = name || PRIMARY_OUTLET; + parentContexts.onChildOutletCreated(this.name, this); } - ngOnDestroy(): void { this.parentOutletMap.removeOutlet(this.name ? this.name : PRIMARY_OUTLET); } + ngOnDestroy(): void { this.parentContexts.onChildOutletDestroyed(this.name); } + + ngOnInit(): void { + if (!this.activated) { + // If the outlet was not instantiated at the time the route got activated we need to populate + // the outlet when it is initialized. + const context = this.parentContexts.getContext(this.name); + if (context && context.route) { + if (context.attachRef) { + // `attachRef` is populated when there is an existing component to mount + this.attach(context.attachRef, context.route); + } else { + // otherwise the component defined in the configuration is created + this.activateWith(context.route, context.resolver || null); + } + } + } + } get isActivated(): boolean { return !!this.activated; } + get component(): Object { if (!this.activated) throw new Error('Outlet is not activated'); return this.activated.instance; } + get activatedRoute(): ActivatedRoute { if (!this.activated) throw new Error('Outlet is not activated'); - return this._activatedRoute; + return this._activatedRoute as ActivatedRoute; + } + + /** + * Called when the `RouteReuseStrategy` instructs to detach the subtree + */ + detach(): ComponentRef { + if (!this.activated) throw new Error('Outlet is not activated'); + this.location.detach(); + const cmp = this.activated; + this.activated = null; + this._activatedRoute = null; + return cmp; + } + + /** + * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree + */ + attach(ref: ComponentRef, activatedRoute: ActivatedRoute) { + this.activated = ref; + this._activatedRoute = activatedRoute; + this.location.insert(ref.hostView); } deactivate(): void { @@ -33,31 +74,41 @@ export class RouterOutlet implements OnDestroy { const c = this.component; this.activated.destroy(); this.activated = null; + this._activatedRoute = null; this.deactivateEvents.emit(c); } } - activate( - activatedRoute: ActivatedRoute, loadedResolver: ComponentFactoryResolver, - loadedInjector: Injector, providers: ResolvedReflectiveProvider[], - outletMap: RouterOutletMap): void { - this.outletMap = outletMap; + activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver) { + if (this.isActivated) { + throw new Error('Cannot activate an already activated outlet'); + } this._activatedRoute = activatedRoute; + const snapshot = activatedRoute['_futureSnapshot']; + const component = snapshot._routeConfig.component; + resolver = resolver || this.resolver; + const factory = resolver.resolveComponentFactory(component); + const childContexts = this.parentContexts.getOrCreateContext(this.name).children; + const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector); + this.activated = this.location.createComponent(factory, this.location.length, injector); + this.activateEvents.emit(this.activated.instance); + } +} - const component: any = activatedRoute.routeConfig.component; +class OutletInjector implements Injector { + constructor( + private route: ActivatedRoute, private childContexts: ChildrenOutletContexts, + private parent: Injector) {} - let factory: ComponentFactory; - if (loadedResolver) { - factory = loadedResolver.resolveComponentFactory(component); - } else { - factory = this.resolver.resolveComponentFactory(component); + get(token: any, notFoundValue?: any): any { + if (token === ActivatedRoute) { + return this.route; } - const injector = loadedInjector ? loadedInjector : this.location.parentInjector; - const inj = ReflectiveInjector.fromResolvedProviders(providers, injector); - this.activated = this.location.createComponent(factory, this.location.length, inj, []); - this.activated.changeDetectorRef.detectChanges(); + if (token === ChildrenOutletContexts) { + return this.childContexts; + } - this.activateEvents.emit(this.activated.instance); + return this.parent.get(token, notFoundValue); } } diff --git a/src/test_helpers/router_testing_module.ts b/src/test_helpers/router_testing_module.ts index 1f47f25..2cbeef0 100644 --- a/src/test_helpers/router_testing_module.ts +++ b/src/test_helpers/router_testing_module.ts @@ -1,6 +1,6 @@ import {Location, LocationStrategy} from '@angular/common'; import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader} from '@angular/core'; -import {Route, Router, RouterOutletMap, Routes, UrlSerializer, provideRoutes, __router_private__, PreloadingStrategy, NoPreloading} from '@angular/router'; +import {Route, Router, Routes, UrlSerializer, provideRoutes, PreloadingStrategy, NoPreloading, ChildrenOutletContexts, ɵflatten, ɵROUTER_PROVIDERS, ROUTES} from '@angular/router'; import {ReactNativeRouterModule} from './../router/router_module'; import {ReactNativeLocationStrategy} from './../router/location_strategy'; @@ -20,23 +20,23 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { } export function setupTestingRouter( - urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, + urlSerializer: UrlSerializer, childrenOutletContexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][]) { return new Router( - null, urlSerializer, outletMap, location, injector, loader, compiler, __router_private__.flatten(routes)); + null, urlSerializer, childrenOutletContexts, location, injector, loader, compiler, ɵflatten(routes)); } @NgModule({ exports: [ReactNativeRouterModule], providers: [ - __router_private__.ROUTER_PROVIDERS, + ɵROUTER_PROVIDERS, {provide: LocationStrategy, useClass: ReactNativeLocationStrategy }, {provide: NgModuleFactoryLoader, useClass: SpyNgModuleFactoryLoader}, { provide: Router, useFactory: setupTestingRouter, deps: [ - UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, Compiler, Injector, __router_private__.ROUTES + UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector, ROUTES ] }, {provide: PreloadingStrategy, useExisting: NoPreloading}, diff --git a/src/test_helpers/utils.ts b/src/test_helpers/utils.ts index 7cf725b..f338cc8 100644 --- a/src/test_helpers/utils.ts +++ b/src/test_helpers/utils.ts @@ -1,11 +1,10 @@ import {MockApplicationRef} from "./mock_application_ref"; -import {RootRenderer, ApplicationRef, Sanitizer, CUSTOM_ELEMENTS_SCHEMA, NgZone} from "@angular/core"; +import {RendererFactory2, ApplicationRef, Sanitizer, CUSTOM_ELEMENTS_SCHEMA, NgZone} from "@angular/core"; import {TestBed, getTestBed, ComponentFixture} from "@angular/core/testing"; import {LocationStrategy} from "@angular/common"; import {ElementSchemaRegistry} from "@angular/compiler"; import { ReactNativeRootRenderer, - ReactNativeRootRenderer_, ReactNativeElementSchemaRegistry, ReactNativeSanitizer, REACT_NATIVE_WRAPPER @@ -51,8 +50,8 @@ function getTestingProviders(mock: ReactNativeWrapper, testCpt: any): Array {provide: ElementSchemaRegistry, useExisting: ReactNativeElementSchemaRegistry}, ReactNativeSanitizer, {provide: Sanitizer, useExisting: ReactNativeSanitizer}, - {provide: ReactNativeRootRenderer, useClass: ReactNativeRootRenderer_}, - {provide: RootRenderer, useExisting: ReactNativeRootRenderer} + [ReactNativeRootRenderer], + {provide: RendererFactory2, useExisting: ReactNativeRootRenderer} ]; } diff --git a/test/components/android/drawer_layout_spec.ts b/test/components/android/drawer_layout_spec.ts index b759d2c..7775229 100644 --- a/test/components/android/drawer_layout_spec.ts +++ b/test/components/android/drawer_layout_spec.ts @@ -14,8 +14,8 @@ describe('DrawerLayout component (Android)', () => { initTest(TestComponent, ``); expect(mock.commandLogs.toString()).toEqual( 'CREATE+2+test-cmp+{},CREATE+3+native-drawerlayout+{"drawerWidth":250},CREATE+4+native-view+{"bottom":0,"collapsable":false,"left":0,"position":"absolute","right":0,"top":0},' + - 'CREATE+5+native-view+{},CREATE+6+native-view+{"bottom":0,"collapsable":false,"position":"absolute","top":0,"width":250},CREATE+7+native-view+{},' + - 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+6+1,ATTACH+4+5+0,ATTACH+6+7+0'); + 'CREATE+5+native-view+{"bottom":0,"collapsable":false,"position":"absolute","top":0,"width":250},CREATE+6+native-view+{},CREATE+7+native-view+{},' + + 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+1,ATTACH+4+6+0,ATTACH+5+7+0'); }); it('should render with properties', () => { diff --git a/test/components/ios/navigator_spec.ts b/test/components/ios/navigator_spec.ts index 0e68030..d9cae87 100644 --- a/test/components/ios/navigator_spec.ts +++ b/test/components/ios/navigator_spec.ts @@ -28,7 +28,7 @@ describe('Navigator component (iOS)', () => { expect(mock.commandLogs.toString()).toEqual( 'CREATE+2+test-cmp+{},ATTACH+1+2+0,CREATE+3+native-navigator+{"onNavigationComplete":true,"flex":1},' + 'CREATE+4+native-navitem+{"onLeftButtonPress":true,"onRightButtonPress":true,"title":"aaa","backgroundColor":"white","overflow":"hidden","position":"absolute","top":0,"left":0,"right":0,"bottom":64,"padding":37},' + - 'CREATE+5+cmp-a+{},CREATE+6+native-text+{},CREATE+7+native-rawtext+{"text":"a"},ATTACH+6+7+0,ATTACH+5+6+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+4+5+0'); + 'CREATE+5+cmp-a+{},CREATE+6+native-text+{},CREATE+7+native-rawtext+{"text":"a"},ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+6+7+0,ATTACH+5+6+0,ATTACH+4+5+0'); })); it('should navigate to another route', fakeAsync(() => { @@ -47,7 +47,7 @@ describe('Navigator component (iOS)', () => { expect(mock.commandLogs.toString()).toEqual( 'REQUEST_NAVIGATOR_LOCK+3+,UPDATE+3+native-navigator+{"requestedTopOfStack":1},' + 'CREATE+8+native-navitem+{"onLeftButtonPress":true,"onRightButtonPress":true,"title":"bbb","backgroundColor":"white","overflow":"hidden","position":"absolute","top":0,"left":0,"right":0,"bottom":64},' + - 'CREATE+9+cmp-b+{},CREATE+10+native-text+{},CREATE+11+native-rawtext+{"text":"b"},ATTACH+10+11+0,ATTACH+9+10+0,ATTACH+3+8+1,ATTACH+8+9+0'); + 'CREATE+9+cmp-b+{},CREATE+10+native-text+{},CREATE+11+native-rawtext+{"text":"b"},ATTACH+3+8+1,ATTACH+10+11+0,ATTACH+9+10+0,ATTACH+8+9+0'); }); })); @@ -66,7 +66,7 @@ describe('Navigator component (iOS)', () => { rootRenderer.executeCommands(); mock.clearLogs(); - const target = fixture.nativeElement.children[0].children[1]; + const target = fixture.nativeElement.children[0].children[0]; fireFunctionalEvent('topNavigationComplete', target, {stackLength: 1}); tick(); @@ -88,7 +88,7 @@ describe('Navigator component (iOS)', () => { 'CREATE+2+test-cmp+{},ATTACH+1+2+0,' + 'CREATE+3+native-navigator+{"onNavigationComplete":true,"flex":1},' + 'CREATE+4+native-navitem+{"onLeftButtonPress":true,"onRightButtonPress":true,"title":"aaa","backgroundColor":"white","overflow":"hidden","position":"absolute","top":0,"left":0,"right":0,"bottom":64,"margin":42,"padding":37},' + - 'CREATE+5+cmp-a+{},CREATE+6+native-text+{},CREATE+7+native-rawtext+{"text":"a"},ATTACH+6+7+0,ATTACH+5+6+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+4+5+0'); + 'CREATE+5+cmp-a+{},CREATE+6+native-text+{},CREATE+7+native-rawtext+{"text":"a"},ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+6+7+0,ATTACH+5+6+0,ATTACH+4+5+0'); })); it('should fire button press events', fakeAsync(() => { @@ -99,7 +99,7 @@ describe('Navigator component (iOS)', () => { fixture.detectChanges(); rootRenderer.executeCommands(); - const target = fixture.elementRef.nativeElement.children[0].children[1].children[2].children[0]; + const target = fixture.elementRef.nativeElement.children[0].children[0].children[2].children[0]; fireFunctionalEvent('topLeftButtonPress', target, {}); tick(); diff --git a/test/renderer/component_without_host_spec.ts b/test/renderer/component_without_host_spec.ts index ab2c60a..b92b228 100644 --- a/test/renderer/component_without_host_spec.ts +++ b/test/renderer/component_without_host_spec.ts @@ -29,14 +29,14 @@ describe('Component without host', () => { expect(mock.commandLogs.toString()).toEqual( 'CREATE+2+test-cmp+{},CREATE+3+native-view+{},CREATE+4+native-view+{},' + 'CREATE+5+native-view+{},CREATE+6+native-view+{},CREATE+7+native-view+{},' + - 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+6+7+0,ATTACH+5+6+0,ATTACH+4+5+0,ATTACH+3+4+0'); + 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+4+5+0,ATTACH+5+6+0,ATTACH+6+7+0'); }); it('should support sub-components', () => { const {fixture, rootRenderer} = initTest(TestComponent, ``); rootRenderer.executeCommands(); expect(mock.commandLogs.toString()).toEqual( - 'CREATE+2+test-cmp+{},CREATE+3+native-view+{},CREATE+4+sub+{},CREATE+5+native-view+{},ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+4+5+0,ATTACH+3+4+0'); + 'CREATE+2+test-cmp+{},CREATE+3+native-view+{},CREATE+4+sub+{},CREATE+5+native-view+{},ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+4+5+0'); }); it('should support ngIf', () => { @@ -107,7 +107,7 @@ describe('Component without host', () => { const {fixture, rootRenderer} = initTest(TestComponent, ``); rootRenderer.executeCommands(); expect(mock.commandLogs.toString()).toEqual( - 'CREATE+2+test-cmp+{},CREATE+3+proj+{},CREATE+4+native-text+{},CREATE+5+sub+{},CREATE+6+native-view+{},ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+1,ATTACH+5+6+0'); + 'CREATE+2+test-cmp+{},CREATE+3+proj+{},CREATE+4+sub+{},CREATE+5+native-view+{},CREATE+6+native-text+{},ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+6+1,ATTACH+4+5+0'); }); it('should support components with ngIf in their templates', () => { @@ -122,7 +122,7 @@ describe('Component without host', () => { rootRenderer.executeCommands(); expect(mock.commandLogs.toString()).toEqual( 'CREATE+2+test-cmp+{},CREATE+3+sub-with-if+{},CREATE+4+native-view+{},CREATE+5+native-view+{},CREATE+6+native-text+{},' + - 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+2,ATTACH+3+6+1'); + 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+1,ATTACH+3+6+2'); }); it('should not attach twice components with ngIf in their templates', () => { @@ -130,7 +130,7 @@ describe('Component without host', () => { rootRenderer.executeCommands(); expect(mock.commandLogs.toString()).toEqual( 'CREATE+2+test-cmp+{},CREATE+3+native-view+{},CREATE+4+native-view+{},CREATE+5+native-view+{},CREATE+6+native-dialogpicker+{"items":[],"mode":"dialog","height":50},' + - 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+2,ATTACH+3+6+1'); + 'ATTACH+1+2+0,ATTACH+2+3+0,ATTACH+3+4+0,ATTACH+3+5+1,ATTACH+3+6+2'); }); }); diff --git a/test/renderer/element_spec.ts b/test/renderer/element_spec.ts index 4615956..471a66b 100644 --- a/test/renderer/element_spec.ts +++ b/test/renderer/element_spec.ts @@ -129,19 +129,19 @@ describe('Element', () => { expect(mock.nativeElementMap.get(1).children[0].children.map((a: NativeElement) => a.children[0].properties['text']).join(',')).toEqual('8'); }); - it('should support NgFor in a