Skip to content

Commit

Permalink
Merge pull request #305 from dvoytenko/helpers2
Browse files Browse the repository at this point in the history
DOM and other helper additions needed for Lightbox
  • Loading branch information
dvoytenko committed Sep 26, 2015
2 parents d5decf6 + 0210c41 commit a28575e
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
86 changes: 86 additions & 0 deletions src/dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Copyright 2015 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


/**
* Removes all child nodes of the specified element.
* @param {!Element} parent
*/
export function removeChildren(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
}


/**
* Copies all children nodes of element "from" to element "to". Child nodes
* are deeply cloned. Notice, that this method should be used with care and
* preferrably on smaller subtrees.
* @param {!Element} from
* @param {!Element} to
*/
export function copyChildren(from, to) {
let frag = to.ownerDocument.createDocumentFragment();
for (let n = from.firstChild; n; n = n.nextSibling) {
frag.appendChild(n.cloneNode(true));
}
to.appendChild(frag);
}


/**
* Finds the closest element that satisfies the callback from this element
* up the DOM subtree.
* @param {!Element} element
* @param {function(!Element):boolean} callback
* @return {?Element}
*/
export function closest(element, callback) {
for (let el = element; el; el = el.parentElement) {
if (callback(el)) {
return el;
}
}
return null;
}


/**
* Finds the closest element with the specified name from this element
* up the DOM subtree.
* @param {!Element} element
* @param {string} tagName
* @return {?Element}
*/
export function closestByTag(element, tagName) {
tagName = tagName.toUpperCase();
return closest(element, (el) => {
return el.tagName == tagName;
});
}


/**
* Finds the first descendant element with the specified name.
* @param {!Element} element
* @param {string} tagName
* @return {?Element}
*/
export function elementByTag(element, tagName) {
let elements = element.getElementsByTagName(tagName);
return elements.length > 0 ? elements[0] : null;
}
12 changes: 11 additions & 1 deletion src/event-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ export function listenOncePromise(element, eventType, opt_capture,
}


/**
* Whether the specified element has been loaded already.
* @param {!Element} element
* @return {boolean}
*/
export function isLoaded(element) {
return element.complete || element.readyState == 'complete';
}


/**
* Returns a promise that will resolve or fail based on the element's 'load'
* and 'error' events. Optionally this method takes a timeout, which will reject
Expand All @@ -73,7 +83,7 @@ export function loadPromise(element, opt_timeout) {
let unlistenLoad;
let unlistenError;
let loadingPromise = new Promise((resolve, reject) => {
if (element.complete || element.readyState == 'complete') {
if (isLoaded(element)) {
resolve(element);
} else {
// Listen once since IE 5/6/7 fire the onload event continuously for
Expand Down
18 changes: 18 additions & 0 deletions src/layout-rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ export function layoutRectLtwh(left, top, width, height) {
}


/**
* Creates a layout rect based on the DOMRect, e.g. obtained from calling
* getBoundingClientRect.
* @param {!DOMRect} rect
* @return {!LayoutRect}
*/
export function layoutRectFromDomRect(rect) {
return {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
bottom: rect.top + rect.height,
right: rect.left + rect.width
};
}


/**
* Returns true if the specified two rects overlap by a single pixel.
* @param {!LayoutRect} r1
Expand Down
96 changes: 96 additions & 0 deletions test/functional/test-dom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright 2015 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as dom from '../../src/dom';


describe('DOM', () => {

it('should remove all children', () => {
let element = document.createElement('div');
element.appendChild(document.createElement('div'));
element.appendChild(document.createTextNode('ABC'));
expect(element.children.length).to.equal(1);
expect(element.firstChild).to.not.equal(null);
expect(element.textContent).to.equal('ABC');

dom.removeChildren(element);
expect(element.children.length).to.equal(0);
expect(element.firstChild).to.equal(null);
expect(element.textContent).to.equal('');
});

it('should copy all children', () => {
let element = document.createElement('div');
element.appendChild(document.createElement('div'));
element.appendChild(document.createTextNode('ABC'));

let other = document.createElement('div');
dom.copyChildren(element, other);

expect(element.children.length).to.equal(1);
expect(element.firstChild).to.not.equal(null);
expect(element.textContent).to.equal('ABC');

expect(other.children.length).to.equal(1);
expect(other.firstChild).to.not.equal(null);
expect(other.firstChild.tagName).to.equal('DIV');
expect(other.textContent).to.equal('ABC');
});

it('closest should find itself', () => {
let element = document.createElement('div');

let child = document.createElement('div');
element.appendChild(child);

expect(dom.closest(child, () => true)).to.equal(child);
expect(dom.closestByTag(child, 'div')).to.equal(child);
expect(dom.closestByTag(child, 'DIV')).to.equal(child);
});

it('closest should find first match', () => {
let parent = document.createElement('parent');

let element = document.createElement('element');
parent.appendChild(element);

let child = document.createElement('child');
element.appendChild(child);

expect(dom.closest(child, (e) => e.tagName == 'CHILD')).to.equal(child);
expect(dom.closestByTag(child, 'child')).to.equal(child);

expect(dom.closest(child, (e) => e.tagName == 'ELEMENT')).to.equal(element);
expect(dom.closestByTag(child, 'element')).to.equal(element);

expect(dom.closest(child, (e) => e.tagName == 'PARENT')).to.equal(parent);
expect(dom.closestByTag(child, 'parent')).to.equal(parent);
});

it('closest should find first match', () => {
let parent = document.createElement('parent');

let element1 = document.createElement('element');
parent.appendChild(element1);

let element2 = document.createElement('element');
parent.appendChild(element2);

expect(dom.elementByTag(parent, 'element')).to.equal(element1);
expect(dom.elementByTag(parent, 'ELEMENT')).to.equal(element1);
});
});
15 changes: 14 additions & 1 deletion test/functional/test-event-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
* limitations under the License.
*/

import {listenOnce, listenOncePromise, loadPromise} from '../../src/event-helper';
import {isLoaded, listenOnce, listenOncePromise, loadPromise}
from '../../src/event-helper';
import {Observable} from '../../src/observable';

describe('EventHelper', () => {
Expand Down Expand Up @@ -120,6 +121,18 @@ describe('EventHelper', () => {
return promise;
});

it('isLoaded for complete property', () => {
expect(isLoaded(element)).to.equal(false);
element.complete = true;
expect(isLoaded(element)).to.equal(true);
});

it('isLoaded for readyState property', () => {
expect(isLoaded(element)).to.equal(false);
element.readyState = 'complete';
expect(isLoaded(element)).to.equal(true);
});

it('loadPromise - already complete', () => {
element.complete = true;
return loadPromise(element).then((result) => {
Expand Down
10 changes: 10 additions & 0 deletions test/functional/test-layout-rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,14 @@ describe('LayoutRect', () => {
expect(rect2.height).to.equal(40 + 40 * 6);
});

it('layoutRectFromDomRect', () => {
let rect = lr.layoutRectFromDomRect({top: 11, left: 12, width: 111,
height: 222});
expect(rect.top).to.equal(11);
expect(rect.left).to.equal(12);
expect(rect.width).to.equal(111);
expect(rect.height).to.equal(222);
expect(rect.bottom).to.equal(11 + 222);
expect(rect.right).to.equal(12 + 111);
});
});

0 comments on commit a28575e

Please sign in to comment.