diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..1d48b78 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,50 @@ + + + + + GitHub ยท angular-pull-to-refresh + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + diff --git a/demo/script.js b/demo/script.js new file mode 100644 index 0000000..aa31fa3 --- /dev/null +++ b/demo/script.js @@ -0,0 +1,20 @@ +'use strict'; + +angular.module('myApp', ['mgcrea.pullToRefresh']); + +angular.module('myApp') + + .controller('AppCtrl', function($scope, $q) { + + $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']; + + $scope.onReload = function() { + console.warn('reload'); + var deferred = $q.defer(); + setTimeout(function() { + deferred.resolve(true); + }, 1000); + return deferred.promise; + }; + + }); diff --git a/demo/style.css b/demo/style.css new file mode 100644 index 0000000..ff99dc9 --- /dev/null +++ b/demo/style.css @@ -0,0 +1,33 @@ + +.content { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: auto; + background-color: #fff; + -webkit-backface-visibility: none; + -webkit-overflow-scrolling: touch; +} + +.navbar-fixed-top ~ .content { + top: 50px; +} + +.list-group-table { + margin-bottom: 0px; +} +.list-group-table .list-group-item { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-right-width: 0; + border-left-width: 0; + border-radius: 0px; + background: none; + height: 45px; +} +.list-group-table .list-group-item:first-child { + border-top-width: 0; +} \ No newline at end of file diff --git a/dist/angular-pull-to-refresh.js b/dist/angular-pull-to-refresh.js index c4ce9b0..7db063d 100644 --- a/dist/angular-pull-to-refresh.js +++ b/dist/angular-pull-to-refresh.js @@ -1,12 +1,13 @@ /** * angular-pull-to-refresh - * @version v0.3.0 - 2013-11-14 + * @version v0.3.0 - 2015-11-26 * @link https://github.com/mgcrea/angular-pull-to-refresh * @author Olivier Louvignes * @license MIT License, http://www.opensource.org/licenses/MIT */ (function (window, document, undefined) { 'use strict'; + // Source: src/angular-pull-to-refresh.js angular.module('mgcrea.pullToRefresh', []).constant('pullToRefreshConfig', { treshold: 60, debounce: 400, @@ -25,7 +26,8 @@ '$timeout', '$q', 'pullToRefreshConfig', - function ($compile, $timeout, $q, pullToRefreshConfig) { + '$injector', + function ($compile, $timeout, $q, pullToRefreshConfig, $injector) { return { scope: true, restrict: 'A', @@ -33,12 +35,43 @@ templateUrl: 'angular-pull-to-refresh.tpl.html', compile: function compile(tElement, tAttrs, transclude) { return function postLink(scope, iElement, iAttrs) { - var config = angular.extend({}, pullToRefreshConfig, iAttrs); + var customConfig = scope.$eval(iAttrs.pullToRefreshConfig); + var config = angular.extend({}, pullToRefreshConfig, customConfig, iAttrs); var scrollElement = iElement.parent(); var ptrElement = window.ptr = iElement.children()[0]; + // Initialize isolated scope vars scope.text = config.text; scope.icon = config.icon; scope.status = 'pull'; + var translateStates = function ($translate) { + var translateKey = 'PULL2REF.'; + var states = { + pull: $translate(translateKey + 'PULL'), + release: $translate(translateKey + 'RELEASE'), + loading: $translate(translateKey + 'LOADING') + }; + if (typeof states.pull === 'string') { + scope.text = states; + } + var deferTraslate = function (name) { + if (states[name].then) { + states[name].then(function (translated) { + scope.text[name] = translated; + }); + } + }; + deferTraslate('pull'); + deferTraslate('release'); + deferTraslate('loading'); + if (typeof states.pull === 'string') { + scope.text = states; + } + }; + try { + // add optional dependency $translate + translateStates($injector.get('$translate')); + } catch (e) { + } var setStatus = function (status) { shouldReload = status === 'release'; scope.$apply(function () { @@ -46,8 +79,36 @@ }); }; var shouldReload = false; + function getTransformStyle(translate) { + if (isUsingOverflowScroll) { + return {}; + } + var translateFn = 'translateY(' + translate + 'px)'; + return { + '-webkit-transform': translateFn, + 'transform': translateFn + }; + } + function getTouch(evt) { + var event = evt; + if (event.originalEvent) { + event = event.originalEvent; + } + return event.touches[0]; + } + var isUsingOverflowScroll = true; + var startY; + iElement.bind('touchstart', function (ev) { + startY = getTouch(ev).pageY; + }); iElement.bind('touchmove', function (ev) { var top = scrollElement[0].scrollTop; + var currentY = getTouch(ev).pageY; + if (top === 0) { + isUsingOverflowScroll = false; + top = startY - currentY; + } + iElement.css(getTransformStyle(currentY - startY)); if (top < -config.treshold && !shouldReload) { setStatus('release'); } else if (top > -config.treshold && shouldReload) { @@ -57,6 +118,7 @@ iElement.bind('touchend', function (ev) { if (!shouldReload) return; + iElement.css(getTransformStyle(0)); ptrElement.style.webkitTransitionDuration = 0; ptrElement.style.margin = '0 auto'; setStatus('loading'); @@ -79,6 +141,7 @@ }; } ]); + // Source: src/angular-pull-to-refresh.tpl.js angular.module('mgcrea.pullToRefresh').run([ '$templateCache', function ($templateCache) { diff --git a/dist/angular-pull-to-refresh.min.js b/dist/angular-pull-to-refresh.min.js index 4e49541..6bdc382 100644 --- a/dist/angular-pull-to-refresh.min.js +++ b/dist/angular-pull-to-refresh.min.js @@ -1,8 +1,8 @@ /** * angular-pull-to-refresh - * @version v0.3.0 - 2013-11-14 + * @version v0.3.0 - 2015-11-26 * @link https://github.com/mgcrea/angular-pull-to-refresh * @author Olivier Louvignes * @license MIT License, http://www.opensource.org/licenses/MIT */ -!function(a){"use strict";angular.module("mgcrea.pullToRefresh",[]).constant("pullToRefreshConfig",{treshold:60,debounce:400,text:{pull:"pull to refresh",release:"release to refresh",loading:"refreshing..."},icon:{pull:"fa fa-arrow-down",release:"fa fa-arrow-up",loading:"fa fa-refresh fa-spin"}}).directive("pullToRefresh",["$compile","$timeout","$q","pullToRefreshConfig",function(b,c,d,e){return{scope:!0,restrict:"A",transclude:!0,templateUrl:"angular-pull-to-refresh.tpl.html",compile:function(){return function(b,f,g){var h=angular.extend({},e,g),i=f.parent(),j=a.ptr=f.children()[0];b.text=h.text,b.icon=h.icon,b.status="pull";var k=function(a){l="release"===a,b.$apply(function(){b.status=a})},l=!1;f.bind("touchmove",function(){var a=i[0].scrollTop;a<-h.treshold&&!l?k("release"):a>-h.treshold&&l&&k("pull")}),f.bind("touchend",function(){if(l){j.style.webkitTransitionDuration=0,j.style.margin="0 auto",k("loading");var a=+new Date;d.when(b.$eval(g.pullToRefresh)).then(function(){var d=+new Date-a;c(function(){j.style.margin="",j.style.webkitTransitionDuration="",b.status="pull"},d\n  \n \n\n
\n')}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("mgcrea.pullToRefresh",[]).constant("pullToRefreshConfig",{treshold:60,debounce:400,text:{pull:"pull to refresh",release:"release to refresh",loading:"refreshing..."},icon:{pull:"fa fa-arrow-down",release:"fa fa-arrow-up",loading:"fa fa-refresh fa-spin"}}).directive("pullToRefresh",["$compile","$timeout","$q","pullToRefreshConfig","$injector",function(b,c,d,e,f){return{scope:!0,restrict:"A",transclude:!0,templateUrl:"angular-pull-to-refresh.tpl.html",compile:function(b,g,h){return function(b,g,h){function i(a){if(t)return{};var b="translateY("+a+"px)";return{"-webkit-transform":b,transform:b}}function j(a){var b=a;return b.originalEvent&&(b=b.originalEvent),b.touches[0]}var k=b.$eval(h.pullToRefreshConfig),l=angular.extend({},e,k,h),m=g.parent(),n=a.ptr=g.children()[0];b.text=l.text,b.icon=l.icon,b.status="pull";var o=function(a){var c="PULL2REF.",d={pull:a(c+"PULL"),release:a(c+"RELEASE"),loading:a(c+"LOADING")};"string"==typeof d.pull&&(b.text=d);var e=function(a){d[a].then&&d[a].then(function(c){b.text[a]=c})};e("pull"),e("release"),e("loading"),"string"==typeof d.pull&&(b.text=d)};try{o(f.get("$translate"))}catch(p){}var q,r=function(a){s="release"===a,b.$apply(function(){b.status=a})},s=!1,t=!0;g.bind("touchstart",function(a){q=j(a).pageY}),g.bind("touchmove",function(a){var b=m[0].scrollTop,c=j(a).pageY;0===b&&(t=!1,b=q-c),g.css(i(c-q)),b<-l.treshold&&!s?r("release"):b>-l.treshold&&s&&r("pull")}),g.bind("touchend",function(a){if(s){g.css(i(0)),n.style.webkitTransitionDuration=0,n.style.margin="0 auto",r("loading");var e=+new Date;d.when(b.$eval(h.pullToRefresh)).then(function(){var a=+new Date-e;c(function(){n.style.margin="",n.style.webkitTransitionDuration="",b.status="pull"},a\n  \n \n\n
\n')}])}(window,document); \ No newline at end of file diff --git a/package.json b/package.json index a01697f..d6456fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-pull-to-refresh", - "version": "0.3.0", + "version": "0.3.1", "description": "angular-pull-to-refresh", "keywords": [ "angular" @@ -34,17 +34,18 @@ "grunt-contrib-less": "~0.8.2", "grunt-contrib-uglify": "~0.2.7", "grunt-contrib-watch": "~0.5.3", - "grunt-karma": "~0.6.2", + "grunt-karma": "~0.9.0", "grunt-ngmin": "0.0.3", "grunt-open": "~0.2.2", - "karma": "~0.10.4", + "karma": "~0.12.0", "karma-chrome-launcher": "~0.1.0", "karma-coffee-preprocessor": "~0.1.0", "karma-firefox-launcher": "~0.1.0", "karma-html2js-preprocessor": "~0.1.0", - "karma-jasmine": "~0.1.3", + "karma-jasmine": "~0.2.0", "karma-phantomjs-launcher": "~0.1.0", - "karma-requirejs": "~0.1.0", + "requirejs": "~2.1.0", + "karma-requirejs": "~0.2.0", "karma-script-launcher": "~0.1.0", "load-grunt-tasks": "~0.2.0", "time-grunt": "~0.2.1" diff --git a/readme.md b/readme.md index 445089b..b20eada 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # [ngPullToRefresh](http://mgcrea.github.com/angular-pull-to-refresh) [![Build Status](https://secure.travis-ci.org/mgcrea/angular-pull-to-refresh.png?branch=master)](http://travis-ci.org/#!/mgcrea/angular-pull-to-refresh) -`mgcrea.pullToRefresh` is a module providing a simple css-only pull-to-refresh component leveraging native style momentum scrolling `-webkit-overflow-scroll: touch`. +`mgcrea.pullToRefresh` is a module providing a simple css-only pull-to-refresh component, it can use native style momentum scrolling `-webkit-overflow-scroll: touch`, or use javascript event data to create touch movement animation. The directive has a configurable built-in debounce system (400ms treshold by default) and can leverage angular `$q` promises. @@ -32,15 +32,17 @@ angular.module('myApp', ['mgcrea.pullToRefresh']); ## Examples -[![Demo](http://mgcrea.github.io/angular-pull-to-refresh/demo.gif)](http://mgcrea.github.com/angular-pull-to-refresh) +[![Demo](http://mgcrea.github.io/angular-pull-to-refresh/demo.gif)](https://rawgit.com/freefri/angular-pull-to-refresh/master/demo/index.html) -You can check out a working demo there (only works on touch devices): +You can check out a working demo there (only works on touch devices, with Firefox, you can switch to "Responsive Design View" and enable "Simulate Touch Events"): -+ **http://plnkr.co/edit/C4dV0cvWxvrfR6y0uCxI?p=preview** ++ **https://rawgit.com/freefri/angular-pull-to-refresh/master/demo/index.html** + +You can include an optional attribute with some configurarion details pull-to-refresh-config: ``` html
-
    +
@@ -51,6 +53,20 @@ angular.module('myApp') .controller('AppCtrl', function($scope, $q) { + $scope.pullConfig = { + treshold: 60, + debounce: 400, + text: { + pull: 'pull to refresh', + release: 'release to refresh', + loading: 'refreshing...' + }, + icon: { + pull: 'fa fa-arrow-down', + release: 'fa fa-arrow-up', + loading: 'fa fa-refresh fa-spin' + } + } $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']; $scope.onReload = function() { @@ -69,17 +85,28 @@ angular.module('myApp') ## Contributing -Please submit all pull requests the against master branch. If your unit test contains JavaScript patches or features, you should include relevant unit tests. Thanks! +Please submit all pull requests the against master branch. It would be nice if we include relevant unit tests. Thanks! ## Authors +**Freefri** + ++ http://github.com/freefri + **Olivier Louvignes** + http://olouv.com + http://github.com/mgcrea +## Tested devices + ++ Firefox desktop 42.0 (under "Responsive Design View" and enabling "Simulate Touch Events") ++ Firefox 37.0 on Android ++ Android stock browser Android 5.1.1 on Samsung Galaxy S7 ++ Chrome 46 on Android 5.1.1 ++ Safari on iPhone 5 (iOS 8.1) ## Copyright and license diff --git a/src/angular-pull-to-refresh.js b/src/angular-pull-to-refresh.js index 148209c..e652ac9 100644 --- a/src/angular-pull-to-refresh.js +++ b/src/angular-pull-to-refresh.js @@ -17,7 +17,7 @@ angular.module('mgcrea.pullToRefresh', []) } }) - .directive('pullToRefresh', function($compile, $timeout, $q, pullToRefreshConfig) { + .directive('pullToRefresh', function($compile, $timeout, $q, pullToRefreshConfig, $injector) { return { scope: true, @@ -28,7 +28,8 @@ angular.module('mgcrea.pullToRefresh', []) return function postLink(scope, iElement, iAttrs) { - var config = angular.extend({}, pullToRefreshConfig, iAttrs); + var customConfig = scope.$eval(iAttrs.pullToRefreshConfig); + var config = angular.extend({}, pullToRefreshConfig, customConfig, iAttrs); var scrollElement = iElement.parent(); var ptrElement = window.ptr = iElement.children()[0]; @@ -37,6 +38,35 @@ angular.module('mgcrea.pullToRefresh', []) scope.icon = config.icon; scope.status = 'pull'; + var translateStates = function ($translate) { + var translateKey = 'PULL2REF.'; + var states = { + pull: $translate(translateKey + 'PULL'), + release: $translate(translateKey + 'RELEASE'), + loading: $translate(translateKey + 'LOADING') + }; + if (typeof states.pull === 'string') { + scope.text = states; + } + var deferTraslate = function (name) { + if (states[name].then) { + states[name].then(function (translated) { + scope.text[name] = translated; + }); + } + }; + deferTraslate('pull'); + deferTraslate('release'); + deferTraslate('loading'); + if (typeof states.pull === 'string') { + scope.text = states; + } + }; + try { + // add optional dependency $translate + translateStates($injector.get('$translate')); + } catch (e) {} + var setStatus = function(status) { shouldReload = status === 'release'; scope.$apply(function() { @@ -45,17 +75,55 @@ angular.module('mgcrea.pullToRefresh', []) }; var shouldReload = false; - iElement.bind('touchmove', function(ev) { + function getTransformStyle(translate) { + if (isUsingOverflowScroll) { + return {}; + } + var translateFn = 'translateY(' + translate + 'px)'; + return { + '-webkit-transform': translateFn, + 'transform': translateFn + }; + } + function getDrag(evt) { + var event = evt; + if (event.originalEvent) { + event = event.originalEvent; + } + return (event.touches && event.touches.length > 0) ? event.touches[0] : event; + } + var isUsingOverflowScroll = true; + var startY; + var isTracking = false; + iElement.bind('touchstart mousedown', function (ev) { + startY = getDrag(ev).pageY; + isTracking = true; + ev.preventDefault() + }); + iElement.bind('touchmove mousemove', function (ev) { + if (!isTracking) { + return; + } + ev.preventDefault() var top = scrollElement[0].scrollTop; - if(top < -config.treshold && !shouldReload) { + var currentY = getDrag(ev).pageY; + if (top === 0) { + isUsingOverflowScroll = false; + top = startY - currentY; + } + iElement.css(getTransformStyle(currentY - startY)); + if (top < -config.treshold && !shouldReload) { setStatus('release'); } else if(top > -config.treshold && shouldReload) { setStatus('pull'); } }); - - iElement.bind('touchend', function(ev) { - if(!shouldReload) return; + iElement.bind('touchend mouseup', function (ev) { + isTracking = false; + if (!shouldReload) { + return; + } + iElement.css(getTransformStyle(0)); ptrElement.style.webkitTransitionDuration = 0; ptrElement.style.margin = '0 auto'; setStatus('loading'); @@ -73,6 +141,7 @@ angular.module('mgcrea.pullToRefresh', []) }); scope.$on('$destroy', function() { + iElement.unbind('touchstart'); iElement.unbind('touchmove'); iElement.unbind('touchend'); }); diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index 914e3f6..0000000 --- a/test/helpers.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -beforeEach(function() { - this.addMatchers({ - toEquals: function(expected) { - this.message = function() { - return 'Expected "' + angular.mock.dump(this.actual) + '" to equal "' + angular.mock.dump(expected) + '".'; - }; - return angular.equals(this.actual, expected); - }, - toHaveClass: function(cls) { - this.message = function() { - return 'Expected "' + angular.mock.dump(this.actual) + '" to have class "' + cls + '".'; - }; - return this.actual.hasClass(cls); - } - }); -}); diff --git a/test/karma.conf.js b/test/karma.conf.js index d1081a4..ee20091 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -18,7 +18,6 @@ module.exports = function(config) { 'bower_components/angular-mocks/angular-mocks.js', 'src/*.js', 'bower_components/jquery/jquery.js', - 'test/helpers.js', 'test/spec/*.js' ], diff --git a/test/spec/angular-pull-to-refresh.js b/test/spec/angular-pull-to-refresh.js index d62664f..053b526 100644 --- a/test/spec/angular-pull-to-refresh.js +++ b/test/spec/angular-pull-to-refresh.js @@ -20,19 +20,22 @@ describe('mgcrea.pullToRefresh', function() { scope.$destroy(); }); - var templates = { - basic: { - scope: {states: ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']}, - element: '
' + - '
    ' + - '
  • ' + - '
' + - '
' - } + var optionalAttrs = ''; + var templates = function () { + return { + basic: { + scope: {states: ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming']}, + element: '
' + + '
    ' + + '
  • ' + + '
' + + '
' + } + }; }; function compileDirective(template, locals) { - template = templates[template]; + template = templates()[template]; angular.extend(scope, template.scope, locals); var element = $(template.element).appendTo(sandbox); element = $compile(element)(scope); @@ -46,6 +49,17 @@ describe('mgcrea.pullToRefresh', function() { expect(ptrElement.length).toBe(1); var config = $injector.get('pullToRefreshConfig'); expect(ptrElement.children('span').html()).toBe(config.text.pull); + expect(ptrElement.children('i').hasClass('fa fa-arrow-down')).toBeTruthy(); + }); + + it('should correctly initialize and attach to DOM adding custom icons', function () { + optionalAttrs = 'pull-to-refresh-config="{icon:{pull: \'glyphicon glyphicon-arrow-down\',release: \'glyphicon glyphicon-arrow-up\',loading: \'glyphicon glyphicon-refresh fa-spin\'}}"'; + var elm = compileDirective('basic'); + var ptrElement = elm.find('.pull-to-refresh'); + expect(ptrElement.length).toBe(1); + var config = $injector.get('pullToRefreshConfig'); + expect(ptrElement.children('span').html()).toBe(config.text.pull); + expect(ptrElement.children('i').hasClass('glyphicon glyphicon-arrow-down')).toBeTruthy(); }); });