Skip to content

Instantly share code, notes, and snippets.

@RadoMark
Last active August 29, 2015 14:18
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 RadoMark/43978ef5425cb078171c to your computer and use it in GitHub Desktop.
Save RadoMark/43978ef5425cb078171c to your computer and use it in GitHub Desktop.
Teambook tb-hide
(function(angular) {
"use strict";
angular
.module('teambook')
.directive('tbHide', ['$timeout', '$parse', 'debug', function($timeout, $parse, debug) {
return {
restrict: 'EA',
scope: {
toggler: '&tbHide',
refreshSuspensionOn: '=refreshHideOn'
},
link: function($scope, element, attrs) {
var scopes = ['$scope', '$isolateScope'],
watchers = {
suspended: false
};
// scope.toggler is external boolean variable that toggles this element's watchers
// it's $watch'ed as function because it's evaluated in parent scope context
$scope.$watch(function() { return $scope.toggler() }, function(newToggler, oldToggler) {
if(typeof newToggler == 'boolean') {
if(newToggler) {
element.hide();
suspendFromRoot();
} else {
element.show();
resumeFromRoot();
}
}
});
// when new scopes under this element are created, we need to refresh toggling mechanism to
// properly handle those news
$scope.$watch('refreshSuspensionOn', function(newVal, oldVal) {
if(newVal !== oldVal) refreshSuspensionFromRoot()
}, true);
// this directive creates isolateScope so we don't have direct access to all watchers we want to disable
// that's why we iterate through first floor of DOM children of this element and take scopes from them
// and the same for resuming and refreshing watchers suspension
function suspendFromRoot() {
if(!watchers.suspended) {
$timeout(function(){
if(debug) console.log('[tb-hide] SUSPEND WATCHERS');
iterateElementChildren(element, suspendWatchers);
watchers.suspended = true;
})
}
}
function refreshSuspensionFromRoot() {
if(watchers.suspended) {
$timeout(function() {
if(debug) console.log('[tb-hide] REFRESH WATCHERS SUSPENSION');
iterateElementChildren(element, suspendWatchers);
})
}
}
function resumeFromRoot() {
if(watchers.suspended) {
$timeout(function(){
if(debug) console.log('[tb-hide] RESUME WATCHERS');
iterateElementChildren(element, resumeWatchers);
watchers.suspended = false;
})
}
}
function iterateElementChildren(element, operationOnElement) {
angular.forEach(element.children(), function (childElement) {
operationOnElement(angular.element(childElement));
});
}
// for each first floot DOM child try to fetch scope and isolateScope and take them as starting points
// for appropriate scopes traversing
function suspendWatchers(element) {
iterateElementScopes(element, suspendScopeWatchers);
};
function resumeWatchers(element) {
iterateElementScopes(element, resumeScopeWatchers);
};
function iterateElementScopes(element, operationOnScope) {
angular.forEach(scopes, function (scopeProperty) {
if (element.data() && element.data().hasOwnProperty(scopeProperty)) {
iterateScopes(element.data()[scopeProperty], operationOnScope);
}
});
}
// mock $watch function for adding new watchers to "disabled" scope.$watchers list
var mockScopeWatch = function(scopeId) {
return function(watchExp, listener, objectEquality, prettyPrintExpression) {
watchers[scopeId].unshift({
fn: angular.isFunction(listener) ? listener : angular.noop,
last: void 0,
get: $parse(watchExp),
exp: prettyPrintExpression || watchExp,
eq: !!objectEquality
})
}
}
// "disabling" and keeping scope.$watchers in storage object and mocking $watch function
function suspendScopeWatchers(scope) {
if(!watchers[scope.$id]) {
watchers[scope.$id] = scope.$$watchers || [];
scope.$$watchers = [];
scope.$watch = mockScopeWatch(scope.$id)
}
}
// "enabling" scope.$watchers from storage object and deleting mocked $watch function
function resumeScopeWatchers(scope) {
if(watchers[scope.$id]) {
scope.$$watchers = watchers[scope.$id];
if(scope.hasOwnProperty('$watch')) delete scope.$watch;
watchers[scope.$id] = false
} else {
if(debug) {
console.warn('[tb-hide] No watchers for scope.')
console.warn('[tb-hide] This happens for Ng-Ifs or when you forgot to refresh watchers when neccessary.')
}
}
}
function iterateScopes(initScope, operationOnScope) {
operationOnScope(initScope);
iterateSiblings(initScope, operationOnScope);
iterateChildren(initScope, operationOnScope);
}
function iterateSiblings(scope, operationOnScope) {
while (!!(scope = scope.$$nextSibling)) {
operationOnScope(scope);
iterateChildren(scope, operationOnScope);
}
}
function iterateChildren(scope, operationOnScope) {
while (!!(scope = scope.$$childHead)) {
operationOnScope(scope);
iterateSiblings(scope, operationOnScope);
}
}
}
}
}]);
})(angular);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment