Files
ionic-framework/js/utils/dom.js

317 lines
9.5 KiB
JavaScript

(function(window, document, ionic) {
var readyCallbacks = [];
var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
function domReady() {
isDomReady = true;
for (var x = 0; x < readyCallbacks.length; x++) {
ionic.requestAnimationFrame(readyCallbacks[x]);
}
readyCallbacks = [];
document.removeEventListener('DOMContentLoaded', domReady);
}
if (!isDomReady) {
document.addEventListener('DOMContentLoaded', domReady);
}
// From the man himself, Mr. Paul Irish.
// The requestAnimationFrame polyfill
// Put it on window just to preserve its context
// without having to use .call
window._rAF = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 16);
};
})();
var cancelAnimationFrame = window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.webkitCancelRequestAnimationFrame;
/**
* @ngdoc utility
* @name ionic.DomUtil
* @module ionic
*/
ionic.DomUtil = {
//Call with proper context
/**
* @ngdoc method
* @name ionic.DomUtil#requestAnimationFrame
* @alias ionic.requestAnimationFrame
* @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
* @param {function} callback The function to call when the next frame
* happens.
*/
requestAnimationFrame: function(cb) {
return window._rAF(cb);
},
cancelAnimationFrame: function(requestId) {
cancelAnimationFrame(requestId);
},
/**
* @ngdoc method
* @name ionic.DomUtil#animationFrameThrottle
* @alias ionic.animationFrameThrottle
* @description
* When given a callback, if that callback is called 100 times between
* animation frames, adding Throttle will make it only run the last of
* the 100 calls.
*
* @param {function} callback a function which will be throttled to
* requestAnimationFrame
* @returns {function} A function which will then call the passed in callback.
* The passed in callback will receive the context the returned function is
* called with.
*/
animationFrameThrottle: function(cb) {
var args, isQueued, context;
return function() {
args = arguments;
context = this;
if (!isQueued) {
isQueued = true;
ionic.requestAnimationFrame(function() {
cb.apply(context, args);
isQueued = false;
});
}
};
},
contains: function(parentNode, otherNode) {
var current = otherNode;
while (current) {
if (current === parentNode) return true;
current = current.parentNode;
}
},
/**
* @ngdoc method
* @name ionic.DomUtil#getPositionInParent
* @description
* Find an element's scroll offset within its container.
* @param {DOMElement} element The element to find the offset of.
* @returns {object} A position object with the following properties:
* - `{number}` `left` The left offset of the element.
* - `{number}` `top` The top offset of the element.
*/
getPositionInParent: function(el) {
return {
left: el.offsetLeft,
top: el.offsetTop
};
},
/**
* @ngdoc method
* @name ionic.DomUtil#ready
* @description
* Call a function when the DOM is ready, or if it is already ready
* call the function immediately.
* @param {function} callback The function to be called.
*/
ready: function(cb) {
if (isDomReady) {
ionic.requestAnimationFrame(cb);
} else {
readyCallbacks.push(cb);
}
},
/**
* @ngdoc method
* @name ionic.DomUtil#getTextBounds
* @description
* Get a rect representing the bounds of the given textNode.
* @param {DOMElement} textNode The textNode to find the bounds of.
* @returns {object} An object representing the bounds of the node. Properties:
* - `{number}` `left` The left position of the textNode.
* - `{number}` `right` The right position of the textNode.
* - `{number}` `top` The top position of the textNode.
* - `{number}` `bottom` The bottom position of the textNode.
* - `{number}` `width` The width of the textNode.
* - `{number}` `height` The height of the textNode.
*/
getTextBounds: function(textNode) {
if (document.createRange) {
var range = document.createRange();
range.selectNodeContents(textNode);
if (range.getBoundingClientRect) {
var rect = range.getBoundingClientRect();
if (rect) {
var sx = window.scrollX;
var sy = window.scrollY;
return {
top: rect.top + sy,
left: rect.left + sx,
right: rect.left + sx + rect.width,
bottom: rect.top + sy + rect.height,
width: rect.width,
height: rect.height
};
}
}
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getChildIndex
* @description
* Get the first index of a child node within the given element of the
* specified type.
* @param {DOMElement} element The element to find the index of.
* @param {string} type The nodeName to match children of element against.
* @returns {number} The index, or -1, of a child with nodeName matching type.
*/
getChildIndex: function(element, type) {
if (type) {
var ch = element.parentNode.children;
var c;
for (var i = 0, k = 0, j = ch.length; i < j; i++) {
c = ch[i];
if (c.nodeName && c.nodeName.toLowerCase() == type) {
if (c == element) {
return k;
}
k++;
}
}
}
return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
},
/**
* @private
*/
swapNodes: function(src, dest) {
dest.parentNode.insertBefore(src, dest);
},
elementIsDescendant: function(el, parent, stopAt) {
var current = el;
do {
if (current === parent) return true;
current = current.parentNode;
} while (current && current !== stopAt);
return false;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getParentWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent of element matching the
* className, or null.
*/
getParentWithClass: function(e, className, depth) {
depth = depth || 10;
while (e.parentNode && depth--) {
if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
return e.parentNode;
}
e = e.parentNode;
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#getParentOrSelfWithClass
* @param {DOMElement} element
* @param {string} className
* @returns {DOMElement} The closest parent or self matching the
* className, or null.
*/
getParentOrSelfWithClass: function(e, className, depth) {
depth = depth || 10;
while (e && depth--) {
if (e.classList && e.classList.contains(className)) {
return e;
}
e = e.parentNode;
}
return null;
},
/**
* @ngdoc method
* @name ionic.DomUtil#rectContains
* @param {number} x
* @param {number} y
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @returns {boolean} Whether {x,y} fits within the rectangle defined by
* {x1,y1,x2,y2}.
*/
rectContains: function(x, y, x1, y1, x2, y2) {
if (x < x1 || x > x2) return false;
if (y < y1 || y > y2) return false;
return true;
},
/**
* @ngdoc method
* @name ionic.DomUtil#blurAll
* @description
* Blurs any currently focused input element
* @returns {DOMElement} The element blurred or null
*/
blurAll: function() {
if (document.activeElement && document.activeElement != document.body) {
document.activeElement.blur();
return document.activeElement;
}
return null;
},
cachedAttr: function(ele, key, value) {
ele = ele && ele.length && ele[0] || ele;
if (ele && ele.setAttribute) {
var dataKey = '$attr-' + key;
if (arguments.length > 2) {
if (ele[dataKey] !== value) {
ele.setAttribute(key, value);
ele[dataKey] = value;
}
} else if (typeof ele[dataKey] == 'undefined') {
ele[dataKey] = ele.getAttribute(key);
}
return ele[dataKey];
}
},
cachedStyles: function(ele, styles) {
ele = ele && ele.length && ele[0] || ele;
if (ele && ele.style) {
for (var prop in styles) {
if (ele['$style-' + prop] !== styles[prop]) {
ele.style[prop] = ele['$style-' + prop] = styles[prop];
}
}
}
}
};
//Shortcuts
ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
})(window, document, ionic);