element-resize-detector
Advanced tools
Comparing version 0.2.8 to 0.3.0
/*! | ||
* element-resize-detector 0.2.7 (2015-04-08, 14:28) | ||
* element-resize-detector 0.2.8 (2015-04-12, 18:14) | ||
* https://github.com/wnr/element-resize-detector | ||
@@ -31,2 +31,3 @@ * Licensed under MIT | ||
var handler; | ||
var onProcessedCallback = function() {}; | ||
@@ -75,2 +76,3 @@ function queueUpdate(element, updater) { | ||
clearBatch(); | ||
onProcessedCallback(); | ||
} | ||
@@ -101,5 +103,10 @@ | ||
function onProcessed(callback) { | ||
onProcessedCallback = callback || function() {}; | ||
} | ||
return { | ||
update: queueUpdate, | ||
force: forceUpdateBatch | ||
force: forceUpdateBatch, | ||
onProcessed: onProcessed | ||
}; | ||
@@ -183,167 +190,27 @@ }; | ||
},{}],5:[function(require,module,exports){ | ||
//Heavily inspired by http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ | ||
/** | ||
* Resize detection strategy that injects objects to elements in order to detect resize events. | ||
* Heavily inspired by: http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ | ||
*/ | ||
"use strict"; | ||
var forEach = require("./collection-utils").forEach; | ||
var elementUtilsMaker = require("./element-utils"); | ||
var listenerHandlerMaker = require("./listener-handler"); | ||
var idGeneratorMaker = require("./id-generator"); | ||
var idHandlerMaker = require("./id-handler"); | ||
var reporterMaker = require("./reporter"); | ||
var batchUpdater = require("batch-updater"); | ||
var forEach = require("../collection-utils").forEach; | ||
var browserDetector = require("../browser-detector"); | ||
/** | ||
* @typedef idHandler | ||
* @type {object} | ||
* @property {function} get Gets the resize detector id of the element. | ||
* @property {function} set Generate and sets the resize detector id of the element. | ||
*/ | ||
/** | ||
* @typedef Options | ||
* @type {object} | ||
* @property {boolean} callOnAdd Determines if listeners should be called when they are getting added. | ||
Default is true. If true, the listener is guaranteed to be called when it has been added. | ||
If false, the listener will not be guarenteed to be called when it has been added (does not prevent it from being called). | ||
* @property {idHandler} idHandler A custom id handler that is responsible for generating, setting and retrieving id's for elements. | ||
If not provided, a default id handler will be used. | ||
* @property {reporter} reporter A custom reporter that handles reporting logs, warnings and errors. | ||
If not provided, a default id handler will be used. | ||
If set to false, then nothing will be reported. | ||
*/ | ||
/** | ||
* Creates an element resize detector instance. | ||
* @public | ||
* @param {Options?} options Optional global options object that will decide how this instance will work. | ||
*/ | ||
module.exports = function(options) { | ||
options = options || {}; | ||
options = options || {}; | ||
var idHandler = options.idHandler; | ||
var reporter = options.reporter; | ||
var batchUpdater = options.batchUpdater; | ||
//idHandler is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var idHandler = options.idHandler; | ||
if(!idHandler) { | ||
var idGenerator = idGeneratorMaker(); | ||
var defaultIdHandler = idHandlerMaker(idGenerator); | ||
idHandler = defaultIdHandler; | ||
throw new Error("Missing required dependency: idHandler."); | ||
} | ||
//reporter is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var reporter = options.reporter; | ||
if(!reporter) { | ||
//If options.reporter is false, then the reporter should be quiet. | ||
var quiet = reporter === false; | ||
reporter = reporterMaker(quiet); | ||
throw new Error("Missing required dependency: reporter."); | ||
} | ||
//Options to be used as default for the listenTo function. | ||
var globalOptions = {}; | ||
globalOptions.callOnAdd = !!getOption(options, "callOnAdd", true); | ||
globalOptions.batchUpdater = getOption(options, "batchUpdater", batchUpdater({ reporter: reporter })); | ||
var eventListenerHandler = listenerHandlerMaker(idHandler); | ||
var elementUtils = elementUtilsMaker(idHandler); | ||
/** | ||
* Makes the given elements resize-detectable and starts listening to resize events on the elements. Calls the event callback for each event for each element. | ||
* @public | ||
* @param {Options?} options Optional options object. These options will override the global options. Some options may not be overriden, such as idHandler. | ||
* @param {element[]|element} elements The given array of elements to detect resize events of. Single element is also valid. | ||
* @param {function} listener The callback to be executed for each resize event for each element. | ||
*/ | ||
function listenTo(options, elements, listener) { | ||
function onResizeCallback(element) { | ||
var listeners = eventListenerHandler.get(element); | ||
forEach(listeners, function callListenerProxy(listener) { | ||
listener(element); | ||
}); | ||
} | ||
function onElementReadyToAddListener(callOnAdd, element, listener) { | ||
eventListenerHandler.add(element, listener); | ||
if(callOnAdd) { | ||
listener(element); | ||
} | ||
} | ||
//Options object may be omitted. | ||
if(!listener) { | ||
listener = elements; | ||
elements = options; | ||
options = {}; | ||
} | ||
if(!elements) { | ||
throw new Error("At least one element required."); | ||
} | ||
if(!listener) { | ||
throw new Error("Listener required."); | ||
} | ||
if(elements.length === undefined) { | ||
elements = [elements]; | ||
} | ||
var callOnAdd = getOption(options, "callOnAdd", globalOptions.callOnAdd); | ||
var batchUpdater = getOption(options, "batchUpdater", globalOptions.batchUpdater); | ||
forEach(elements, function attachListenerToElement(element) { | ||
if(!elementUtils.isDetectable(element)) { | ||
//The element is not prepared to be detectable, so do prepare it and add a listener to it. | ||
return elementUtils.makeDetectable(batchUpdater, reporter, element, function onElementDetectable(element) { | ||
elementUtils.addListener(element, onResizeCallback); | ||
onElementReadyToAddListener(callOnAdd, element, listener); | ||
}); | ||
} | ||
//The element has been prepared to be detectable and is ready to be listened to. | ||
onElementReadyToAddListener(callOnAdd, element, listener); | ||
}); | ||
} | ||
return { | ||
listenTo: listenTo | ||
}; | ||
}; | ||
function getOption(options, name, defaultValue) { | ||
var value = options[name]; | ||
if((value === undefined || value === null) && defaultValue !== undefined) { | ||
return defaultValue; | ||
} | ||
return value; | ||
} | ||
},{"./collection-utils":4,"./element-utils":6,"./id-generator":7,"./id-handler":8,"./listener-handler":9,"./reporter":10,"batch-updater":1}],6:[function(require,module,exports){ | ||
"use strict"; | ||
var forEach = require("./collection-utils").forEach; | ||
var browserDetector = require("./browser-detector"); | ||
module.exports = function(idHandler) { | ||
/** | ||
* Tells if the element has been made detectable and ready to be listened for resize events. | ||
* @public | ||
* @param {element} The element to check. | ||
* @returns {boolean} True or false depending on if the element is detectable or not. | ||
*/ | ||
function isDetectable(element) { | ||
if(browserDetector.isIE(8)) { | ||
//IE 8 does not use the object method. | ||
//Check only if the element has been given an id. | ||
return !!idHandler.get(element); | ||
} | ||
return !!getObject(element); | ||
} | ||
/** | ||
* Adds a resize event listener to the element. | ||
@@ -355,4 +222,4 @@ * @public | ||
function addListener(element, listener) { | ||
if(!isDetectable(element)) { | ||
throw new Error("Element is not detectable."); | ||
if(!getObject(element)) { | ||
throw new Error("Element is not detectable by this strategy."); | ||
} | ||
@@ -379,3 +246,3 @@ | ||
*/ | ||
function makeDetectable(batchUpdater, reporter, element, callback) { | ||
function makeDetectable(element, callback) { | ||
function injectObject(id, element, callback) { | ||
@@ -506,12 +373,534 @@ var OBJECT_STYLE = "display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;"; | ||
return { | ||
isDetectable: isDetectable, | ||
makeDetectable: makeDetectable, | ||
addListener: addListener, | ||
addListener: addListener | ||
}; | ||
}; | ||
},{"./browser-detector":3,"./collection-utils":4}],7:[function(require,module,exports){ | ||
},{"../browser-detector":3,"../collection-utils":4}],6:[function(require,module,exports){ | ||
/** | ||
* Resize detection strategy that injects divs to elements in order to detect resize events on scroll events. | ||
* Heavily inspired by: https://github.com/marcj/css-element-queries/blob/master/src/ResizeSensor.js | ||
*/ | ||
"use strict"; | ||
var batchUpdaterMaker = require("batch-updater"); | ||
module.exports = function(options) { | ||
options = options || {}; | ||
var idHandler = options.idHandler; | ||
var reporter = options.reporter; | ||
var batchUpdater = options.batchUpdater; | ||
//TODO: This is ugly. The batch updator should support leveled batches or something similar. | ||
var testBatchUpdater = batchUpdaterMaker({ | ||
reporter: reporter | ||
}); | ||
var testBatchUpdater2 = batchUpdaterMaker({ | ||
reporter: reporter | ||
}); | ||
//TODO: This should probably be DI, or atleast the maker function so that other frameworks can share the batch-updater code. It might not make sense to share a batch updater, since batches can interfere with each other. | ||
var scrollbarsBatchUpdater = batchUpdaterMaker({ | ||
reporter: reporter | ||
}); | ||
if(!idHandler) { | ||
throw new Error("Missing required dependency: idHandler."); | ||
} | ||
if(!reporter) { | ||
throw new Error("Missing required dependency: reporter."); | ||
} | ||
/** | ||
* Adds a resize event listener to the element. | ||
* @public | ||
* @param {element} element The element that should have the listener added. | ||
* @param {function} listener The listener callback to be called for each resize event of the element. The element will be given as a parameter to the listener callback. | ||
*/ | ||
function addListener(element, listener) { | ||
var changed = function() { | ||
var elementStyle = getComputedStyle(element); | ||
var width = parseSize(elementStyle.width); | ||
var height = parseSize(elementStyle.height); | ||
var id = idHandler.get(element); | ||
testBatchUpdater.update(id, function updateDetectorElements() { | ||
updateChildSizes(element, width, height); | ||
storeCurrentSize(element, width, height); | ||
testBatchUpdater2.update(id, function updateScrollbars() { | ||
positionScrollbars(element, width, height); | ||
listener(element); | ||
}); | ||
}); | ||
}; | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
addEvent(expand, "scroll", function onExpand() { | ||
var style = getComputedStyle(element); | ||
var width = parseSize(style.width); | ||
var height = parseSize(style.height); | ||
if (width > element.lastWidth || height > element.lastHeight) { | ||
changed(); | ||
} | ||
}); | ||
addEvent(shrink, "scroll", function onShrink() { | ||
var style = getComputedStyle(element); | ||
var width = parseSize(style.width); | ||
var height = parseSize(style.height); | ||
if (width < element.lastWidth || height < element.lastHeight) { | ||
changed(); | ||
} | ||
}); | ||
} | ||
/** | ||
* Makes an element detectable and ready to be listened for resize events. Will call the callback when the element is ready to be listened for resize changes. | ||
* @private | ||
* @param {element} element The element to make detectable | ||
* @param {function} callback The callback to be called when the element is ready to be listened for resize changes. Will be called with the element as first parameter. | ||
*/ | ||
function makeDetectable(element, callback) { | ||
var elementStyle = getComputedStyle(element); | ||
var width = parseSize(elementStyle.width); | ||
var height = parseSize(elementStyle.height); | ||
function mutateDom() { | ||
if(elementStyle.position === "static") { | ||
element.style.position = "relative"; | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var value = elementStyle[property]; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, elementStyle, "top"); | ||
removeRelativeStyles(reporter, element, elementStyle, "right"); | ||
removeRelativeStyles(reporter, element, elementStyle, "bottom"); | ||
removeRelativeStyles(reporter, element, elementStyle, "left"); | ||
} | ||
function getContainerCssText(left, top) { | ||
left = (!left ? "0" : (left + "px")); | ||
top = (!top ? "0" : (top + "px")); | ||
return "position: absolute; left: " + left + "; top: " + top + "; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"; | ||
} | ||
var readyExpandScroll = false; | ||
var readyShrinkScroll = false; | ||
var readyOverall = false; | ||
function ready() { | ||
if(readyExpandScroll && readyShrinkScroll && readyOverall) { | ||
callback(element); | ||
} | ||
} | ||
var containerStyle = getContainerCssText(-1, -1); | ||
var shrinkExpandstyle = getContainerCssText(0, 0); | ||
var shrinkExpandChildStyle = "position: absolute; left: 0; top: 0;"; | ||
var container = document.createElement("div"); | ||
var expand = document.createElement("div"); | ||
var expandChild = document.createElement("div"); | ||
var shrink = document.createElement("div"); | ||
var shrinkChild = document.createElement("div"); | ||
container.style.cssText = containerStyle; | ||
expand.style.cssText = shrinkExpandstyle; | ||
expandChild.style.cssText = shrinkExpandChildStyle; | ||
shrink.style.cssText = shrinkExpandstyle; | ||
shrinkChild.style.cssText = shrinkExpandChildStyle + " width: 200%; height: 200%;"; | ||
expand.appendChild(expandChild); | ||
shrink.appendChild(shrinkChild); | ||
container.appendChild(expand); | ||
container.appendChild(shrink); | ||
element.appendChild(container); | ||
element._erdElement = container; | ||
addEvent(expand, "scroll", function onFirstExpandScroll() { | ||
removeEvent(expand, "scroll", onFirstExpandScroll); | ||
readyExpandScroll = true; | ||
ready(); | ||
}); | ||
addEvent(shrink, "scroll", function onFirstShrinkScroll() { | ||
removeEvent(shrink, "scroll", onFirstShrinkScroll); | ||
readyShrinkScroll = true; | ||
ready(); | ||
}); | ||
updateChildSizes(element, width, height); | ||
scrollbarsBatchUpdater.update(id, function finalize() { | ||
storeCurrentSize(element, width, height); | ||
positionScrollbars(element, width, height); | ||
readyOverall = true; | ||
ready(); | ||
}); | ||
} | ||
var id = idHandler.get(element); | ||
if(batchUpdater) { | ||
batchUpdater.update(id, mutateDom); | ||
} else { | ||
mutateDom(); | ||
} | ||
} | ||
function getExpandElement(element) { | ||
return element._erdElement.childNodes[0]; | ||
} | ||
function getExpandChildElement(element) { | ||
return getExpandElement(element).childNodes[0]; | ||
} | ||
function getShrinkElement(element) { | ||
return element._erdElement.childNodes[1]; | ||
} | ||
function getExpandSize(size) { | ||
return size + 10; | ||
} | ||
function getShrinkSize(size) { | ||
return size * 2; | ||
} | ||
function updateChildSizes(element, width, height) { | ||
var expandChild = getExpandChildElement(element); | ||
var expandWidth = getExpandSize(width); | ||
var expandHeight = getExpandSize(height); | ||
expandChild.style.width = expandWidth + "px"; | ||
expandChild.style.height = expandHeight + "px"; | ||
} | ||
function storeCurrentSize(element, width, height) { | ||
element.lastWidth = width; | ||
element.lastHeight = height; | ||
} | ||
function positionScrollbars(element, width, height) { | ||
var expand = getExpandElement(element); | ||
var shrink = getShrinkElement(element); | ||
var expandWidth = getExpandSize(width); | ||
var expandHeight = getExpandSize(height); | ||
var shrinkWidth = getShrinkSize(width); | ||
var shrinkHeight = getShrinkSize(height); | ||
expand.scrollLeft = expandWidth; | ||
expand.scrollTop = expandHeight; | ||
shrink.scrollLeft = shrinkWidth; | ||
shrink.scrollTop = shrinkHeight; | ||
} | ||
function addEvent(el, name, cb) { | ||
if (el.attachEvent) { | ||
el.attachEvent("on" + name, cb); | ||
} else { | ||
el.addEventListener(name, cb); | ||
} | ||
} | ||
function removeEvent(el, name, cb) { | ||
if(el.attachEvent) { | ||
el.detachEvent("on" + name, cb); | ||
} else { | ||
el.removeEventListener(name, cb); | ||
} | ||
} | ||
return { | ||
makeDetectable: makeDetectable, | ||
addListener: addListener | ||
}; | ||
}; | ||
function parseSize(size) { | ||
return parseFloat(size.replace(/px/, "")); | ||
} | ||
},{"batch-updater":1}],7:[function(require,module,exports){ | ||
//Heavily inspired by http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ | ||
"use strict"; | ||
var forEach = require("./collection-utils").forEach; | ||
var elementUtilsMaker = require("./element-utils"); | ||
var listenerHandlerMaker = require("./listener-handler"); | ||
var idGeneratorMaker = require("./id-generator"); | ||
var idHandlerMaker = require("./id-handler"); | ||
var reporterMaker = require("./reporter"); | ||
var batchUpdaterMaker = require("batch-updater"); | ||
//Detection strategies. | ||
var objectStrategyMaker = require("./detection-strategy/object.js"); | ||
var scrollStrategyMaker = require("./detection-strategy/scroll.js"); | ||
/** | ||
* @typedef idHandler | ||
* @type {object} | ||
* @property {function} get Gets the resize detector id of the element. | ||
* @property {function} set Generate and sets the resize detector id of the element. | ||
*/ | ||
/** | ||
* @typedef Options | ||
* @type {object} | ||
* @property {boolean} callOnAdd Determines if listeners should be called when they are getting added. | ||
Default is true. If true, the listener is guaranteed to be called when it has been added. | ||
If false, the listener will not be guarenteed to be called when it has been added (does not prevent it from being called). | ||
* @property {idHandler} idHandler A custom id handler that is responsible for generating, setting and retrieving id's for elements. | ||
If not provided, a default id handler will be used. | ||
* @property {reporter} reporter A custom reporter that handles reporting logs, warnings and errors. | ||
If not provided, a default id handler will be used. | ||
If set to false, then nothing will be reported. | ||
*/ | ||
/** | ||
* Creates an element resize detector instance. | ||
* @public | ||
* @param {Options?} options Optional global options object that will decide how this instance will work. | ||
*/ | ||
module.exports = function(options) { | ||
options = options || {}; | ||
//idHandler is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var idHandler = options.idHandler; | ||
if(!idHandler) { | ||
var idGenerator = idGeneratorMaker(); | ||
var defaultIdHandler = idHandlerMaker(idGenerator); | ||
idHandler = defaultIdHandler; | ||
} | ||
//reporter is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var reporter = options.reporter; | ||
if(!reporter) { | ||
//If options.reporter is false, then the reporter should be quiet. | ||
var quiet = reporter === false; | ||
reporter = reporterMaker(quiet); | ||
} | ||
//batchUpdater is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var batchUpdater = getOption(options, "batchUpdater", batchUpdaterMaker({ reporter: reporter })); | ||
//Options to be used as default for the listenTo function. | ||
var globalOptions = {}; | ||
globalOptions.callOnAdd = !!getOption(options, "callOnAdd", true); | ||
var eventListenerHandler = listenerHandlerMaker(idHandler); | ||
var elementUtils = elementUtilsMaker(); | ||
//The detection strategy to be used. | ||
var detectionStrategy; | ||
var desiredStrategy = getOption(options, "strategy", "object"); | ||
var strategyOptions = { | ||
idHandler: idHandler, | ||
reporter: reporter, | ||
batchUpdater: batchUpdater | ||
}; | ||
if(desiredStrategy === "scroll") { | ||
detectionStrategy = scrollStrategyMaker(strategyOptions); | ||
} else if(desiredStrategy === "object") { | ||
detectionStrategy = objectStrategyMaker(strategyOptions); | ||
} else { | ||
throw new Error("Invalid strategy name: " + desiredStrategy); | ||
} | ||
//Calls can be made to listenTo with elements that are still are being installed. | ||
//Also, same elements can occur in the elements list in the listenTo function. | ||
//With this map, the ready callbacks can be synchronized between the calls | ||
//so that the ready callback can always be called when an element is ready - even if | ||
//it wasn't installed from the function intself. | ||
var onReadyCallbacks = {}; | ||
/** | ||
* Makes the given elements resize-detectable and starts listening to resize events on the elements. Calls the event callback for each event for each element. | ||
* @public | ||
* @param {Options?} options Optional options object. These options will override the global options. Some options may not be overriden, such as idHandler. | ||
* @param {element[]|element} elements The given array of elements to detect resize events of. Single element is also valid. | ||
* @param {function} listener The callback to be executed for each resize event for each element. | ||
*/ | ||
function listenTo(options, elements, listener) { | ||
function onResizeCallback(element) { | ||
var listeners = eventListenerHandler.get(element); | ||
forEach(listeners, function callListenerProxy(listener) { | ||
listener(element); | ||
}); | ||
} | ||
function addListener(callOnAdd, element, listener) { | ||
eventListenerHandler.add(element, listener); | ||
if(callOnAdd) { | ||
listener(element); | ||
} | ||
} | ||
//Options object may be omitted. | ||
if(!listener) { | ||
listener = elements; | ||
elements = options; | ||
options = {}; | ||
} | ||
if(!elements) { | ||
throw new Error("At least one element required."); | ||
} | ||
if(!listener) { | ||
throw new Error("Listener required."); | ||
} | ||
if(elements.length === undefined) { | ||
elements = [elements]; | ||
} | ||
var elementsReady = 0; | ||
var callOnAdd = getOption(options, "callOnAdd", globalOptions.callOnAdd); | ||
var onReadyCallback = getOption(options, "onReady", function noop() {}); | ||
forEach(elements, function attachListenerToElement(element) { | ||
if(!elementUtils.isDetectable(element)) { | ||
if(elementUtils.isBusy(element)) { | ||
//The element is being prepared to be detectable. Do not make it detectable. | ||
//Just add the listener, because the element will soon be detectable. | ||
addListener(callOnAdd, element, listener); | ||
var id = idHandler.get(element); | ||
onReadyCallbacks[id] = onReadyCallbacks[id] || []; | ||
onReadyCallbacks[id].push(function onReady() { | ||
elementsReady++; | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
}); | ||
return; | ||
} | ||
//The element is not prepared to be detectable, so do prepare it and add a listener to it. | ||
elementUtils.markBusy(element, true); | ||
return detectionStrategy.makeDetectable(element, function onElementDetectable(element) { | ||
elementUtils.markAsDetectable(element); | ||
elementUtils.markBusy(element, false); | ||
detectionStrategy.addListener(element, onResizeCallback); | ||
addListener(callOnAdd, element, listener); | ||
elementsReady++; | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
var id = idHandler.get(element); | ||
if(onReadyCallbacks[id]) { | ||
forEach(onReadyCallbacks[id], function(callback) { | ||
callback(); | ||
}); | ||
delete onReadyCallbacks[id]; | ||
} | ||
}); | ||
} | ||
//The element has been prepared to be detectable and is ready to be listened to. | ||
addListener(callOnAdd, element, listener); | ||
elementsReady++; | ||
}); | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
} | ||
return { | ||
listenTo: listenTo | ||
}; | ||
}; | ||
function getOption(options, name, defaultValue) { | ||
var value = options[name]; | ||
if((value === undefined || value === null) && defaultValue !== undefined) { | ||
return defaultValue; | ||
} | ||
return value; | ||
} | ||
},{"./collection-utils":4,"./detection-strategy/object.js":5,"./detection-strategy/scroll.js":6,"./element-utils":8,"./id-generator":9,"./id-handler":10,"./listener-handler":11,"./reporter":12,"batch-updater":1}],8:[function(require,module,exports){ | ||
"use strict"; | ||
module.exports = function() { | ||
/** | ||
* Tells if the element has been made detectable and ready to be listened for resize events. | ||
* @public | ||
* @param {element} The element to check. | ||
* @returns {boolean} True or false depending on if the element is detectable or not. | ||
*/ | ||
function isDetectable(element) { | ||
return !!element._erdIsDetectable; | ||
} | ||
/** | ||
* Marks the element that it has been made detectable and ready to be listened for resize events. | ||
* @public | ||
* @param {element} The element to mark. | ||
*/ | ||
function markAsDetectable(element) { | ||
element._erdIsDetectable = true; | ||
} | ||
/** | ||
* Tells if the element is busy or not. | ||
* @public | ||
* @param {element} The element to check. | ||
* @returns {boolean} True or false depending on if the element is busy or not. | ||
*/ | ||
function isBusy(element) { | ||
return !!element._erdBusy; | ||
} | ||
/** | ||
* Marks the object is busy and should not be made detectable. | ||
* @public | ||
* @param {element} element The element to mark. | ||
* @param {boolean} busy If the element is busy or not. | ||
*/ | ||
function markBusy(element, busy) { | ||
element._erdBusy = !!busy; | ||
} | ||
return { | ||
isDetectable: isDetectable, | ||
markAsDetectable: markAsDetectable, | ||
isBusy: isBusy, | ||
markBusy: markBusy | ||
}; | ||
}; | ||
},{}],9:[function(require,module,exports){ | ||
"use strict"; | ||
module.exports = function() { | ||
var idCount = 1; | ||
@@ -533,3 +922,3 @@ | ||
},{}],8:[function(require,module,exports){ | ||
},{}],10:[function(require,module,exports){ | ||
"use strict"; | ||
@@ -572,3 +961,3 @@ | ||
},{}],9:[function(require,module,exports){ | ||
},{}],11:[function(require,module,exports){ | ||
"use strict"; | ||
@@ -611,3 +1000,3 @@ | ||
},{}],10:[function(require,module,exports){ | ||
},{}],12:[function(require,module,exports){ | ||
"use strict"; | ||
@@ -649,3 +1038,3 @@ | ||
}; | ||
},{}]},{},[5])(5) | ||
},{}]},{},[7])(7) | ||
}); |
/*! | ||
* element-resize-detector 0.2.7 (2015-04-08, 14:28) | ||
* element-resize-detector 0.2.8 (2015-04-12, 18:14) | ||
* https://github.com/wnr/element-resize-detector | ||
@@ -7,2 +7,2 @@ * Licensed under MIT | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.elementResizeDetectorMaker=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){"use strict";var c=a("./utils");b.exports=function(a){function b(a,b){l&&k&&0===n&&f(),o[a]||(o[a]=[]),o[a].push(b),n++}function d(a){void 0===a&&(a=k),m&&(h(m),m=null),k?f():e()}function e(){for(var a in o)if(o.hasOwnProperty(a))for(var b=o[a],c=0;c<b.length;c++){var d=b[c];d()}g()}function f(){m=i(function(){e()})}function g(){n=0,o={}}function h(a){var b=window.clearTimeout;return b(a)}function i(a){var b=function(a){return window.setTimeout(a,0)};return b(a)}a=a||{};var j=a.reporter,k=c.getOption(a,"async",!0),l=c.getOption(a,"auto",!0);if(l&&!k&&(j.warn("Invalid options combination. auto=true and async=false is invalid. Setting async=true."),k=!0),!j)throw new Error("Reporter required.");var m,n=0,o={};return{update:b,force:d}}},{"./utils":2}],2:[function(a,b){"use strict";function c(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var d=b.exports={};d.getOption=c},{}],3:[function(a,b){"use strict";var c=b.exports={};c.isIE=function(a){function b(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("msie")||-1!==a.indexOf("trident")}if(!b())return!1;if(!a)return!0;var c=function(){var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");do c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->";while(d[0]);return b>4?b:a}();return a===c}},{}],4:[function(a,b){"use strict";var c=b.exports={};c.forEach=function(a,b){for(var c=0;c<a.length;c++){var d=b(a[c]);if(d)return d}}},{}],5:[function(a,b){"use strict";function c(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var d=a("./collection-utils").forEach,e=a("./element-utils"),f=a("./listener-handler"),g=a("./id-generator"),h=a("./id-handler"),i=a("./reporter"),j=a("batch-updater");b.exports=function(a){function b(a,b,e){function f(a){var b=q.get(a);d(b,function(b){b(a)})}function g(a,b,c){q.add(b,c),a&&c(b)}if(e||(e=b,b=a,a={}),!b)throw new Error("At least one element required.");if(!e)throw new Error("Listener required.");void 0===b.length&&(b=[b]);var h=c(a,"callOnAdd",p.callOnAdd),i=c(a,"batchUpdater",p.batchUpdater);d(b,function(a){return r.isDetectable(a)?void g(h,a,e):r.makeDetectable(i,n,a,function(a){r.addListener(a,f),g(h,a,e)})})}a=a||{};var k=a.idHandler;if(!k){var l=g(),m=h(l);k=m}var n=a.reporter;if(!n){var o=n===!1;n=i(o)}var p={};p.callOnAdd=!!c(a,"callOnAdd",!0),p.batchUpdater=c(a,"batchUpdater",j({reporter:n}));var q=f(k),r=e(k);return{listenTo:b}}},{"./collection-utils":4,"./element-utils":6,"./id-generator":7,"./id-handler":8,"./listener-handler":9,"./reporter":10,"batch-updater":1}],6:[function(a,b){"use strict";var c=a("./collection-utils").forEach,d=a("./browser-detector");b.exports=function(a){function b(b){return d.isIE(8)?!!a.get(b):!!g(b)}function e(a,c){function e(){c(a)}if(!b(a))throw new Error("Element is not detectable.");if(d.isIE(8))a.attachEvent("onresize",e);else{var f=g(a);f.contentDocument.defaultView.addEventListener("resize",e)}}function f(b,c,e,f){function g(a,e,f){function g(){function a(b,c){return b.contentDocument?void c(b.contentDocument):void setTimeout(function(){a(b,c)},100)}var b=this;a(b,function(a){var b=a.createElement("style");b.innerHTML="html, body { margin: 0; padding: 0 } div { -webkit-transition: opacity 0.01s; -ms-transition: opacity 0.01s; -o-transition: opacity 0.01s; transition: opacity 0.01s; opacity: 0; }",a.head.appendChild(b),f(e)})}function h(){if("static"===k){e.style.position="relative";var b=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};b(c,e,j,"top"),b(c,e,j,"right"),b(c,e,j,"bottom"),b(c,e,j,"left")}var f=document.createElement("object");f.style.cssText=i,f.type="text/html",f.onload=g,f._erdObjectId=a,d.isIE()||(f.data="about:blank"),e.appendChild(f),d.isIE()&&(f.data="about:blank")}var i="display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;",j=getComputedStyle(e),k=j.position;b?b.update(a,h):h()}var h=a.get(e);d.isIE(8)?f(e):g(h,e,f)}function g(a){return c(a.children,function(a){return void 0!==a._erdObjectId&&null!==a._erdObjectId?a:void 0})}return{isDetectable:b,makeDetectable:f,addListener:e}}},{"./browser-detector":3,"./collection-utils":4}],7:[function(a,b){"use strict";b.exports=function(){function a(){return b++}var b=1;return{generate:a}}},{}],8:[function(a,b){"use strict";b.exports=function(a){function b(a,b){return b||d(a)||c(a),a[e]}function c(b){var c=a.generate();return b[e]=c,c}function d(a){return void 0!==a[e]}var e="_erdTargetId";return{get:b}}},{}],9:[function(a,b){"use strict";b.exports=function(a){function b(b){return d[a.get(b)]}function c(b,c){var e=a.get(b);d[e]||(d[e]=[]),d[e].push(c)}var d={};return{get:b,add:c}}},{}],10:[function(a,b){"use strict";b.exports=function(a){function b(){}var c={log:b,warn:b,error:b};if(!a&&window.console){var d=function(a,b){a[b]=function(){console[b].apply(console,arguments)}};d(c,"log"),d(c,"warn"),d(c,"error")}return c}},{}]},{},[5])(5)}); | ||
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.elementResizeDetectorMaker=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){"use strict";var c=a("./utils");b.exports=function(a){function b(a,b){m&&l&&0===o&&f(),p[a]||(p[a]=[]),p[a].push(b),o++}function d(a){void 0===a&&(a=l),n&&(h(n),n=null),l?f():e()}function e(){for(var a in p)if(p.hasOwnProperty(a))for(var b=p[a],c=0;c<b.length;c++){var d=b[c];d()}g(),q()}function f(){n=i(function(){e()})}function g(){o=0,p={}}function h(a){var b=window.clearTimeout;return b(a)}function i(a){var b=function(a){return window.setTimeout(a,0)};return b(a)}function j(a){q=a||function(){}}a=a||{};var k=a.reporter,l=c.getOption(a,"async",!0),m=c.getOption(a,"auto",!0);if(m&&!l&&(k.warn("Invalid options combination. auto=true and async=false is invalid. Setting async=true."),l=!0),!k)throw new Error("Reporter required.");var n,o=0,p={},q=function(){};return{update:b,force:d,onProcessed:j}}},{"./utils":2}],2:[function(a,b){"use strict";function c(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var d=b.exports={};d.getOption=c},{}],3:[function(a,b){"use strict";var c=b.exports={};c.isIE=function(a){function b(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("msie")||-1!==a.indexOf("trident")}if(!b())return!1;if(!a)return!0;var c=function(){var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");do c.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->";while(d[0]);return b>4?b:a}();return a===c}},{}],4:[function(a,b){"use strict";var c=b.exports={};c.forEach=function(a,b){for(var c=0;c<a.length;c++){var d=b(a[c]);if(d)return d}}},{}],5:[function(a,b){"use strict";var c=a("../collection-utils").forEach,d=a("../browser-detector");b.exports=function(a){function b(a,b){function c(){b(a)}if(!f(a))throw new Error("Element is not detectable by this strategy.");if(d.isIE(8))a.attachEvent("onresize",c);else{var e=f(a);e.contentDocument.defaultView.addEventListener("resize",c)}}function e(a,b){function c(a,b,c){function e(){function a(b,c){return b.contentDocument?void c(b.contentDocument):void setTimeout(function(){a(b,c)},100)}var d=this;a(d,function(a){var d=a.createElement("style");d.innerHTML="html, body { margin: 0; padding: 0 } div { -webkit-transition: opacity 0.01s; -ms-transition: opacity 0.01s; -o-transition: opacity 0.01s; transition: opacity 0.01s; opacity: 0; }",a.head.appendChild(d),c(b)})}function f(){if("static"===k){b.style.position="relative";var c=function(a,b,c,d){function e(a){return a.replace(/[^-\d\.]/g,"")}var f=c[d];"auto"!==f&&"0"!==e(f)&&(a.warn("An element that is positioned static has style."+d+"="+f+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};c(h,b,j,"top"),c(h,b,j,"right"),c(h,b,j,"bottom"),c(h,b,j,"left")}var f=document.createElement("object");f.style.cssText=g,f.type="text/html",f.onload=e,f._erdObjectId=a,d.isIE()||(f.data="about:blank"),b.appendChild(f),d.isIE()&&(f.data="about:blank")}var g="display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;",j=getComputedStyle(b),k=j.position;i?i.update(a,f):f()}var e=g.get(a);d.isIE(8)?b(a):c(e,a,b)}function f(a){return c(a.children,function(a){return void 0!==a._erdObjectId&&null!==a._erdObjectId?a:void 0})}a=a||{};var g=a.idHandler,h=a.reporter,i=a.batchUpdater;if(!g)throw new Error("Missing required dependency: idHandler.");if(!h)throw new Error("Missing required dependency: reporter.");return{makeDetectable:e,addListener:b}}},{"../browser-detector":3,"../collection-utils":4}],6:[function(a,b){"use strict";function c(a){return parseFloat(a.replace(/px/,""))}var d=a("batch-updater");b.exports=function(a){function b(a,b){var d=function(){var d=getComputedStyle(a),e=c(d.width),f=c(d.height),g=p.get(a);s.update(g,function(){k(a,e,f),l(a,e,f),t.update(g,function(){m(a,e,f),b(a)})})},e=f(a),g=h(a);n(e,"scroll",function(){var b=getComputedStyle(a),e=c(b.width),f=c(b.height);(e>a.lastWidth||f>a.lastHeight)&&d()}),n(g,"scroll",function(){var b=getComputedStyle(a),e=c(b.width),f=c(b.height);(e<a.lastWidth||f<a.lastHeight)&&d()})}function e(a,b){function d(){function c(a,b){return a=a?a+"px":"0",b=b?b+"px":"0","position: absolute; left: "+a+"; top: "+b+"; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"}function d(){j&&p&&r&&b(a)}if("static"===e.position){a.style.position="relative";var i=function(a,b,c,d){function f(a){return a.replace(/[^-\d\.]/g,"")}var g=e[d];"auto"!==g&&"0"!==f(g)&&(a.warn("An element that is positioned static has style."+d+"="+g+" which is ignored due to the static positioning. The element will need to be positioned relative, so the style."+d+" will be set to 0. Element: ",b),b.style[d]=0)};i(q,a,e,"top"),i(q,a,e,"right"),i(q,a,e,"bottom"),i(q,a,e,"left")}var j=!1,p=!1,r=!1,s=c(-1,-1),t=c(0,0),v="position: absolute; left: 0; top: 0;",w=document.createElement("div"),x=document.createElement("div"),y=document.createElement("div"),z=document.createElement("div"),A=document.createElement("div");w.style.cssText=s,x.style.cssText=t,y.style.cssText=v,z.style.cssText=t,A.style.cssText=v+" width: 200%; height: 200%;",x.appendChild(y),z.appendChild(A),w.appendChild(x),w.appendChild(z),a.appendChild(w),a._erdElement=w,n(x,"scroll",function B(){o(x,"scroll",B),j=!0,d()}),n(z,"scroll",function C(){o(z,"scroll",C),p=!0,d()}),k(a,f,g),u.update(h,function(){l(a,f,g),m(a,f,g),r=!0,d()})}var e=getComputedStyle(a),f=c(e.width),g=c(e.height),h=p.get(a);r?r.update(h,d):d()}function f(a){return a._erdElement.childNodes[0]}function g(a){return f(a).childNodes[0]}function h(a){return a._erdElement.childNodes[1]}function i(a){return a+10}function j(a){return 2*a}function k(a,b,c){var d=g(a),e=i(b),f=i(c);d.style.width=e+"px",d.style.height=f+"px"}function l(a,b,c){a.lastWidth=b,a.lastHeight=c}function m(a,b,c){var d=f(a),e=h(a),g=i(b),k=i(c),l=j(b),m=j(c);d.scrollLeft=g,d.scrollTop=k,e.scrollLeft=l,e.scrollTop=m}function n(a,b,c){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener(b,c)}function o(a,b,c){a.attachEvent?a.detachEvent("on"+b,c):a.removeEventListener(b,c)}a=a||{};var p=a.idHandler,q=a.reporter,r=a.batchUpdater,s=d({reporter:q}),t=d({reporter:q}),u=d({reporter:q});if(!p)throw new Error("Missing required dependency: idHandler.");if(!q)throw new Error("Missing required dependency: reporter.");return{makeDetectable:e,addListener:b}}},{"batch-updater":1}],7:[function(a,b){"use strict";function c(a,b,c){var d=a[b];return void 0!==d&&null!==d||void 0===c?d:c}var d=a("./collection-utils").forEach,e=a("./element-utils"),f=a("./listener-handler"),g=a("./id-generator"),h=a("./id-handler"),i=a("./reporter"),j=a("batch-updater"),k=a("./detection-strategy/object.js"),l=a("./detection-strategy/scroll.js");b.exports=function(a){function b(a,b,e){function f(a){var b=u.get(a);d(b,function(b){b(a)})}function g(a,b,c){u.add(b,c),a&&c(b)}if(e||(e=b,b=a,a={}),!b)throw new Error("At least one element required.");if(!e)throw new Error("Listener required.");void 0===b.length&&(b=[b]);var h=0,i=c(a,"callOnAdd",s.callOnAdd),j=c(a,"onReady",function(){});d(b,function(a){if(!v.isDetectable(a)){if(v.isBusy(a)){g(i,a,e);var c=m.get(a);return y[c]=y[c]||[],void y[c].push(function(){h++,h===b.length&&j()})}return v.markBusy(a,!0),t.makeDetectable(a,function(a){v.markAsDetectable(a),v.markBusy(a,!1),t.addListener(a,f),g(i,a,e),h++,h===b.length&&j();var c=m.get(a);y[c]&&(d(y[c],function(a){a()}),delete y[c])})}g(i,a,e),h++}),h===b.length&&j()}a=a||{};var m=a.idHandler;if(!m){var n=g(),o=h(n);m=o}var p=a.reporter;if(!p){var q=p===!1;p=i(q)}var r=c(a,"batchUpdater",j({reporter:p})),s={};s.callOnAdd=!!c(a,"callOnAdd",!0);var t,u=f(m),v=e(),w=c(a,"strategy","object"),x={idHandler:m,reporter:p,batchUpdater:r};if("scroll"===w)t=l(x);else{if("object"!==w)throw new Error("Invalid strategy name: "+w);t=k(x)}var y={};return{listenTo:b}}},{"./collection-utils":4,"./detection-strategy/object.js":5,"./detection-strategy/scroll.js":6,"./element-utils":8,"./id-generator":9,"./id-handler":10,"./listener-handler":11,"./reporter":12,"batch-updater":1}],8:[function(a,b){"use strict";b.exports=function(){function a(a){return!!a._erdIsDetectable}function b(a){a._erdIsDetectable=!0}function c(a){return!!a._erdBusy}function d(a,b){a._erdBusy=!!b}return{isDetectable:a,markAsDetectable:b,isBusy:c,markBusy:d}}},{}],9:[function(a,b){"use strict";b.exports=function(){function a(){return b++}var b=1;return{generate:a}}},{}],10:[function(a,b){"use strict";b.exports=function(a){function b(a,b){return b||d(a)||c(a),a[e]}function c(b){var c=a.generate();return b[e]=c,c}function d(a){return void 0!==a[e]}var e="_erdTargetId";return{get:b}}},{}],11:[function(a,b){"use strict";b.exports=function(a){function b(b){return d[a.get(b)]}function c(b,c){var e=a.get(b);d[e]||(d[e]=[]),d[e].push(c)}var d={};return{get:b,add:c}}},{}],12:[function(a,b){"use strict";b.exports=function(a){function b(){}var c={log:b,warn:b,error:b};if(!a&&window.console){var d=function(a,b){a[b]=function(){console[b].apply(console,arguments)}};d(c,"log"),d(c,"warn"),d(c,"error")}return c}},{}]},{},[7])(7)}); |
{ | ||
"name": "element-resize-detector", | ||
"version": "0.2.8", | ||
"version": "0.3.0", | ||
"description": "resize event emitter for elements.", | ||
@@ -14,5 +14,6 @@ "homepage": "https://github.com/wnr/element-resize-detector", | ||
"dependencies": { | ||
"batch-updater": "^0.1.0" | ||
"batch-processor": "^0.2.0" | ||
}, | ||
"devDependencies": { | ||
"benchmark": "^1.0.0", | ||
"grunt": "^0.4.5", | ||
@@ -19,0 +20,0 @@ "grunt-banner": "^0.3.1", |
@@ -5,10 +5,14 @@ //Heavily inspired by http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/ | ||
var forEach = require("./collection-utils").forEach; | ||
var elementUtilsMaker = require("./element-utils"); | ||
var listenerHandlerMaker = require("./listener-handler"); | ||
var idGeneratorMaker = require("./id-generator"); | ||
var idHandlerMaker = require("./id-handler"); | ||
var reporterMaker = require("./reporter"); | ||
var batchUpdater = require("batch-updater"); | ||
var forEach = require("./collection-utils").forEach; | ||
var elementUtilsMaker = require("./element-utils"); | ||
var listenerHandlerMaker = require("./listener-handler"); | ||
var idGeneratorMaker = require("./id-generator"); | ||
var idHandlerMaker = require("./id-handler"); | ||
var reporterMaker = require("./reporter"); | ||
var batchProcessorMaker = require("batch-processor"); | ||
//Detection strategies. | ||
var objectStrategyMaker = require("./detection-strategy/object.js"); | ||
var scrollStrategyMaker = require("./detection-strategy/scroll.js"); | ||
/** | ||
@@ -60,10 +64,36 @@ * @typedef idHandler | ||
//batchProcessor is currently not an option to the listenTo function, so it should not be added to globalOptions. | ||
var batchProcessor = getOption(options, "batchProcessor", batchProcessorMaker({ reporter: reporter })); | ||
//Options to be used as default for the listenTo function. | ||
var globalOptions = {}; | ||
globalOptions.callOnAdd = !!getOption(options, "callOnAdd", true); | ||
globalOptions.batchUpdater = getOption(options, "batchUpdater", batchUpdater({ reporter: reporter })); | ||
var eventListenerHandler = listenerHandlerMaker(idHandler); | ||
var elementUtils = elementUtilsMaker(idHandler); | ||
var eventListenerHandler = listenerHandlerMaker(idHandler); | ||
var elementUtils = elementUtilsMaker(); | ||
//The detection strategy to be used. | ||
var detectionStrategy; | ||
var desiredStrategy = getOption(options, "strategy", "object"); | ||
var strategyOptions = { | ||
idHandler: idHandler, | ||
reporter: reporter, | ||
batchProcessor: batchProcessor | ||
}; | ||
if(desiredStrategy === "scroll") { | ||
detectionStrategy = scrollStrategyMaker(strategyOptions); | ||
} else if(desiredStrategy === "object") { | ||
detectionStrategy = objectStrategyMaker(strategyOptions); | ||
} else { | ||
throw new Error("Invalid strategy name: " + desiredStrategy); | ||
} | ||
//Calls can be made to listenTo with elements that are still are being installed. | ||
//Also, same elements can occur in the elements list in the listenTo function. | ||
//With this map, the ready callbacks can be synchronized between the calls | ||
//so that the ready callback can always be called when an element is ready - even if | ||
//it wasn't installed from the function intself. | ||
var onReadyCallbacks = {}; | ||
/** | ||
@@ -85,3 +115,3 @@ * Makes the given elements resize-detectable and starts listening to resize events on the elements. Calls the event callback for each event for each element. | ||
function onElementReadyToAddListener(callOnAdd, element, listener) { | ||
function addListener(callOnAdd, element, listener) { | ||
eventListenerHandler.add(element, listener); | ||
@@ -113,11 +143,45 @@ | ||
var callOnAdd = getOption(options, "callOnAdd", globalOptions.callOnAdd); | ||
var batchUpdater = getOption(options, "batchUpdater", globalOptions.batchUpdater); | ||
var elementsReady = 0; | ||
var callOnAdd = getOption(options, "callOnAdd", globalOptions.callOnAdd); | ||
var onReadyCallback = getOption(options, "onReady", function noop() {}); | ||
forEach(elements, function attachListenerToElement(element) { | ||
if(!elementUtils.isDetectable(element)) { | ||
if(elementUtils.isBusy(element)) { | ||
//The element is being prepared to be detectable. Do not make it detectable. | ||
//Just add the listener, because the element will soon be detectable. | ||
addListener(callOnAdd, element, listener); | ||
var id = idHandler.get(element); | ||
onReadyCallbacks[id] = onReadyCallbacks[id] || []; | ||
onReadyCallbacks[id].push(function onReady() { | ||
elementsReady++; | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
}); | ||
return; | ||
} | ||
//The element is not prepared to be detectable, so do prepare it and add a listener to it. | ||
return elementUtils.makeDetectable(batchUpdater, reporter, element, function onElementDetectable(element) { | ||
elementUtils.addListener(element, onResizeCallback); | ||
onElementReadyToAddListener(callOnAdd, element, listener); | ||
elementUtils.markBusy(element, true); | ||
return detectionStrategy.makeDetectable(element, function onElementDetectable(element) { | ||
elementUtils.markAsDetectable(element); | ||
elementUtils.markBusy(element, false); | ||
detectionStrategy.addListener(element, onResizeCallback); | ||
addListener(callOnAdd, element, listener); | ||
elementsReady++; | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
var id = idHandler.get(element); | ||
if(onReadyCallbacks[id]) { | ||
forEach(onReadyCallbacks[id], function(callback) { | ||
callback(); | ||
}); | ||
delete onReadyCallbacks[id]; | ||
} | ||
}); | ||
@@ -127,4 +191,9 @@ } | ||
//The element has been prepared to be detectable and is ready to be listened to. | ||
onElementReadyToAddListener(callOnAdd, element, listener); | ||
addListener(callOnAdd, element, listener); | ||
elementsReady++; | ||
}); | ||
if(elementsReady === elements.length) { | ||
onReadyCallback(); | ||
} | ||
} | ||
@@ -131,0 +200,0 @@ |
"use strict"; | ||
var forEach = require("./collection-utils").forEach; | ||
var browserDetector = require("./browser-detector"); | ||
module.exports = function(idHandler) { | ||
module.exports = function() { | ||
/** | ||
@@ -14,164 +11,32 @@ * Tells if the element has been made detectable and ready to be listened for resize events. | ||
function isDetectable(element) { | ||
if(browserDetector.isIE(8)) { | ||
//IE 8 does not use the object method. | ||
//Check only if the element has been given an id. | ||
return !!idHandler.get(element); | ||
} | ||
return !!getObject(element); | ||
return !!element._erdIsDetectable; | ||
} | ||
/** | ||
* Adds a resize event listener to the element. | ||
* Marks the element that it has been made detectable and ready to be listened for resize events. | ||
* @public | ||
* @param {element} element The element that should have the listener added. | ||
* @param {function} listener The listener callback to be called for each resize event of the element. The element will be given as a parameter to the listener callback. | ||
* @param {element} The element to mark. | ||
*/ | ||
function addListener(element, listener) { | ||
if(!isDetectable(element)) { | ||
throw new Error("Element is not detectable."); | ||
} | ||
function listenerProxy() { | ||
listener(element); | ||
} | ||
if(browserDetector.isIE(8)) { | ||
//IE 8 does not support object, but supports the resize event directly on elements. | ||
element.attachEvent("onresize", listenerProxy); | ||
} else { | ||
var object = getObject(element); | ||
object.contentDocument.defaultView.addEventListener("resize", listenerProxy); | ||
} | ||
function markAsDetectable(element) { | ||
element._erdIsDetectable = true; | ||
} | ||
/** | ||
* Makes an element detectable and ready to be listened for resize events. Will call the callback when the element is ready to be listened for resize changes. | ||
* @private | ||
* @param {element} element The element to make detectable | ||
* @param {function} callback The callback to be called when the element is ready to be listened for resize changes. Will be called with the element as first parameter. | ||
* Tells if the element is busy or not. | ||
* @public | ||
* @param {element} The element to check. | ||
* @returns {boolean} True or false depending on if the element is busy or not. | ||
*/ | ||
function makeDetectable(batchUpdater, reporter, element, callback) { | ||
function injectObject(id, element, callback) { | ||
var OBJECT_STYLE = "display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; padding: 0; margin: 0; opacity: 0; z-index: -1000; pointer-events: none;"; | ||
function onObjectLoad() { | ||
/*jshint validthis: true */ | ||
function getDocument(element, callback) { | ||
//Opera 12 seem to call the object.onload before the actual document has been created. | ||
//So if it is not present, poll it with an timeout until it is present. | ||
//TODO: Could maybe be handled better with object.onreadystatechange or similar. | ||
if(!element.contentDocument) { | ||
setTimeout(function checkForObjectDocument() { | ||
getDocument(element, callback); | ||
}, 100); | ||
return; | ||
} | ||
callback(element.contentDocument); | ||
} | ||
//Mutating the object element here seems to fire another load event. | ||
//Mutating the inner document of the object element is fine though. | ||
var objectElement = this; | ||
//Create the style element to be added to the object. | ||
getDocument(objectElement, function onObjectDocumentReady(objectDocument) { | ||
var style = objectDocument.createElement("style"); | ||
style.innerHTML = "html, body { margin: 0; padding: 0 } div { -webkit-transition: opacity 0.01s; -ms-transition: opacity 0.01s; -o-transition: opacity 0.01s; transition: opacity 0.01s; opacity: 0; }"; | ||
//TODO: Remove any styles that has been set on the object. Only the style above should be styling the object. | ||
//Append the style to the object. | ||
objectDocument.head.appendChild(style); | ||
//Notify that the element is ready to be listened to. | ||
callback(element); | ||
}); | ||
} | ||
//The target element needs to be positioned (everything except static) so the absolute positioned object will be positioned relative to the target element. | ||
var style = getComputedStyle(element); | ||
var position = style.position; | ||
function mutateDom() { | ||
if(position === "static") { | ||
element.style.position = "relative"; | ||
var removeRelativeStyles = function(reporter, element, style, property) { | ||
function getNumericalValue(value) { | ||
return value.replace(/[^-\d\.]/g, ""); | ||
} | ||
var value = style[property]; | ||
if(value !== "auto" && getNumericalValue(value) !== "0") { | ||
reporter.warn("An element that is positioned static has style." + property + "=" + value + " which is ignored due to the static positioning. The element will need to be positioned relative, so the style." + property + " will be set to 0. Element: ", element); | ||
element.style[property] = 0; | ||
} | ||
}; | ||
//Check so that there are no accidental styles that will make the element styled differently now that is is relative. | ||
//If there are any, set them to 0 (this should be okay with the user since the style properties did nothing before [since the element was positioned static] anyway). | ||
removeRelativeStyles(reporter, element, style, "top"); | ||
removeRelativeStyles(reporter, element, style, "right"); | ||
removeRelativeStyles(reporter, element, style, "bottom"); | ||
removeRelativeStyles(reporter, element, style, "left"); | ||
} | ||
//Add an object element as a child to the target element that will be listened to for resize events. | ||
var object = document.createElement("object"); | ||
object.style.cssText = OBJECT_STYLE; | ||
object.type = "text/html"; | ||
object.onload = onObjectLoad; | ||
object._erdObjectId = id; | ||
//Safari: This must occur before adding the object to the DOM. | ||
//IE: Does not like that this happens before, even if it is also added after. | ||
if(!browserDetector.isIE()) { | ||
object.data = "about:blank"; | ||
} | ||
element.appendChild(object); | ||
//IE: This must occur after adding the object to the DOM. | ||
if(browserDetector.isIE()) { | ||
object.data = "about:blank"; | ||
} | ||
} | ||
if(batchUpdater) { | ||
batchUpdater.update(id, mutateDom); | ||
} else { | ||
mutateDom(); | ||
} | ||
} | ||
//Obtain the id of the element (will be generated if not present), so that event listeners can be identified to this element. | ||
var id = idHandler.get(element); | ||
if(browserDetector.isIE(8)) { | ||
//IE 8 does not support objects properly. Luckily they do support the resize event. | ||
//So do not inject the object and notify that the element is already ready to be listened to. | ||
//The event handler for the resize event is attached in the utils.addListener instead. | ||
callback(element); | ||
} else { | ||
injectObject(id, element, callback); | ||
} | ||
function isBusy(element) { | ||
return !!element._erdBusy; | ||
} | ||
/** | ||
* Returns the child object of the target element. | ||
* @private | ||
* @param {element} element The target element. | ||
* @returns The object element of the target. | ||
* Marks the object is busy and should not be made detectable. | ||
* @public | ||
* @param {element} element The element to mark. | ||
* @param {boolean} busy If the element is busy or not. | ||
*/ | ||
function getObject(element) { | ||
return forEach(element.children, function isObject(child) { | ||
if(child._erdObjectId !== undefined && child._erdObjectId !== null) { | ||
return child; | ||
} | ||
}); | ||
function markBusy(element, busy) { | ||
element._erdBusy = !!busy; | ||
} | ||
@@ -181,5 +46,6 @@ | ||
isDetectable: isDetectable, | ||
makeDetectable: makeDetectable, | ||
addListener: addListener, | ||
markAsDetectable: markAsDetectable, | ||
isBusy: isBusy, | ||
markBusy: markBusy | ||
}; | ||
}; |
@@ -80,28 +80,9 @@ /* global describe:false, it:false, beforeEach:false, expect:false, elementResizeDetectorMaker:false, _:false, $:false, jasmine:false */ | ||
describe("element-resize-detector", function() { | ||
beforeEach(function() { | ||
//This messed with tests in IE8. | ||
//TODO: Investigate why, because it would be nice to have instead of the current solution. | ||
//loadFixtures("element-resize-detector_fixture.html"); | ||
$("#fixtures").html("<div id=test></div><div id=test2></div>"); | ||
}); | ||
describe("elementResizeDetectorMaker", function() { | ||
it("should be globally defined", function() { | ||
expect(elementResizeDetectorMaker).toBeFunction(); | ||
}); | ||
it("should create an element-resize-detector instance", function() { | ||
var erd = elementResizeDetectorMaker(); | ||
expect(erd).toBeObject(); | ||
expect(erd).toHaveMethod("listenTo"); | ||
}); | ||
}); | ||
describe("listenTo", function() { | ||
it("should be able to attach an listener to an element", function(done) { | ||
function listenToTest(strategy) { | ||
describe("listenTo (" + strategy + ")", function() { | ||
it("should be able to attach a listener to an element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -126,3 +107,4 @@ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -135,6 +117,99 @@ | ||
describe("option.onReady", function() { | ||
it("should be called when installing a listener to an element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener = jasmine.createSpy("listener"); | ||
erd.listenTo({ | ||
onReady: function() { | ||
$("#test").width(200); | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
done(); | ||
}, 100); | ||
} | ||
}, $("#test")[0], listener); | ||
}); | ||
it("should be called when all elements are ready", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener = jasmine.createSpy("listener"); | ||
erd.listenTo({ | ||
onReady: function() { | ||
$("#test").width(200); | ||
$("#test2").width(300); | ||
setTimeout(function() { | ||
expect(listener).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener).toHaveBeenCalledWith($("#test2")[0]); | ||
done(); | ||
}, 100); | ||
} | ||
}, $("#test, #test2"), listener); | ||
}); | ||
it("should be able to handle listeners for the same element but different calls", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var onReady1 = jasmine.createSpy("listener"); | ||
var onReady2 = jasmine.createSpy("listener"); | ||
erd.listenTo({ | ||
onReady: onReady1 | ||
}, $("#test"), function noop() {}); | ||
erd.listenTo({ | ||
onReady: onReady2 | ||
}, $("#test"), function noop() {}); | ||
setTimeout(function() { | ||
expect(onReady1.calls.count()).toBe(1); | ||
expect(onReady2.calls.count()).toBe(1); | ||
done(); | ||
}, 300); | ||
}); | ||
it("should be able to handle when elements occur multiple times in the same call (and other calls)", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var onReady1 = jasmine.createSpy("listener"); | ||
var onReady2 = jasmine.createSpy("listener"); | ||
erd.listenTo({ | ||
onReady: onReady1 | ||
}, [$("#test")[0], $("#test")[0]], function noop() {}); | ||
erd.listenTo({ | ||
onReady: onReady2 | ||
}, $("#test"), function noop() {}); | ||
setTimeout(function() { | ||
expect(onReady1.calls.count()).toBe(1); | ||
expect(onReady2.calls.count()).toBe(1); | ||
done(); | ||
}, 300); | ||
}); | ||
}); | ||
it("should be able to attach multiple listeners to an element", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -150,3 +225,3 @@ | ||
$("#test").width(300); | ||
}, 100); | ||
}, 200); | ||
@@ -157,3 +232,25 @@ setTimeout(function() { | ||
done(); | ||
}, 400); | ||
}); | ||
it("should be able to attach a listener to an element multiple times within the same call", function(done) { | ||
var erd = elementResizeDetectorMaker({ | ||
callOnAdd: false, | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
var listener1 = jasmine.createSpy("listener1"); | ||
erd.listenTo([$("#test")[0], $("#test")[0]], listener1); | ||
setTimeout(function() { | ||
$("#test").width(300); | ||
}, 200); | ||
setTimeout(function() { | ||
expect(listener1).toHaveBeenCalledWith($("#test")[0]); | ||
expect(listener1.calls.count()).toBe(2); | ||
done(); | ||
}, 400); | ||
}); | ||
@@ -164,3 +261,4 @@ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -196,3 +294,4 @@ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -217,3 +316,4 @@ | ||
callOnAdd: true, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -255,3 +355,4 @@ | ||
callOnAdd: false, | ||
reporter: reporter | ||
reporter: reporter, | ||
strategy: strategy | ||
}); | ||
@@ -293,33 +394,23 @@ | ||
}); | ||
}); | ||
} | ||
it("should report warnings when top/right/bottom/left is set for an element to be changed to relative", function() { | ||
$("#test").css("bottom", "1px"); | ||
describe("element-resize-detector", function() { | ||
beforeEach(function() { | ||
//This messed with tests in IE8. | ||
//TODO: Investigate why, because it would be nice to have instead of the current solution. | ||
//loadFixtures("element-resize-detector_fixture.html"); | ||
$("#fixtures").html("<div id=test></div><div id=test2></div>"); | ||
}); | ||
var oldWarn; | ||
describe("elementResizeDetectorMaker", function() { | ||
it("should be globally defined", function() { | ||
expect(elementResizeDetectorMaker).toBeFunction(); | ||
}); | ||
var called = false; | ||
it("should create an element-resize-detector instance", function() { | ||
var erd = elementResizeDetectorMaker(); | ||
if(window.console) { | ||
/* global console: false */ | ||
oldWarn = console.warn; | ||
var warn = function() { | ||
expect(this).toEqual(console); | ||
called = true; | ||
}; | ||
console.warn = warn; | ||
} | ||
var erd = elementResizeDetectorMaker(); | ||
erd.listenTo($("#test"), _.noop); | ||
//The test should not fail because the reporter should not be using console. | ||
//So succeed the test if this has been reached. | ||
if(window.console) { | ||
setTimeout(function() { | ||
console.warn = oldWarn; | ||
expect(called).toEqual(true); | ||
}, 200); | ||
} | ||
expect(erd).toBeObject(); | ||
expect(erd).toHaveMethod("listenTo"); | ||
}); | ||
@@ -356,5 +447,4 @@ }); | ||
// describe("options.reporter", function() { | ||
// it("") | ||
// }); | ||
listenToTest("object"); | ||
listenToTest("scroll"); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
228066
29
3636
22
+ Addedbatch-processor@^0.2.0
+ Addedbatch-processor@0.2.2(transitive)
- Removedbatch-updater@^0.1.0
- Removedbatch-updater@0.1.0(transitive)