diff options
Diffstat (limited to 'src/main/resources/META-INF/resources/designer/lib/angular-vs-repeat.js')
-rw-r--r-- | src/main/resources/META-INF/resources/designer/lib/angular-vs-repeat.js | 427 |
1 files changed, 427 insertions, 0 deletions
diff --git a/src/main/resources/META-INF/resources/designer/lib/angular-vs-repeat.js b/src/main/resources/META-INF/resources/designer/lib/angular-vs-repeat.js new file mode 100644 index 000000000..11e34d7b3 --- /dev/null +++ b/src/main/resources/META-INF/resources/designer/lib/angular-vs-repeat.js @@ -0,0 +1,427 @@ +// +// Copyright Kamil Pękala http://github.com/kamilkp +// Angular Virtual Scroll Repeat v1.0.0-rc5 2014/08/01 +// + +(function(window, angular){ + 'use strict'; + /* jshint eqnull:true */ + /* jshint -W038 */ + + // DESCRIPTION: + // vsRepeat directive stands for Virtual Scroll Repeat. It turns a standard ngRepeated set of elements in a scrollable container + // into a component, where the user thinks he has all the elements rendered and all he needs to do is scroll (without any kind of + // pagination - which most users loath) and at the same time the browser isn't overloaded by that many elements/angular bindings etc. + // The directive renders only so many elements that can fit into current container's clientHeight/clientWidth. + + // LIMITATIONS: + // - current version only supports an Array as a right-hand-side object for ngRepeat + // - all rendered elements must have the same height/width or the sizes of the elements must be known up front + + // USAGE: + // In order to use the vsRepeat directive you need to place a vs-repeat attribute on a direct parent of an element with ng-repeat + // example: + // <div vs-repeat> + // <div ng-repeat="item in someArray"> + // <!-- content --> + // </div> + // </div> + // + // You can also measure the single element's height/width (including all paddings and margins), and then speficy it as a value + // of the attribute 'vs-repeat'. This can be used if one wants to override the automatically computed element size. + // example: + // <div vs-repeat="50"> <!-- the specified element height is 50px --> + // <div ng-repeat="item in someArray"> + // <!-- content --> + // </div> + // </div> + // + // IMPORTANT! + // + // - the vsRepeat directive must be applied to a direct parent of an element with ngRepeat + // - the value of vsRepeat attribute is the single element's height/width measured in pixels. If none provided, the directive + // will compute it automatically + + // OPTIONAL PARAMETERS (attributes): + // vs-scroll-parent="selector" - selector to the scrollable container. The directive will look for a closest parent matching + // he given selector (defaults to the current element) + // vs-horizontal - stack repeated elements horizontally instead of vertically + // vs-offset-before="value" - top/left offset in pixels (defaults to 0) + // vs-offset-after="value" - bottom/right offset in pixels (defaults to 0) + // vs-excess="value" - an integer number representing the number of elements to be rendered outside of the current container's viewport + // (defaults to 2) + // vs-size-property - a property name of the items in collection that is a number denoting the element size (in pixels) + // vs-autoresize - use this attribute without vs-size-property and without specifying element's size. The automatically computed element style will + // readjust upon window resize if the size is dependable on the viewport size + + // EVENTS: + // - 'vsRepeatTrigger' - an event the directive listens for to manually trigger reinitialization + // - 'vsRepeatReinitialized' - an event the directive emits upon reinitialization done + + var isMacOS = navigator.appVersion.indexOf('Mac') != -1, + wheelEventName = typeof window.onwheel !== 'undefined' ? 'wheel' : typeof window.onmousewheel !== 'undefined' ? 'mousewheel' : 'DOMMouseScroll', + dde = document.documentElement, + matchingFunction = dde.matches ? 'matches' : + dde.matchesSelector ? 'matchesSelector' : + dde.webkitMatches ? 'webkitMatches' : + dde.webkitMatchesSelector ? 'webkitMatchesSelector' : + dde.msMatches ? 'msMatches' : + dde.msMatchesSelector ? 'msMatchesSelector' : + dde.mozMatches ? 'mozMatches' : + dde.mozMatchesSelector ? 'mozMatchesSelector' : null; + + var closestElement = angular.element.prototype.closest || function (selector){ + var el = this[0].parentNode; + while(el !== document.documentElement && el != null && !el[matchingFunction](selector)){ + el = el.parentNode; + } + + if(el && el[matchingFunction](selector)) + return angular.element(el); + else + return angular.element(); + }; + + angular.module('vs-repeat', []).directive('vsRepeat', ['$compile', function($compile){ + return { + restrict: 'A', + scope: true, + require: '?^vsRepeat', + controller: ['$scope', function($scope){ + this.$scrollParent = $scope.$scrollParent; + this.$fillElement = $scope.$fillElement; + }], + compile: function($element, $attrs){ + var ngRepeatChild = $element.children().eq(0), + ngRepeatExpression = ngRepeatChild.attr('ng-repeat'), + childCloneHtml = ngRepeatChild[0].outerHTML, + expressionMatches = /^\s*(\S+)\s+in\s+([\S\s]+?)(track\s+by\s+\S+)?$/.exec(ngRepeatExpression), + lhs = expressionMatches[1], + rhs = expressionMatches[2], + rhsSuffix = expressionMatches[3], + collectionName = '$vs_collection', + attributesDictionary = { + 'vsRepeat': 'elementSize', + 'vsOffsetBefore': 'offsetBefore', + 'vsOffsetAfter': 'offsetAfter', + 'vsExcess': 'excess' + }; + + $element.empty(); + if(!window.getComputedStyle || window.getComputedStyle($element[0]).position !== 'absolute') + $element.css('position', 'relative'); + return { + pre: function($scope, $element, $attrs, $ctrl){ + var childClone = angular.element(childCloneHtml), + originalCollection = [], + originalLength, + $$horizontal = typeof $attrs.vsHorizontal !== "undefined", + $wheelHelper, + $fillElement, + autoSize = !$attrs.vsRepeat, + sizesPropertyExists = !!$attrs.vsSizeProperty, + $scrollParent = $attrs.vsScrollParent ? closestElement.call($element, $attrs.vsScrollParent) : $element, + positioningPropertyTransform = $$horizontal ? 'translateX' : 'translateY', + positioningProperty = $$horizontal ? 'left' : 'top', + + clientSize = $$horizontal ? 'clientWidth' : 'clientHeight', + offsetSize = $$horizontal ? 'offsetWidth' : 'offsetHeight', + scrollPos = $$horizontal ? 'scrollLeft' : 'scrollTop'; + + if($scrollParent.length === 0) throw 'Specified scroll parent selector did not match any element'; + $scope.$scrollParent = $scrollParent; + + if(sizesPropertyExists) $scope.sizesCumulative = []; + + //initial defaults + $scope.elementSize = $scrollParent[0][clientSize] || 50; + $scope.offsetBefore = 0; + $scope.offsetAfter = 0; + $scope.excess = 2; + + Object.keys(attributesDictionary).forEach(function(key){ + if($attrs[key]){ + $attrs.$observe(key, function(value){ + $scope[attributesDictionary[key]] = +value; + reinitialize(); + }); + } + }); + + + $scope.$watchCollection(rhs, function(coll){ + originalCollection = coll || []; + refresh(); + }); + + function refresh(){ + if(!originalCollection || originalCollection.length < 1){ + $scope[collectionName] = []; + originalLength = 0; + resizeFillElement(0); + $scope.sizesCumulative = [0]; + return; + } + else{ + originalLength = originalCollection.length; + if(sizesPropertyExists){ + $scope.sizes = originalCollection.map(function(item){ + return item[$attrs.vsSizeProperty]; + }); + var sum = 0; + $scope.sizesCumulative = $scope.sizes.map(function(size){ + var res = sum; + sum += size; + return res; + }); + $scope.sizesCumulative.push(sum); + } + setAutoSize(); + } + + reinitialize(); + } + + function setAutoSize(){ + if(autoSize){ + $scope.$$postDigest(function(){ + if($element[0].offsetHeight || $element[0].offsetWidth){ // element is visible + var children = $element.children(), + i = 0; + while(i < children.length){ + if(children[i].attributes['ng-repeat'] != null){ + if(children[i][offsetSize]){ + $scope.elementSize = children[i][offsetSize]; + reinitialize(); + autoSize = false; + if($scope.$root && !$scope.$root.$$phase) + $scope.$apply(); + } + break; + } + i++; + } + } + else{ + var dereg = $scope.$watch(function(){ + if($element[0].offsetHeight || $element[0].offsetWidth){ + dereg(); + setAutoSize(); + } + }); + } + }); + } + } + + childClone.attr('ng-repeat', lhs + ' in ' + collectionName + (rhsSuffix ? ' ' + rhsSuffix : '')) + .addClass('vs-repeat-repeated-element'); + + var offsetCalculationString = sizesPropertyExists ? + '(sizesCumulative[$index + startIndex] + offsetBefore)' : + '(($index + startIndex) * elementSize + offsetBefore)'; + + if(typeof document.documentElement.style.transform !== "undefined"){ // browser supports transform css property + childClone.attr('ng-style', '{ "transform": "' + positioningPropertyTransform + '(" + ' + offsetCalculationString + ' + "px)"}'); + } + else if(typeof document.documentElement.style.webkitTransform !== "undefined"){ // browser supports -webkit-transform css property + childClone.attr('ng-style', '{ "-webkit-transform": "' + positioningPropertyTransform + '(" + ' + offsetCalculationString + ' + "px)"}'); + } + else{ + childClone.attr('ng-style', '{' + positioningProperty + ': ' + offsetCalculationString + ' + "px"}'); + } + + $compile(childClone)($scope); + $element.append(childClone); + + $fillElement = angular.element('<div class="vs-repeat-fill-element"></div>') + .css({ + 'position':'relative', + 'min-height': '100%', + 'min-width': '100%' + }); + $element.append($fillElement); + $compile($fillElement)($scope); + $scope.$fillElement = $fillElement; + + var _prevMouse = {}; + if(isMacOS){ + $wheelHelper = angular.element('<div class="vs-repeat-wheel-helper"></div>') + .on(wheelEventName, function(e){ + e.preventDefault(); + e.stopPropagation(); + if(e.originalEvent) e = e.originalEvent; + $scrollParent[0].scrollLeft += (e.deltaX || -e.wheelDeltaX); + $scrollParent[0].scrollTop += (e.deltaY || -e.wheelDeltaY); + }).on('mousemove', function(e){ + if(_prevMouse.x !== e.clientX || _prevMouse.y !== e.clientY) + angular.element(this).css('display', 'none'); + _prevMouse = { + x: e.clientX, + y: e.clientY + }; + }).css('display', 'none'); + $fillElement.append($wheelHelper); + } + + $scope.startIndex = 0; + $scope.endIndex = 0; + + $scrollParent.on('scroll', function scrollHandler(e){ + if(updateInnerCollection()) + $scope.$apply(); + }); + + if(isMacOS){ + $scrollParent.on(wheelEventName, wheelHandler); + } + function wheelHandler(e){ + var elem = e.currentTarget; + if(elem.scrollWidth > elem.clientWidth || elem.scrollHeight > elem.clientHeight) + $wheelHelper.css('display', 'block'); + } + + function onWindowResize(){ + if(typeof $attrs.vsAutoresize !== 'undefined'){ + autoSize = true; + setAutoSize(); + if($scope.$root && !$scope.$root.$$phase) + $scope.$apply(); + } + if(updateInnerCollection()) + $scope.$apply(); + } + + angular.element(window).on('resize', onWindowResize); + $scope.$on('$destroy', function(){ + angular.element(window).off('resize', onWindowResize); + }); + + $scope.$on('vsRepeatTrigger', refresh); + $scope.$on('vsRepeatResize', function(){ + autoSize = true; + setAutoSize(); + }); + + var _prevStartIndex, + _prevEndIndex; + function reinitialize(){ + _prevStartIndex = void 0; + _prevEndIndex = void 0; + updateInnerCollection(); + resizeFillElement(sizesPropertyExists ? + $scope.sizesCumulative[originalLength] : + $scope.elementSize*originalLength + ); + $scope.$emit('vsRepeatReinitialized'); + } + + function resizeFillElement(size){ + if($$horizontal){ + $fillElement.css({ + 'width': $scope.offsetBefore + size + $scope.offsetAfter + 'px', + 'height': '100%' + }); + if($ctrl && $ctrl.$fillElement){ + var referenceElement = $ctrl.$fillElement[0].parentNode.querySelector('[ng-repeat]'); + if(referenceElement) + $ctrl.$fillElement.css({ + 'width': referenceElement.scrollWidth + 'px' + }); + } + } + else{ + $fillElement.css({ + 'height': $scope.offsetBefore + size + $scope.offsetAfter + 'px', + 'width': '100%' + }); + if($ctrl && $ctrl.$fillElement){ + referenceElement = $ctrl.$fillElement[0].parentNode.querySelector('[ng-repeat]'); + if(referenceElement) + $ctrl.$fillElement.css({ + 'height': referenceElement.scrollHeight + 'px' + }); + } + } + } + + var _prevClientSize; + function reinitOnClientHeightChange(){ + var ch = $scrollParent[0][clientSize]; + if(ch !== _prevClientSize){ + reinitialize(); + if($scope.$root && !$scope.$root.$$phase) + $scope.$apply(); + } + _prevClientSize = ch; + } + + $scope.$watch(function(){ + if(typeof window.requestAnimationFrame === "function") + window.requestAnimationFrame(reinitOnClientHeightChange); + else + reinitOnClientHeightChange(); + }); + + function updateInnerCollection(){ + if(sizesPropertyExists){ + $scope.startIndex = 0; + while($scope.sizesCumulative[$scope.startIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore) + $scope.startIndex++; + if($scope.startIndex > 0) $scope.startIndex--; + + $scope.endIndex = $scope.startIndex; + while($scope.sizesCumulative[$scope.endIndex] < $scrollParent[0][scrollPos] - $scope.offsetBefore + $scrollParent[0][clientSize]) + $scope.endIndex++; + } + else{ + $scope.startIndex = Math.max( + Math.floor( + ($scrollParent[0][scrollPos] - $scope.offsetBefore) / $scope.elementSize + $scope.excess/2 + ) - $scope.excess, + 0 + ); + + $scope.endIndex = Math.min( + $scope.startIndex + Math.ceil( + $scrollParent[0][clientSize] / $scope.elementSize + ) + $scope.excess, + originalLength + ); + } + + + var digestRequired = $scope.startIndex !== _prevStartIndex || $scope.endIndex !== _prevEndIndex; + + if(digestRequired) + $scope[collectionName] = originalCollection.slice($scope.startIndex, $scope.endIndex); + + _prevStartIndex = $scope.startIndex; + _prevEndIndex = $scope.endIndex; + + return digestRequired; + } + } + }; + } + }; + }]); + + angular.element(document.head).append([ + '<style>' + + '.vs-repeat-wheel-helper{' + + 'position: absolute;' + + 'top: 0;' + + 'bottom: 0;' + + 'left: 0;' + + 'right: 0;' + + 'z-index: 99999;' + + 'background: rgba(0, 0, 0, 0);' + + '}' + + '.vs-repeat-repeated-element{' + + 'position: absolute;' + + 'z-index: 1;' + + '}' + + '</style>' + ].join('')); +})(window, window.angular);
\ No newline at end of file |