├── .gitignore ├── bower.json ├── .babelrc ├── src ├── util │ ├── selectorhooks.js │ ├── accessorhooks.js │ ├── eventhooks.js │ ├── index.js │ ├── animationhandler.js │ ├── selectormatcher.js │ ├── stylehooks.js │ └── eventhandler.js ├── node │ ├── index.js │ ├── contains.js │ ├── clone.js │ ├── get.js │ ├── fire.js │ ├── set.js │ ├── on.js │ └── find.js ├── const.js ├── element │ ├── matches.js │ ├── index.js │ ├── offset.js │ ├── children.js │ ├── value.js │ ├── css.js │ ├── visibility.js │ ├── classes.js │ ├── traversing.js │ └── manipulation.js ├── errors.js ├── index.js └── document │ ├── importstyles.js │ ├── importscripts.js │ ├── index.js │ ├── create.js │ └── extend.js ├── conf ├── jsdoc.json ├── karma.conf.js ├── jshintrc-test.json └── karma.conf-ci.js ├── .jshintrc ├── test ├── spec │ ├── element │ │ ├── tostring.spec.js │ │ ├── children.spec.js │ │ ├── contains.spec.js │ │ ├── offset.spec.js │ │ ├── clone.spec.js │ │ ├── fire.spec.js │ │ ├── get.spec.js │ │ ├── find.spec.js │ │ ├── visibility.spec.js │ │ ├── classes.spec.js │ │ ├── set.spec.js │ │ ├── value.spec.js │ │ ├── traversing.spec.js │ │ ├── matches.spec.js │ │ ├── css.spec.js │ │ ├── manipulation.spec.js │ │ └── on.spec.js │ └── dom │ │ ├── noconflict.spec.js │ │ ├── constructor.spec.js │ │ ├── mock.spec.js │ │ ├── importscripts.spec.js │ │ ├── create.spec.js │ │ ├── importstyles.spec.js │ │ └── extend.spec.js ├── context.html ├── animation.html └── lib │ └── jasmine-better-dom-matchers.js ├── .travis.yml ├── LICENSE.txt ├── package.json ├── task └── compile.js ├── README.md ├── gulpfile.js └── dist └── better-dom.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | bower_components 4 | coverage 5 | *.log 6 | build 7 | docs 8 | conf/sauce.json 9 | .publish 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "better-dom", 3 | "version": "4.1.0", 4 | "main": [ 5 | "dist/better-dom.js" 6 | ], 7 | "ignore": [ 8 | ".*", 9 | "test", 10 | "src", 11 | "conf", 12 | "task", 13 | "gulpfile.js", 14 | "package.json" 15 | ] 16 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "retainLines": true, 3 | "presets": [ 4 | ["@babel/env", { 5 | "targets": { 6 | browsers: ["ChromeAndroid 30", "iOS 7", "IE 10"] 7 | }, 8 | "loose": true, 9 | "modules": false 10 | }] 11 | ], 12 | "ignore": [ 13 | "./bower_components/**/*.js", 14 | "./node_modules/**/*.js" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/util/selectorhooks.js: -------------------------------------------------------------------------------- 1 | // import { computeStyle } from "../util/index"; 2 | 3 | // var isHidden = (node) => { 4 | // var computed = computeStyle(node); 5 | 6 | // return computed.visibility === "hidden" || computed.display === "none"; 7 | // }; 8 | 9 | export default { 10 | ":focus": (node) => node === node.ownerDocument.activeElement 11 | 12 | // ":visible": (node) => !isHidden(node), 13 | 14 | // ":hidden": isHidden 15 | }; 16 | -------------------------------------------------------------------------------- /conf/jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "opts": { 6 | "destination": "./docs" 7 | }, 8 | "plugins": [ 9 | "plugins/markdown" 10 | ], 11 | "templates": { 12 | "cleverLinks": false, 13 | "monospaceLinks": false, 14 | "path": "ink-docstrap", 15 | "theme": "spacelab", 16 | "navType": "vertical", 17 | "linenums": true, 18 | "dateFormat": "MMMM Do YYYY, h:mm:ss" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/node/index.js: -------------------------------------------------------------------------------- 1 | import { UNKNOWN_NODE } from "../const"; 2 | 3 | /** 4 | * Used to represent a node in better-dom 5 | * @class $Node 6 | */ 7 | export function $Node(node) { 8 | if (node) { 9 | this[0] = node; 10 | // use a generated property to store a reference 11 | // to the wrapper for circular object binding 12 | node["<%= prop() %>"] = this; 13 | } 14 | } 15 | 16 | $Node.prototype = { 17 | toString: () => "", 18 | valueOf: () => UNKNOWN_NODE // undefined 19 | }; 20 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | 4 | "boss": true, 5 | "newcap": false, 6 | "eqnull": true, 7 | 8 | "quotmark": "double", 9 | "indent": 4, 10 | "eqeqeq": true, 11 | "undef": true, 12 | "noarg": true, 13 | "noempty": true, 14 | "trailing": true, 15 | "maxdepth": 6, 16 | "unused": true, 17 | "immed": true, 18 | "latedef": true, 19 | "nonew": true, 20 | "lastsemic": true, 21 | 22 | "globals": { 23 | "process": true, 24 | "module": true, 25 | "require": true 26 | } 27 | } -------------------------------------------------------------------------------- /src/util/accessorhooks.js: -------------------------------------------------------------------------------- 1 | var hooks = {get: {}, set: {}}; 2 | 3 | // fix camel cased attributes 4 | "tabIndex readOnly maxLength cellSpacing cellPadding rowSpan colSpan useMap frameBorder contentEditable".split(" ").forEach((key) => { 5 | hooks.get[ key.toLowerCase() ] = (node) => node[key]; 6 | }); 7 | 8 | // style hook 9 | hooks.get.style = (node) => node.style.cssText; 10 | hooks.set.style = (node, value) => { node.style.cssText = value }; 11 | // some browsers don't recognize input[type=email] etc. 12 | hooks.get.type = (node) => node.getAttribute("type") || node.type; 13 | 14 | export default hooks; 15 | -------------------------------------------------------------------------------- /conf/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | "use strict"; 3 | 4 | config.set({ 5 | basePath: "..", 6 | singleRun: true, 7 | frameworks: ["jasmine"], 8 | browsers: ["ChromeHeadless"], 9 | preprocessors: { "build/better-dom.js": "coverage" }, 10 | coverageReporter: { 11 | type: "html", 12 | dir: "coverage/" 13 | }, 14 | files: [ 15 | "./test/lib/jasmine-better-dom-matchers.js", 16 | "./build/better-dom.js", 17 | "./test/spec/**/*.spec.js" 18 | ] 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /src/const.js: -------------------------------------------------------------------------------- 1 | /* globals window, document */ 2 | 3 | // globals 4 | export const WINDOW = window; 5 | export const DOCUMENT = document; 6 | export const HTML = DOCUMENT.documentElement; 7 | 8 | // constants 9 | export const UNKNOWN_NODE = 0; 10 | export const ELEMENT_NODE = DOCUMENT.ELEMENT_NODE; 11 | export const DOCUMENT_NODE = DOCUMENT.DOCUMENT_NODE; 12 | export const VENDOR_PREFIXES = ["Webkit", "O", "Moz", "ms"]; 13 | export const FAKE_ANIMATION_NAME = "v<%= prop() %>"; 14 | export const SHEET_PROP_NAME = "<%= prop() %>sheet"; 15 | 16 | // feature checks 17 | export const WEBKIT_PREFIX = WINDOW.WebKitAnimationEvent ? "-webkit-" : ""; 18 | -------------------------------------------------------------------------------- /src/util/eventhooks.js: -------------------------------------------------------------------------------- 1 | import { HTML, DOCUMENT } from "../const"; 2 | 3 | var hooks = {}; 4 | /* istanbul ignore if */ 5 | if ("onfocusin" in HTML) { 6 | hooks.focus = (handler) => { handler._type = "focusin" }; 7 | hooks.blur = (handler) => { handler._type = "focusout" }; 8 | } else { 9 | // firefox doesn't support focusin/focusout events 10 | hooks.focus = hooks.blur = (handler) => { 11 | handler.options.capture = true; 12 | }; 13 | } 14 | /* istanbul ignore else */ 15 | if (DOCUMENT.createElement("input").validity) { 16 | hooks.invalid = (handler) => { 17 | handler.options.capture = true; 18 | }; 19 | } 20 | 21 | export default hooks; 22 | -------------------------------------------------------------------------------- /conf/jshintrc-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | 4 | "newcap": false, 5 | "eqnull": true, 6 | 7 | "quotmark": "double", 8 | "indent": 4, 9 | "eqeqeq": true, 10 | "undef": true, 11 | "noarg": true, 12 | "noempty": true, 13 | "trailing": true, 14 | "maxdepth": 6, 15 | "unused": true, 16 | "immed": true, 17 | "latedef": true, 18 | "nonew": true, 19 | "lastsemic": true, 20 | 21 | "globals": { 22 | "DOM": true, 23 | 24 | "beforeEach": true, 25 | "afterEach": true, 26 | "describe": true, 27 | "it": true, 28 | "expect": true, 29 | "jasmine": true, 30 | "spyOn": true 31 | } 32 | } -------------------------------------------------------------------------------- /test/spec/element/tostring.spec.js: -------------------------------------------------------------------------------- 1 | describe("Node", function() { 2 | "use strict"; 3 | 4 | // it("should always have length property", function() { 5 | // expect(DOM.find("abc").length).toBe(0); 6 | // }); 7 | 8 | it("should have overloaded toString", function() { 9 | var link = DOM.create(""), 10 | input = DOM.create(""), 11 | spans = DOM.createAll(""), 12 | empty = DOM.mock(); 13 | 14 | expect(link.toString()).toBe(""); 15 | expect(input.toString()).toBe(""); 16 | expect(spans.toString()).toBe(","); 17 | expect(empty.toString()).toBe("#unknown"); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/spec/dom/noconflict.spec.js: -------------------------------------------------------------------------------- 1 | describe("DOM.noConflict", function() { 2 | "use strict"; 3 | 4 | beforeEach(function() { 5 | this.currentDOM = window.DOM; 6 | }); 7 | 8 | afterEach(function() { 9 | window.DOM = this.currentDOM; 10 | }); 11 | 12 | it("should return to the previous state", function() { 13 | expect(this.currentDOM.noConflict()).toBe(this.currentDOM); 14 | expect(window.DOM).toBeUndefined(); 15 | }); 16 | 17 | it("should not touch changed state", function() { 18 | var otherDOM = {}; 19 | 20 | window.DOM = otherDOM; 21 | 22 | expect(this.currentDOM.noConflict()).toBe(this.currentDOM); 23 | expect(window.DOM).toBe(otherDOM); 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/spec/element/children.spec.js: -------------------------------------------------------------------------------- 1 | describe("children", function() { 2 | "use strict"; 3 | 4 | beforeEach(function() { 5 | 6 | }); 7 | 8 | it("should read all children elements", function() { 9 | var select = DOM.mock(""); 10 | 11 | expect(select.children().length).toBe(2); 12 | }); 13 | 14 | it("should allow to filter children by selector", function() { 15 | var list = DOM.create("
"); 16 | 17 | expect(list.children().length).toBe(3); 18 | expect(list.children("a").length).toBe(0); 19 | expect(list.children("li").length).toBe(3); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | import { WINDOW, ELEMENT_NODE } from "../const"; 2 | 3 | const arrayProto = Array.prototype; 4 | 5 | export const every = arrayProto.every; 6 | export const each = arrayProto.forEach; 7 | export const filter = arrayProto.filter; 8 | export const map = arrayProto.map; 9 | export const slice = arrayProto.slice; 10 | export const isArray = Array.isArray; 11 | export const keys = Object.keys; 12 | export const raf = WINDOW.requestAnimationFrame; 13 | 14 | export function computeStyle(node) { 15 | return node.ownerDocument.defaultView.getComputedStyle(node); 16 | } 17 | 18 | export function injectElement(node) { 19 | if (node && node.nodeType === ELEMENT_NODE) { 20 | return node.ownerDocument.getElementsByTagName("head")[0].appendChild(node); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | dist: trusty 5 | sudo: required 6 | addons: 7 | chrome: stable 8 | sauce_connect: true 9 | git: 10 | depth: 1 11 | cache: 12 | directories: 13 | - node_modules 14 | install: 15 | - npm install 16 | - cd node_modules/es6-module-transpiler && npm install 17 | - npm install -g gulp 18 | script: 19 | - gulp test 20 | - gulp sauce 21 | env: 22 | global: 23 | - secure: JUEHpRY6mX0+vHbm/swGR+x0NZDSqusihJyk9oHY1PzoqPCiXLWxZu/3gxR9zwy4CF3EudNDVf+lNTLiAI0SsBRfQBgxwlVYEhbWk8Fcqt5WLEG/zkkv0NheFdrKQbgA6oerL6zstwRUwY5hgCN8MgVQaYnXlCiQmpMk/7HzVcM= 24 | - secure: EaanBOOFDcsSnv6OkTsEeBRXVHhW9iGq4vnUg7qmsL3llWJdt2abcQEM6xlkGsA7xeX/CZwNIrwcb5mPD26gonJEctGsEsc/VWkI18zc0LVZbNMFxcn0IZijuPjs/jCpoXJ/y0szLb53zcrISz8pEur0Bt19DqUG/Vw/tYxfqmw= 25 | -------------------------------------------------------------------------------- /src/element/matches.js: -------------------------------------------------------------------------------- 1 | import { $Element } from "./index"; 2 | import { MethodError } from "../errors"; 3 | import SelectorMatcher from "../util/selectormatcher"; 4 | import HOOK from "../util/selectorhooks"; 5 | 6 | /** 7 | * Check if element matches a specified selector 8 | * @param {String} selector css selector for checking 9 | * @return {Boolean} `true` if matches and `false` otherwise 10 | * @example 11 | * DOM.find("body").matches("html>body"); // => true 12 | * DOM.find("body").matches("body>html"); // => false 13 | */ 14 | $Element.prototype.matches = function(selector) { 15 | if (!selector || typeof selector !== "string") { 16 | throw new MethodError("matches", arguments); 17 | } 18 | 19 | const checker = HOOK[selector] || SelectorMatcher(selector); 20 | 21 | return !!checker(this[0]); 22 | }; 23 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | import { map } from "./util/index"; 2 | 3 | // customized errors 4 | 5 | export function MethodError(methodName, args, type = "$Element") { 6 | var url = "<%= pkg.docs %>/" + type + ".html#" + methodName, 7 | line = "invalid call `" + type + (type === "DOM" ? "." : "#") + methodName + "("; 8 | 9 | line += map.call(args, String).join(", ") + ")`. "; 10 | 11 | this.message = line + "Check " + url + " to verify the arguments"; 12 | } 13 | 14 | MethodError.prototype = new TypeError(); 15 | 16 | export function StaticMethodError(methodName, args) { 17 | MethodError.call(this, methodName, args, "DOM"); 18 | } 19 | 20 | StaticMethodError.prototype = new TypeError(); 21 | 22 | export function DocumentTypeError(methodName, args) { 23 | MethodError.call(this, methodName, args, "$Document"); 24 | } 25 | 26 | DocumentTypeError.prototype = new TypeError(); 27 | -------------------------------------------------------------------------------- /test/spec/element/contains.spec.js: -------------------------------------------------------------------------------- 1 | describe("contains", function() { 2 | "use strict"; 3 | 4 | var testEl; 5 | 6 | beforeEach(function() { 7 | jasmine.sandbox.set("
"); 8 | 9 | testEl = DOM.find("#test"); 10 | }); 11 | 12 | it("should accept a DOM element", function() { 13 | expect(testEl.contains(testEl.find("a"))).toBeTruthy(); 14 | }); 15 | 16 | it("should return true for node itself", function() { 17 | expect(testEl.contains(testEl)).toBeTruthy(); 18 | }); 19 | 20 | it("should throw error if the first argument is not a DOM or native node", function() { 21 | expect(function() { testEl.contains(2); }).toThrow(); 22 | }); 23 | 24 | it("should return false for empty node", function() { 25 | expect(DOM.find("some-node").contains(DOM)).toBe(false); 26 | }); 27 | }); -------------------------------------------------------------------------------- /src/element/index.js: -------------------------------------------------------------------------------- 1 | import { $Node } from "../node/index"; 2 | import { UNKNOWN_NODE, ELEMENT_NODE } from "../const"; 3 | 4 | /** 5 | * Used to represent an element in better-dom 6 | * @class $Element 7 | * @extends {$Node} 8 | */ 9 | export function $Element(node) { 10 | if (this instanceof $Element) { 11 | $Node.call(this, node); 12 | } else if (node) { 13 | // create a new wrapper or return existing object 14 | return node["<%= prop() %>"] || new $Element(node); 15 | } else { 16 | return new $Element(); 17 | } 18 | } 19 | 20 | const ElementProto = new $Node(); 21 | 22 | $Element.prototype = ElementProto; 23 | 24 | ElementProto.valueOf = function() { 25 | const node = this[0]; 26 | return node ? ELEMENT_NODE : UNKNOWN_NODE; 27 | }; 28 | 29 | ElementProto.toString = function() { 30 | const node = this[0]; 31 | 32 | return node ? "<" + node.tagName.toLowerCase() + ">" : "#unknown"; 33 | }; 34 | -------------------------------------------------------------------------------- /src/node/contains.js: -------------------------------------------------------------------------------- 1 | import { $Node } from "./index"; 2 | import { $Element } from "../element/index"; 3 | import { MethodError } from "../errors"; 4 | 5 | /** 6 | * Check if an element is inside of the current context 7 | * @param {$Node} element Element to check 8 | * @return {Boolean} `true` if success and `false` otherwise 9 | * @example 10 | * DOM.contains(DOM.find("body")); // => true 11 | * DOM.find("body").contains(DOM); // => false 12 | */ 13 | $Node.prototype.contains = function(element) { 14 | const node = this[0]; 15 | 16 | if (!node) return false; 17 | 18 | if (element instanceof $Element) { 19 | const otherNode = element[0]; 20 | 21 | if (otherNode === node) return true; 22 | /* istanbul ignore else */ 23 | if (node.contains) { 24 | return node.contains(otherNode); 25 | } else { 26 | return node.compareDocumentPosition(otherNode) & 16; 27 | } 28 | } 29 | 30 | throw new MethodError("contains", arguments); 31 | }; 32 | -------------------------------------------------------------------------------- /src/node/clone.js: -------------------------------------------------------------------------------- 1 | import { $Node } from "./index"; 2 | import { $Element } from "../element/index"; 3 | import { $Document } from "../document/index"; 4 | import { MethodError } from "../errors"; 5 | 6 | /** 7 | * Clone element 8 | * @param {Boolean} deepCopy `true` when all children should also be cloned, otherwise `false` 9 | * @return {$Node} A clone of the current element 10 | * @example 11 | * ul.clone(true); // => clone of