diff --git a/demo/index.html b/demo/index.html index 5b8e864..d8c8ab5 100644 --- a/demo/index.html +++ b/demo/index.html @@ -58,30 +58,21 @@ relative: { x: 15, y: 0 } }; + function resetScrollpoint(){ + $scope.$broadcast('scrollpointShouldReset'); + } + // watch toggle controls - $scope.$watchGroup(['topSpacer', 'showAbsolute', 'shiftRelative', 'target.showAbsolute', 'target.showRelative'], - function(){ - $scope.$broadcast('scrollpointShouldReset'); - }); + $scope.$watchGroup(['topSpacer', 'showAbsolute', 'shiftRelative', 'target.showAbsolute', 'target.showRelative'], resetScrollpoint); // watch co-ordinate collections - $scope.$watchCollection('absolute', function(){ - $scope.$broadcast('scrollpointShouldReset'); - }); - $scope.$watchCollection('relative', function(){ - $scope.$broadcast('scrollpointShouldReset'); - }); - $scope.$watchCollection('target.absolute', function(){ - $scope.$broadcast('scrollpointShouldReset'); - }); - $scope.$watchCollection('target.relative', function(){ - $scope.$broadcast('scrollpointShouldReset'); - }); + $scope.$watchCollection('absolute', resetScrollpoint); + $scope.$watchCollection('relative', resetScrollpoint); + $scope.$watchCollection('target.absolute', resetScrollpoint); + $scope.$watchCollection('target.relative', resetScrollpoint); // initialize - $timeout(function(){ - $scope.$broadcast('scrollpointShouldReset'); - }, 10); + $timeout( resetScrollpoint, 10 ); }); @@ -206,13 +197,15 @@

Scrollpoints

id="viewtrack-1" -
- ui-scrollpoint="{{scrollpoint}}"
+
+ ui-scrollpoint
+ ui-scrollpoint-edge="{ top: '-25', bottom: '+25' }"
ui-scrollpoint-action="reportScroll"
-
+
ui-scrollpoint="{{scrollpoint}}"
+ ui-scrollpoint-edge="['top', 'bottom']"
ui-scrollpoint-class="my-scrollpoint"
@@ -221,6 +214,11 @@

Scrollpoints

ui-scrollpoint-class="ui-scrollpoint another-scrollpoint"
+
+ ui-scrollpoint
+ ui-scrollpoint-edge="{ top: '25%', bottom: '70%' }" +
+
@@ -391,7 +389,6 @@

absolutely positioned parent

-
ui-scrollpoint
ui-scrollpoint-edge="bottom" @@ -418,6 +415,28 @@

absolutely positioned parent

