├── docs ├── api │ └── README.md └── README.md ├── src ├── end.js ├── clone.js ├── data.js ├── ready.js ├── filter.js ├── prop.js ├── attr.js ├── base.js ├── css-classes.js ├── manipulation.js ├── css.js ├── traversing.js ├── wrap.js ├── dimensions.js ├── utils.js └── event.js ├── codecov.yml ├── test ├── ready-test.js ├── filter-test.js ├── base-test.js ├── end-test.js ├── data-test.js ├── setup.js ├── clone-test.js ├── css-test.js ├── prop-test.js ├── attr-test.js ├── css-classes-test.js ├── manipulation-test.js ├── traversing-test.js ├── dimensions-test.js ├── wrap-test.js └── event-test.js ├── .eslintrc ├── .travis.yml ├── LICENSE ├── karma.conf.js ├── .eslintignore ├── package.json ├── .gitignore ├── dist ├── uxr.min.js └── uxr.js └── README.md /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # API Reference -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Read Me](/README.md) 4 | * [API References](/docs/api/README.md) -------------------------------------------------------------------------------- /src/end.js: -------------------------------------------------------------------------------- 1 | /** 2 | * end 3 | **/ 4 | 5 | _.extend.end = function () { 6 | return this.prevObj || this; 7 | }; -------------------------------------------------------------------------------- /src/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * clone 3 | **/ 4 | 5 | /* global mutated */ 6 | 7 | _.extend.clone = function (deep = false) { 8 | return mutated(this, this.el.map(item => item.cloneNode(deep))); 9 | }; -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | coverage: 4 | precision: 2 5 | round: down 6 | range: "70...100" 7 | status: 8 | project: 9 | default: 10 | target: auto 11 | patch: 12 | default: 13 | target: auto 14 | changes: 15 | default: 16 | branches: null 17 | comment: off -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * data 3 | **/ 4 | 5 | /* global toDomString */ 6 | 7 | _.extend.data = function (name, value) { 8 | let domName = toDomString(name); 9 | 10 | if (typeof value !== 'undefined') { 11 | this.el.forEach(item => item.dataset[domName] = value); 12 | return this; 13 | } 14 | 15 | return this.el[0].dataset[domName]; 16 | }; -------------------------------------------------------------------------------- /test/ready-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ready-test 3 | **/ 4 | 5 | describe('Ready', () => { 6 | it('should run a function when Document is ready', () => { 7 | let i = 0; 8 | 9 | _.ready(() => i++); 10 | }); 11 | }); 12 | 13 | describe('Load', () => { 14 | it('should run a function when Document is loaded', () => { 15 | let i = 1; 16 | 17 | _.load(() => i++); 18 | }); 19 | }); -------------------------------------------------------------------------------- /src/ready.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ready 3 | **/ 4 | 5 | /* istanbul ignore next */ 6 | const _stateChange = (condition) => { 7 | return fn => { 8 | return document.addEventListener('readystatechange', e => { 9 | if (e.target.readyState === condition) { 10 | fn(); 11 | } 12 | }); 13 | }; 14 | }; 15 | 16 | _.ready = _stateChange('interactive'); 17 | 18 | _.load = _stateChange('complete'); -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * filter 3 | **/ 4 | 5 | /* global mutated */ 6 | 7 | _.extend.filter = function (selector) { 8 | return mutated(this, this.el.filter(item => item.matches(selector))); 9 | }; 10 | 11 | _.extend.find = _.extend.has = function (selector) { 12 | return mutated(this, this.el.map(item => [...item.querySelectorAll(selector)]).reduce((acc, cur) => acc.concat(cur), [])); 13 | }; 14 | 15 | _.extend.not = function (selector) { 16 | return mutated(this, this.el.filter(item => !item.matches(selector))); 17 | }; -------------------------------------------------------------------------------- /test/filter-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * filter-test 3 | **/ 4 | 5 | describe('Filter Methods', () => { 6 | describe('Not', () => { 7 | it('should return a subset of non-matching elements', () => { 8 | let list = _('#traversing-list li'); 9 | let notApples = list.not('.apple').addClass('.not-apples'); 10 | 11 | expect(notApples.length).to.be.equal(_('#traversing-list .not-apples').length); 12 | expect(notApples.el).to.deep.equal(_('#traversing-list .not-apples').el); 13 | }); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/base-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * base-test 3 | **/ 4 | 5 | describe('Constructor', () => { 6 | let objSelector = _([1, 2, 3]); 7 | let emptySelector = _({}); 8 | 9 | it('should equal to selector if selector is an object', () => { 10 | expect(objSelector.el.toString()).to.equal(objSelector.selector.toString()); 11 | }); 12 | 13 | it('should return and empty array if selector is not matched', () => { 14 | emptySelector.el.length.should.equal(0); 15 | }); 16 | 17 | it('should return hash as "0"', () => { 18 | hashCode('').should.equal(0); 19 | }); 20 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "globals": { 11 | "_": true, 12 | "uxr": true 13 | }, 14 | "extends": "eslint:recommended", 15 | "rules": { 16 | "curly": "error", 17 | "eqeqeq": [ 18 | "error", 19 | "smart" 20 | ], 21 | "global-require": "error", 22 | "quotes": [ 23 | "error", 24 | "single", 25 | { 26 | "avoidEscape": true, 27 | "allowTemplateLiterals": true 28 | } 29 | ], 30 | "semi": "error", 31 | "space-before-blocks": "error" 32 | } 33 | } -------------------------------------------------------------------------------- /src/prop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * prop 3 | **/ 4 | 5 | _.extend.prop = function (prop, value) { 6 | if (value) { 7 | return this.setProp(prop, value); 8 | } 9 | 10 | return this.getProp(prop); 11 | }; 12 | 13 | _.extend.setProp = function (prop, value) { 14 | this.el.forEach(item => item[prop] = value); 15 | 16 | return this; 17 | }; 18 | 19 | _.extend.getProp = function (prop) { 20 | return this[0][prop]; 21 | }; 22 | 23 | _.extend.text = function (txt) { 24 | return this.prop('innerText', txt); 25 | }; 26 | 27 | _.extend.html = function (html) { 28 | return this.prop('innerHTML', html); 29 | }; 30 | 31 | _.extend.value = function (value) { 32 | return this.prop('value', value); 33 | }; -------------------------------------------------------------------------------- /test/end-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * end-test 3 | **/ 4 | 5 | describe('End', () => { 6 | // end setup 7 | (() => { 8 | let div = createElement('div', {id: 'end'}); 9 | let div2 = createElement('div', {id: 'end-inner'}); 10 | 11 | appendTo(div, div2); 12 | appendToBody(div); 13 | })(); 14 | 15 | let endElem = _('#end'); 16 | 17 | it('should return the selection itself if selection is not filtered', () => { 18 | expect(endElem.end()).to.be.equal(endElem); 19 | }); 20 | 21 | it('should revert the previously selected element', () => { 22 | let endInner = endElem.find('#end-inner'); 23 | expect(endInner.end()).to.be.equal(endElem); 24 | }); 25 | }); -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - '12' 6 | 7 | git: 8 | depth: 1 9 | 10 | addons: 11 | chrome: stable 12 | 13 | cache: 14 | yarn: true 15 | directories: 16 | - node_modules 17 | 18 | install: 19 | - yarn 20 | 21 | script: 22 | - yarn run ci 23 | 24 | before_script: 25 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 26 | - chmod +x ./cc-test-reporter 27 | - ./cc-test-reporter before-build 28 | 29 | after_success: 30 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 31 | - cat ./coverage/coverage-final.json | node_modules/codecov.io/bin/codecov.io.js 32 | - rm -rf ./coverage -------------------------------------------------------------------------------- /src/attr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * attr 3 | **/ 4 | 5 | /* global toDomString */ 6 | 7 | _.extend.attr = function (attr, value) { 8 | if (value) { 9 | return this.setAttribute(attr, value); 10 | } 11 | 12 | return this.getAttr(attr); 13 | }; 14 | 15 | _.extend.setAttr = _.extend.setAttribute = function (attr, value) { 16 | this.el.forEach(item => item.setAttribute(toDomString(attr), value)); 17 | 18 | return this; 19 | }; 20 | 21 | _.extend.getAttr = _.extend.getAttribute = function (attr) { 22 | return this.el[0].getAttribute(toDomString(attr)); 23 | }; 24 | 25 | _.extend.removeAttr = _.extend.removeAttribute = function (attr) { 26 | this.el.forEach(item => item.removeAttribute(toDomString(attr))); 27 | 28 | return this; 29 | }; 30 | 31 | _.extend.src = function (url) { 32 | return this.attr('src', url); 33 | }; 34 | 35 | _.extend.href = function (url) { 36 | return this.attr('href', url); 37 | }; -------------------------------------------------------------------------------- /src/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * uxr 3 | **/ 4 | 5 | const _ = window['uxr'] = function (selector) { 6 | return new uxr(selector); 7 | }; 8 | 9 | const uxr = function (selector) { 10 | this.selector = selector; 11 | this.init(); 12 | }; 13 | 14 | _.extend = uxr.prototype = { 15 | constructor: uxr, 16 | 17 | init: function () { 18 | const _this = this; 19 | 20 | if (typeof this.selector === 'string') { 21 | this.el = document.querySelectorAll(this.selector); 22 | } 23 | 24 | else if (this.selector.length) { 25 | this.el = this.selector; 26 | } 27 | 28 | else { 29 | this.el = []; 30 | } 31 | 32 | this.el = [...this.el]; 33 | 34 | for (let i = 0; i < this.el.length; i++) { 35 | _this[i] = this.el[i]; 36 | } 37 | 38 | this.length = this.el.length; 39 | } 40 | }; 41 | 42 | _.extend.init.prototype = _.extend; -------------------------------------------------------------------------------- /src/css-classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * css-classes 3 | **/ 4 | 5 | /* global normalizeClassName */ 6 | /* global maybeMultiple */ 7 | 8 | const _class = type => { 9 | return function (className) { 10 | this.el.forEach(item => { 11 | if (item.nodeType === 1) { 12 | maybeMultiple(className).map(className => item.classList[type](normalizeClassName(className))); 13 | } 14 | }); 15 | 16 | return this; 17 | }; 18 | }; 19 | 20 | _.extend.addClass = _class('add'); 21 | 22 | _.extend.removeClass = _class('remove'); 23 | 24 | _.extend.hasClass = function (className) { 25 | return this.el[0].nodeType === 1 && this.filter('.' + normalizeClassName(className)).length > 0; 26 | }; 27 | 28 | _.extend.toggleClass = function (className) { 29 | this.el.forEach(item => { 30 | let classNames = maybeMultiple(className); 31 | 32 | if (item.nodeType === 1) { 33 | classNames.forEach(className => item.classList.toggle(normalizeClassName(className))); 34 | } 35 | }); 36 | 37 | return this; 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Bilal Çınarlı 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 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * karma config 3 | */ 4 | 5 | const karmaConfig = config => 6 | config.set({ 7 | frameworks: ['mocha', 'chai'], 8 | files: ['src/base.js', 'src/**/*.js', 'test/setup.js', 'test/base-test.js', 'test/**/*-test.js'], 9 | reporters: ['mocha', 'coverage'], 10 | preprocessors: { 11 | 'src/**/*.js': ['coverage'] 12 | }, 13 | coverageReporter: { 14 | reporters: [ 15 | {type: 'text-summary'}, 16 | {type: 'html', subdir: '.'}, 17 | {type: 'lcovonly', subdir: '.'}, 18 | {type: 'json', subdir: '.'} 19 | ] 20 | }, 21 | logLevel: config.LOG_INFO, 22 | browsers: ['ChromeHeadless', 'TravisChrome'], 23 | customLaunchers: { 24 | TravisChrome: { 25 | base: 'ChromeHeadless', 26 | flags: ['--no-sandbox', '--disable-translate', 27 | '--disable-extensions', '--no-first-run', 28 | '--disable-background-networking', '--remote-debugging-port=9223'] 29 | } 30 | }, 31 | autoWatch: false 32 | }); 33 | 34 | module.exports = karmaConfig; -------------------------------------------------------------------------------- /src/manipulation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * manipulation 3 | **/ 4 | 5 | /* global getInsertableElement */ 6 | /* global insertBefore */ 7 | 8 | const _insert = ({where, parent}) => { 9 | return function(stringOrObject) { 10 | this.el.forEach( 11 | item => insertBefore(stringOrObject, item, where, parent)); 12 | 13 | return this; 14 | }; 15 | }; 16 | 17 | _.extend.empty = function () { 18 | this.el.forEach(item => item.innerHTML = ''); 19 | 20 | return this; 21 | }; 22 | 23 | _.extend.remove = function () { 24 | this.el.forEach(item => item.parentNode.removeChild(item)); 25 | 26 | return this; 27 | }; 28 | 29 | _.extend.append = function (stringOrObject) { 30 | this.el.forEach(item => item.appendChild(getInsertableElement(stringOrObject))); 31 | 32 | return this; 33 | }; 34 | 35 | _.extend.prepend = _insert({where: 'firstChild', parent: false}); 36 | 37 | _.extend.after = _insert({where: 'nextSibling', parent: true}); 38 | 39 | _.extend.before = _insert({where: 'self', parent: true}); 40 | 41 | _.extend.replaceWith = function (stringOrObject) { 42 | this.el.map( 43 | item => item.parentNode.replaceChild(getInsertableElement(stringOrObject), item) 44 | ); 45 | 46 | return this; 47 | }; -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | test/ 41 | build/ 42 | dist/ 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | *.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /test/data-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * data-tests 3 | **/ 4 | 5 | describe('Data Manager', () => { 6 | // data setup 7 | (() => { 8 | let div = createElement('div', {id: 'data'}); 9 | div.setAttribute('data-' + controls.dataAttr, controls.dataValue); 10 | 11 | let div2 = createElement('div', {id: 'data2'}); 12 | div2.setAttribute('data-' + controls.dataAttr, controls.dataValue); 13 | 14 | appendToBody(div); 15 | appendToBody(div2); 16 | })(); 17 | 18 | let dataElem = _('#data'); 19 | 20 | it('should return the value of data-* attribute', () => { 21 | expect(dataElem.data(controls.dataAttr)).to.equal(controls.dataValue); 22 | }); 23 | 24 | it('should change the value of data-* attribute', () => { 25 | dataElem.data(controls.dataAttr, controls.newDataValue); 26 | expect(dataElem.data(controls.dataAttr)).to.equal(controls.newDataValue); 27 | }); 28 | 29 | it('should sets a new data-* attribute', () => { 30 | dataElem.data(controls.newDataAttr, controls.newDataValue); 31 | dataElem.data('uxr-empty', ''); 32 | 33 | expect(dataElem.data(controls.newDataAttr)).to.equal(controls.newDataValue); 34 | expect(dataElem.data('uxr-empty')).to.equal(''); 35 | }); 36 | }); -------------------------------------------------------------------------------- /src/css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * css 3 | **/ 4 | 5 | /* global toDomString */ 6 | /* global isObject */ 7 | 8 | const maybePropIsObject = prop => { 9 | let properties = []; 10 | let values = []; 11 | 12 | if (isObject(prop)) { 13 | Object.keys(prop).forEach(p => { 14 | properties.push(toDomString(p)); 15 | values.push(prop[p]); 16 | }); 17 | return {properties, values}; 18 | } 19 | 20 | return false; 21 | }; 22 | 23 | const setProps = (props) => { 24 | if (typeof props === 'string') { 25 | return [toDomString(props)]; 26 | } 27 | 28 | if (Array.isArray(props)) { 29 | return props.map(p => toDomString(p)); 30 | } 31 | }; 32 | 33 | const getStyles = (el, props) => { 34 | let list = {}; 35 | 36 | props.forEach(prop => { 37 | list[prop] = el.style[prop]; 38 | }); 39 | 40 | return props.length === 1 ? list[props[0]] : list; 41 | }; 42 | 43 | 44 | _.extend.css = function (prop, value) { 45 | let options = { 46 | properties: [], 47 | values: value ? [value] : [] 48 | }; 49 | 50 | options.properties = setProps(prop); 51 | 52 | // if the prop is object for set of prop/value pair 53 | options = maybePropIsObject(prop) || options; 54 | 55 | if (options.values.length > 0) { 56 | this.el.map(item => options.properties.forEach((p, i) => item.style[p] = options.values[i])); 57 | 58 | return this; 59 | } 60 | 61 | // if no value set then we are asking for getting the values of properties 62 | // this breaks the chaining 63 | return getStyles(this.el[0], options.properties); 64 | }; -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * setup 3 | **/ 4 | 5 | let controls = { 6 | innerText: 'Hello World!', 7 | newInnerText: 'New innerText Value', 8 | innerHTML: 'Hello new World!', 9 | newInnerHTML: 'Hello to you too!', 10 | href: 'https://uxrocket.io/', 11 | newHref: 'https://github.com/uxrocket', 12 | src: 'https://avatars1.githubusercontent.com/u/9591419', 13 | newSrc: 'https://uxrocket.io/dummy.jpg', 14 | value: 'Hello World!', 15 | newValue: 'New input value', 16 | className: 'my-class', 17 | newClassName: 'my-new-class', 18 | missingClassName: 'my-missing-class', 19 | dataAttr: 'uxr-demo', 20 | dataValue: 'uxr-data', 21 | newDataAttr: 'uxr-new', 22 | newDataValue: 'uxr-defined-data' 23 | }; 24 | 25 | const createElement = (type, attributes) => { 26 | let element = document.createElement(type); 27 | 28 | if (attributes) { 29 | Object.keys(attributes).forEach(key => { 30 | if (typeof attributes[key] === 'object' && !Array.isArray(attributes[key])) { 31 | Object.keys(attributes[key]).forEach(prop => { 32 | element[key][prop] = attributes[key][prop]; 33 | }); 34 | } 35 | else { 36 | element[key] = attributes[key]; 37 | } 38 | }); 39 | } 40 | 41 | return element; 42 | }; 43 | 44 | const appendTo = (to, elements) => { 45 | let list = Array.isArray(elements) ? elements : [elements]; 46 | 47 | list.forEach(element => to.appendChild(element)); 48 | 49 | return to; 50 | }; 51 | 52 | const appendToBody = elements => appendTo(document.body, elements); -------------------------------------------------------------------------------- /test/clone-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * clone-test 3 | **/ 4 | 5 | describe('Clone', () => { 6 | let div = createElement('div', {id: 'clone'}); 7 | let list = createElement('nav', {id: 'clone-list'}); 8 | let link1 = createElement('a', {id: 'clone-link1', innerText: 'Link1'}); 9 | let link2 = createElement('a', {id: 'clone-link2', innerText: 'Link2'}); 10 | let list2 = createElement('nav', {id: 'clone-list2'}); 11 | let link3 = createElement('a', {id: 'clone-link3', innerText: 'Link3'}); 12 | let link4 = createElement('a', {id: 'clone-link4', innerText: 'Link4'}); 13 | 14 | appendTo(div, [appendTo(list, [link1, link2]), appendTo(list2, [link3, link4])]); 15 | appendToBody(div); 16 | 17 | it('should clone the UXR obj elements without children', () => { 18 | let original = _('#clone-list a'); 19 | let clones = _('#clone-list a').clone(); 20 | clones.addClass('cloned-element').attr('id', ''); 21 | 22 | _('#clone-list').append(clones); 23 | 24 | expect(clones.length).to.be.equal(original.length); 25 | expect(clones[0].innerText).to.be.equal(''); 26 | expect(_('#clone-list .cloned-element').length).to.be.greaterThan(0); 27 | }); 28 | 29 | it('should deep clone the UXR obj elements', () => { 30 | let original = _('#clone-list2 a'); 31 | let clones = _('#clone-list2 a').clone(true); 32 | clones.addClass('cloned-element').attr('id', ''); 33 | 34 | _('#clone-list2').append(clones); 35 | 36 | expect(clones.length).to.be.equal(original.length); 37 | expect(clones[0].innerText).to.be.equal(original[0].innerText); 38 | expect(_('#clone-list2 .cloned-element').length).to.be.greaterThan(0); 39 | }); 40 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uxr", 3 | "version": "0.7.0", 4 | "description": "DOM utilities", 5 | "main": "index.js", 6 | "scripts": { 7 | "ci": "yarn run test-ci && yarn run report-coverage", 8 | "test": "karma start --browsers ChromeHeadless", 9 | "test-ci": "yarn run lint && karma start --browsers TravisChrome --singleRun", 10 | "lint": "eslint src/**/*.js --fix", 11 | "build": "node -e 'require(\"./build\").build()'", 12 | "minify": "node -e 'require(\"./build\").minify()'", 13 | "release": "node -e 'require(\"./build\").release()'", 14 | "report-coverage": "codecov", 15 | "watch:test": "karma start --browsers ChromeHeadless --autoWatch", 16 | "watch:build": "nodemon --watch src --exec 'yarn run lint && yarn run build'", 17 | "watch": "yarn run watch:build & yarn run watch:test" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/bcinarli/uxr.git" 22 | }, 23 | "keywords": [ 24 | "JavaScript", 25 | "ES6", 26 | "DOM", 27 | "Utilities" 28 | ], 29 | "author": "Bilal Cinarli ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/bcinarli/uxr/issues" 33 | }, 34 | "homepage": "https://github.com/bcinarli/uxr#readme", 35 | "devDependencies": { 36 | "chai": "^4.2.0", 37 | "codecov": "^4.0.0-0", 38 | "eslint": "^6.3.0", 39 | "eslint-config-standard": "^14.1.0", 40 | "google-closure-compiler": "^20190819.0.0", 41 | "karma": "^4.3.0", 42 | "karma-chai": "^0.1.0", 43 | "karma-chrome-launcher": "^3.1.0", 44 | "karma-coverage": "^2.0.1", 45 | "karma-mocha": "^1.3.0", 46 | "karma-mocha-reporter": "^2.2.5", 47 | "mocha": "^6.2.0", 48 | "nodemon": "^1.19.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/traversing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * traversing 3 | **/ 4 | 5 | /* global mutated */ 6 | 7 | const _mapFilterAndMutate = map => { 8 | return function (selector) { 9 | return mutated( 10 | this, 11 | this.el.map(item => item[map]).filter(item => selector ? item.matches(selector) : item)); 12 | }; 13 | }; 14 | 15 | _.extend.closest = function (selector) { 16 | let el = this.el[0]; 17 | 18 | if (!selector) { 19 | return mutated(this, [el.parentNode]); 20 | } 21 | 22 | while (el !== null && el.nodeType === 1) { 23 | if (el.matches(selector)) { 24 | return mutated(this, [el]); 25 | } 26 | 27 | el = el.parentNode; 28 | } 29 | 30 | return mutated(this, []); 31 | }; 32 | 33 | _.extend.parent = _mapFilterAndMutate('parentNode'); 34 | 35 | _.extend.children = function (selector) { 36 | return mutated( 37 | this, 38 | this.el.map(item => Array.from(item.children)) 39 | .reduce((acc, cur) => acc.concat(cur), []) 40 | .filter(item => selector ? item.matches(selector) : item)); 41 | }; 42 | 43 | _.extend.siblings = function (selector) { 44 | return mutated( 45 | this, 46 | this.el.map(item => 47 | Array.from(item.parentNode.children) 48 | .filter(child => !child.isEqualNode(item))) 49 | .reduce((acc, cur) => acc.concat(cur), []) 50 | .filter(item => selector ? item.matches(selector) : item)); 51 | }; 52 | 53 | _.extend.next = _mapFilterAndMutate('nextElementSibling'); 54 | 55 | _.extend.prev = _mapFilterAndMutate('previousElementSibling'); 56 | 57 | _.extend.first = function () { 58 | return mutated(this, this.el.filter((item, index) => index === 0)); 59 | }; 60 | 61 | _.extend.last = function () { 62 | let last = this.length - 1; 63 | return mutated(this, this.el.filter((item, index) => index === last)); 64 | }; 65 | -------------------------------------------------------------------------------- /src/wrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wrap 3 | **/ 4 | 5 | /* global elementFromString */ 6 | 7 | const getWrapper = wrapperStr => { 8 | let wrapperString = wrapperStr.toString(); 9 | 10 | return wrapperString.charAt(0) !== '<' ? 11 | document.createElement(wrapperString) : 12 | elementFromString(wrapperString); 13 | }; 14 | 15 | _.extend.wrap = function (wrapper) { 16 | let newWrap = getWrapper(wrapper); 17 | 18 | let parent = this.el[0].parentNode; 19 | let siblings = this.el[0].nextSibling; 20 | 21 | newWrap.appendChild(this.el[0]); 22 | 23 | if (siblings) { 24 | parent.insertBefore(newWrap, siblings); 25 | } 26 | else { 27 | parent.appendChild(newWrap); 28 | } 29 | 30 | return this; 31 | }; 32 | 33 | _.extend.wrapAll = function (wrapper) { 34 | let firstSibling = true; 35 | let newWrap = getWrapper(wrapper); 36 | 37 | this.el.forEach(item => { 38 | if (firstSibling) { 39 | let parent = item.parentNode; 40 | let siblings = item.nextSibling; 41 | 42 | newWrap.appendChild(item); 43 | 44 | if (siblings) { 45 | parent.insertBefore(newWrap, siblings); 46 | } 47 | else { 48 | parent.appendChild(newWrap); 49 | } 50 | 51 | firstSibling = false; 52 | } 53 | 54 | else { 55 | newWrap.appendChild(item); 56 | } 57 | }); 58 | 59 | return this; 60 | }; 61 | 62 | _.extend.unwrap = function (selector) { 63 | let parent = this.el[0].parentNode; 64 | 65 | // if the parent is not the desired one, skip unwrapping 66 | if (selector && !parent.matches(selector.toString())) { 67 | return this; 68 | } 69 | 70 | parent.parentNode.appendChild(this.el[0]); 71 | 72 | if (parent.children.length === 0) { 73 | parent.parentNode.removeChild(parent); 74 | } 75 | 76 | return this; 77 | }; -------------------------------------------------------------------------------- /test/css-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * css-test 3 | **/ 4 | 5 | describe('CSS', () => { 6 | // css setup 7 | (() => { 8 | let div = createElement('div', {id: 'css'}); 9 | let inner = createElement('div', { 10 | id: 'css-test', 11 | style: {display: 'inline', color: 'blue', border: '1px solid red', marginTop: '10px'} 12 | }); 13 | let inner2 = createElement('div', {id: 'css-test2', style: {display: 'flex', fontWeight: 'bold'}}); 14 | 15 | appendTo(div, [inner, inner2]); 16 | appendToBody(div); 17 | })(); 18 | 19 | let css = _('#css-test, #css-test2'); 20 | 21 | it('should return the value of CSS property', () => { 22 | let display = css.css('display'); 23 | let marginTop = css.css('margin-top'); 24 | let marginTopAlt = css.css('marginTop'); 25 | 26 | expect(display).to.be.equal(css.el[0].style.display); 27 | expect(marginTop).to.be.equal(css.el[0].style.marginTop); 28 | expect(marginTopAlt).to.be.equal(css.el[0].style.marginTop); 29 | }); 30 | 31 | it('should return the value of list of CSS property', () => { 32 | let properties = css.css(['display', 'margin-top', 'color']); 33 | 34 | expect(properties.display).to.be.equal(css.el[0].style.display); 35 | expect(properties.marginTop).to.be.equal(css.el[0].style.marginTop); 36 | expect(properties.color).to.be.equal(css.el[0].style.color); 37 | }); 38 | 39 | it('should update the value of CSS property', () => { 40 | css.css('padding', '10px'); 41 | 42 | css.el.forEach(elm => { 43 | expect(elm.style.padding).to.be.equal('10px'); 44 | }); 45 | }); 46 | 47 | it('should update the set of CSS properties', () => { 48 | css.css({width: '100px', height: '50px', 'margin-bottom': '5px'}); 49 | 50 | expect(css[0].style.width).to.be.equal('100px'); 51 | expect(css[0].style.height).to.be.equal('50px'); 52 | expect(css[0].style.marginBottom).to.be.equal('5px'); 53 | }); 54 | }); -------------------------------------------------------------------------------- /src/dimensions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dimensions 3 | **/ 4 | 5 | /* global removeUnit */ 6 | 7 | const _getContentSize = (el, type) => { 8 | let client = type === 'width' ? 'clientWidth' : 'clientHeight'; 9 | let styleFirst = type === 'width' ? 'paddingLeft' : 'paddingTop'; 10 | let styleLast = type === 'width' ? 'paddingRight' : 'paddingBottom'; 11 | 12 | return el[client] - removeUnit(el.style[styleFirst]) - removeUnit(el.style[styleLast]); 13 | }; 14 | 15 | const _contentSize = type => { 16 | return function (newSize) { 17 | if (this.length === 0) { 18 | return false; 19 | } 20 | 21 | if (newSize) { 22 | this.el.forEach(item => item.style[type] = newSize); 23 | 24 | return this; 25 | } 26 | 27 | return _getContentSize(this.el[0], type); 28 | }; 29 | }; 30 | 31 | const _clientSize = type => { 32 | return function () { 33 | return this.length > 0 ? this.el[0][type] : false; 34 | }; 35 | }; 36 | 37 | const _getMarginSize = (el, type) => { 38 | let styleFirst = type === 'offsetWidth' ? 'marginLeft' : 'marginTop'; 39 | let styleLast = type === 'offsetHeight' ? 'marginRight' : 'marginBottom'; 40 | 41 | return removeUnit(el.style[styleFirst]) + removeUnit(el.style[styleLast]); 42 | }; 43 | 44 | const _offsetSize = type => { 45 | return function (includeMargins = false) { 46 | 47 | if (this.length === 0) { 48 | return false; 49 | } 50 | 51 | let el = this.el[0]; 52 | let sizeType = el[type]; 53 | 54 | if (includeMargins) { 55 | sizeType += _getMarginSize(el, type); 56 | } 57 | 58 | return sizeType; 59 | }; 60 | }; 61 | 62 | _.extend.contentWidth = _.extend.width = _contentSize('width'); 63 | _.extend.clientWidth = _.extend.innerWidth = _clientSize('clientWidth'); 64 | _.extend.offsetWidth = _.extend.outerWidth = _offsetSize('offsetWidth'); 65 | 66 | _.extend.contentHeight = _.extend.height = _contentSize('height'); 67 | _.extend.clientHeight = _.extend.innerHeight = _clientSize('clientHeight'); 68 | _.extend.offsetHeight = _.extend.outerHeight = _offsetSize('offsetHeight'); -------------------------------------------------------------------------------- /test/prop-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * prop-test 3 | **/ 4 | 5 | describe('Prop', () => { 6 | // prop setup 7 | (() => { 8 | let div = createElement('div', {id: 'prop'}); 9 | let paragraph = createElement('p', {id: 'prop-paragraph', innerText: controls.innerText}); 10 | let input = createElement('input', {id: 'prop-input', type: 'text', value: controls.value}); 11 | let span = createElement('span', {id: 'prop-span', innerHTML: controls.innerHTML}); 12 | 13 | appendTo(div, [paragraph, input, span]); 14 | appendToBody(div); 15 | })(); 16 | 17 | let paragraphElem = _('#prop-paragraph'); 18 | let inputElem = _('#prop-input'); 19 | let spanElem = _('#prop-span'); 20 | 21 | describe('text', () => { 22 | it('should get the text in the selected element', () => { 23 | expect(paragraphElem.text()).to.be.a('string'); 24 | expect(paragraphElem.text()).to.equal(controls.innerText); 25 | }); 26 | 27 | it(`should set the text in the selected element to "${controls.newInnerText}"`, () => { 28 | paragraphElem.text(controls.newInnerText); 29 | 30 | expect(paragraphElem.text()).to.be.a('string'); 31 | expect(paragraphElem.text()).to.equal(controls.newInnerText); 32 | }); 33 | }); 34 | 35 | describe('html', () => { 36 | it('should get the innerHTML of the selected element', () => { 37 | expect(spanElem.html()).to.equal(controls.innerHTML); 38 | }); 39 | 40 | it(`should set the innerHTML of the selected element to "${controls.newInnerHTML}`, () => { 41 | spanElem.html(controls.newInnerHTML); 42 | 43 | expect(spanElem.html()).to.equal(controls.newInnerHTML); 44 | }); 45 | }); 46 | 47 | describe('value', () => { 48 | it('should get the value of an input', () => { 49 | expect(inputElem.value()).to.equal(controls.value); 50 | }); 51 | 52 | it(`should set the value of an input element to "${controls.newValue}"`, () => { 53 | inputElem.value(controls.newValue); 54 | 55 | expect(inputElem.value()).to.equal(controls.newValue); 56 | }); 57 | }); 58 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | debug 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | ### VisualStudioCode template 64 | .vscode/* 65 | !.vscode/settings.json 66 | !.vscode/tasks.json 67 | !.vscode/launch.json 68 | !.vscode/extensions.json 69 | ### JetBrains template 70 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 71 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 72 | 73 | # User-specific stuff: 74 | .idea 75 | 76 | # CMake 77 | cmake-build-debug/ 78 | 79 | # Mongo Explorer plugin: 80 | .idea/**/mongoSettings.xml 81 | 82 | ## File-based project format: 83 | *.iws 84 | 85 | ## Plugin-specific files: 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # mpeltonen/sbt-idea plugin 91 | .idea_modules/ 92 | 93 | # JIRA plugin 94 | atlassian-ide-plugin.xml 95 | 96 | # Cursive Clojure plugin 97 | .idea/replstate.xml 98 | 99 | # Crashlytics plugin (for Android Studio and IntelliJ) 100 | com_crashlytics_export_strings.xml 101 | crashlytics.properties 102 | crashlytics-build.properties 103 | fabric.properties 104 | -------------------------------------------------------------------------------- /test/attr-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * manipulation-test 3 | **/ 4 | 5 | describe('Attribute Methods', () => { 6 | // attribute setup 7 | (() => { 8 | let div = createElement('div', {id: 'attr'}); 9 | let anchor = createElement('a', {id: 'attr-anchor', href: controls.href}); 10 | let img = createElement('img', {id: 'attr-img', src: controls.src}); 11 | 12 | appendTo(div, [anchor, img]); 13 | appendToBody(div); 14 | })(); 15 | 16 | let attrElem = _('#attr'); 17 | let attrElemNode = document.querySelector('#attr'); 18 | 19 | let anchorElem = _('#attr-anchor'); 20 | let imgElem = _('#attr-img'); 21 | 22 | describe('attr', () => { 23 | describe('Gets any attribute value of the selected element', () => { 24 | it('should get the value of `id` attribute', () => { 25 | expect(attrElem.attr('id')).to.equal('attr'); 26 | expect(attrElem.attr('id')).to.equal(attrElemNode.id); 27 | }); 28 | }); 29 | 30 | describe('Sets the attribute value of the selected element', () => { 31 | it('should set a new attribute named `test-attr` with a value of "My Attribute Value"', () => { 32 | attrElem.attr('test-attr', 'My Attribute Value'); 33 | expect(attrElem.attr('test-attr')).to.equal('My Attribute Value'); 34 | }); 35 | }); 36 | }); 37 | 38 | describe('removeAttr', () => { 39 | it('should remove the attribute from element', () => { 40 | attrElem.removeAttr('test-attr'); 41 | expect(attrElem.attr('test-attr')).to.be.equal(null); 42 | }); 43 | }); 44 | 45 | describe('src', () => { 46 | it('should get the value of "src" attribute', () => { 47 | expect(imgElem.src()).to.equal(controls.src); 48 | }); 49 | 50 | it(`should set the value of "src" attribute to "${controls.newSrc}"`, () => { 51 | imgElem.src(controls.newSrc); 52 | 53 | expect(imgElem.src()).to.equal(controls.newSrc); 54 | }); 55 | }); 56 | 57 | describe('href', () => { 58 | it('should get the value of "href" attribute', () => { 59 | expect(anchorElem.href()).to.equal(controls.href); 60 | }); 61 | 62 | it(`should set the value of "href" attribute to "${controls.newHref}"`, () => { 63 | anchorElem.href(controls.newHref); 64 | 65 | expect(anchorElem.href()).to.equal(controls.newHref); 66 | }); 67 | }); 68 | }); -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * utils 3 | **/ 4 | 5 | Element.prototype.matches = Element.prototype.matches ? Element.prototype.matches : Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 6 | 7 | // eslint-disable-next-line 8 | const normalizeClassName = className => className.charAt(0) === '.' ? className.substr(1) : className; 9 | 10 | // generate hashes for internal usage 11 | // eslint-disable-next-line 12 | const hashCode = s => s.split('').reduce((a, b) => { 13 | a = ((a << 5) - a) + b.charCodeAt(0); 14 | return a & a; 15 | }, 0); 16 | 17 | // trims the string and replaces to multiple spaces in the string with single space 18 | // eslint-disable-next-line 19 | const justifyString = s => s.replace(/\s\s+/g, ' ').trim(); 20 | 21 | // split selector 22 | // eslint-disable-next-line 23 | const maybeMultiple = s => typeof s === 'string' ? justifyString(s).split(' ') : s; 24 | 25 | // Dom String Format 26 | // eslint-disable-next-line 27 | const toDomString = s => s.substr(0, 1).toLowerCase() + s.split('-').map(chunk => chunk.charAt(0).toUpperCase() + chunk.slice(1)).join('').substring(1); 28 | 29 | // Element from string 30 | // eslint-disable-next-line 31 | const elementFromString = s => { 32 | if (typeof s === 'string') { 33 | let template = document.createElement('template'); 34 | template.innerHTML = s.trim(); 35 | 36 | return template.content.firstChild; 37 | } 38 | 39 | return s; 40 | }; 41 | 42 | // Insertable Element 43 | // eslint-disable-next-line 44 | const getInsertableElement = s => { 45 | let insertableElement = elementFromString(s); 46 | 47 | if (insertableElement instanceof uxr) { 48 | let template = document.createElement('template'); 49 | insertableElement.el.forEach(item => template.content.appendChild(item)); 50 | insertableElement = template.content; 51 | } 52 | 53 | return insertableElement; 54 | }; 55 | 56 | // InserBefore 57 | // eslint-disable-next-line 58 | const insertBefore = (insert, target, ref, parent) => { 59 | let to = parent === true ? target.parentNode : target; 60 | let where = ref === 'self' ? target : target[ref]; 61 | 62 | to.insertBefore(getInsertableElement(insert), where); 63 | }; 64 | 65 | // mutatedObj 66 | // eslint-disable-next-line 67 | const mutated = (orgObj, newSet) => { 68 | let obj = _(newSet); 69 | 70 | obj.prevObj = orgObj; 71 | 72 | return obj; 73 | }; 74 | 75 | // Is Object => {key: value} 76 | // eslint-disable-next-line 77 | const isObject = objLike => ({}.toString.call(objLike) === '[object Object]'); 78 | 79 | // Remove Unit 80 | // eslint-disable-next-line 81 | const removeUnit = number => parseInt(number, 10); -------------------------------------------------------------------------------- /test/css-classes-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * class-test 3 | **/ 4 | 5 | describe('CSS Classes', () => { 6 | // css-classes setup 7 | (() => { 8 | let div = createElement('div', {id: 'css-classes'}); 9 | div.classList.add(controls.className); 10 | 11 | appendToBody(div); 12 | })(); 13 | 14 | let cssElem = _('#css-classes'); 15 | let cssElemNode = document.querySelector('#css-classes'); 16 | let noneElem = _([1, 2, 3]); 17 | 18 | describe('Add Class', () => { 19 | it('should add a new class to elements classList', () => { 20 | cssElem.addClass(controls.newClassName); 21 | cssElem.addClass('chained-class').addClass('.another-chained-class'); 22 | noneElem.addClass('this-will-be-skipped'); 23 | 24 | expect(cssElemNode.classList.contains(controls.newClassName)).to.be.equal(true); 25 | expect(cssElemNode.classList.contains('chained-class')).to.be.equal(true); 26 | }); 27 | }); 28 | 29 | describe('Add Classes with an array', () => { 30 | it('should add class from array list', () => { 31 | cssElem.addClass(['array', 'of', 'classnames']); 32 | 33 | expect(cssElemNode.classList.contains('array')).to.be.equal(true); 34 | expect(cssElemNode.classList.contains('of')).to.be.equal(true); 35 | expect(cssElemNode.classList.contains('classnames')).to.be.equal(true); 36 | }); 37 | }); 38 | 39 | describe('Remove Class', () => { 40 | it('should remove a new class to elements classList', () => { 41 | cssElem.removeClass(controls.newClassName); 42 | cssElem.removeClass('.chained-class').removeClass('another-chained-class'); 43 | 44 | expect(cssElemNode.classList.contains(controls.newClassName)).to.be.equal(false); 45 | }); 46 | }); 47 | 48 | describe('Has Class', () => { 49 | it('should check selected element whether has the css class or not ', () => { 50 | expect(cssElem.hasClass(controls.className)).to.be.equal(true); 51 | expect(cssElem.hasClass(controls.missingClassName)).to.be.equal(false); 52 | }); 53 | }); 54 | 55 | describe('Toggle Class', () => { 56 | it('should check toggles a css class from element. Adds the class if not present, removes it otherwise', () => { 57 | cssElem.toggleClass(controls.className).addClass('chained'); 58 | expect(cssElem.hasClass(controls.className)).to.be.equal(false); 59 | cssElem.toggleClass(controls.className); 60 | expect(cssElem.hasClass(controls.className)).to.be.equal(true); 61 | expect(cssElem.hasClass('chained')).to.be.equal(true); 62 | 63 | noneElem.toggleClass(controls.className); 64 | expect(noneElem.hasClass(controls.className)).to.be.equal(false); 65 | }); 66 | }); 67 | }); -------------------------------------------------------------------------------- /test/manipulation-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * manipulation-test 3 | **/ 4 | 5 | describe('Manipulation Methods', () => { 6 | // manipulation setup 7 | (() => { 8 | let div = createElement('div', {id: 'manipulation', innerHTML: '

