├── .babelrc
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .jshintrc
├── .npmignore
├── LICENSE
├── README.md
├── bower.json
├── demo
├── adapter
│ ├── adapter.html
│ └── adapter.js
├── adapterSync
│ ├── adapterSync.html
│ ├── adapterSync.js
│ └── server.js
├── animation
│ ├── animation.html
│ └── animation.js
├── append
│ ├── append.html
│ ├── append.js
│ └── server.js
├── bottomVisible
│ ├── bottomVisibleAdapter.html
│ └── bottomVisibleAdapter.js
├── bufferItems
│ ├── bufferItems.html
│ └── bufferItems.js
├── cache
│ ├── cache.html
│ └── cache.js
├── chat
│ ├── chat.html
│ └── chat.js
├── controllerAs
│ ├── controllerAs.html
│ └── controllerAs.js
├── css
│ ├── bootstrap.css
│ ├── style.css
│ └── style.less
├── differentItemHeights
│ ├── differentItemHeights.html
│ └── differentItemHeights.js
├── disabled
│ ├── disabled.html
│ └── disabled.js
├── grid-dnd-sort-2
│ ├── angular-dnd.js
│ ├── grid-dnd-sort.html
│ ├── grid-dnd-sort.js
│ └── grid.css
├── grid-dnd-sort
│ ├── grid-dnd-sort.html
│ ├── grid-dnd-sort.js
│ └── grid.css
├── grid-dnd-widths
│ ├── grid-dnd-widths.html
│ ├── grid-dnd-widths.js
│ └── grid.css
├── grid-layout-apply
│ ├── grid-layout-apply.html
│ ├── grid-layout-apply.js
│ └── grid.css
├── grid-layout-manipulations
│ ├── grid-layout-manipulations.html
│ ├── grid-layout-manipulations.js
│ └── grid.css
├── grid-scopes-wrapping
│ ├── grid-scopes-wrapping.html
│ ├── grid-scopes-wrapping.js
│ └── grid.css
├── index.html
├── insideComponent
│ ├── insideComponent.html
│ └── insideComponent.js
├── insideDirective
│ ├── insideDirective.html
│ └── insideDirective.js
├── isLoading
│ ├── isLoading.html
│ ├── isLoading.js
│ ├── isLoadingAdapter.html
│ └── isLoadingAdapter.js
├── jquery
│ ├── jquery.html
│ └── jquery.js
├── listScroller
│ ├── listScroller.html
│ └── listScroller.js
├── multipleLists
│ ├── multipleLists.html
│ └── multipleLists.js
├── multipleReloadTest
│ ├── multipleReload.html
│ └── multipleReload.js
├── outOfBuffer
│ ├── outOfBuffer.html
│ └── outOfBuffer.js
├── persistentScroll
│ ├── persistentScroll.html
│ └── persistentScroll.js
├── positionedList
│ ├── positionedList.html
│ └── positionedList.js
├── rebuilding
│ ├── rebuilding.html
│ └── rebuilding.js
├── reload100
│ ├── reload100.html
│ └── reload100.js
├── remote
│ ├── remote.html
│ └── remote.js
├── scopeDatasource
│ ├── scopeDatasource.html
│ └── scopeDatasource.js
├── scrollBubblingPrevent
│ ├── scrollBubblingPrevent.html
│ └── scrollBubblingPrevent.js
├── serviceDatasource
│ ├── serviceDatasource.html
│ └── serviceDatasource.js
├── tableScroller
│ ├── tableScroller.html
│ └── tableScroller.js
├── topVisible
│ ├── topVisible.html
│ ├── topVisible.js
│ ├── topVisibleAdapter.html
│ └── topVisibleAdapter.js
├── ui-scroll-demo.gif
├── userIndexes
│ ├── userIndexes.html
│ └── userIndexes.js
├── visibility
│ ├── visibility.html
│ └── visibility.js
├── windowViewport
│ ├── windowViewport-iframe.html
│ ├── windowViewport.html
│ └── windowViewport.js
└── windowviewportInline
│ ├── windowviewportInline.html
│ └── windowviewportInline.js
├── dist
├── ui-scroll-grid.js
├── ui-scroll-grid.js.map
├── ui-scroll-grid.min.js
├── ui-scroll-grid.min.js.map
├── ui-scroll-jqlite.js
├── ui-scroll-jqlite.min.js
├── ui-scroll.js
├── ui-scroll.js.map
├── ui-scroll.min.js
└── ui-scroll.min.js.map
├── package-lock.json
├── package.json
├── src
├── modules
│ ├── adapter.js
│ ├── buffer.js
│ ├── elementRoutines.js
│ ├── jqLiteExtras.js
│ ├── padding.js
│ ├── utils.js
│ └── viewport.js
├── ui-scroll-grid.js
├── ui-scroll-jqlite.js
└── ui-scroll.js
├── test
├── .jshintrc
├── AdapterTestsSpec.js
├── AssigningSpec.js
├── BasicSetupSpec.js
├── BasicTestsSpec.js
├── BufferCleanupSpec.js
├── GridTestsSpec.js
├── PaddingsSpec.js
├── UserIndicesSpec.js
├── VisibilitySwitchingSpec.js
├── config
│ ├── karma.conf.files.js
│ └── karma.conf.js
├── jqliteExtrasSpec.js
└── misc
│ ├── datasources.js
│ ├── helpers.js
│ ├── scaffolding.js
│ ├── scaffoldingGrid.js
│ └── test.css
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "targets": {
7 | "browsers": [
8 | "last 2 versions",
9 | "IE >= 9"
10 | ]
11 | }
12 | }
13 | ]
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: ui-scroll build
2 |
3 | on:
4 | push:
5 | branches:
6 | - "**"
7 | workflow_dispatch:
8 | inputs:
9 | cause:
10 | description: 'Reason'
11 | required: true
12 | default: 'Manual triggering'
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 5
18 | steps:
19 | - name: Dispatched?
20 | if: ${{ github.event_name == 'workflow_dispatch' }}
21 | run: |
22 | echo "This is dispatched"
23 | echo "Build reason: ${{ github.event.inputs.cause }}"
24 |
25 | - name: Checkout
26 | uses: actions/checkout@v3
27 |
28 | - name: Use Node.js
29 | uses: actions/setup-node@v3
30 | with:
31 | node-version: 18
32 |
33 | - name: Install dependencies
34 | run: npm ci
35 |
36 | - name: Run tests
37 | env:
38 | CI: true
39 | BROWSER: headless
40 | run: npm test
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | temp
3 | *.log
4 | .idea
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "boss": true,
3 | "browser": true,
4 | "eqnull": true,
5 | "expr": true,
6 | "esversion": 6,
7 | "immed": true,
8 | "laxbreak": true,
9 | "loopfunc": true,
10 | "newcap": true,
11 | "noarg": true,
12 | "noempty": true,
13 | "nonew": true,
14 | "quotmark": true,
15 | "smarttabs": true,
16 | "strict": false,
17 | "sub": true,
18 | "trailing": true,
19 | "undef": true,
20 | "unused": true,
21 | "globals": {
22 | "angular": false
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /node_modules/
3 | /src
4 | /temp
5 | /test
6 | /.github
7 | .babelrc
8 | .gitignore
9 | .jshintrc
10 | .npmignore
11 | webpack.config.js
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013-2023 Hill30 INC, https://github.com/Hill30
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-scroll",
3 | "description": "AngularJS infinite scrolling module",
4 | "version": "1.9.1",
5 | "main": "./dist/ui-scroll.js",
6 | "homepage": "https://github.com/angular-ui/ui-scroll.git",
7 | "license": "MIT",
8 | "keywords": [
9 | "angular",
10 | "angularjs",
11 | "angular.ui",
12 | "angular-ui",
13 | "ui.scroll",
14 | "ui-scroll",
15 | "angular-ui-scroll",
16 | "virtual",
17 | "unlimited",
18 | "infinite",
19 | "live",
20 | "perpetual",
21 | "scroll",
22 | "scroller",
23 | "scrolling"
24 | ],
25 | "ignore": [
26 | "node_modules",
27 | "src",
28 | "temp",
29 | "test",
30 | ".github",
31 | ".gitignore",
32 | ".jshintrc",
33 | ".npmignore",
34 | "Gruntfile.js",
35 | "package.json",
36 | "webpack.config.js"
37 | ]
38 | }
--------------------------------------------------------------------------------
/demo/adapter/adapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{"item #" + item.index + ' (h = ' + item.height + ')'}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/demo/differentItemHeights/differentItemHeights.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 |
3 | .run(function($rootScope) {
4 | $rootScope.doReload = function () {
5 | $rootScope.$broadcast('DO_RELOAD');
6 | };
7 | })
8 |
9 | .controller('MainCtrl', function($scope) {
10 | $scope.hello = 'Hello Main Controller!';
11 |
12 | var reloadListener = $scope.$on('DO_RELOAD', function() {
13 | if ($scope.adapter) {
14 | $scope.adapter.reload();
15 | }
16 | });
17 |
18 | $scope.$on("$destroy", function() {
19 | reloadListener();
20 | });
21 |
22 | var min = -50, max = 100, delay = 0;
23 |
24 | $scope.datasource = {
25 | get: function(index, count, success) {
26 | console.log('Getting ' + count + ' items started from ' + index + '...');
27 | setTimeout(function() {
28 | var result = [];
29 | var start = Math.max(min, index);
30 | var end = Math.min(index + count - 1, max);
31 | if (start <= end) {
32 | for (var i = start; i <= end; i++) {
33 | height = 50 + (i + 1);
34 | result.push({ index: i, height: height });
35 | }
36 | }
37 | console.log('Got ' + result.length + ' items [' + start + '..' + end + ']');
38 | success(result);
39 | }, delay);
40 | }
41 | };
42 |
43 | });
44 |
--------------------------------------------------------------------------------
/demo/disabled/disabled.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Disabling
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | There is an property "disabled" on the ui-scroll adapter which allows to ignore user scroll/resize events:
21 |
22 |
23 |
<div ui-scroll="item in datasource" adapter="myAdapter">{{item}}</div>
24 |
25 | Here we have two-way binding, so just set the property in appropriate time:
26 |
27 |
$scope.myAdapter.disabled = true;
28 |
29 | Since "disabled" is true no user scroll is being processed by ui-scroll engine.
30 |
31 |
32 |
33 |
34 | User scroll processing is {{myAdapter.disabled ? "disabled" : "enabled"}} now
35 | {{myAdapter.disabled?"Enable":"Disable"}}
36 |
37 |
38 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/demo/disabled/disabled.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 | }
18 | ]);
19 |
--------------------------------------------------------------------------------
/demo/grid-dnd-sort-2/angular-dnd.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angular-ui/ui-scroll/3339d3b4ccdb7fe0de0de279047205a4fa45cd02/demo/grid-dnd-sort-2/angular-dnd.js
--------------------------------------------------------------------------------
/demo/grid-dnd-sort-2/grid-dnd-sort.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid dnd sort (applyLayout)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
browse other examples
18 |
19 |
20 |
21 |
22 | Here is the implementation sample of drag and drop grid columns sorting through the applyLayout method call.
23 | Also this demo demonstrates an option of an integration with some external components.
24 | And here is being used
angular-dnd component to provide
25 | columns drag and drop functionality.
26 |
27 |
28 |
29 |
30 |
31 |
37 | {{item.name}}
38 |
39 |
40 |
41 |
42 |
43 | {{item.col1}}
44 | {{item.col2}}
45 | {{item.col3}}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/demo/grid-dnd-sort-2/grid-dnd-sort.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid', 'dnd'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push({
11 | col1: i,
12 | col2: 'item #' + i,
13 | col3: (Math.random() < 0.5)
14 | });
15 | }
16 | success(result);
17 | }, 100);
18 | };
19 |
20 | $scope.datasource = datasource;
21 |
22 | $scope.headers = [{
23 | index: 0,
24 | name: 'col1',
25 | sortable: true
26 | }, {
27 | index: 1,
28 | name: 'col2',
29 | sortable: true
30 | }, {
31 | index: 2,
32 | name: 'col3',
33 | sortable: true
34 | }];
35 |
36 | $scope.onSortEnd = function () {
37 | var layout = $scope.adapter.gridAdapter.getLayout();
38 | for (var i = 0; i < $scope.headers.length; i++) {
39 | layout[$scope.headers[i].index].mapTo = i;
40 | }
41 | $scope.adapter.gridAdapter.applyLayout(layout)
42 | };
43 |
44 | }
45 | ]);
--------------------------------------------------------------------------------
/demo/grid-dnd-sort-2/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .col1 {
6 | width: 80px;
7 | font-style: italic;
8 | }
9 |
10 | .col2 {
11 | width: 200px;
12 | }
13 |
14 | .col3 {
15 | width: 80px;
16 | }
17 |
18 | .grid th {
19 | cursor: pointer;
20 | }
21 |
22 | .angular-dnd-placeholder {
23 | background: #ddd;
24 | }
25 |
26 | .active {
27 | border: 1px dashed red;
28 | }
--------------------------------------------------------------------------------
/demo/grid-dnd-sort/grid-dnd-sort.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid dnd sort
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 |
22 | Here two methods are being demonstrated. columnFromPoint() which allows you to get the appropriate ColumnAdapter object by coordinates:
23 |
24 | target = $scope.adapter.gridAdapter.columnFromPoint(evt.clientX, evt.clientY)
25 | Another method is moveBefore() which provides an ability to move one column in front of another:
26 |
27 | column.moveBefore(target)
28 |
29 |
30 |
31 |
32 |
33 |
34 |
41 | {{item.name}}
42 |
43 |
44 |
45 |
46 |
47 | {{item.col1}}
48 | {{item.col2}}
49 | {{item.col3}}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/demo/grid-dnd-sort/grid-dnd-sort.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push({
11 | col1: i,
12 | col2: 'item #' + i,
13 | col3: (Math.random() < 0.5)
14 | });
15 | }
16 | success(result);
17 | }, 100);
18 | };
19 |
20 | $scope.datasource = datasource;
21 |
22 | $scope.headers = [{
23 | index: 0,
24 | name: 'col1',
25 | sortable: true
26 | }, {
27 | index: 1,
28 | name: 'col2',
29 | sortable: true
30 | }, {
31 | index: 2,
32 | name: 'col3',
33 | sortable: true
34 | }];
35 |
36 | $scope.dragStart = function (evt) {
37 | var column = $scope.adapter.gridAdapter.columnFromPoint(evt.clientX, evt.clientY);
38 | evt.dataTransfer.setData('application/x-data',
39 | $scope.adapter.gridAdapter.columns.findIndex((c) => c.columnId === column.columnId)
40 | );
41 | }
42 |
43 | $scope.dragOver = function (evt) {
44 | evt.preventDefault();
45 | return false;
46 | }
47 |
48 | $scope.dragDrop = function (evt) {
49 | var target = $scope.adapter.gridAdapter.columnFromPoint(evt.clientX, evt.clientY);
50 | var column = $scope.adapter.gridAdapter.columns[evt.dataTransfer.getData('application/x-data')];
51 | column.moveBefore(target);
52 | console.log(evt.dataTransfer);
53 | }
54 |
55 | }
56 | ]);
57 |
--------------------------------------------------------------------------------
/demo/grid-dnd-sort/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .col1 {
6 | width: 80px;
7 | font-style: italic;
8 | }
9 |
10 | .col2 {
11 | width: 200px;
12 | }
13 |
14 | .col3 {
15 | width: 80px;
16 | }
17 |
18 | .grid th {
19 | cursor: pointer;
20 | }
21 |
22 | .angular-dnd-placeholder {
23 | background: #ddd;
24 | }
25 |
26 | .active {
27 | border: 1px dashed red;
28 | }
--------------------------------------------------------------------------------
/demo/grid-dnd-widths/grid-dnd-widths.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid dnd widths
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 | To manipulate grid columns widths .css method on gridAdapter column is available:
22 |
23 |
24 | $scope.adapter.gridAdapter.columns[0].css('width', '200px');
25 |
26 | You also can set any css property this way.
27 |
28 |
29 |
30 |
31 |
34 |
35 | num
36 |
41 |
42 | text
43 |
44 |
45 |
46 |
47 | {{item.col1}}
48 | {{item.col2}}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/demo/grid-dnd-widths/grid-dnd-widths.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push({
11 | col1: i,
12 | col2: 'item #' + i
13 | });
14 | }
15 | success(result);
16 | }, 100);
17 | };
18 |
19 | $scope.datasource = datasource;
20 |
21 | var splitter = angular.element(document.getElementById('splitter'));
22 | var startX = 0;
23 | var right = 0;
24 | var startDrag = false;
25 |
26 | $scope.dragStart = function (evt) {
27 | if(startDrag) {
28 | return false;
29 | }
30 | splitter.addClass('active');
31 | startDrag = true;
32 | startX = right + evt.clientX;
33 | };
34 |
35 | $scope.dragOver = function (evt) {
36 | if(!startDrag) {
37 | return false;
38 | }
39 |
40 | right = startX - evt.clientX;
41 | $scope.adapter.gridAdapter.columns[1].css('width', (200 + right) + 'px');
42 | $scope.adapter.gridAdapter.columns[0].css('width', (80 - right) + 'px');
43 | splitter.css('right', '0');
44 |
45 | return false;
46 | };
47 |
48 | $scope.dragDrop = function (evt) {
49 | startDrag = false;
50 | splitter.removeClass('active');
51 | }
52 |
53 |
54 | }
55 | ]);
56 |
57 |
58 |
--------------------------------------------------------------------------------
/demo/grid-dnd-widths/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .grid tbody {
6 | width: 300px;
7 | }
8 |
9 | .col1 {
10 | width: 80px;
11 | }
12 |
13 | td.col1 {
14 | font-style: italic;
15 | }
16 |
17 | .col2 {
18 | width: 200px;
19 | }
20 |
21 | .splitter{
22 | cursor: e-resize;
23 | float: right;
24 | border: 2px solid black;
25 | height: 20px;
26 | position: relative;
27 | right: 0;
28 | }
29 |
30 | td, th {
31 | border: 0 dashed #999;
32 | border-left-width: 1px;
33 | }
34 | th {
35 | border-bottom-width: 1px;
36 | }
37 |
38 | .active {
39 | border: 2px solid red;
40 | }
--------------------------------------------------------------------------------
/demo/grid-layout-apply/grid-layout-apply.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid layout apply
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 |
Apply layout - should apply bg-colors and a new columns order
22 |
23 |
Clear layout - just should clear
24 |
25 |
26 |
27 |
28 |
29 |
30 | number
31 | content
32 | bool
33 |
34 |
35 |
36 |
37 | {{item.col1}}
38 | {{item.col2}}
39 | {{item.col3}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/demo/grid-layout-apply/grid-layout-apply.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push({
11 | col1: i,
12 | col2: 'item #' + i,
13 | col3: (Math.random() < 0.5)
14 | });
15 | }
16 | success(result);
17 | }, 100);
18 | };
19 |
20 | $scope.datasource = datasource;
21 |
22 | var clearLayout = [
23 | {index: 0, mapTo: 0, css: {backgroundColor: ''}},
24 | {index: 1, mapTo: 1, css: {backgroundColor: ''}},
25 | {index: 2, mapTo: 2, css: {backgroundColor: ''}}
26 | ];
27 |
28 | var someLayout = [
29 | {index: 0, mapTo: 2, css: {backgroundColor: '#ccc'}},
30 | {index: 1, mapTo: 1, css: {backgroundColor: '#ddd'}},
31 | {index: 2, mapTo: 0, css: {backgroundColor: '#eee'}}
32 | ];
33 |
34 | $scope.applyLayout = function () {
35 | $scope.adapter.gridAdapter.applyLayout(someLayout);
36 | };
37 |
38 | $scope.clearLayout = function () {
39 | $scope.adapter.gridAdapter.applyLayout(clearLayout);
40 | };
41 |
42 | }
43 | ]);
44 |
--------------------------------------------------------------------------------
/demo/grid-layout-apply/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .grid tbody {
6 | width: 380px;
7 | }
8 |
9 | hr {
10 | margin: 5px;
11 | }
12 |
13 | .col1 {
14 | width: 80px;
15 | }
16 |
17 | td.col1 {
18 | font-style: italic;
19 | }
20 |
21 | .col2 {
22 | width: 200px;
23 | }
24 |
25 | .col3 {
26 | width: 80px;
27 | }
28 |
29 | input {
30 | margin-bottom: 5px;
31 | }
32 |
--------------------------------------------------------------------------------
/demo/grid-layout-manipulations/grid-layout-manipulations.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid layout manipulations
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 |
22 | Here we have a demo on grid layout manipulations.
23 | The layout can be updated out of the components core, saved and applied.
24 | To apply some layout you may call applyLayout method on gridAdapter:
25 |
26 |
27 | $scope.adapter.gridAdapter.applyLayout(layout)
28 | The "layout" variable must have an appropriate format which exactly matches the format of getLayout() method call result.
29 |
30 |
31 | layout = $scope.adapter.gridAdapter.getLayout()
32 | Note that saving via cookie requires web-server.
33 |
34 |
35 |
36 |
37 | 1 column bg-color
38 | 2 column bg-color
39 | 3 column bg-color
40 | Apply layout
41 | Clear layout
42 |
43 | Save layout to cookie
44 | Restore layout from cookie
45 |
46 |
47 |
48 |
49 |
50 | number
51 | content
52 | bool
53 |
54 |
55 |
56 |
57 | {{item.col1}}
58 | {{item.col2}}
59 | {{item.col3}}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/demo/grid-layout-manipulations/grid-layout-manipulations.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push({
11 | col1: i,
12 | col2: 'item #' + i,
13 | col3: (Math.random() < 0.5)
14 | });
15 | }
16 | success(result);
17 | }, 100);
18 | };
19 |
20 | $scope.datasource = datasource;
21 |
22 | var cookieName = 'ui-scroll-grid-layout';
23 |
24 | var clearLayout = [
25 | {index: 0, mapTo: 0, css: {backgroundColor: ''}},
26 | {index: 1, mapTo: 1, css: {backgroundColor: ''}},
27 | {index: 2, mapTo: 2, css: {backgroundColor: ''}}
28 | ];
29 |
30 | $scope.layout = [
31 | {index: 0, mapTo: 0, css: {backgroundColor: '#eee'}},
32 | {index: 1, mapTo: 1, css: {backgroundColor: '#ddd'}},
33 | {index: 2, mapTo: 2, css: {backgroundColor: '#ccc'}}
34 | ];
35 |
36 | $scope.applyLayout = function () {
37 | $scope.adapter.gridAdapter.applyLayout($scope.layout);
38 | };
39 |
40 | $scope.clearLayout = function () {
41 | $scope.adapter.gridAdapter.applyLayout(clearLayout);
42 | };
43 |
44 | $scope.saveLayout = function () {
45 | var layout = $scope.adapter.gridAdapter.getLayout();
46 |
47 | var date = new Date();
48 | date.setTime(date.getTime() + 30 * 24 * 3600 * 1000); // 30 days
49 | document.cookie = cookieName + "=" + JSON.stringify(layout) + "; path=/;expires = " + date.toGMTString();
50 | };
51 |
52 | $scope.restoreLayout = function () {
53 | var value = "; " + document.cookie;
54 | var parts = value.split("; " + cookieName + "=");
55 | var result;
56 | if (parts.length != 2 || !(result = parts.pop().split(";").shift())) {
57 | alert('Nothing to apply');
58 | return;
59 | }
60 | $scope.layout = JSON.parse(result);
61 | $scope.applyLayout();
62 | };
63 |
64 | }
65 | ]);
66 |
--------------------------------------------------------------------------------
/demo/grid-layout-manipulations/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .grid tbody {
6 | width: 380px;
7 | }
8 |
9 | hr {
10 | margin: 5px;
11 | }
12 |
13 | .col1 {
14 | width: 80px;
15 | }
16 |
17 | td.col1 {
18 | font-style: italic;
19 | }
20 |
21 | .col2 {
22 | width: 200px;
23 | }
24 |
25 | .col3 {
26 | width: 80px;
27 | }
28 |
29 | input {
30 | margin-bottom: 5px;
31 | }
32 |
--------------------------------------------------------------------------------
/demo/grid-scopes-wrapping/grid-scopes-wrapping.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Grid scopes wrapping
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 |
Here we have ng-repeat wrapping ui-scroll-td directive!
22 |
23 |
24 |
25 |
26 |
27 | {{col}}
28 |
29 |
30 |
31 |
32 | {{item[col]}}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/grid-scopes-wrapping/grid-scopes-wrapping.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll', 'ui.scroll.grid'])
2 | .controller('gridController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | $scope.columns = ['col1','col2','col3'];
7 |
8 | datasource.get = function (index, count, success) {
9 | $timeout(function () {
10 | var result = [];
11 | for (var i = index; i <= index + count - 1; i++) {
12 | result.push({
13 | col1: i,
14 | col2: 'item #' + i,
15 | col3: (Math.random() < 0.5)
16 | });
17 | }
18 | success(result);
19 | }, 100);
20 | };
21 |
22 | $scope.datasource = datasource;
23 |
24 | var clearLayout = [
25 | {index: 0, mapTo: 0, css: {backgroundColor: ''}},
26 | {index: 1, mapTo: 1, css: {backgroundColor: ''}},
27 | {index: 2, mapTo: 2, css: {backgroundColor: ''}}
28 | ];
29 |
30 | var someLayout = [
31 | {index: 0, mapTo: 2, css: {backgroundColor: '#ccc'}},
32 | {index: 1, mapTo: 1, css: {backgroundColor: '#ddd'}},
33 | {index: 2, mapTo: 0, css: {backgroundColor: '#eee'}}
34 | ];
35 |
36 | $scope.applyLayout = function () {
37 | $scope.adapter.gridAdapter.applyLayout(someLayout);
38 | };
39 |
40 | $scope.clearLayout = function () {
41 | $scope.adapter.gridAdapter.applyLayout(clearLayout);
42 | };
43 |
44 | }
45 | ]);
46 |
--------------------------------------------------------------------------------
/demo/grid-scopes-wrapping/grid.css:
--------------------------------------------------------------------------------
1 | .grid {
2 | height: 300px;
3 | }
4 |
5 | .grid tbody {
6 | width: 380px;
7 | }
8 |
9 | hr {
10 | margin: 5px;
11 | }
12 |
13 | .col1 {
14 | width: 80px;
15 | }
16 |
17 | td.col1 {
18 | font-style: italic;
19 | }
20 |
21 | .col2 {
22 | width: 200px;
23 | }
24 |
25 | .col3 {
26 | width: 80px;
27 | }
28 |
29 | input {
30 | margin-bottom: 5px;
31 | }
32 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Scroller Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
158 |
159 |
160 |
161 |
188 |
189 |
192 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/demo/insideComponent/insideComponent.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Inside Angular 1.5+ component
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This sample demonstrates encapsulation of the ui-scroll directive inside some custom component (Angular 1.5+).
21 |
22 | To demonstrate the work of the Adapter, click on any row. The text of the item should change after click.
23 |
24 | The controller of this Component is implemented as ES6 class. Note that this demo might not work in old browsers which don't support ES6 classes.
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demo/insideComponent/insideComponent.js:
--------------------------------------------------------------------------------
1 | (function (angular) {
2 |
3 | class Ctrl {
4 | constructor($timeout, $scope) {
5 | this.timeout = $timeout;
6 | this.show = true;
7 | this.$scope = $scope;
8 | }
9 |
10 | get(index, count, success) {
11 | this.timeout(function () {
12 | var result = [];
13 | for (var i = index; i <= index + count - 1; i++) {
14 | result.push({
15 | id: i,
16 | name: "item #" + i
17 | });
18 | }
19 | success(result);
20 | }, 100);
21 | }
22 |
23 | update(id) {
24 | return this.scrollAdapter.applyUpdates(function (item) {
25 | if (item.id === id) {
26 | item.name += " *";
27 | }
28 | });
29 | }
30 | }
31 |
32 | angular
33 | .module('application', ['ui.scroll'])
34 | .component('myComponent', {
35 | controllerAs: 'ctrl',
36 | template:
37 | '
' +
38 | '
' +
39 | '
{{item.name}}
' +
40 | '
' +
41 | '
',
42 | controller: Ctrl
43 | });
44 |
45 | })(angular);
46 |
--------------------------------------------------------------------------------
/demo/insideDirective/insideDirective.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Inside directive
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This sample demonstrates encapsulation of the ui-scroll directive inside another custom directive wich has it's own controller and wich uses "Controller As" syntax in it's template.
21 | To demonstrate the work of the Adapter, click on any row. The text of the item should change after click.
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/demo/insideDirective/insideDirective.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', ['$scope', function($scope) {
3 | $scope.show = true;
4 | }])
5 | .directive('myDir', function() {
6 | return {
7 | restrict: 'E',
8 | controllerAs: 'ctrl',
9 | template:
10 | '
' +
11 | '
' +
12 | '
{{item.name}}
' +
13 | '
' +
14 | '
',
15 | controller: function ($timeout) {
16 | var ctrl = this;
17 | ctrl.show = true;
18 | ctrl.get = function(index, count, success) {
19 | $timeout(function () {
20 | var result = [];
21 | for (var i = index; i <= index + count - 1; i++) {
22 | result.push({
23 | id: i,
24 | name: "item #" + i
25 | });
26 | }
27 | success(result);
28 | }, 100);
29 | }
30 | ctrl.update = function(id) {
31 | return ctrl.scrollAdapter.applyUpdates(function(item) {
32 | if (item.id === id) {
33 | item.name += " *";
34 | }
35 | });
36 | }
37 | }
38 | }
39 | }
40 | );
41 |
--------------------------------------------------------------------------------
/demo/isLoading/isLoading.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Is loading
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | There is an optional parameter of ui-scroll directive which allows us to know whether there are any pending load requests.
21 |
22 |
23 |
<li ui-scroll="item in datasource" is-loading="loading">*{{item}}*</li>
24 |
25 |
26 | We recommend to use
isLoading property on
adapter .
27 | Please follow
this demo .
28 |
29 |
30 |
31 |
is loading: {{loading}}
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/demo/isLoading/isLoading.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
--------------------------------------------------------------------------------
/demo/isLoading/isLoadingAdapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Is loading (Adapter)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | As it follows from documentation Adapter implements isLoading property to make us know whether there are any pending load requests.
21 |
22 |
23 |
<li ui-scroll="item in datasource" adapter="adapter">*{{item}}*</li>
24 |
25 |
26 |
27 |
is loading: {{adapter.isLoading}}
28 |
29 |
30 |
31 |
32 |
is loading: {{adapter.isLoading}}
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/demo/isLoading/isLoadingAdapter.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 |
5 | $scope.adapter = {};
6 |
7 | $scope.datasource = {};
8 |
9 | $scope.datasource.get = function (index, count, success) {
10 | $timeout(function () {
11 | var result = [];
12 | for (var i = index; i <= index + count - 1; i++) {
13 | result.push("item #" + i);
14 | }
15 | success(result);
16 | }, 100);
17 | };
18 |
19 | }
20 | ]);
21 |
--------------------------------------------------------------------------------
/demo/jquery/jquery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
jQuery
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
browse other examples
17 |
18 |
19 |
20 |
21 | In case the Angular App works with jQuery, the ui-scroll should use jQuery.
22 | Otherwise, the ui-scroll should use jqLiteExtras.
23 |
24 |
25 |
26 | jQuery version is {{jqueryVersion}}
27 | jQuery is not loaded
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/demo/jquery/jquery.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 |
18 | $scope.jqueryVersion = window.jQuery && angular.element.fn && angular.element.fn.jquery;
19 |
20 | }
21 | ]);
22 |
--------------------------------------------------------------------------------
/demo/listScroller/listScroller.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
li based scrollable list
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Since html container with ui-scroll attribute is repeatable there are some ways to build a template.
21 | This sample demonstrates list based (<li>) template usage.
22 |
23 |
24 |
25 | <ul ui-scroll-viewport>
26 | <li ui-scroll="item in datasource">*{{item}}*</li>
27 | </ul>
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/demo/listScroller/listScroller.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
--------------------------------------------------------------------------------
/demo/multipleLists/multipleLists.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Independent scrollable lists
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Here we have two different viewports but single datasource.
21 |
22 |
23 |
24 |
is loading (1): {{adapter.isLoading}}
25 |
is loading (2): {{adapter2.isLoading}}
26 |
27 |
28 |
34 |
35 |
36 |
One more list
37 |
38 |
39 | *two {{item}}*
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/demo/multipleLists/multipleLists.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 | $scope.adapter = {};
18 | }
19 | ]);
20 |
--------------------------------------------------------------------------------
/demo/multipleReloadTest/multipleReload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Multiple reload test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Do single reload
21 | Do multiple reload
22 | Loading: {{adapter.isLoading}}
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/demo/multipleReloadTest/multipleReload.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 |
18 | $scope.doSingleReload = function () {
19 | $scope.adapter.reload();
20 | };
21 |
22 | $scope.doMultipleReload = function () {
23 | $scope.adapter.reload();
24 | $timeout(function () {
25 | $scope.adapter.reload();
26 | });
27 | $timeout(function () {
28 | $scope.adapter.reload();
29 | });
30 | };
31 |
32 | }
33 | ]);
34 |
--------------------------------------------------------------------------------
/demo/outOfBuffer/outOfBuffer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Adapter sync
6 |
7 |
8 |
9 |
10 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Prepend one item
30 | Append one item
31 |
32 | Remove first
33 | Remove last
34 |
35 |
36 |
37 |
38 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/demo/outOfBuffer/outOfBuffer.js:
--------------------------------------------------------------------------------
1 | var app = angular.module('application', ['ui.scroll']);
2 |
3 | app.factory('Server', [
4 | '$timeout', '$q', function ($timeout, $q) {
5 |
6 | var ServerFactory = {
7 |
8 | firstIndex: 1,
9 |
10 | lastIndex: 40,
11 |
12 | delay: 100,
13 |
14 | data: [],
15 |
16 | absIndex: 1,
17 |
18 | generateId: function () {
19 | var d = '-';
20 | function S4() {
21 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
22 | }
23 | return (S4() + S4() + d + S4() + d + S4() + d + S4() + d + S4() + S4() + S4());
24 | },
25 |
26 | generateItem: function (index) {
27 | return {
28 | index: index,
29 | id: this.generateId(),
30 | content: 'Item #' + this.absIndex++
31 | }
32 | },
33 |
34 | init: function () {
35 | for (var i = this.firstIndex; i <= this.lastIndex; i++) {
36 | this.data.push(this.generateItem(i));
37 | }
38 | },
39 |
40 | getItem: function (index) {
41 | for (var i = this.data.length - 1; i >= 0; i--) {
42 | if (this.data[i].index === index) {
43 | return this.data[i];
44 | }
45 | }
46 | },
47 |
48 | returnDeferredResult: function (result) {
49 | var deferred = $q.defer();
50 | $timeout(function () {
51 | deferred.resolve(result);
52 | }, this.delay);
53 | return deferred.promise;
54 | },
55 |
56 | request: function (index, count) {
57 | var start = index;
58 | var end = index + count - 1;
59 | var item, result = {
60 | items: []
61 | };
62 | if (start <= end) {
63 | for (var i = start; i <= end; i++) {
64 | if (item = this.getItem(i)) {
65 | result.items.push(item);
66 | }
67 | }
68 | }
69 | return this.returnDeferredResult(result);
70 | },
71 |
72 | prependItem: function (params) {
73 | var newItem = this.generateItem(--this.firstIndex);
74 | newItem.content += params;
75 | this.data.unshift(newItem);
76 | return this.returnDeferredResult(newItem);
77 | },
78 |
79 | appendItem: function (params) {
80 | var newItem = this.generateItem(++this.lastIndex);
81 | newItem.content += params;
82 | this.data.push(newItem);
83 | return this.returnDeferredResult(newItem);
84 | },
85 |
86 | removeFirst: function () {
87 | var firstItem = this.data.find(i => i.index === this.firstIndex);
88 | if(!firstItem) {
89 | return;
90 | }
91 | return this.removeItemById(firstItem.id);
92 | },
93 |
94 | removeLast: function () {
95 | var lastItem = this.data.find(i => i.index === this.lastIndex);
96 | if(!lastItem) {
97 | return;
98 | }
99 | return this.removeItemById(lastItem.id);
100 | },
101 |
102 | removeItemById: function (itemId) {
103 | var length = this.data.length;
104 | for (var i = 0; i < length; i++) {
105 | if (this.data[i].id === itemId) {
106 | var indexRemoved = this.data[i].index;
107 | this.data.splice(i, 1);
108 | this.setIndices();
109 | return this.returnDeferredResult(indexRemoved);
110 | }
111 | }
112 | return this.returnDeferredResult(false);
113 | },
114 |
115 | setIndices: function() {
116 | if(!this.data.length) {
117 | this.firstIndex = 1;
118 | this.lastIndex = 1;
119 | return;
120 | }
121 | this.firstIndex = this.data[0].index;
122 | this.lastIndex = this.data[0].index;
123 | for (var i = this.data.length - 1; i >= 0; i--) {
124 | if(this.data[i].index > this.lastIndex) {
125 | this.lastIndex = this.data[i].index;
126 | }
127 | if(this.data[i].index < this.firstIndex) {
128 | this.firstIndex = this.data[i].index;
129 | }
130 | }
131 | }
132 | };
133 |
134 | ServerFactory.init();
135 |
136 | return ServerFactory;
137 |
138 | }
139 | ]);
140 |
141 |
142 | app.controller('mainController', [
143 | '$scope', 'Server', function ($scope, Server) {
144 |
145 | var ctrl = this;
146 |
147 | ctrl.datasource = {
148 | get: function (index, count, success) {
149 | console.log('request by index = ' + index + ', count = ' + count);
150 | Server.request(index, count).then(function (result) {
151 | if (result.items) {
152 | console.log('resolved ' + result.items.length + ' items');
153 | }
154 | success(result.items);
155 | });
156 | }
157 | };
158 |
159 | $scope.$watch('adapter', (prev, next) => {
160 | console.log('The adapter has been initialized');
161 | });
162 |
163 | ctrl.prepend = function () {
164 | Server.prependItem(' ***').then(function (newItem) {
165 | if (ctrl.adapter.isBOF()) {
166 | ctrl.adapter.prepend([newItem]);
167 | }
168 | });
169 | };
170 |
171 | ctrl.append = function () {
172 | Server.appendItem(' ***').then(function (newItem) {
173 | if (ctrl.adapter.isEOF()) {
174 | ctrl.adapter.append([newItem]);
175 | }
176 | });
177 | };
178 |
179 | // todo dhilt : need to implement it properly
180 | ctrl.removeAll = function () {
181 | ctrl.adapter.applyUpdates(function (item) {
182 | if (item.id) {
183 | Server.removeItemById(item.id);
184 | return [];
185 | }
186 | });
187 | };
188 |
189 | ctrl.remove = function (itemRemove) {
190 | Server.removeItemById(itemRemove.id).then(function (result) {
191 | if (result !== false) {
192 | ctrl.adapter.applyUpdates(function (item) {
193 | if (item.id === itemRemove.id) {
194 | return [];
195 | }
196 | });
197 | }
198 | });
199 | };
200 |
201 | ctrl.removeFirst = function () {
202 | Server.removeFirst().then(function (indexRemoved) {
203 | if (indexRemoved !== false) {
204 | ctrl.adapter.applyUpdates(indexRemoved, []);
205 | }
206 | });
207 | };
208 |
209 | ctrl.removeLast = function () {
210 | Server.removeLast().then(function (indexRemoved) {
211 | if (indexRemoved !== false) {
212 | ctrl.adapter.applyUpdates(indexRemoved, []);
213 | }
214 | });
215 | };
216 | }
217 | ]);
218 |
--------------------------------------------------------------------------------
/demo/persistentScroll/persistentScroll.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Preserves the scroll position on refresh
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This sample demonstrates a functionality of top item position persistence.
21 | This persistence is based on topVisible element's index (offset) which is stored in URL ($location).
22 | So when you do some scrolls you see that 'offset' param is persisted via URL hash.
23 | Then you refresh the page and after that the $index value (and leftmost column in the example) changes,
24 | but the item which used to be on top stays on top.
25 |
26 |
27 |
28 |
top visible: {{topVisible.$index}}
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/demo/persistentScroll/persistentScroll.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', [ '$log', '$timeout', '$rootScope', '$location',
3 | function (console, $timeout, $rootScope, $location) {
4 |
5 | var offset = parseInt($location.search().offset || '0', 10);
6 |
7 | var get = function (index, count, success) {
8 | $timeout(function () {
9 | var actualIndex = index + offset;
10 | var result = [];
11 | var start = Math.max(-40, actualIndex);
12 | var end = Math.min(actualIndex + count - 1, 100);
13 | if (start <= end) {
14 | for (var i = start; i <= end; i++) {
15 | result.push("item " + i);
16 | }
17 | }
18 | success(result);
19 | }, 100);
20 | };
21 |
22 | $rootScope.$watch((function () {
23 | return $rootScope.topVisible;
24 | }), function () {
25 | if ($rootScope.topVisible) {
26 | $location.search('offset', $rootScope.topVisible.$index + offset-1);
27 | $location.replace();
28 | }
29 | });
30 |
31 | return {
32 | get: get
33 | };
34 | }
35 | ]);
--------------------------------------------------------------------------------
/demo/positionedList/positionedList.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Scroller Demo (list based)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This sample demonstrates a functionality of list positioning based on element data.
21 | There is no persistence, just enter key value in textinput (from 'aa' to 'zz') and get the key-offset positioned list.
22 |
23 |
24 |
31 |
32 |
33 |
{{$index}}: {{item}}
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/demo/positionedList/positionedList.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout', '$rootScope',
3 | function (console, $timeout, $rootScope) {
4 |
5 | $rootScope.key = "";
6 | var position = 0;
7 | var data = [];
8 | var ref1 = 'abcdefghijklmnopqrstuvwxyz';
9 | var ref2 = 'abcdefghijklmnopqrstuvwxyz';
10 |
11 | for (var j = 0; j < ref1.length; j++)
12 | for (var k = 0, letter1 = ref1[j]; k < ref2.length; k++)
13 | for (var i = 0, letter2 = ref2[k]; i <= 9; i++)
14 | data.push("" + letter1 + letter2 + ": 0" + i);
15 |
16 | var get = function (index, count, success) {
17 | return $timeout(function () {
18 | var actualIndex = index + position;
19 | var start = Math.max(0 - position, actualIndex);
20 | var end = Math.min(actualIndex + count - 1, data.length);
21 |
22 | if (start > end) {
23 | success([]);
24 | } else {
25 | success(data.slice(start, end + 1));
26 | }
27 | }, 100);
28 | };
29 |
30 | $rootScope.$watch((function () {
31 | return $rootScope.key;
32 | }), function () {
33 | position = 0;
34 | for (var m = 0; m < data.length; m++) {
35 | if ($rootScope.key > data[m]) {
36 | position++;
37 | }
38 | }
39 | if ($rootScope.key)
40 | $rootScope.adapter.reload();
41 | });
42 |
43 | return {
44 | get: get,
45 | };
46 | }
47 | ]);
48 |
--------------------------------------------------------------------------------
/demo/rebuilding/rebuilding.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Rebuilding
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This demo allows to trace memory leaks during destroy and rebuild ui-scroll component.
21 |
22 |
23 |
24 |
25 |
26 | {{switch ? 'Destroy' : 'Build'}}
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/demo/rebuilding/rebuilding.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 |
18 | $scope.switch = false;
19 | }
20 | ]);
21 |
--------------------------------------------------------------------------------
/demo/reload100/reload100.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Reload 100
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Here we provide an ability to reload the datasource to some specified position. First you need an adapter defined on your scope:
21 |
22 |
<div ui-scroll="item in datasource" adapter="myAdapter">{{item}}</div>
23 |
24 | Then just call the reload method on the adapter with index parameter:
25 |
26 |
$scope.myAdapter.reload(100);
27 |
28 |
29 |
30 |
31 | - index to reload
32 | Reload({{reloadIndex}})
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/demo/reload100/reload100.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 |
18 | $scope.doReload = function () {
19 | if (angular.isFunction($scope.adapter.reload)) {
20 | var reloadIndex = parseInt($scope.reloadIndex, 10);
21 | reloadIndex = isNaN(reloadIndex) ? 1 : reloadIndex;
22 | $scope.adapter.reload(reloadIndex);
23 | }
24 | };
25 | /*
26 | $scope.delay = false;
27 | $timeout(function() {
28 | $scope.delay = true;
29 | }, 500);
30 | */
31 | }
32 | ]);
33 |
--------------------------------------------------------------------------------
/demo/remote/remote.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Remote server emulation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 | This demo implements Remote server emulation factory, so the datasource get method looks like
19 |
20 |
21 | datasource.get = function(index, count, success) {
22 | Server.request(index, count).then(success);
23 | };
24 |
25 |
26 |
27 |
28 | {{item.content}}
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/demo/remote/remote.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 |
3 | .factory('Server', function($timeout, $q) {
4 | return {
5 |
6 | default: {
7 | first: 0,
8 | max: 99,
9 | delay: 100
10 | },
11 |
12 | data: [],
13 |
14 | init: function(settings = {}) {
15 | this.first = settings.hasOwnProperty('first') ? settings.first : this.default.first;
16 | this.max = settings.hasOwnProperty('max') ? settings.max : this.default.max;
17 | this.delay = settings.hasOwnProperty('delay') ? settings.delay : this.default.delay;
18 | for (var i = this.first; i <= this.max; i++) {
19 | this.data[i] = {
20 | index: i,
21 | content: 'Item #' + i
22 | };
23 | }
24 | },
25 |
26 | request: function(index, count) {
27 | var self = this;
28 | var deferred = $q.defer();
29 |
30 | var start = index;
31 | var end = index + count - 1;
32 |
33 | $timeout(function() {
34 | var item, result = [];
35 | if (start <= end) {
36 | for (var i = start; i <= end; i++) {
37 | if (item = self.data[i]) {
38 | result.push(item);
39 | }
40 | }
41 | }
42 | deferred.resolve(result);
43 | }, self.delay);
44 |
45 | return deferred.promise;
46 | }
47 | };
48 | })
49 |
50 | .controller('mainController', function($scope, Server) {
51 |
52 | $scope.firstIndex = 1;
53 |
54 | Server.init({
55 | first: $scope.firstIndex,
56 | max: 100,
57 | delay: 40
58 | });
59 |
60 | $scope.datasource = {
61 | get: function(index, count, success) {
62 | console.log('requested index = ' + index + ', count = ' + count);
63 | Server.request(index, count).then(function(result) {
64 | console.log('resolved ' + result.length + ' items');
65 | success(result);
66 | });
67 | }
68 | };
69 | });
--------------------------------------------------------------------------------
/demo/scopeDatasource/scopeDatasource.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Datasource on scope (not as service)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Per documentation the datasource object can be defined in two different ways.
21 |
22 |
23 |
<li ui-scroll="item in datasource">{{item}}</li>
24 |
25 |
26 | And the directive will first look for a property with the given name (datasource) on its $scope.
27 | So to use scope-approach you need to declare
datasource object on scope of your controller and define method
get on it.
28 |
29 |
30 |
31 | angular.module('application', ['ui.scroll'])
32 | .controller('mainController', ...
33 |
34 | var get = function(index, count, success) { ... };
35 |
36 | $scope.datasource = { get: get };
37 |
38 | );
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/demo/scopeDatasource/scopeDatasource.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 | }
18 | ]);
19 |
--------------------------------------------------------------------------------
/demo/scrollBubblingPrevent/scrollBubblingPrevent.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Scroller Demo (scroll bubbles up only after eof/bof)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | There are 100 elements in datasource (from -50 to +50).
21 | Scrolling of the document begins only after bof/eof is reached.
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/demo/scrollBubblingPrevent/scrollBubblingPrevent.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll']).factory('datasource', [
2 | '$log', '$timeout', function(console, $timeout) {
3 | var get, max, min;
4 | min = -50;
5 | max = 50;
6 |
7 | get = function(index, count, success) {
8 | $timeout(function() {
9 | var result = [];
10 | var start = Math.max(min, index);
11 | var end = Math.min(index + count - 1, max);
12 | if (start <= end) {
13 | for (var i = start; i <= end; i++) {
14 | result.push("item #" + i);
15 | }
16 | }
17 | success(result);
18 | }, 50);
19 | };
20 |
21 | return {
22 | get: get
23 | };
24 | }
25 | ]);
--------------------------------------------------------------------------------
/demo/serviceDatasource/serviceDatasource.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Datasource as service
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Per documentation the datasource object can be defined in two different ways.
21 |
22 |
23 |
<li ui-scroll="item in datasource">{{item}}</li>
24 |
25 |
26 | In this sample we use service-approach. Here you need to define angular service and declare method
get on it.
27 |
28 |
29 |
30 | angular.module('application', ['ui.scroll'])
31 | .factory('datasource', function() { ...
32 |
33 | var get = function(index, count, success) { ... };
34 |
35 | return { get: get };
36 | });
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/demo/serviceDatasource/serviceDatasource.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
20 |
--------------------------------------------------------------------------------
/demo/tableScroller/tableScroller.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Table based scrollable list
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | Since html container with ui-scroll attribute is repeatable there are some ways to build a template.
21 | This sample demonstrates table based (<tr>) template usage.
22 |
23 |
24 |
25 | <table ui-scroll-viewport>
26 | <tr ui-scroll="item in datasource">
27 | <td>{{$index}}</td>
28 | <td>{{item}}</td>
29 | </tr>
30 | </table>
31 |
32 |
33 | And it needed to be said that we have special logic within ui-scroll sources to support table markup.
34 |
35 |
36 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/tableScroller/tableScroller.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
20 |
--------------------------------------------------------------------------------
/demo/topVisible/topVisible.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Top visible
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | There is an optional parameter of ui-scroll directive which gives us a reference to the item currently in the topmost visible position.
21 |
22 |
23 |
<li ui-scroll="item in datasource" top-visible="topItem">*{{item}}*</li>
24 |
25 |
26 | We recommend to use
topVisible property on
adapter .
27 | Please follow
this demo .
28 |
29 |
30 |
31 |
top visible: {{topVisible}}
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/demo/topVisible/topVisible.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
--------------------------------------------------------------------------------
/demo/topVisible/topVisibleAdapter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Top visible (Adapter)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | As it follows from documentation Adapter implements topVisible property which is a reference to the item currently in the topmost visible position.
21 |
22 |
23 |
<li ui-scroll="item in datasource" adapter="adapter">*{{item}}*</li>
24 |
25 |
26 |
27 |
top visible: {{adapter.topVisible}}
28 |
29 |
30 |
31 |
32 |
top visible: {{adapter.topVisible}}
33 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/demo/topVisible/topVisibleAdapter.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 |
5 | $scope.adapter = {};
6 |
7 | $scope.datasource = {};
8 |
9 | $scope.datasource.get = function (index, count, success) {
10 | $timeout(function () {
11 | var result = [];
12 | for (var i = index; i <= index + count - 1; i++) {
13 | result.push("item #" + i);
14 | }
15 | success(result);
16 | }, 100);
17 | };
18 |
19 | }
20 | ]);
21 |
--------------------------------------------------------------------------------
/demo/ui-scroll-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/angular-ui/ui-scroll/3339d3b4ccdb7fe0de0de279047205a4fa45cd02/demo/ui-scroll-demo.gif
--------------------------------------------------------------------------------
/demo/userIndexes/userIndexes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
User min and max indexes
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
browse other examples
23 |
24 |
25 |
26 |
27 | Here we provide an ability of external min and max indexes setting to let the viewport know how many items do we have.
28 |
29 |
<li ui-scroll="item in datasource">{{item}}</li>
30 |
31 | Then you can play with minIndex and maxIndex properties which are being accessible on your datasource after the ui-scroll directive is linked:
32 |
33 |
34 | datasource.minIndex = -100;
35 | datasource.maxIndex = 100;
36 |
37 | Such setting does not not lead to data fetching but the scroll bar params do match datasource size defined this way.
38 |
39 |
40 |
41 | Set minIndex = {{userMinIndex}}
42 | minIndex value to set
43 |
44 | Set maxIndex = {{userMaxIndex}}
45 | maxIndex value to set
46 |
47 | Set both Indexes
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/demo/userIndexes/userIndexes.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .controller('mainController', [
3 | '$scope', '$log', '$timeout', function ($scope, console, $timeout) {
4 | var datasource = {};
5 |
6 | datasource.get = function (index, count, success) {
7 | $timeout(function () {
8 | var result = [];
9 | for (var i = index; i <= index + count - 1; i++) {
10 | result.push("item #" + i);
11 | }
12 | success(result);
13 | }, 100);
14 | };
15 |
16 | $scope.datasource = datasource;
17 | $scope.adapter = {};
18 |
19 | $scope.setUserMinIndex = function () {
20 | var userMinIndex = parseInt($scope.userMinIndex, 10);
21 | if(!isNaN(userMinIndex))
22 | $scope.datasource.minIndex = userMinIndex;
23 | };
24 |
25 | $scope.setUserMaxIndex = function () {
26 | var userMaxIndex = parseInt($scope.userMaxIndex, 10);
27 | if(!isNaN(userMaxIndex))
28 | $scope.datasource.maxIndex = userMaxIndex;
29 | };
30 |
31 | $scope.setUserIndexes = function () {
32 | $scope.setUserMinIndex();
33 | $scope.setUserMaxIndex();
34 | };
35 |
36 | $scope.delay = false;
37 | $timeout(function() {
38 | $scope.delay = true;
39 | }, 500);
40 |
41 | }
42 | ]);
--------------------------------------------------------------------------------
/demo/visibility/visibility.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Visibility
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | We have an internal mechanism to start data fetching after invisible scroller becomes visible.
21 | So generally you don't need to execute reload method when you play with scroller visibility.
22 |
23 |
24 |
25 |
26 |
27 | Visible {{visible}}
28 |
29 |
30 |
31 |
32 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/demo/visibility/visibility.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
--------------------------------------------------------------------------------
/demo/windowViewport/windowViewport-iframe.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Scroller Demo (entire window)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
is loading: {{loading}}
17 |
top visible: {{topItem}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 | {{item}}
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demo/windowViewport/windowViewport.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Entire window scrollable
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
browse other examples
16 |
17 |
18 |
19 |
20 | This sample demonstrates the scroller without viewport defining.
21 | As it follows from documentation:
22 | unless specified explicitly with the uiScrollViewport directive, browser window will be used as viewport.
23 | And this is the reason why this demo is placed in an iframe. You also can open it in a new tab.
24 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/demo/windowViewport/windowViewport.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll'])
2 | .factory('datasource', ['$log', '$timeout',
3 | function (console, $timeout) {
4 |
5 | var get = function (index, count, success) {
6 | $timeout(function () {
7 | var result = [];
8 | for (var i = index; i <= index + count - 1; i++) {
9 | result.push("item #" + i);
10 | }
11 | success(result);
12 | }, 100);
13 | };
14 |
15 | return {
16 | get: get
17 | };
18 | }
19 | ]);
20 |
--------------------------------------------------------------------------------
/demo/windowviewportInline/windowviewportInline.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Inline blocks demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
73 |
74 |
75 |
browse other examples
76 |
77 |
78 |
79 |
is loading: {{loading}}
80 |
top visible: {{topItem}}
81 |
82 |
83 |
84 |
85 |
86 | *{{item.content}}*
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/demo/windowviewportInline/windowviewportInline.js:
--------------------------------------------------------------------------------
1 | angular.module('application', ['ui.scroll']).factory('datasource', [
2 | '$log', '$timeout', function(console, $timeout) {
3 | var get;
4 | get = function(index, count, success) {
5 | return $timeout(function() {
6 | var i, item, j, ref, ref1, result;
7 | result = [];
8 | for (i = j = ref = index, ref1 = index + count - 1; ref <= ref1 ? j <= ref1 : j >= ref1; i = ref <= ref1 ? ++j : --j) {
9 | item = {};
10 | if (inlineDemo) {
11 | item.width = inlineDemo.getWidth(i);
12 | item.height = inlineDemo.getHeight(i);
13 | item.color = inlineDemo.getColor(i);
14 | }
15 | item.content = "item #" + i;
16 | result.push(item);
17 | }
18 | return success(result);
19 | }, 100);
20 | };
21 | return {
22 | get: get
23 | };
24 | }
25 | ]);
26 |
27 |
28 |
29 |
30 | /*
31 | //# sourceURL=src/windowviewportInline.js
32 | */
33 |
34 | // ---
35 | // generated by coffee-script 1.9.2
--------------------------------------------------------------------------------
/dist/ui-scroll-grid.min.js:
--------------------------------------------------------------------------------
1 | angular.module("ui.scroll.grid",[]).directive("uiScrollTh",["$log","$timeout",function(t,n){function r(t){this.getLayout=function(){return t.getLayout()},this.applyLayout=function(n){return t.applyLayout(n)},this.columnFromPoint=function(n,r){return t.columnFromPoint(n,r)},Object.defineProperty(this,"columns",{get:function(){return t.getColumns()}})}function o(t,n){this.css=function(){var r=arguments[0],o=arguments[1];if(1==arguments.length)return n.header.css(r);2==arguments.length&&(n.header.css(r,o),t.forEachRow((function(t){return t[n.id].css(r,o)})),n.css[r]=o)},this.moveBefore=function(r){return t.moveBefore(n,r)},this.exchangeWith=function(r){return t.exchangeWith(n,r)},Object.defineProperty(this,"columnId",{get:function(){return n.id}})}function e(t,n,r){function o(t,n,r){var o=t.offset();return!(n
=u.length||(r.push(n),0))},this.unregisterCell=function(t,n){var r=c.get(t),o=r.indexOf(n);r.splice(o,1),r.length||c.delete(t)},this.forEachRow=function(t){c.forEach(t)},this.getColumns=function(){var t=this,n=[];return u.slice().sort((function(t,n){return t.mapTo-n.mapTo})).forEach((function(r){return n.push(new o(t,r))})),n},this.getLayout=function(){var t=[];return u.forEach((function(n,r){return t.push({index:r,css:angular.extend({},n.css),mapTo:n.mapTo})})),t},this.applyLayout=function(t){if(!t||t.length!=u.length)throw new Error("Failed to apply layout - number of layouts should match number of columns");t.forEach((function(t,n){return u[n].applyLayout(t)})),a(u.map((function(t){return t.header}))),c.forEach((function(t){return a(t)}))},this.moveBefore=function(t,n){var r=n;if(n%1!=0&&(r=n?u[n.columnId].mapTo:u.length),!(r<0||r>u.length)){var o=t.mapTo,e=null;r-=oo?1:0,t.mapTo+=t.mapTo>=r?1:0,e=t.mapTo===r+1?t:e})),t.mapTo=r,t.moveBefore(e)}},this.exchangeWith=function(t,n){n<0||n>=u.length||(u.find((function(t){return t.mapTo===n})).mapTo=t.mapTo,t.mapTo=n)},this.columnFromPoint=function(t,n){var r=u.find((function(r){return r.columnFromPoint(t,n)}));return r?new o(this,r):void 0}}return{require:["^^uiScrollViewport"],restrict:"A",link:function(t,n,r,o){o[0].gridController=o[0].gridController||new i(o[0]),o[0].gridController.registerColumn(n)}}}]).directive("uiScrollTd",(function(){return{require:["?^^uiScrollViewport"],restrict:"A",link:function(t,n,r,o){if(o[0]){var e=t,i=t.uiScrollTdInitializer;i||(i=t.uiScrollTdInitializer={linking:!0}),i.linking||(e=i.scope);var u=o[0].gridController;u.registerCell(e,n)&&t.$on("$destroy",(function(){return u.unregisterCell(e,n)})),i.linking||i.onLink()}}}}));
2 | //# sourceMappingURL=ui-scroll-grid.min.js.map
--------------------------------------------------------------------------------
/dist/ui-scroll-jqlite.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * angular-ui-scroll
3 | * https://github.com/angular-ui/ui-scroll.git
4 | * This module is deprecated since 1.6.0 and will be removed in future versions!
5 | * License: MIT
6 | */
7 | (function () {
8 | 'use strict';
9 |
10 | angular.module('ui.scroll.jqlite', []);
11 |
12 | })();
--------------------------------------------------------------------------------
/dist/ui-scroll-jqlite.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";angular.module("ui.scroll.jqlite",[])}();
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-scroll",
3 | "description": "AngularJS virtual scrolling module",
4 | "version": "1.9.1",
5 | "src": "./src/",
6 | "public": "./dist/",
7 | "main": "./dist/ui-scroll.js",
8 | "homepage": "https://github.com/angular-ui/ui-scroll",
9 | "author": {
10 | "name": "Michael Feingold",
11 | "email": "mfeingold@hill30.com"
12 | },
13 | "maintainers": [
14 | {
15 | "name": "Denis Hilt",
16 | "web": "https://github.com/dhilt"
17 | }
18 | ],
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/angular-ui/ui-scroll.git"
22 | },
23 | "bugs": {
24 | "url": "https://github.com/angular-ui/ui-scroll/issues"
25 | },
26 | "license": "MIT",
27 | "scripts": {
28 | "prod-build": "webpack --color",
29 | "prod-test": "karma start test/config/karma.conf.js",
30 | "dev-server": "webpack-dev-server --color",
31 | "dev-test": "karma start test/config/karma.conf.js",
32 | "hint-test": "jshint --verbose test",
33 | "hint-src": "jshint --verbose src",
34 | "build": "npm run hint-src && npm run prod-build && npm run hint-test && npm run prod-test",
35 | "dev": "npm-run-all --parallel dev-server dev-test",
36 | "test": "npm run dev-test",
37 | "start": "npm run dev-server"
38 | },
39 | "peerDependencies": {
40 | "angular": ">=1.2.0"
41 | },
42 | "devDependencies": {
43 | "@babel/core": "^7.21.4",
44 | "@babel/preset-env": "^7.21.4",
45 | "babel-loader": "^9.1.2",
46 | "clean-webpack-plugin": "^4.0.0",
47 | "copy-webpack-plugin": "^11.0.0",
48 | "jasmine-core": "^4.6.0",
49 | "jshint": "^2.13.6",
50 | "karma": "^6.4.1",
51 | "karma-chrome-launcher": "^3.1.1",
52 | "karma-firefox-launcher": "^2.1.2",
53 | "karma-jasmine": "^5.1.0",
54 | "karma-sourcemap-loader": "^0.4.0",
55 | "karma-webpack": "^5.0.0",
56 | "npm-run-all": "^4.1.5",
57 | "puppeteer": "^19.9.1",
58 | "terser-webpack-plugin": "^5.3.7",
59 | "webpack": "^5.79.0",
60 | "webpack-cli": "^5.0.1",
61 | "webpack-dev-server": "^4.13.3"
62 | },
63 | "keywords": [
64 | "angular",
65 | "angularjs",
66 | "angular.ui",
67 | "angular-ui",
68 | "ui.scroll",
69 | "ui-scroll",
70 | "angular-ui-scroll",
71 | "virtual",
72 | "unlimited",
73 | "infinite",
74 | "live",
75 | "perpetual",
76 | "scroll",
77 | "scroller",
78 | "scrolling"
79 | ]
80 | }
--------------------------------------------------------------------------------
/src/modules/buffer.js:
--------------------------------------------------------------------------------
1 | import { OPERATIONS } from './utils';
2 |
3 | export default function ScrollBuffer(elementRoutines, bufferSize, startIndex) {
4 | const buffer = Object.create(Array.prototype);
5 |
6 | angular.extend(buffer, {
7 | size: bufferSize,
8 |
9 | reset(startIndex) {
10 | buffer.remove(0, buffer.length);
11 | buffer.eof = false;
12 | buffer.bof = false;
13 | buffer.first = startIndex;
14 | buffer.next = startIndex;
15 | buffer.minIndex = startIndex;
16 | buffer.maxIndex = startIndex;
17 | buffer.minIndexUser = null;
18 | buffer.maxIndexUser = null;
19 | },
20 |
21 | append(items) {
22 | items.forEach((item) => {
23 | ++buffer.next;
24 | buffer.insert(OPERATIONS.APPEND, item);
25 | });
26 | buffer.maxIndex = buffer.eof ? buffer.next - 1 : Math.max(buffer.next - 1, buffer.maxIndex);
27 | },
28 |
29 | prepend(items, immutableTop) {
30 | items.reverse().forEach((item) => {
31 | if (immutableTop) {
32 | ++buffer.next;
33 | }
34 | else {
35 | --buffer.first;
36 | }
37 | buffer.insert(OPERATIONS.PREPEND, item);
38 | });
39 | buffer.minIndex = buffer.bof ? buffer.minIndex = buffer.first : Math.min(buffer.first, buffer.minIndex);
40 | },
41 |
42 | /**
43 | * inserts wrapped element in the buffer
44 | * the first argument is either operation keyword (see below) or a number for operation 'insert'
45 | * for insert the number is the index for the buffer element the new one have to be inserted after
46 | * operations: 'append', 'prepend', 'insert', 'remove', 'none'
47 | */
48 | insert(operation, item, shiftTop) {
49 | const wrapper = {
50 | item: item
51 | };
52 |
53 | if (operation % 1 === 0) { // it is an insert
54 | wrapper.op = OPERATIONS.INSERT;
55 | buffer.splice(operation, 0, wrapper);
56 | if (shiftTop) {
57 | buffer.first--;
58 | }
59 | else {
60 | buffer.next++;
61 | }
62 | } else {
63 | wrapper.op = operation;
64 | switch (operation) {
65 | case OPERATIONS.APPEND:
66 | buffer.push(wrapper);
67 | break;
68 | case OPERATIONS.PREPEND:
69 | buffer.unshift(wrapper);
70 | break;
71 | }
72 | }
73 | },
74 |
75 | // removes elements from buffer
76 | remove(arg1, arg2) {
77 | if (angular.isNumber(arg1)) {
78 | // removes items from arg1 (including) through arg2 (excluding)
79 | for (let i = arg1; i < arg2; i++) {
80 | elementRoutines.removeElement(buffer[i]);
81 | }
82 | return buffer.splice(arg1, arg2 - arg1);
83 | }
84 | // removes single item (wrapper) from the buffer
85 | buffer.splice(buffer.indexOf(arg1), 1);
86 | if (arg1.shiftTop && buffer.first === this.getAbsMinIndex()) {
87 | this.incrementMinIndex();
88 | }
89 | else {
90 | this.decrementMaxIndex();
91 | }
92 | if (arg1.shiftTop) {
93 | buffer.first++;
94 | }
95 | else {
96 | buffer.next--;
97 | }
98 | if (!buffer.length) {
99 | buffer.minIndex = Math.min(buffer.maxIndex, buffer.minIndex);
100 | }
101 |
102 | return elementRoutines.removeElementAnimated(arg1);
103 | },
104 |
105 | incrementMinIndex() {
106 | if (buffer.minIndexUser !== null) {
107 | if (buffer.minIndex > buffer.minIndexUser) {
108 | buffer.minIndexUser++;
109 | return;
110 | }
111 | if (buffer.minIndex === buffer.minIndexUser) {
112 | buffer.minIndexUser++;
113 | }
114 | }
115 | buffer.minIndex++;
116 | },
117 |
118 | decrementMaxIndex() {
119 | if (buffer.maxIndexUser !== null && buffer.maxIndex <= buffer.maxIndexUser) {
120 | buffer.maxIndexUser--;
121 | }
122 | buffer.maxIndex--;
123 | },
124 |
125 | getAbsMinIndex() {
126 | if (buffer.minIndexUser !== null) {
127 | return Math.min(buffer.minIndexUser, buffer.minIndex);
128 | }
129 | return buffer.minIndex;
130 | },
131 |
132 | getAbsMaxIndex() {
133 | if (buffer.maxIndexUser !== null) {
134 | return Math.max(buffer.maxIndexUser, buffer.maxIndex);
135 | }
136 | return buffer.maxIndex;
137 | },
138 |
139 | effectiveHeight(elements) {
140 | if (!elements.length) {
141 | return 0;
142 | }
143 | let top = Number.MAX_VALUE;
144 | let bottom = Number.NEGATIVE_INFINITY;
145 | elements.forEach((wrapper) => {
146 | if (wrapper.element[0].offsetParent) {
147 | // element style is not display:none
148 | top = Math.min(top, wrapper.element.offset().top);
149 | bottom = Math.max(bottom, wrapper.element.offset().top + wrapper.element.outerHeight(true));
150 | }
151 | });
152 | return Math.max(0, bottom - top);
153 | },
154 |
155 | getItems() {
156 | return buffer.filter(item => item.op === OPERATIONS.NONE);
157 | },
158 |
159 | getFirstItem() {
160 | const list = buffer.getItems();
161 | if (!list.length) {
162 | return null;
163 | }
164 | return list[0].item;
165 | },
166 |
167 | getLastItem() {
168 | const list = buffer.getItems();
169 | if (!list.length) {
170 | return null;
171 | }
172 | return list[list.length - 1].item;
173 | }
174 |
175 | });
176 |
177 | buffer.reset(startIndex);
178 |
179 | return buffer;
180 | }
181 |
--------------------------------------------------------------------------------
/src/modules/elementRoutines.js:
--------------------------------------------------------------------------------
1 | const hideClassToken = 'ng-ui-scroll-hide';
2 |
3 | export default class ElementRoutines {
4 |
5 | static addCSSRules() {
6 | const selector = '.' + hideClassToken;
7 | const rules = 'display: none';
8 | const sheet = document.styleSheets[0];
9 | let index;
10 | try {
11 | index = sheet.cssRules.length;
12 | } catch (err) {
13 | index = 0;
14 | }
15 | if('insertRule' in sheet) {
16 | sheet.insertRule(selector + '{' + rules + '}', index);
17 | }
18 | else if('addRule' in sheet) {
19 | sheet.addRule(selector, rules, index);
20 | }
21 | }
22 |
23 | constructor($injector, $q) {
24 | this.$animate = ($injector.has && $injector.has('$animate')) ? $injector.get('$animate') : null;
25 | this.isAngularVersionLessThen1_3 = angular.version.major === 1 && angular.version.minor < 3;
26 | this.$q = $q;
27 | }
28 |
29 | hideElement(wrapper) {
30 | wrapper.element.addClass(hideClassToken);
31 | }
32 |
33 | showElement(wrapper) {
34 | wrapper.element.removeClass(hideClassToken);
35 | }
36 |
37 | insertElement(newElement, previousElement) {
38 | previousElement.after(newElement);
39 | return [];
40 | }
41 |
42 | removeElement(wrapper) {
43 | wrapper.element.remove();
44 | wrapper.scope.$destroy();
45 | return [];
46 | }
47 |
48 | insertElementAnimated(newElement, previousElement) {
49 | if (!this.$animate) {
50 | return this.insertElement(newElement, previousElement);
51 | }
52 |
53 | if (this.isAngularVersionLessThen1_3) {
54 | const deferred = this.$q.defer();
55 | // no need for parent - previous element is never null
56 | this.$animate.enter(newElement, null, previousElement, () => deferred.resolve());
57 |
58 | return [deferred.promise];
59 | }
60 |
61 | // no need for parent - previous element is never null
62 | return [this.$animate.enter(newElement, null, previousElement)];
63 | }
64 |
65 | removeElementAnimated(wrapper) {
66 | if (!this.$animate) {
67 | return this.removeElement(wrapper);
68 | }
69 |
70 | if (this.isAngularVersionLessThen1_3) {
71 | const deferred = this.$q.defer();
72 | this.$animate.leave(wrapper.element, () => {
73 | wrapper.scope.$destroy();
74 | return deferred.resolve();
75 | });
76 |
77 | return [deferred.promise];
78 | }
79 |
80 | return [(this.$animate.leave(wrapper.element)).then(() => wrapper.scope.$destroy())];
81 | }
82 | }
--------------------------------------------------------------------------------
/src/modules/padding.js:
--------------------------------------------------------------------------------
1 | // Can't just extend the Array, due to Babel does not support built-in classes extending
2 | // This solution was taken from https://stackoverflow.com/questions/46897414/es6-class-extends-array-workaround-for-es5-babel-transpile
3 | class CacheProto {
4 | add(item) {
5 | for (let i = this.length - 1; i >= 0; i--) {
6 | if (this[i].index === item.scope.$index) {
7 | this[i].height = item.element.outerHeight();
8 | return;
9 | }
10 | }
11 | this.push({
12 | index: item.scope.$index,
13 | height: item.element.outerHeight()
14 | });
15 | this.sort((a, b) => ((a.index < b.index) ? -1 : ((a.index > b.index) ? 1 : 0)));
16 | }
17 |
18 | remove(argument, _shiftTop) {
19 | const index = argument % 1 === 0 ? argument : argument.scope.$index;
20 | const shiftTop = argument % 1 === 0 ? _shiftTop : argument.shiftTop;
21 | for (let i = this.length - 1; i >= 0; i--) {
22 | if (this[i].index === index) {
23 | this.splice(i, 1);
24 | break;
25 | }
26 | }
27 | if (!shiftTop) {
28 | for (let i = this.length - 1; i >= 0; i--) {
29 | if (this[i].index > index) {
30 | this[i].index--;
31 | }
32 | }
33 | }
34 | }
35 |
36 | clear() {
37 | this.length = 0;
38 | }
39 | }
40 |
41 | function Cache() {
42 | const instance = [];
43 | instance.push.apply(instance, arguments);
44 | Object.setPrototypeOf(instance, Cache.prototype);
45 | return instance;
46 | }
47 | Cache.prototype = Object.create(Array.prototype);
48 | Object.getOwnPropertyNames(CacheProto.prototype).forEach(methodName =>
49 | Cache.prototype[methodName] = CacheProto.prototype[methodName]
50 | );
51 |
52 | function generateElement(template) {
53 | if (template.nodeType !== Node.ELEMENT_NODE) {
54 | throw new Error('ui-scroll directive requires an Element node for templating the view');
55 | }
56 | let element;
57 | switch (template.tagName.toLowerCase()) {
58 | case 'dl':
59 | throw new Error(`ui-scroll directive does not support <${template.tagName}> as a repeating tag: ${template.outerHTML}`);
60 | case 'tr':
61 | let table = angular.element('');
62 | element = table.find('tr');
63 | break;
64 | case 'li':
65 | element = angular.element(' ');
66 | break;
67 | default:
68 | element = angular.element('
');
69 | }
70 | return element;
71 | }
72 |
73 | class Padding {
74 | constructor(template) {
75 | this.element = generateElement(template);
76 | this.cache = new Cache();
77 | }
78 |
79 | height() {
80 | return this.element.height.apply(this.element, arguments);
81 | }
82 | }
83 |
84 | export default Padding;
--------------------------------------------------------------------------------
/src/modules/utils.js:
--------------------------------------------------------------------------------
1 | export const OPERATIONS = {
2 | PREPEND: 'prepend',
3 | APPEND: 'append',
4 | INSERT: 'insert',
5 | REMOVE: 'remove',
6 | NONE: 'none'
7 | };
8 |
--------------------------------------------------------------------------------
/src/ui-scroll-jqlite.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * angular-ui-scroll
3 | * https://github.com/angular-ui/ui-scroll.git
4 | * This module is deprecated since 1.6.0 and will be removed in future versions!
5 | * License: MIT
6 | */
7 | (function () {
8 | 'use strict';
9 |
10 | angular.module('ui.scroll.jqlite', []);
11 |
12 | })();
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "curly": true,
4 | "browser": true,
5 | "eqeqeq": true,
6 | "expr": true,
7 | "esversion": 9,
8 | "forin": true,
9 | "freeze": true,
10 | "futurehostile": true,
11 | "iterator": true,
12 | "jasmine": true,
13 | "jquery": true,
14 | "latedef": true,
15 | "noarg": true,
16 | "nocomma": true,
17 | "node": true,
18 | "nonbsp": true,
19 | "nonew": true,
20 | "smarttabs": true,
21 | "strict": true,
22 | "sub": true,
23 | "trailing": true,
24 | "undef": true,
25 | "unused": true,
26 | "globals": {
27 | "angular": false,
28 | "inject": false,
29 | "runTest": true,
30 | "runGridTest": false,
31 | "Helper": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/BasicSetupSpec.js:
--------------------------------------------------------------------------------
1 | describe('uiScroll', function() {
2 | 'use strict';
3 |
4 | beforeEach(module('ui.scroll'));
5 | beforeEach(module('ui.scroll.test.datasources'));
6 |
7 | describe('basic setup', function() {
8 |
9 | it('should throw an error if the template\'s wrong', function() {
10 | runTest({ datasource: 'myEmptyDatasource', extra: 'ng-if="false"' }, null, {
11 | catch: function(error) {
12 | expect(error.message).toBe('ui-scroll directive requires an Element node for templating the view');
13 | }
14 | }
15 | );
16 | });
17 |
18 | var scrollSettings = { datasource: 'myEmptyDatasource' };
19 |
20 | it('should bind to window scroll and resize events and unbind them after the scope is destroyed', function() {
21 | spyOn($.fn, 'on').and.callThrough();
22 | spyOn($.fn, 'off').and.callThrough();
23 | runTest(scrollSettings,
24 | function(viewport) {
25 | expect($.fn.on.calls.all().length).toBe(4);
26 | expect($.fn.on.calls.all()[0].args[0]).toBe('visibilitychange');
27 | expect($.fn.on.calls.all()[1].args[0]).toBe('mousewheel');
28 | expect($.fn.on.calls.all()[1].object[0]).toBe(viewport[0]);
29 | expect($.fn.on.calls.all()[2].args[0]).toBe('resize');
30 | expect($.fn.on.calls.all()[2].object[0]).toBe(viewport[0]);
31 | expect($.fn.on.calls.all()[3].args[0]).toBe('scroll');
32 | expect($.fn.on.calls.all()[3].object[0]).toBe(viewport[0]);
33 | }, {
34 | cleanupTest: function(viewport, scope, $timeout) {
35 | $timeout(function() {
36 | expect($.fn.off.calls.all().length).toBe(4);
37 | expect($.fn.off.calls.all()[0].args[0]).toBe('visibilitychange');
38 | expect($.fn.off.calls.all()[1].args[0]).toBe('resize');
39 | expect($.fn.off.calls.all()[1].object[0]).toBe(viewport[0]);
40 | expect($.fn.off.calls.all()[2].args[0]).toBe('scroll');
41 | expect($.fn.off.calls.all()[2].object[0]).toBe(viewport[0]);
42 | expect($.fn.off.calls.all()[3].args[0]).toBe('mousewheel');
43 | expect($.fn.off.calls.all()[3].object[0]).toBe(viewport[0]);
44 | });
45 | }
46 | }
47 | );
48 | });
49 |
50 | it('should create 2 divs of 0 height', function() {
51 | runTest(scrollSettings,
52 | function(viewport) {
53 | expect(viewport.children().length).toBe(2);
54 |
55 | var topPadding = viewport.children()[0];
56 | expect(topPadding.tagName.toLowerCase()).toBe('div');
57 | expect(Helper.getTopPadding(viewport)).toBe(0);
58 |
59 | var bottomPadding = viewport.children()[1];
60 | expect(bottomPadding.tagName.toLowerCase()).toBe('div');
61 | expect(Helper.getBottomPadding(viewport)).toBe(0);
62 | }
63 | );
64 | });
65 |
66 | it('should call get on the datasource 2 times ', function() {
67 | var spy;
68 | inject(function(myEmptyDatasource) {
69 | spy = spyOn(myEmptyDatasource, 'get').and.callThrough();
70 | });
71 | runTest(scrollSettings,
72 | function() {
73 | expect(spy.calls.all().length).toBe(2);
74 | expect(spy.calls.all()[0].args.length).toBe(3);
75 | expect(spy.calls.all()[0].args[0]).toBe(1);
76 | expect(spy.calls.all()[0].args[1]).toBe(10);
77 | expect(spy.calls.all()[1].args.length).toBe(3);
78 | expect(spy.calls.all()[1].args[0]).toBe(-9);
79 | expect(spy.calls.all()[1].args[1]).toBe(10);
80 | }
81 | );
82 | });
83 | });
84 |
85 | describe('basic setup (new datasource get signature)', function() {
86 | var scrollSettings = { datasource: 'myDescriptoEmptyDatasource' };
87 |
88 | it('should call get on the datasource 2 times ', function() {
89 | var spy;
90 | inject(function(myDescriptoEmptyDatasource) {
91 | spy = spyOn(myDescriptoEmptyDatasource, 'get').and.callThrough();
92 | });
93 | runTest(scrollSettings,
94 | function() {
95 | expect(spy.calls.all().length).toBe(2);
96 | expect(spy.calls.all()[0].args.length).toBe(2);
97 | expect(spy.calls.all()[0].args[0].index).toBe(1);
98 | expect(spy.calls.all()[0].args[0].count).toBe(10);
99 | expect('append' in spy.calls.all()[0].args[0]).toBe(true);
100 | expect(spy.calls.all()[0].args[0].append).toBeUndefined();
101 | expect('prepend' in spy.calls.all()[0].args[0]).toBe(false);
102 | expect(spy.calls.all()[1].args.length).toBe(2);
103 | expect(spy.calls.all()[1].args[0].index).toBe(-9);
104 | expect(spy.calls.all()[1].args[0].count).toBe(10);
105 | expect('append' in spy.calls.all()[1].args[0]).toBe(false);
106 | expect('prepend' in spy.calls.all()[1].args[0]).toBe(true);
107 | expect(spy.calls.all()[1].args[0].prepend).toBeUndefined();
108 | }
109 | );
110 | });
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/test/BufferCleanupSpec.js:
--------------------------------------------------------------------------------
1 | describe('uiScroll', () => {
2 | 'use strict';
3 |
4 | let datasource;
5 | beforeEach(module('ui.scroll'));
6 | beforeEach(module('ui.scroll.test.datasources'));
7 |
8 | const injectDatasource = datasourceToken =>
9 | beforeEach(
10 | inject([datasourceToken, _datasource =>
11 | datasource = _datasource
12 | ])
13 | );
14 |
15 | describe('buffer cleanup', () => {
16 | const settings = {
17 | datasource: 'myEdgeDatasource', // items range is [-5..6]
18 | adapter: 'adapter',
19 | viewportHeight: 60,
20 | itemHeight: 20,
21 | padding: 0.3,
22 | startIndex: 3,
23 | bufferSize: 3
24 | };
25 |
26 | injectDatasource('myEdgeDatasource');
27 |
28 | const cleanBuffer = (scope, applyUpdateOptions) => {
29 | const get = datasource.get;
30 | const removedItems = [];
31 | // sync the datasource
32 | datasource.get = (index, count, success) => {
33 | if (removedItems.indexOf('item' + index) !== -1) {
34 | index += removedItems.length;
35 | }
36 | get(index, count, success);
37 | };
38 | // clean up the buffer
39 | scope.adapter.applyUpdates(item => {
40 | removedItems.push(item);
41 | return [];
42 | }, applyUpdateOptions);
43 | };
44 |
45 | const shouldWorkWhenEOF = (viewport, scope, options) => {
46 | expect(scope.adapter.isBOF()).toBe(false);
47 | expect(scope.adapter.isEOF()).toBe(true);
48 | expect(scope.adapter.bufferFirst).toBe('item0');
49 | expect(scope.adapter.bufferLast).toBe('item6');
50 |
51 | // remove items 0..6 items form -5..6 datasource
52 | cleanBuffer(scope, options);
53 |
54 | // result [-5..-1]
55 | expect(scope.adapter.isBOF()).toBe(true);
56 | expect(scope.adapter.isEOF()).toBe(true);
57 | expect(Helper.getRow(viewport, 1)).toBe('-5: item-5');
58 | expect(Helper.getRow(viewport, 2)).toBe('-4: item-4');
59 | expect(Helper.getRow(viewport, 3)).toBe('-3: item-3');
60 | expect(Helper.getRow(viewport, 4)).toBe('-2: item-2');
61 | expect(Helper.getRow(viewport, 5)).toBe('-1: item-1');
62 | expect(scope.adapter.bufferLength).toBe(5);
63 | };
64 |
65 | it('should be consistent on forward direction when eof with immutabeTop', () =>
66 | runTest(settings, (viewport, scope) =>
67 | shouldWorkWhenEOF(viewport, scope, { immutableTop: true })
68 | )
69 | );
70 |
71 | it('should be consistent on forward direction when eof without immutabeTop', () =>
72 | runTest(settings, (viewport, scope) =>
73 | shouldWorkWhenEOF(viewport, scope)
74 | )
75 | );
76 |
77 | const shouldWorkWhenNotEOF = (viewport, scope, options) => {
78 | expect(scope.adapter.isBOF()).toBe(false);
79 | expect(scope.adapter.isEOF()).toBe(false);
80 | expect(scope.adapter.bufferFirst).toBe('item-4');
81 | expect(scope.adapter.bufferLast).toBe('item1');
82 |
83 | // remove items -4..1 items form -5..6 datasource
84 | cleanBuffer(scope, options);
85 |
86 | // result [-5, 2, 3, 4]
87 | expect(scope.adapter.isBOF()).toBe(true);
88 | expect(scope.adapter.isEOF()).toBe(false);
89 | expect(Helper.getRow(viewport, 1)).toBe('-5: item-5');
90 | expect(Helper.getRow(viewport, 2)).toBe('-4: item2');
91 | expect(Helper.getRow(viewport, 3)).toBe('-3: item3');
92 | expect(Helper.getRow(viewport, 4)).toBe('-2: item4');
93 | expect(scope.adapter.bufferLength).toBe(4);
94 | };
95 |
96 | it('should be consistent on forward direction when not eof with immutabeTop', () =>
97 | runTest({
98 | ...settings,
99 | startIndex: -1,
100 | viewportHeight: 40
101 | }, (viewport, scope) =>
102 | shouldWorkWhenNotEOF(viewport, scope, { immutableTop: true })
103 | )
104 | );
105 |
106 | it('should be consistent on forward direction when not eof without immutabeTop', () =>
107 | runTest({
108 | ...settings,
109 | startIndex: -1,
110 | viewportHeight: 40
111 | }, (viewport, scope) =>
112 | shouldWorkWhenNotEOF(viewport, scope)
113 | )
114 | );
115 |
116 | it('should be consistent on backward direction when bof with immutableTop', () =>
117 | runTest({
118 | ...settings,
119 | startIndex: -3,
120 | padding: 0.5
121 | }, (viewport, scope) => {
122 | expect(scope.adapter.isBOF()).toBe(true);
123 | expect(scope.adapter.isEOF()).toBe(false);
124 | expect(scope.adapter.bufferFirst).toBe('item-5');
125 | expect(scope.adapter.bufferLast).toBe('item1');
126 |
127 | // remove items -5..1 items form -5..6 datasource
128 | cleanBuffer(scope, { immutableTop: true });
129 |
130 | // result [2..6]
131 | expect(scope.adapter.isBOF()).toBe(true);
132 | expect(scope.adapter.isEOF()).toBe(true);
133 | expect(Helper.getRow(viewport, 1)).toBe('-5: item2');
134 | expect(Helper.getRow(viewport, 2)).toBe('-4: item3');
135 | expect(Helper.getRow(viewport, 3)).toBe('-3: item4');
136 | expect(Helper.getRow(viewport, 4)).toBe('-2: item5');
137 | expect(Helper.getRow(viewport, 5)).toBe('-1: item6');
138 | expect(scope.adapter.bufferLength).toBe(5);
139 | })
140 | );
141 |
142 | it('should be consistent on backward direction when bof without immutableTop', () =>
143 | runTest({
144 | ...settings,
145 | startIndex: -3,
146 | padding: 0.5
147 | }, (viewport, scope) => {
148 | expect(scope.adapter.isBOF()).toBe(true);
149 | expect(scope.adapter.isEOF()).toBe(false);
150 | expect(scope.adapter.bufferFirst).toBe('item-5');
151 | expect(scope.adapter.bufferLast).toBe('item1');
152 |
153 | // remove items -5..1 items form -5..6 datasource
154 | cleanBuffer(scope);
155 |
156 | // result [2..6]
157 | expect(scope.adapter.isBOF()).toBe(true);
158 | expect(scope.adapter.isEOF()).toBe(true);
159 | expect(Helper.getRow(viewport, 1)).toBe('2: item2');
160 | expect(Helper.getRow(viewport, 2)).toBe('3: item3');
161 | expect(Helper.getRow(viewport, 3)).toBe('4: item4');
162 | expect(Helper.getRow(viewport, 4)).toBe('5: item5');
163 | expect(Helper.getRow(viewport, 5)).toBe('6: item6');
164 | expect(scope.adapter.bufferLength).toBe(5);
165 | })
166 | );
167 |
168 | const shouldWorkWhenNotBOF = (viewport, scope, options) => {
169 | expect(scope.adapter.isBOF()).toBe(false);
170 | expect(scope.adapter.isEOF()).toBe(false);
171 | expect(scope.adapter.bufferFirst).toBe('item-4');
172 | expect(scope.adapter.bufferLast).toBe('item2');
173 |
174 | // remove items -4..2 items form -5..6 datasource
175 | cleanBuffer(scope, options);
176 |
177 | // result [-5, 3, 4, 5, 6]
178 | expect(scope.adapter.isBOF()).toBe(true);
179 | expect(scope.adapter.isEOF()).toBe(true);
180 | expect(Helper.getRow(viewport, 1)).toBe('-5: item-5');
181 | expect(Helper.getRow(viewport, 2)).toBe('-4: item3');
182 | expect(Helper.getRow(viewport, 3)).toBe('-3: item4');
183 | expect(Helper.getRow(viewport, 4)).toBe('-2: item5');
184 | expect(Helper.getRow(viewport, 5)).toBe('-1: item6');
185 | expect(scope.adapter.bufferLength).toBe(5);
186 | };
187 |
188 | it('should be consistent on backward direction when not bof with immutableTop', () =>
189 | runTest({
190 | ...settings,
191 | startIndex: -1,
192 | padding: 0.3
193 | }, (viewport, scope) =>
194 | shouldWorkWhenNotBOF(viewport, scope, { immutableTop: true })
195 | )
196 | );
197 |
198 | it('should be consistent on backward direction when not bof without immutableTop', () =>
199 | runTest({
200 | ...settings,
201 | startIndex: -1,
202 | padding: 0.3
203 | }, (viewport, scope) =>
204 | shouldWorkWhenNotBOF(viewport, scope)
205 | )
206 | );
207 | });
208 |
209 | });
210 |
--------------------------------------------------------------------------------
/test/UserIndicesSpec.js:
--------------------------------------------------------------------------------
1 | /*global describe, beforeEach, module, inject, it, expect, runTest, Helper */
2 | describe('uiScroll main/max indices', function() {
3 | 'use strict';
4 |
5 | let datasource;
6 | beforeEach(module('ui.scroll'));
7 | beforeEach(module('ui.scroll.test.datasources'));
8 |
9 | const injectDatasource = (datasourceToken) =>
10 | beforeEach(
11 | inject([datasourceToken, function(_datasource) {
12 | datasource = _datasource;
13 | }])
14 | );
15 |
16 | const viewportHeight = 120;
17 | const itemHeight = 20;
18 | const bufferSize = 3;
19 | const userMinIndex = -99; // for 100 items
20 | const userMaxIndex = 100;
21 |
22 | const scrollSettings = {
23 | datasource: 'myInfiniteDatasource',
24 | viewportHeight: viewportHeight,
25 | itemHeight: itemHeight,
26 | bufferSize: bufferSize,
27 | adapter: 'adapter'
28 | };
29 |
30 | describe('Setting\n', () => {
31 | injectDatasource('myInfiniteDatasource');
32 |
33 | it('should set up bottom padding element\'s height after user max index is set', () =>
34 | runTest(scrollSettings,
35 | (viewport) => {
36 | expect(viewport.scrollTop()).toBe(itemHeight * bufferSize);
37 |
38 | datasource.maxIndex = userMaxIndex;
39 |
40 | const virtualItemsAmount = userMaxIndex - (viewportHeight / itemHeight) - bufferSize;
41 | expect(Helper.getBottomPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
42 | expect(viewport.scrollTop()).toBe(itemHeight * bufferSize);
43 | }
44 | )
45 | );
46 |
47 | it('should set up top padding element\'s height after user min index is set', () =>
48 | runTest(scrollSettings,
49 | (viewport) => {
50 | expect(viewport.scrollTop()).toBe(itemHeight * bufferSize);
51 |
52 | datasource.minIndex = userMinIndex;
53 |
54 | const virtualItemsAmount = (-1) * userMinIndex - bufferSize + 1;
55 | expect(Helper.getTopPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
56 | expect(viewport.scrollTop()).toBe(itemHeight * ((-1) * userMinIndex + 1));
57 | }
58 | )
59 | );
60 |
61 | });
62 |
63 | describe('Pre-setting\n', () => {
64 | injectDatasource('myInfiniteDatasource');
65 |
66 | it('should work with maxIndex pre-set on datasource', () => {
67 | datasource.maxIndex = userMaxIndex;
68 | runTest(scrollSettings,
69 | (viewport) => {
70 | const virtualItemsAmount = userMaxIndex - (viewportHeight / itemHeight) - bufferSize;
71 | expect(Helper.getBottomPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
72 | expect(viewport.scrollTop()).toBe(itemHeight * bufferSize);
73 | }
74 | );
75 | });
76 |
77 | it('should work with minIndex pre-set on datasource', () => {
78 | datasource.minIndex = userMinIndex;
79 | runTest(scrollSettings,
80 | (viewport) => {
81 | const virtualItemsAmount = (-1) * userMinIndex - bufferSize + 1;
82 | expect(Helper.getTopPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
83 | expect(viewport.scrollTop()).toBe(itemHeight * ((-1) * userMinIndex + 1));
84 | }
85 | );
86 | });
87 |
88 | it('should work when the viewport is big enough to include more than 1 pack of item (last)', () => {
89 | const viewportHeight = 450;
90 | const _topItemsCount = Math.round(viewportHeight * 0.5 / itemHeight);
91 | const _topPackCount = Math.ceil(_topItemsCount / bufferSize);
92 | const _minIndex = (-1) * _topPackCount * bufferSize + 1; // one additinal adjustment should occur
93 | datasource.minIndex = _minIndex;
94 | datasource.maxIndex = userMaxIndex;
95 | runTest(Object.assign({}, scrollSettings, { viewportHeight }),
96 | (viewport) => {
97 | expect(Helper.getTopPadding(viewport)).toBe(0);
98 | expect(viewport.scrollTop()).toBe(_topPackCount * bufferSize * itemHeight);
99 | }
100 | );
101 | });
102 |
103 | it('should work when the viewport is big enough to include more than 1 pack of item (first)', () => {
104 | const viewportHeight = 450;
105 | const _topItemsCount = Math.round(viewportHeight * 0.5 / itemHeight);
106 | const _topPackCount = Math.ceil(_topItemsCount / bufferSize);
107 | let _minIndex = -1; // ~9 additinal adjustments should occur
108 | datasource.minIndex = _minIndex;
109 | datasource.maxIndex = userMaxIndex;
110 | runTest(Object.assign({}, scrollSettings, { viewportHeight }),
111 | (viewport) => {
112 | expect(Helper.getTopPadding(viewport)).toBe(0);
113 | expect(viewport.scrollTop()).toBe(_topPackCount * bufferSize * itemHeight);
114 | }
115 | );
116 | });
117 |
118 | });
119 |
120 | describe('Reload\n', () => {
121 | injectDatasource('myResponsiveDatasource');
122 | beforeEach(() => {
123 | datasource.min = userMinIndex;
124 | datasource.max = userMaxIndex;
125 | datasource.init();
126 | });
127 |
128 | it('should persist user maxIndex after reload', () => {
129 | datasource.maxIndex = userMaxIndex;
130 | runTest(Object.assign({}, scrollSettings, { datasource: 'myResponsiveDatasource' }),
131 | (viewport, scope) => {
132 | scope.adapter.reload();
133 | const virtualItemsAmount = userMaxIndex - (viewportHeight / itemHeight) - bufferSize;
134 | expect(Helper.getBottomPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
135 | expect(viewport.scrollTop()).toBe(itemHeight * bufferSize);
136 | }
137 | );
138 | });
139 |
140 | it('should persist user minIndex after reload', () => {
141 | datasource.minIndex = userMinIndex;
142 | runTest(Object.assign({}, scrollSettings, { datasource: 'myResponsiveDatasource' }),
143 | (viewport, scope) => {
144 | scope.adapter.reload();
145 | const virtualItemsAmount = (-1) * userMinIndex - bufferSize + 1;
146 | expect(Helper.getTopPadding(viewport)).toBe(itemHeight * virtualItemsAmount);
147 | expect(viewport.scrollTop()).toBe(itemHeight * ((-1) * userMinIndex + 1));
148 | }
149 | );
150 | });
151 |
152 | it('should apply new user minIndex and maxIndex after reload', () => {
153 | const startIndex = 10;
154 | const add = 50;
155 | const minIndexNew = userMinIndex - add;
156 | const maxIndexNew = userMaxIndex + add;
157 | datasource.minIndex = userMinIndex;
158 | datasource.maxIndex = userMaxIndex;
159 | runTest(Object.assign({}, scrollSettings, { datasource: 'myResponsiveDatasource', startIndex }),
160 | (viewport, scope) => {
161 | const _scrollTop = viewport.scrollTop();
162 |
163 | scope.adapter.reload(startIndex);
164 | datasource.min = minIndexNew;
165 | datasource.max = maxIndexNew;
166 | datasource.minIndex = minIndexNew;
167 | datasource.maxIndex = maxIndexNew;
168 |
169 | expect(Helper.getTopPadding(viewport)).toBe(itemHeight * ((-1) * minIndexNew + startIndex - bufferSize));
170 | expect(Helper.getBottomPadding(viewport)).toBe(itemHeight * (maxIndexNew - startIndex + 1 - (viewportHeight / itemHeight) - bufferSize));
171 | expect(viewport.scrollTop()).toBe(_scrollTop + itemHeight * add);
172 | }
173 | );
174 | });
175 |
176 | });
177 |
178 | });
179 |
--------------------------------------------------------------------------------
/test/VisibilitySwitchingSpec.js:
--------------------------------------------------------------------------------
1 | describe('uiScroll visibility.', () => {
2 | 'use strict';
3 |
4 | beforeEach(module('ui.scroll'));
5 | beforeEach(module('ui.scroll.test.datasources'));
6 |
7 | const scrollSettings = {
8 | datasource: 'myMultipageDatasource',
9 | viewportHeight: 200,
10 | itemHeight: 40,
11 | bufferSize: 3,
12 | adapter: 'adapter'
13 | };
14 |
15 | const checkContent = (rows, count) => {
16 | expect(rows.length).toBe(count);
17 | for (var i = 1; i < count - 1; i++) {
18 | expect(rows[i].innerHTML).toBe(i + ': item' + i);
19 | }
20 | };
21 |
22 | const onePackItemsCount = 3 * 1 + 2;
23 | const twoPacksItemsCount = 3 * 2 + 2;
24 | const threePacksItemsCount = 3 * 3 + 2;
25 |
26 | describe('Viewport visibility changing\n', () => {
27 |
28 | it('should create 9 divs with data (+ 2 padding divs)', () =>
29 | runTest(scrollSettings,
30 | (viewport) => {
31 | expect(viewport.scrollTop()).toBe(0);
32 | checkContent(viewport.children(), threePacksItemsCount);
33 | }
34 | )
35 | );
36 |
37 | it('should preserve elements after visibility switched off (display:none)', () =>
38 | runTest(scrollSettings,
39 | (viewport, scope) => {
40 | viewport.css('display', 'none');
41 | scope.$apply();
42 |
43 | expect(viewport.scrollTop()).toBe(0);
44 | checkContent(viewport.children(), threePacksItemsCount);
45 | }
46 | )
47 | );
48 |
49 | it('should only load one batch with visibility switched off (display:none)', () =>
50 | runTest(scrollSettings,
51 | (viewport, scope) => {
52 | viewport.css('display', 'none');
53 | scope.adapter.reload();
54 |
55 | expect(viewport.scrollTop()).toBe(0);
56 | checkContent(viewport.children(), onePackItemsCount);
57 | }
58 | )
59 | );
60 |
61 | it('should load full set after css-visibility switched back on', () =>
62 | runTest(scrollSettings,
63 | (viewport, scope, $timeout) => {
64 | viewport.css('display', 'none');
65 | scope.adapter.reload();
66 |
67 | viewport.css('display', 'block');
68 | scope.$apply();
69 | $timeout.flush();
70 |
71 | expect(viewport.scrollTop()).toBe(0);
72 | checkContent(viewport.children(), threePacksItemsCount);
73 | expect(scope.adapter.topVisible).toBe('item1');
74 | }
75 | )
76 | );
77 |
78 | it('should load full set after scope-visibility switched back on', () =>
79 | runTest(Object.assign({}, scrollSettings, {
80 | wrapper: {
81 | start: '',
82 | end: '
'
83 | }
84 | }), (viewport, scope) => {
85 | scope.show = false;
86 | scope.$apply();
87 | expect(viewport.children().length).toBe(0);
88 |
89 | scope.show = true;
90 | scope.$apply();
91 | expect(viewport.scrollTop()).toBe(0);
92 | checkContent(viewport.children().children(), threePacksItemsCount);
93 | }, {
94 | scope: {
95 | show: true
96 | }
97 | }
98 | )
99 | );
100 | });
101 |
102 | describe('Items visibility changing\n', () => {
103 |
104 | it('should load only one batch with items height = 0', () =>
105 | runTest(Object.assign({}, scrollSettings, { itemHeight: '0' }),
106 | (viewport) => {
107 | expect(viewport.children().length).toBe(onePackItemsCount);
108 | expect(viewport.scrollTop()).toBe(0);
109 | checkContent(viewport.children(), onePackItemsCount);
110 | }
111 | )
112 | );
113 |
114 | it('should load one more batch after the height of some item is set to a positive value', () =>
115 | runTest(Object.assign({}, scrollSettings, { itemHeight: '0' }),
116 | (viewport, scope, $timeout) => {
117 | angular.element(viewport.children()[onePackItemsCount - 2]).css('height', 40);
118 | expect(angular.element(viewport.children()[onePackItemsCount - 2]).css('height')).toBe('40px');
119 | scope.$apply();
120 | $timeout.flush();
121 |
122 | expect(viewport.scrollTop()).toBe(0);
123 | checkContent(viewport.children(), twoPacksItemsCount);
124 | }
125 | )
126 | );
127 | });
128 |
129 | });
130 |
--------------------------------------------------------------------------------
/test/config/karma.conf.files.js:
--------------------------------------------------------------------------------
1 | const files = [
2 | 'https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js',
3 | 'https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular.js',
4 | 'https://ajax.googleapis.com/ajax/libs/angularjs/1.6.5/angular-mocks.js',
5 | '../misc/test.css',
6 | '../misc/datasources.js',
7 | '../misc/scaffolding*.js',
8 | '../misc/helpers.js',
9 | {
10 | pattern: '../*Spec.js',
11 | watched: false,
12 | served: true,
13 | included: true
14 | }
15 | ];
16 |
17 | module.exports.development = [
18 | ...files,
19 | '../../src/ui-scroll.js',
20 | '../../src/ui-scroll-grid.js'
21 | ];
22 |
23 | module.exports.production = [
24 | ...files,
25 | '../../dist/ui-scroll.min.js',
26 | '../../dist/ui-scroll-grid.min.js',
27 | {
28 | pattern: '../../dist/*.js.map',
29 | included: false
30 | }
31 | ];
32 |
--------------------------------------------------------------------------------
/test/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | // BROWSER env variable could be "headless", "firefox" or "chrome" (default)
2 |
3 | const browsers = [];
4 | if (process.env.BROWSER === 'headless') {
5 | process.env.CHROME_BIN = require('puppeteer').executablePath();
6 | browsers.push('ChromeHeadlessSized');
7 | } else if (process.env.BROWSERS === "firefox") {
8 | browsers.push('FirefoxSized');
9 | } else {
10 | browsers.push(process.platform === 'linux' ? ['ChromiumSized'] : ['ChromeSized']);
11 | }
12 |
13 | const flags = ['--window-size=1024,768'];
14 | const customLaunchers = {
15 | 'ChromeHeadlessSized': { base: 'ChromeHeadless', flags },
16 | 'ChromiumSized': { base: 'Chromium', flags },
17 | 'ChromeSized': { base: 'Chrome', flags },
18 | 'FirefoxSized': { base: 'Firefox', flags }
19 | };
20 |
21 | const ENV = (!process.env.CI && process.env.npm_lifecycle_event.indexOf('dev') === 0) ?
22 | 'development' :
23 | 'production';
24 |
25 | const webpackSettings = ENV === 'development' ? {
26 | preprocessors: {
27 | '../../src/ui-scroll*.js': ['webpack', 'sourcemap']
28 | },
29 | webpack: require('../../webpack.config.js')
30 | } : {};
31 |
32 | module.exports = function (config) {
33 | 'use strict';
34 |
35 | config.set(Object.assign({
36 |
37 | basePath: '',
38 |
39 | frameworks: ['jasmine'],
40 |
41 | files: [
42 | ...require('./karma.conf.files.js')[ENV]
43 | ],
44 |
45 | exclude: [],
46 |
47 | reporters: ['dots'],
48 |
49 | port: ENV === 'development' ? 9100 : 8082,
50 |
51 | colors: true,
52 |
53 | logLevel: config.LOG_INFO,
54 |
55 | autoWatch: ENV === 'development',
56 |
57 | keepalive: ENV === 'development',
58 |
59 | browsers,
60 | customLaunchers,
61 |
62 | captureTimeout: 60000,
63 |
64 | singleRun: ENV !== 'development'
65 |
66 | }, webpackSettings));
67 | };
68 |
--------------------------------------------------------------------------------
/test/jqliteExtrasSpec.js:
--------------------------------------------------------------------------------
1 | describe('\njqLite: testing against jQuery\n', function () {
2 | 'use strict';
3 |
4 | var sandbox = angular.element('
');
5 | var extras;
6 |
7 | beforeEach(module('ui.scroll'));
8 | beforeEach(function(){
9 | angular.element(document).find('body').append(sandbox = angular.element('
'));
10 | inject(function(JQLiteExtras) {
11 | extras = function(){};
12 | (new JQLiteExtras()).registerFor(extras);
13 | });
14 | });
15 |
16 | afterEach(function() {sandbox.remove();});
17 |
18 | describe('height() getter for window\n', function() {
19 | it('should work for window element', function() {
20 | var element = angular.element(window);
21 | expect(extras.prototype.height.call(element)).toBe(element.height());
22 | });
23 | });
24 |
25 | describe('getters height() and outerHeight()\n', function () {
26 |
27 | function createElement(element) {
28 | var result = angular.element(element);
29 | sandbox.append(result);
30 | return result;
31 | }
32 |
33 | angular.forEach(
34 | [
35 | 'some text
',
36 | 'some text (height in em)
',
37 | 'some text height in px
',
38 | 'some text w border
',
39 | 'some text w border
',
40 | 'some text w padding
',
41 | 'some text w padding
',
42 | 'some text w margin
',
43 | 'some text w margin
'
44 | ], function(element) {
45 |
46 | it('should be the same as jQuery height() for ' + element, function() {
47 | (function(element) {
48 | expect(extras.prototype.height.call(element)).toBe(element.height());
49 | })(createElement(element));
50 | }
51 | );
52 |
53 | it ('should be the same as jQuery outerHeight() for ' + element, function() {
54 | (function(element) {
55 | expect(extras.prototype.outerHeight.call(element)).toBe(element.outerHeight());
56 | })(createElement(element));
57 | }
58 | );
59 |
60 | it ('should be the same as jQuery outerHeight(true) for ' + element, function() {
61 | (function(element) {
62 | expect(extras.prototype.outerHeight.call(element, true)).toBe(element.outerHeight(true));
63 | })(createElement(element));
64 | }
65 | );
66 |
67 | }
68 |
69 | );
70 | });
71 |
72 | describe('height(value) setter\n', function () {
73 |
74 | function createElement(element) {
75 | var result = angular.element(element);
76 | sandbox.append(result);
77 | return result;
78 | }
79 |
80 | angular.forEach(
81 | [
82 | 'some text
',
83 | 'some text (height in em)
',
84 | 'some text height in px
',
85 | 'some text w border
',
86 | 'some text w border
',
87 | 'some text w padding
',
88 | 'some text w padding
',
89 | 'some text w margin
',
90 | 'some text w margin
',
91 | 'some text w margin
'
92 | ], function(element) {
93 |
94 | // Since jQuery v3 the .hegth() results don't being rounded (https://github.com/jquery/jquery/pull/2454).
95 | // So the element 'some text w line height
' will cause the error --
96 | // Expected 18 to be 17.6
97 |
98 | it('height(value) for ' + element, function() {
99 | (function (element) {
100 | expect(extras.prototype.height.call(element)).toBe(element.height());
101 | var h = element.height();
102 | extras.prototype.height.call(element, h*2);
103 | expect(extras.prototype.height.call(element)).toBe(h*2);
104 | })(createElement(element));
105 | }
106 | );
107 |
108 | }
109 |
110 | );
111 | });
112 |
113 | describe('offset() getter\n', function () {
114 |
115 | function createElement(element) {
116 | var result = angular.element(element);
117 | sandbox.append(result);
118 | return result;
119 | }
120 |
121 | angular.forEach(
122 | [
123 | '',
124 | '',
125 | // 'some text height in px
',
126 | // 'some text w border
',
127 | // 'some text w border
',
128 | // 'some text w padding
',
129 | // 'some text w padding
',
130 | // 'some text w margin
',
131 | ''
132 | ], function(element) {
133 |
134 | it('should be the same as jQuery offset() for ' + element, function() {
135 | (function (element) {
136 | var target = jQuery(element.contents()[0]);
137 | expect(extras.prototype.offset.call(target)).toEqual(element.offset());
138 | })(createElement(element));
139 | }
140 | );
141 |
142 | }
143 |
144 | );
145 | });
146 |
147 | describe('scrollTop()\n', function() {
148 |
149 | function createElement(element) {
150 | var result = angular.element(element);
151 | sandbox.append(result);
152 | return result;
153 | }
154 |
155 | it('should be the same as jQuery scrollTop() for window', function() {
156 |
157 | createElement('
');
158 | var element = jQuery(window);
159 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
160 | element.scrollTop(100);
161 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
162 | extras.prototype.scrollTop.call(element, 200);
163 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
164 | }
165 | );
166 |
167 | it('should be the same as jQuery scrollTop() for window', function() {
168 |
169 | var element = createElement('');
170 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
171 | element.scrollTop(100);
172 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
173 | extras.prototype.scrollTop.call(element, 200);
174 | expect(extras.prototype.scrollTop.call(element)).toBe(element.scrollTop());
175 | }
176 | );
177 |
178 | });
179 |
180 | });
--------------------------------------------------------------------------------
/test/misc/datasources.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('ui.scroll.test.datasources', [])
5 |
6 | .factory('myEmptyDatasource', [
7 | '$log', '$timeout', '$rootScope',
8 | function() {
9 | return {
10 | get: function(index, count, success) {
11 | success([]);
12 | }
13 | };
14 | }
15 | ])
16 |
17 | .factory('myDescriptoEmptyDatasource', [
18 | '$log', '$timeout', '$rootScope',
19 | function() {
20 | return {
21 | get: function(descriptor, success) {
22 | success([]);
23 | }
24 | };
25 | }
26 | ])
27 |
28 | .factory('myOnePageDatasource', [
29 | '$log', '$timeout', '$rootScope',
30 | function() {
31 | return {
32 | get: function(index, count, success) {
33 | if (index === 1) {
34 | success(['one', 'two', 'three']);
35 | } else {
36 | success([]);
37 | }
38 | }
39 | };
40 | }
41 | ])
42 |
43 | .factory('myOneBigPageDatasource', [
44 | '$log', '$timeout', '$rootScope',
45 | function() {
46 | return {
47 | get: function(index, count, success) {
48 | if (index === 1) {
49 | var resultList = [];
50 | for (var i = 1; i < 100; i++) {
51 | resultList.push('item' + i);
52 | }
53 | success(resultList);
54 | } else {
55 | success([]);
56 | }
57 | }
58 | };
59 | }
60 | ])
61 |
62 | .factory('myDescriptorOnePageDatasource', [
63 | '$log', '$timeout', '$rootScope',
64 | function() {
65 | return {
66 | get: function(descriptor, success) {
67 | if (descriptor.index === 1) {
68 | success(['one', 'two', 'three']);
69 | } else {
70 | success([]);
71 | }
72 | }
73 | };
74 | }
75 | ])
76 |
77 | .factory('myObjectDatasource', [
78 | '$log', '$timeout', '$rootScope',
79 | function() {
80 | return {
81 | get: function(index, count, success) {
82 | if (index === 1) {
83 | success([{ text: 'one' }, { text: 'two' }, { text: 'three' }]);
84 | } else {
85 | success([]);
86 | }
87 | }
88 | };
89 | }
90 | ])
91 |
92 | .factory('myMultipageDatasource', [
93 | '$log', '$timeout', '$rootScope',
94 | function() {
95 | return {
96 | get: function(index, count, success) {
97 | var result = [];
98 | for (var i = index; i < index + count; i++) {
99 | if (i > 0 && i <= 20) {
100 | result.push('item' + i);
101 | }
102 | }
103 | success(result);
104 | }
105 | };
106 | }
107 | ])
108 |
109 | .factory('anotherDatasource', [
110 | '$log', '$timeout', '$rootScope',
111 | function() {
112 | return {
113 | get: function(index, count, success) {
114 | var result = [];
115 | for (var i = index; i < index + count; i++) {
116 | if (i > -3 && i < 1) {
117 | result.push('item' + i);
118 | }
119 | }
120 | success(result);
121 | }
122 | };
123 | }
124 | ])
125 |
126 | .factory('myEdgeDatasource', [
127 | '$log', '$timeout', '$rootScope',
128 | function() {
129 | return {
130 | get: function(index, count, success) {
131 | var result = [];
132 | for (var i = index; i < index + count; i++) {
133 | if (i > -6 && i <= 6) {
134 | result.push('item' + i);
135 | }
136 | }
137 | success(result);
138 | }
139 | };
140 | }
141 | ])
142 |
143 | .factory('myDatasourceToPreventScrollBubbling', [
144 | '$log', '$timeout', '$rootScope',
145 | function() {
146 | return {
147 | get: function(index, count, success) {
148 | var result = [];
149 | for (var i = index; i < index + count; i++) {
150 | if (i < -6 || i > 20) {
151 | break;
152 | }
153 | result.push('item' + i);
154 | }
155 | success(result);
156 | }
157 | };
158 | }
159 | ])
160 |
161 | .factory('myInfiniteDatasource', [
162 | '$log', '$timeout', '$rootScope',
163 | function() {
164 | return {
165 | get: function(index, count, success) {
166 | var result = [];
167 | for (var i = index; i < index + count; i++) {
168 | result.push('item' + i);
169 | }
170 | success(result);
171 | }
172 | };
173 | }
174 | ])
175 |
176 | .factory('myGridDatasource', [
177 | '$log', '$timeout', '$rootScope',
178 | function() {
179 | return {
180 | get: function(index, count, success) {
181 | var result = [];
182 | for (var i = index; i < index + count; i++) {
183 | result.push({
184 | col0: 'col0',
185 | col1: 'col1',
186 | col2: 'col2',
187 | col3: 'col3'
188 | });
189 | }
190 | success(result);
191 | }
192 | };
193 | }
194 | ])
195 |
196 |
197 | .factory('myResponsiveDatasource', function() {
198 | var datasource = {
199 | data: [],
200 | min: 1,
201 | max: 30,
202 | init: function() {
203 | this.data = [];
204 | for (var i = this.min; i <= this.max; i++) {
205 | this.data.push('item' + i);
206 | }
207 | },
208 | getItem: function(index) {
209 | return this.data[index - this.min];
210 | },
211 | get: function(index, count, success) {
212 | var result = [];
213 | var start = Math.max(this.min, index);
214 | var end = Math.min(index + count - 1, this.max);
215 | if (start <= end) {
216 | for (var i = start; i <= end; i++) {
217 | result.push(this.getItem(i));
218 | }
219 | }
220 | success(result);
221 | }
222 | };
223 | datasource.init();
224 | return datasource;
225 | }
226 | );
227 |
228 | })();
--------------------------------------------------------------------------------
/test/misc/helpers.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | let Helper;
4 |
5 | (function() {
6 | 'use strict';
7 |
8 | Helper = {
9 |
10 | getTopPadding: (viewport) => {
11 | const viewportChildren = viewport.children();
12 | const topPadding = viewportChildren[0];
13 | return parseInt(topPadding.style.height, 10);
14 | },
15 |
16 | getBottomPadding: (viewport) => {
17 | const viewportChildren = viewport.children();
18 | const bottomPadding = viewportChildren[viewportChildren.length - 1];
19 | return parseInt(bottomPadding.style.height, 10);
20 | },
21 |
22 | getRow: (viewport, number) => { // number is index + 1
23 | const viewportChildren = viewport.children();
24 | if (viewportChildren.length < 2 + number) {
25 | return;
26 | }
27 | return viewportChildren[number].innerHTML;
28 | },
29 |
30 | getFirstRow: (viewport) => Helper.getRow(viewport, 1),
31 |
32 | getLastRow: (viewport) => {
33 | const viewportChildren = viewport.children();
34 | if (viewportChildren.length < 3) {
35 | return;
36 | }
37 | return viewportChildren[viewportChildren.length - 2].innerHTML;
38 | }
39 |
40 | };
41 |
42 | })();
43 |
--------------------------------------------------------------------------------
/test/misc/scaffolding.js:
--------------------------------------------------------------------------------
1 | /* exported runTest */
2 |
3 | function createHtml(settings) {
4 | 'use strict';
5 | var viewportStyle = ' style="height:' + (settings.viewportHeight || 200) + 'px"';
6 | var itemStyle = settings.itemHeight ? ' style="height:' + settings.itemHeight + 'px"' : '';
7 | var bufferSize = settings.bufferSize ? ' buffer-size="' + settings.bufferSize + '"' : '';
8 | var padding = settings.padding ? ' padding="' + settings.padding + '"' : '';
9 | var isLoading = settings.isLoading ? ' is-loading="' + settings.isLoading + '"' : '';
10 | var topVisible = settings.topVisible ? ' top-visible="' + settings.topVisible + '"' : '';
11 | var disabled = settings.disabled ? ' disabled="' + settings.disabled + '"' : '';
12 | var adapter = settings.adapter ? ' adapter="' + settings.adapter + '"' : '';
13 | var template = settings.template ? settings.template : '{{$index}}: {{item}}';
14 | var startIndex = settings.startIndex ? ' start-index="' + settings.startIndex + '"' : '';
15 | var inertia = ' handle-inertia="false"';
16 | var extra = settings.extra || '';
17 | return '' +
18 | (settings.wrapper ? settings.wrapper.start : '') +
19 | '
' +
22 | template +
23 | '
' +
24 | (settings.wrapper ? settings.wrapper.end : '') +
25 | '
';
26 | }
27 |
28 | function finalize(scroller, options, scope, $timeout) {
29 | 'use strict';
30 | options = options || {};
31 | scroller.remove();
32 |
33 | if (typeof options.cleanupTest === 'function') {
34 | options.cleanupTest(scroller, scope, $timeout);
35 | }
36 | }
37 |
38 | function augmentScroller(scroller) {
39 | 'use strict';
40 | var scrollTop = scroller.scrollTop;
41 | scroller.scrollTop = function () {
42 | var result = scrollTop.apply(scroller, arguments);
43 | if (arguments.length) {
44 | scroller.trigger('scroll');
45 | }
46 | return result;
47 | };
48 | }
49 |
50 | function runTest(scrollSettings, run, options) {
51 | 'use strict';
52 | options = options || {};
53 | inject(function ($rootScope, $compile, $window, $timeout) {
54 | var scroller = angular.element(createHtml(scrollSettings));
55 | var scope = $rootScope.$new();
56 | augmentScroller(scroller);
57 |
58 | angular.element(document).find('body').append(scroller);
59 |
60 | if (options.scope) {
61 | angular.extend(scope, options.scope);
62 | }
63 |
64 | var compile = function() {
65 | $compile(scroller)(scope);
66 | scope.$apply();
67 | };
68 |
69 | if (typeof options.catch === 'function') {
70 | try {
71 | compile();
72 | } catch (error) {
73 | options.catch(error);
74 | }
75 | } else {
76 | compile();
77 | }
78 |
79 | if(typeof run === 'function') {
80 | try {
81 | run(scroller, scope, $timeout);
82 | } finally {
83 | finalize(scroller, options, scope, $timeout);
84 | }
85 | }
86 | });
87 | }
--------------------------------------------------------------------------------
/test/misc/scaffoldingGrid.js:
--------------------------------------------------------------------------------
1 | /* exported runGridTest */
2 |
3 | function createGridHtml (settings) {
4 | 'use strict';
5 | var viewportStyle = ' style="height:' + (settings.viewportHeight || 200) + 'px"';
6 | var columns = ['col0', 'col1', 'col2', 'col3'];
7 |
8 | var html =
9 | '' +
10 | '' +
11 | '';
12 | columns.forEach(col => { html +=
13 | '' + col + ' ';
14 | }); html +=
15 | ' ' +
16 | ' ' +
17 | '' +
18 | '';
19 | if(settings.rowTemplate) {
20 | html += settings.rowTemplate;
21 | } else {
22 | columns.forEach(col => { html +=
23 | '{{item.' + col + '}} ';
24 | });
25 | } html +=
26 | ' ' +
27 | ' ' +
28 | '
';
29 | return html;
30 | }
31 |
32 | function finalize(scroller, options, scope, $timeout) {
33 | 'use strict';
34 | options = options || {};
35 | scroller.remove();
36 |
37 | if (typeof options.cleanupTest === 'function') {
38 | options.cleanupTest(scroller, scope, $timeout);
39 | }
40 | }
41 |
42 | function runGridTest(scrollSettings, run, options) {
43 | 'use strict';
44 | options = options || {};
45 | inject(function($rootScope, $compile, $window, $timeout) {
46 | var scroller = angular.element(createGridHtml(scrollSettings));
47 | var scope = $rootScope.$new();
48 |
49 | angular.element(document).find('body').append(scroller);
50 | var head = angular.element(scroller.children()[0]);
51 | var body = angular.element(scroller.children()[1]);
52 |
53 | if (options.scope) {
54 | angular.extend(scope, options.scope);
55 | }
56 |
57 | $compile(scroller)(scope);
58 |
59 | scope.$apply();
60 | $timeout.flush();
61 |
62 | try {
63 | run(head, body, scope, $timeout);
64 | } finally {
65 | finalize(scroller, options, scope, $timeout);
66 | }
67 | });
68 | }
--------------------------------------------------------------------------------
/test/misc/test.css:
--------------------------------------------------------------------------------
1 | .grid { width: 250px; }
2 | .col0 { width: 40px; }
3 | .col1 { width: 80px; }
4 | .col2 { width: 40px; }
5 | .col3 { width: 50px; }
6 | .item:nth-child(odd) { background-color: #e6e6e6; }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const glob = require('glob');
5 | const webpack = require('webpack');
6 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
7 | const CopyWebpackPlugin = require('copy-webpack-plugin');
8 | const TerserPlugin = require('terser-webpack-plugin');
9 |
10 | const packageJSON = require('./package.json');
11 |
12 | const getBanner = () =>
13 | packageJSON.name + '\n' +
14 | packageJSON.homepage + '\n' +
15 | 'Version: ' + packageJSON.version + ' -- ' + (new Date()).toISOString() + '\n' +
16 | 'License: ' + packageJSON.license;
17 |
18 | const scriptName = process.env.npm_lifecycle_event;
19 | const ENV = scriptName.indexOf('dev') === 0 ? 'development' : 'production';
20 | const isTest = scriptName.indexOf('test') >= 0;
21 |
22 | console.log('***** webpack runs in ' + ENV + (isTest ? ' (test)' : '') + ' environment\n');
23 |
24 | const devServerPort = 5005;
25 | const devServerHost = 'localhost';
26 | let configEnv;
27 |
28 | if (ENV === 'development') {
29 | configEnv = {
30 | entry: isTest ? ({
31 | 'test': glob.sync(path.resolve(__dirname, 'test/*.js'))
32 | }) : ({}),
33 |
34 | output: {
35 | filename: '[name].js',
36 | publicPath: '/'
37 | },
38 |
39 | devtool: 'inline-source-map',
40 |
41 | plugins: [],
42 |
43 | optimization: {},
44 |
45 | devServer: !isTest ? {
46 | proxy: {
47 | '/dist': {
48 | target: 'http://' + devServerHost + ':' + devServerPort,
49 | pathRewrite: { '^/dist': '' }
50 | }
51 | },
52 |
53 | port: devServerPort,
54 | host: devServerHost,
55 | static: "demo",
56 | devMiddleware: {
57 | stats: {
58 | modules: false,
59 | errors: true,
60 | warnings: true
61 | },
62 | publicPath: '/'
63 | },
64 | } : {},
65 |
66 | watch: true
67 | }
68 | }
69 |
70 | if (ENV === 'production') {
71 | configEnv = {
72 | entry: {
73 | 'ui-scroll.min': path.resolve(__dirname, 'src/ui-scroll.js'),
74 | 'ui-scroll-grid.min': path.resolve(__dirname, 'src/ui-scroll-grid.js')
75 | },
76 |
77 | output: {
78 | path: path.resolve(__dirname, 'dist'),
79 | filename: '[name].js'
80 | },
81 |
82 | devtool: 'source-map',
83 |
84 | optimization: {
85 | minimize: true,
86 | minimizer: [
87 | new TerserPlugin({
88 | parallel: true,
89 | extractComments: false,
90 | terserOptions: {
91 | sourceMap: true,
92 | warnings: true,
93 | compress: {
94 | warnings: true,
95 | },
96 | output: {
97 | comments: false,
98 | },
99 | },
100 | include: /\.min\.js$/
101 | })
102 | ],
103 | },
104 |
105 | plugins: [
106 | new CleanWebpackPlugin({
107 | cleanOnceBeforeBuildPatterns: [path.join(__dirname, 'dist/**/*')]
108 | }),
109 | new CopyWebpackPlugin({
110 | patterns: [
111 | { from: 'src/ui-scroll-jqlite.js', to: 'ui-scroll-jqlite.min.js' },
112 | { from: 'src/ui-scroll-jqlite.js', to: 'ui-scroll-jqlite.js' }
113 | ]
114 | }),
115 | new webpack.BannerPlugin(getBanner())
116 | ],
117 |
118 | devServer: {},
119 |
120 | watch: false
121 | }
122 | }
123 |
124 | module.exports = {
125 | entry: Object.assign({
126 | 'ui-scroll': path.resolve(__dirname, 'src/ui-scroll.js'),
127 | 'ui-scroll-grid': path.resolve(__dirname, 'src/ui-scroll-grid.js')
128 | }, configEnv.entry),
129 |
130 | output: configEnv.output,
131 |
132 | cache: false,
133 |
134 | devtool: configEnv.devtool,
135 |
136 | mode: ENV,
137 |
138 | target: ['web', 'es5'],
139 |
140 | optimization: configEnv.optimization,
141 |
142 | module: {
143 | rules: [
144 | {
145 | test: /\.js$/,
146 | exclude: /node_modules/,
147 | loader: 'babel-loader'
148 | }
149 | ]
150 | },
151 |
152 | plugins: configEnv.plugins,
153 |
154 | devServer: configEnv.devServer,
155 |
156 | watch: configEnv.watch
157 | };
158 |
--------------------------------------------------------------------------------