├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── bower.json ├── dist ├── angular-pull-to-refresh.css ├── angular-pull-to-refresh.js ├── angular-pull-to-refresh.min.css └── angular-pull-to-refresh.min.js ├── license.md ├── package.json ├── readme.md ├── src ├── angular-pull-to-refresh.js ├── angular-pull-to-refresh.less ├── angular-pull-to-refresh.tpl.html └── angular-pull-to-refresh.tpl.js └── test ├── .jshintrc ├── helpers.js ├── karma.conf.js └── spec └── angular-pull-to-refresh.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sublime-project 3 | *.sublime-workspace 4 | *.todo 5 | bower_components/ 6 | node_modules/ 7 | docs/ 8 | !.gitignore 9 | 10 | src/*.css 11 | scr/*.tpl.js 12 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "node": true, 4 | "browser": true, 5 | "jquery": true, 6 | "devel": false, 7 | "esnext": true, 8 | "bitwise": true, 9 | 10 | "camelcase": false, 11 | "curly": false, 12 | "eqeqeq": true, 13 | "immed": true, 14 | "indent": 2, 15 | "latedef": false, 16 | "newcap": true, 17 | "noarg": true, 18 | "quotmark": "single", 19 | "regexp": true, 20 | "undef": true, 21 | "unused": false, 22 | "strict": true, 23 | "trailing": true, 24 | "smarttabs": true, 25 | 26 | "boss": false, 27 | "laxbreak": false, 28 | "eqnull": false, 29 | "expr": true, 30 | 31 | "globals": { 32 | "angular": false 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - export PHANTOMJS_BIN=/usr/local/phantomjs/bin/phantomjs 8 | - sh -e /etc/init.d/xvfb start 9 | - sleep 3 # give xvfb some time to start 10 | - npm install -g grunt-cli bower 11 | - bower install 12 | 13 | script: 14 | - grunt test 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 8 | 9 | ## Modifying the code 10 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 11 | 12 | Test that Grunt's CLI and Bower are installed by running `grunt --version` and `bower --version`. If the commands aren't found, run `npm install -g grunt-cli bower`. For more information about installing the tools, see the [getting started with Grunt guide](http://gruntjs.com/getting-started) or [bower.io](http://bower.io/) respectively. 13 | 14 | 1. Fork and clone the repo. 15 | 1. Run `npm install` to install all build dependencies (including Grunt). 16 | 1. Run `bower install` to install the front-end dependencies. 17 | 1. Run `grunt` to grunt this project. 18 | 19 | Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken. 20 | 21 | ## Submitting pull requests 22 | 23 | 1. Create a new branch, please don't work in your `master` branch directly. 24 | 1. Add failing tests for the change you want to make. Run `grunt` to see the tests fail. 25 | 1. Fix stuff. 26 | 1. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 27 | 1. Open `test/*.html` unit test file(s) in actual browser to ensure tests pass everywhere. 28 | 1. Update the documentation to reflect any changes. 29 | 1. Push to your fork and submit a pull request. 30 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2013-11-04 using generator-angular-component 0.2.3 2 | 'use strict'; 3 | 4 | module.exports = function(grunt) { 5 | 6 | require('load-grunt-tasks')(grunt); 7 | require('time-grunt')(grunt); 8 | 9 | // Project configuration 10 | grunt.initConfig({ 11 | pkg: require('./package.json'), 12 | bower: require('./bower.json'), 13 | yo: { 14 | // Configurable paths 15 | name: '<%= pkg.name %>', 16 | src: require('./bower.json').appPath || 'src', 17 | dist: 'dist' 18 | }, 19 | meta: { 20 | banner: '/**\n' + 21 | ' * <%= pkg.name %>\n' + 22 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 23 | ' * @link <%= pkg.homepage %>\n' + 24 | ' * @author <%= pkg.author.name %> <<%= pkg.author.email %>>\n' + 25 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 26 | ' */\n' 27 | }, 28 | open: { 29 | server: { 30 | path: 'http://localhost:<%= connect.options.port %>' 31 | } 32 | }, 33 | watch: { 34 | test: { 35 | files: '<%= jshint.test.src %>', 36 | tasks: ['jshint:test', 'karma:unit'] 37 | }, 38 | less: { 39 | files: ['{.tmp,docs,<%= yo.src %>}/**/*.less'], 40 | tasks: ['less:dev'] 41 | }, 42 | app: { 43 | options: { 44 | livereload: '<%= connect.options.livereload %>' 45 | }, 46 | files: [ 47 | '{.tmp,docs,<%= yo.src %>}/{,*/}*.html', 48 | '{.tmp,docs,<%= yo.src %>}/styles/{,*/}*.css', 49 | '{.tmp,docs,<%= yo.src %>}/{,*/}*.json', 50 | '{.tmp,docs,<%= yo.src %>}/scripts/{,*/}*.js', 51 | '{.tmp,docs,<%= yo.src %>}/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 52 | ] 53 | } 54 | }, 55 | connect: { 56 | options: { 57 | // Use a system-assigned port. 58 | port: 51903, 59 | // Change this to '0.0.0.0' to access the server from outside. 60 | hostname: '0.0.0.0', 61 | livereload: true 62 | }, 63 | livereload: { 64 | options: { 65 | open: true, 66 | base: [ 67 | '.tmp', 68 | 'docs' 69 | ] 70 | } 71 | }, 72 | test: { 73 | options: { 74 | port: 9001, 75 | base: [ 76 | '.tmp', 77 | 'test', 78 | '<%= yo.src %>' 79 | ] 80 | } 81 | } 82 | }, 83 | clean: { 84 | dist: { 85 | files: [{ 86 | dot: true, 87 | src: [ 88 | '.tmp', 89 | '<%= yo.dist %>/*', 90 | '!<%= yo.dist %>/.git*' 91 | ] 92 | }] 93 | }, 94 | server: '.tmp' 95 | }, 96 | less: { 97 | options: { 98 | paths: ['<%= yo.src %>'] 99 | }, 100 | dev: { 101 | options: { 102 | dumpLineNumbers: false, 103 | }, 104 | files: { 105 | '<%= yo.src %>/<%= pkg.name %>.css': ['<%= yo.src %>/{,*/}*.less'] 106 | } 107 | }, 108 | dist: { 109 | options: { 110 | cleancss: true, 111 | report: 'gzip' 112 | }, 113 | files: { 114 | '<%= yo.dist %>/<%= pkg.name %>.min.css': ['<%= yo.src %>/{,*/}*.less'] 115 | } 116 | } 117 | }, 118 | jshint: { 119 | src: { 120 | options: { 121 | jshintrc: '.jshintrc', 122 | ignores: ['<%= yo.src %>/{,*/}*.tpl.js'] 123 | }, 124 | src: [ 125 | 'Gruntfile.js', 126 | '<%= yo.src %>/{,*/}*.js' 127 | ] 128 | }, 129 | test: { 130 | options: { 131 | jshintrc: 'test/.jshintrc' 132 | }, 133 | src: ['test/**/*.js'] 134 | } 135 | }, 136 | karma: { 137 | options: { 138 | configFile: 'test/karma.conf.js', 139 | browsers: ['PhantomJS'] 140 | }, 141 | unit: { 142 | singleRun: true 143 | }, 144 | server: { 145 | autoWatch: true 146 | } 147 | }, 148 | concat: { 149 | options: { 150 | stripBanners: true 151 | }, 152 | banner: { 153 | options: { 154 | banner: '<%= meta.banner %>', 155 | }, 156 | files: { 157 | '<%= yo.dist %>/<%= pkg.name %>.js': ['<%= yo.dist %>/<%= pkg.name %>.js' ], 158 | '<%= yo.dist %>/<%= pkg.name %>.min.js': ['<%= yo.dist %>/<%= pkg.name %>.min.js' ], 159 | '<%= yo.dist %>/<%= pkg.name %>.css': ['<%= yo.src %>/<%= pkg.name %>.css' ], 160 | '<%= yo.dist %>/<%= pkg.name %>.min.css': ['<%= yo.dist %>/<%= pkg.name %>.min.css' ] 161 | } 162 | }, 163 | dist: { 164 | options: { 165 | // Replace all 'use strict' statements in the code with a single one at the top 166 | banner: '(function(window, document, undefined) {\n\'use strict\';\n', 167 | footer: '\n})(window, document);\n', 168 | process: function(src, filepath) { 169 | return '// Source: ' + filepath + '\n' + 170 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 171 | } 172 | }, 173 | files: { 174 | '<%= yo.dist %>/<%= pkg.name %>.js': [ 175 | '<%= yo.src %>/{,*/}*.js' 176 | ] 177 | } 178 | } 179 | }, 180 | ngmin: { 181 | options: { 182 | expand: true 183 | }, 184 | dist: { 185 | files: { 186 | '<%= yo.dist %>/<%= yo.name %>.js': ['<%= yo.dist %>/<%= yo.name %>.js'] 187 | } 188 | } 189 | }, 190 | ngtemplates: { 191 | options: { 192 | module: 'mgcrea.pullToRefresh', 193 | }, 194 | dev: { 195 | cwd: 'src', 196 | src: '{,*/}*.html', 197 | dest: '<%= yo.src %>/<%= yo.name %>.tpl.js' 198 | } 199 | }, 200 | uglify: { 201 | options: { 202 | banner: '<%= meta.banner %>', 203 | report: 'gzip' 204 | }, 205 | dist: { 206 | files: { 207 | '<%= yo.dist %>/<%= yo.name %>.min.js': ['<%= Object.keys(ngmin.dist.files)[0] %>'] 208 | } 209 | } 210 | } 211 | }); 212 | 213 | grunt.registerTask('server', [ 214 | 'clean:server', 215 | 'less:dev', 216 | 'connect:livereload', 217 | 'watch' 218 | ]); 219 | 220 | grunt.registerTask('test', [ 221 | 'jshint', 222 | 'karma:unit' 223 | ]); 224 | 225 | grunt.registerTask('build', [ 226 | 'clean:dist', 227 | 'less:dev', 228 | 'less:dist', 229 | 'ngtemplates:dev', 230 | 'concat:dist', 231 | 'ngmin:dist', 232 | 'uglify:dist', 233 | 'concat:banner' 234 | ]); 235 | 236 | grunt.registerTask('release', [ 237 | 'test', 238 | 'bump-only', 239 | 'dist', 240 | 'bump-commit' 241 | ]); 242 | 243 | grunt.registerTask('default', ['build']); 244 | 245 | }; 246 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-pull-to-refresh", 3 | "version": "0.3.0", 4 | "description": "angular-pull-to-refresh", 5 | "keywords": [ 6 | "angular" 7 | ], 8 | "homepage": "https://github.com/mgcrea/angular-pull-to-refresh", 9 | "bugs": "https://github.com/mgcrea/angular-pull-to-refresh/issues", 10 | "author": { 11 | "name": "Olivier Louvignes", 12 | "email": "olivier@mg-crea.com", 13 | "url": "https://github.com/mgcrea" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mgcrea/angular-pull-to-refresh.git" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "MIT" 22 | } 23 | ], 24 | "dependencies": {}, 25 | "devDependencies": { 26 | "angular": "~1.2.0", 27 | "angular-mocks": "~1.2.0", 28 | "jquery": "~2.0.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dist/angular-pull-to-refresh.css: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-pull-to-refresh 3 | * @version v0.3.0 - 2013-11-14 4 | * @link https://github.com/mgcrea/angular-pull-to-refresh 5 | * @author Olivier Louvignes 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | .pull-to-refresh { 9 | position: relative; 10 | display: block; 11 | -webkit-box-sizing: border-box; 12 | -moz-box-sizing: border-box; 13 | box-sizing: border-box; 14 | margin: -40px auto 0; 15 | padding: 0; 16 | width: 100%; 17 | height: 40px; 18 | border-bottom: 1px dashed #ccc; 19 | text-align: center; 20 | line-height: 40px; 21 | -webkit-transition-duration: 400ms; 22 | -moz-transition-duration: 400ms; 23 | transition-duration: 400ms; 24 | -webkit-transition-property: margin; 25 | -moz-transition-property: margin; 26 | transition-property: margin; 27 | -webkit-transform: translateZ(0px); 28 | -moz-transform: translateZ(0px); 29 | transform: translateZ(0px); 30 | } 31 | -------------------------------------------------------------------------------- /dist/angular-pull-to-refresh.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-pull-to-refresh 3 | * @version v0.3.0 - 2013-11-14 4 | * @link https://github.com/mgcrea/angular-pull-to-refresh 5 | * @author Olivier Louvignes 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | (function (window, document, undefined) { 9 | 'use strict'; 10 | angular.module('mgcrea.pullToRefresh', []).constant('pullToRefreshConfig', { 11 | treshold: 60, 12 | debounce: 400, 13 | text: { 14 | pull: 'pull to refresh', 15 | release: 'release to refresh', 16 | loading: 'refreshing...' 17 | }, 18 | icon: { 19 | pull: 'fa fa-arrow-down', 20 | release: 'fa fa-arrow-up', 21 | loading: 'fa fa-refresh fa-spin' 22 | } 23 | }).directive('pullToRefresh', [ 24 | '$compile', 25 | '$timeout', 26 | '$q', 27 | 'pullToRefreshConfig', 28 | function ($compile, $timeout, $q, pullToRefreshConfig) { 29 | return { 30 | scope: true, 31 | restrict: 'A', 32 | transclude: true, 33 | templateUrl: 'angular-pull-to-refresh.tpl.html', 34 | compile: function compile(tElement, tAttrs, transclude) { 35 | return function postLink(scope, iElement, iAttrs) { 36 | var config = angular.extend({}, pullToRefreshConfig, iAttrs); 37 | var scrollElement = iElement.parent(); 38 | var ptrElement = window.ptr = iElement.children()[0]; 39 | scope.text = config.text; 40 | scope.icon = config.icon; 41 | scope.status = 'pull'; 42 | var setStatus = function (status) { 43 | shouldReload = status === 'release'; 44 | scope.$apply(function () { 45 | scope.status = status; 46 | }); 47 | }; 48 | var shouldReload = false; 49 | iElement.bind('touchmove', function (ev) { 50 | var top = scrollElement[0].scrollTop; 51 | if (top < -config.treshold && !shouldReload) { 52 | setStatus('release'); 53 | } else if (top > -config.treshold && shouldReload) { 54 | setStatus('pull'); 55 | } 56 | }); 57 | iElement.bind('touchend', function (ev) { 58 | if (!shouldReload) 59 | return; 60 | ptrElement.style.webkitTransitionDuration = 0; 61 | ptrElement.style.margin = '0 auto'; 62 | setStatus('loading'); 63 | var start = +new Date(); 64 | $q.when(scope.$eval(iAttrs.pullToRefresh)).then(function () { 65 | var elapsed = +new Date() - start; 66 | $timeout(function () { 67 | ptrElement.style.margin = ''; 68 | ptrElement.style.webkitTransitionDuration = ''; 69 | scope.status = 'pull'; 70 | }, elapsed < config.debounce ? config.debounce - elapsed : 0); 71 | }); 72 | }); 73 | scope.$on('$destroy', function () { 74 | iElement.unbind('touchmove'); 75 | iElement.unbind('touchend'); 76 | }); 77 | }; 78 | } 79 | }; 80 | } 81 | ]); 82 | angular.module('mgcrea.pullToRefresh').run([ 83 | '$templateCache', 84 | function ($templateCache) { 85 | $templateCache.put('angular-pull-to-refresh.tpl.html', '
\n' + '  \n' + ' \n' + '
\n' + '
\n'); 86 | } 87 | ]); 88 | }(window, document)); -------------------------------------------------------------------------------- /dist/angular-pull-to-refresh.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-pull-to-refresh 3 | * @version v0.3.0 - 2013-11-14 4 | * @link https://github.com/mgcrea/angular-pull-to-refresh 5 | * @author Olivier Louvignes 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | .pull-to-refresh{position:relative;display:block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:-40px auto 0;padding:0;width:100%;height:40px;border-bottom:1px dashed #ccc;text-align:center;line-height:40px;-webkit-transition-duration:400ms;-moz-transition-duration:400ms;transition-duration:400ms;-webkit-transition-property:margin;-moz-transition-property:margin;transition-property:margin;-webkit-transform:translateZ(0px);-moz-transform:translateZ(0px);transform:translateZ(0px)} -------------------------------------------------------------------------------- /dist/angular-pull-to-refresh.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-pull-to-refresh 3 | * @version v0.3.0 - 2013-11-14 4 | * @link https://github.com/mgcrea/angular-pull-to-refresh 5 | * @author Olivier Louvignes 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | !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); -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Olivier Louvignes http://olouv.com 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-pull-to-refresh", 3 | "version": "0.3.0", 4 | "description": "angular-pull-to-refresh", 5 | "keywords": [ 6 | "angular" 7 | ], 8 | "homepage": "https://github.com/mgcrea/angular-pull-to-refresh", 9 | "bugs": "https://github.com/mgcrea/angular-pull-to-refresh/issues", 10 | "author": { 11 | "name": "Olivier Louvignes", 12 | "email": "olivier@mg-crea.com", 13 | "url": "https://github.com/mgcrea" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mgcrea/angular-pull-to-refresh.git" 18 | }, 19 | "licenses": [ 20 | { 21 | "type": "MIT" 22 | } 23 | ], 24 | "devDependencies": { 25 | "connect-livereload": "~0.3.0", 26 | "grunt": "~0.4.1", 27 | "grunt-angular-templates": "~0.4.7", 28 | "grunt-bump": "0.0.11", 29 | "grunt-contrib-clean": "~0.5.0", 30 | "grunt-contrib-concat": "~0.3.0", 31 | "grunt-contrib-connect": "~0.5.0", 32 | "grunt-contrib-htmlmin": "~0.1.3", 33 | "grunt-contrib-jshint": "~0.7.1", 34 | "grunt-contrib-less": "~0.8.2", 35 | "grunt-contrib-uglify": "~0.2.7", 36 | "grunt-contrib-watch": "~0.5.3", 37 | "grunt-karma": "~0.6.2", 38 | "grunt-ngmin": "0.0.3", 39 | "grunt-open": "~0.2.2", 40 | "karma": "~0.10.4", 41 | "karma-chrome-launcher": "~0.1.0", 42 | "karma-coffee-preprocessor": "~0.1.0", 43 | "karma-firefox-launcher": "~0.1.0", 44 | "karma-html2js-preprocessor": "~0.1.0", 45 | "karma-jasmine": "~0.1.3", 46 | "karma-phantomjs-launcher": "~0.1.0", 47 | "karma-requirejs": "~0.1.0", 48 | "karma-script-launcher": "~0.1.0", 49 | "load-grunt-tasks": "~0.2.0", 50 | "time-grunt": "~0.2.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [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) 2 | 3 | 4 | `mgcrea.pullToRefresh` is a module providing a simple css-only pull-to-refresh component leveraging native style momentum scrolling `-webkit-overflow-scroll: touch`. 5 | 6 | The directive has a configurable built-in debounce system (400ms treshold by default) and can leverage angular `$q` promises. 7 | 8 | 9 | 10 | ## Quick start 11 | 12 | + Install the module with [bower](http://bower.io/) 13 | 14 | ``` bash 15 | $ bower install angular-pull-to-refresh --save 16 | ``` 17 | 18 | + Include the required libraries: 19 | 20 | ``` html 21 | 22 | 23 | ``` 24 | 25 | + Inject the `mgcrea.pullToRefresh` module into your app: 26 | 27 | ``` javascript 28 | angular.module('myApp', ['mgcrea.pullToRefresh']); 29 | ``` 30 | 31 | 32 | 33 | ## Examples 34 | 35 | [![Demo](http://mgcrea.github.io/angular-pull-to-refresh/demo.gif)](http://mgcrea.github.com/angular-pull-to-refresh) 36 | 37 | You can check out a working demo there (only works on touch devices): 38 | 39 | + **http://plnkr.co/edit/C4dV0cvWxvrfR6y0uCxI?p=preview** 40 | 41 | ``` html 42 |
43 |
    44 |
  • 45 |
46 |
47 | ``` 48 | 49 | ``` javascript 50 | angular.module('myApp') 51 | 52 | .controller('AppCtrl', function($scope, $q) { 53 | 54 | $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']; 55 | 56 | $scope.onReload = function() { 57 | console.warn('reload'); 58 | var deferred = $q.defer(); 59 | setTimeout(function() { 60 | deferred.resolve(true); 61 | }, 1000); 62 | return deferred.promise; 63 | }; 64 | 65 | }); 66 | ``` 67 | 68 | 69 | 70 | ## Contributing 71 | 72 | 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! 73 | 74 | 75 | 76 | ## Authors 77 | 78 | **Olivier Louvignes** 79 | 80 | + http://olouv.com 81 | + http://github.com/mgcrea 82 | 83 | 84 | 85 | ## Copyright and license 86 | 87 | The MIT License 88 | 89 | Copyright (c) 2012 Olivier Louvignes 90 | 91 | Permission is hereby granted, free of charge, to any person obtaining a copy 92 | of this software and associated documentation files (the "Software"), to deal 93 | in the Software without restriction, including without limitation the rights 94 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 95 | copies of the Software, and to permit persons to whom the Software is 96 | furnished to do so, subject to the following conditions: 97 | 98 | The above copyright notice and this permission notice shall be included in 99 | all copies or substantial portions of the Software. 100 | 101 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 102 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 103 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 104 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 105 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 106 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 107 | THE SOFTWARE. 108 | -------------------------------------------------------------------------------- /src/angular-pull-to-refresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('mgcrea.pullToRefresh', []) 4 | 5 | .constant('pullToRefreshConfig', { 6 | treshold: 60, 7 | debounce: 400, 8 | text: { 9 | pull: 'pull to refresh', 10 | release: 'release to refresh', 11 | loading: 'refreshing...' 12 | }, 13 | icon: { 14 | pull: 'fa fa-arrow-down', 15 | release: 'fa fa-arrow-up', 16 | loading: 'fa fa-refresh fa-spin' 17 | } 18 | }) 19 | 20 | .directive('pullToRefresh', function($compile, $timeout, $q, pullToRefreshConfig) { 21 | 22 | return { 23 | scope: true, 24 | restrict: 'A', 25 | transclude: true, 26 | templateUrl: 'angular-pull-to-refresh.tpl.html', 27 | compile: function compile(tElement, tAttrs, transclude) { 28 | 29 | return function postLink(scope, iElement, iAttrs) { 30 | 31 | var config = angular.extend({}, pullToRefreshConfig, iAttrs); 32 | var scrollElement = iElement.parent(); 33 | var ptrElement = window.ptr = iElement.children()[0]; 34 | 35 | // Initialize isolated scope vars 36 | scope.text = config.text; 37 | scope.icon = config.icon; 38 | scope.status = 'pull'; 39 | 40 | var setStatus = function(status) { 41 | shouldReload = status === 'release'; 42 | scope.$apply(function() { 43 | scope.status = status; 44 | }); 45 | }; 46 | 47 | var shouldReload = false; 48 | iElement.bind('touchmove', function(ev) { 49 | var top = scrollElement[0].scrollTop; 50 | if(top < -config.treshold && !shouldReload) { 51 | setStatus('release'); 52 | } else if(top > -config.treshold && shouldReload) { 53 | setStatus('pull'); 54 | } 55 | }); 56 | 57 | iElement.bind('touchend', function(ev) { 58 | if(!shouldReload) return; 59 | ptrElement.style.webkitTransitionDuration = 0; 60 | ptrElement.style.margin = '0 auto'; 61 | setStatus('loading'); 62 | 63 | var start = +new Date(); 64 | $q.when(scope.$eval(iAttrs.pullToRefresh)) 65 | .then(function() { 66 | var elapsed = +new Date() - start; 67 | $timeout(function() { 68 | ptrElement.style.margin = ''; 69 | ptrElement.style.webkitTransitionDuration = ''; 70 | scope.status = 'pull'; 71 | }, elapsed < config.debounce ? config.debounce - elapsed : 0); 72 | }); 73 | }); 74 | 75 | scope.$on('$destroy', function() { 76 | iElement.unbind('touchmove'); 77 | iElement.unbind('touchend'); 78 | }); 79 | 80 | }; 81 | } 82 | }; 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /src/angular-pull-to-refresh.less: -------------------------------------------------------------------------------- 1 | 2 | // Vars 3 | // 4 | 5 | @height: 40px; 6 | 7 | // Styles 8 | // 9 | 10 | .pull-to-refresh { 11 | position: relative; 12 | display: block; 13 | -webkit-box-sizing: border-box; 14 | -moz-box-sizing: border-box; 15 | box-sizing: border-box; 16 | margin: -@height auto 0; 17 | padding: 0; 18 | width: 100%; 19 | height: @height; 20 | border-bottom: 1px dashed #ccc; 21 | text-align: center; 22 | line-height: @height; 23 | -webkit-transition-duration: 400ms; 24 | -moz-transition-duration: 400ms; 25 | transition-duration: 400ms; 26 | -webkit-transition-property: margin; 27 | -moz-transition-property: margin; 28 | transition-property: margin; 29 | // Make block visible to gpu scroll 30 | -webkit-transform: translateZ(0px); 31 | -moz-transform: translateZ(0px); 32 | transform: translateZ(0px); 33 | } 34 | -------------------------------------------------------------------------------- /src/angular-pull-to-refresh.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |   3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/angular-pull-to-refresh.tpl.js: -------------------------------------------------------------------------------- 1 | angular.module('mgcrea.pullToRefresh').run(['$templateCache', function($templateCache) { 2 | 3 | $templateCache.put('angular-pull-to-refresh.tpl.html', 4 | "
\n" + 5 | "  \n" + 6 | " \n" + 7 | "
\n" + 8 | "
\n" 9 | ); 10 | 11 | }]); 12 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "node": true, 4 | "browser": true, 5 | "jquery": true, 6 | "devel": false, 7 | "esnext": true, 8 | "bitwise": true, 9 | 10 | "camelcase": false, 11 | "curly": false, 12 | "eqeqeq": true, 13 | "immed": true, 14 | "indent": 2, 15 | "latedef": false, 16 | "newcap": true, 17 | "noarg": true, 18 | "quotmark": "single", 19 | "regexp": true, 20 | "undef": true, 21 | "unused": false, 22 | "strict": true, 23 | "trailing": true, 24 | "smarttabs": true, 25 | 26 | "boss": false, 27 | "laxbreak": true, 28 | "eqnull": false, 29 | "expr": true, 30 | 31 | "globals": { 32 | "angular": true, 33 | "inject": true, 34 | "describe": true, 35 | "beforeEach": true, 36 | "afterEach": true, 37 | "it": true, 38 | "expect": true 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | beforeEach(function() { 4 | this.addMatchers({ 5 | toEquals: function(expected) { 6 | this.message = function() { 7 | return 'Expected "' + angular.mock.dump(this.actual) + '" to equal "' + angular.mock.dump(expected) + '".'; 8 | }; 9 | return angular.equals(this.actual, expected); 10 | }, 11 | toHaveClass: function(cls) { 12 | this.message = function() { 13 | return 'Expected "' + angular.mock.dump(this.actual) + '" to have class "' + cls + '".'; 14 | }; 15 | return this.actual.hasClass(cls); 16 | } 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Karma configuration 4 | // 5 | module.exports = function(config) { 6 | 7 | config.set({ 8 | 9 | // base path, that will be used to resolve files and exclude 10 | basePath: './..', 11 | 12 | // frameworks to use 13 | frameworks: ['jasmine'], 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-mocks/angular-mocks.js', 19 | 'src/*.js', 20 | 'bower_components/jquery/jquery.js', 21 | 'test/helpers.js', 22 | 'test/spec/*.js' 23 | ], 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | 28 | ], 29 | 30 | // test results reporter to use 31 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 32 | reporters: ['progress'], 33 | 34 | // web server port 35 | port: 9876, 36 | 37 | // enable / disable colors in the output (reporters and logs) 38 | colors: true, 39 | 40 | // level of logging 41 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 42 | logLevel: config.LOG_INFO, 43 | 44 | // enable / disable watching file and executing tests whenever any file changes 45 | autoWatch: true, 46 | 47 | // Start these browsers, currently available: 48 | // - Chrome 49 | // - ChromeCanary 50 | // - Firefox 51 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 52 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 53 | // - PhantomJS 54 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 55 | browsers: ['PhantomJS'], 56 | 57 | // If browser does not capture in given timeout [ms], kill it 58 | captureTimeout: 60000, 59 | 60 | // Continuous Integration mode 61 | // if true, it capture browsers, run tests and exit 62 | singleRun: false 63 | 64 | }); 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /test/spec/angular-pull-to-refresh.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global jasmine */ 3 | 4 | describe('mgcrea.pullToRefresh', function() { 5 | 6 | var $injector, $compile, $timeout, scope, sandbox; 7 | 8 | beforeEach(module('mgcrea.pullToRefresh')); 9 | 10 | beforeEach(inject(function (_$injector_) { 11 | $injector = _$injector_; 12 | $compile = $injector.get('$compile'); 13 | $timeout = $injector.get('$timeout'); 14 | scope = $injector.get('$rootScope'); 15 | sandbox = $('
').attr('id', 'sandbox').appendTo('body'); 16 | })); 17 | 18 | afterEach(function() { 19 | sandbox.remove(); 20 | scope.$destroy(); 21 | }); 22 | 23 | var templates = { 24 | basic: { 25 | 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']}, 26 | element: '
' + 27 | '
    ' + 28 | '
  • ' + 29 | '
' + 30 | '
' 31 | } 32 | }; 33 | 34 | function compileDirective(template, locals) { 35 | template = templates[template]; 36 | angular.extend(scope, template.scope, locals); 37 | var element = $(template.element).appendTo(sandbox); 38 | element = $compile(element)(scope); 39 | scope.$digest(); 40 | return jQuery(element[0]); 41 | } 42 | 43 | it('should correctly initialize and attach to DOM', function () { 44 | var elm = compileDirective('basic'); 45 | var ptrElement = elm.find('.pull-to-refresh'); 46 | expect(ptrElement.length).toBe(1); 47 | var config = $injector.get('pullToRefreshConfig'); 48 | expect(ptrElement.children('span').html()).toBe(config.text.pull); 49 | }); 50 | 51 | }); 52 | --------------------------------------------------------------------------------