├── .gitignore ├── Gruntfile.js ├── README.md ├── bower.json ├── dist ├── html-include.html └── html-include.min.js ├── footer.html ├── header.html ├── index.html ├── package.json └── src ├── html-include.html └── html-include.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | 11 | pids 12 | logs 13 | results 14 | node_modules 15 | 16 | npm-debug.log 17 | bower_components/* 18 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | 'jslint' : { 5 | all : { 6 | src : [ 'src/*.js' ], 7 | directives : { 8 | indent : 2 9 | } 10 | } 11 | }, 12 | 'uglify' : { 13 | target : { 14 | files : { 'dist/html-include.min.js' : 'src/html-include.js' } 15 | } 16 | }, 17 | 'connect': { 18 | demo: { 19 | options: { 20 | open: true, 21 | keepalive: true 22 | } 23 | } 24 | }, 25 | 'gh-pages': { 26 | options: { 27 | clone: 'bower_components/html-include' 28 | }, 29 | src: [ 30 | 'bower_components/**/*', 31 | '!bower_components/html-include/**/*', 32 | 'demo/*', 'src/*', 'index.html', 'header.html', 'footer.html' 33 | ] 34 | }, 35 | vulcanize: { 36 | default: { 37 | options: { 38 | inline: true, 39 | 'strip-excludes' : false, 40 | excludes: { 41 | imports: [ "polymer.html" ] 42 | } 43 | }, 44 | files: { 45 | 'dist/html-include.html' : 'dist/html-include.html' 46 | } 47 | } 48 | }, 49 | clean : [ 'dist/html-include.js' ], 50 | 'replace': { 51 | example: { 52 | src: ['src/*'], 53 | dest: 'dist/', 54 | replacements: [ 55 | { 56 | from: 'bower_components', 57 | to: '..' 58 | }, 59 | { 60 | from: 'html-include.js', 61 | to: 'html-include.min.js' 62 | } 63 | ] 64 | } 65 | } 66 | }); 67 | 68 | grunt.loadNpmTasks('grunt-jslint'); 69 | grunt.loadNpmTasks('grunt-contrib-connect'); 70 | grunt.loadNpmTasks('grunt-gh-pages'); 71 | grunt.loadNpmTasks('grunt-text-replace'); 72 | grunt.loadNpmTasks('grunt-contrib-uglify'); 73 | grunt.loadNpmTasks('grunt-vulcanize'); 74 | grunt.loadNpmTasks('grunt-contrib-clean'); 75 | 76 | grunt.registerTask('lint', ['jslint']); 77 | grunt.registerTask('default', ['jslint', 'replace', 'uglify', 'clean', 'vulcanize']); 78 | grunt.registerTask('deploy', ['gh-pages']); 79 | grunt.registerTask('server', ['connect']); 80 | 81 | }; 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # <html-include> 2 | 3 | 4 | **<html-include>** is a very simple Web Component for including the raw content of an HTML file. 5 | 6 | It's somewhat similar to doing a [PHP include](http://php.net/manual/en/function.include.php) for a plain html file (looks familiar? ``), but on the browser side. 7 | 8 | It looks like this: 9 | 10 | ```html 11 | 12 | ``` 13 | 14 | Whatever content you load with it, it will replace the ``'s **outerHTML**. 15 | 16 | This is a vanilla-js component and it has no dependencies. 17 | 18 | ## Demo 19 | 20 | Check a demo here: http://chris-l.github.io/html-include/ 21 | 22 | ## Performance 23 | 24 | Actually, perhaps you would get better performance by just having everything on your main html and not using this. ;) 25 | 26 | Using <html-include> means it will make an extra request for each file it loads. 27 | 28 | But, if you are not expecting certain files to change, you can configure your server cache control for those files to improve performance and reduce requests. 29 | 30 | If it's an option, try to use server-side includes instead. (For static sites, you could use a preprocessor, like [jekyll](http://jekyllrb.com/) or [harpjs](http://harpjs.com/)) 31 | 32 | ## Installation 33 | 34 | You can just copy the `dist/html-include.min.js` file somewhere onto your server, or you can use bower: 35 | 36 | ```bash 37 | bower install --save html-include 38 | ``` 39 | 40 | ## Usage 41 | 42 | First, make sure you have the webcomponent's polyfill: 43 | 44 | ```html 45 | 55 | ``` 56 | 57 | Then you can choose to either include the HTML, like a regular web component: 58 | 59 | ```html 60 | 61 | ``` 62 | 63 | Or actually, you can just include the script: 64 | 65 | ```html 66 | 67 | ``` 68 | 69 | (just do one of the two, don't do both) 70 | 71 | Do it immediately after the webcomponents polyfill, and before any other scripts. (So it can capture the `DOMContentLoaded` event) 72 | 73 | After that you can use it like this: 74 | 75 | ```html 76 | 77 | ``` 78 | 79 | Whatever content `header.html` has, it will replace the actual `html-include` tag in the DOM. 80 | 81 | If the file in the `src` can't be read, then it will be replaced by an empty string. 82 | 83 | It will load the content using a `XMLHttpRequest`, and prevent emitting the `DOMContentLoaded` event (and the `window load` event) until all the `html-include` elements (with the src attribute) on the DOM are resolved. 84 | At that moment, a `DOMContentLoaded` event will be emitted, and it will see the fully composed html. 85 | 86 | ## Options 87 | 88 | Attribute | Options | Default | Description 89 | --- | --- | --- | --- 90 | `src` | *string* | `''` | Name of the file to be included. 91 | `prevent-cache`| *boolean* | false | Add a random string as query parameter to the `src` file, to prevent cache. 92 | 93 | ## License 94 | 95 | [MIT License](http://opensource.org/licenses/MIT) 96 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-include", 3 | "version": "0.1.3", 4 | "authors": [ 5 | "Christopher Luna " 6 | ], 7 | "main": "dist/html-include.html", 8 | "description": "Simple element to include the raw content of an HTML file", 9 | "keywords": [ 10 | "html", 11 | "include", 12 | "web-components" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "*/.*", 17 | "node_modules", 18 | "bower_components" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /dist/html-include.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /dist/html-include.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";function b(b,c){var d=new XMLHttpRequest;(b.preventCache||null!==b.getAttribute("prevent-cache"))&&(c+=(/\?/.test(c)?"&":"?")+(new Date).getTime()),d.open("GET",c,!0),d.onreadystatechange=function(){var c,e,f;4===d.readyState&&(c=d.responseText||"",null!==b.parentNode?b.outerHTML=c:b.content=c,e=new DOMParser,f=e.parseFromString(c,"text/xml"),Array.prototype.forEach.call(f.querySelectorAll("script"),function(b){var c,d=b.getAttribute("src");a.addEventListener("beforeLoad",function(){d&&(c=document.createElement("script"),c.setAttribute("src",d),document.querySelector("head").appendChild(c)),d||eval.call(null,b.innerHTML)})}))},d.send()}var c;!function(){var b,c,d=new Event("DOMContentLoaded"),e=new Event("load"),f=new Event("beforeLoad"),g=!1;b=function(a){a.stopImmediatePropagation(),g=!0},c=function(h){h.stopImmediatePropagation();var i=setInterval(function(){0===document.body.querySelectorAll("html-include[src]").length&&(clearInterval(i),document.removeEventListener("DOMContentLoaded",c,!0),a.removeEventListener("load",b,!0),a.dispatchEvent(f),document.dispatchEvent(d),g&&a.dispatchEvent(e))},1)},a.addEventListener("load",b,!0),document.addEventListener("DOMContentLoaded",c,!0)}(),c=Object.create(a.HTMLElement.prototype),c.attributeChangedCallback=function(a,b,c){"src"===a&&(this.src=c)},c.attachedCallback=function(){this.content&&(this.outerHTML=this.content)},c.createdCallback=function(){var a,c=this;return(a=this.src||this.getAttribute("src")||!1)?void b(this,a):void Object.defineProperty(this,"src",{set:function(a){b(c,a)},get:function(){return c.getAttribute("src")||""}})},document.registerElement("html-include",{prototype:c})}(this); -------------------------------------------------------------------------------- /footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

