/** | |
* Copyright by Robin Gruenke | |
* License: NOT for public or commercial usage permitted. Third parties are not allowed to reuse this software without permission. | |
* @author Robin Gruenke | |
* @module ns.libs.effects | |
* Service to generate event receivers which specifically manipulate data sets | |
* to trigger certain external sideeffects (like managed time sequenced animations). | |
* These Factories should be depedency free (deps are passed on create), enabliing | |
* to control dynamics via passed functions / promise chains. | |
* | |
* | |
* | |
* @typedef {Object} Options | |
* @property {Function} waitForSomethingBeforeRemoval - returns a Promise | |
* @property {Function} promiseSequencePart - returns a Promise | |
* @property {Function} reject - returns a Promise as resolved | |
* @property {Array.<Notification>} notifications - array reference which will be mutated. Represents shared state (used in e.g. view state to render notifications) | |
* @typedef {Function} receiver | |
* @typedef {Object} Notification | |
* @property {String} id - can be any | |
* @property {*} add any properties for usage in e.g. ui | |
*/ | |
/* | |
* Usage example in directive | |
* | |
* | |
var notificationListener = $rootScope.$on( | |
'NsNotification', | |
nsEffects.notificationReceiverFactory( | |
{ | |
waitForSomethingBeforeRemoval: function () { | |
return deferRemoval(); | |
function deferRemoval() { | |
return nsLibsSystemQueue.waitForAnyNext() | |
.then(function () { | |
// wait at least one second to give | |
// the ui a chance to display the new price to the user | |
// or to defer the removal if the user picks a | |
// new product within the given time | |
return $timeout(1000); | |
}) | |
.then(function () { | |
if (nsLibsSystemQueue.isInProgress('*')) { | |
return deferRemoval(); | |
} | |
}); | |
} | |
}, | |
promiseSequencePart: function () { | |
return $timeout(500); | |
}, | |
notifications: vm.notifications, | |
reject: $q.reject | |
} | |
) | |
); | |
$scope.$on('$destroy', notificationListener); | |
*/ | |
(function () { | |
'use strict'; | |
angular | |
.module('ns.libs.effects', []) | |
.factory('NsEffects', nsEffects); | |
function nsEffects() { | |
return { | |
notificationReceiverFactory: notificationReceiverFactory | |
}; | |
/** | |
* notificationReceiverFactory- creates a function to use as listener for | |
* triggering notifications | |
* @return {receiver} Listener function invoked with Notification object | |
* @param {Options} options | |
* @memberof module:ns.libs.effects | |
* | |
*/ | |
function notificationReceiverFactory(options) { | |
var waitForSomethingBeforeRemoval = options.waitForSomethingBeforeRemoval; | |
var promiseSequencePart = options.promiseSequencePart; | |
var reject = options.reject; | |
var notifications = options.notifications; | |
var managerRunning = false; | |
var removalOngoing = false; | |
/** | |
* receiver - event callback which adds/replaces incoming Notification Objects to | |
* given list and starts a manager which will remove the items in given list | |
* according to a promise resolve | |
* | |
* @param {Object} event | |
* @param {Notification} notification | |
*/ | |
return function receiver(event, notification) { | |
if (removalOngoing === false && !!notification.id) { | |
// Check if a matching notification (by id) exists and replace its values | |
var index = _.findIndex(notifications, function (element) { | |
return notification.id === element.id; | |
}); | |
if (index > -1) { | |
_.assign(notifications[index], notification); | |
return; | |
} | |
} | |
notifications.push(_.assign({}, notification)); | |
if (!managerRunning) { | |
managerRunning = true; | |
waitForSomethingBeforeRemoval() | |
.then(function () { | |
managerRunning = false; | |
removalOngoing = true; | |
return sequencedRemoval(promiseSequencePart, reject, notifications); | |
}) | |
.then(function () { | |
removalOngoing = false; | |
}); | |
} | |
}; | |
} | |
/** | |
* timeSequencedRemoval - takes an array and removes every instance sequentially | |
* based on a resolve of a promise returned by the invokation of `promiseSequencePart` | |
* | |
* @param {function} promiseSequencePart returns a Promise | |
* @param {function} reject callback which returns a rejected promise | |
* @param {Array} list Array to work on | |
* @return {Promise} | |
*/ | |
function sequencedRemoval(promiseSequencePart, reject, list) { | |
if (list.length === 0) { | |
return reject(); | |
} | |
var promiseSequences = list.map(function () { | |
return function () { | |
return promiseSequencePart(); | |
}; | |
}); | |
// remove notification objects sequentially after given time | |
var promiseLastTimeout = promiseSequences.reduce( | |
function (currentSequencePart, nextSequencePart) { | |
return currentSequencePart.then(function () { | |
list.shift(); | |
return nextSequencePart(); | |
}); | |
}, | |
promiseSequences[0]() | |
); | |
return promiseLastTimeout; | |
} | |
} | |
})(); | |
describe('ns.libs.effects', function () { | |
'use strict'; | |
var $q; | |
var $timeout; | |
var nsEffects; | |
var createReceiver; | |
beforeEach(function () { | |
module('ns.libs.effects'); | |
inject(function ($injector) { | |
$q = $injector.get('$q'); | |
$timeout = $injector.get('$timeout'); | |
nsEffects = $injector.get('nsEffects'); | |
createReceiver = function (notifications) { | |
return nsEffects.notificationReceiverFactory( | |
{ | |
waitForSomethingBeforeRemoval: function () { | |
return $q.reject(); | |
}, | |
promiseSequencePart: function () { | |
return $q.resolve(); | |
}, | |
notifications: notifications, | |
reject: $q.reject | |
} | |
); | |
}; | |
}); | |
}); | |
describe('notificationReceiverFactory()', function () { | |
it('returns a function', function () { | |
var receiver = createReceiver([]); | |
expect(_.isFunction(receiver)).toBe(true); | |
}); | |
}); | |
describe('receiver()', function () { | |
var randomVal = _.random(100000); | |
var randomProp = _.random(100000); | |
it('pushes new notification at the end of given array', function () { | |
var notifications = [{}, {}]; | |
var notificationOneRef = notifications[0]; | |
var notificationTwoRef = notifications[1]; | |
var receiver = createReceiver(notifications); | |
var notificationRef = {}; | |
notificationRef[randomProp] = randomVal; | |
receiver({}, notificationRef); | |
var newInstanceRef = notifications[notifications.length - 1]; | |
expect(notifications.length).toEqual(3); | |
expect(notificationOneRef === newInstanceRef).toBe(false); | |
expect(notificationTwoRef === newInstanceRef).toBe(false); | |
}); | |
it('pushes new notification which must be a shallow copy', function () { | |
var notifications = [{}, {}]; | |
var receiver = createReceiver(notifications); | |
var notificationRef = {}; | |
notificationRef[randomProp] = randomVal; | |
receiver({}, notificationRef); | |
var newInstanceRef = notifications[notifications.length - 1]; | |
expect(newInstanceRef !== notificationRef).toBe(true); | |
expect(randomProp in newInstanceRef).toBe(true); | |
expect(newInstanceRef[randomProp] === randomVal).toBe(true); | |
}); | |
it('replaces properties of same id instances which must still be part of the array', function () { | |
var notifications = [{id: 'a', some: 'b'}, {}]; | |
var instanceRef = notifications[0]; | |
var receiver = createReceiver(notifications); | |
var newNotification = {id: 'a', some: 'c'}; | |
newNotification[randomProp] = randomVal; | |
receiver({}, newNotification); | |
expect(notifications.indexOf(instanceRef)).toBeGreaterThan(-1); | |
}); | |
it('replaces properties of same id instances which must copy over everything', function () { | |
var notifications = [{id: 'a', some: 'b'}, {}]; | |
var instanceRef = notifications[0]; | |
var receiver = createReceiver(notifications); | |
var newNotification = {id: 'a', some: 'c'}; | |
newNotification[randomProp] = randomVal; | |
receiver({}, newNotification); | |
expect(randomProp in instanceRef).toBe(true); | |
expect(instanceRef[randomProp] === randomVal); | |
expect(instanceRef.some === 'c'); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment