From 29c0fc0c045b8b1443ceb7443862a49b3aa3c7dc Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 10:38:15 +0800 Subject: [PATCH 01/10] Add infinite scroll CSS --- scss/_scaffolding.scss | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/scss/_scaffolding.scss b/scss/_scaffolding.scss index f2b8b337e2..68462d28cd 100644 --- a/scss/_scaffolding.scss +++ b/scss/_scaffolding.scss @@ -219,6 +219,27 @@ body, .ionic-body { } } +// Infinite scroll +infinite-scroll .scroll-infinite { + overflow: hidden; + margin-top: -70px; + height: 60px; + position: relative; +} + +.scroll-infinite-content { + position: absolute; + left: 0; + bottom: 15px; + width: 100%; + text-align: center; + font-size: 30px; + color: #666666; } + +infinite-scroll.active .scroll-infinite { + margin-top: -10px; +} + .overflow-scroll { overflow-x: hidden; overflow-y: scroll; From 7d5f5e63256e37539ea4b0081e7fba0ef1c0b5b2 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 10:43:50 +0800 Subject: [PATCH 02/10] Add infinite scroll directive and handle infinite scroll in content directive --- js/ext/angular/src/directive/ionicContent.js | 26 ++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 377c3eb77f..5a054fa840 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -122,7 +122,22 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) $scope.$parent.scrollView = sv; }); - + // Infinite scroll + var infiniteScroll = $element.find('infinite-scroll'); + var infiniteStarted = false; + if(infiniteScroll) { + $element.bind('scroll', function(e) { + if( sv && !infiniteStarted && (sv.getValues().top > (sv.getScrollMax().top * 0.99) ) ) { + infiniteStarted = true; + infiniteScroll.addClass('active'); + var cb = function() { + infiniteStarted = false; + infiniteScroll.removeClass('active'); + } + $scope.$apply(angular.bind($scope, $scope.onInfiniteScroll, cb)); + } + }); + } } // if padding attribute is true, then add padding if it wasn't added to the .scroll @@ -152,7 +167,14 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) transclude: true, template: '
' }; +})) + +.directive('infiniteScroll', function() { + return { + restrict: 'E', + replace: false, + template: '
' + }; }); - })(); From 197349e77d18d896a431e6fbacd6229d6e92d3c8 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 11:50:08 +0800 Subject: [PATCH 03/10] Bind infinite scroll --- js/ext/angular/src/directive/ionicContent.js | 1 + scss/_scaffolding.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 5a054fa840..fb8012ab97 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -30,6 +30,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) onScroll: '&', onScrollComplete: '&', refreshComplete: '=', + onInfiniteScroll: '=', scroll: '@', hasScrollX: '@', hasScrollY: '@', diff --git a/scss/_scaffolding.scss b/scss/_scaffolding.scss index 68462d28cd..2d6583c964 100644 --- a/scss/_scaffolding.scss +++ b/scss/_scaffolding.scss @@ -237,7 +237,7 @@ infinite-scroll .scroll-infinite { color: #666666; } infinite-scroll.active .scroll-infinite { - margin-top: -10px; + margin-top: -30px; } .overflow-scroll { From 658649c48de94211c3d070e5110df6447dee97db Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 11:53:42 +0800 Subject: [PATCH 04/10] Parse error --- js/ext/angular/src/directive/ionicContent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index fb8012ab97..171312cc77 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -134,7 +134,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) var cb = function() { infiniteStarted = false; infiniteScroll.removeClass('active'); - } + }; $scope.$apply(angular.bind($scope, $scope.onInfiniteScroll, cb)); } }); @@ -168,7 +168,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) transclude: true, template: '
' }; -})) +}) .directive('infiniteScroll', function() { return { From 7a1045058cea9f565d3dc318983ca8642bc0831b Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 11:57:20 +0800 Subject: [PATCH 05/10] Add a new example including infinite scroll --- examples/starters/infinitescroll/index.html | 98 ++++++++++++ examples/starters/infinitescroll/js/app.js | 36 +++++ .../starters/infinitescroll/js/controllers.js | 36 +++++ .../starters/infinitescroll/js/services.js | 146 ++++++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 examples/starters/infinitescroll/index.html create mode 100644 examples/starters/infinitescroll/js/app.js create mode 100644 examples/starters/infinitescroll/js/controllers.js create mode 100644 examples/starters/infinitescroll/js/services.js diff --git a/examples/starters/infinitescroll/index.html b/examples/starters/infinitescroll/index.html new file mode 100644 index 0000000000..651ae62e20 --- /dev/null +++ b/examples/starters/infinitescroll/index.html @@ -0,0 +1,98 @@ + + + + + Ionic List Example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/starters/infinitescroll/js/app.js b/examples/starters/infinitescroll/js/app.js new file mode 100644 index 0000000000..ec82ea2e8a --- /dev/null +++ b/examples/starters/infinitescroll/js/app.js @@ -0,0 +1,36 @@ +// angular.module is a global place for creating, registering and retrieving Angular modules +// 'listExample' is the name of this angular module example (also set in a attribute in index.html) +// the 2nd parameter is an array or 'requires' +// 'listExample.services' is found in services.js +// 'listExample.controllers' is found in controllers.js +angular.module('listExample', ['ionic', 'ngRoute', 'ngAnimate', 'listExample.services', 'listExample.controllers']) + +.config(function ($compileProvider){ + // Needed for routing to work + $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|tel):/); +}) + +.config(function($routeProvider, $locationProvider) { + + // Set up the initial routes that our app will respond to. + // These are then tied up to our nav router which animates and + // updates a navigation bar + $routeProvider.when('/', { + templateUrl: '/index.html', + controller: 'MovieIndexCtrl' + }); + + // if the url matches something like /movie/88 then this route + // will fire off the MovieDetailCtrl (controllers.js) + $routeProvider.when('/movie/:movieId', { + templateUrl: '/movie.html', + controller: 'MovieDetailCtrl' + }); + + // if none of the above routes are met, use this fallback + $routeProvider.otherwise({ + redirectTo: '/' + }); + +}); + diff --git a/examples/starters/infinitescroll/js/controllers.js b/examples/starters/infinitescroll/js/controllers.js new file mode 100644 index 0000000000..57fdc97c47 --- /dev/null +++ b/examples/starters/infinitescroll/js/controllers.js @@ -0,0 +1,36 @@ +angular.module('listExample.controllers', []) + + +// Controller that fetches a list of data +.controller('MovieIndexCtrl', function($scope, MovieService,$timeout) { + + // "MovieService" is a service returning mock data (services.js) + // the returned data from the service is placed into this + // controller's scope so the template can render the data + $scope.movies = MovieService.all(); + + $scope.title = "Completely Random Collection Of Movies"; + + // Method called on infinite scroll + // Receives a "done" callback to inform the infinite scroll that we are done + $scope.loadMore = function(done) { + $timeout(function() { + $scope.movies.push({ + id: 'tt0114814', + title: 'Usual Suspects', + released: '1995', + description: 'A boat has been destroyed, criminals are dead, and the key to this mystery lies with the only survivor and his twisted, convoluted story beginning with five career crooks in a seemingly random police lineup.', + director: 'Bryan Singer', + rating: 8.3 + }); + done(); + }, 1000); + } +}) + +// Controller that shows more detailed info about a movie +.controller('MovieDetailCtrl', function($scope, $routeParams, MovieService) { + // "MovieService" is a service returning mock data (services.js) + $scope.movie = MovieService.get($routeParams.movieId); + $scope.title = "Movie Info"; +}); diff --git a/examples/starters/infinitescroll/js/services.js b/examples/starters/infinitescroll/js/services.js new file mode 100644 index 0000000000..3f26eeb22b --- /dev/null +++ b/examples/starters/infinitescroll/js/services.js @@ -0,0 +1,146 @@ +angular.module('listExample.services', []) + +/** + * A simple example service that returns some data. + */ +.factory('MovieService', function() { + // Might use a resource here that returns a JSON array + + // Some fake testing data + var movies = [ + { + id: 'tt0080487', + title: 'Caddyshack', + released: '1980', + description: 'An exclusive golf course has to deal with a brash new member and a destructive dancing gopher.', + director: 'Harold Ramis', + rating: 7.2 + }, + { + id: 'tt0087332', + title: 'Ghostbusters', + released: '1984', + description: 'Three unemployed parapsychology professors set up shop as a unique ghost removal service.', + director: 'Ivan Reitman', + rating: 7.8 + }, + { + id: 'tt0097428', + title: 'Ghostbusters II', + released: '1989', + description: 'The discovery of a massive river of ectoplasm and a resurgence of spectral activity allows the staff of Ghostbusters to revive the business.', + director: 'Ivan Reitman', + rating: 6.4 + }, + { + id: 'tt0107048', + title: 'Groundhog Day', + released: '1993', + description: 'A weatherman finds himself living the same day over and over again.', + director: 'Harold Ramis', + rating: 8.1 + }, + { + id: 'tt0116778', + title: 'Kingpin', + released: '1996', + description: 'A star bowler whose career was prematurely "cut off" hopes to ride a new prodigy to success and riches.', + director: 'Bobby Farrelly, Peter Farrelly', + rating: 6.8 + }, + { + id: 'tt0335266', + title: 'Lost in Translation', + released: '2003', + description: 'A faded movie star and a neglected young wife form an unlikely bond after crossing paths in Tokyo.', + director: 'Sofia Coppola', + rating: 7.8 + }, + { + id: 'tt0079540', + title: 'Meatballs', + released: '1979', + description: 'Wacky hijinks of counselors and campers at a less-than-average summer camp.', + director: 'Ivan Reitman', + rating: 5.9 + }, + { + id: 'tt0128445', + title: 'Rushmore', + released: '1998', + description: 'The king of Rushmore prep school is put on academic probation.', + director: 'Wes Anderson', + rating: 7.7 + }, + { + id: 'tt0096061', + title: 'Scrooged', + released: '1988', + description: 'A cynically selfish TV executive gets haunted by three spirits bearing lessons on Christmas Eve.', + director: 'Richard Donner', + rating: 6.9 + }, + { + id: 'tt0083131', + title: 'Stripes', + released: '1981', + description: 'Two friends who are dissatisfied with their jobs decide to join the army for a bit of fun.', + director: 'Ivan Reitman', + rating: 6.8 + }, + { + id: 'tt0362270', + title: 'The Life Aquatic with Steve Zissou', + released: '2004', + description: 'With a plan to exact revenge on a mythical shark that killed his partner, oceanographer Steve Zissou rallies a crew that includes his estranged wife, a journalist, and a man who may or may not be his son.', + director: 'Wes Anderson', + rating: 7.2 + }, + { + id: 'tt0120483', + title: 'The Man Who Knew Too Little', + released: '1997', + description: 'Murray is mistaken for a spy and must stop a plot to assasinate international leaders at a banquet.', + director: 'Jon Amiel', + rating: 6.4 + }, + { + id: 'tt0265666', + title: 'The Royal Tenenbaums', + released: '2001', + description: 'An estranged family of former child prodigies reunites when one of their member announces he has a terminal illness.', + director: 'Wes Anderson', + rating: 7.5 + }, + { + id: 'tt0103241', + title: 'What About Bob?', + released: '1991', + description: 'A successful psychiatrist loses his mind after one of his most dependent patients, a highly manipulative obsessive-compulsive, tracks him down during his family vacation.', + director: 'Frank Oz', + rating: 6.8 + }, + { + id: 'tt1156398', + title: 'Zombieland', + released: '2009', + description: 'A shy student trying to reach his family in Ohio, and a gun-toting tough guy trying to find the Last Twinkie and a pair of sisters trying to get to an amusement park join forces to travel across a zombie-filled America.', + director: 'Ruben Fleischer', + rating: 7.7 + } + ]; + + return { + all: function() { + return movies; + }, + get: function(movieId) { + // Simple index lookup + for(var i=0, l=movies.length; i < l; i++) { + if(movies[i].id == movieId) { + return movies[i]; + } + } + } + } +}); From fed9ff29a9a2390146666ec19119836fa91cd96a Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 12:05:09 +0800 Subject: [PATCH 06/10] Add 3 movies in example, looks better --- examples/starters/list/js/controllers.js | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/examples/starters/list/js/controllers.js b/examples/starters/list/js/controllers.js index ff72f71568..e620bcb6dd 100644 --- a/examples/starters/list/js/controllers.js +++ b/examples/starters/list/js/controllers.js @@ -2,7 +2,7 @@ angular.module('listExample.controllers', []) // Controller that fetches a list of data -.controller('MovieIndexCtrl', function($scope, MovieService) { +.controller('MovieIndexCtrl', function($scope, MovieService,$timeout) { // "MovieService" is a service returning mock data (services.js) // the returned data from the service is placed into this @@ -10,6 +10,38 @@ angular.module('listExample.controllers', []) $scope.movies = MovieService.all(); $scope.title = "Completely Random Collection Of Movies"; + + // Method called on infinite scroll + // Receives a "done" callback to inform the infinite scroll that we are done + $scope.loadMore = function(done) { + $timeout(function() { + $scope.movies.push({ + id: 'tt0114814', + title: 'Usual Suspects', + released: '1995', + description: 'A boat has been destroyed, criminals are dead, and the key to this mystery lies with the only survivor and his twisted, convoluted story beginning with five career crooks in a seemingly random police lineup.', + director: 'Bryan Singer', + rating: 8.3 + }); + $scope.movies.push({ + id: 'tt0357413', + title: 'Anchorman: The Legend of Ron Burgundy', + released: '2004', + description: "Ron Burgundy is San Diego's top rated newsman in the male-dominated broadcasting of the 70's, but that's all about to change for Ron and his cronies when an ambitious woman is hired as a new anchor.", + director: 'Adam McKay', + rating: 7.2 + }); + $scope.movies.push({ + id: 'tt0058331', + title: 'Mary Poppins', + released: '1964', + description: "A magic nanny comes to work for a cold banker's unhappy family.", + director: 'Robert Stevenson', + rating: 7.7 + }); + done(); + }, 1000); + } }) // Controller that shows more detailed info about a movie From 2fdc431bc303383e866ade6d835d8bb51fcc8b4a Mon Sep 17 00:00:00 2001 From: Mathieu Date: Sat, 28 Dec 2013 12:20:05 +0800 Subject: [PATCH 07/10] Add support for distance attribute in infinite scroll --- js/ext/angular/src/directive/ionicContent.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 171312cc77..9511521d38 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -31,6 +31,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) onScrollComplete: '&', refreshComplete: '=', onInfiniteScroll: '=', + infiniteScrollDistance: '@', scroll: '@', hasScrollX: '@', hasScrollY: '@', @@ -127,8 +128,22 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) var infiniteScroll = $element.find('infinite-scroll'); var infiniteStarted = false; if(infiniteScroll) { + // Parse infinite scroll distance + var distance = attr.infiniteScrollDistance || '1%'; + var maxScroll; + if(distance.indexOf('%')) { + // It's a multiplier + maxScroll = function() { + return sv.getScrollMax().top * ( 1 - parseInt(distance, 10) / 100 ); + }; + } else { + // It's a pixel value + maxScroll = function() { + return sv.getScrollMax().top - parseInt(distance, 10); + }; + } $element.bind('scroll', function(e) { - if( sv && !infiniteStarted && (sv.getValues().top > (sv.getScrollMax().top * 0.99) ) ) { + if( sv && !infiniteStarted && (sv.getValues().top > maxScroll() ) ) { infiniteStarted = true; infiniteScroll.addClass('active'); var cb = function() { From afa389d9ce1fd913829e1770e62ba764b96b6017 Mon Sep 17 00:00:00 2001 From: Mathieu Date: Fri, 3 Jan 2014 14:48:14 +0800 Subject: [PATCH 08/10] Make item type dynamic (similar to ng-class) --- js/ext/angular/src/directive/ionicList.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index f881a99910..d34f647642 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -3,7 +3,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) -.directive('item', ['$timeout', function($timeout) { +.directive('item', ['$timeout', '$parse', function($timeout, $parse) { return { restrict: 'E', require: '?^list', @@ -22,7 +22,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) reorderIcon: '@' }, - template: '
\ + template: '
\
\ \
\ @@ -47,7 +47,10 @@ angular.module('ionic.ui.list', ['ngAnimate']) // Set this item's class, first from the item directive attr, and then the list attr if item not set $scope.itemClass = $scope.itemType || $parentScope.itemType; - + var getter = $parse( $scope.itemClass ); + $scope.parsedClass = function(a) { + return getter($scope.$parent); + }; // 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") { From ec9c04befa4193ef7cf52d9924912c15542940cd Mon Sep 17 00:00:00 2001 From: Mathieu Date: Fri, 3 Jan 2014 14:52:50 +0800 Subject: [PATCH 09/10] Cleaner using bind --- js/ext/angular/src/directive/ionicList.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/js/ext/angular/src/directive/ionicList.js b/js/ext/angular/src/directive/ionicList.js index d34f647642..81dfc0f8fc 100644 --- a/js/ext/angular/src/directive/ionicList.js +++ b/js/ext/angular/src/directive/ionicList.js @@ -48,9 +48,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) // Set this item's class, first from the item directive attr, and then the list attr if item not set $scope.itemClass = $scope.itemType || $parentScope.itemType; var getter = $parse( $scope.itemClass ); - $scope.parsedClass = function(a) { - return getter($scope.$parent); - }; + $scope.parsedClass = angular.bind( $scope, getter, $scope.$parent ); // 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") { From c0e11312bbe9c6f156d641707c6b22ed2e784fc1 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Tue, 7 Jan 2014 19:08:16 -0600 Subject: [PATCH 10/10] Resize after infinite scroll --- dist/css/ionic.css | 20 +- dist/js/ionic-angular.js | 53 +- dist/js/ionic.js | 588 +------------------ js/ext/angular/src/directive/ionicContent.js | 4 +- 4 files changed, 71 insertions(+), 594 deletions(-) diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 00ee0f0e1e..d3b2713fc7 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -3,7 +3,7 @@ * Copyright 2013 Drifty Co. * http://drifty.com/ * - * Ionic, v0.9.17 + * Ionic, v{{ VERSION }} * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * @@ -2700,6 +2700,24 @@ body, .ionic-body { -moz-animation-name: refresh-spin; animation-name: refresh-spin; } +infinite-scroll .scroll-infinite { + overflow: hidden; + margin-top: -70px; + height: 60px; + position: relative; } + +.scroll-infinite-content { + position: absolute; + left: 0; + bottom: 15px; + width: 100%; + text-align: center; + font-size: 30px; + color: #666666; } + +infinite-scroll.active .scroll-infinite { + margin-top: -30px; } + .overflow-scroll { overflow-x: hidden; overflow-y: scroll; diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index 18d5d69337..a23047b823 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -2,7 +2,7 @@ * Copyright 2013 Drifty Co. * http://drifty.com/ * - * Ionic, v0.9.17 + * Ionic, v{{ VERSION }} * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * @@ -736,6 +736,8 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) onScroll: '&', onScrollComplete: '&', refreshComplete: '=', + onInfiniteScroll: '=', + infiniteScrollDistance: '@', scroll: '@', hasScrollX: '@', hasScrollY: '@', @@ -828,7 +830,38 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) $scope.$parent.scrollView = sv; }); - + // Check if this supports infinite scrolling and listen for scroll events + // to trigger the infinite scrolling + var infiniteScroll = $element.find('infinite-scroll'); + var infiniteStarted = false; + if(infiniteScroll) { + // Parse infinite scroll distance + var distance = attr.infiniteScrollDistance || '1%'; + var maxScroll; + if(distance.indexOf('%')) { + // It's a multiplier + maxScroll = function() { + return sv.getScrollMax().top * ( 1 - parseInt(distance, 10) / 100 ); + }; + } else { + // It's a pixel value + maxScroll = function() { + return sv.getScrollMax().top - parseInt(distance, 10); + }; + } + $element.bind('scroll', function(e) { + if( sv && !infiniteStarted && (sv.getValues().top > maxScroll() ) ) { + infiniteStarted = true; + infiniteScroll.addClass('active'); + var cb = function() { + sv.resize(); + infiniteStarted = false; + infiniteScroll.removeClass('active'); + }; + $scope.$apply(angular.bind($scope, $scope.onInfiniteScroll, cb)); + } + }); + } } // if padding attribute is true, then add padding if it wasn't added to the .scroll @@ -858,8 +891,15 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) transclude: true, template: '
' }; -}); +}) +.directive('infiniteScroll', function() { + return { + restrict: 'E', + replace: false, + template: '
' + }; +}); })(); ; @@ -868,7 +908,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) angular.module('ionic.ui.list', ['ngAnimate']) -.directive('item', ['$timeout', function($timeout) { +.directive('item', ['$timeout', '$parse', function($timeout, $parse) { return { restrict: 'E', require: '?^list', @@ -887,7 +927,7 @@ angular.module('ionic.ui.list', ['ngAnimate']) reorderIcon: '@' }, - template: '
\ + template: '
\
\ \
\ @@ -912,7 +952,8 @@ angular.module('ionic.ui.list', ['ngAnimate']) // Set this item's class, first from the item directive attr, and then the list attr if item not set $scope.itemClass = $scope.itemType || $parentScope.itemType; - + var getter = $parse( $scope.itemClass ); + $scope.parsedClass = angular.bind( $scope, getter, $scope.$parent ); // 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") { diff --git a/dist/js/ionic.js b/dist/js/ionic.js index c3111ac4bd..7826e26588 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -2,7 +2,7 @@ * Copyright 2013 Drifty Co. * http://drifty.com/ * - * Ionic, v0.9.17 + * Ionic, v{{ VERSION }} * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * @@ -16,7 +16,7 @@ window.ionic = { controllers: {}, views: {}, - version: '0.9.17' + version: '{{ VERSION }}' };; (function(ionic) { @@ -4933,590 +4933,6 @@ ionic.views.Scroll = ionic.views.View.inherit({ } }); -})(ionic); -; -/* - * Adapted from Swipe.js 2.0 - * - * Brad Birdsall - * Copyright 2013, MIT License - * -*/ - -(function(ionic) { -'use strict'; - -ionic.views.Slider = ionic.views.View.inherit({ - initialize: function (options) { - // utilities - var noop = function() {}; // simple no operation function - var offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution - - // check browser capabilities - var browser = { - addEventListener: !!window.addEventListener, - touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch, - transitions: (function(temp) { - var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition']; - for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true; - return false; - })(document.createElement('swipe')) - }; - - - var container = options.el; - - // quit if no root element - if (!container) return; - var element = container.children[0]; - var slides, slidePos, width, length; - options = options || {}; - var index = parseInt(options.startSlide, 10) || 0; - var speed = options.speed || 300; - options.continuous = options.continuous !== undefined ? options.continuous : true; - - function setup() { - - // cache slides - slides = element.children; - length = slides.length; - - // set continuous to false if only one slide - if (slides.length < 2) options.continuous = false; - - //special case if two slides - if (browser.transitions && options.continuous && slides.length < 3) { - element.appendChild(slides[0].cloneNode(true)); - element.appendChild(element.children[1].cloneNode(true)); - slides = element.children; - } - - // create an array to store current positions of each slide - slidePos = new Array(slides.length); - - // determine width of each slide - width = container.getBoundingClientRect().width || container.offsetWidth; - - element.style.width = (slides.length * width) + 'px'; - - // stack elements - var pos = slides.length; - while(pos--) { - - var slide = slides[pos]; - - slide.style.width = width + 'px'; - slide.setAttribute('data-index', pos); - - if (browser.transitions) { - slide.style.left = (pos * -width) + 'px'; - move(pos, index > pos ? -width : (index < pos ? width : 0), 0); - } - - } - - // reposition elements before and after index - if (options.continuous && browser.transitions) { - move(circle(index-1), -width, 0); - move(circle(index+1), width, 0); - } - - if (!browser.transitions) element.style.left = (index * -width) + 'px'; - - container.style.visibility = 'visible'; - - options.slidesChanged && options.slidesChanged(); - } - - function prev() { - - if (options.continuous) slide(index-1); - else if (index) slide(index-1); - - } - - function next() { - - if (options.continuous) slide(index+1); - else if (index < slides.length - 1) slide(index+1); - - } - - function circle(index) { - - // a simple positive modulo using slides.length - return (slides.length + (index % slides.length)) % slides.length; - - } - - function slide(to, slideSpeed) { - - // do nothing if already on requested slide - if (index == to) return; - - if (browser.transitions) { - - var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward - - // get the actual position of the slide - if (options.continuous) { - var natural_direction = direction; - direction = -slidePos[circle(to)] / width; - - // if going forward but to < index, use to = slides.length + to - // if going backward but to > index, use to = -slides.length + to - if (direction !== natural_direction) to = -direction * slides.length + to; - - } - - var diff = Math.abs(index-to) - 1; - - // move all the slides between index and to in the right direction - while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0); - - to = circle(to); - - move(index, width * direction, slideSpeed || speed); - move(to, 0, slideSpeed || speed); - - if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place - - } else { - - to = circle(to); - animate(index * -width, to * -width, slideSpeed || speed); - //no fallback for a circular continuous if the browser does not accept transitions - } - - index = to; - offloadFn(options.callback && options.callback(index, slides[index])); - } - - function move(index, dist, speed) { - - translate(index, dist, speed); - slidePos[index] = dist; - - } - - function translate(index, dist, speed) { - - var slide = slides[index]; - var style = slide && slide.style; - - if (!style) return; - - style.webkitTransitionDuration = - style.MozTransitionDuration = - style.msTransitionDuration = - style.OTransitionDuration = - style.transitionDuration = speed + 'ms'; - - style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)'; - style.msTransform = - style.MozTransform = - style.OTransform = 'translateX(' + dist + 'px)'; - - } - - function animate(from, to, speed) { - - // if not an animation, just reposition - if (!speed) { - - element.style.left = to + 'px'; - return; - - } - - var start = +new Date; - - var timer = setInterval(function() { - - var timeElap = +new Date - start; - - if (timeElap > speed) { - - element.style.left = to + 'px'; - - if (delay) begin(); - - options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); - - clearInterval(timer); - return; - - } - - element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px'; - - }, 4); - - } - - // setup auto slideshow - var delay = options.auto || 0; - var interval; - - function begin() { - - interval = setTimeout(next, delay); - - } - - function stop() { - - delay = 0; - clearTimeout(interval); - - } - - - // setup initial vars - var start = {}; - var delta = {}; - var isScrolling; - - // setup event capturing - var events = { - - handleEvent: function(event) { - if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') { - event.touches = [{ - pageX: event.pageX, - pageY: event.pageY - }]; - } - - switch (event.type) { - case 'mousedown': this.start(event); break; - case 'touchstart': this.start(event); break; - case 'touchmove': this.move(event); break; - case 'mousemove': this.move(event); break; - case 'touchend': offloadFn(this.end(event)); break; - case 'mouseup': offloadFn(this.end(event)); break; - case 'webkitTransitionEnd': - case 'msTransitionEnd': - case 'oTransitionEnd': - case 'otransitionend': - case 'transitionend': offloadFn(this.transitionEnd(event)); break; - case 'resize': offloadFn(setup); break; - } - - if (options.stopPropagation) event.stopPropagation(); - - }, - start: function(event) { - - var touches = event.touches[0]; - - // measure start values - start = { - - // get initial touch coords - x: touches.pageX, - y: touches.pageY, - - // store time to determine touch duration - time: +new Date - - }; - - // used for testing first move event - isScrolling = undefined; - - // reset delta and end measurements - delta = {}; - - // attach touchmove and touchend listeners - if(browser.touch) { - element.addEventListener('touchmove', this, false); - element.addEventListener('touchend', this, false); - } else { - element.addEventListener('mousemove', this, false); - element.addEventListener('mouseup', this, false); - document.addEventListener('mouseup', this, false); - } - }, - move: function(event) { - - // ensure swiping with one touch and not pinching - if ( event.touches.length > 1 || event.scale && event.scale !== 1) return - - if (options.disableScroll) event.preventDefault(); - - var touches = event.touches[0]; - - // measure change in x and y - delta = { - x: touches.pageX - start.x, - y: touches.pageY - start.y - } - - // determine if scrolling test has run - one time test - if ( typeof isScrolling == 'undefined') { - isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) ); - } - - // if user is not trying to scroll vertically - if (!isScrolling) { - - // prevent native scrolling - event.preventDefault(); - - // stop slideshow - stop(); - - // increase resistance if first or last slide - if (options.continuous) { // we don't add resistance at the end - - translate(circle(index-1), delta.x + slidePos[circle(index-1)], 0); - translate(index, delta.x + slidePos[index], 0); - translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0); - - } else { - - delta.x = - delta.x / - ( (!index && delta.x > 0 // if first slide and sliding left - || index == slides.length - 1 // or if last slide and sliding right - && delta.x < 0 // and if sliding at all - ) ? - ( Math.abs(delta.x) / width + 1 ) // determine resistance level - : 1 ); // no resistance if false - - // translate 1:1 - translate(index-1, delta.x + slidePos[index-1], 0); - translate(index, delta.x + slidePos[index], 0); - translate(index+1, delta.x + slidePos[index+1], 0); - } - - } - - }, - end: function(event) { - - // measure duration - var duration = +new Date - start.time; - - // determine if slide attempt triggers next/prev slide - var isValidSlide = - Number(duration) < 250 // if slide duration is less than 250ms - && Math.abs(delta.x) > 20 // and if slide amt is greater than 20px - || Math.abs(delta.x) > width/2; // or if slide amt is greater than half the width - - // determine if slide attempt is past start and end - var isPastBounds = - !index && delta.x > 0 // if first slide and slide amt is greater than 0 - || index == slides.length - 1 && delta.x < 0; // or if last slide and slide amt is less than 0 - - if (options.continuous) isPastBounds = false; - - // determine direction of swipe (true:right, false:left) - var direction = delta.x < 0; - - // if not scrolling vertically - if (!isScrolling) { - - if (isValidSlide && !isPastBounds) { - - if (direction) { - - if (options.continuous) { // we need to get the next in this direction in place - - move(circle(index-1), -width, 0); - move(circle(index+2), width, 0); - - } else { - move(index-1, -width, 0); - } - - move(index, slidePos[index]-width, speed); - move(circle(index+1), slidePos[circle(index+1)]-width, speed); - index = circle(index+1); - - } else { - if (options.continuous) { // we need to get the next in this direction in place - - move(circle(index+1), width, 0); - move(circle(index-2), -width, 0); - - } else { - move(index+1, width, 0); - } - - move(index, slidePos[index]+width, speed); - move(circle(index-1), slidePos[circle(index-1)]+width, speed); - index = circle(index-1); - - } - - options.callback && options.callback(index, slides[index]); - - } else { - - if (options.continuous) { - - move(circle(index-1), -width, speed); - move(index, 0, speed); - move(circle(index+1), width, speed); - - } else { - - move(index-1, -width, speed); - move(index, 0, speed); - move(index+1, width, speed); - } - - } - - } - - // kill touchmove and touchend event listeners until touchstart called again - if(browser.touch) { - element.removeEventListener('touchmove', events, false) - element.removeEventListener('touchend', events, false) - } else { - element.removeEventListener('mousemove', events, false) - element.removeEventListener('mouseup', events, false) - document.removeEventListener('mouseup', events, false); - } - - }, - transitionEnd: function(event) { - - if (parseInt(event.target.getAttribute('data-index'), 10) == index) { - - if (delay) begin(); - - options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); - - } - - } - - } - - // Public API - this.setup = function() { - setup(); - }; - - this.slide = function(to, speed) { - // cancel slideshow - stop(); - - slide(to, speed); - }; - - this.prev = function() { - // cancel slideshow - stop(); - - prev(); - }; - - this.next = function() { - // cancel slideshow - stop(); - - next(); - }; - - this.stop = function() { - // cancel slideshow - stop(); - }; - - this.getPos = function() { - // return current index position - return index; - }; - - this.getNumSlides = function() { - // return total number of slides - return length; - }; - - this.kill = function() { - // cancel slideshow - stop(); - - // reset element - element.style.width = ''; - element.style.left = ''; - - // reset slides - var pos = slides.length; - while(pos--) { - - var slide = slides[pos]; - slide.style.width = ''; - slide.style.left = ''; - - if (browser.transitions) translate(pos, 0, 0); - - } - - // removed event listeners - if (browser.addEventListener) { - - // remove current event listeners - element.removeEventListener('touchstart', events, false); - element.removeEventListener('webkitTransitionEnd', events, false); - element.removeEventListener('msTransitionEnd', events, false); - element.removeEventListener('oTransitionEnd', events, false); - element.removeEventListener('otransitionend', events, false); - element.removeEventListener('transitionend', events, false); - window.removeEventListener('resize', events, false); - - } - else { - - window.onresize = null; - - } - }; - - this.load = function() { - // trigger setup - setup(); - - // start auto slideshow if applicable - if (delay) begin(); - - - // add event listeners - if (browser.addEventListener) { - - // set touchstart event on element - if (browser.touch) { - element.addEventListener('touchstart', events, false); - } else { - element.addEventListener('mousedown', events, false); - } - - if (browser.transitions) { - element.addEventListener('webkitTransitionEnd', events, false); - element.addEventListener('msTransitionEnd', events, false); - element.addEventListener('oTransitionEnd', events, false); - element.addEventListener('otransitionend', events, false); - element.addEventListener('transitionend', events, false); - } - - // set resize event on window - window.addEventListener('resize', events, false); - - } else { - - window.onresize = function () { setup() }; // to play nice with old IE - - } - } - - } -}); - })(ionic); ; (function(ionic) { diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index 9511521d38..6b12782c8e 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -124,7 +124,8 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) $scope.$parent.scrollView = sv; }); - // Infinite scroll + // Check if this supports infinite scrolling and listen for scroll events + // to trigger the infinite scrolling var infiniteScroll = $element.find('infinite-scroll'); var infiniteStarted = false; if(infiniteScroll) { @@ -147,6 +148,7 @@ angular.module('ionic.ui.content', ['ionic.ui.service']) infiniteStarted = true; infiniteScroll.addClass('active'); var cb = function() { + sv.resize(); infiniteStarted = false; infiniteScroll.removeClass('active'); };