mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
feat(list): reordering scrolls page, reordering performance better
Fixes #521. Reordering now uses webkitTransform instead of element.style.left. Additionally, as you drag the drag-element to the top or bottom of the scroll-area, it will scroll it up or down as allowed. Refactors necessary: Common code from `<content>` and `<scroll>` moved into js/ext/angular/controllers/ionicScrollController. Then `<content>` and `<scroll>` expose the controller, and `<list>` can require it. `<list>` then uses the controller (if exists) to pass the scrollView and scrollEl to ReorderDrag, and ReorderDrag uses that to scroll. Additionally, js/ext/angular/test/controller/ionicScrollController tests much functionality that was untested before.
This commit is contained in:
@@ -11,7 +11,7 @@ notifications:
|
||||
rooms:
|
||||
secure: mkHfRTsuxidtOOORbJJ0Jspb/DSa8jAiQwWWUljqLwefy1p4HGC9P/rLdXXg3vsjiulCzyjEkfvDWAHXvu34GhGWfQuD8U140Fon1Os3AO5Hbme+yRmjXmTcgH8XetSLQufyBBMqXHMd6o1tkxXql1p54G1IShhgAdPNe76d5ZE=
|
||||
template:
|
||||
- '<a href="%{build_url}">%{repository}: build#%{build_number} (%{duration%})</a> #{message} (%{branch} - %{commit} : %{author})'
|
||||
- '<a href="%{build_url}">%{repository}: build#%{build_number}</a> #{message} (%{branch} - %{commit} : %{author})'
|
||||
format: html
|
||||
|
||||
before_install:
|
||||
|
||||
@@ -37,7 +37,8 @@ module.exports = {
|
||||
'js/_license.js',
|
||||
'js/ext/angular/src/ionicAngular.js',
|
||||
'js/ext/angular/src/service/**/*.js',
|
||||
'js/ext/angular/src/directive/**/*.js'
|
||||
'js/ext/angular/src/directive/**/*.js',
|
||||
'js/ext/angular/src/controller/**/*.js'
|
||||
],
|
||||
//Which vendor files to include in dist, used by build
|
||||
//Matched relative to config/lib/
|
||||
|
||||
44
js/ext/angular/src/controller/ionicScrollController.js
vendored
Normal file
44
js/ext/angular/src/controller/ionicScrollController.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('ionic.ui.scroll')
|
||||
|
||||
.controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout',
|
||||
function($scope, scrollViewOptions, $timeout) {
|
||||
|
||||
scrollViewOptions.bouncing = angular.isDefined(scrollViewOptions.bouncing) ?
|
||||
scrollViewOptions.bouncing :
|
||||
!ionic.Platform.isAndroid();
|
||||
|
||||
var element = this.element = scrollViewOptions.el;
|
||||
var refresher = this.refresher = element.querySelector('.scroll-refresher');
|
||||
var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions);
|
||||
|
||||
this.$element = angular.element(element);
|
||||
|
||||
//Attach self to element as a controller so other directives can require this controller
|
||||
//through `require: '$ionicScroll'
|
||||
this.$element.data('$$ionicScrollController', this);
|
||||
|
||||
$timeout(function() {
|
||||
scrollView.run();
|
||||
|
||||
// Activate pull-to-refresh
|
||||
if(refresher) {
|
||||
var refresherHeight = refresher.clientHeight || 0;
|
||||
scrollView.activatePullToRefresh(refresherHeight, function() {
|
||||
refresher.classList.add('active');
|
||||
}, function() {
|
||||
refresher.classList.remove('refreshing');
|
||||
refresher.classList.remove('active');
|
||||
}, function() {
|
||||
refresher.classList.add('refreshing');
|
||||
$scope.onRefresh && $scope.onRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefresh');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}]);
|
||||
|
||||
})();
|
||||
125
js/ext/angular/src/directive/ionicContent.js
vendored
125
js/ext/angular/src/directive/ionicContent.js
vendored
@@ -1,7 +1,7 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular.module('ionic.ui.content', ['ionic.ui.service'])
|
||||
angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
|
||||
|
||||
/**
|
||||
* Panel is a simple 100% width and height, fixed panel. It's meant for content to be
|
||||
@@ -18,7 +18,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service'])
|
||||
|
||||
// The content directive is a core scrollable content area
|
||||
// that is part of many View hierarchies
|
||||
.directive('content', ['$parse', '$timeout', '$ionicScrollDelegate', function($parse, $timeout, $ionicScrollDelegate) {
|
||||
.directive('content', ['$parse', '$timeout', '$ionicScrollDelegate', '$controller', function($parse, $timeout, $ionicScrollDelegate, $controller) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
@@ -52,46 +52,33 @@ angular.module('ionic.ui.content', ['ionic.ui.service'])
|
||||
if(attr.hasTabs == "true") { element.addClass('has-tabs'); }
|
||||
if(attr.padding == "true") { element.find('div').addClass('padding'); }
|
||||
|
||||
return function link($scope, $element, $attr, navViewCtrl) {
|
||||
var clone, sc, sv,
|
||||
return {
|
||||
//Prelink <content> so it can compile before other directives compile.
|
||||
//Then other directives can require ionicScrollCtrl
|
||||
pre: prelink
|
||||
};
|
||||
|
||||
function prelink($scope, $element, $attr, navViewCtrl) {
|
||||
var clone, sc, scrollView, scrollCtrl,
|
||||
c = angular.element($element.children()[0]);
|
||||
|
||||
if($scope.scroll === "false") {
|
||||
// No scrolling
|
||||
return;
|
||||
}
|
||||
|
||||
if (navViewCtrl) {
|
||||
// If we do have a parent navView, wait for them to give us $viewContentLoaded event
|
||||
// before we fully initialize
|
||||
$scope.$on('$viewContentLoaded', function(e, viewHistoryData) {
|
||||
initScroll(viewHistoryData);
|
||||
});
|
||||
} else {
|
||||
// If we are standalone view, just initialize immediately.
|
||||
initScroll();
|
||||
}
|
||||
|
||||
function initScroll(viewHistoryData) {
|
||||
viewHistoryData || (viewHistoryData = {});
|
||||
var savedScroll = viewHistoryData.scrollValues || {};
|
||||
if(attr.overflowScroll === "true") {
|
||||
$element.addClass('overflow-scroll');
|
||||
return;
|
||||
}
|
||||
|
||||
// If they want plain overflow scrolling, add that as a class
|
||||
if(attr.overflowScroll === "true") {
|
||||
$element.addClass('overflow-scroll');
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, use our scroll system
|
||||
var hasBouncing = $scope.$eval($scope.hasBouncing);
|
||||
var enableBouncing = (!ionic.Platform.isAndroid() && hasBouncing !== false) || hasBouncing === true;
|
||||
// No bouncing by default for Android users, lest they take up pitchforks
|
||||
// to our bouncing goodness
|
||||
sv = new ionic.views.Scroll({
|
||||
scrollCtrl = $controller('$ionicScroll', {
|
||||
$scope: $scope,
|
||||
scrollViewOptions: {
|
||||
el: $element[0],
|
||||
bouncing: enableBouncing,
|
||||
startX: $scope.$eval($scope.startX) || savedScroll.left || 0,
|
||||
startY: $scope.$eval($scope.startY) || savedScroll.top || 0,
|
||||
bouncing: $scope.$eval($scope.hasBouncing),
|
||||
startX: $scope.$eval($scope.startX) || 0,
|
||||
startY: $scope.$eval($scope.startY) || 0,
|
||||
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
|
||||
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
|
||||
scrollingX: $scope.$eval($scope.hasScrollX) === true,
|
||||
@@ -103,54 +90,36 @@ angular.module('ionic.ui.content', ['ionic.ui.service'])
|
||||
scrollLeft: this.__scrollLeft
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
//Publish scrollView to parent so children can access it
|
||||
scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
|
||||
|
||||
$scope.$on('$viewContentLoaded', function(e, viewHistoryData) {
|
||||
viewHistoryData || (viewHistoryData = {});
|
||||
if (viewHistoryData.scrollValues) {
|
||||
scrollView.scrollTo(viewHistoryData.scrollValues);
|
||||
}
|
||||
|
||||
//Save scroll onto viewHistoryData when scope is destroyed
|
||||
$scope.$on('$destroy', function() {
|
||||
viewHistoryData.scrollValues = sv.getValues();
|
||||
viewHistoryData.scrollValues = scrollView.getValues();
|
||||
});
|
||||
});
|
||||
|
||||
var refresher = $element[0].querySelector('.scroll-refresher');
|
||||
var refresherHeight = refresher && refresher.clientHeight || 0;
|
||||
|
||||
if(attr.refreshComplete) {
|
||||
$scope.refreshComplete = function() {
|
||||
if($scope.scrollView) {
|
||||
refresher && refresher.classList.remove('active');
|
||||
$scope.scrollView.finishPullToRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefreshComplete');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Activate pull-to-refresh
|
||||
if(refresher) {
|
||||
sv.activatePullToRefresh(50, function() {
|
||||
refresher.classList.add('active');
|
||||
}, function() {
|
||||
refresher.classList.remove('refreshing');
|
||||
refresher.classList.remove('active');
|
||||
}, function() {
|
||||
refresher.classList.add('refreshing');
|
||||
$scope.onRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefresh');
|
||||
});
|
||||
}
|
||||
|
||||
// Register for scroll delegate event handling
|
||||
$ionicScrollDelegate.register($scope, $element);
|
||||
|
||||
// Let child scopes access this
|
||||
$scope.$parent.scrollView = sv;
|
||||
|
||||
$timeout(function() {
|
||||
// Give child containers a chance to build and size themselves
|
||||
sv.run();
|
||||
});
|
||||
|
||||
return sv;
|
||||
if(attr.refreshComplete) {
|
||||
$scope.refreshComplete = function() {
|
||||
if($scope.scrollView) {
|
||||
scrollCtrl.refresher && scrollCtrl.refresher.classList.remove('active');
|
||||
scrollView.finishPullToRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefreshComplete');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Register for scroll delegate event handling
|
||||
$ionicScrollDelegate.register($scope, $element);
|
||||
|
||||
// Check if this supports infinite scrolling and listen for scroll events
|
||||
// to trigger the infinite scrolling
|
||||
// TODO(ajoslin): move functionality out of this function and make testable
|
||||
@@ -163,20 +132,20 @@ angular.module('ionic.ui.content', ['ionic.ui.service'])
|
||||
if(distance.indexOf('%')) {
|
||||
// It's a multiplier
|
||||
maxScroll = function() {
|
||||
return sv.getScrollMax().top * ( 1 - parseInt(distance, 10) / 100 );
|
||||
return scrollView.getScrollMax().top * ( 1 - parseInt(distance, 10) / 100 );
|
||||
};
|
||||
} else {
|
||||
// It's a pixel value
|
||||
maxScroll = function() {
|
||||
return sv.getScrollMax().top - parseInt(distance, 10);
|
||||
return scrollView.getScrollMax().top - parseInt(distance, 10);
|
||||
};
|
||||
}
|
||||
$element.bind('scroll', function(e) {
|
||||
if( sv && !infiniteStarted && (sv.getValues().top > maxScroll() ) ) {
|
||||
if( scrollView && !infiniteStarted && (scrollView.getValues().top > maxScroll() ) ) {
|
||||
infiniteStarted = true;
|
||||
infiniteScroll.addClass('active');
|
||||
var cb = function() {
|
||||
sv.resize();
|
||||
scrollView.resize();
|
||||
infiniteStarted = false;
|
||||
infiniteScroll.removeClass('active');
|
||||
};
|
||||
|
||||
10
js/ext/angular/src/directive/ionicList.js
vendored
10
js/ext/angular/src/directive/ionicList.js
vendored
@@ -37,7 +37,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
|
||||
|
||||
link: function($scope, $element, $attr, list) {
|
||||
if(!list) return;
|
||||
|
||||
|
||||
var $parentScope = list.scope;
|
||||
var $parentAttrs = list.attrs;
|
||||
|
||||
@@ -54,7 +54,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
|
||||
|
||||
$scope.itemClass = $scope.itemType;
|
||||
|
||||
// Decide if this item can do stuff, and follow a certain priority
|
||||
// Decide if this item can do stuff, and follow a certain priority
|
||||
// depending on where the value comes from
|
||||
if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") {
|
||||
if($attr.onDelete || $parentAttrs.onDelete) {
|
||||
@@ -100,7 +100,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
|
||||
require: '^?$ionicScroll',
|
||||
scope: {
|
||||
itemType: '@',
|
||||
canDelete: '@',
|
||||
@@ -122,10 +122,12 @@ angular.module('ionic.ui.list', ['ngAnimate'])
|
||||
this.attrs = $attrs;
|
||||
}],
|
||||
|
||||
link: function($scope, $element, $attr) {
|
||||
link: function($scope, $element, $attr, ionicScrollCtrl) {
|
||||
$scope.listView = new ionic.views.ListView({
|
||||
el: $element[0],
|
||||
listEl: $element[0].children[0],
|
||||
scrollEl: ionicScrollCtrl && ionicScrollCtrl.element,
|
||||
scrollView: ionicScrollCtrl && ionicScrollCtrl.scrollView,
|
||||
onReorder: function(el, oldIndex, newIndex) {
|
||||
$scope.$apply(function() {
|
||||
$scope.onReorder({el: el, start: oldIndex, end: newIndex});
|
||||
|
||||
103
js/ext/angular/src/directive/ionicScroll.js
vendored
103
js/ext/angular/src/directive/ionicScroll.js
vendored
@@ -3,11 +3,11 @@
|
||||
|
||||
angular.module('ionic.ui.scroll', [])
|
||||
|
||||
.directive('scroll', ['$parse', '$timeout', function($parse, $timeout) {
|
||||
.directive('scroll', ['$parse', '$timeout', '$controller', function($parse, $timeout, $controller) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
template: '<div class="scroll-view"></div>',
|
||||
template: '<div class="scroll-view"><div class="scroll" ng-transclude></div></div>',
|
||||
transclude: true,
|
||||
scope: {
|
||||
direction: '@',
|
||||
@@ -23,8 +23,15 @@ angular.module('ionic.ui.scroll', [])
|
||||
controller: function() {},
|
||||
|
||||
compile: function(element, attr, transclude) {
|
||||
return function($scope, $element, $attr) {
|
||||
var clone, sv, sc = document.createElement('div');
|
||||
|
||||
return {
|
||||
//Prelink <scroll> so it can compile before other directives compile.
|
||||
//Then other directives can require ionicScrollCtrl
|
||||
pre: prelink
|
||||
};
|
||||
|
||||
function prelink($scope, $element, $attr) {
|
||||
var scrollView, scrollCtrl, sc = $element[0].children[0];
|
||||
|
||||
// Create the internal scroll div
|
||||
sc.className = 'scroll';
|
||||
@@ -34,72 +41,46 @@ angular.module('ionic.ui.scroll', [])
|
||||
if($scope.$eval($scope.paging) === true) {
|
||||
sc.classList.add('scroll-paging');
|
||||
}
|
||||
$element.append(sc);
|
||||
|
||||
// Pass the parent scope down to the child
|
||||
clone = transclude($scope.$parent);
|
||||
angular.element($element[0].firstElementChild).append(clone);
|
||||
|
||||
// Get refresher size
|
||||
var refresher = $element[0].querySelector('.scroll-refresher');
|
||||
var refresherHeight = refresher && refresher.clientHeight || 0;
|
||||
|
||||
if(!$scope.direction) { $scope.direction = 'y'; }
|
||||
var hasScrollingX = $scope.direction.indexOf('x') >= 0;
|
||||
var hasScrollingY = $scope.direction.indexOf('y') >= 0;
|
||||
var isPaging = $scope.$eval($scope.paging) === true;
|
||||
|
||||
$timeout(function() {
|
||||
var options = {
|
||||
el: $element[0],
|
||||
paging: $scope.$eval($scope.paging) === true,
|
||||
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
|
||||
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
|
||||
scrollingX: hasScrollingX,
|
||||
scrollingY: hasScrollingY
|
||||
};
|
||||
var scrollViewOptions= {
|
||||
el: $element[0],
|
||||
paging: isPaging,
|
||||
scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
|
||||
scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
|
||||
scrollingX: $scope.direction.indexOf('x') >= 0,
|
||||
scrollingY: $scope.direction.indexOf('y') >= 0
|
||||
};
|
||||
if (isPaging) {
|
||||
scrollViewOptions.speedMultiplier = 0.8;
|
||||
scrollViewOptions.bouncing = false;
|
||||
}
|
||||
|
||||
if(options.paging) {
|
||||
options.speedMultiplier = 0.8;
|
||||
options.bouncing = false;
|
||||
}
|
||||
scrollCtrl = $controller('$ionicScroll', {
|
||||
$scope: $scope,
|
||||
scrollViewOptions: scrollViewOptions
|
||||
});
|
||||
scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
|
||||
|
||||
sv = new ionic.views.Scroll(options);
|
||||
|
||||
// Activate pull-to-refresh
|
||||
if(refresher) {
|
||||
sv.activatePullToRefresh(refresherHeight, function() {
|
||||
refresher.classList.add('active');
|
||||
}, function() {
|
||||
refresher.classList.remove('refreshing');
|
||||
refresher.classList.remove('active');
|
||||
}, function() {
|
||||
refresher.classList.add('refreshing');
|
||||
$scope.onRefresh();
|
||||
$scope.$parent.$broadcast('scroll.onRefresh');
|
||||
});
|
||||
}
|
||||
|
||||
$element.bind('scroll', function(e) {
|
||||
$scope.onScroll({
|
||||
event: e,
|
||||
scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0,
|
||||
scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0
|
||||
});
|
||||
$element.bind('scroll', function(e) {
|
||||
$scope.onScroll({
|
||||
event: e,
|
||||
scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0,
|
||||
scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$parent.$on('scroll.resize', function(e) {
|
||||
// Run the resize after this digest
|
||||
$timeout(function() {
|
||||
sv && sv.resize();
|
||||
});
|
||||
$scope.$parent.$on('scroll.resize', function(e) {
|
||||
// Run the resize after this digest
|
||||
$timeout(function() {
|
||||
scrollView && scrollView.resize();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$parent.$on('scroll.refreshComplete', function(e) {
|
||||
sv && sv.finishPullToRefresh();
|
||||
});
|
||||
|
||||
// Let child scopes access this
|
||||
$scope.$parent.scrollView = sv;
|
||||
$scope.$parent.$on('scroll.refreshComplete', function(e) {
|
||||
scrollView && scrollView.finishPullToRefresh();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
90
js/ext/angular/test/controller/ionicScrollController.unit.js
Normal file
90
js/ext/angular/test/controller/ionicScrollController.unit.js
Normal file
@@ -0,0 +1,90 @@
|
||||
describe('$ionicScroll Controller', function() {
|
||||
|
||||
beforeEach(module('ionic.ui.scroll'));
|
||||
|
||||
var scope, ctrl, timeout;
|
||||
function setup(options) {
|
||||
options = options || {};
|
||||
|
||||
options.el = options.el || document.createElement('div');
|
||||
|
||||
inject(function($controller, $rootScope, $timeout) {
|
||||
scope = $rootScope.$new();
|
||||
ctrl = $controller('$ionicScroll', {
|
||||
$scope: scope,
|
||||
scrollViewOptions: options
|
||||
});
|
||||
spyOn(ctrl.scrollView, 'run'); // don't actually call run, this is a dumb test
|
||||
timeout = $timeout;
|
||||
});
|
||||
}
|
||||
|
||||
it('should set this.element and this.$element', function() {
|
||||
setup();
|
||||
expect(ctrl.element.tagName).toMatch(/div/i);
|
||||
expect(ctrl.$element[0]).toBe(ctrl.element);
|
||||
});
|
||||
|
||||
it('should register scrollView as this.scrollView', function() {
|
||||
setup();
|
||||
expect(ctrl.scrollView instanceof ionic.views.Scroll).toBe(true);
|
||||
});
|
||||
|
||||
it('should register controller on element.data', function() {
|
||||
setup();
|
||||
expect(ctrl.$element.controller('$ionicScroll')).toBe(ctrl);
|
||||
});
|
||||
|
||||
it('should run after a timeout', function() {
|
||||
setup();
|
||||
timeout.flush();
|
||||
expect(ctrl.scrollView.run).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not setup if no child .scroll-refresher', function() {
|
||||
setup();
|
||||
expect(ctrl.refresher).toBeFalsy();
|
||||
spyOn(ctrl.scrollView, 'activatePullToRefresh');
|
||||
timeout.flush();
|
||||
expect(ctrl.scrollView.activatePullToRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should work with .scroll-refresher child and proper refresher', function() {
|
||||
var startCb, refreshingCb, doneCb, refresherEl;
|
||||
setup({
|
||||
el: angular.element('<div><div class="scroll-refresher"></div></div>')[0]
|
||||
});
|
||||
spyOn(ctrl.scrollView, 'activatePullToRefresh').andCallFake(function(height, start, refreshing, done) {
|
||||
startCb = start;
|
||||
refreshingCb = refreshing;
|
||||
doneCb = done;
|
||||
});
|
||||
|
||||
scope.onRefresh = jasmine.createSpy('onRefresh');
|
||||
scope.$parent.$broadcast = jasmine.createSpy('$broadcast');
|
||||
var refresher = ctrl.refresher;
|
||||
|
||||
timeout.flush();
|
||||
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
|
||||
startCb();
|
||||
expect(refresher.classList.contains('active')).toBe(true);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
|
||||
refreshingCb();
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(false);
|
||||
|
||||
expect(scope.onRefresh).not.toHaveBeenCalled();
|
||||
expect(scope.$parent.$broadcast).not.toHaveBeenCalledWith('scroll.onRefresh');
|
||||
|
||||
doneCb();
|
||||
expect(refresher.classList.contains('active')).toBe(false);
|
||||
expect(refresher.classList.contains('refreshing')).toBe(true);
|
||||
expect(scope.onRefresh).toHaveBeenCalled();
|
||||
expect(scope.$parent.$broadcast).toHaveBeenCalledWith('scroll.onRefresh');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -87,23 +87,14 @@ describe('Ionic Content directive', function() {
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
it('should not initialize scroll until $viewContentLoaded if there is a parent view', function() {
|
||||
compileWithParent();
|
||||
expect(scope.scrollView).toBeUndefined();
|
||||
scope.$broadcast('$viewContentLoaded', {});
|
||||
expect(scope.scrollView instanceof ionic.views.Scroll).toBe(true);
|
||||
});
|
||||
|
||||
it('should set start x and y with historyData.scroll passed in through $viewContentLoaded', function() {
|
||||
it('should set x and y with historyData.scrollValues passed in through $viewContentLoaded', function() {
|
||||
compileWithParent();
|
||||
spyOn(scope.scrollView, 'scrollTo');
|
||||
var scrollValues = { top: 40, left: -20, zoom: 3 };
|
||||
scope.$broadcast('$viewContentLoaded', {
|
||||
scrollValues: { top: 40, left: -20 }
|
||||
scrollValues: scrollValues
|
||||
});
|
||||
timeout.flush();
|
||||
var scrollView = scope.scrollView;
|
||||
var vals = scrollView.getValues();
|
||||
expect(vals.top).toBe(40);
|
||||
expect(vals.left).toBe(-20);
|
||||
expect(scope.scrollView.scrollTo).toHaveBeenCalledWith(scrollValues);
|
||||
});
|
||||
|
||||
it('should save scroll on the historyData passed in on $destroy', function() {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
if(!buttons) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
buttonsWidth = buttons.offsetWidth;
|
||||
|
||||
this._currentDrag = {
|
||||
@@ -111,7 +111,7 @@
|
||||
// The final resting point X will be the width of the exposed buttons
|
||||
var restingPoint = -this._currentDrag.buttonsWidth;
|
||||
|
||||
// Check if the drag didn't clear the buttons mid-point
|
||||
// Check if the drag didn't clear the buttons mid-point
|
||||
// and we aren't moving fast enough to swipe open
|
||||
if(e.gesture.deltaX > -(this._currentDrag.buttonsWidth/2)) {
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
_this._currentDrag.content.style.webkitTransform = 'translate3d(' + restingPoint + 'px, 0, 0)';
|
||||
}
|
||||
_this._currentDrag.content.style.webkitTransition = '';
|
||||
|
||||
|
||||
|
||||
// Kill the current drag
|
||||
_this._currentDrag = null;
|
||||
@@ -160,10 +160,17 @@
|
||||
this.dragThresholdY = opts.dragThresholdY || 0;
|
||||
this.onReorder = opts.onReorder;
|
||||
this.el = opts.el;
|
||||
this.scrollEl = opts.scrollEl;
|
||||
this.scrollView = opts.scrollView;
|
||||
};
|
||||
|
||||
ReorderDrag.prototype = new DragOp();
|
||||
|
||||
ReorderDrag.prototype._moveElement = function(e) {
|
||||
var y = (e.gesture.center.pageY - this._currentDrag.elementHeight/2);
|
||||
this.el.style.webkitTransform = 'translate3d(0, '+y+'px, 0)';
|
||||
};
|
||||
|
||||
ReorderDrag.prototype.start = function(e) {
|
||||
var content;
|
||||
|
||||
@@ -172,20 +179,31 @@
|
||||
var offsetY = this.el.offsetTop;//parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0;
|
||||
|
||||
var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
|
||||
|
||||
var elementHeight = this.el.offsetHeight;
|
||||
var placeholder = this.el.cloneNode(true);
|
||||
|
||||
// If we have a scroll pane, move our draggable element outside of it
|
||||
// We do this because when we drag our element down below the edge of the page
|
||||
// and scroll the scroll-pane, if the element is *part* of the scroll-pane,
|
||||
// it will scroll 'with' the scroll-pane's contents and change position.
|
||||
var appendToElement = (this.scrollEl || this.el).parentNode;
|
||||
|
||||
placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
|
||||
|
||||
this.el.parentNode.insertBefore(placeholder, this.el);
|
||||
|
||||
this.el.classList.add(ITEM_REORDERING_CLASS);
|
||||
|
||||
appendToElement.parentNode.appendChild(this.el);
|
||||
|
||||
this._currentDrag = {
|
||||
startOffsetTop: offsetY,
|
||||
elementHeight: elementHeight,
|
||||
startIndex: startIndex,
|
||||
placeholder: placeholder
|
||||
placeholder: placeholder,
|
||||
scrollHeight: scroll,
|
||||
list: placeholder.parentNode
|
||||
};
|
||||
|
||||
this._moveElement(e);
|
||||
};
|
||||
|
||||
ReorderDrag.prototype.drag = function(e) {
|
||||
@@ -197,6 +215,29 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollY = 0;
|
||||
var pageY = e.gesture.center.pageY;
|
||||
|
||||
//If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
|
||||
if (_this.scrollView) {
|
||||
var container = _this.scrollEl;
|
||||
|
||||
scrollY = _this.scrollView.getValues().top;
|
||||
|
||||
var containerTop = container.offsetTop;
|
||||
var pixelsPastTop = containerTop - pageY + _this._currentDrag.elementHeight/2;
|
||||
var pixelsPastBottom = pageY + _this._currentDrag.elementHeight/2 - containerTop - container.offsetHeight;
|
||||
|
||||
if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
|
||||
_this.scrollView.scrollBy(null, -pixelsPastTop);
|
||||
}
|
||||
if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
|
||||
if (scrollY < _this.scrollView.getScrollMax().top) {
|
||||
_this.scrollView.scrollBy(null, pixelsPastBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should start dragging. Check if we've dragged past the threshold,
|
||||
// or we are starting from the open state.
|
||||
if(!_this._isDragging && Math.abs(e.gesture.deltaY) > _this.dragThresholdY) {
|
||||
@@ -204,11 +245,9 @@
|
||||
}
|
||||
|
||||
if(_this._isDragging) {
|
||||
var newY = _this._currentDrag.startOffsetTop + e.gesture.deltaY;
|
||||
|
||||
_this.el.style.top = newY + 'px';
|
||||
_this._moveElement(e);
|
||||
|
||||
_this._currentDrag.currentY = newY;
|
||||
_this._currentDrag.currentY = scrollY + pageY - _this._currentDrag.placeholder.parentNode.offsetTop;
|
||||
|
||||
_this._reorderItems();
|
||||
}
|
||||
@@ -219,9 +258,6 @@
|
||||
ReorderDrag.prototype._reorderItems = function() {
|
||||
var placeholder = this._currentDrag.placeholder;
|
||||
var siblings = Array.prototype.slice.call(this._currentDrag.placeholder.parentNode.children);
|
||||
|
||||
// Remove the floating element from the child search list
|
||||
siblings.splice(siblings.indexOf(this.el), 1);
|
||||
|
||||
var index = siblings.indexOf(this._currentDrag.placeholder);
|
||||
var topSibling = siblings[Math.max(0, index - 1)];
|
||||
@@ -244,12 +280,12 @@
|
||||
}
|
||||
|
||||
var placeholder = this._currentDrag.placeholder;
|
||||
var finalPosition = ionic.DomUtil.getChildIndex(placeholder, placeholder.nodeName.toLowerCase());
|
||||
|
||||
// Reposition the element
|
||||
this.el.classList.remove(ITEM_REORDERING_CLASS);
|
||||
this.el.style.top = 0;
|
||||
this.el.style.webkitTransform = '';
|
||||
|
||||
var finalPosition = ionic.DomUtil.getChildIndex(placeholder, placeholder.nodeName.toLowerCase());
|
||||
placeholder.parentNode.insertBefore(this.el, placeholder);
|
||||
placeholder.parentNode.removeChild(placeholder);
|
||||
|
||||
@@ -294,7 +330,7 @@
|
||||
window.ionic.onGesture('release', function(e) {
|
||||
_this._handleEndDrag(e);
|
||||
}, this.el);
|
||||
|
||||
|
||||
window.ionic.onGesture('drag', function(e) {
|
||||
_this._handleDrag(e);
|
||||
}, this.el);
|
||||
@@ -399,6 +435,8 @@
|
||||
if(item) {
|
||||
this._dragOp = new ReorderDrag({
|
||||
el: item,
|
||||
scrollEl: this.scrollEl,
|
||||
scrollView: this.scrollView,
|
||||
onReorder: function(el, start, end) {
|
||||
_this.onReorder && _this.onReorder(el, start, end);
|
||||
}
|
||||
@@ -424,7 +462,7 @@
|
||||
|
||||
_handleEndDrag: function(e) {
|
||||
var _this = this;
|
||||
|
||||
|
||||
if(!this._dragOp) {
|
||||
//ionic.views.ListView.__super__._handleEndDrag.call(this, e);
|
||||
return;
|
||||
@@ -447,7 +485,7 @@
|
||||
*/
|
||||
_handleDrag: function(e) {
|
||||
var _this = this, content, buttons;
|
||||
|
||||
|
||||
// If the user has a touch timeout to highlight an element, clear it if we
|
||||
// get sufficient draggage
|
||||
if(Math.abs(e.gesture.deltaX) > 10 || Math.abs(e.gesture.deltaY) > 10) {
|
||||
@@ -462,7 +500,7 @@
|
||||
}
|
||||
|
||||
// No drag still, pass it up
|
||||
if(!this._dragOp) {
|
||||
if(!this._dragOp) {
|
||||
//ionic.views.ListView.__super__._handleDrag.call(this, e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ $z-index-item-drag: 0;
|
||||
$z-index-item-edit: 0;
|
||||
$z-index-item-options: 1;
|
||||
$z-index-item-radio: 3;
|
||||
$z-index-item-reordering: 20;
|
||||
$z-index-item-reordering: 9;
|
||||
$z-index-item-toggle: 3;
|
||||
$z-index-menu: 0;
|
||||
$z-index-modal: 10;
|
||||
|
||||
@@ -37,7 +37,8 @@ describe('List View', function() {
|
||||
expect(list.itemHeight).toEqual(50);
|
||||
});
|
||||
|
||||
xit('Should support virtual scrolling', function() {
|
||||
/*
|
||||
it('Should support virtual scrolling', function() {
|
||||
var list = new ionic.views.ListView({
|
||||
el: h,
|
||||
listEl: listEl,
|
||||
@@ -70,5 +71,6 @@ describe('List View', function() {
|
||||
expect(list.renderViewport).toHaveBeenCalledWith(scrollTop + list.virtualRemoveThreshold,
|
||||
scrollTop + viewportHeight + list.virtualAddThreshold, start, end);
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user