diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fcf8e8d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: node_js +node_js: 6.3 +before_script: + - npm install -g testee +script: npm test +before_install: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" +addons: + firefox: "47.0" diff --git a/attr.js b/attr.js new file mode 100644 index 0000000..f313e9f --- /dev/null +++ b/attr.js @@ -0,0 +1,118 @@ +var forEach = Array.prototype.forEach; + +class CustomAttributeRegistry { + constructor(ownerDocument){ + if(!ownerDocument) { + throw new Error("Must be given a document"); + } + + this.ownerDocument = ownerDocument; + this._attrs = {}; + this._elementMap = new WeakMap(); + this._observe(); + } + + define(attrName, Constructor) { + this._attrs[attrName] = Constructor; + this._upgrade(attrName); + } + + get(attrName){ + return this._attrs[attrName]; + } + + _observe(){ + var customAttributes = this; + var document = this.ownerDocument; + var root = document.documentElement; + + this.attrMO = new MutationObserver(function(mutations){ + forEach.call(mutations, function(m){ + var attr = customAttributes.get(m.attributeName); + if(attr) { + customAttributes._found(m.attributeName, m.target, m.oldValue); + } + }); + }); + + this.attrMO.observe(root, { + subtree: true, + attributes: true, + attributeOldValue: true + }); + + this.childMO = new MutationObserver(function(mutations){ + var downgrade = customAttributes._downgrade.bind(customAttributes); + forEach.call(mutations, function(m){ + forEach.call(m.removedNodes, downgrade); + }); + }); + + this.childMO.observe(root, { + childList: true, + subtree: true + }); + } + + _upgrade(attrName) { + var document = this.ownerDocument; + var matches = document.querySelectorAll("[" + attrName + "]"); + for(var match of matches) { + this._found(attrName, match); + } + } + + _downgrade(element) { + var map = this._elementMap.get(element); + if(!map) return; + + for(var inst of map.values()) { + if(inst.disconnectedCallback) { + inst.disconnectedCallback(); + } + } + + this._elementMap.delete(element); + } + + _found(attrName, el, oldVal) { + var map = this._elementMap.get(el); + if(!map) { + map = new Map(); + this._elementMap.set(el, map); + } + + var inst = map.get(attrName); + var newVal = el.getAttribute(attrName); + if(!inst) { + var Constructor = this.get(attrName); + inst = new Constructor(); + map.set(attrName, inst); + inst.ownerElement = el; + inst.name = attrName; + inst.value = newVal; + if(inst.connectedCallback) { + inst.connectedCallback(); + } + } + // Attribute was removed + else if(newVal == null && !!inst.value) { + inst.value = newVal; + if(inst.disconnectedCallback) { + inst.disconnectedCallback(); + } + + map.delete(attrName); + } + // Attribute changed + else if(newVal !== inst.value) { + inst.value = newVal; + if(inst.changedCallback) { + inst.changedCallback(oldVal, newVal); + } + } + + } +} + +window.customAttributes = new CustomAttributeRegistry(document); diff --git a/package.json b/package.json new file mode 100644 index 0000000..9ad7ad2 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "custom-attributes", + "version": "1.0.0", + "description": "Custom attributes, like custom elements, but for attributes", + "main": "attr.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "testee test/connect.html test/disconnect.html --browsers firefox" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/matthewp/custom-attributes.git" + }, + "keywords": [ + "web", + "components" + ], + "files": [ + "attr.js" + ], + "author": "Matthew Phillips", + "license": "BSD-2-Clause", + "bugs": { + "url": "https://github.com/matthewp/custom-attributes/issues" + }, + "homepage": "https://github.com/matthewp/custom-attributes#readme", + "devDependencies": { + "mocha-test": "^1.0.3", + "webcomponents.js": "^0.7.22" + } +} diff --git a/test/all.html b/test/all.html new file mode 100644 index 0000000..c082d82 --- /dev/null +++ b/test/all.html @@ -0,0 +1,26 @@ + + + + custom-attributes tests + + + +
+ + +
+ + diff --git a/test/connect.html b/test/connect.html new file mode 100644 index 0000000..bc1fc15 --- /dev/null +++ b/test/connect.html @@ -0,0 +1,73 @@ + + + + simple test + + + + + + + +
+

This article is static

+
+ +
+ + + + + + + + diff --git a/test/disconnect.html b/test/disconnect.html new file mode 100644 index 0000000..0b996a1 --- /dev/null +++ b/test/disconnect.html @@ -0,0 +1,77 @@ + + + + disconnectedCallback() tests + + + + + + +
+ + + + + +