mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
317 lines
9.5 KiB
JavaScript
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);
|