dynamic-directive
Version:
Dynamic directives for AngularJS
228 lines (193 loc) • 7.08 kB
JavaScript
;
(function(angular) {
var uniqueId = 0;
const DEFAULT_PRIORITY = 0;
class DynamicDirective {
constructor(injectionFunction, name, options) {
if (!name) {
throw new Error('DynamicInjection: name argument should be a string');
}
this.name = name;
if (injectionFunction === true) {
injectionFunction = function() { return true; };
} else if (!injectionFunction || !angular.isFunction(injectionFunction)) {
throw new Error('DynamicInjection: injectionFunction argument should be a function');
}
this.injectionFunction = injectionFunction;
options = options || {};
this.attributes = options.attributes || [];
this.scope = options.scope || undefined;
this.priority = !isNaN(parseInt(options.priority, 10)) ? parseInt(options.priority, 10) : DEFAULT_PRIORITY;
this._id = ++uniqueId;
}
}
angular.module('op.dynamicDirective', [])
.provider('dynamicDirectiveService', (function() {
let injections = {};
function _dynamicDirectivesSortByName(a, b, onEquality) {
if (b.name < a.name) {
return -1;
} else if (b.name > a.name) {
return 1;
} else {
return onEquality(a, b);
}
}
function _dynamicDirectivesSortByid(a, b) {
if (b._id < a._id) {
return -1;
} else if (b._id > a._id) {
return 1;
} else {
return 0;
}
}
function _dynamicDirectivesSort(a, b) {
var prio = b.priority - a.priority;
if (prio !== 0) {
return prio;
}
if (b.name < a.name) {
return -1;
} else if (b.name > a.name) {
return 1;
} else {
return _dynamicDirectivesSortByName(a, b, _dynamicDirectivesSortByid);
}
}
function _ensureInjectionsArray(anchorName) {
injections[anchorName] = injections[anchorName] || [];
}
function getInjections(anchorName, scope) {
_ensureInjectionsArray(anchorName);
let ia = injections[anchorName];
return ia.filter((da) => da.injectionFunction(scope));
}
function addInjection(anchorName, da) {
_ensureInjectionsArray(anchorName);
injections[anchorName].push(da);
}
function resetInjections(anchorName) {
injections[anchorName] = [];
}
function orderInjections(injections) {
injections.sort(_dynamicDirectivesSort);
return injections;
}
function resetAllInjections() {
injections = {};
}
return {
addInjection: addInjection,
resetAllInjections: resetAllInjections,
DynamicDirective: DynamicDirective,
$get: ['$rootScope', function($rootScope) {
return {
DynamicDirective: DynamicDirective,
getInjections: getInjections,
sort: orderInjections,
addInjection: function(anchorName, da) {
addInjection(anchorName, da);
$rootScope.$broadcast('dynamicDirectiveInjectionUpdated', anchorName, da);
},
resetInjections: function(anchorName) {
resetInjections(anchorName);
$rootScope.$broadcast('dynamicDirectiveInjectionUpdated', anchorName);
}
};
}]
};
})())
.value('DynamicDirective', DynamicDirective)
.directive('dynamicDirective', ['$compile', 'dynamicDirectiveService', function($compile, dynamicDirectiveService) {
const DYNAMIC_DIRECTIVE_ID = 'dynamic-directive-id';
function orderDirectives(element, dynamicDirectives) {
let orderedIds = dynamicDirectiveService.sort(dynamicDirectives).map((d) => d._id);
let domIds = element.children('[' + DYNAMIC_DIRECTIVE_ID + ']')
.map((index, e) => parseInt(angular.element(e).attr(DYNAMIC_DIRECTIVE_ID), 10))
.toArray();
// 99% of the time
if (orderedIds.join(',') === domIds.join(',')) {
return;
}
for (let i = 0, len = orderedIds.length - 2; i <= len; i++) {
let current = orderedIds[i], next = orderedIds[(i + 1)],
$current = element.children('[' + DYNAMIC_DIRECTIVE_ID + '=' + current + ']');
if ($current.next().attr(DYNAMIC_DIRECTIVE_ID) !== next) {
element.children('[' + DYNAMIC_DIRECTIVE_ID + '=' + next + ']').insertAfter($current);
}
}
}
function hideElement(element) {
element.data('__dd_original_display', element.attr('display'));
element.attr('display', 'none');
}
function showElement(element) {
var originalDisplay = element.data('__dd_original_display');
if (!originalDisplay || originalDisplay === 'none') {
originalDisplay = 'block;';
}
element.attr('display', originalDisplay);
}
function link(scope, element, attrs) {
function appendDirective(dynamicDirective) {
let template = angular.element(buildHtmlFromInjectionData(dynamicDirective));
let newElt = $compile(template)(dynamicDirective.scope || scope);
element.append(newElt);
}
function buildHtmlFromInjectionData(dynamicDirective) {
let attributes = {};
attributes[DYNAMIC_DIRECTIVE_ID] = dynamicDirective._id;
dynamicDirective.attributes.forEach((attribute) => attributes[attribute.name] = attribute.value);
let e = angular.element('<' + dynamicDirective.name + '/>');
e.attr(attributes);
return e;
}
function fixVisibility() {
if (element.children().length) {
showElement(element);
} else {
hideElement(element);
}
}
let anchorName = attrs.dynamicDirective;
hideElement(element);
let dynamicDirectives = dynamicDirectiveService.sort(dynamicDirectiveService.getInjections(anchorName, scope));
dynamicDirectives.forEach(appendDirective);
fixVisibility();
scope.$on('dynamicDirectiveInjectionUpdated', function(evt, name) {
if (name !== anchorName) {
return;
}
let dynamicDirectives = dynamicDirectiveService.sort(dynamicDirectiveService.getInjections(anchorName, scope));
let dIds = {}, currentIds = {};
dynamicDirectives.forEach((d) => dIds[d._id] = d);
element.children().each((index, elt) => {
let $e = angular.element(elt), directiveId = $e.attr(DYNAMIC_DIRECTIVE_ID);
if (!directiveId) {
return;
}
if (!dIds[directiveId]) {
$e.remove();
return;
}
currentIds[directiveId] = true;
});
let dIdsCount = Object.keys(dIds).length, currentIdsCount = Object.keys(currentIds).length;
if (dIdsCount !== currentIdsCount) {
Object.keys(dIds).forEach((id) => {
if (!currentIds[id]) {
appendDirective(dIds[id]);
}
});
}
orderDirectives(element, dynamicDirectives);
fixVisibility();
});
}
return {
restrict: 'A',
link: link
};
}]);
})(angular);