This is the footer, inside footer.html

4 |
5 |
6 | -------------------------------------------------------------------------------- /header.html: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <html-include> demo 8 | 9 | 10 | 16 | 17 | 18 | 28 | 29 | 30 | 31 |
32 |

<html-include>

33 |

This is how the DOM looks like at the DOMContentLoaded event (before inserting the content of <pre>).
Compare it against the raw HTML source.

34 |

35 |     
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "grunt": "~0.4.1", 4 | "grunt-cli": "~0.1.9", 5 | "grunt-contrib-clean": "^0.6.0", 6 | "grunt-contrib-connect": "~0.9.0", 7 | "grunt-contrib-uglify": "^0.9.1", 8 | "grunt-gh-pages": "~0.9.1", 9 | "grunt-jslint": "^1.1.14", 10 | "grunt-text-replace": "~0.4.0", 11 | "grunt-vulcanize": "^0.6.4" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/html-include.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/html-include.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true, indent: 2, evil: true*/ 2 | /*global Event, DOMParser*/ 3 | 4 | (function (window) { 5 | 'use strict'; 6 | var proto; 7 | 8 | 9 | /** 10 | * Prevent the regular DOMContentLoaded event from bubbling, 11 | * and instead, manually emit a DOMContentLoaded event once 12 | * there are no more * html-include elements with a src attribute. 13 | */ 14 | (function () { 15 | var CustomDOMContentLoaded = new Event('DOMContentLoaded'), 16 | CustomWindowLoad = new Event('load'), 17 | beforeLoad = new Event('beforeLoad'), 18 | windowEmitted = false, 19 | windowListener, 20 | listener; 21 | 22 | windowListener = function (e) { 23 | e.stopImmediatePropagation(); 24 | windowEmitted = true; 25 | }; 26 | listener = function (e) { 27 | e.stopImmediatePropagation(); 28 | var inter = setInterval(function () { 29 | if (document.body.querySelectorAll('html-include[src]').length === 0) { 30 | clearInterval(inter); 31 | document.removeEventListener('DOMContentLoaded', listener, true); 32 | window.removeEventListener('load', windowListener, true); 33 | window.dispatchEvent(beforeLoad); 34 | document.dispatchEvent(CustomDOMContentLoaded); 35 | if (windowEmitted) { 36 | window.dispatchEvent(CustomWindowLoad); 37 | } 38 | } 39 | }, 1); 40 | }; 41 | window.addEventListener('load', windowListener, true); 42 | document.addEventListener('DOMContentLoaded', listener, true); 43 | }()); 44 | 45 | 46 | function xhr(that, uri) { 47 | var r = new XMLHttpRequest(); 48 | 49 | if (that.preventCache || that.getAttribute('prevent-cache') !== null) { 50 | uri += (/\?/.test(uri) ? '&' : '?') + (new Date().getTime()); 51 | } 52 | 53 | r.open("GET", uri, true); 54 | r.onreadystatechange = function () { 55 | var response, parser, doc; 56 | if (r.readyState !== 4) { 57 | return; 58 | } 59 | response = r.responseText || ''; 60 | 61 | // It is already attached? 62 | if (that.parentNode !== null) { 63 | that.outerHTML = response; 64 | } else { 65 | //It is not, save the content. 66 | that.content = response; 67 | } 68 | 69 | parser = new DOMParser(); 70 | doc = parser.parseFromString(response, "text/xml"); 71 | Array.prototype.forEach.call(doc.querySelectorAll('script'), function (script) { 72 | var src = script.getAttribute('src'), ele; 73 | window.addEventListener('beforeLoad', function () { 74 | if (src) { 75 | ele = document.createElement('script'); 76 | ele.setAttribute('src', src); 77 | document.querySelector('head').appendChild(ele); 78 | } 79 | if (!src) { 80 | eval.call(null, script.innerHTML); 81 | } 82 | }); 83 | }); 84 | }; 85 | r.send(); 86 | } 87 | 88 | proto = Object.create(window.HTMLElement.prototype); 89 | 90 | /*jslint unparam:true*/ 91 | proto.attributeChangedCallback = function (attr, oldVal, newVal) { 92 | if (attr === 'src') { 93 | this.src = newVal; 94 | } 95 | }; 96 | /*jslint unparam:false*/ 97 | 98 | proto.attachedCallback = function () { 99 | // If it already has content, just replace it. 100 | if (this.content) { 101 | this.outerHTML = this.content; 102 | } 103 | }; 104 | 105 | proto.createdCallback = function () { 106 | var that = this, src; 107 | src = this.src || this.getAttribute('src') || false; 108 | 109 | if (src) { 110 | xhr(this, src); 111 | return; 112 | } 113 | Object.defineProperty(this, 'src', { 114 | set : function (val) { 115 | xhr(that, val); 116 | }, 117 | get : function () { 118 | return that.getAttribute('src') || ''; 119 | } 120 | }); 121 | }; 122 | document.registerElement('html-include', { 123 | prototype : proto 124 | }); 125 | }(this)); 126 | --------------------------------------------------------------------------------