Hello World

'}); 9 | let div2 = createElement('div', {id: 'manipulation-2', innerHTML: '

Hello World

'}); 10 | let div3 = createElement('div', {id: 'manipulation-3', innerHTML: '

Hello World

'}); 11 | let ul = createElement('ul', {id: 'manipulation-4', innerHTML: '
  • Apple
  • Banana
  • '}); 12 | let ul2 = createElement('ul', {id: 'manipulation-5', innerHTML: '
  • Apple2
  • Banana2
  • '}); 13 | 14 | appendToBody(div); 15 | appendToBody(div3); 16 | appendToBody(div2); 17 | appendToBody(ul); 18 | appendToBody(ul2); 19 | })(); 20 | 21 | let manipulationElem = _('#manipulation'); 22 | let manipulationElem2 = _('#manipulation-2'); 23 | let manipulationElem3 = _('#manipulation-3'); 24 | 25 | describe('Empty', () => { 26 | it('should clears element inner HTML', () => { 27 | manipulationElem.empty(); 28 | 29 | expect(manipulationElem.html()).to.be.equal(''); 30 | }); 31 | }); 32 | 33 | describe('Remove', () => { 34 | it('should removes the element', () => { 35 | manipulationElem.remove(); 36 | 37 | expect(_('#manipulation').length).to.be.equal(0); 38 | }); 39 | }); 40 | 41 | describe('After, with string', () => { 42 | it('should insert an element after selected element', () => { 43 | manipulationElem2.after('
    '); 44 | 45 | expect(manipulationElem2.el[0].nextSibling.matches('#inserted-after')).to.be.equal(true); 46 | }); 47 | }); 48 | 49 | describe('After, with object', () => { 50 | it('should insert an element after selected element', () => { 51 | manipulationElem2.after(manipulationElem3); 52 | 53 | expect(manipulationElem2.el[0].nextSibling.matches('#manipulation-3')).to.be.equal(true); 54 | }); 55 | }); 56 | 57 | describe('Before', () => { 58 | it('should insert an element before selected element', () => { 59 | manipulationElem2.before('
    '); 60 | 61 | expect(manipulationElem2.el[0].previousSibling.matches('#inserted-before')).to.be.equal(true); 62 | }); 63 | }); 64 | 65 | describe('Before, with object', () => { 66 | it('should insert an element before selected element', () => { 67 | manipulationElem2.before(manipulationElem3); 68 | 69 | expect(manipulationElem2.el[0].previousSibling.matches('#manipulation-3')).to.be.equal(true); 70 | }); 71 | }); 72 | 73 | describe('Append', () => { 74 | it('should insert an element inside the selected element', () => { 75 | manipulationElem2.append('
    '); 76 | 77 | let originalLi = _('#manipulation-4 li'); 78 | let li = _('#manipulation-5 li'); 79 | _('#manipulation-4').append(li); 80 | 81 | expect(manipulationElem2.el[0].lastChild.matches('#appended')).to.be.equal(true); 82 | expect(_('#manipulation-4 li').length).to.be.equal(li.length + originalLi.length); 83 | }); 84 | }); 85 | 86 | describe('Prepend', () => { 87 | it('should insert an element inside the selected element', () => { 88 | manipulationElem2.prepend('
    '); 89 | 90 | expect(manipulationElem2.el[0].firstChild.matches('#prepended')).to.be.equal(true); 91 | }); 92 | }); 93 | 94 | describe('Replace With', () => { 95 | it('should replace the element with a new one', () => { 96 | let m3 = _('#manipulation-3'); 97 | let parent = m3[0].parentNode; 98 | m3.replaceWith('

    '); 99 | 100 | expect(parent.querySelector('#manipulation-3')).to.be.equal(null); 101 | expect(parent.querySelectorAll('#replaced').length).to.be.equal(1); 102 | }); 103 | }); 104 | }); -------------------------------------------------------------------------------- /test/traversing-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * traversing-test 3 | **/ 4 | 5 | describe('Traversing', () => { 6 | // traversing setup 7 | (() => { 8 | let div = createElement('div', {id: 'traversing'}); 9 | let ul = createElement('ul', {id: 'traversing-list'}); 10 | 11 | ['apple', 'banana', 'strawberry'].forEach(fruit => { 12 | let f = createElement('li', {innerText: fruit, classList: [fruit]}); 13 | 14 | appendTo(ul, f); 15 | }); 16 | 17 | appendTo(div, ul); 18 | appendToBody(div); 19 | })(); 20 | 21 | let traversingList = _('#traversing-list li'); 22 | 23 | describe('Closest', () => { 24 | it('should find the closest parent or returns null', () => { 25 | let immediateParent = traversingList.closest(); 26 | let secondParent = traversingList.closest('div'); 27 | let nonMatchedParent = traversingList.closest('#not-matched'); 28 | 29 | expect(immediateParent.el[0].matches('#traversing-list')).to.be.equal(true); 30 | expect(secondParent.el[0].matches('#traversing')).to.be.equal(true); 31 | expect(nonMatchedParent.length).to.be.equal(0); 32 | }); 33 | }); 34 | 35 | describe('Parent', () => { 36 | it('should get the parent or checks if parent matches the selector', () => { 37 | let parent = traversingList.parent(); 38 | let parentSelector = traversingList.parent('.parent'); 39 | let parentSelectorMatched = traversingList.parent('#traversing-list'); 40 | 41 | expect(parent.el[0].matches('#traversing-list')).to.be.equal(true); 42 | expect(parentSelector.length).to.be.equal(0); 43 | expect(parentSelectorMatched[0].matches('#traversing-list')).to.be.equal(true); 44 | }); 45 | }); 46 | 47 | describe('Children', () => { 48 | it('should get the children of selected elements or filter the children', () => { 49 | let list = _('#traversing-list'); 50 | let children = list.children(); 51 | let childrenSelector = list.children('div'); 52 | let childrenSelectorMatched = list.children('.apple'); 53 | 54 | expect(children.length).to.be.equal(3); 55 | expect(childrenSelector.length).to.be.equal(0); 56 | expect(childrenSelectorMatched.length).to.be.equal(1); 57 | }); 58 | }); 59 | 60 | describe('Siblings', () => { 61 | it('should get the siblings of selected elements or filter the siblings', () => { 62 | let banana = _('#traversing-list .banana'); 63 | let siblings = banana.siblings(); 64 | let siblingsSelector = banana.siblings('.banana'); 65 | let siblingsSelectorMatched = banana.siblings('.apple'); 66 | 67 | expect(siblings.length).to.be.equal(2); 68 | expect(siblingsSelector.length).to.be.equal(0); 69 | expect(siblingsSelectorMatched.length).to.be.equal(1); 70 | }) 71 | }); 72 | 73 | describe('Next', () => { 74 | it('should find the next element sibling', () => { 75 | let next = traversingList.filter('.apple').next(); 76 | let nextSelector = next.next('.strawberr'); 77 | 78 | expect(next.el[0].matches('.banana')).to.be.equal(true); 79 | expect(nextSelector.length).to.be.equal(0); 80 | }); 81 | }); 82 | 83 | describe('Prev', () => { 84 | it('should find the prev element sibling', () => { 85 | let prev = traversingList.filter('.banana').prev(); 86 | let prevSelector = traversingList.filter('.strawberry').prev('.apple'); 87 | 88 | expect(prev.el[0].matches('.apple')).to.be.equal(true); 89 | expect(prevSelector.length).to.be.equal(0); 90 | }); 91 | }); 92 | 93 | describe('First', () => { 94 | it('should find the first element', () => { 95 | let first = traversingList.first(); 96 | 97 | expect(first.el[0].matches('.apple')).to.be.equal(true); 98 | }); 99 | }); 100 | 101 | describe('Last', () => { 102 | it('should find the last element', () => { 103 | let last = traversingList.last(); 104 | 105 | expect(last.el[0].matches('.strawberry')).to.be.equal(true); 106 | }); 107 | }); 108 | }); -------------------------------------------------------------------------------- /test/dimensions-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dimensions-test 3 | **/ 4 | 5 | describe('Dimensions', () => { 6 | // dimensions setup 7 | (() => { 8 | let div = createElement('div', {id: 'dimensions'}); 9 | let widthTest = createElement('div', {id: 'dim-width', innerText: 'Hello World'}); 10 | let widthTest2 = createElement('div', { 11 | id: 'dim-width2', 12 | innerText: 'Hello World', 13 | style: {width: '50%', padding: '10px', border: '5px solid'} 14 | }); 15 | let widthTest3 = createElement('ul', {id: 'dim-width3'}); 16 | let widthTest3Li = createElement('li', {style: {width: '500px', padding: '5px', margin: '10px'}}); 17 | let widthTest3Li2 = createElement('li'); 18 | 19 | appendTo(div, [widthTest, widthTest2, appendTo(widthTest3, [widthTest3Li, widthTest3Li2])]); 20 | 21 | appendToBody(div); 22 | })(); 23 | 24 | describe('Width', () => { 25 | it('should return the first elements content width without padding', () => { 26 | let width = _('#dim-width3 li').width(); 27 | let width_false = _('#dim-width3 lil').width(); 28 | 29 | expect(width).to.be.equal(500); 30 | expect(width_false).to.be.equal(false); 31 | }); 32 | 33 | it('should set the content width', () => { 34 | let width = _('#dim-width3 li:first-child').width('40%'); 35 | 36 | expect(width.width()).to.be.equal(width[0].clientWidth - 10); 37 | }); 38 | }); 39 | 40 | describe('Inner Width', () => { 41 | it('should return the first elements clientWidth', () => { 42 | let width = _('#dim-width3 li').innerWidth(); 43 | let width_false = _('#dim-width3 lil').innerWidth(); 44 | 45 | expect(width).to.be.equal(_('#dim-width3 li:first-child')[0].clientWidth); 46 | expect(width_false).to.be.equal(false); 47 | }); 48 | }); 49 | 50 | describe('Outer Width', () => { 51 | it('should return the first elements offsetWidth', () => { 52 | let width = _('#dim-width3 li').outerWidth(); 53 | let width_false = _('#dim-width3 lil').outerWidth(); 54 | 55 | expect(width).to.be.equal(_('#dim-width3 li:first-child')[0].offsetWidth); 56 | expect(width_false).to.be.equal(false); 57 | }); 58 | 59 | it('should return the first elements offsetWidth including margins', () => { 60 | let width = _('#dim-width3 li').outerWidth(true); 61 | 62 | expect(width).to.be.equal(_('#dim-width3 li:first-child')[0].offsetWidth + 20); 63 | }); 64 | }); 65 | 66 | describe('Height', () => { 67 | it('should return the first elements content height without padding', () => { 68 | let height = _('#dim-width3 li').height(); 69 | let height_false = _('#dim-width3 lil').height(); 70 | 71 | expect(height).to.be.equal(_('#dim-width3 li')[0].clientHeight - 10); 72 | expect(height_false).to.be.equal(false); 73 | }); 74 | 75 | it('should set the content height', () => { 76 | let height = _('#dim-width3 li:first-child').height('100px'); 77 | 78 | expect(100).to.be.equal(height[0].clientHeight - 10); 79 | }); 80 | }); 81 | 82 | describe('Inner Height', () => { 83 | it('should return the first elements clientHeight', () => { 84 | let height = _('#dim-width3 li').innerHeight(); 85 | let height_false = _('#dim-width3 lil').innerHeight(); 86 | 87 | expect(height).to.be.equal(_('#dim-width3 li:first-child')[0].clientHeight); 88 | expect(height_false).to.be.equal(false); 89 | }); 90 | }); 91 | 92 | describe('Outer Height', () => { 93 | it('should return the first elements offsetHeight', () => { 94 | let height = _('#dim-width3 li').outerHeight(); 95 | let height_false = _('#dim-width3 lil').outerHeight(); 96 | 97 | expect(height).to.be.equal(_('#dim-width3 li:first-child')[0].offsetHeight); 98 | expect(height_false).to.be.equal(false); 99 | }); 100 | 101 | it('should return the first elements offsetHeight including margins', () => { 102 | let height = _('#dim-width3 li').outerHeight(true); 103 | 104 | expect(height).to.be.equal(_('#dim-width3 li:first-child')[0].offsetHeight + 20); 105 | }); 106 | }); 107 | }); -------------------------------------------------------------------------------- /test/wrap-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * wrap-test 3 | **/ 4 | 5 | describe('Element Wrapping', () => { 6 | // wrap setup 7 | (() => { 8 | let div = createElement('div', {id: 'wrap'}); 9 | let single = createElement('div', {id: 'wrap-single'}); 10 | let singleWithAttr = createElement('div', {id: 'wrap-single-attr'}); 11 | let singleInner = createElement('div', {id: 'wrap-inner', innerHTML: '
    '}); 12 | let apple = createElement('li', {innerText: 'Apple'}); 13 | let banana = createElement('li', {innerText: 'Banana'}); 14 | let strawberry = createElement('li', {innerText: 'Strawberry'}); 15 | 16 | let singleWrapAll = createElement('div', {id: 'wrap-all', innerHTML: 'Hello'}); 17 | 18 | appendTo(div, [single, singleWithAttr, singleInner, apple, banana, strawberry, singleWrapAll]); 19 | appendToBody(div); 20 | })(); 21 | 22 | describe('Single Element Wrap', () => { 23 | let single = _('#wrap-single'); 24 | let singleAttr = _('#wrap-single-attr'); 25 | let singleInner = _('.wrap-single-inner'); 26 | 27 | it('should wrap element with "div"', () => { 28 | let firstParent = single['0'].parentNode; 29 | 30 | single.wrap('div'); 31 | 32 | let newParent = single['0'].parentNode; 33 | 34 | expect(firstParent.matches('#wrap')).to.be.equal(true); 35 | expect(newParent.matches('#wrap')).to.be.equal(false); 36 | }); 37 | 38 | it('should wrap and replace child if the element is the only child', () => { 39 | let parent = singleInner['0'].parentNode; 40 | let length = parent.childNodes.length; 41 | let child = parent.firstChild.classList.contains('wrap-single-inner'); 42 | 43 | singleInner.wrap('
    '); 44 | 45 | let newLength = parent.childNodes.length; 46 | let newChild = parent.firstChild.classList.contains('wrap-single-inner'); 47 | 48 | expect(length).to.be.equal(1); 49 | expect(child).to.be.equal(true); 50 | expect(newLength).to.be.equal(1); 51 | expect(newChild).to.be.equal(false); 52 | }); 53 | 54 | it('should wrap element with "div" has a class value "new-wrap" and id value "wrap-id"', () => { 55 | let firstParent = singleAttr['0'].parentNode; 56 | 57 | singleAttr.wrap('
    '); 58 | 59 | let newParent = singleAttr['0'].parentNode; 60 | 61 | expect(firstParent.matches('#wrap')).to.be.equal(true); 62 | expect(newParent.matches('#wrap-id')).to.be.equal(true); 63 | expect(newParent.classList.contains('new-wrap')).to.be.equal(true); 64 | }); 65 | }); 66 | 67 | describe('Multiple Elements Wrap', () => { 68 | it('should wrap siblings with same parent', () => { 69 | let list = _("#wrap > li"); 70 | let parent1 = list['0'].parentNode; 71 | let parent2 = list['1'].parentNode; 72 | 73 | list.wrapAll('ul'); 74 | 75 | let oldList = _("#wrap > li"); 76 | let newParent1 = list['0'].parentNode; 77 | let newParent2 = list['1'].parentNode; 78 | 79 | expect(list).not.to.be.equal(oldList); 80 | expect(parent1.nodeName.toUpperCase()).to.be.equal('DIV'); 81 | expect(parent2.nodeName.toUpperCase()).to.be.equal('DIV'); 82 | expect(parent1).to.be.equal(parent2); 83 | expect(newParent1.nodeName.toUpperCase()).to.be.equal('UL'); 84 | expect(newParent2.nodeName.toUpperCase()).to.be.equal('UL'); 85 | expect(newParent1).to.be.equal(newParent2); 86 | }); 87 | 88 | it('should wrap the element even if it is the only child', () => { 89 | let list = _("#wrap-all > span"); 90 | let parent1 = list['0'].parentNode; 91 | 92 | list.wrapAll('p'); 93 | 94 | let oldList = _("#wrap-all > span"); 95 | let newParent1 = list['0'].parentNode; 96 | 97 | expect(list).not.to.be.equal(oldList); 98 | expect(parent1.nodeName.toUpperCase()).to.be.equal('DIV'); 99 | expect(newParent1.nodeName.toUpperCase()).to.be.equal('P'); 100 | }); 101 | }); 102 | 103 | describe('Single Element UnWrap', () => { 104 | let single = _('#wrap-single'); 105 | let singleAttr = _('#wrap-single-attr'); 106 | 107 | it('should unwraps the element', () => { 108 | let parent = single['0'].parentNode; 109 | 110 | single.unwrap(); 111 | 112 | let newParent = single['0'].parentNode; 113 | 114 | expect(parent).not.to.be.equal(newParent); 115 | }); 116 | 117 | it('will not unwrap the element if parent selector is not matched', () => { 118 | let parent = singleAttr['0'].parentNode; 119 | 120 | singleAttr.unwrap('.not-matching-selector'); 121 | 122 | let newParent = singleAttr['0'].parentNode; 123 | 124 | expect(parent).to.be.equal(newParent); 125 | }); 126 | 127 | it('should remove parent, if after unwrapping it become an empty element', () => { 128 | let parent = singleAttr.parent(); 129 | 130 | singleAttr.unwrap(); 131 | 132 | expect(document.body.contains(parent[0])).to.be.equal(false); 133 | }); 134 | }); 135 | }); -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | /** 2 | * event 3 | **/ 4 | 5 | /* global hashCode */ 6 | /* global maybeMultiple */ 7 | /* global isObject */ 8 | 9 | /*const events = { 10 | animation: ['animationend', 'animationiteration', 'animationstart'], 11 | drag: ['drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop'], 12 | frame: ['abort', 'beforeunload', 'error', 'hashchange', 'load', 'pageshow', 'pagehide', 'resize', 'scroll', 'unload'], 13 | form: ['blur', 'change', 'focus', 'focusin', 'focusout', 'input', 'invalid', 'reset', 'search', 'select', 'submit'], 14 | keyboard: ['keydown', 'keypress', 'keyup'], 15 | media: ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'progress', 'ratechange', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting'], 16 | misc: ['message', 'mousewheel', 'online', 'offline', 'popstate', 'show', 'storage', 'toggle', 'wheel'], 17 | mouse: ['click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup'], 18 | print: ['afterprint', 'beforeprint'], 19 | server: ['error', 'message', 'open'], 20 | transition: ['transitionend'], 21 | touch: ['touchcancel', 'touchend', 'touchmove', 'touchstart'] 22 | }; 23 | 24 | const allEvents = Object.keys(events).reduce((acc, cur) => acc.concat(events[cur]), []);*/ 25 | 26 | const removeEventFromItem = (item, event, fn) => { 27 | item.removeEventListener(event, item.uxrAttachedEvents[event][fn]); 28 | delete item.uxrAttachedEvents[event][fn]; 29 | 30 | return item; 31 | }; 32 | 33 | _.extend.off = function (eventName, eventHandlerOrSelector, eventHandler) { 34 | let stack = this; 35 | let handler = eventHandlerOrSelector; 36 | let events = maybeMultiple(eventName); 37 | 38 | if (typeof eventHandlerOrSelector === 'string' 39 | || typeof eventHandler !== 'undefined') { 40 | handler = eventHandler; 41 | stack = this.find(eventHandlerOrSelector); 42 | } 43 | 44 | stack.el.forEach(item => { 45 | item.uxrAttachedEvents = item.uxrAttachedEvents || {}; 46 | 47 | events.forEach(event => { 48 | // make sure not to give an error, if user tried to remove unattached event 49 | if (typeof item.uxrAttachedEvents[event] !== 'undefined') { 50 | if (typeof handler === 'undefined') { 51 | Object.keys(item.uxrAttachedEvents[event]).forEach(fn => { 52 | removeEventFromItem(item, event, fn); 53 | }); 54 | } 55 | 56 | else { 57 | let hash = hashCode((handler).toString()); 58 | removeEventFromItem(item, event, hash); 59 | } 60 | } 61 | }); 62 | }); 63 | 64 | return this; 65 | }; 66 | 67 | _.extend.on = function (eventName, eventHandlerOrSelector, eventHandler) { 68 | let stack = this; 69 | let handler = eventHandlerOrSelector; 70 | let events = maybeMultiple(eventName); 71 | 72 | if (typeof eventHandler !== 'undefined') { 73 | handler = eventHandler; 74 | stack = this.find(eventHandlerOrSelector); 75 | } 76 | 77 | let hash = hashCode((handler).toString()); 78 | 79 | stack.el.forEach(item => { 80 | item.uxrAttachedEvents = item.uxrAttachedEvents || {}; 81 | 82 | events.forEach(event => { 83 | item.uxrAttachedEvents[event] = item.uxrAttachedEvents[event] || {}; 84 | item.uxrAttachedEvents[event][hash] = handler; 85 | 86 | item.addEventListener(event, item.uxrAttachedEvents[event][hash]); 87 | }); 88 | }); 89 | 90 | return this; 91 | }; 92 | 93 | _.extend.once = function (eventName, eventHandlerOrSelector, eventHandler) { 94 | let stack = this; 95 | let handler = eventHandlerOrSelector; 96 | let events = maybeMultiple(eventName); 97 | 98 | if (typeof eventHandler !== 'undefined') { 99 | handler = eventHandler; 100 | stack = this.find(eventHandlerOrSelector); 101 | } 102 | 103 | stack.el.forEach(item => { 104 | events.forEach(event => { 105 | /* istanbul ignore next */ 106 | let oneHandler = e => { 107 | e.preventDefault(); 108 | _(item).off(event, handler); 109 | _(item).off(event, oneHandler); 110 | }; 111 | 112 | _(item).on(event, handler); 113 | _(item).on(event, oneHandler); 114 | }); 115 | }); 116 | 117 | return this; 118 | }; 119 | 120 | _.extend.trigger = function (eventName, eventParamsOrSelector, eventParams) { 121 | let stack = this; 122 | let event; 123 | 124 | if (eventParamsOrSelector !== 'undefined' && typeof eventParamsOrSelector === 'string') { 125 | stack = this.find(eventParamsOrSelector); 126 | } 127 | 128 | if (!_eventHasParams(eventParamsOrSelector, eventParams)) { 129 | event = getNativeEvent(eventName); 130 | } 131 | 132 | else { 133 | event = getCustomEvent(eventName, eventParamsOrSelector, eventParams); 134 | } 135 | 136 | stack.el.forEach(item => item.dispatchEvent(event)); 137 | 138 | return this; 139 | }; 140 | 141 | const _eventHasParams = (eventParamsOrSelector, eventParams) => { 142 | return (typeof eventParamsOrSelector !== 'undefined' && isObject(eventParamsOrSelector)) || typeof eventParams !== 'undefined'; 143 | }; 144 | 145 | const getNativeEvent = (eventName) => { 146 | return new Event(eventName); 147 | }; 148 | 149 | const getCustomEvent = (eventName, eventParamsOrSelector, eventParams) => { 150 | let params = eventParams; 151 | 152 | if (typeof eventParamsOrSelector !== 'undefined' 153 | && isObject(eventParamsOrSelector)) { 154 | params = eventParamsOrSelector; 155 | } 156 | 157 | return new CustomEvent(eventName, {detail: params}); 158 | }; -------------------------------------------------------------------------------- /test/event-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * event-test 3 | **/ 4 | 5 | describe('Event Manager', () => { 6 | // event test 7 | (() => { 8 | let div = createElement('div', {id: 'event'}); 9 | let input = createElement('input', {id: 'event-input', type: 'text'}); 10 | let p = createElement('p', { 11 | id: 'event-paragraph', 12 | innerHTML: 'test' + 13 | 'test' 14 | }); 15 | 16 | appendTo(div, [input, p]); 17 | appendToBody(div); 18 | })(); 19 | 20 | let inputElem = _('#event-input'); 21 | let paragraphElem = _('#event-paragraph'); 22 | 23 | let value = ''; 24 | let triggered = []; 25 | let handler = e => { 26 | e.preventDefault(); 27 | triggered.push(e.currentTarget.dataset.trigger); 28 | }; 29 | 30 | describe('Add Event', () => { 31 | it('should add an event', () => { 32 | inputElem.on('change', e => value = e.currentTarget.value); 33 | 34 | inputElem.attr('value', controls.value); 35 | inputElem.trigger('change'); 36 | 37 | expect(value).to.be.equal(controls.value); 38 | }); 39 | 40 | it('should add multiple events at once', () => { 41 | inputElem.on('focus blur', e => triggered.push(e.type)); 42 | 43 | inputElem.trigger('focus'); 44 | inputElem.trigger('blur'); 45 | 46 | expect(triggered.includes('focus')).to.be.true; 47 | expect(triggered.includes('blur')).to.be.true; 48 | }); 49 | 50 | it('should add an event to child element', () => { 51 | paragraphElem.on('click', '.event-link', handler); 52 | 53 | paragraphElem.find('.event-link').trigger('click'); 54 | 55 | expect(triggered.includes('child-element')).to.be.true; 56 | }); 57 | 58 | it('should stores the attached events in `uxrAttachedEvents` on item', () => { 59 | expect(inputElem[0].uxrAttachedEvents.change).to.not.be.undefined; 60 | }); 61 | }); 62 | 63 | describe('Remove Event', () => { 64 | it('should remove an event', () => { 65 | value = ''; 66 | inputElem.off('change'); 67 | inputElem.attr('value', controls.value); 68 | inputElem.trigger('change'); 69 | 70 | inputElem.off('this-should-be-skipped'); 71 | 72 | expect(inputElem[0].uxrAttachedEvents['change']).to.be.empty; 73 | expect(inputElem.attr('value')).to.not.be.equal(value); 74 | }); 75 | 76 | it('should remove event with defined handler', () => { 77 | let eventResult = {}; 78 | let handler = e => { 79 | eventResult['withHandler'] = e.type 80 | }; 81 | 82 | inputElem.on('change', handler); 83 | inputElem.on('change', e => { 84 | eventResult['withAnonFunc'] = e.type 85 | }); 86 | 87 | inputElem.off('change', handler); 88 | inputElem.trigger('change'); 89 | 90 | expect(eventResult['withHandler']).to.be.undefined; 91 | expect(eventResult['withAnonFunc']).to.be.equal('change'); 92 | }); 93 | 94 | it('should remove multiple events at once', () => { 95 | inputElem.off('focus blur'); 96 | expect(inputElem[0].uxrAttachedEvents['focus']).to.be.empty; 97 | expect(inputElem[0].uxrAttachedEvents['blur']).to.be.empty; 98 | }); 99 | 100 | it('should remove the attached event with a handler from child element', () => { 101 | // if you send handler, it only removes the handler but event type stays in element object 102 | paragraphElem.off('click', '.event-link', handler); 103 | 104 | expect(paragraphElem.find('.event-link')[0].uxrAttachedEvents['click']).to.be.empty; 105 | }); 106 | 107 | it('should remove the attached event with anonymous func from child element', () => { 108 | paragraphElem.on('click', '.event-link2', e => { 109 | e.preventDefault(); 110 | triggered.push(e.currentTarget.dataset.trigger); 111 | }); 112 | 113 | _('.event-link2').trigger('click'); 114 | 115 | paragraphElem.off('click', '.event-link2'); 116 | 117 | expect(triggered.includes('child-element2')).to.be.true; 118 | expect(paragraphElem.find('.event-link2')[0].uxrAttachedEvents['click']).to.be.empty; 119 | }); 120 | }); 121 | 122 | describe('Single Run Event', () => { 123 | it('should runs the event one time and remove itself', () => { 124 | value = ''; 125 | inputElem.once('focus', e => value = e.currentTarget.value); 126 | inputElem.trigger('focus'); 127 | 128 | inputElem.attr('value', controls.newValue); 129 | inputElem.trigger('focus'); 130 | 131 | expect(value).to.not.be.equal(controls.newValue); 132 | }); 133 | 134 | it('should runs the event one time and remove itself from a child element', () => { 135 | let counter = 0; 136 | 137 | paragraphElem.once('click', '.event-link2', e => { 138 | e.preventDefault(); 139 | counter++; 140 | }); 141 | 142 | // first trigger => counter = 1; 143 | paragraphElem.find('.event-link2').trigger('click'); 144 | 145 | // second trigger => counter = 1 no changed 146 | paragraphElem.find('.event-link2').trigger('click'); 147 | 148 | expect(counter).to.not.be.equal(1); 149 | }); 150 | }); 151 | 152 | describe('Event Trigger', () => { 153 | it('should trigger an event', () => { 154 | let counter = 0; 155 | paragraphElem.on('click', () => counter++); 156 | paragraphElem.trigger('click'); 157 | paragraphElem.off('click'); 158 | expect(counter).to.be.equal(1); 159 | }); 160 | 161 | it('should trigger an event on children elements', () => { 162 | let counter = 0; 163 | paragraphElem.on('click', '.event-link', () => counter++); 164 | paragraphElem.trigger('click'); // no effect 165 | paragraphElem.trigger('click', '.event-link'); 166 | paragraphElem.off('click', '.event-link'); 167 | paragraphElem.off('click'); 168 | expect(counter).to.be.equal(1); 169 | }); 170 | 171 | it('should trigger an event', () => { 172 | let counter = 0; 173 | let event = {}; 174 | 175 | paragraphElem.on('customClick', (e) => { 176 | event = e; 177 | counter++ 178 | }); 179 | paragraphElem.trigger('customClick', {custom: 'params'}); 180 | paragraphElem.off('customClick'); 181 | expect(counter).to.be.equal(1); 182 | expect(event.detail.custom).to.be.equal('params'); 183 | }); 184 | 185 | it('should trigger an event with params in children elements', () => { 186 | let counter = 0; 187 | let event = {}; 188 | 189 | paragraphElem.on('customClick', '.event-link', (e) => { 190 | event = e; 191 | counter++ 192 | }); 193 | paragraphElem.trigger('customClick', '.event-link', {custom: 'params'}); 194 | paragraphElem.off('customClick', '.event-link'); 195 | expect(counter).to.be.equal(1); 196 | expect(event.detail.custom).to.be.equal('params'); 197 | }); 198 | }); 199 | }); -------------------------------------------------------------------------------- /dist/uxr.min.js: -------------------------------------------------------------------------------- 1 | function t(e){var g=0;return function(){return g 2 | 3 | # UXR 4 | [![npm][npm]][npm-url] 5 | [![tests][tests]][tests-url] 6 | [![coverage][cover]][cover-url] 7 | [![devDependencies Status][dm]][dm-url] 8 | [![Maintainability][cc]][cc-url] 9 | [![Open Source Love][os]][os-url] 10 | [![PRs Welcome][pr]][pr-url] 11 | 12 | 13 | A minimal in mind library for DOM related routines and element selections. UXR wraps some most used methods like CSS Classes, Event Management, Data Management, Attribute selections/updates. Supports chaining and plugins. 14 | 15 | UXR has the philosophy of fewer code and low file size. Because of this, widely supported ES6 codes are not transpiled to ES5 versions and not trying to cover all JavaScript methods which are normally written without much effort. UXR provides easy to wrappers for normally complex once. 16 | 17 | ## Browser Support 18 | | Browser | | | | | | 19 | | ------- | ----------------- | ------------------- | --------------- | ----------------- | ------------- | 20 | | Version | 49+ | 36+ | 37+ | 10+ | 12+ | 21 | 22 | ## How To Use 23 | You can install UXR via Node package managers 24 | ``` js 25 | $ npm install uxr 26 | // or 27 | $ yarn add uxr 28 | ``` 29 | 30 | Or you can directly include you `dist` files to your project after downloading the desired version. 31 | 32 | After adding the `dist/uxr.min.js` to your page, you can select an element set from DOM and start to manipulate/modify the selection. 33 | 34 | ### Loading UXR methods 35 | You can define UXR methods to run when page loads and content ready. Or run when needed. 36 | 37 | ``` js 38 | uxr.ready(function(){ 39 | // inner functions automatically runs when document is ready 40 | }); 41 | 42 | uxr.load(function(){ 43 | // inner functions automatically runs when document is fully loaded 44 | }); 45 | ``` 46 | 47 | ### Element Selection 48 | Every `uxr` methods starts with element selections. Basically selection uses `querySelectorAll` getting element from DOM or Arrays. 49 | 50 | ``` js 51 | // selecting an element with querySelectorAll supported selector strings 52 | uxr(selector); 53 | 54 | // getting an array as selector 55 | uxr([0, 1, 2, 3]) 56 | ``` 57 | 58 | ### Chaining 59 | `uxr` methods supports chaining. So you can call `uxr` methods one after another. 60 | 61 | ``` js 62 | uxr(selector) 63 | .addClass('hello') 64 | .find('a') 65 | .on('click', e => { 66 | e.preventDefault(); 67 | console.log('Hello World!') 68 | }) 69 | .end() 70 | .attr('id', 'my-id'); 71 | ``` 72 | 73 | ### Attribute Manipulation 74 | With `uxr(selector).attr()` methods you can get an attribute's value or set a value in HTML tags 75 | 76 | ``` js 77 | let el = uxr(selector); 78 | 79 | // get the ID 80 | let id = el.attr('id'); 81 | // getAttr method is an alias to attr(name) 82 | let id = el.getAttr('id'); 83 | 84 | // set the ID 85 | el.attr('id', 'new-id'); 86 | // setAttr method is an alias to attr(name, value); 87 | el.setAttr('id', 'new-id'); 88 | 89 | // get a data-attr value 90 | // the following both samples gets the same attribute 91 | el.attr('data-attr'); 92 | el.attr('dataAttr'); 93 | 94 | // Remove an attribute from HTML 95 | el.removeAttr('id'); 96 | el.removeAttribute('id'); 97 | ``` 98 | 99 | There are some, easy to use - easy to remember attribute methods 100 | 101 | 102 | * `uxr(selector).src(value)` : if you send the `value` it sets the `src` of the element. Otherwise returns the `src` value. 103 | * `uxr(selector).href(value)` : if you send the `value` it sets the `href` value of the anchor with the new one. Otherwise returns the `href` value. 104 | 105 | ### Props 106 | With `uxr(selector).prop()` methods you can get an DOM node element's properties. This is different than attr methods where it get native elements properties rather than HTML attributes. 107 | 108 | ``` js 109 | let el = uxr(selector); 110 | 111 | // get a property 112 | let innerText = el.prop('innerText'); 113 | 114 | // set a property 115 | el.prop('innerText', 'New Text'); 116 | ``` 117 | 118 | There are some, easy to use - easy to remember property methods 119 | * `uxr(selector).text(value)` : if you send the `value` it sets the `innerText` value with the new one. Otherwise returns the `innerText` value. 120 | * `uxr(selector).html(value)` : if you send the `value` it sets the `innerHTML` value with the new one. Otherwise returns the `innerHTML` value. 121 | * `uxr(selector).value(value)` : if you send the `value` it sets the value of form elements with the new one. Otherwise returns the value of the form element. 122 | 123 | ### Class Manipulation 124 | With `uxr` it is easier to add/remove or check classes. All for class manipulation methods supports multiple class names separated with space and setting class starting with dot (`.`) 125 | 126 | ``` js 127 | let el = uxr(selector); 128 | 129 | // add a new css class 130 | el.addClass('new-class'); 131 | el.addClass('.new-class'); 132 | 133 | // add multiple classes at once 134 | el.addClass('.a set .of classes'); 135 | el.addClass(['array', 'of', 'classes']); 136 | 137 | // remove a css class 138 | el.removeClass('old-class'); 139 | el.removeClass('.old-class'); 140 | 141 | // toggles a class 142 | el.toggleClass('class-to-toggle'); 143 | el.toggleClass('.class-to-toggle'); 144 | 145 | // checks if has the class or not 146 | el.hasClass('class-to-check'); 147 | el.hasClass('.class-to-check'); 148 | ``` 149 | 150 | ### Data Attributes 151 | Data method gets or sets a data attribute. If `dataset` supported, gets or sets values via `dataset` otherwise, uses the `getAttribute` method to get value. Both supports _camelCase_ and _dashed_ attribute names. 152 | 153 | ``` js 154 | let el = uxr(selector); 155 | 156 | // get data value 157 | el.data('uxr-demo'); 158 | el.data('uxrDemo'); 159 | 160 | // set data value 161 | el.data('uxr-demo', true); 162 | el.data('uxrDemo', true); 163 | ``` 164 | 165 | ### Events 166 | DOM events can easily attached to elements. Named events and anonymous events supported while attaching the event. Also _triggering_ an event is possible. 167 | 168 | #### Add Events 169 | 170 | ``` js 171 | let myFunc = (e) => { console.log(e.currentTarget);} 172 | 173 | // attach an event 174 | uxr(selector).on('click', myFunc); 175 | 176 | // attach an event to child 177 | uxr(selector).on('click', '.clickable', myFunc); 178 | 179 | // attach multiple event 180 | uxr(selector).on('input focus', e => { console.log(e.currentTarget.value); } 181 | ``` 182 | 183 | #### Remove Events 184 | 185 | ``` js 186 | // remove all click events 187 | uxr(selector).off('click'); 188 | 189 | // remove only click event attached with myFunc 190 | uxr(selector).off('click', myFunc); 191 | 192 | // remove events attached with an anonymous function 193 | uxr(selector).off('input focus', e => { console.log(e.currentTarget.value); } 194 | ``` 195 | 196 | #### Single Run Events 197 | Single Run events are only run once then remove itself from the element. 198 | 199 | ``` js 200 | // run once 201 | uxr(selector).once('touchend', e => { console.log('touch ended'); }) 202 | ``` 203 | 204 | #### Trigger Events 205 | Native and custom events can be triggered by using trigger method. Similar to event bindings, event trigger can be triggered on children elements. Despite event binding, where you can bind multiple events at once, you can trigger one event at a time. 206 | 207 | ``` js 208 | // trigger a click event 209 | uxr(selector).trigger('click'); 210 | 211 | // trigger a custom event 212 | uxr(selector).trigger('custom'); 213 | 214 | // trigger a focus event in children 215 | uxr(selector).trigger('focus', 'input[type=text]'); 216 | 217 | // trigger event with params 218 | uxr(selector).trigger('click', {custom: 'params', another: {custom: 'paramater'}}); 219 | 220 | // trigger event with params in children 221 | uxr(selector).trigger('blur', 'textarea', {custom: 'params', another: {custom: 'paramater'}}); 222 | ``` 223 | 224 | ### Wrapper Methods 225 | With wrapper methods, you can wrap element or elements to a new parent or unwrap them. 226 | 227 | ``` js 228 | let single = uxr('.single-element'); 229 | let list = uxr('.list-element'); 230 | 231 | // wrap element 232 | single.wrap('div'); 233 | 234 | // wrap all elements in a single parent 235 | list.wrapAll('
      '); 236 | 237 | // Unwrap the parent 238 | single.unwrap(); 239 | ``` 240 | 241 | Unwrap removes the immediate parent. If a selector also defined for the unwrap as `el.unwrap(selector)`, it check if the immediate parent matches the selector. 242 | 243 | For wrapper definitions, you can define wrapper string without brackets, with brackets, with attributes etc. 244 | All of the following strings are valid wrapper definitions 245 | 246 | * `div` _only name of the tag_ 247 | * `
      ` _tag name with brackets_ 248 | * `
      ` 249 | * `
      ` 250 | * `
      ` _tag name with attributes_ 251 | * `
      ` 252 | 253 | ### Element Insertions 254 | By using `before`, `after`, `prepend` and `append` you can control where to insert newly created elements. Also with `replaceWith` you can swap the element with a new one. 255 | 256 | ``` js 257 | let el = uxr('.container'); 258 | 259 | // adds an element before selection 260 | el.before('

      This will be before of "el"

      '); 261 | el.before(uxr('#new')); 262 | 263 | // adds an element after selection 264 | el.after('

      This will be after of "el"

      '); 265 | el.after(uxr('#new')); 266 | 267 | // appends an element add the end of selection's content 268 | el.append('

      This will be at the end of "el"

      '); 269 | el.append(uxr('#new')); 270 | 271 | // appends an element add the beginning of selection's content 272 | el.prepend('

      This will be at the beginning of "el"

      '); 273 | el.prepend(uxr('#new')); 274 | 275 | // replaces the element with new one 276 | el.replaceWith('
      Previous element replaced
      '); 277 | ``` 278 | 279 | ### Filtering and Finding 280 | Filtering methods help to find or filter elements in a UXR object. 281 | 282 | ``` js 283 | // create a subset of elements in a UXR object 284 | uxr(selector).filter(anotherSelector); 285 | 286 | // create a subset of elements that a not matched the selector in a UXR object 287 | uxr(selector).not(anotherSelector); 288 | 289 | // find / select children elements in a UXR object 290 | // has method is an alias to find 291 | uxr(selector).find(childrenSelector); 292 | uxr(selector).has(childrenSelecotr); 293 | ``` 294 | 295 | ### Traversing 296 | With traversal methods, you can find adjacent or parent elements accordingly. Almost all traversal methods returns a `uxr` object. You can return the previous `uxr` by chaining `end()` 297 | 298 | ``` js 299 | 300 | let el = uxr('li'); 301 | 302 | // get the immediate parent 303 | el.closest(); 304 | 305 | // get the grandparent 306 | el.closest().closest(); 307 | 308 | // filter the parents and get the first matched 309 | el.closest(selector); 310 | 311 | // get the next sibling 312 | el.next(); 313 | 314 | // get the next sibling if matched 315 | el.next(selector); 316 | 317 | // get the previous sibling 318 | el.prev(); 319 | 320 | // get the previous sibling if matched 321 | el.prev(selector); 322 | 323 | // get the first element in uxr object - selection 324 | el.first(); 325 | 326 | // get the last element in uxr object - selection 327 | el.last(); 328 | 329 | // get the immediate parent 330 | el.parent(); 331 | 332 | // get the immediate parent if matched to selector 333 | el.parent(selector); 334 | 335 | // get the all children element 336 | el.children(); 337 | 338 | // get the all matched children 339 | el.children(selector); 340 | 341 | // get the all siblings 342 | el.siblings(); 343 | 344 | // get the all matched siblings 345 | el.siblings(selector); 346 | ``` 347 | 348 | ### CSS 349 | `css` method helps to set or get style attributes of the elements. 350 | 351 | ``` js 352 | let el = uxr(selector); 353 | 354 | // get a style property 355 | el.css('display'); // returns the display property value 356 | 357 | // get a list of style properties 358 | // returns an object with listed values. 359 | // note that, you can ask for properties both kebap-case and camelCase 360 | el.css(['display', 'margin', 'padding-top', 'borderLeft']); 361 | // returns {display: value, margin: value, paddingTop: value, borderLeft: value} 362 | 363 | // sets or updates a single property 364 | el.css('padding', '10px'); 365 | el.css('background-color', '#ccc'); 366 | el.css('backgroundSize', '100% auto'); 367 | 368 | // sets or updates a list of properties 369 | // note that, you can send a object contains property:value pairs 370 | el.css({width: '100px', height: '50px', 'margin-bottom': '5px'}); 371 | ``` 372 | 373 | ### Dimensions 374 | Dimension related methods returns or sets content width or height according to dimension method. Except setting `width` and `height` methods, all other usages break the chaining. 375 | 376 | ``` js 377 | let el = uxr(selector); 378 | 379 | // returns the first elements content width 380 | // note that: this return only the content width, no-border, no-padding, no-margin 381 | el.width(); 382 | el.contentWidth(); // alias method 383 | 384 | // sets the width of elements in the uxr object. 385 | // similar method to el.css('width', value); 386 | el.width('100px'); 387 | el.contentWidth('100%'); 388 | 389 | // returns the clientWidth of the first element 390 | // note that: this is only differs from width method with addition of padding 391 | el.innerWidth(); 392 | el.clientWidth(); // alias method 393 | 394 | 395 | // returns the offsetWidth of the first element 396 | // note that: this calculates width with border, padding and content-width altogether 397 | el.outerWidth(); 398 | el.offsetWidth(); // alias method 399 | 400 | // returns the offsetWidth of the first element including margins 401 | // note that: this calculates width with margin, border, padding and content-width altogether 402 | el.outerWidth(true); 403 | el.offsetWidth(true); // alias method 404 | 405 | // returns the first elements content height 406 | // note that: this return only the content height, no-border, no-padding, no-margin 407 | el.height(); 408 | el.contentHeight(); // alias method 409 | 410 | // sets the height of elements in the uxr object. 411 | // similar method to el.css('height', value); 412 | el.height('100px'); 413 | el.contentHeight('100%'); 414 | 415 | // returns the clientHeight of the first element 416 | // note that: this is only differs from width method with addition of padding 417 | el.innerHeight(); 418 | el.clientHeight(); // alias method 419 | 420 | 421 | // returns the offsetHeight of the first element 422 | // note that: this calculates height with border, padding and content-height altogether 423 | el.outerHeight(); 424 | el.offsetHeight(); // alias method 425 | 426 | // returns the offsetHeight of the first element including margins 427 | // note that: this calculates height with margin, border, padding and content-height altogether 428 | el.outerHeight(true); 429 | el.offsetHeight(true); // alias method 430 | ``` 431 | 432 | ### Cloning 433 | Clone methods, clones the nodes in a UXR object. 434 | 435 | ``` js 436 | let el = uxr(selector); 437 | 438 | // clones the all elements in uxr object 439 | let clone = el.clone(); 440 | 441 | // deep clones (child elements and inner contents) the all elements in uxr object 442 | let cloneDeep = el.clone(true); 443 | ``` 444 | 445 | [npm]: https://img.shields.io/npm/v/uxr.svg 446 | [npm-url]: https://npmjs.com/package/uxr 447 | 448 | [tests]: http://img.shields.io/travis/bcinarli/uxr.svg 449 | [tests-url]: https://travis-ci.org/bcinarli/uxr 450 | 451 | [cover]: https://codecov.io/gh/bcinarli/uxr/branch/master/graph/badge.svg 452 | [cover-url]: https://codecov.io/gh/bcinarli/uxr 453 | 454 | [os]:https://badges.frapsoft.com/os/v2/open-source.svg?v=103 455 | [os-url]:(https://github.com/ellerbrock/open-source-badges/) 456 | 457 | [pr]:https://img.shields.io/badge/PRs-welcome-brightgreen.svg 458 | [pr-url]:http://makeapullrequest.com 459 | 460 | [cc]:https://api.codeclimate.com/v1/badges/2da503653af06036b031/maintainability 461 | [cc-url]: https://codeclimate.com/github/bcinarli/uxr/maintainability 462 | 463 | [dm]:https://david-dm.org/bcinarli/uxr/dev-status.svg 464 | [dm-url]:https://david-dm.org/bcinarli/uxr?type=dev 465 | -------------------------------------------------------------------------------- /dist/uxr.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /** 3 | * uxr 4 | **/ 5 | 6 | const _ = window['uxr'] = function (selector) { 7 | return new uxr(selector); 8 | }; 9 | 10 | const uxr = function (selector) { 11 | this.selector = selector; 12 | this.init(); 13 | }; 14 | 15 | _.extend = uxr.prototype = { 16 | constructor: uxr, 17 | 18 | init: function () { 19 | const _this = this; 20 | 21 | if (typeof this.selector === 'string') { 22 | this.el = document.querySelectorAll(this.selector); 23 | } 24 | 25 | else if (this.selector.length) { 26 | this.el = this.selector; 27 | } 28 | 29 | else { 30 | this.el = []; 31 | } 32 | 33 | this.el = [...this.el]; 34 | 35 | for (let i = 0; i < this.el.length; i++) { 36 | _this[i] = this.el[i]; 37 | } 38 | 39 | this.length = this.el.length; 40 | } 41 | }; 42 | 43 | _.extend.init.prototype = _.extend; 44 | /** 45 | * attr 46 | **/ 47 | 48 | /* global toDomString */ 49 | 50 | _.extend.attr = function (attr, value) { 51 | if (value) { 52 | return this.setAttribute(attr, value); 53 | } 54 | 55 | return this.getAttr(attr); 56 | }; 57 | 58 | _.extend.setAttr = _.extend.setAttribute = function (attr, value) { 59 | this.el.forEach(item => item.setAttribute(toDomString(attr), value)); 60 | 61 | return this; 62 | }; 63 | 64 | _.extend.getAttr = _.extend.getAttribute = function (attr) { 65 | return this.el[0].getAttribute(toDomString(attr)); 66 | }; 67 | 68 | _.extend.removeAttr = _.extend.removeAttribute = function (attr) { 69 | this.el.forEach(item => item.removeAttribute(toDomString(attr))); 70 | 71 | return this; 72 | }; 73 | 74 | _.extend.src = function (url) { 75 | return this.attr('src', url); 76 | }; 77 | 78 | _.extend.href = function (url) { 79 | return this.attr('href', url); 80 | }; 81 | /** 82 | * clone 83 | **/ 84 | 85 | /* global mutated */ 86 | 87 | _.extend.clone = function (deep = false) { 88 | return mutated(this, this.el.map(item => item.cloneNode(deep))); 89 | }; 90 | /** 91 | * css-classes 92 | **/ 93 | 94 | /* global normalizeClassName */ 95 | /* global maybeMultiple */ 96 | 97 | const _class = type => { 98 | return function (className) { 99 | this.el.forEach(item => { 100 | if (item.nodeType === 1) { 101 | maybeMultiple(className).map(className => item.classList[type](normalizeClassName(className))); 102 | } 103 | }); 104 | 105 | return this; 106 | }; 107 | }; 108 | 109 | _.extend.addClass = _class('add'); 110 | 111 | _.extend.removeClass = _class('remove'); 112 | 113 | _.extend.hasClass = function (className) { 114 | return this.el[0].nodeType === 1 && this.filter('.' + normalizeClassName(className)).length > 0; 115 | }; 116 | 117 | _.extend.toggleClass = function (className) { 118 | this.el.forEach(item => { 119 | let classNames = maybeMultiple(className); 120 | 121 | if (item.nodeType === 1) { 122 | classNames.forEach(className => item.classList.toggle(normalizeClassName(className))); 123 | } 124 | }); 125 | 126 | return this; 127 | }; 128 | 129 | /** 130 | * css 131 | **/ 132 | 133 | /* global toDomString */ 134 | /* global isObject */ 135 | 136 | const maybePropIsObject = prop => { 137 | let properties = []; 138 | let values = []; 139 | 140 | if (isObject(prop)) { 141 | Object.keys(prop).forEach(p => { 142 | properties.push(toDomString(p)); 143 | values.push(prop[p]); 144 | }); 145 | return {properties, values}; 146 | } 147 | 148 | return false; 149 | }; 150 | 151 | const setProps = (props) => { 152 | if (typeof props === 'string') { 153 | return [toDomString(props)]; 154 | } 155 | 156 | if (Array.isArray(props)) { 157 | return props.map(p => toDomString(p)); 158 | } 159 | }; 160 | 161 | const getStyles = (el, props) => { 162 | let list = {}; 163 | 164 | props.forEach(prop => { 165 | list[prop] = el.style[prop]; 166 | }); 167 | 168 | return props.length === 1 ? list[props[0]] : list; 169 | }; 170 | 171 | 172 | _.extend.css = function (prop, value) { 173 | let options = { 174 | properties: [], 175 | values: value ? [value] : [] 176 | }; 177 | 178 | options.properties = setProps(prop); 179 | 180 | // if the prop is object for set of prop/value pair 181 | options = maybePropIsObject(prop) || options; 182 | 183 | if (options.values.length > 0) { 184 | this.el.map(item => options.properties.forEach((p, i) => item.style[p] = options.values[i])); 185 | 186 | return this; 187 | } 188 | 189 | // if no value set then we are asking for getting the values of properties 190 | // this breaks the chaining 191 | return getStyles(this.el[0], options.properties); 192 | }; 193 | /** 194 | * data 195 | **/ 196 | 197 | /* global toDomString */ 198 | 199 | _.extend.data = function (name, value) { 200 | let domName = toDomString(name); 201 | 202 | if (typeof value !== 'undefined') { 203 | this.el.forEach(item => item.dataset[domName] = value); 204 | return this; 205 | } 206 | 207 | return this.el[0].dataset[domName]; 208 | }; 209 | /** 210 | * dimensions 211 | **/ 212 | 213 | /* global removeUnit */ 214 | 215 | const _getContentSize = (el, type) => { 216 | let client = type === 'width' ? 'clientWidth' : 'clientHeight'; 217 | let styleFirst = type === 'width' ? 'paddingLeft' : 'paddingTop'; 218 | let styleLast = type === 'width' ? 'paddingRight' : 'paddingBottom'; 219 | 220 | return el[client] - removeUnit(el.style[styleFirst]) - removeUnit(el.style[styleLast]); 221 | }; 222 | 223 | const _contentSize = type => { 224 | return function (newSize) { 225 | if (this.length === 0) { 226 | return false; 227 | } 228 | 229 | if (newSize) { 230 | this.el.forEach(item => item.style[type] = newSize); 231 | 232 | return this; 233 | } 234 | 235 | return _getContentSize(this.el[0], type); 236 | }; 237 | }; 238 | 239 | const _clientSize = type => { 240 | return function () { 241 | return this.length > 0 ? this.el[0][type] : false; 242 | }; 243 | }; 244 | 245 | const _getMarginSize = (el, type) => { 246 | let styleFirst = type === 'offsetWidth' ? 'marginLeft' : 'marginTop'; 247 | let styleLast = type === 'offsetHeight' ? 'marginRight' : 'marginBottom'; 248 | 249 | return removeUnit(el.style[styleFirst]) + removeUnit(el.style[styleLast]); 250 | }; 251 | 252 | const _offsetSize = type => { 253 | return function (includeMargins = false) { 254 | 255 | if (this.length === 0) { 256 | return false; 257 | } 258 | 259 | let el = this.el[0]; 260 | let sizeType = el[type]; 261 | 262 | if (includeMargins) { 263 | sizeType += _getMarginSize(el, type); 264 | } 265 | 266 | return sizeType; 267 | }; 268 | }; 269 | 270 | _.extend.contentWidth = _.extend.width = _contentSize('width'); 271 | _.extend.clientWidth = _.extend.innerWidth = _clientSize('clientWidth'); 272 | _.extend.offsetWidth = _.extend.outerWidth = _offsetSize('offsetWidth'); 273 | 274 | _.extend.contentHeight = _.extend.height = _contentSize('height'); 275 | _.extend.clientHeight = _.extend.innerHeight = _clientSize('clientHeight'); 276 | _.extend.offsetHeight = _.extend.outerHeight = _offsetSize('offsetHeight'); 277 | /** 278 | * end 279 | **/ 280 | 281 | _.extend.end = function () { 282 | return this.prevObj || this; 283 | }; 284 | /** 285 | * event 286 | **/ 287 | 288 | /* global hashCode */ 289 | /* global maybeMultiple */ 290 | /* global isObject */ 291 | 292 | /*const events = { 293 | animation: ['animationend', 'animationiteration', 'animationstart'], 294 | drag: ['drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'dragstart', 'drop'], 295 | frame: ['abort', 'beforeunload', 'error', 'hashchange', 'load', 'pageshow', 'pagehide', 'resize', 'scroll', 'unload'], 296 | form: ['blur', 'change', 'focus', 'focusin', 'focusout', 'input', 'invalid', 'reset', 'search', 'select', 'submit'], 297 | keyboard: ['keydown', 'keypress', 'keyup'], 298 | media: ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'progress', 'ratechange', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting'], 299 | misc: ['message', 'mousewheel', 'online', 'offline', 'popstate', 'show', 'storage', 'toggle', 'wheel'], 300 | mouse: ['click', 'contextmenu', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup'], 301 | print: ['afterprint', 'beforeprint'], 302 | server: ['error', 'message', 'open'], 303 | transition: ['transitionend'], 304 | touch: ['touchcancel', 'touchend', 'touchmove', 'touchstart'] 305 | }; 306 | 307 | const allEvents = Object.keys(events).reduce((acc, cur) => acc.concat(events[cur]), []);*/ 308 | 309 | const removeEventFromItem = (item, event, fn) => { 310 | item.removeEventListener(event, item.uxrAttachedEvents[event][fn]); 311 | delete item.uxrAttachedEvents[event][fn]; 312 | 313 | return item; 314 | }; 315 | 316 | _.extend.off = function (eventName, eventHandlerOrSelector, eventHandler) { 317 | let stack = this; 318 | let handler = eventHandlerOrSelector; 319 | let events = maybeMultiple(eventName); 320 | 321 | if (typeof eventHandlerOrSelector === 'string' 322 | || typeof eventHandler !== 'undefined') { 323 | handler = eventHandler; 324 | stack = this.find(eventHandlerOrSelector); 325 | } 326 | 327 | stack.el.forEach(item => { 328 | item.uxrAttachedEvents = item.uxrAttachedEvents || {}; 329 | 330 | events.forEach(event => { 331 | // make sure not to give an error, if user tried to remove unattached event 332 | if (typeof item.uxrAttachedEvents[event] !== 'undefined') { 333 | if (typeof handler === 'undefined') { 334 | Object.keys(item.uxrAttachedEvents[event]).forEach(fn => { 335 | removeEventFromItem(item, event, fn); 336 | }); 337 | } 338 | 339 | else { 340 | let hash = hashCode((handler).toString()); 341 | removeEventFromItem(item, event, hash); 342 | } 343 | } 344 | }); 345 | }); 346 | 347 | return this; 348 | }; 349 | 350 | _.extend.on = function (eventName, eventHandlerOrSelector, eventHandler) { 351 | let stack = this; 352 | let handler = eventHandlerOrSelector; 353 | let events = maybeMultiple(eventName); 354 | 355 | if (typeof eventHandler !== 'undefined') { 356 | handler = eventHandler; 357 | stack = this.find(eventHandlerOrSelector); 358 | } 359 | 360 | let hash = hashCode((handler).toString()); 361 | 362 | stack.el.forEach(item => { 363 | item.uxrAttachedEvents = item.uxrAttachedEvents || {}; 364 | 365 | events.forEach(event => { 366 | item.uxrAttachedEvents[event] = item.uxrAttachedEvents[event] || {}; 367 | item.uxrAttachedEvents[event][hash] = handler; 368 | 369 | item.addEventListener(event, item.uxrAttachedEvents[event][hash]); 370 | }); 371 | }); 372 | 373 | return this; 374 | }; 375 | 376 | _.extend.once = function (eventName, eventHandlerOrSelector, eventHandler) { 377 | let stack = this; 378 | let handler = eventHandlerOrSelector; 379 | let events = maybeMultiple(eventName); 380 | 381 | if (typeof eventHandler !== 'undefined') { 382 | handler = eventHandler; 383 | stack = this.find(eventHandlerOrSelector); 384 | } 385 | 386 | stack.el.forEach(item => { 387 | events.forEach(event => { 388 | /* istanbul ignore next */ 389 | let oneHandler = e => { 390 | e.preventDefault(); 391 | _(item).off(event, handler); 392 | _(item).off(event, oneHandler); 393 | }; 394 | 395 | _(item).on(event, handler); 396 | _(item).on(event, oneHandler); 397 | }); 398 | }); 399 | 400 | return this; 401 | }; 402 | 403 | _.extend.trigger = function (eventName, eventParamsOrSelector, eventParams) { 404 | let stack = this; 405 | let event; 406 | 407 | if (eventParamsOrSelector !== 'undefined' && typeof eventParamsOrSelector === 'string') { 408 | stack = this.find(eventParamsOrSelector); 409 | } 410 | 411 | if (!_eventHasParams(eventParamsOrSelector, eventParams)) { 412 | event = getNativeEvent(eventName); 413 | } 414 | 415 | else { 416 | event = getCustomEvent(eventName, eventParamsOrSelector, eventParams); 417 | } 418 | 419 | stack.el.forEach(item => item.dispatchEvent(event)); 420 | 421 | return this; 422 | }; 423 | 424 | const _eventHasParams = (eventParamsOrSelector, eventParams) => { 425 | return (typeof eventParamsOrSelector !== 'undefined' && isObject(eventParamsOrSelector)) || typeof eventParams !== 'undefined'; 426 | }; 427 | 428 | const getNativeEvent = (eventName) => { 429 | return new Event(eventName); 430 | }; 431 | 432 | const getCustomEvent = (eventName, eventParamsOrSelector, eventParams) => { 433 | let params = eventParams; 434 | 435 | if (typeof eventParamsOrSelector !== 'undefined' 436 | && isObject(eventParamsOrSelector)) { 437 | params = eventParamsOrSelector; 438 | } 439 | 440 | return new CustomEvent(eventName, {detail: params}); 441 | }; 442 | /** 443 | * filter 444 | **/ 445 | 446 | /* global mutated */ 447 | 448 | _.extend.filter = function (selector) { 449 | return mutated(this, this.el.filter(item => item.matches(selector))); 450 | }; 451 | 452 | _.extend.find = _.extend.has = function (selector) { 453 | return mutated(this, this.el.map(item => [...item.querySelectorAll(selector)]).reduce((acc, cur) => acc.concat(cur), [])); 454 | }; 455 | 456 | _.extend.not = function (selector) { 457 | return mutated(this, this.el.filter(item => !item.matches(selector))); 458 | }; 459 | /** 460 | * manipulation 461 | **/ 462 | 463 | /* global getInsertableElement */ 464 | /* global insertBefore */ 465 | 466 | const _insert = ({where, parent}) => { 467 | return function(stringOrObject) { 468 | this.el.forEach( 469 | item => insertBefore(stringOrObject, item, where, parent)); 470 | 471 | return this; 472 | }; 473 | }; 474 | 475 | _.extend.empty = function () { 476 | this.el.forEach(item => item.innerHTML = ''); 477 | 478 | return this; 479 | }; 480 | 481 | _.extend.remove = function () { 482 | this.el.forEach(item => item.parentNode.removeChild(item)); 483 | 484 | return this; 485 | }; 486 | 487 | _.extend.append = function (stringOrObject) { 488 | this.el.forEach(item => item.appendChild(getInsertableElement(stringOrObject))); 489 | 490 | return this; 491 | }; 492 | 493 | _.extend.prepend = _insert({where: 'firstChild', parent: false}); 494 | 495 | _.extend.after = _insert({where: 'nextSibling', parent: true}); 496 | 497 | _.extend.before = _insert({where: 'self', parent: true}); 498 | 499 | _.extend.replaceWith = function (stringOrObject) { 500 | this.el.map( 501 | item => item.parentNode.replaceChild(getInsertableElement(stringOrObject), item) 502 | ); 503 | 504 | return this; 505 | }; 506 | /** 507 | * prop 508 | **/ 509 | 510 | _.extend.prop = function (prop, value) { 511 | if (value) { 512 | return this.setProp(prop, value); 513 | } 514 | 515 | return this.getProp(prop); 516 | }; 517 | 518 | _.extend.setProp = function (prop, value) { 519 | this.el.forEach(item => item[prop] = value); 520 | 521 | return this; 522 | }; 523 | 524 | _.extend.getProp = function (prop) { 525 | return this[0][prop]; 526 | }; 527 | 528 | _.extend.text = function (txt) { 529 | return this.prop('innerText', txt); 530 | }; 531 | 532 | _.extend.html = function (html) { 533 | return this.prop('innerHTML', html); 534 | }; 535 | 536 | _.extend.value = function (value) { 537 | return this.prop('value', value); 538 | }; 539 | /** 540 | * ready 541 | **/ 542 | 543 | /* istanbul ignore next */ 544 | const _stateChange = (condition) => { 545 | return fn => { 546 | return document.addEventListener('readystatechange', e => { 547 | if (e.target.readyState === condition) { 548 | fn(); 549 | } 550 | }); 551 | }; 552 | }; 553 | 554 | _.ready = _stateChange('interactive'); 555 | 556 | _.load = _stateChange('complete'); 557 | /** 558 | * traversing 559 | **/ 560 | 561 | /* global mutated */ 562 | 563 | const _mapFilterAndMutate = map => { 564 | return function (selector) { 565 | return mutated( 566 | this, 567 | this.el.map(item => item[map]).filter(item => selector ? item.matches(selector) : item)); 568 | }; 569 | }; 570 | 571 | _.extend.closest = function (selector) { 572 | let el = this.el[0]; 573 | 574 | if (!selector) { 575 | return mutated(this, [el.parentNode]); 576 | } 577 | 578 | while (el !== null && el.nodeType === 1) { 579 | if (el.matches(selector)) { 580 | return mutated(this, [el]); 581 | } 582 | 583 | el = el.parentNode; 584 | } 585 | 586 | return mutated(this, []); 587 | }; 588 | 589 | _.extend.parent = _mapFilterAndMutate('parentNode'); 590 | 591 | _.extend.children = function (selector) { 592 | return mutated( 593 | this, 594 | this.el.map(item => Array.from(item.children)) 595 | .reduce((acc, cur) => acc.concat(cur), []) 596 | .filter(item => selector ? item.matches(selector) : item)); 597 | }; 598 | 599 | _.extend.siblings = function (selector) { 600 | return mutated( 601 | this, 602 | this.el.map(item => 603 | Array.from(item.parentNode.children) 604 | .filter(child => !child.isEqualNode(item))) 605 | .reduce((acc, cur) => acc.concat(cur), []) 606 | .filter(item => selector ? item.matches(selector) : item)); 607 | }; 608 | 609 | _.extend.next = _mapFilterAndMutate('nextElementSibling'); 610 | 611 | _.extend.prev = _mapFilterAndMutate('previousElementSibling'); 612 | 613 | _.extend.first = function () { 614 | return mutated(this, this.el.filter((item, index) => index === 0)); 615 | }; 616 | 617 | _.extend.last = function () { 618 | let last = this.length - 1; 619 | return mutated(this, this.el.filter((item, index) => index === last)); 620 | }; 621 | 622 | /** 623 | * utils 624 | **/ 625 | 626 | Element.prototype.matches = Element.prototype.matches ? Element.prototype.matches : Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 627 | 628 | // eslint-disable-next-line 629 | const normalizeClassName = className => className.charAt(0) === '.' ? className.substr(1) : className; 630 | 631 | // generate hashes for internal usage 632 | // eslint-disable-next-line 633 | const hashCode = s => s.split('').reduce((a, b) => { 634 | a = ((a << 5) - a) + b.charCodeAt(0); 635 | return a & a; 636 | }, 0); 637 | 638 | // trims the string and replaces to multiple spaces in the string with single space 639 | // eslint-disable-next-line 640 | const justifyString = s => s.replace(/\s\s+/g, ' ').trim(); 641 | 642 | // split selector 643 | // eslint-disable-next-line 644 | const maybeMultiple = s => typeof s === 'string' ? justifyString(s).split(' ') : s; 645 | 646 | // Dom String Format 647 | // eslint-disable-next-line 648 | const toDomString = s => s.substr(0, 1).toLowerCase() + s.split('-').map(chunk => chunk.charAt(0).toUpperCase() + chunk.slice(1)).join('').substring(1); 649 | 650 | // Element from string 651 | // eslint-disable-next-line 652 | const elementFromString = s => { 653 | if (typeof s === 'string') { 654 | let template = document.createElement('template'); 655 | template.innerHTML = s.trim(); 656 | 657 | return template.content.firstChild; 658 | } 659 | 660 | return s; 661 | }; 662 | 663 | // Insertable Element 664 | // eslint-disable-next-line 665 | const getInsertableElement = s => { 666 | let insertableElement = elementFromString(s); 667 | 668 | if (insertableElement instanceof uxr) { 669 | let template = document.createElement('template'); 670 | insertableElement.el.forEach(item => template.content.appendChild(item)); 671 | insertableElement = template.content; 672 | } 673 | 674 | return insertableElement; 675 | }; 676 | 677 | // InserBefore 678 | // eslint-disable-next-line 679 | const insertBefore = (insert, target, ref, parent) => { 680 | let to = parent === true ? target.parentNode : target; 681 | let where = ref === 'self' ? target : target[ref]; 682 | 683 | to.insertBefore(getInsertableElement(insert), where); 684 | }; 685 | 686 | // mutatedObj 687 | // eslint-disable-next-line 688 | const mutated = (orgObj, newSet) => { 689 | let obj = _(newSet); 690 | 691 | obj.prevObj = orgObj; 692 | 693 | return obj; 694 | }; 695 | 696 | // Is Object => {key: value} 697 | // eslint-disable-next-line 698 | const isObject = objLike => ({}.toString.call(objLike) === '[object Object]'); 699 | 700 | // Remove Unit 701 | // eslint-disable-next-line 702 | const removeUnit = number => parseInt(number, 10); 703 | /** 704 | * wrap 705 | **/ 706 | 707 | /* global elementFromString */ 708 | 709 | const getWrapper = wrapperStr => { 710 | let wrapperString = wrapperStr.toString(); 711 | 712 | return wrapperString.charAt(0) !== '<' ? 713 | document.createElement(wrapperString) : 714 | elementFromString(wrapperString); 715 | }; 716 | 717 | _.extend.wrap = function (wrapper) { 718 | let newWrap = getWrapper(wrapper); 719 | 720 | let parent = this.el[0].parentNode; 721 | let siblings = this.el[0].nextSibling; 722 | 723 | newWrap.appendChild(this.el[0]); 724 | 725 | if (siblings) { 726 | parent.insertBefore(newWrap, siblings); 727 | } 728 | else { 729 | parent.appendChild(newWrap); 730 | } 731 | 732 | return this; 733 | }; 734 | 735 | _.extend.wrapAll = function (wrapper) { 736 | let firstSibling = true; 737 | let newWrap = getWrapper(wrapper); 738 | 739 | this.el.forEach(item => { 740 | if (firstSibling) { 741 | let parent = item.parentNode; 742 | let siblings = item.nextSibling; 743 | 744 | newWrap.appendChild(item); 745 | 746 | if (siblings) { 747 | parent.insertBefore(newWrap, siblings); 748 | } 749 | else { 750 | parent.appendChild(newWrap); 751 | } 752 | 753 | firstSibling = false; 754 | } 755 | 756 | else { 757 | newWrap.appendChild(item); 758 | } 759 | }); 760 | 761 | return this; 762 | }; 763 | 764 | _.extend.unwrap = function (selector) { 765 | let parent = this.el[0].parentNode; 766 | 767 | // if the parent is not the desired one, skip unwrapping 768 | if (selector && !parent.matches(selector.toString())) { 769 | return this; 770 | } 771 | 772 | parent.parentNode.appendChild(this.el[0]); 773 | 774 | if (parent.children.length === 0) { 775 | parent.parentNode.removeChild(parent); 776 | } 777 | 778 | return this; 779 | }; 780 | _.uxr = { version: '0.7.0' }; 781 | })(); --------------------------------------------------------------------------------