Last active
August 29, 2015 14:18
-
-
Save RadoMark/43978ef5425cb078171c to your computer and use it in GitHub Desktop.
Teambook tb-hide
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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