Skip to content

Instantly share code, notes, and snippets.

@eimfach
Last active June 10, 2018 17:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eimfach/f0cc3ccc533ed75605c37a9c05251aef to your computer and use it in GitHub Desktop.
Save eimfach/f0cc3ccc533ed75605c37a9c05251aef to your computer and use it in GitHub Desktop.
/**
* 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