├── .gitignore ├── .vimrc ├── Gruntfile.coffee ├── README.md ├── bower.json ├── dist ├── page_visibility.js └── page_visibility.min.js ├── js ├── page_visibility.js └── page_visibility.js.map ├── karma.conf.coffee ├── package.json ├── spec └── page_visibility.spec.coffee ├── src └── page_visibility.coffee └── todos.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | bower_components 3 | node_modules 4 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | set wildignore+=*node_modules*,*bower_components* 2 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt)-> 2 | grunt.loadNpmTasks 'grunt-contrib-coffee' 3 | grunt.loadNpmTasks 'grunt-contrib-watch' 4 | grunt.loadNpmTasks 'grunt-karma' 5 | grunt.loadNpmTasks 'grunt-contrib-uglify' 6 | grunt.loadNpmTasks 'grunt-contrib-copy' 7 | 8 | grunt.initConfig 9 | coffee: 10 | compile: 11 | options: 12 | sourceMap: true 13 | watch: true 14 | files: [ 15 | expand: true 16 | cwd: 'src/' 17 | src: '**/*.coffee' 18 | dest: 'js/' 19 | ext: '.js' 20 | ] 21 | watch: 22 | source: 23 | files: [ 'src/**/*' ] 24 | tasks: [ 'coffee' ] 25 | 26 | karma: 27 | unit: 28 | configFile: 'karma.conf.coffee' 29 | single: 30 | configFile: 'karma.conf.coffee' 31 | options: 32 | singleRun: true 33 | 34 | uglify: 35 | source: 36 | expand: true 37 | cwd: 'js/' 38 | src: '**/*.js' 39 | dest: 'dist/' 40 | ext: '.min.js' 41 | 42 | copy: 43 | source: 44 | expand: true 45 | cwd: 'js/' 46 | src: '**/*.js' 47 | dest: 'dist/' 48 | 49 | 50 | 51 | grunt.registerTask 'dev', [ 'coffee', 'watch' ] 52 | grunt.registerTask 'build', [ 'karma:single', 'coffee', 'copy', 'uglify' ] 53 | grunt.registerTask 'default', [ 'dev' ] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Angular Page Visibility: a Page Visibility API interface for Angular 2 | `angular-page-visibility` is a tiny lib which integrate [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API) with Angular. 3 | 4 | It is exposed as a scope, which `$broadcast`-s `pageFocused` and `pageBlurred` when page is focused / blurred. 5 | For old browsers [not supporting page visibility API](http://caniuse.com/#feat=pagevisibility), it ignores it silently. 6 | 7 | ## Usage 8 | To use `angular-page-visibility`, just inject it, then listen to the events. 9 | 10 | ```javascript 11 | angular.module('app') 12 | .controller('MyController', function($scope, $pageVisibility) { 13 | $pageVisibility.$on('pageFocused', function(){ 14 | // page is focused 15 | }); 16 | 17 | $pageVisibility.$on('pageBlurred', function(){ 18 | // page is blurred 19 | }); 20 | }); 21 | ``` 22 | 23 | ## Installation 24 | 25 | 1) include script: script can be included via `bower` or downloading directly 26 | 27 | - via bower: 28 | `$ bower install angular-page-visibility` 29 | 30 | - download directly 31 | ```html 32 | 33 | ``` 34 | 35 | 2) include the module: 36 | 37 | ```javascript 38 | angular.app('myApp', [ 'angular-page-visibility' ]) 39 | 40 | ``` 41 | 42 | ## Testing 43 | to test `angular-page-visibility`, `grunt`, `karma` are needed. 44 | 45 | 1. `$ npm install` 46 | 2. `$ bower install` 47 | 3. `$ npm test` 48 | 49 | ## Licence: 50 | MIT 51 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular_page_visibility", 3 | "version": "0.0.4", 4 | "main": "./dist/page_visibility.min.js", 5 | "authors": [ 6 | "Yang-Hsing Lin " 7 | ], 8 | "description": "angularjs interface of page visibility API", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "angular": "1.2.22", 20 | "angular-mocks": "1.2.22", 21 | "sinonjs": "~1.10.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dist/page_visibility.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | angular.module('angular-page-visibility', []).factory('$pageVisibility', [ 3 | '$rootScope', '$document', function($rootScope, $document) { 4 | var getVisibilityKeys, hiddenKey, pageVisibility, visibilityChagedKey, _ref; 5 | pageVisibility = $rootScope.$new(); 6 | getVisibilityKeys = function() { 7 | if (typeof ($document.prop('hidden')) !== 'undefined') { 8 | return ['hidden', 'visibilitychange']; 9 | } else if (typeof ($document.prop('mozHidden')) !== 'undefined') { 10 | return ['mozHidden', 'mozvisibilitychange']; 11 | } else if (typeof ($document.prop('msHidden')) !== 'undefined') { 12 | return ['msHidden', 'msvisibilitychange']; 13 | } else if (typeof ($document.prop('webkitHidden')) !== 'undefined') { 14 | return ['webkitHidden', 'webkitvisibilitychange']; 15 | } 16 | }; 17 | if (!getVisibilityKeys()) { 18 | return pageVisibility; 19 | } 20 | _ref = getVisibilityKeys(), hiddenKey = _ref[0], visibilityChagedKey = _ref[1]; 21 | $document.on(visibilityChagedKey, function() { 22 | if ($document.prop(hiddenKey)) { 23 | return pageVisibility.$broadcast('pageBlurred'); 24 | } else { 25 | return pageVisibility.$broadcast('pageFocused'); 26 | } 27 | }); 28 | return pageVisibility; 29 | } 30 | ]); 31 | 32 | }).call(this); 33 | 34 | //# sourceMappingURL=page_visibility.js.map 35 | -------------------------------------------------------------------------------- /dist/page_visibility.min.js: -------------------------------------------------------------------------------- 1 | (function(){angular.module("angular-page-visibility",[]).factory("$pageVisibility",["$rootScope","$document",function(a,b){var c,d,e,f,g;return e=a.$new(),(c=function(){return"undefined"!=typeof b.prop("hidden")?["hidden","visibilitychange"]:"undefined"!=typeof b.prop("mozHidden")?["mozHidden","mozvisibilitychange"]:"undefined"!=typeof b.prop("msHidden")?["msHidden","msvisibilitychange"]:"undefined"!=typeof b.prop("webkitHidden")?["webkitHidden","webkitvisibilitychange"]:void 0})()?(g=c(),d=g[0],f=g[1],b.on(f,function(){return e.$broadcast(b.prop(d)?"pageBlurred":"pageFocused")}),e):e}])}).call(this); -------------------------------------------------------------------------------- /js/page_visibility.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | angular.module('angular-page-visibility', []).factory('$pageVisibility', [ 3 | '$rootScope', '$document', function($rootScope, $document) { 4 | var getVisibilityKeys, hiddenKey, pageVisibility, visibilityChagedKey, _ref; 5 | pageVisibility = $rootScope.$new(); 6 | getVisibilityKeys = function() { 7 | if (typeof ($document.prop('hidden')) !== 'undefined') { 8 | return ['hidden', 'visibilitychange']; 9 | } else if (typeof ($document.prop('mozHidden')) !== 'undefined') { 10 | return ['mozHidden', 'mozvisibilitychange']; 11 | } else if (typeof ($document.prop('msHidden')) !== 'undefined') { 12 | return ['msHidden', 'msvisibilitychange']; 13 | } else if (typeof ($document.prop('webkitHidden')) !== 'undefined') { 14 | return ['webkitHidden', 'webkitvisibilitychange']; 15 | } 16 | }; 17 | if (!getVisibilityKeys()) { 18 | return pageVisibility; 19 | } 20 | _ref = getVisibilityKeys(), hiddenKey = _ref[0], visibilityChagedKey = _ref[1]; 21 | $document.on(visibilityChagedKey, function() { 22 | if ($document.prop(hiddenKey)) { 23 | return pageVisibility.$broadcast('pageBlurred'); 24 | } else { 25 | return pageVisibility.$broadcast('pageFocused'); 26 | } 27 | }); 28 | return pageVisibility; 29 | } 30 | ]); 31 | 32 | }).call(this); 33 | 34 | //# sourceMappingURL=page_visibility.js.map 35 | -------------------------------------------------------------------------------- /js/page_visibility.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "page_visibility.js", 4 | "sourceRoot": "../src/", 5 | "sources": [ 6 | "page_visibility.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "AAAA;AAAA,EAAA,OAAO,CAAC,MAAR,CAAe,yBAAf,EAA0C,EAA1C,CACE,CAAC,OADH,CACW,iBADX,EAC8B;IAAE,YAAF,EAAgB,WAAhB,EAA6B,SAAC,UAAD,EAAa,SAAb,GAAA;AACvD,UAAA,uEAAA;AAAA,MAAA,cAAA,GAAiB,UAAU,CAAC,IAAX,CAAA,CAAjB,CAAA;AAAA,MAEA,iBAAA,GAAoB,SAAA,GAAA;AAClB,QAAA,IAAG,MAAA,CAAA,CAAO,SAAS,CAAC,IAAV,CAAe,QAAf,CAAD,CAAN,KAAoC,WAAvC;iBACE,CAAE,QAAF,EAAY,kBAAZ,EADF;SAAA,MAEK,IAAG,MAAA,CAAA,CAAO,SAAS,CAAC,IAAV,CAAe,WAAf,CAAD,CAAN,KAAuC,WAA1C;iBACH,CAAE,WAAF,EAAe,qBAAf,EADG;SAAA,MAEA,IAAG,MAAA,CAAA,CAAO,SAAS,CAAC,IAAV,CAAe,UAAf,CAAD,CAAN,KAAsC,WAAzC;iBACH,CAAE,UAAF,EAAc,oBAAd,EADG;SAAA,MAEA,IAAG,MAAA,CAAA,CAAO,SAAS,CAAC,IAAV,CAAe,cAAf,CAAD,CAAN,KAA0C,WAA7C;iBACH,CAAE,cAAF,EAAkB,wBAAlB,EADG;SAPa;MAAA,CAFpB,CAAA;AAYA,MAAA,IAAA,CAAA,iBAA6B,CAAA,CAA7B;AAAA,eAAO,cAAP,CAAA;OAZA;AAAA,MAcA,OAAmC,iBAAA,CAAA,CAAnC,EAAC,mBAAD,EAAY,6BAdZ,CAAA;AAAA,MAgBA,SAAS,CAAC,EAAV,CAAa,mBAAb,EAAkC,SAAA,GAAA;AAChC,QAAA,IAAG,SAAS,CAAC,IAAV,CAAe,SAAf,CAAH;iBACE,cAAc,CAAC,UAAf,CAA0B,aAA1B,EADF;SAAA,MAAA;iBAGE,cAAc,CAAC,UAAf,CAA0B,aAA1B,EAHF;SADgC;MAAA,CAAlC,CAhBA,CAAA;aAuBA,eAxBuD;IAAA,CAA7B;GAD9B,CAAA,CAAA;AAAA" 10 | } -------------------------------------------------------------------------------- /karma.conf.coffee: -------------------------------------------------------------------------------- 1 | # Karma configuration 2 | # Generated on Mon Aug 25 2014 17:15:24 GMT+0800 (CST) 3 | 4 | module.exports = (config) -> 5 | config.set 6 | 7 | # base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '' 9 | 10 | 11 | # frameworks to use 12 | # available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'] 14 | 15 | 16 | # list of files / patterns to load in the browser 17 | files: [ 18 | 'bower_components/angular/angular.js', 19 | 'bower_components/angular-mocks/angular-mocks.js', 20 | 'bower_components/sinonjs/sinon.js', 21 | 'src/**/*.coffee', 22 | 'spec/**/*.coffee' 23 | ] 24 | 25 | 26 | # list of files to exclude 27 | exclude: [ 28 | '**/*.swp' 29 | ] 30 | 31 | 32 | # preprocess matching files before serving them to the browser 33 | # available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 34 | preprocessors: { 35 | '**/*.coffee': ['coffee'] 36 | } 37 | 38 | coffeePreprocessor: 39 | options: 40 | sourceMap: true 41 | 42 | 43 | # test results reporter to use 44 | # possible values: 'dots', 'progress' 45 | # available reporters: https://npmjs.org/browse/keyword/karma-reporter 46 | reporters: ['progress'] 47 | 48 | 49 | # web server port 50 | port: 9876 51 | 52 | 53 | # enable / disable colors in the output (reporters and logs) 54 | colors: true 55 | 56 | 57 | # level of logging 58 | # possible values: 59 | # - config.LOG_DISABLE 60 | # - config.LOG_ERROR 61 | # - config.LOG_WARN 62 | # - config.LOG_INFO 63 | # - config.LOG_DEBUG 64 | logLevel: config.LOG_INFO 65 | 66 | 67 | # enable / disable watching file and executing tests whenever any file changes 68 | autoWatch: true 69 | 70 | 71 | # start these browsers 72 | # available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 73 | browsers: ['Chrome'] 74 | 75 | 76 | # Continuous Integration mode 77 | # if true, Karma captures browsers, runs the tests and exits 78 | singleRun: false 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular_page_visibility", 3 | "version": "0.0.4", 4 | "description": "angularjs interface of page visibility API", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "grunt karma:single" 8 | }, 9 | "devDependencies": { 10 | "coffee-script": "^1.7.1", 11 | "grunt": "^0.4.5", 12 | "grunt-concurrent": "^0.5.0", 13 | "grunt-contrib-coffee": "~0.11.1", 14 | "grunt-contrib-copy": "^0.5.0", 15 | "grunt-contrib-uglify": "^0.5.1", 16 | "grunt-contrib-watch": "^0.6.1", 17 | "grunt-karma": "^0.8.3", 18 | "karma": "~0.12.22", 19 | "karma-chrome-launcher": "~0.1.4", 20 | "karma-coffee-preprocessor": "^0.2.1", 21 | "karma-jasmine": "~0.1.5" 22 | }, 23 | "author": [ 24 | { "name": "Yang-Hsing Lin", "email": "yanghsing.lin@gmail.com" } 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/mz026/angular_page_visibility.git" 29 | }, 30 | "license": "MIT" 31 | } 32 | -------------------------------------------------------------------------------- /spec/page_visibility.spec.coffee: -------------------------------------------------------------------------------- 1 | describe 'Page Visibility', -> 2 | $document = null 3 | beforeEach module 'angular-page-visibility' 4 | beforeEach module ($provide)-> 5 | $document = 6 | attrs: {} 7 | events: {} 8 | _setAttr: (key, val)-> 9 | @attrs[key] = val 10 | attr: (key)-> 11 | @attrs[key] 12 | prop: (key)-> 13 | @attrs[key] 14 | on: (eventName, cb)-> 15 | @events[eventName] = cb 16 | _trigger: (eventName)-> 17 | @events[eventName] && @events[eventName]() 18 | 19 | $provide.value('$document', $document) 20 | null 21 | 22 | ensurePageVisibilityEventsWith = (hiddenKey, visibilityChagedKey)-> 23 | describe "when document visibility is controlled by `#{hiddenKey}`", -> 24 | it '$broadcast-s `pageFocused` when page turn visible', -> 25 | inject (_$document_)-> 26 | $document._setAttr(hiddenKey, false) 27 | 28 | inject ($pageVisibility)-> 29 | onFocused = sinon.spy() 30 | $pageVisibility.$on('pageFocused', onFocused) 31 | 32 | $document._trigger(visibilityChagedKey) 33 | 34 | expect(onFocused.called).toBe(true) 35 | 36 | it '$broadcast-s `pageBlurred` when page turns invisible', -> 37 | inject (_$document_)-> 38 | $document._setAttr(hiddenKey, true) 39 | 40 | inject ($pageVisibility)-> 41 | onBlurred = sinon.spy() 42 | $pageVisibility.$on('pageBlurred', onBlurred) 43 | 44 | $document._trigger(visibilityChagedKey) 45 | 46 | expect(onBlurred.called).toBe(true) 47 | 48 | ensurePageVisibilityEventsWith('hidden', 'visibilitychange') 49 | ensurePageVisibilityEventsWith('mozHidden', 'mozvisibilitychange') 50 | ensurePageVisibilityEventsWith('msHidden', 'msvisibilitychange') 51 | ensurePageVisibilityEventsWith('webkitHidden', 'webkitvisibilitychange') 52 | 53 | it 'does nothing if the browser does not support page visibility API', -> 54 | inject ($pageVisibility)-> 55 | 56 | -------------------------------------------------------------------------------- /src/page_visibility.coffee: -------------------------------------------------------------------------------- 1 | angular.module('angular-page-visibility', []) 2 | .factory('$pageVisibility', [ '$rootScope', '$document', ($rootScope, $document)-> 3 | pageVisibility = $rootScope.$new() 4 | 5 | getVisibilityKeys = -> 6 | if typeof($document.prop('hidden')) != 'undefined' 7 | [ 'hidden', 'visibilitychange' ] 8 | else if typeof($document.prop('mozHidden')) != 'undefined' 9 | [ 'mozHidden', 'mozvisibilitychange' ] 10 | else if typeof($document.prop('msHidden')) != 'undefined' 11 | [ 'msHidden', 'msvisibilitychange' ] 12 | else if typeof($document.prop('webkitHidden')) != 'undefined' 13 | [ 'webkitHidden', 'webkitvisibilitychange' ] 14 | 15 | return pageVisibility unless getVisibilityKeys() 16 | 17 | [hiddenKey, visibilityChagedKey] = getVisibilityKeys() 18 | 19 | $document.on(visibilityChagedKey, -> 20 | if $document.prop(hiddenKey) 21 | pageVisibility.$broadcast('pageBlurred') 22 | else 23 | pageVisibility.$broadcast('pageFocused') 24 | ) 25 | 26 | pageVisibility 27 | ]) 28 | -------------------------------------------------------------------------------- /todos.md: -------------------------------------------------------------------------------- 1 | - create `Gruntfile` to 2 | 3 | 1. `grunt serve`: 4 | - watch / compile coffee script 5 | - run tests 6 | 7 | 2. `grunt build`: 8 | - minify script to `dist/` 9 | --------------------------------------------------------------------------------