Skip to content

Commit

Permalink
Merge pull request #28 from uzairfarooq/dev
Browse files Browse the repository at this point in the history
Release v2.2.0
  • Loading branch information
uzairfarooq committed May 11, 2015
2 parents a50a2e2 + 8941c62 commit 4c9ff97
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 59 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ $(document).unbindArrive(callbackFunc);
// unbind only a specific callback on ".test-elem" selector
$(document).unbindArrive(".test-elem", callbackFunc);
```

####Options
As of v2.0 `arrive` event accepts an optional `options` object as 2nd argument. Options object consists of following:
```javascript
var options = {
fireOnAttributesModification: boolean // Defaults to false. Setting it to true would make arrive event fire on existing elements which start to satisfy selector after some modification in DOM. If false, id'd only fire for newly created elements.
fireOnAttributesModification: boolean, // Defaults to false. Setting it to true would make arrive event fire on existing elements which start to satisfy selector after some modification in DOM attributes (an arrive event won't fire twice for a single element even if the option is true). If false, it'd only fire for newly created elements.
onceOnly: boolean // Defaults to false. Setting it to true would ensure that registered callbacks fire only once.
};
```
Example:
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "arrive",
"version": "2.1.0",
"version": "2.2.0",
"description": "Watch for DOM elements creation and removal.",
"main": "src/arrive.js",
"keywords": [
Expand Down
19 changes: 9 additions & 10 deletions minified/arrive.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 51 additions & 47 deletions src/arrive.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
/*
* arrive.js
* v2.1.0
* v2.2.0
* https://github.com/uzairfarooq/arrive
* MIT licensed
*
* Copyright (c) 2014-2015 Uzair Farooq
*/

var _arrive_unique_id_ = 0;

(function(window, $, undefined) {

"use strict";
Expand All @@ -17,6 +15,8 @@ var _arrive_unique_id_ = 0;
return; //for unsupported browsers
}

var arriveUniqueId = 0;

var utils = (function() {
var matches = HTMLElement.prototype.matches || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector
|| HTMLElement.prototype.msMatchesSelector;
Expand All @@ -39,7 +39,7 @@ var _arrive_unique_id_ = 0;
})();


// Class to mantain state of all registered events of a single type
// Class to maintain state of all registered events of a single type
var EventsBucket = (function() {
var EventsBucket = function() {
// holds all the events
Expand Down Expand Up @@ -91,10 +91,13 @@ var _arrive_unique_id_ = 0;
})();


// General class for binding/unbinding arrive and leave events
/**
* @constructor
* General class for binding/unbinding arrive and leave events
*/
var MutationEvents = function(getObserverConfig, defaultOptions, onMutation) {
var eventsBucket = new EventsBucket(),
me = this;
var eventsBucket = new EventsBucket(),
me = this;

// actual event registration before adding it to bucket
eventsBucket.beforeAdding(function(registrationData) {
Expand All @@ -118,6 +121,7 @@ var _arrive_unique_id_ = 0;
observer.observe(target, config);

registrationData.observer = observer;
registrationData.me = me;
});

// cleanup/unregister before removing an event
Expand All @@ -136,6 +140,8 @@ var _arrive_unique_id_ = 0;
if (typeof callback === "undefined") {
callback = options;
options = defaultOptions;
} else {
options = mergeOptions(defaultOptions, options);
}

var elements = toArray(this);
Expand Down Expand Up @@ -199,37 +205,38 @@ var _arrive_unique_id_ = 0;
return this;
};

function shouldBeIgnored(node){
if(node._shouldBeIgnored === undefined){
if ((' '+node.className+' ').indexOf(' ignore-arrive ') != -1){
return node._shouldBeIgnored = true;
}
if (node.parentNode == null){
return node._shouldBeIgnored = false;
function checkNode(node, registrationData, callbacksToBeCalled) {
// check a single node to see if it matches the selector
if (utils.matchesSelector(node, registrationData.selector)) {
if(node._id === undefined) {
node._id = arriveUniqueId++;
}
// make sure the arrive event is not already fired for the element
if (registrationData.firedElems.indexOf(node._id) == -1) {

if (registrationData.options.onceOnly) {
if (registrationData.firedElems.length === 0) {
// On first callback, unbind event.
registrationData.me.unbindEventWithSelectorAndCallback.call(
registrationData.target, registrationData.selector, registrationData.callback);
} else {
// Ignore multiple mutations which may have been queued before the event was unbound.
return;
}
}
return node._shouldBeIgnored = shouldBeIgnored(node.parentNode);

registrationData.firedElems.push(node._id);
callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
}
}
return node._shouldBeIgnored;
}

// traverse through all descendants of a node to check if event should be fired for any descendant
function checkChildNodesRecursively(nodes, registrationData, callbacksToBeCalled) {
// check each new node if it matches the selector
for (var i=0, node; node = nodes[i]; i++) {
if (shouldBeIgnored(node)) {
continue;
}
checkNode(node, registrationData, callbacksToBeCalled);

if (utils.matchesSelector(node, registrationData.selector)) {
if(node._id === undefined) {
node._id = _arrive_unique_id_++;
}
// make sure the arrive event is not already fired for the element
if (registrationData.firedElems.indexOf(node._id) == -1) {
registrationData.firedElems.push(node._id);
callbacksToBeCalled.push({ callback: registrationData.callback, elem: node });
}
}
if (node.childNodes.length > 0) {
checkChildNodesRecursively(node.childNodes, registrationData, callbacksToBeCalled);
}
Expand All @@ -244,9 +251,6 @@ var _arrive_unique_id_ = 0;

function onArriveMutation(mutations, registrationData) {
mutations.forEach(function( mutation ) {
if (shouldBeIgnored(mutation.target)) {
return;
}
var newNodes = mutation.addedNodes,
targetNode = mutation.target,
callbacksToBeCalled = [];
Expand All @@ -256,16 +260,7 @@ var _arrive_unique_id_ = 0;
checkChildNodesRecursively(newNodes, registrationData, callbacksToBeCalled);
}
else if (mutation.type === "attributes") {
if(utils.matchesSelector(targetNode, registrationData.selector)) {
if(targetNode._id === undefined){
targetNode._id = _arrive_unique_id_++;
}
// make sure the arrive event is not already fired for the element
if (registrationData.firedElems.indexOf(targetNode._id) == -1) {
registrationData.firedElems.push(targetNode._id);
callbacksToBeCalled.push({ callback: registrationData.callback, elem: targetNode });
}
}
checkNode(targetNode, registrationData, callbacksToBeCalled);
}

callCallbacks(callbacksToBeCalled);
Expand All @@ -274,9 +269,6 @@ var _arrive_unique_id_ = 0;

function onLeaveMutation(mutations, registrationData) {
mutations.forEach(function( mutation ) {
if (shouldBeIgnored(mutation.target)) {
return;
}
var removedNodes = mutation.removedNodes,
targetNode = mutation.target,
callbacksToBeCalled = [];
Expand Down Expand Up @@ -311,10 +303,22 @@ var _arrive_unique_id_ = 0;
return config;
}

function mergeOptions(defaultOpts, userOpts){
// Overwrites default options with user-defined options.
var options = {};
for (var attrname in defaultOpts) {
options[attrname] = defaultOpts[attrname];
}
for (var attrname in userOpts) {
options[attrname] = userOpts[attrname];
}
return options;
}

// Default options
var arriveDefaultOptions = {
fireOnAttributesModification: false
fireOnAttributesModification: false,
onceOnly: false
},
leaveDefaultOptions = {};

Expand Down Expand Up @@ -346,4 +350,4 @@ var _arrive_unique_id_ = 0;
exposeApi(HTMLDocument.prototype);
exposeApi(Window.prototype);

})(this, typeof jQuery === 'undefined' ? null : jQuery);
})(this, typeof jQuery === 'undefined' ? null : jQuery, undefined);
42 changes: 42 additions & 0 deletions tests/spec/arriveSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,48 @@ describe("Arrive", function() {
}, 400);
});
});

describe("Multiple events tests.", function() {
var selector = ".test-elem",
appendedElem = "<div class='test-elem'></div>";

beforeEach(function() {
j(document).unbindArrive();
});

it("Multiple callbacks can be called for a single registration", function(done) {
var callCount = 0;

j(document).arrive(selector, function() {
callCount += 1;

if (callCount >= 2) {
expect(true).toBe(true);
done();
}
});

$("body").append(appendedElem);
$("body").append(appendedElem);
});

it("onceOnly argument prevents multiple callbacks for a single registration", function(done) {
var callCount = 0;

j(document).arrive(selector, {onceOnly: true}, function() {
callCount += 1;
});

$("body").append(appendedElem);
$("body").append(appendedElem);

setTimeout(function() {
expect(callCount).toBe(1);
done();
}, 400);

});
});
});

describe("Leave Event Tests", function() {
Expand Down

0 comments on commit 4c9ff97

Please sign in to comment.