diff --git a/.gitignore b/.gitignore index 9f5e8a18..d8c2af69 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,11 @@ typings src/ngFactory *.metadata.json -dist \ No newline at end of file + +$ cat .gitignore +/node_modules +/dist +/documentation + +*.log +*.tgz \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ac1c2de6..0430783b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,23 @@ +sudo: required +dist: trusty +addons: + apt: + sources: + - google-chrome + packages: + - google-chrome-stable language: node_js -sudo: false node_js: -- '5.9.1' - + - stable +before_install: + - npm i npm@^4 -g install: -- npm install - -after_script: -- process.exit() - -cache: - directories: - - $HOME/.nvm \ No newline at end of file + - npm install +script: + - npm test +before_script: + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start + - sleep 3 +notifications: + email: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 49e8a5b2..ca58b9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,13 @@ -## v 0.3.3 (2017/03/01) +## Angular library starter Changelog -### Updates -* reverted the fix of #126 + +### Mar 25, 2017 +* Upgrade to Angular 4 configuration -## v 0.3.2 (2017/03/01) - -### Updates -* fixes [#126](https://github.com/orizens/angular2-infinite-scroll/issues/126) - -## v 0.3.1 (2017/02/15) - -### Updates -* added custom scrollable container from [#108](https://github.com/orizens/angular2-infinite-scroll/pull/108/files) - -## v 0.3.0 (2017/01/31) - -### Updates -* refactored infinite scroller to smaller modules with composition -* added "models" - includes interfaces for development - -## v 0.2.9 (2017/01/13) - -### Updates -* added changelog.md -* ([refactor(scroll): replaces throttle with debounce](https://github.com/orizens/angular2-infinite-scroll/pull/82)) + +### Mar 6, 2017 +* Add _compodoc_ for generating documentation + +### Feb 5, 2017 +* Create library \ No newline at end of file diff --git a/Development Instructions.md b/Development Instructions.md new file mode 100644 index 00000000..ad076f40 --- /dev/null +++ b/Development Instructions.md @@ -0,0 +1,161 @@ +# angular-library-starter +[![Build Status](https://travis-ci.org/robisim74/angular-library-starter.svg?branch=master)](https://travis-ci.org/robisim74/angular-library-starter) +>Build an Angular library compatible with AoT compilation & Tree shaking. + +This starter allows you to create a library for **Angular 2+** apps written in _TypeScript_, _ES6_ or _ES5_. +The project is based on the official _Angular_ packages. + +Get the [Changelog](https://github.com/robisim74/angular-library-starter/blob/master/CHANGELOG.md). + +## Contents +* [1 Project structure](#1) +* [2 Customizing](#2) +* [3 Unit testing](#3) +* [4 Building](#4) +* [5 Publishing](#5) +* [6 Documentation](#6) +* [7 Using the library](#7) +* [8 What it is important to know](#8) + +## 1 Project structure +- Library: + - **src** folder for the classes + - **public_api.ts** entry point for all public APIs of the package + - **package.json** _npm_ options + - **rollup.config.js** _Rollup_ configuration for building the bundles + - **tsconfig-build.json** _ngc_ compiler options for _AoT compilation_ + - **build.js** building process using _ShellJS_ +- Unit testing: + - **tests** folder for unit tests + - **karma.conf.js** _Karma_ configuration that uses _webpack_ to build the tests + - **spec.bundle.js** defines the files used by _webpack_ + - **tsconfig.json** _TypeScript_ compiler options +- Extra: + - **tslint.json** _TypeScript_ linter rules with _Codelyzer_ + - **travis.yml** _Travis CI_ configuration + +## 2 Customizing +1. Update [Node & npm](https://docs.npmjs.com/getting-started/installing-node). + +2. Rename `angular-library-starter` and `angularLibraryStarter` everywhere to `my-library` and `myLibrary`. + +3. Update in `package.json` file: + - version: [Semantic Versioning](http://semver.org/) + - description + - urls + - packages + + and run `npm install`. + +4. Create your classes in `src` folder, and export public classes in `my-library.ts`. + +5. You can create only one _module_ for the whole library: +I suggest you create different _modules_ for different functions, +so that the user can import only those he needs and optimize _Tree shaking_ of his app. + +6. Update in `rollup.config.js` file `external` & `globals` libraries with those that actually you use. + +7. Create unit tests in `tests` folder. +_Karma_ is configured to use _webpack_ only for `*.ts` files: if you need to test different formats, you have to update it. + +## 3 Unit testing +```Shell +npm test +``` + +## 4 Building +The following command: +```Shell +npm run build +``` +- starts _TSLint_ with _Codelyzer_ +- starts _AoT compilation_ using _ngc_ compiler +- creates `dist` folder with all the files of distribution + +To test locally the npm package: +```Shell +npm run pack-lib +``` +Then you can install it in an app to test it: +```Shell +npm install [path]my-library-[version].tgz +``` + +## 5 Publishing +Before publishing the first time: +- you can register your library on [Travis CI](https://travis-ci.org/): you have already configured `.travis.yml` file +- you must have a user on the _npm_ registry: [Publishing npm packages](https://docs.npmjs.com/getting-started/publishing-npm-packages) + +```Shell +npm run publish-lib +``` + +## 6 Documentation +To generate the documentation, this starter uses [compodoc](https://github.com/compodoc/compodoc): +```Shell +npm run compodoc +npm run compodoc-serve +``` + +## 7 Using the library +### Installing +```Shell +npm install my-library --save +``` +### Loading +#### Using SystemJS configuration +```JavaScript +System.config({ + map: { + 'my-library': 'node_modules/my-library/bundles/my-library.umd.js' + } +}); +``` +#### Angular-CLI +No need to set up anything, just import it in your code. +#### Rollup or webpack +No need to set up anything, just import it in your code. +#### Plain JavaScript +Include the `umd` bundle in your `index.html`: +```Html + +``` +and use global `ng.myLibrary` namespace. + +### AoT compilation +The library is compatible with _AoT compilation_. + +## 8 What it is important to know +1. `package.json` + + * `"main": "./bundles/angular-library-starter.umd.js"` legacy module format + * `"module": "./bundles/angular-library-starter.es5.js"` flat _ES_ module, for using module bundlers such as _Rollup_ or _webpack_: + [package module](https://github.com/rollup/rollup/wiki/pkg.module) + * `"es2015": "./bundles/angular-library-starter.js"` _ES2015_ flat _ESM_ format, experimental _ES2015_ build + * `"peerDependencies"` the packages and their versions required by the library when it will be installed + +2. `tsconfig-build.json` file used by _ngc_ compiler + + * Compiler options: + * `"declaration": true` to emit _TypeScript_ declaration files + * `"module": "es2015"` & `"target": "es2015"` are used by _Rollup_ to create the _ES2015_ bundle + + * Angular Compiler Options: + * `"skipTemplateCodegen": true,` skips generating _AoT_ files + * `"annotateForClosureCompiler": true` for compatibility with _Google Closure compiler_ + * `"strictMetadataEmit": true` without emitting metadata files, the library will not compatible with _AoT compilation_ + +3. `rollup.config.js` file used by _Rollup_ + + * `format: 'umd'` the _Universal Module Definition_ pattern is used by _Angular_ for its bundles + * `moduleName: 'ng.angularLibraryStarter'` defines the global namespace used by _JavaScript_ apps + * `external` & `globals` declare the external packages + +4. Server-side prerendering + + If you want the library will be compatible with server-side prerendering: + * `window`, `document`, `navigator` and other browser types do not exist on the server + * don't manipulate the _nativeElement_ directly + +## License +MIT diff --git a/LICENSE b/LICENSE index 5ef2594b..091d4ab6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Oren Farhi +Copyright (c) 2017 Roberto Simonetti Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 10423710..adcfea9d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular (> 2). ## Angular Support -Supports in Angular - **Final - 2.x.x** +Supports in Angular **> +2, 4** ## Angular Consulting Services I'm a Senior Javascript Engineer & A Front End Consultant at [Orizens](http://orizens.com). @@ -144,15 +144,5 @@ export class AppComponent { } ``` -## Testing -To start developing tdd/bdd style: ```npm run dev``` -This will: compile ts files, watch for changes and start the test task. Whenever a ts file is changed, it will rerun the tests. - -Travis-ci is integrated - -### Credits For Tests Setup -[ng2-test-seed](https://github.com/juliemr/ng2-test-seed) has been a huge help and source of inspiration. At first, copy & paste, then, customisation to adapt to this code repository. -Thanks [@juliemr](https://github.com/juliemr)! - # Showcase Examples * [Echoes Player Ng2 Version](http://orizens.github.io/echoes-ng2) ([github repo for echoes player](http://github.com/orizens/echoes-ng2)) diff --git a/build.js b/build.js new file mode 100644 index 00000000..b9f5c276 --- /dev/null +++ b/build.js @@ -0,0 +1,61 @@ +"use strict"; + +require('shelljs/global'); +const chalk = require('chalk'); + +const PACKAGE = `ngx-infinite-scroll`; +const NPM_DIR = `dist`; +const MODULES_DIR = `${NPM_DIR}/modules`; +const BUNDLES_DIR = `${NPM_DIR}/bundles`; + +echo('Start building...'); + +rm(`-Rf`, `${NPM_DIR}/*`); +mkdir(`-p`, `./${MODULES_DIR}`); +mkdir(`-p`, `./${BUNDLES_DIR}`); + +/* TSLint with Codelyzer */ +// https://github.com/palantir/tslint/blob/master/src/configs/recommended.ts +// https://github.com/mgechev/codelyzer +echo(`Start TSLint`); +exec(`tslint --project ./tsconfig.json --type-check ./src/**/*.ts`); +echo(chalk.green(`TSLint completed`)); + +/* Aot compilation: ES2015 sources */ +echo(`Start AoT compilation`); +exec(`ngc -p tsconfig-build.json`); +echo(chalk.green(`AoT compilation completed`)); + +/* Creates bundles: ESM/ES5 and UMD bundles */ +echo(`Start bundling`); +echo(`Rollup package`); +exec(`rollup -i ${NPM_DIR}/index.js -o ${MODULES_DIR}/${PACKAGE}.js --sourcemap`, {silent: true}); +exec(`node scripts/map-sources -f ${MODULES_DIR}/${PACKAGE}.js`); + +echo(`Downleveling ES2015 to ESM/ES5`); +cp(`${MODULES_DIR}/${PACKAGE}.js`, `${MODULES_DIR}/${PACKAGE}.es5.ts`); +exec(`tsc ${MODULES_DIR}/${PACKAGE}.es5.ts --target es5 --module es2015 --noLib --sourceMap`, {silent: true}); +exec(`node scripts/map-sources -f ${MODULES_DIR}/${PACKAGE}.es5.js`); +rm(`-f`, `${MODULES_DIR}/${PACKAGE}.es5.ts`); + +echo(`Run Rollup conversion on package`); +exec(`rollup -c rollup.config.js --sourcemap`, {silent: true}); +exec(`node scripts/map-sources -f ${BUNDLES_DIR}/${PACKAGE}.umd.js`); + +echo(`Minifying`); +cd(`${BUNDLES_DIR}`); +exec(`uglifyjs -c --screw-ie8 --comments -o ${PACKAGE}.umd.min.js --source-map ${PACKAGE}.umd.min.js.map --source-map-include-sources ${PACKAGE}.umd.js`, {silent: true}); +exec(`node ../../scripts/map-sources -f ${PACKAGE}.umd.min.js`); +cd(`..`); +cd(`..`); + +echo(chalk.green(`Bundling completed`)); + +rm(`-Rf`, `${NPM_DIR}/*.js`); +rm(`-Rf`, `${NPM_DIR}/*.js.map`); +rm(`-Rf`, `${NPM_DIR}/src/**/*.js`); +rm(`-Rf`, `${NPM_DIR}/src/**/*.js.map`); + +cp(`-Rf`, [`package.json`, `LICENSE`, `README.md`], `${NPM_DIR}`); + +echo(chalk.green(`End building`)); diff --git a/bundles/angular2-infinite-scroll.js b/bundles/angular2-infinite-scroll.js deleted file mode 100644 index f9729f7d..00000000 --- a/bundles/angular2-infinite-scroll.js +++ /dev/null @@ -1,428 +0,0 @@ -System.registerDynamic('src/infinite-scroll', ['@angular/core', './position-resolver', './scroll-register', './scroll-resolver'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var position_resolver_1 = $__require('./position-resolver'); - var scroll_register_1 = $__require('./scroll-register'); - var scroll_resolver_1 = $__require('./scroll-resolver'); - var InfiniteScroll = function () { - function InfiniteScroll(element, zone, positionResolverFactory, scrollRegister, scrollerResolver) { - this.element = element; - this.zone = zone; - this.positionResolverFactory = positionResolverFactory; - this.scrollRegister = scrollRegister; - this.scrollerResolver = scrollerResolver; - this.scrolled = new core_1.EventEmitter(); - this.scrolledUp = new core_1.EventEmitter(); - this._distanceDown = 2; - this._distanceUp = 1.5; - this._throttle = 300; - this._disabled = false; - this._container = null; - this.scrollWindow = true; - this._immediate = false; - this._horizontal = false; - this._alwaysCallback = false; - this.throttleType = 'throttle'; - } - Object.defineProperty(InfiniteScroll.prototype, "debounce", { - set: function (value) { - this.throttleType = value === '' || !!value ? 'debounce' : 'throttle'; - }, - enumerable: true, - configurable: true - }); - InfiniteScroll.prototype.ngOnInit = function () { - var _this = this; - if (typeof window !== 'undefined') { - var containerElement = this.resolveContainerElement(); - var positionResolver_1 = this.positionResolverFactory.create({ - windowElement: containerElement, - horizontal: this._horizontal - }); - var options = { - container: positionResolver_1.container, - throttleType: this.throttleType, - throttleDuration: this._throttle, - filterBefore: function () { - return !_this._disabled; - }, - mergeMap: function () { - return positionResolver_1.calculatePoints(_this.element); - }, - scrollHandler: function (container) { - return _this.handleOnScroll(container); - } - }; - this.disposeScroller = this.scrollRegister.attachEvent(options); - } - }; - InfiniteScroll.prototype.handleOnScroll = function (container) { - var scrollResolverConfig = { - distance: { - down: this._distanceDown, - up: this._distanceUp - } - }; - var scrollStats = this.scrollerResolver.getScrollStats(container, scrollResolverConfig); - if (this.shouldTriggerEvents(scrollStats.shouldScroll)) { - var infiniteScrollEvent = { - currentScrollPosition: container.scrolledUntilNow - }; - if (scrollStats.isScrollingDown) { - this.onScrollDown(infiniteScrollEvent); - } else { - this.onScrollUp(infiniteScrollEvent); - } - } - }; - InfiniteScroll.prototype.shouldTriggerEvents = function (shouldScroll) { - return (this._alwaysCallback || shouldScroll) && !this._disabled; - }; - InfiniteScroll.prototype.ngOnDestroy = function () { - if (this.disposeScroller) { - this.disposeScroller.unsubscribe(); - } - }; - InfiniteScroll.prototype.onScrollDown = function (data) { - var _this = this; - if (data === void 0) { - data = { currentScrollPosition: 0 }; - } - this.zone.run(function () { - return _this.scrolled.emit(data); - }); - }; - InfiniteScroll.prototype.onScrollUp = function (data) { - var _this = this; - if (data === void 0) { - data = { currentScrollPosition: 0 }; - } - this.zone.run(function () { - return _this.scrolledUp.emit(data); - }); - }; - InfiniteScroll.prototype.resolveContainerElement = function () { - if (this._container) { - return typeof this._container === 'string' ? window.document.querySelector(this._container) : this._container; - } else { - return this.scrollWindow ? window : this.element; - } - }; - InfiniteScroll.decorators = [{ type: core_1.Directive, args: [{ - selector: '[infinite-scroll]' - }] }]; - /** @nocollapse */ - InfiniteScroll.ctorParameters = function () { - return [{ type: core_1.ElementRef }, { type: core_1.NgZone }, { type: position_resolver_1.PositionResolverFactory }, { type: scroll_register_1.ScrollRegister }, { type: scroll_resolver_1.ScrollResolver }]; - }; - InfiniteScroll.propDecorators = { - 'scrolled': [{ type: core_1.Output }], - 'scrolledUp': [{ type: core_1.Output }], - '_distanceDown': [{ type: core_1.Input, args: ['infiniteScrollDistance'] }], - '_distanceUp': [{ type: core_1.Input, args: ['infiniteScrollUpDistance'] }], - '_throttle': [{ type: core_1.Input, args: ['infiniteScrollThrottle'] }], - '_disabled': [{ type: core_1.Input, args: ['infiniteScrollDisabled'] }], - '_container': [{ type: core_1.Input, args: ['infiniteScrollContainer'] }], - 'scrollWindow': [{ type: core_1.Input, args: ['scrollWindow'] }], - '_immediate': [{ type: core_1.Input, args: ['immediateCheck'] }], - '_horizontal': [{ type: core_1.Input, args: ['horizontal'] }], - '_alwaysCallback': [{ type: core_1.Input, args: ['alwaysCallback'] }], - 'debounce': [{ type: core_1.Input }] - }; - return InfiniteScroll; - }(); - exports.InfiniteScroll = InfiniteScroll; -}); -System.registerDynamic('src/axis-resolver', ['@angular/core'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var AxisResolverFactory = function () { - function AxisResolverFactory() {} - AxisResolverFactory.prototype.create = function (vertical) { - if (vertical === void 0) { - vertical = true; - } - return new AxisResolver(vertical); - }; - AxisResolverFactory.decorators = [{ type: core_1.Injectable }]; - /** @nocollapse */ - AxisResolverFactory.ctorParameters = function () { - return []; - }; - return AxisResolverFactory; - }(); - exports.AxisResolverFactory = AxisResolverFactory; - var AxisResolver = function () { - function AxisResolver(vertical) { - if (vertical === void 0) { - vertical = true; - } - this.vertical = vertical; - } - AxisResolver.prototype.clientHeightKey = function () { - return this.vertical ? 'clientHeight' : 'clientWidth'; - }; - AxisResolver.prototype.offsetHeightKey = function () { - return this.vertical ? 'offsetHeight' : 'offsetWidth'; - }; - AxisResolver.prototype.scrollHeightKey = function () { - return this.vertical ? 'scrollHeight' : 'scrollWidth'; - }; - AxisResolver.prototype.pageYOffsetKey = function () { - return this.vertical ? 'pageYOffset' : 'pageXOffset'; - }; - AxisResolver.prototype.offsetTopKey = function () { - return this.vertical ? 'offsetTop' : 'offsetLeft'; - }; - AxisResolver.prototype.scrollTopKey = function () { - return this.vertical ? 'scrollTop' : 'scrollLeft'; - }; - AxisResolver.prototype.topKey = function () { - return this.vertical ? 'top' : 'left'; - }; - return AxisResolver; - }(); - exports.AxisResolver = AxisResolver; -}); -System.registerDynamic('src/position-resolver', ['@angular/core', './axis-resolver'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var axis_resolver_1 = $__require('./axis-resolver'); - var PositionResolverFactory = function () { - function PositionResolverFactory(axisResolver) { - this.axisResolver = axisResolver; - } - PositionResolverFactory.prototype.create = function (options) { - return new PositionResolver(this.axisResolver.create(!options.horizontal), options); - }; - PositionResolverFactory.decorators = [{ type: core_1.Injectable }]; - /** @nocollapse */ - PositionResolverFactory.ctorParameters = function () { - return [{ type: axis_resolver_1.AxisResolverFactory }]; - }; - return PositionResolverFactory; - }(); - exports.PositionResolverFactory = PositionResolverFactory; - var PositionResolver = function () { - function PositionResolver(axis, options) { - this.axis = axis; - this.options = options; - this.resolveContainer(this.options.windowElement); - this.defineContainer(this.options.windowElement); - } - PositionResolver.prototype.defineContainer = function (windowElement) { - if (this.resolveContainer(windowElement) || !windowElement.nativeElement) { - this.container = windowElement; - } else { - this.container = windowElement.nativeElement; - } - return this.container; - }; - PositionResolver.prototype.resolveContainer = function (windowElement) { - var isContainerWindow = Object.prototype.toString.call(windowElement).includes('Window'); - this.isContainerWindow = isContainerWindow; - return isContainerWindow; - }; - PositionResolver.prototype.getDocumentElement = function () { - return this.isContainerWindow ? this.options.windowElement.document.documentElement : null; - }; - PositionResolver.prototype.calculatePoints = function (element) { - return this.isContainerWindow ? this.calculatePointsForWindow(element) : this.calculatePointsForElement(element); - }; - PositionResolver.prototype.calculatePointsForWindow = function (element) { - // container's height - var height = this.height(this.container); - // scrolled until now / current y point - var scrolledUntilNow = height + this.pageYOffset(this.getDocumentElement()); - // total height / most bottom y point - var totalToScroll = this.offsetTop(element.nativeElement) + this.height(element.nativeElement); - return { height: height, scrolledUntilNow: scrolledUntilNow, totalToScroll: totalToScroll }; - }; - PositionResolver.prototype.calculatePointsForElement = function (element) { - var scrollTop = this.axis.scrollTopKey(); - var scrollHeight = this.axis.scrollHeightKey(); - var container = this.container; - var height = this.height(container); - // perhaps use this.container.offsetTop instead of 'scrollTop' - var scrolledUntilNow = container[scrollTop]; - var containerTopOffset = 0; - var offsetTop = this.offsetTop(container); - if (offsetTop !== void 0) { - containerTopOffset = offsetTop; - } - var totalToScroll = container[scrollHeight]; - return { height: height, scrolledUntilNow: scrolledUntilNow, totalToScroll: totalToScroll }; - }; - PositionResolver.prototype.height = function (elem) { - var offsetHeight = this.axis.offsetHeightKey(); - var clientHeight = this.axis.clientHeightKey(); - // elem = elem.nativeElement; - if (isNaN(elem[offsetHeight])) { - return this.getDocumentElement()[clientHeight]; - } else { - return elem[offsetHeight]; - } - }; - PositionResolver.prototype.offsetTop = function (elem) { - var top = this.axis.topKey(); - // elem = elem.nativeElement; - if (!elem.getBoundingClientRect) { - return; - } - return elem.getBoundingClientRect()[top] + this.pageYOffset(elem); - }; - PositionResolver.prototype.pageYOffset = function (elem) { - var pageYOffset = this.axis.pageYOffsetKey(); - var scrollTop = this.axis.scrollTopKey(); - var offsetTop = this.axis.offsetTopKey(); - // elem = elem.nativeElement; - if (isNaN(window[pageYOffset])) { - return this.getDocumentElement()[scrollTop]; - } else if (elem.ownerDocument) { - return elem.ownerDocument.defaultView[pageYOffset]; - } else { - return elem[offsetTop]; - } - }; - return PositionResolver; - }(); - exports.PositionResolver = PositionResolver; -}); -System.registerDynamic('src/index', ['@angular/core', './infinite-scroll', './axis-resolver', './position-resolver', './scroll-register', './scroll-resolver'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var infinite_scroll_1 = $__require('./infinite-scroll'); - var axis_resolver_1 = $__require('./axis-resolver'); - var position_resolver_1 = $__require('./position-resolver'); - var scroll_register_1 = $__require('./scroll-register'); - var scroll_resolver_1 = $__require('./scroll-resolver'); - var InfiniteScrollModule = function () { - function InfiniteScrollModule() {} - InfiniteScrollModule.decorators = [{ type: core_1.NgModule, args: [{ - imports: [], - declarations: [infinite_scroll_1.InfiniteScroll], - exports: [infinite_scroll_1.InfiniteScroll], - providers: [axis_resolver_1.AxisResolverFactory, position_resolver_1.PositionResolverFactory, scroll_register_1.ScrollRegister, scroll_resolver_1.ScrollResolver] - }] }]; - /** @nocollapse */ - InfiniteScrollModule.ctorParameters = function () { - return []; - }; - return InfiniteScrollModule; - }(); - exports.InfiniteScrollModule = InfiniteScrollModule; -}); -System.registerDynamic('src/scroll-register', ['@angular/core', 'rxjs/Observable', 'rxjs/add/observable/fromEvent', 'rxjs/add/observable/timer', 'rxjs/add/observable/of', 'rxjs/add/operator/debounce', 'rxjs/add/operator/throttle', 'rxjs/add/operator/filter', 'rxjs/add/operator/mergeMap'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var Observable_1 = $__require('rxjs/Observable'); - $__require('rxjs/add/observable/fromEvent'); - $__require('rxjs/add/observable/timer'); - $__require('rxjs/add/observable/of'); - $__require('rxjs/add/operator/debounce'); - $__require('rxjs/add/operator/throttle'); - $__require('rxjs/add/operator/filter'); - $__require('rxjs/add/operator/mergeMap'); - var ScrollRegister = function () { - function ScrollRegister() {} - ScrollRegister.prototype.attachEvent = function (options) { - var scroller$ = Observable_1.Observable.fromEvent(options.container, 'scroll')[options.throttleType](function () { - return Observable_1.Observable.timer(options.throttleDuration); - }).filter(options.filterBefore).mergeMap(function (ev) { - return Observable_1.Observable.of(options.mergeMap(ev)); - }).subscribe(options.scrollHandler); - return scroller$; - }; - ScrollRegister.decorators = [{ type: core_1.Injectable }]; - /** @nocollapse */ - ScrollRegister.ctorParameters = function () { - return []; - }; - return ScrollRegister; - }(); - exports.ScrollRegister = ScrollRegister; -}); -System.registerDynamic("src/scroll-resolver", ["@angular/core"], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var core_1 = $__require('@angular/core'); - var ScrollResolver = function () { - function ScrollResolver() { - this.lastScrollPosition = 0; - } - ScrollResolver.prototype.shouldScroll = function (container, config, scrollingDown) { - var distance = config.distance; - var remaining; - var containerBreakpoint; - if (scrollingDown) { - remaining = container.totalToScroll - container.scrolledUntilNow; - containerBreakpoint = container.height * distance.down + 1; - } else { - remaining = container.scrolledUntilNow; - containerBreakpoint = container.height * distance.up + 1; - } - var shouldScroll = remaining <= containerBreakpoint; - this.lastScrollPosition = container.scrolledUntilNow; - return shouldScroll; - }; - ScrollResolver.prototype.isScrollingDown = function (container) { - return this.lastScrollPosition < container.scrolledUntilNow; - }; - ScrollResolver.prototype.getScrollStats = function (container, config) { - var isScrollingDown = this.isScrollingDown(container); - var shouldScroll = this.shouldScroll(container, config, isScrollingDown); - return { isScrollingDown: isScrollingDown, shouldScroll: shouldScroll }; - }; - ScrollResolver.decorators = [{ type: core_1.Injectable }]; - /** @nocollapse */ - ScrollResolver.ctorParameters = function () { - return []; - }; - return ScrollResolver; - }(); - exports.ScrollResolver = ScrollResolver; -}); -System.registerDynamic('angular2-infinite-scroll', ['./src/infinite-scroll', './src/position-resolver', './src/axis-resolver', './src/index', './src/scroll-register', './src/scroll-resolver'], true, function ($__require, exports, module) { - "use strict"; - - var global = this || self, - GLOBAL = global; - var infinite_scroll_1 = $__require('./src/infinite-scroll'); - var position_resolver_1 = $__require('./src/position-resolver'); - var axis_resolver_1 = $__require('./src/axis-resolver'); - var infinite_scroll_2 = $__require('./src/infinite-scroll'); - exports.InfiniteScroll = infinite_scroll_2.InfiniteScroll; - var position_resolver_2 = $__require('./src/position-resolver'); - exports.PositionResolver = position_resolver_2.PositionResolver; - exports.PositionResolverFactory = position_resolver_2.PositionResolverFactory; - var axis_resolver_2 = $__require('./src/axis-resolver'); - exports.AxisResolver = axis_resolver_2.AxisResolver; - exports.AxisResolverFactory = axis_resolver_2.AxisResolverFactory; - var index_1 = $__require('./src/index'); - exports.InfiniteScrollModule = index_1.InfiniteScrollModule; - var scroll_register_1 = $__require('./src/scroll-register'); - exports.ScrollRegister = scroll_register_1.ScrollRegister; - var scroll_resolver_1 = $__require('./src/scroll-resolver'); - exports.ScrollResolver = scroll_resolver_1.ScrollResolver; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.default = { - directives: [infinite_scroll_1.InfiniteScroll, axis_resolver_1.AxisResolver, position_resolver_1.PositionResolver] - }; -}); \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 00000000..4aaf8f92 --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/karma.conf.js b/karma.conf.js index 75eda637..42dec8fc 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,2 +1,96 @@ -// Look in ./config for karma.conf.js -module.exports = require('./config/karma.conf.js'); +// Karma configuration for Unit testing + +module.exports = function (config) { + + var configuration = { + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-webpack'), + require('karma-sourcemap-loader'), + require('karma-spec-reporter') + ], + + // list of files / patterns to load in the browser + files: [ + { pattern: 'spec.bundle.js', watched: false } + ], + + // list of files to exclude + exclude: [ + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'spec.bundle.js': ['webpack'] + }, + + // webpack + webpack: { + resolve: { + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts/, + loaders: ['ts-loader'], + exclude: /node_modules/ + } + ], + exprContextCritical: false + }, + performance: { hints: false } + }, + + webpackServer: { + noInfo: true + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['spec'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true + + }; + + config.set(configuration); + +} diff --git a/.eslintrc.json b/old/.eslintrc.json similarity index 100% rename from .eslintrc.json rename to old/.eslintrc.json diff --git a/old/.travis.yml b/old/.travis.yml new file mode 100644 index 00000000..ac1c2de6 --- /dev/null +++ b/old/.travis.yml @@ -0,0 +1,14 @@ +language: node_js +sudo: false +node_js: +- '5.9.1' + +install: +- npm install + +after_script: +- process.exit() + +cache: + directories: + - $HOME/.nvm \ No newline at end of file diff --git a/old/CHANGELOG.md b/old/CHANGELOG.md new file mode 100644 index 00000000..49e8a5b2 --- /dev/null +++ b/old/CHANGELOG.md @@ -0,0 +1,27 @@ +## v 0.3.3 (2017/03/01) + +### Updates +* reverted the fix of #126 + +## v 0.3.2 (2017/03/01) + +### Updates +* fixes [#126](https://github.com/orizens/angular2-infinite-scroll/issues/126) + +## v 0.3.1 (2017/02/15) + +### Updates +* added custom scrollable container from [#108](https://github.com/orizens/angular2-infinite-scroll/pull/108/files) + +## v 0.3.0 (2017/01/31) + +### Updates +* refactored infinite scroller to smaller modules with composition +* added "models" - includes interfaces for development + +## v 0.2.9 (2017/01/13) + +### Updates +* added changelog.md +* ([refactor(scroll): replaces throttle with debounce](https://github.com/orizens/angular2-infinite-scroll/pull/82)) + diff --git a/old/LICENSE b/old/LICENSE new file mode 100644 index 00000000..5ef2594b --- /dev/null +++ b/old/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Oren Farhi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/old/README.md b/old/README.md new file mode 100644 index 00000000..ecce7fe6 --- /dev/null +++ b/old/README.md @@ -0,0 +1,148 @@ +[![Build Status](https://travis-ci.org/orizens/angular2-infinite-scroll.svg?branch=master)](https://travis-ci.org/orizens/angular2-infinite-scroll) + +# Angular Infinite Scroll +Inspired by [ng-infinite-scroll](https://github.com/sroze/ngInfiniteScroll) directive for angular (> 2). + +## Angular Support +Supports in Angular **> +2, 4** + +## Angular Consulting Services +I'm a Senior Javascript Engineer & A Front End Consultant at [Orizens](http://orizens.com). +My services include: +- consulting to companies and startups on how to approach code in their projects and keep it maintainable. +- I provide project bootstrapping and development - while afterwards, I integrate it on site and guide the team on it. + +[Contact Me Here](http://orizens.com/contact) + +## Installation +``` +npm install angular2-infinite-scroll --save +``` + +## Supported API +Currently supported attributes: +* **infiniteScrollDistance**<_number_> - (optional, default: **2**) - should get a number, the number of viewport lenghts from the bottom of the page at which the event will be triggered. +* **infiniteScrollUpDistance**<_number_> - (optional, default: **1.5**) - should get a number +* **infiniteScrollThrottle**<_number_> - (optional, default: **300**) - should get a number of **milliseconds** for throttle. The event will be triggered this many milliseconds after the user *stops* scrolling. +* **infiniteScrollContainer**<_string|HTMLElement_> - (optional, default: null) - should get a html element or css selector for a scrollable element; window or current element will be used if this attribute is empty. +* **scrolled**<_function_> - this will callback if the distance threshold has been reached on a scroll down. +* **scrolledUp**<_function_> - (event: InfiniteScrollEvent) - this will callback if the distance threshold has been reached on a scroll up. +* **scrollWindow**<_boolean_> - (optional, default: **true**) - listens to the window scroll instead of the actual element scroll. this allows to invoke a callback function in the scope of the element while listenning to the window scroll. +* **immediateCheck**<_boolean_> - (optional, default: **false**) - invokes the handler immediately to check if a scroll event has been already triggred when the page has been loaded (i.e. - when you refresh a page that has been scrolled). +* **infiniteScrollDisabled**<_boolean_> - (optional, default: **false**) - doesn't invoke the handler if set to true + +## Behavior +By default, the directive listens to the **window scroll** event and invoked the callback. +**To trigger the callback when the actual element is scrolled**, these settings should be configured: +* [scrollWindow]="false" +* set an explict css "height" value to the element + +## DEMO +- [**Default Scroll** By Window - plunkr](https://plnkr.co/edit/DrEDetYnZkFxR7OWWrxS?p=preview) +- [Scroll On a **"Modal"** - plunkr](https://plnkr.co/edit/QnQOwE9SEapwJCCFII3L?p=preview) + +## Usage +First, import the InfiniteScrollModule to your module: + +```typescript +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { InfiniteScrollModule } from 'angular2-infinite-scroll'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppComponent } from './app'; + +@NgModule({ + imports:[ BrowserModule, InfiniteScrollModule ], + declarations: [ AppComponent, ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); +``` + +In this example, the **onScroll** callback will be invoked when the window is scrolled down: + +```typescript +import { Component } from '@angular/core'; + +@Component({ + selector: 'app', + template: ` +
+
+ ` +}) +export class AppComponent { + onScroll () { + console.log('scrolled!!') + } +} +``` +in this example, whenever the "search-results" is scrolled, the callback will be invoked: + +```typescript +import { Component } from '@angular/core'; + +@Component({ + selector: 'app', + styles: [ + `.search-results { + height: 20rem; + overflow: scroll; + }` + ], + template: ` +
+
+ ` +}) +export class AppComponent { + onScroll () { + console.log('scrolled!!') + } +} +``` + +In this example, the **onScrollDown** callback will be invoked when the window is scrolled down and the **onScrollUp** callback will be invoked when the window is scrolled up: + +```typescript +import { Component } from '@angular/core'; +import { InfiniteScroll } from 'angular2-infinite-scroll'; + +@Component({ + selector: 'app', + directives: [ InfiniteScroll ], + template: ` +
+
+ ` +}) +export class AppComponent { + onScrollDown () { + console.log('scrolled down!!') + } + + onScrollUp () { + console.log('scrolled up!!') + } +} +``` + +# Showcase Examples +* [Echoes Player Ng2 Version](http://orizens.github.io/echoes-ng2) ([github repo for echoes player](http://github.com/orizens/echoes-ng2)) diff --git a/angular2-infinite-scroll.ts b/old/angular2-infinite-scroll.ts similarity index 100% rename from angular2-infinite-scroll.ts rename to old/angular2-infinite-scroll.ts diff --git a/config/helpers.js b/old/config/helpers.js similarity index 100% rename from config/helpers.js rename to old/config/helpers.js diff --git a/config/karma.conf.js b/old/config/karma.conf.js similarity index 100% rename from config/karma.conf.js rename to old/config/karma.conf.js diff --git a/config/spec-bundle.js b/old/config/spec-bundle.js similarity index 100% rename from config/spec-bundle.js rename to old/config/spec-bundle.js diff --git a/config/testing-utils.js b/old/config/testing-utils.js similarity index 100% rename from config/testing-utils.js rename to old/config/testing-utils.js diff --git a/config/webpack.test.js b/old/config/webpack.test.js similarity index 100% rename from config/webpack.test.js rename to old/config/webpack.test.js diff --git a/karma-test-shim.js b/old/karma-test-shim.js similarity index 100% rename from karma-test-shim.js rename to old/karma-test-shim.js diff --git a/old/karma.conf.js b/old/karma.conf.js new file mode 100644 index 00000000..75eda637 --- /dev/null +++ b/old/karma.conf.js @@ -0,0 +1,2 @@ +// Look in ./config for karma.conf.js +module.exports = require('./config/karma.conf.js'); diff --git a/make.js b/old/make.js similarity index 100% rename from make.js rename to old/make.js diff --git a/old/package.json b/old/package.json new file mode 100644 index 00000000..5bcce568 --- /dev/null +++ b/old/package.json @@ -0,0 +1,74 @@ +{ + "name": "angular2-infinite-scroll", + "version": "0.3.4", + "description": "An infinite scroll directive for angular2", + "main": "angular2-infinite-scroll.js", + "typings": "./angular2-infinite-scroll.d.ts", + "repository": "orizens/angular2-infinite-scroll", + "scripts": { + "build:test": "tsc --project ./src", + "clean": "npm run clean:jsfiles && npm run clean:typefiles && npm run clean:metadata", + "clean:jsfiles": "rimraf src/*.js && rimraf ./*scroll.js", + "clean:typefiles": "rimraf src/*.d.ts && rimraf ./*scroll.d.ts", + "clean:node": "rimraf node_modules", + "clean:metadata": "rimraf ./*metadata.json && rimraf src/ngfactory && rimraf ./src/*.metadata.json", + "bdd": "NODE_ENV='bdd' karma start karma.conf.js", + "lite": "lite-server", + "postversion": "git push origin master", + "prepublish": "node ./node_modules/@angular/compiler-cli/src/main.js && node make.js", + "pretest": "npm run clean", + "preversion": "npm run clean && npm run prepublish && npm test", + "setup": "npm run typings -- install", + "start": "npm run build && npm run lite", + "test": "karma start karma.conf.js", + "version": "git add ./", + "watch": "tsc --project ./src --watch" + }, + "keywords": [ + "angular2", + "scroll", + "infinite" + ], + "author": "Oren Farhi", + "license": "MIT", + "devDependencies": { + "@angular/common": "2.3.1", + "@angular/compiler": "2.3.1", + "@angular/compiler-cli": "2.3.1", + "@angular/core": "2.3.1", + "@angular/platform-browser": "2.3.1", + "@angular/platform-browser-dynamic": "2.3.1", + "@types/core-js": "0.9.32", + "@types/jasmine": "2.2.33", + "@types/node": "6.0.38", + "autodts": "0.0.6", + "awesome-typescript-loader": "2.2.4", + "es6-promise": "3.0.2", + "es6-shim": "0.33.3", + "istanbul-instrumenter-loader": "^1.0.0", + "jasmine-core": "2.4.1", + "karma": "^0.13.22", + "karma-chrome-launcher": "^1.0.1", + "karma-cli": "^1.0.0", + "karma-jasmine": "^1.0.2", + "karma-mocha-reporter": "2.0.4", + "karma-phantomjs-launcher": "1.0.0", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^1.8.0", + "lite-server": "2.2.0", + "path": "^0.12.7", + "phantomjs-prebuilt": "^2.1.7", + "reflect-metadata": "0.1.2", + "rimraf": "2.5.2", + "rxjs": "5.0.2", + "source-map-loader": "^0.1.5", + "systemjs": "0.19.31", + "systemjs-builder": "^0.15.16", + "ts-helpers": "^1.1.1", + "tslint": "^3.15.1", + "tslint-loader": "^2.1.5", + "typescript": "2.0.3", + "webpack": "2.1.0-beta.21", + "zone.js": "^0.6.17" + } +} diff --git a/src/axis-resolver.spec.ts b/old/src/axis-resolver.spec.ts similarity index 100% rename from src/axis-resolver.spec.ts rename to old/src/axis-resolver.spec.ts diff --git a/src/axis-resolver.ts b/old/src/axis-resolver.ts similarity index 100% rename from src/axis-resolver.ts rename to old/src/axis-resolver.ts diff --git a/src/index.ts b/old/src/index.ts similarity index 100% rename from src/index.ts rename to old/src/index.ts diff --git a/src/infinite-scroll.spec.ts b/old/src/infinite-scroll.spec.ts similarity index 100% rename from src/infinite-scroll.spec.ts rename to old/src/infinite-scroll.spec.ts diff --git a/src/infinite-scroll.ts b/old/src/infinite-scroll.ts similarity index 100% rename from src/infinite-scroll.ts rename to old/src/infinite-scroll.ts diff --git a/old/src/models.ts b/old/src/models.ts new file mode 100644 index 00000000..c6ee43df --- /dev/null +++ b/old/src/models.ts @@ -0,0 +1,31 @@ +import { ElementRef } from '@angular/core'; + +export type ContainerRef = Window | ElementRef | any; + +export interface InfiniteScrollEvent { + currentScrollPosition: number; +}; + +export interface PositionElements { + windowElement: ContainerRef; + horizontal: boolean; +} + +export interface PositionStats { + height: number; + scrolledUntilNow: number; + totalToScroll: number; +} + +export interface ScrollerConfig { + distance: { + down: number; + up: number; + }; + scrollParent?: ContainerRef; +} + +export interface ScrollStats { + isScrollingDown: boolean; + shouldScroll: boolean +} diff --git a/src/position-resolver.spec.ts b/old/src/position-resolver.spec.ts similarity index 100% rename from src/position-resolver.spec.ts rename to old/src/position-resolver.spec.ts diff --git a/src/position-resolver.ts b/old/src/position-resolver.ts similarity index 100% rename from src/position-resolver.ts rename to old/src/position-resolver.ts diff --git a/src/scroll-register.spec.ts b/old/src/scroll-register.spec.ts similarity index 100% rename from src/scroll-register.spec.ts rename to old/src/scroll-register.spec.ts diff --git a/src/scroll-register.ts b/old/src/scroll-register.ts similarity index 100% rename from src/scroll-register.ts rename to old/src/scroll-register.ts diff --git a/src/scroll-resolver.ts b/old/src/scroll-resolver.ts similarity index 100% rename from src/scroll-resolver.ts rename to old/src/scroll-resolver.ts diff --git a/src/tsconfig.json b/old/src/tsconfig.json similarity index 100% rename from src/tsconfig.json rename to old/src/tsconfig.json diff --git a/old/tsconfig.json b/old/tsconfig.json new file mode 100644 index 00000000..65877efd --- /dev/null +++ b/old/tsconfig.json @@ -0,0 +1,47 @@ +{ + "compilerOptions": { + "noImplicitAny": true, + "module": "commonjs", + "target": "es5", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "inlineSourceMap": true, + "inlineSources": true, + "declaration": true, + "suppressImplicitAnyIndexErrors": true, + "moduleResolution": "node", + "lib": [ + "dom", + "es6" + ], + "types": [ + "jasmine", + "node" + ] + }, + "exclude": [ + "node_modules", + "bundles" + ], + "awesomeTypescriptLoaderOptions": { + "forkChecker": true, + "useWebpackText": true + }, + "files": [ + "./angular2-infinite-scroll.ts", + "./src/infinite-scroll.ts", + "./src/infinite-scroll.spec.ts", + "./src/axis-resolver.ts", + "./src/axis-resolver.spec.ts", + "./src/position-resolver.ts", + "./src/position-resolver.spec.ts", + "./src/scroll-resolver.ts", + "./src/scroll-register.ts", + "./src/models.ts", + "./src/index.ts" + ], + "angularCompilerOptions": { + "genDir": "./src/ngfactory", + "debug": false + } +} diff --git a/package.json b/package.json index 5bcce568..af4dee4d 100644 --- a/package.json +++ b/package.json @@ -1,74 +1,72 @@ { - "name": "angular2-infinite-scroll", - "version": "0.3.4", - "description": "An infinite scroll directive for angular2", - "main": "angular2-infinite-scroll.js", - "typings": "./angular2-infinite-scroll.d.ts", - "repository": "orizens/angular2-infinite-scroll", - "scripts": { - "build:test": "tsc --project ./src", - "clean": "npm run clean:jsfiles && npm run clean:typefiles && npm run clean:metadata", - "clean:jsfiles": "rimraf src/*.js && rimraf ./*scroll.js", - "clean:typefiles": "rimraf src/*.d.ts && rimraf ./*scroll.d.ts", - "clean:node": "rimraf node_modules", - "clean:metadata": "rimraf ./*metadata.json && rimraf src/ngfactory && rimraf ./src/*.metadata.json", - "bdd": "NODE_ENV='bdd' karma start karma.conf.js", - "lite": "lite-server", - "postversion": "git push origin master", - "prepublish": "node ./node_modules/@angular/compiler-cli/src/main.js && node make.js", - "pretest": "npm run clean", - "preversion": "npm run clean && npm run prepublish && npm test", - "setup": "npm run typings -- install", - "start": "npm run build && npm run lite", - "test": "karma start karma.conf.js", - "version": "git add ./", - "watch": "tsc --project ./src --watch" - }, - "keywords": [ - "angular2", - "scroll", - "infinite" - ], - "author": "Oren Farhi", - "license": "MIT", - "devDependencies": { - "@angular/common": "2.3.1", - "@angular/compiler": "2.3.1", - "@angular/compiler-cli": "2.3.1", - "@angular/core": "2.3.1", - "@angular/platform-browser": "2.3.1", - "@angular/platform-browser-dynamic": "2.3.1", - "@types/core-js": "0.9.32", - "@types/jasmine": "2.2.33", - "@types/node": "6.0.38", - "autodts": "0.0.6", - "awesome-typescript-loader": "2.2.4", - "es6-promise": "3.0.2", - "es6-shim": "0.33.3", - "istanbul-instrumenter-loader": "^1.0.0", - "jasmine-core": "2.4.1", - "karma": "^0.13.22", - "karma-chrome-launcher": "^1.0.1", - "karma-cli": "^1.0.0", - "karma-jasmine": "^1.0.2", - "karma-mocha-reporter": "2.0.4", - "karma-phantomjs-launcher": "1.0.0", - "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^1.8.0", - "lite-server": "2.2.0", - "path": "^0.12.7", - "phantomjs-prebuilt": "^2.1.7", - "reflect-metadata": "0.1.2", - "rimraf": "2.5.2", - "rxjs": "5.0.2", - "source-map-loader": "^0.1.5", - "systemjs": "0.19.31", - "systemjs-builder": "^0.15.16", - "ts-helpers": "^1.1.1", - "tslint": "^3.15.1", - "tslint-loader": "^2.1.5", - "typescript": "2.0.3", - "webpack": "2.1.0-beta.21", - "zone.js": "^0.6.17" - } -} + "name": "ngx-infinite-scroll", + "version": "0.4.0-0", + "description": "Build an Angular library compatible with AoT compilation and Tree shaking", + "main": "./bundles/ngx-infinite-scroll.umd.js", + "module": "./modules/ngx-infinite-scroll.es5.js", + "es2015": "./modules/ngx-infinite-scroll.js", + "scripts": { + "build": "node build.js", + "test": "karma start", + "pack-lib": "npm pack ./dist", + "publish-lib": "npm publish ./dist", + "compodoc": "compodoc -p tsconfig.json", + "compodoc-serve": "compodoc -s" + }, + "typings": "./index.d.ts", + "author": "", + "repository": { + "type": "git", + "url": "https://github.com/orizens/ngx-infinite-scroll.git" + }, + "bugs": { + "url": "https://github.com/orizens/ngx-infinite-scroll/issues" + }, + "homepage": "https://github.com/orizens/ngx-infinite-scroll", + "keywords": [ + "angular", + "javascript", + "typescript" + ], + "license": "MIT", + "peerDependencies": { + "@angular/common": ">= 4.0.0", + "@angular/core": ">= 4.0.0" + }, + "devDependencies": { + "@angular/common": "4.0.0", + "@angular/compiler": "4.0.0", + "@angular/compiler-cli": "4.0.0", + "@angular/core": "4.0.0", + "@angular/platform-browser": "4.0.0", + "@angular/platform-browser-dynamic": "4.0.0", + "@angular/platform-server": "4.0.0", + "@angular/animations": "4.0.0", + "@types/jasmine": "2.5.46", + "@types/node": "7.0.10", + "codelyzer": "3.0.0-beta.4", + "compodoc": "0.0.41", + "core-js": "2.4.1", + "jasmine-core": "2.5.2", + "karma": "1.5.0", + "karma-chrome-launcher": "2.0.0", + "karma-jasmine": "1.1.0", + "karma-sourcemap-loader": "0.3.7", + "karma-spec-reporter": "0.0.30", + "karma-webpack": "2.0.3", + "reflect-metadata": "0.1.10", + "rollup": "0.41.6", + "rxjs": "5.2.0", + "ts-helpers": "1.1.2", + "ts-loader": "2.0.3", + "tslint": "4.5.1", + "typescript": "^2.1.5", + "webpack": "2.3.1", + "zone.js": "0.8.5", + "shelljs": "0.7.7", + "chalk": "1.1.3", + "uglify-js": "2.8.15", + "sorcery": "0.10.0", + "yargs": "7.0.2" + } +} \ No newline at end of file diff --git a/public_api.ts b/public_api.ts new file mode 100644 index 00000000..cac48cee --- /dev/null +++ b/public_api.ts @@ -0,0 +1,12 @@ +/** + * Angular library starter. + * Build an Angular library compatible with AoT compilation & Tree shaking. + * Written by Roberto Simonetti. + * MIT license. + * https://github.com/robisim74/angular-library-starter + */ + +/** + * Entry point for all public APIs of the package. + */ +export * from './src/ngx-infinite-scroll'; diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 00000000..da2fe839 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,20 @@ +export default { + entry: './dist/modules/angular-library-starter.es5.js', + dest: './dist/bundles/angular-library-starter.umd.js', + format: 'umd', + exports: 'named', + moduleName: 'ng.angularLibraryStarter', + external: [ + '@angular/core', + '@angular/common', + 'rxjs/Observable', + 'rxjs/Observer' + ], + globals: { + '@angular/core': 'ng.core', + '@angular/common': 'ng.common', + 'rxjs/Observable': 'Rx', + 'rxjs/Observer': 'Rx' + }, + onwarn: () => { return } +} \ No newline at end of file diff --git a/scripts/map-sources.js b/scripts/map-sources.js new file mode 100644 index 00000000..1d53166b --- /dev/null +++ b/scripts/map-sources.js @@ -0,0 +1,9 @@ +const sorcery = require('sorcery'); + +var argv = require('yargs') + .alias('f', 'file') + .argv; + +sorcery.load(argv.file).then(function(chain) { + chain.write(); +}); diff --git a/spec.bundle.js b/spec.bundle.js new file mode 100644 index 00000000..b1890cfb --- /dev/null +++ b/spec.bundle.js @@ -0,0 +1,28 @@ +Error.stackTraceLimit = Infinity; + +require('core-js'); +require('ts-helpers'); +require('zone.js/dist/zone'); +require('zone.js/dist/long-stack-trace-zone'); +require('zone.js/dist/proxy'); +require('zone.js/dist/sync-test'); +require('zone.js/dist/jasmine-patch'); +require('zone.js/dist/async-test'); +require('zone.js/dist/fake-async-test'); +require('rxjs/Rx'); + +var testing = require('@angular/core/testing'); +var browser = require('@angular/platform-browser-dynamic/testing'); + +testing.TestBed.initTestEnvironment( + browser.BrowserDynamicTestingModule, + browser.platformBrowserDynamicTesting() +); + +var testContext = require.context('./tests', true, /\.spec\.ts/); + +function requireAll(requireContext) { + return requireContext.keys().map(requireContext); +} + +var modules = requireAll(testContext); diff --git a/src/modules/arithmetic-module.ts b/src/modules/arithmetic-module.ts new file mode 100644 index 00000000..db0d4837 --- /dev/null +++ b/src/modules/arithmetic-module.ts @@ -0,0 +1,37 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; + +import { SumService } from '../services/sum-service'; + +@NgModule({ + declarations: [ + // Pipes. + // Directives. + ], + exports: [ + // Pipes. + // Directives. + ] +}) +export class ArithmeticModule { + + /** + * Use in AppModule: new instance of SumService. + */ + public static forRoot(): ModuleWithProviders { + return { + ngModule: ArithmeticModule, + providers: [SumService] + }; + } + + /** + * Use in features modules with lazy loading: new instance of SumService. + */ + public static forChild(): ModuleWithProviders { + return { + ngModule: ArithmeticModule, + providers: [SumService] + }; + } + +} diff --git a/src/modules/index.ts b/src/modules/index.ts new file mode 100644 index 00000000..a54cccc8 --- /dev/null +++ b/src/modules/index.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; + +import { InfiniteScroll } from './infinite-scroll'; +import { AxisResolverFactory } from '../services/axis-resolver'; +import { PositionResolverFactory } from '../services/position-resolver'; +import { ScrollRegister } from '../services/scroll-register'; +import { ScrollResolver } from '../services/scroll-resolver'; + +@NgModule({ + imports: [], + declarations: [InfiniteScroll], + exports: [InfiniteScroll], + providers: [ + AxisResolverFactory, + PositionResolverFactory, + ScrollRegister, + ScrollResolver + ] +}) +export class InfiniteScrollModule { } diff --git a/src/modules/infinite-scroll.ts b/src/modules/infinite-scroll.ts new file mode 100644 index 00000000..4a2b1257 --- /dev/null +++ b/src/modules/infinite-scroll.ts @@ -0,0 +1,109 @@ +import { InfiniteScrollEvent, ScrollStats, PositionStats } from '../models'; +import { + Directive, ElementRef, Input, Output, + EventEmitter, OnDestroy, OnInit, + SimpleChanges, NgZone +} from '@angular/core'; +import { PositionResolverFactory } from '../services/position-resolver'; +import { ScrollRegister, ScrollRegisterConfig } from '../services/scroll-register'; +import { ScrollResolver } from '../services/scroll-resolver'; +import { Subscription } from 'rxjs/Rx'; + + +@Directive({ + selector: '[infinite-scroll]' +}) +export class InfiniteScroll implements OnDestroy, OnInit { + @Output() scrolled = new EventEmitter(); + @Output() scrolledUp = new EventEmitter(); + + @Input('infiniteScrollDistance') _distanceDown: number = 2; + @Input('infiniteScrollUpDistance') _distanceUp: number = 1.5; + @Input('infiniteScrollThrottle') _throttle: number = 300; + @Input('infiniteScrollDisabled') _disabled: boolean = false; + @Input('infiniteScrollContainer') _container: any = null; + @Input('scrollWindow') scrollWindow: boolean = true; + @Input('immediateCheck') _immediate: boolean = false; + @Input('horizontal') _horizontal: boolean = false; + @Input('alwaysCallback') _alwaysCallback: boolean = false; + @Input() + set debounce(value: string | boolean) { + this.throttleType = value === '' || !!value ? 'debounce' : 'throttle'; + } + + private throttleType: string = 'throttle'; + private disposeScroller: Subscription; + + constructor( + private element: ElementRef, + private zone: NgZone, + private positionResolverFactory: PositionResolverFactory, + private scrollRegister: ScrollRegister, + private scrollerResolver: ScrollResolver + ) {} + + ngOnInit() { + if (typeof window !== 'undefined') { + const containerElement = this.resolveContainerElement(); + const positionResolver = this.positionResolverFactory.create({ + windowElement: containerElement, + horizontal: this._horizontal + }); + const options: ScrollRegisterConfig = { + container: positionResolver.container, + throttleType: this.throttleType, + throttleDuration: this._throttle, + filterBefore: () => !this._disabled, + mergeMap: () => positionResolver.calculatePoints(this.element), + scrollHandler: (container: PositionStats) => this.handleOnScroll(container) + }; + this.disposeScroller = this.scrollRegister.attachEvent(options); + } + } + + handleOnScroll(container: PositionStats) { + const scrollResolverConfig = { + distance: { + down: this._distanceDown, + up: this._distanceUp + } + }; + const scrollStats: ScrollStats = this.scrollerResolver.getScrollStats(container, scrollResolverConfig); + if (this.shouldTriggerEvents(scrollStats.shouldScroll)) { + const infiniteScrollEvent: InfiniteScrollEvent = { + currentScrollPosition: container.scrolledUntilNow + }; + if (scrollStats.isScrollingDown) { + this.onScrollDown(infiniteScrollEvent); + } else { + this.onScrollUp(infiniteScrollEvent); + } + } + } + + shouldTriggerEvents(shouldScroll: boolean) { + return (this._alwaysCallback || shouldScroll) && !this._disabled; + } + + ngOnDestroy () { + if (this.disposeScroller) { + this.disposeScroller.unsubscribe(); + } + } + + onScrollDown(data: InfiniteScrollEvent = { currentScrollPosition: 0 }) { + this.zone.run(() => this.scrolled.emit(data)); + } + + onScrollUp(data: InfiniteScrollEvent = { currentScrollPosition: 0 }) { + this.zone.run(() => this.scrolledUp.emit(data)); + } + + private resolveContainerElement(): any { + if (this._container) { + return typeof(this._container) === 'string' ? window.document.querySelector(this._container) : this._container; + } else { + return this.scrollWindow ? window : this.element; + } + } +} diff --git a/src/ngx-infinite-scroll.ts b/src/ngx-infinite-scroll.ts new file mode 100644 index 00000000..52087060 --- /dev/null +++ b/src/ngx-infinite-scroll.ts @@ -0,0 +1,12 @@ +// Public classes. +// export { SumService } from './services/sum-service'; +// export { ArithmeticModule } from './modules/arithmetic-module'; + +export { InfiniteScroll } from './modules/infinite-scroll'; +export { PositionResolver, PositionResolverFactory } from './services/position-resolver'; +export { AxisResolver, AxisResolverFactory } from './services/axis-resolver'; +export { ScrollRegister } from './services/scroll-register'; +export { ScrollResolver } from './services/scroll-resolver'; +export { InfiniteScrollModule } from './modules'; + +export { ContainerRef, InfiniteScrollEvent, PositionElements, PositionStats, ScrollStats, ScrollerConfig} from './models'; \ No newline at end of file diff --git a/src/services/axis-resolver.ts b/src/services/axis-resolver.ts new file mode 100644 index 00000000..7792aee0 --- /dev/null +++ b/src/services/axis-resolver.ts @@ -0,0 +1,22 @@ +import { Injectable, Inject } from '@angular/core'; + +@Injectable() +export class AxisResolverFactory { + constructor() { } + + create(vertical: boolean = true) { + return new AxisResolver(vertical); + } +} + +export class AxisResolver { + constructor(private vertical: boolean = true) { + } + clientHeightKey() { return this.vertical ? 'clientHeight' : 'clientWidth'; } + offsetHeightKey() { return this.vertical ? 'offsetHeight' : 'offsetWidth'; } + scrollHeightKey() { return this.vertical ? 'scrollHeight' : 'scrollWidth'; } + pageYOffsetKey() { return this.vertical ? 'pageYOffset' : 'pageXOffset'; } + offsetTopKey() { return this.vertical ? 'offsetTop' : 'offsetLeft'; } + scrollTopKey() { return this.vertical ? 'scrollTop' : 'scrollLeft'; } + topKey() { return this.vertical ? 'top' : 'left'; } +} diff --git a/src/services/position-resolver.ts b/src/services/position-resolver.ts new file mode 100644 index 00000000..2ee74a5c --- /dev/null +++ b/src/services/position-resolver.ts @@ -0,0 +1,116 @@ +import { Injectable, ElementRef } from '@angular/core'; +import { AxisResolver, AxisResolverFactory } from './axis-resolver'; +import { ContainerRef, PositionElements, PositionStats } from '../models'; + +@Injectable() +export class PositionResolverFactory { + + constructor(private axisResolver: AxisResolverFactory) { + } + + create (options: PositionElements) { + return new PositionResolver(this.axisResolver.create(!options.horizontal), options); + } +} + +export class PositionResolver { + private documentElement: ContainerRef; + private isContainerWindow: boolean; + public container: ContainerRef; + + constructor (private axis: AxisResolver, private options: PositionElements) { + this.resolveContainer(this.options.windowElement); + this.defineContainer(this.options.windowElement); + } + + defineContainer(windowElement: ContainerRef) { + if (this.resolveContainer(windowElement) || !windowElement.nativeElement) { + this.container = windowElement; + } else { + this.container = windowElement.nativeElement; + } + return this.container; + } + + resolveContainer(windowElement: ContainerRef): boolean { + const isContainerWindow = Object.prototype.toString.call(windowElement).includes('Window'); + this.isContainerWindow = isContainerWindow; + return isContainerWindow; + } + + getDocumentElement() { + return this.isContainerWindow + ? this.options.windowElement.document.documentElement + : null; + } + + calculatePoints (element: ElementRef) { + return this.isContainerWindow + ? this.calculatePointsForWindow(element) + : this.calculatePointsForElement(element); + } + + calculatePointsForWindow (element: ElementRef): PositionStats { + // container's height + const height = this.height(this.container); + // scrolled until now / current y point + const scrolledUntilNow = height + this.pageYOffset(this.getDocumentElement()); + // total height / most bottom y point + const totalToScroll = this.offsetTop(element.nativeElement) + this.height(element.nativeElement); + return { height, scrolledUntilNow, totalToScroll }; + } + + calculatePointsForElement (element: ElementRef) { + let scrollTop = this.axis.scrollTopKey(); + let scrollHeight = this.axis.scrollHeightKey(); + const container = this.container; + + const height = this.height(container); + // perhaps use this.container.offsetTop instead of 'scrollTop' + const scrolledUntilNow = container[scrollTop]; + let containerTopOffset = 0; + const offsetTop = this.offsetTop(container); + if (offsetTop !== void 0) { + containerTopOffset = offsetTop; + } + const totalToScroll = container[scrollHeight]; + return { height, scrolledUntilNow, totalToScroll }; + } + + private height (elem: any) { + let offsetHeight = this.axis.offsetHeightKey(); + let clientHeight = this.axis.clientHeightKey(); + + // elem = elem.nativeElement; + if (isNaN(elem[offsetHeight])) { + return this.getDocumentElement()[clientHeight]; + } else { + return elem[offsetHeight]; + } + } + + private offsetTop (elem: any) { + let top = this.axis.topKey(); + + // elem = elem.nativeElement; + if (!elem.getBoundingClientRect) { // || elem.css('none')) { + return; + } + return elem.getBoundingClientRect()[top] + this.pageYOffset(elem); + } + + pageYOffset (elem: any) { + let pageYOffset = this.axis.pageYOffsetKey(); + let scrollTop = this.axis.scrollTopKey(); + let offsetTop = this.axis.offsetTopKey(); + + // elem = elem.nativeElement; + if (isNaN(window[pageYOffset])) { + return this.getDocumentElement()[scrollTop]; + } else if (elem.ownerDocument) { + return elem.ownerDocument.defaultView[pageYOffset]; + } else { + return elem[offsetTop]; + } + } +} diff --git a/src/services/scroll-register.ts b/src/services/scroll-register.ts new file mode 100644 index 00000000..b27823f5 --- /dev/null +++ b/src/services/scroll-register.ts @@ -0,0 +1,33 @@ +import { ContainerRef } from '../models'; +import { Injectable, ElementRef } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs/Subscription'; +import 'rxjs/add/observable/fromEvent'; +import 'rxjs/add/observable/timer'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/operator/debounce'; +import 'rxjs/add/operator/throttle'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/mergeMap'; + + +export interface ScrollRegisterConfig { + container: ContainerRef; + throttleType: string; + throttleDuration: number; + filterBefore: Function; + mergeMap: Function; + scrollHandler: Function; +} + +@Injectable() +export class ScrollRegister { + attachEvent (options: ScrollRegisterConfig): Subscription { + const scroller$: Subscription = Observable.fromEvent(options.container, 'scroll') + [options.throttleType](() => Observable.timer(options.throttleDuration)) + .filter(options.filterBefore) + .mergeMap((ev: any) => Observable.of(options.mergeMap(ev))) + .subscribe(options.scrollHandler); + return scroller$; + } +} diff --git a/src/services/scroll-resolver.ts b/src/services/scroll-resolver.ts new file mode 100644 index 00000000..01a5db0e --- /dev/null +++ b/src/services/scroll-resolver.ts @@ -0,0 +1,33 @@ +import { PositionStats, ScrollerConfig } from '../models'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class ScrollResolver { + public lastScrollPosition: number = 0; + + shouldScroll (container: PositionStats, config: ScrollerConfig, scrollingDown: boolean) { + const distance = config.distance; + let remaining: number; + let containerBreakpoint: number; + if (scrollingDown) { + remaining = container.totalToScroll - container.scrolledUntilNow; + containerBreakpoint = container.height * distance.down + 1; + } else { + remaining = container.scrolledUntilNow; + containerBreakpoint = container.height * distance.up + 1; + } + const shouldScroll: boolean = remaining <= containerBreakpoint; + this.lastScrollPosition = container.scrolledUntilNow; + return shouldScroll; + } + + isScrollingDown (container: PositionStats) { + return this.lastScrollPosition < container.scrolledUntilNow; + } + + getScrollStats (container: PositionStats, config: ScrollerConfig) { + const isScrollingDown = this.isScrollingDown(container); + const shouldScroll = this.shouldScroll(container, config, isScrollingDown); + return { isScrollingDown, shouldScroll }; + } +} diff --git a/src/services/sum-service.ts b/src/services/sum-service.ts new file mode 100644 index 00000000..157991dd --- /dev/null +++ b/src/services/sum-service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class SumService { + + /** + * Stores the last sum. + */ + public sum: number; + + /** + * Calculates the sum. + * @param addends Numbers to be added + */ + public calculate(...addends: number[]): void { + this.sum = 0; + for (let addend of addends) { + this.sum += addend; + } + } + +} diff --git a/tests/modules/infinite-scroll.spec.ts b/tests/modules/infinite-scroll.spec.ts new file mode 100644 index 00000000..039704b6 --- /dev/null +++ b/tests/modules/infinite-scroll.spec.ts @@ -0,0 +1,185 @@ +import { + async, + inject +} from '@angular/core/testing'; +import { InfiniteScroll } from '../../src/modules/infinite-scroll'; +import { AxisResolverFactory } from '../../src/services/axis-resolver'; +import { PositionResolverFactory } from '../../src/services/position-resolver'; +import { ScrollRegister } from '../../src/services/scroll-register'; +import { ScrollResolver } from '../../src/services/scroll-resolver'; + +import { ElementRef, NgZone, SimpleChanges, SimpleChange } from '@angular/core'; + +describe('Infinite Scroll Directive', () => { + // const zone = new NgZone({ enableLongStackTrace: false }); + let isScrollingDown = true; + let zoneSpy: any, scrollResolverSpy: any, scrollRegisterSpy: any, positionResolverSpy: any; + const positionFactoryMock: any = { + create: () => positionResolverSpy + }; + const createMockElement = () => { + const mockedElement: ElementRef = new ElementRef(document.createElement('div')); + return mockedElement; + }; + const createInfiniteScroll = (mockedElement?: any) => { + mockedElement = mockedElement || createMockElement(); + return new InfiniteScroll( + mockedElement, + zoneSpy, + positionFactoryMock, + scrollRegisterSpy, + scrollResolverSpy + ); + }; + + beforeEach(() =>{ + zoneSpy = jasmine.createSpyObj('zone', ['run']); + scrollResolverSpy = { + getScrollStats: () => { + return { shouldScroll: true, isScrollingDown }; + } + }; + scrollRegisterSpy = jasmine.createSpyObj('register', ['attachEvent']) + positionResolverSpy = jasmine.createSpyObj('pos', ['create', 'container']); + }); + + it('should create an instance of the directive', () => { + const actual = createInfiniteScroll(); + expect(actual).toBeDefined(); + }); + + it('should have default @Input properties values', () => { + const directive: any = createInfiniteScroll(); + const expectedInputs: Object = { + _distanceDown: 2, + _distanceUp: 1.5, + _throttle: 300, + scrollWindow: true, + _immediate: false, + _horizontal: false, + _alwaysCallback: false, + _disabled: false, + _container: null + }; + + Object.keys(expectedInputs).forEach(input => + expect(directive[input]).toEqual(expectedInputs[input])); + }); + + it('should trigger the onScrollDown event when scroll has passed _distancedDown', () => { + const directive = createInfiniteScroll(); + const container = { + height: 0, + scrolledUntilNow: 0, + totalToScroll: 0, + } + spyOn(directive, 'onScrollDown'); + directive.ngOnInit(); + directive.handleOnScroll(container) + const actual = directive.onScrollDown; + expect(actual).toHaveBeenCalled(); + }); + + it('should trigger the onScrollUp event when scroll has passed _distanceUp', () => { + const directive = createInfiniteScroll(); + const container = { + height: 0, + scrolledUntilNow: 0, + totalToScroll: 0, + }; + spyOn(directive, 'onScrollUp'); + directive.ngOnInit(); + isScrollingDown = false; + directive.handleOnScroll(container); + const actual = directive.onScrollUp; + expect(actual).toHaveBeenCalled(); + }); + + it('should disable the scroller', () => { + const directive = createInfiniteScroll(); + const container = { + height: 0, + scrolledUntilNow: 0, + totalToScroll: 0, + } + spyOn(directive, 'onScrollDown'); + directive.ngOnInit(); + directive._disabled = true; + directive.handleOnScroll(container); + const actual = directive.onScrollDown; + expect(actual).not.toHaveBeenCalled(); + }); + + describe('resolving container', () => { + let directive: InfiniteScroll; + let mockedElement: ElementRef; + const container = { + height: 0, + scrolledUntilNow: 0, + totalToScroll: 0, + }; + + beforeEach(() => { + mockedElement = createMockElement(); + directive = createInfiniteScroll(mockedElement); + spyOn(positionFactoryMock, 'create').and.callThrough(); + }); + + describe('when container input is defined', () => { + describe('when css selector is used', () => { + beforeEach(() => { + spyOn(document, 'querySelector').and.returnValue(container); + directive._container = '.test'; + directive.ngOnInit(); + }); + + it('should find element in DOM', () => { + expect(document.querySelector).toHaveBeenCalledWith('.test'); + }); + + it('should return container', () => { + expect(positionFactoryMock.create) + .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: container})); + }); + }); + + describe('when container is passed directly', () => { + beforeEach(() => { + directive._container = container; + directive.ngOnInit(); + }); + + it('should return container', () => { + expect(positionFactoryMock.create) + .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: container})); + }); + }); + }); + + describe('when container input is not defined', () => { + describe('when scrollWindow is true', () => { + beforeEach(() => { + directive.scrollWindow = true; + directive.ngOnInit(); + }); + + it('should return window', () => { + expect(positionFactoryMock.create) + .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: window})); + }); + }); + + describe('when scrollWindow is false', () => { + beforeEach(() => { + directive.scrollWindow = false; + directive.ngOnInit(); + }); + + it('should return current element', () => { + expect(positionFactoryMock.create) + .toHaveBeenCalledWith(jasmine.objectContaining({windowElement: mockedElement})); + }); + }); + }); + }); +}); diff --git a/tests/services/position-resolver.spec.ts b/tests/services/position-resolver.spec.ts new file mode 100644 index 00000000..17722718 --- /dev/null +++ b/tests/services/position-resolver.spec.ts @@ -0,0 +1,75 @@ +import { + async, + inject +} from '@angular/core/testing'; +import { PositionResolver } from '../../src/services/position-resolver'; +import { AxisResolver } from '../../src/services/axis-resolver'; +import { ElementRef } from '@angular/core'; + +describe('Position Resolver', () => { + let mockedElement: ElementRef; + let mockedContainer: ElementRef; + + const createMockDom = () => { + const container = document.createElement('section'); + container.setAttribute('style', 'height: 500px; overflow-y: scroll'); + const el = document.createElement('div'); + el.setAttribute('style', 'height: 1000px;'); + container.appendChild(el); + mockedElement = new ElementRef(el); + mockedContainer = new ElementRef(container); + return { element: mockedElement, container: mockedContainer }; + }; + + const createPositionResolver = (element: ElementRef, container: ElementRef) => { + const options = { + windowElement: element, + horizontal: true + }; + const axis: AxisResolver = new AxisResolver(); + return new PositionResolver(axis, options); + }; + + beforeEach(() =>{ + + }); + + it('should create an instance of position resolver', () => { + const mockDom = createMockDom(); + const actual = createPositionResolver(mockDom.element, mockDom.container); + expect(actual).toBeDefined(); + }); + + it('should calculate points', () => { + const mockDom = createMockDom(); + const service = createPositionResolver(mockDom.element, mockDom.container); + const actual = service.calculatePoints(mockDom.element); + expect(actual).toBeDefined(); + }); + + describe('creating instance for non-window element', () => { + let service: PositionResolver; + + describe('when nativeElement is present', () => { + beforeEach(() => { + const mockDom = createMockDom(); + service = createPositionResolver(mockDom.element, mockDom.container); + }); + + it('should use container as nativeElement', () => { + expect(service.container instanceof HTMLDivElement).toBeTruthy(); + }); + }); + + describe('when nativeElement is not present', () => { + beforeEach(() => { + const mockDom = createMockDom(); + service = createPositionResolver(mockDom.element, mockDom.container.nativeElement); + }); + + it('should use container as nativeElement', () => { + expect(service.container instanceof HTMLDivElement).toBeTruthy(); + }); + }); + }); +}); diff --git a/tests/services/scroll-register.spec.ts b/tests/services/scroll-register.spec.ts new file mode 100644 index 00000000..08e629d0 --- /dev/null +++ b/tests/services/scroll-register.spec.ts @@ -0,0 +1,44 @@ +import { Subscription } from 'rxjs/Rx'; +import { + async, + inject +} from '@angular/core/testing'; +import { ScrollRegister, ScrollRegisterConfig } from '../../src/services/scroll-register'; +import { ElementRef } from '@angular/core'; + +describe('Scroll Regsiter', () => { + let mockedElement: ElementRef; + let mockedContainer: ElementRef; + let scrollRegister: ScrollRegister; + + const createMockDom = () => { + const container = document.createElement('section'); + container.setAttribute('style', 'height: 500px; overflow-y: scroll'); + const el = document.createElement('div'); + el.setAttribute('style', 'height: 1000px;'); + container.appendChild(el); + mockedElement = new ElementRef(el); + mockedContainer = new ElementRef(container); + return { element: mockedElement, container: mockedContainer }; + }; + + beforeEach(() =>{ + scrollRegister = new ScrollRegister(); + }); + + it('should create a Subscription of scroll observable', () => { + const mockDom = createMockDom(); + const scrollConfig: ScrollRegisterConfig = { + container: mockDom.container.nativeElement, + filterBefore: () => true, + mergeMap: (e: any) => e, + scrollHandler: (ev: any) => ev, + throttleDuration: 300, + throttleType: 'throttle' + + }; + const scroller$: Subscription = scrollRegister.attachEvent(scrollConfig); + const actual = scroller$; + expect(actual).toBeDefined(); + }); +}); diff --git a/tsconfig-build.json b/tsconfig-build.json new file mode 100644 index 00000000..766a8f25 --- /dev/null +++ b/tsconfig-build.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "experimentalDecorators": true, + "module": "es2015", + "moduleResolution": "node", + "outDir": "dist", + "rootDir": ".", + "sourceMap": true, + "inlineSources": true, + "target": "es2015", + "skipLibCheck": true, + "lib": [ + "es2015", + "dom" + ] + }, + "files": [ + "public_api.ts", + "node_modules/zone.js/dist/zone.js.d.ts" + ], + "angularCompilerOptions": { + "skipTemplateCodegen": true, + "annotateForClosureCompiler": true, + "strictMetadataEmit": true, + "flatModuleOutFile": "index.js", + "flatModuleId": "angular-library-starter" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 65877efd..dfd43e3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,47 +1,27 @@ { - "compilerOptions": { - "noImplicitAny": true, - "module": "commonjs", - "target": "es5", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "inlineSourceMap": true, - "inlineSources": true, - "declaration": true, - "suppressImplicitAnyIndexErrors": true, - "moduleResolution": "node", - "lib": [ - "dom", - "es6" - ], - "types": [ - "jasmine", - "node" + "compilerOptions": { + "baseUrl": ".", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "module": "commonjs", + "moduleResolution": "node", + "noImplicitAny": false, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "rootDir": ".", + "inlineSources": true, + "lib": [ + "es6", + "dom" + ], + "target": "es5", + "skipLibCheck": true, + "types": [ + "jasmine", + "node" + ] + }, + "exclude": [ + "node_modules" ] - }, - "exclude": [ - "node_modules", - "bundles" - ], - "awesomeTypescriptLoaderOptions": { - "forkChecker": true, - "useWebpackText": true - }, - "files": [ - "./angular2-infinite-scroll.ts", - "./src/infinite-scroll.ts", - "./src/infinite-scroll.spec.ts", - "./src/axis-resolver.ts", - "./src/axis-resolver.spec.ts", - "./src/position-resolver.ts", - "./src/position-resolver.spec.ts", - "./src/scroll-resolver.ts", - "./src/scroll-register.ts", - "./src/models.ts", - "./src/index.ts" - ], - "angularCompilerOptions": { - "genDir": "./src/ngfactory", - "debug": false - } -} +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..c0d7a7bf --- /dev/null +++ b/tslint.json @@ -0,0 +1,55 @@ +{ + "extends": "tslint:recommended", + "rulesDirectory": [ + "node_modules/codelyzer" + ], + "rules": { + "directive-selector": [ + true, + "attribute", + [ + "dir-prefix1", + "dir-prefix2" + ], + "camelCase" + ], + "component-selector": [ + true, + "element", + [ + "cmp-prefix1", + "cmp-prefix2" + ], + "kebab-case" + ], + "use-input-property-decorator": true, + "use-output-property-decorator": true, + "use-host-property-decorator": true, + "no-attribute-parameter-decorator": true, + "no-input-rename": true, + "no-output-rename": true, + "no-forward-ref": true, + "use-life-cycle-interface": true, + "use-pipe-transform-interface": true, + "component-class-suffix": [ + true, + "Component" + ], + "directive-class-suffix": [ + true, + "Directive" + ], + "templates-use-public": true, + "no-access-missing-member": true, + "invoke-injectable": true, + "ordered-imports": [ + false + ], + "quotemark": [ + false + ], + "trailing-comma": [ + false + ] + } +} \ No newline at end of file