├── .npmignore ├── .travis.yml ├── .github └── FUNDING.yml ├── rollup.config.js ├── src └── index.js ├── test ├── types.js └── unit.js ├── LICENSE ├── .gitignore ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | script: npm run lint && npm test 4 | after_success: npm run coverage 5 | notifications: 6 | email: 7 | on_success: never 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 2 | - 'lukechilds' 3 | custom: 4 | - 'https://blockstream.info/address/1LukeQU5jwebXbMLDVydeH4vFSobRV9rkj' 5 | - 'https://blockstream.info/address/3Luke2qRn5iLj4NiFrvLBu2jaEj7JeMR6w' 6 | - 'https://blockstream.info/address/bc1qlukeyq0c69v97uss68fet26kjkcsrymd2kv6d4' 7 | - 'https://tippin.me/@lukechilds' 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import camelCase from 'camelcase'; 3 | 4 | const pkg = require('./package.json'); 5 | 6 | export default { 7 | entry: 'src/index.js', 8 | plugins: [ 9 | babel() 10 | ], 11 | targets: [ 12 | { 13 | dest: pkg.main, 14 | format: 'umd', 15 | moduleName: camelCase(pkg.name), 16 | sourceMap: true 17 | }, 18 | { 19 | dest: pkg.module, 20 | format: 'es', 21 | sourceMap: true 22 | } 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint no-void: "off" */ 2 | 3 | // Loaded ready states 4 | const loadedStates = ['interactive', 'complete']; 5 | 6 | // Return Promise 7 | const whenDomReady = (cb, doc) => new Promise(resolve => { 8 | // Allow doc to be passed in as the lone first param 9 | if (cb && typeof cb !== 'function') { 10 | doc = cb; 11 | cb = null; 12 | } 13 | 14 | // Use global document if we don't have one 15 | doc = doc || window.document; 16 | 17 | // Handle DOM load 18 | const done = () => resolve(void (cb && setTimeout(cb))); 19 | 20 | // Resolve now if DOM has already loaded 21 | // Otherwise wait for DOMContentLoaded 22 | if (loadedStates.includes(doc.readyState)) { 23 | done(); 24 | } else { 25 | doc.addEventListener('DOMContentLoaded', done); 26 | } 27 | }); 28 | 29 | // Promise chain helper 30 | whenDomReady.resume = doc => val => whenDomReady(doc).then(() => val); 31 | 32 | export default whenDomReady; 33 | -------------------------------------------------------------------------------- /test/types.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import Window from 'window'; 3 | import whenDomReady from '../'; 4 | 5 | test('whenDomReady is a function', t => { 6 | t.is(typeof whenDomReady, 'function'); 7 | }); 8 | 9 | test('whenDomReady returns a Promise', t => { 10 | const { document } = new Window(); 11 | t.true(whenDomReady(document) instanceof Promise); 12 | }); 13 | 14 | test('whenDomReady.resume is a function', t => { 15 | t.is(typeof whenDomReady.resume, 'function'); 16 | }); 17 | 18 | test('whenDomReady.resume returns a function that returns a promise', t => { 19 | const { document } = new Window(); 20 | const returnValue = whenDomReady.resume(document); 21 | t.is(typeof returnValue, 'function'); 22 | t.true(returnValue() instanceof Promise); 23 | }); 24 | 25 | test('Promise value always resolves to undefined', async t => { 26 | t.plan(2); 27 | const { document } = new Window(); 28 | const promises = [ 29 | whenDomReady(() => 'foo', document).then(val => t.is(val, undefined)), 30 | whenDomReady(document).then(val => t.is(val, undefined)) 31 | ]; 32 | await Promise.all(promises); 33 | }); 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Luke Childs 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Project 2 | dist 3 | 4 | ## Node 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional eslint cache 43 | .eslintcache 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | ## OS X 49 | 50 | *.DS_Store 51 | .AppleDouble 52 | .LSOverride 53 | 54 | # Icon must end with two \r 55 | Icon 56 | 57 | 58 | # Thumbnails 59 | ._* 60 | 61 | # Files that might appear in the root of a volume 62 | .DocumentRevisions-V100 63 | .fseventsd 64 | .Spotlight-V100 65 | .TemporaryItems 66 | .Trashes 67 | .VolumeIcon.icns 68 | .com.apple.timemachine.donotpresent 69 | 70 | # Directories potentially created on remote AFP share 71 | .AppleDB 72 | .AppleDesktop 73 | Network Trash Folder 74 | Temporary Items 75 | .apdisk 76 | -------------------------------------------------------------------------------- /test/unit.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | import test from 'ava'; 3 | import Window from 'window'; 4 | import whenDomReady from '../'; 5 | 6 | test.cb('callback fires', t => { 7 | t.plan(1); 8 | const { document } = new Window(); 9 | whenDomReady(() => { 10 | t.pass(); 11 | t.end(); 12 | }, document); 13 | }); 14 | 15 | test('Promise resolves', async t => { 16 | const { document } = new Window(); 17 | t.plan(1); 18 | await whenDomReady(document).then(() => t.pass()); 19 | }); 20 | 21 | test('Promise chain helper passes value through', async t => { 22 | const { document } = new Window(); 23 | t.plan(1); 24 | await Promise 25 | .resolve('foo') 26 | .then(whenDomReady.resume(document)) 27 | .then(val => t.is(val, 'foo')); 28 | }); 29 | 30 | test('If document.readyState is already "interactive" run cb', async t => { 31 | const document = { readyState: 'interactive' }; 32 | t.plan(1); 33 | await whenDomReady(document).then(() => t.pass()); 34 | }); 35 | 36 | test('If document.readyState is already "complete" run cb', async t => { 37 | const document = { readyState: 'complete' }; 38 | t.plan(1); 39 | await whenDomReady(document).then(() => t.pass()); 40 | }); 41 | 42 | test('If document.readyState is "loading" run cb on DOMContentLoaded event', async t => { 43 | const document = new EventEmitter(); 44 | document.addEventListener = document.on; 45 | document.readyState = 'loading'; 46 | t.plan(1); 47 | setTimeout(() => document.emit('DOMContentLoaded'), 500); 48 | await whenDomReady(document).then(() => t.pass()); 49 | }); 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "when-dom-ready", 3 | "version": "1.2.12", 4 | "description": "$(document).ready() for the 21st century", 5 | "main": "dist/index.umd.js", 6 | "module": "dist/index.es2015.js", 7 | "scripts": { 8 | "prebuild": "rm -rf dist", 9 | "build": "rollup -c", 10 | "pretest": "npm run build", 11 | "lint": "xo", 12 | "test": "nyc ava", 13 | "coverage": "nyc report --reporter=text-lcov | coveralls", 14 | "prepublish": "npm run build" 15 | }, 16 | "babel": { 17 | "presets": [ 18 | [ 19 | "es2015", 20 | { 21 | "modules": false 22 | } 23 | ] 24 | ], 25 | "plugins": [ 26 | "array-includes" 27 | ] 28 | }, 29 | "xo": { 30 | "env": "browser", 31 | "extends": "xo-lukechilds" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/lukechilds/when-dom-ready.git" 36 | }, 37 | "keywords": [ 38 | "check", 39 | "dom", 40 | "loaded", 41 | "ready", 42 | "promise", 43 | "async", 44 | "asynchronous", 45 | "pure" 46 | ], 47 | "author": "Luke Childs (http://lukechilds.co.uk)", 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/lukechilds/when-dom-ready/issues" 51 | }, 52 | "homepage": "https://github.com/lukechilds/when-dom-ready", 53 | "dependencies": {}, 54 | "devDependencies": { 55 | "ava": "^0.25.0", 56 | "babel-plugin-array-includes": "^2.0.3", 57 | "babel-preset-es2015": "^6.24.1", 58 | "camelcase": "^4.0.0", 59 | "coveralls": "^3.0.0", 60 | "eslint-config-xo-lukechilds": "^1.0.0", 61 | "nyc": "^11.0.2", 62 | "rollup": "^0.52.0", 63 | "rollup-plugin-babel": "^2.7.1", 64 | "window": "^4.2.1", 65 | "xo": "^0.18.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # when-dom-ready 2 | 3 | > $(document).ready() for the 21st century 4 | 5 | [![Build Status](https://travis-ci.org/lukechilds/when-dom-ready.svg?branch=master)](https://travis-ci.org/lukechilds/when-dom-ready) 6 | [![Coverage Status](https://coveralls.io/repos/github/lukechilds/when-dom-ready/badge.svg?branch=master)](https://coveralls.io/github/lukechilds/when-dom-ready?branch=master) 7 | [![npm](https://img.shields.io/npm/v/when-dom-ready.svg)](https://www.npmjs.com/package/when-dom-ready) 8 | [![GitHub Donate](https://badgen.net/badge/GitHub/Sponsor/D959A7?icon=github)](https://github.com/sponsors/lukechilds) 9 | [![Bitcoin Donate](https://badgen.net/badge/Bitcoin/Donate/F19537?icon=bitcoin)](https://lu.ke/tip/bitcoin) 10 | [![Lightning Donate](https://badgen.net/badge/Lightning/Donate/F6BC41?icon=bitcoin-lightning)](https://lu.ke/tip/lightning) 11 | 12 | Returns a Promise for cleaner usage, provides a Promise chain helper function and can also be used as a pure function. The Promise will resolve instantly if the DOM is already ready. 13 | 14 | ## Browser Compatibility 15 | 16 | - IE9+ (requires Promise polyfill) 17 | - Edge * 18 | - Firefox 29+ 19 | - Safari 8+ 20 | - Chrome 33+ 21 | - Opera 23+ 22 | 23 | ## Install 24 | 25 | ```shell 26 | npm install --save when-dom-ready 27 | ``` 28 | 29 | or for quick testing: 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ## Usage 36 | 37 | ```js 38 | import whenDomReady from 'when-dom-ready'; 39 | 40 | whenDomReady().then(() => console.log('DOM is ready yo!')); 41 | ``` 42 | 43 | You can still use callbacks if you want to: 44 | 45 | ```js 46 | whenDomReady(() => console.log('DOM is ready yo!')); 47 | ``` 48 | 49 | ## Promise chain helper 50 | 51 | There is also a little helper function, `whenDomReady.resume()`, that pauses the execution of a Promise chain and then resumes it with the last value once the DOM is ready. 52 | 53 | This allows you to specify complex async control flow in simple readable code: 54 | 55 | ```js 56 | fetch('/my-badass-api.json') 57 | .then(getSomeProcessingDoneWhileWaitingForDom) 58 | .then(whenDomReady.resume()) 59 | .then(injectDataIntoDom); 60 | ``` 61 | 62 | ## Pure usage 63 | 64 | You can make the function pure by passing in a `document` object. This is really [useful for tests](https://github.com/lukechilds/when-dom-ready/blob/master/test/unit.js) and mocking environments. 65 | 66 | For example this works in Node.js: 67 | 68 | ```js 69 | const Window = require('window'); 70 | const whenDomReady = require('when-dom-ready'); 71 | 72 | const { document } = new Window(); 73 | 74 | whenDomReady(document).then(() => console.log('DOM is ready yo!')); 75 | ``` 76 | 77 | Again, you can use the callback version as a pure function too: 78 | 79 | ```js 80 | whenDomReady(() => console.log('DOM is ready yo!'), document); 81 | ``` 82 | 83 | And of course the helper: 84 | 85 | ```js 86 | //... 87 | .then(whenDomReady.resume(document)) 88 | ``` 89 | 90 | ## License 91 | 92 | MIT © Luke Childs 93 | --------------------------------------------------------------------------------