ui-scrollpoint-action="reportScroll"
+
+ ui-scrollpoint
+ ui-scrollpoint-edge="bottom" +
+ +
+ ui-scrollpoint
+ ui-scrollpoint-edge="{ top: '-25', bottom: '+25' }" +
+ +
+ ui-scrollpoint="{{scrollpoint}}"
+ ui-scrollpoint-edge="['bottom', 'top']"
+ ui-scrollpoint-class="my-scrollpoint" +
+ +
+ ui-scrollpoint="{{scrollpoint}}"
+ ui-scrollpoint-edge="bottom"
+ ui-scrollpoint-class="ui-scrollpoint another-scrollpoint" +
+
diff --git a/dist/scrollpoint.js b/dist/scrollpoint.js index 84cf5c7..e01258b 100644 --- a/dist/scrollpoint.js +++ b/dist/scrollpoint.js @@ -1,7 +1,7 @@ /*! * angular-ui-scrollpoint * https://github.com/angular-ui/ui-scrollpoint - * Version: 2.0.2 - 2016-01-03T02:19:46.904Z + * Version: 2.1.1 - 2016-02-22T01:35:08.609Z * License: MIT */ @@ -32,33 +32,99 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti return { require: ['uiScrollpoint', '^?uiScrollpointTarget'], controller: function(){ + var self = this; this.$element = undefined; this.$target = undefined; this.hasTarget = false; - this.edges = {top: true}; + this.hit = undefined; + this.edges = { top: { top: true }}; // ui-scrollpoint on top edge of element with top edge of target this.hitEdge = undefined; - this.absolute = true; - this.percent = false; - this.shift = 0; + this.default_edge = { + absolute: false, + percent: false, + shift: 0 + }; this.posCache = {}; + this.ready = false; this.enabled = true; this.scrollpointClass = 'ui-scrollpoint'; this.actions = undefined; + function parseScrollpoint(scrollpoint){ + var def = { shift: 0, absolute: false, percent: false }; + if(scrollpoint && angular.isString(scrollpoint)) { + def.percent = (scrollpoint.charAt(scrollpoint.length-1) == '%'); + if(def.percent) { + scrollpoint = scrollpoint.substr(0, scrollpoint.length-1); + } + if(scrollpoint.charAt(0) === '-') { + def.absolute = def.percent; + def.shift = -parseFloat(scrollpoint.substr(1)); + } + else if(scrollpoint.charAt(0) === '+') { + def.absolute = def.percent; + def.shift = parseFloat(scrollpoint.substr(1)); + } + else { + var parsed = parseFloat(scrollpoint); + if (!isNaN(parsed) && isFinite(parsed)) { + def.absolute = true; + def.shift = parsed; + } + } + } + else if(angular.isNumber(scrollpoint)){ + return parseScrollpoint(scrollpoint.toString()); + } + return def; + } + this.addEdge = function(view_edge, element_edge){ if(angular.isString(view_edge)){ if(angular.isUndefined(element_edge)){ element_edge = true; } if(view_edge == 'view'){ + // view is a shorthand for matching top of element with bottom of view, and vice versa this.addEdge('top', 'bottom'); this.addEdge('bottom', 'top'); } else{ + var edge, parsedEdge; + if(angular.isObject(element_edge)){ + // the view_edge interacts with more than one element_edge + for(edge in element_edge){ + // parse each element_edge definition (allows each element_edge to have its own scrollpoint with view_edge) + if(element_edge[edge] === true){ + element_edge[edge] = true; // use the ui-scrollpoint default + } + else{ + element_edge[edge] = parseScrollpoint(element_edge[edge]); + } + } + } + else if(element_edge == 'top' || element_edge == 'bottom'){ + // simple top or bottom of element with 0 shift + edge = element_edge; + parsedEdge = parseScrollpoint(); + element_edge = {}; + element_edge[edge] = parsedEdge; + } + else if(element_edge === true){ + element_edge = {}; + element_edge[view_edge] = true; // use the ui-scrollpoint default + } + else{ + // element_edge matches view_edge (ie. top of element interacts with top of view) + parsedEdge = parseScrollpoint(element_edge); + element_edge = {}; + element_edge[view_edge] = parsedEdge; + } + // element_edge has been parsed this.edges[view_edge] = element_edge; } } @@ -76,33 +142,7 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti }; this.setScrollpoint = function(scrollpoint){ - if (!scrollpoint) { - this.absolute = false; - this.percent = false; - this.shift = 0; - } else if (typeof (scrollpoint) === 'string') { - // charAt is generally faster than indexOf: http://jsperf.com/indexof-vs-charat - this.percent = (scrollpoint.charAt(scrollpoint.length-1) == '%'); - if(this.percent){ - scrollpoint = scrollpoint.substr(0, scrollpoint.length-1); - } - if (scrollpoint.charAt(0) === '-') { - this.absolute = this.percent; - this.shift = -parseFloat(scrollpoint.substr(1)); - } else if (scrollpoint.charAt(0) === '+') { - this.absolute = this.percent; - this.shift = parseFloat(scrollpoint.substr(1)); - } else { - var parsed = parseFloat(scrollpoint); - if (!isNaN(parsed) && isFinite(parsed)) { - this.absolute = true; - this.shift = parsed; - } - } - } else if (typeof (scrollpoint) === 'number') { - this.setScrollpoint(scrollpoint.toString()); - return; - } + this.default_edge = parseScrollpoint(scrollpoint); }; this.setClass = function(_class){ @@ -121,7 +161,7 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti } else if(angular.isArray(edges)){ this.edges = {}; - for(var i in edges){ + for(var i=0; i < edges.length; i++){ this.addEdge(edges[i]); } } @@ -133,7 +173,8 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti } else{ // default - this.edges = {top: true}; + this.edges = {}; + this.addEdge('top'); } }; @@ -152,63 +193,117 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti } }; - this.scrollEdgeHit = function(){ - var offset, hitEdge, flipOffset; - for(var scroll_edge in this.edges){ - var scroll_top = (scroll_edge == 'top'); - var scroll_bottom = (scroll_edge == 'bottom'); - - var elem_edge = this.edges[scroll_edge]; - var elem_top = (elem_edge == 'top'); - var elem_bottom = (elem_edge == 'bottom'); - if(elem_edge === true){ - if(scroll_top){ elem_top = true; } - if(scroll_bottom){ elem_bottom = true; } + this.getEdge = function(scroll_edge, element_edge){ + if(scroll_edge && element_edge){ + if(this.edges[scroll_edge] && this.edges[scroll_edge][element_edge] && this.edges[scroll_edge][element_edge] !== true){ + return this.edges[scroll_edge][element_edge]; + } + } + else if(scroll_edge && !element_edge){ + if(this.edges[scroll_edge]){ + return this.edges[scroll_edge]; } + return; + } + return this.default_edge; + }; - var scrollOffset = this.getScrollOffset(); + this.checkOffset = function(scroll_edge, elem_edge, edge){ + var offset; + if(!edge){ + edge = this.default_edge; + } + + var scroll_bottom = (scroll_edge == 'bottom'); + var elem_top = (elem_edge == 'top'); + var elem_bottom = (elem_edge == 'bottom'); + + var scrollOffset = this.getScrollOffset(); + if(scroll_bottom){ + scrollOffset += this.getTargetHeight(); + } + + var checkOffset; + if(edge.absolute){ + if(edge.percent){ + checkOffset = edge.shift / 100.0 * this.getTargetScrollHeight(); + } + else{ + checkOffset = edge.shift; + } if(scroll_bottom){ - scrollOffset += this.getTargetHeight(); + checkOffset = this.getTargetContentHeight() - checkOffset; + if(this.hasTarget){ + checkOffset += this.getTargetHeight(); + } } + } + else{ + if(elem_top){ + checkOffset = this.getElementTop(); + } + else if(elem_bottom){ + checkOffset = this.getElementBottom(); + } + checkOffset += edge.shift; + } - var checkOffset; - if(this.absolute){ - if(this.percent){ - checkOffset = this.shift / 100.0 * this.getTargetScrollHeight(); - } - else{ - checkOffset = this.shift; - } - if(scroll_bottom){ - checkOffset = this.getTargetContentHeight() - checkOffset; - if(this.hasTarget){ - checkOffset += this.getTargetHeight(); + offset = (scrollOffset - checkOffset); + if(scroll_bottom){ + offset *= -1.0; + } + return offset; + }; + + this.scrollEdgeHit = function(){ + var offset, edgeHit, absEdges, absEdgeHits; + var edge, scroll_edge, element_edge; + absEdges = 0; + absEdgeHits = {}; + for(scroll_edge in this.edges){ + for(element_edge in this.edges[scroll_edge]){ + edge = this.getEdge(scroll_edge, element_edge); + var edge_offset = this.checkOffset(scroll_edge, element_edge, edge); + + if(edge.absolute){ + if(angular.isUndefined(absEdgeHits)){ + absEdgeHits = {}; } + if(angular.isUndefined(absEdgeHits[scroll_edge])){ + absEdgeHits[scroll_edge] = {}; + } + absEdgeHits[scroll_edge][element_edge] = edge_offset; + absEdges++; } - } - else{ - if(elem_top){ - checkOffset = this.getElementTop(); - } - else if(elem_bottom){ - checkOffset = this.getElementBottom(); + else if(angular.isUndefined(offset) || edge_offset > offset){ + offset = edge_offset; + edgeHit = {scroll: scroll_edge, element: element_edge}; } - checkOffset += this.shift; } - - var edge_offset = (scrollOffset - checkOffset); - if(scroll_bottom){ - edge_offset *= -1.0; + } + // special handling for absolute edges when no relative edges hit + if(absEdges && !edgeHit){ + // in case there is more than one absolute edge, they all should pass to count a hit (allows for creating ranges where the scrollpoint is active) + var allPass = true; + offset = undefined; + for(scroll_edge in absEdgeHits){ + for(element_edge in absEdgeHits[scroll_edge]){ + if(absEdges > 1 && absEdgeHits[scroll_edge][element_edge] < 0){ + allPass = false; + } + else if(angular.isUndefined(offset) || absEdgeHits[scroll_edge][element_edge] > offset){ + offset = absEdgeHits[scroll_edge][element_edge]; + edgeHit = {scroll: scroll_edge, element: element_edge}; + } + } } - - if(angular.isUndefined(offset) || edge_offset > offset){ - offset = edge_offset; - hitEdge = scroll_edge; - flipOffset = (scroll_bottom && this.absolute); + if(!allPass){ + edgeHit = undefined; + offset = undefined; } } - this.hitEdge = (offset >= 0) ? hitEdge : undefined; - return offset*(flipOffset?-1.0:1.0); + this.hitEdge = ((offset >= 0) ? edgeHit : undefined); + return offset; }; this.getScrollOffset = function(){ @@ -245,12 +340,66 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti this.cachePosition = function(){ this.posCache.top = this.getElementTop(true); }; + + this.onScroll = function() { + if(!self.ready || !self.enabled){ return; } + + var edgeHit = self.scrollEdgeHit(); + + // edgeHit >= 0 - scrollpoint is scrolled out of active view + // edgeHit < 0 - scrollpoint is in active view + + // hit is toggled at the moment the scrollpoint is crossed + + var fireActions = false; + + if(edgeHit >= 0){ + // SCROLLPOINT is OUT by edgeHit pixels + if(!self.hit){ + // add the scrollpoint class + if(!self.$element.hasClass(self.scrollpointClass)){ + self.$element.addClass(self.scrollpointClass); + } + fireActions = true; + self.hit = true; + } + } + else{ + // SCROLLPOINT is IN by edgeHit pixels + if(self.hit || angular.isUndefined(self.hit)){ + // remove the scrollpoint class + if(self.$element.hasClass(self.scrollpointClass)){ + self.$element.removeClass(self.scrollpointClass); + } + fireActions = true; + self.hit = false; + } + self.cachePosition(); + } + + if(fireActions){ + // fire the actions + if(self.actions){ + for(var i=0; i < self.actions.length; i++){ + self.actions[i](edgeHit, self.$element, (self.hitEdge ? self.hitEdge.scroll : undefined), (self.hitEdge ? self.hitEdge.element : undefined)); + } + } + } + }; + + this.reset = function(){ + $timeout(function(){ + self.$element.removeClass(self.scrollpointClass); + self.hit = undefined; + self.hitEdge = undefined; + self.cachePosition(); + self.onScroll(); + }); + }; }, link: function (scope, elm, attrs, Ctrl) { var uiScrollpoint = Ctrl[0]; var uiScrollpointTarget = Ctrl[1]; - var ready = false; - var hit = false; var absoluteParent = false; uiScrollpoint.setElement(elm); @@ -259,14 +408,14 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // base ui-scrollpoint (leave blank or set to: absolute, +, -, or %) attrs.$observe('uiScrollpoint', function(scrollpoint){ uiScrollpoint.setScrollpoint(scrollpoint); - onScroll(); + uiScrollpoint.reset(); }); // ui-scrollpoint-enabled allows disabling the scrollpoint attrs.$observe('uiScrollpointEnabled', function(scrollpointEnabled){ scrollpointEnabled = scope.$eval(scrollpointEnabled); if(scrollpointEnabled != uiScrollpoint.enabled){ - reset(); + uiScrollpoint.reset(); } uiScrollpoint.enabled = scrollpointEnabled; }); @@ -276,11 +425,11 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti scrollpointAbsolute = scope.$eval(scrollpointAbsolute); if(scrollpointAbsolute != absoluteParent){ if(uiScrollpoint.$target){ - uiScrollpoint.$target.off('scroll', onScroll); + uiScrollpoint.$target.off('scroll', uiScrollpoint.onScroll); } uiScrollpoint.setTarget( (!scrollpointAbsolute && uiScrollpointTarget) ? uiScrollpointTarget.$element : null); resetTarget(); - reset(); + uiScrollpoint.reset(); } absoluteParent = scrollpointAbsolute; }); @@ -295,9 +444,9 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // ui-scrollpoint-class class to add instead of ui-scrollpoint attrs.$observe('uiScrollpointClass', function(scrollpointClass){ + elm.removeClass(uiScrollpoint.scrollpointClass); uiScrollpoint.setClass(scrollpointClass); - hit = false; - onScroll(); + uiScrollpoint.reset(); }); // ui-scrollpoint-edge allows configuring which element and scroll edges match @@ -312,74 +461,19 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // assign it in controller uiScrollpoint.setEdges(scrollpointEdge); + uiScrollpoint.reset(); } }); - function onScroll() { - if(!ready || !uiScrollpoint.enabled){ return; } - - var hitEdge = uiScrollpoint.hitEdge; // which edge did scrollpoint trigger at before - var edgeHit = uiScrollpoint.scrollEdgeHit(); - - // edgeHit >= 0 - scrollpoint is scrolled out of active view - // edgeHit < 0 - scrollpoint is in active view - - // hit is toggled at the moment the scrollpoint is crossed - - var fireActions = false; - - if(edgeHit >= 0){ - // SCROLLPOINT is OUT by edgeHit pixels - if(!hit){ - // add the scrollpoint class - if(!elm.hasClass(uiScrollpoint.scrollpointClass)){ - elm.addClass(uiScrollpoint.scrollpointClass); - } - fireActions = true; - hit = true; - } - } - else{ - // SCROLLPOINT is IN by edgeHit pixels - if(hit || angular.isUndefined(hit)){ - // remove the scrollpoint class - if(elm.hasClass(uiScrollpoint.scrollpointClass)){ - elm.removeClass(uiScrollpoint.scrollpointClass); - } - fireActions = true; - hit = false; - } - uiScrollpoint.cachePosition(); - } - - if(fireActions){ - // fire the actions - if(uiScrollpoint.actions){ - for(var i in uiScrollpoint.actions){ - uiScrollpoint.actions[i](edgeHit, elm, uiScrollpoint.hitEdge || hitEdge); - } - } - } - } - - function reset() { - $timeout(function(){ - elm.removeClass(uiScrollpoint.scrollpointClass); - hit = undefined; - uiScrollpoint.hitEdge = undefined; - uiScrollpoint.cachePosition(); - onScroll(); - }); - } function resetTarget() { - uiScrollpoint.$target.on('scroll', onScroll); + uiScrollpoint.$target.on('scroll', uiScrollpoint.onScroll); scope.$on('$destroy', function () { - uiScrollpoint.$target.off('scroll', onScroll); + uiScrollpoint.$target.off('scroll', uiScrollpoint.onScroll); }); } resetTarget(); - elm.ready(function(){ ready=true; onScroll(); }); - scope.$on('scrollpointShouldReset', reset); + elm.ready(function(){ uiScrollpoint.ready=true; uiScrollpoint.onScroll(); }); + scope.$on('scrollpointShouldReset', uiScrollpoint.reset); } }; }]).directive('uiScrollpointTarget', [function () { diff --git a/dist/scrollpoint.min.js b/dist/scrollpoint.min.js index 02650c7..7898bd8 100644 --- a/dist/scrollpoint.min.js +++ b/dist/scrollpoint.min.js @@ -1,7 +1,7 @@ /*! * angular-ui-scrollpoint * https://github.com/angular-ui/ui-scrollpoint - * Version: 2.0.2 - 2016-01-03T02:19:46.904Z + * Version: 2.1.1 - 2016-02-22T01:35:08.609Z * License: MIT */ -!function(){"use strict";angular.module("ui.scrollpoint",[]).directive("uiScrollpoint",["$window","$timeout",function(t,e){function i(){if(angular.isDefined(t.pageYOffset))return t.pageYOffset;var e=document.compatMode&&"BackCompat"!==document.compatMode?document.documentElement:document.body;return e.scrollTop}function s(){return t.document.body.scrollHeight-t.innerHeight}function n(e){return e?t.document.body.clientHeight:t.innerHeight}return{require:["uiScrollpoint","^?uiScrollpointTarget"],controller:function(){this.$element=void 0,this.$target=void 0,this.hasTarget=!1,this.edges={top:!0},this.hitEdge=void 0,this.absolute=!0,this.percent=!1,this.shift=0,this.posCache={},this.enabled=!0,this.scrollpointClass="ui-scrollpoint",this.actions=void 0,this.addEdge=function(t,e){angular.isString(t)&&(angular.isUndefined(e)&&(e=!0),"view"==t?(this.addEdge("top","bottom"),this.addEdge("bottom","top")):this.edges[t]=e)},this.addAction=function(t){t&&angular.isFunction(t)&&(angular.isUndefined(this.actions)?this.actions=[t]:-1==this.actions.indexOf(t)&&this.actions.push(t))},this.setScrollpoint=function(t){if(t){if("string"==typeof t)if(this.percent="%"==t.charAt(t.length-1),this.percent&&(t=t.substr(0,t.length-1)),"-"===t.charAt(0))this.absolute=this.percent,this.shift=-parseFloat(t.substr(1));else if("+"===t.charAt(0))this.absolute=this.percent,this.shift=parseFloat(t.substr(1));else{var e=parseFloat(t);!isNaN(e)&&isFinite(e)&&(this.absolute=!0,this.shift=e)}else if("number"==typeof t)return void this.setScrollpoint(t.toString())}else this.absolute=!1,this.percent=!1,this.shift=0},this.setClass=function(t){t||(t="ui-scrollpoint"),this.scrollpointClass=t},this.setEdges=function(t){if(angular.isString(t))this.edges={},this.addEdge(t);else if(angular.isArray(t)){this.edges={};for(var e in t)this.addEdge(t[e])}else if(angular.isObject(t)){this.edges={};for(var i in t)this.addEdge(i,t[i])}else this.edges={top:!0}},this.setElement=function(t){this.$element=t},this.setTarget=function(e){e?(this.$target=e,this.hasTarget=!0):(this.$target=angular.element(t),this.hasTarget=!1)},this.scrollEdgeHit=function(){var t,e,i;for(var s in this.edges){var n="top"==s,o="bottom"==s,r=this.edges[s],l="top"==r,a="bottom"==r;r===!0&&(n&&(l=!0),o&&(a=!0));var h=this.getScrollOffset();o&&(h+=this.getTargetHeight());var c;this.absolute?(c=this.percent?this.shift/100*this.getTargetScrollHeight():this.shift,o&&(c=this.getTargetContentHeight()-c,this.hasTarget&&(c+=this.getTargetHeight()))):(l?c=this.getElementTop():a&&(c=this.getElementBottom()),c+=this.shift);var g=h-c;o&&(g*=-1),(angular.isUndefined(t)||g>t)&&(t=g,e=s,i=o&&this.absolute)}return this.hitEdge=t>=0?e:void 0,t*(i?-1:1)},this.getScrollOffset=function(){return this.hasTarget?this.$target[0].scrollTop:i()},this.getTargetHeight=function(){return this.hasTarget?this.$target[0].offsetHeight:n()},this.getTargetContentHeight=function(){return this.hasTarget?this.$target[0].scrollHeight-this.$target[0].clientHeight:n(!0)},this.getTargetScrollHeight=function(){return this.hasTarget?this.$target[0].scrollHeight-this.$target[0].clientHeight:s()},this.getElementTop=function(t){if(!t&&angular.isDefined(this.posCache.top))return this.posCache.top;var e=this.$element[0].getBoundingClientRect(),i=e.top+this.getScrollOffset();if(this.hasTarget){var s=this.$target[0].getBoundingClientRect();i-=s.top}return i},this.getElementBottom=function(t){return this.getElementTop(t)+this.$element[0].offsetHeight},this.cachePosition=function(){this.posCache.top=this.getElementTop(!0)}},link:function(t,i,s,n){function o(){if(c&&a.enabled){var t=a.hitEdge,e=a.scrollEdgeHit(),s=!1;if(e>=0?g||(i.hasClass(a.scrollpointClass)||i.addClass(a.scrollpointClass),s=!0,g=!0):((g||angular.isUndefined(g))&&(i.hasClass(a.scrollpointClass)&&i.removeClass(a.scrollpointClass),s=!0,g=!1),a.cachePosition()),s&&a.actions)for(var n in a.actions)a.actions[n](e,i,a.hitEdge||t)}}function r(){e(function(){i.removeClass(a.scrollpointClass),g=void 0,a.hitEdge=void 0,a.cachePosition(),o()})}function l(){a.$target.on("scroll",o),t.$on("$destroy",function(){a.$target.off("scroll",o)})}var a=n[0],h=n[1],c=!1,g=!1,u=!1;a.setElement(i),a.setTarget(h?h.$element:null),s.$observe("uiScrollpoint",function(t){a.setScrollpoint(t),o()}),s.$observe("uiScrollpointEnabled",function(e){e=t.$eval(e),e!=a.enabled&&r(),a.enabled=e}),s.$observe("uiScrollpointAbsolute",function(e){e=t.$eval(e),e!=u&&(a.$target&&a.$target.off("scroll",o),a.setTarget(!e&&h?h.$element:null),l(),r()),u=e}),s.$observe("uiScrollpointAction",function(e){var i=t.$eval(e);i&&angular.isFunction(i)&&a.addAction(i)}),s.$observe("uiScrollpointClass",function(t){a.setClass(t),g=!1,o()}),s.$observe("uiScrollpointEdge",function(e){if(e){var i=["top","bottom","view"];-1==i.indexOf(e)&&(e=t.$eval(e)),a.setEdges(e)}}),l(),i.ready(function(){c=!0,o()}),t.$on("scrollpointShouldReset",r)}}}]).directive("uiScrollpointTarget",[function(){return{controller:["$element",function(t){this.$element=t}]}}])}(); \ No newline at end of file +!function(){"use strict";angular.module("ui.scrollpoint",[]).directive("uiScrollpoint",["$window","$timeout",function(t,e){function i(){if(angular.isDefined(t.pageYOffset))return t.pageYOffset;var e=document.compatMode&&"BackCompat"!==document.compatMode?document.documentElement:document.body;return e.scrollTop}function s(){return t.document.body.scrollHeight-t.innerHeight}function n(e){return e?t.document.body.clientHeight:t.innerHeight}return{require:["uiScrollpoint","^?uiScrollpointTarget"],controller:function(){function o(t){var e={shift:0,absolute:!1,percent:!1};if(t&&angular.isString(t))if(e.percent="%"==t.charAt(t.length-1),e.percent&&(t=t.substr(0,t.length-1)),"-"===t.charAt(0))e.absolute=e.percent,e.shift=-parseFloat(t.substr(1));else if("+"===t.charAt(0))e.absolute=e.percent,e.shift=parseFloat(t.substr(1));else{var i=parseFloat(t);!isNaN(i)&&isFinite(i)&&(e.absolute=!0,e.shift=i)}else if(angular.isNumber(t))return o(t.toString());return e}var r=this;this.$element=void 0,this.$target=void 0,this.hasTarget=!1,this.hit=void 0,this.edges={top:{top:!0}},this.hitEdge=void 0,this.default_edge={absolute:!1,percent:!1,shift:0},this.posCache={},this.ready=!1,this.enabled=!0,this.scrollpointClass="ui-scrollpoint",this.actions=void 0,this.addEdge=function(t,e){if(angular.isString(t))if(angular.isUndefined(e)&&(e=!0),"view"==t)this.addEdge("top","bottom"),this.addEdge("bottom","top");else{var i,s;if(angular.isObject(e))for(i in e)e[i]=e[i]===!0?!0:o(e[i]);else"top"==e||"bottom"==e?(i=e,s=o(),e={},e[i]=s):e===!0?(e={},e[t]=!0):(s=o(e),e={},e[t]=s);this.edges[t]=e}},this.addAction=function(t){t&&angular.isFunction(t)&&(angular.isUndefined(this.actions)?this.actions=[t]:-1==this.actions.indexOf(t)&&this.actions.push(t))},this.setScrollpoint=function(t){this.default_edge=o(t)},this.setClass=function(t){t||(t="ui-scrollpoint"),this.scrollpointClass=t},this.setEdges=function(t){if(angular.isString(t))this.edges={},this.addEdge(t);else if(angular.isArray(t)){this.edges={};for(var e=0;et)&&(t=l,e={scroll:o,element:r})}if(i&&!e){var a=!0;t=void 0;for(o in s)for(r in s[o])i>1&&s[o][r]<0?a=!1:(angular.isUndefined(t)||s[o][r]>t)&&(t=s[o][r],e={scroll:o,element:r});a||(e=void 0,t=void 0)}return this.hitEdge=t>=0?e:void 0,t},this.getScrollOffset=function(){return this.hasTarget?this.$target[0].scrollTop:i()},this.getTargetHeight=function(){return this.hasTarget?this.$target[0].offsetHeight:n()},this.getTargetContentHeight=function(){return this.hasTarget?this.$target[0].scrollHeight-this.$target[0].clientHeight:n(!0)},this.getTargetScrollHeight=function(){return this.hasTarget?this.$target[0].scrollHeight-this.$target[0].clientHeight:s()},this.getElementTop=function(t){if(!t&&angular.isDefined(this.posCache.top))return this.posCache.top;var e=this.$element[0].getBoundingClientRect(),i=e.top+this.getScrollOffset();if(this.hasTarget){var s=this.$target[0].getBoundingClientRect();i-=s.top}return i},this.getElementBottom=function(t){return this.getElementTop(t)+this.$element[0].offsetHeight},this.cachePosition=function(){this.posCache.top=this.getElementTop(!0)},this.onScroll=function(){if(r.ready&&r.enabled){var t=r.scrollEdgeHit(),e=!1;if(t>=0?r.hit||(r.$element.hasClass(r.scrollpointClass)||r.$element.addClass(r.scrollpointClass),e=!0,r.hit=!0):((r.hit||angular.isUndefined(r.hit))&&(r.$element.hasClass(r.scrollpointClass)&&r.$element.removeClass(r.scrollpointClass),e=!0,r.hit=!1),r.cachePosition()),e&&r.actions)for(var i=0;i offset){ + offset = edge_offset; + edgeHit = {scroll: scroll_edge, element: element_edge}; } - checkOffset += this.shift; } - - var edge_offset = (scrollOffset - checkOffset); - if(scroll_bottom){ - edge_offset *= -1.0; + } + // special handling for absolute edges when no relative edges hit + if(absEdges && !edgeHit){ + // in case there is more than one absolute edge, they all should pass to count a hit (allows for creating ranges where the scrollpoint is active) + var allPass = true; + offset = undefined; + for(scroll_edge in absEdgeHits){ + for(element_edge in absEdgeHits[scroll_edge]){ + if(absEdges > 1 && absEdgeHits[scroll_edge][element_edge] < 0){ + allPass = false; + } + else if(angular.isUndefined(offset) || absEdgeHits[scroll_edge][element_edge] > offset){ + offset = absEdgeHits[scroll_edge][element_edge]; + edgeHit = {scroll: scroll_edge, element: element_edge}; + } + } } - - if(angular.isUndefined(offset) || edge_offset > offset){ - offset = edge_offset; - hitEdge = scroll_edge; - flipOffset = (scroll_bottom && this.absolute); + if(!allPass){ + edgeHit = undefined; + offset = undefined; } } - this.hitEdge = (offset >= 0) ? hitEdge : undefined; - return offset*(flipOffset?-1.0:1.0); + this.hitEdge = ((offset >= 0) ? edgeHit : undefined); + return offset; }; this.getScrollOffset = function(){ @@ -235,12 +330,66 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti this.cachePosition = function(){ this.posCache.top = this.getElementTop(true); }; + + this.onScroll = function() { + if(!self.ready || !self.enabled){ return; } + + var edgeHit = self.scrollEdgeHit(); + + // edgeHit >= 0 - scrollpoint is scrolled out of active view + // edgeHit < 0 - scrollpoint is in active view + + // hit is toggled at the moment the scrollpoint is crossed + + var fireActions = false; + + if(edgeHit >= 0){ + // SCROLLPOINT is OUT by edgeHit pixels + if(!self.hit){ + // add the scrollpoint class + if(!self.$element.hasClass(self.scrollpointClass)){ + self.$element.addClass(self.scrollpointClass); + } + fireActions = true; + self.hit = true; + } + } + else{ + // SCROLLPOINT is IN by edgeHit pixels + if(self.hit || angular.isUndefined(self.hit)){ + // remove the scrollpoint class + if(self.$element.hasClass(self.scrollpointClass)){ + self.$element.removeClass(self.scrollpointClass); + } + fireActions = true; + self.hit = false; + } + self.cachePosition(); + } + + if(fireActions){ + // fire the actions + if(self.actions){ + for(var i=0; i < self.actions.length; i++){ + self.actions[i](edgeHit, self.$element, (self.hitEdge ? self.hitEdge.scroll : undefined), (self.hitEdge ? self.hitEdge.element : undefined)); + } + } + } + }; + + this.reset = function(){ + $timeout(function(){ + self.$element.removeClass(self.scrollpointClass); + self.hit = undefined; + self.hitEdge = undefined; + self.cachePosition(); + self.onScroll(); + }); + }; }, link: function (scope, elm, attrs, Ctrl) { var uiScrollpoint = Ctrl[0]; var uiScrollpointTarget = Ctrl[1]; - var ready = false; - var hit = false; var absoluteParent = false; uiScrollpoint.setElement(elm); @@ -249,14 +398,14 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // base ui-scrollpoint (leave blank or set to: absolute, +, -, or %) attrs.$observe('uiScrollpoint', function(scrollpoint){ uiScrollpoint.setScrollpoint(scrollpoint); - onScroll(); + uiScrollpoint.reset(); }); // ui-scrollpoint-enabled allows disabling the scrollpoint attrs.$observe('uiScrollpointEnabled', function(scrollpointEnabled){ scrollpointEnabled = scope.$eval(scrollpointEnabled); if(scrollpointEnabled != uiScrollpoint.enabled){ - reset(); + uiScrollpoint.reset(); } uiScrollpoint.enabled = scrollpointEnabled; }); @@ -266,11 +415,11 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti scrollpointAbsolute = scope.$eval(scrollpointAbsolute); if(scrollpointAbsolute != absoluteParent){ if(uiScrollpoint.$target){ - uiScrollpoint.$target.off('scroll', onScroll); + uiScrollpoint.$target.off('scroll', uiScrollpoint.onScroll); } uiScrollpoint.setTarget( (!scrollpointAbsolute && uiScrollpointTarget) ? uiScrollpointTarget.$element : null); resetTarget(); - reset(); + uiScrollpoint.reset(); } absoluteParent = scrollpointAbsolute; }); @@ -285,9 +434,9 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // ui-scrollpoint-class class to add instead of ui-scrollpoint attrs.$observe('uiScrollpointClass', function(scrollpointClass){ + elm.removeClass(uiScrollpoint.scrollpointClass); uiScrollpoint.setClass(scrollpointClass); - hit = false; - onScroll(); + uiScrollpoint.reset(); }); // ui-scrollpoint-edge allows configuring which element and scroll edges match @@ -302,74 +451,19 @@ angular.module('ui.scrollpoint', []).directive('uiScrollpoint', ['$window', '$ti // assign it in controller uiScrollpoint.setEdges(scrollpointEdge); + uiScrollpoint.reset(); } }); - function onScroll() { - if(!ready || !uiScrollpoint.enabled){ return; } - - var hitEdge = uiScrollpoint.hitEdge; // which edge did scrollpoint trigger at before - var edgeHit = uiScrollpoint.scrollEdgeHit(); - - // edgeHit >= 0 - scrollpoint is scrolled out of active view - // edgeHit < 0 - scrollpoint is in active view - - // hit is toggled at the moment the scrollpoint is crossed - - var fireActions = false; - - if(edgeHit >= 0){ - // SCROLLPOINT is OUT by edgeHit pixels - if(!hit){ - // add the scrollpoint class - if(!elm.hasClass(uiScrollpoint.scrollpointClass)){ - elm.addClass(uiScrollpoint.scrollpointClass); - } - fireActions = true; - hit = true; - } - } - else{ - // SCROLLPOINT is IN by edgeHit pixels - if(hit || angular.isUndefined(hit)){ - // remove the scrollpoint class - if(elm.hasClass(uiScrollpoint.scrollpointClass)){ - elm.removeClass(uiScrollpoint.scrollpointClass); - } - fireActions = true; - hit = false; - } - uiScrollpoint.cachePosition(); - } - - if(fireActions){ - // fire the actions - if(uiScrollpoint.actions){ - for(var i in uiScrollpoint.actions){ - uiScrollpoint.actions[i](edgeHit, elm, uiScrollpoint.hitEdge || hitEdge); - } - } - } - } - - function reset() { - $timeout(function(){ - elm.removeClass(uiScrollpoint.scrollpointClass); - hit = undefined; - uiScrollpoint.hitEdge = undefined; - uiScrollpoint.cachePosition(); - onScroll(); - }); - } function resetTarget() { - uiScrollpoint.$target.on('scroll', onScroll); + uiScrollpoint.$target.on('scroll', uiScrollpoint.onScroll); scope.$on('$destroy', function () { - uiScrollpoint.$target.off('scroll', onScroll); + uiScrollpoint.$target.off('scroll', uiScrollpoint.onScroll); }); } resetTarget(); - elm.ready(function(){ ready=true; onScroll(); }); - scope.$on('scrollpointShouldReset', reset); + elm.ready(function(){ uiScrollpoint.ready=true; uiScrollpoint.onScroll(); }); + scope.$on('scrollpointShouldReset', uiScrollpoint.reset); } }; }]).directive('uiScrollpointTarget', [function () {