angular-sticky-table-header
Advanced tools
Comparing version
{ | ||
"name": "angular-sticky-table-header", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "Sticky headers for tables", | ||
@@ -14,3 +14,3 @@ "main": "dist/angular-sticky-table-header.js", | ||
"jquery": "^1.11", | ||
"watch-dom": "^0.0" | ||
"watch-dom": "~0.0" | ||
}, | ||
@@ -17,0 +17,0 @@ "keywords": [ |
@@ -20,3 +20,3 @@ angular.module('watchDom', []).constant('watchDomOptions', { | ||
mutationObserver.observe(element, angular.extend({}, watchDomOptions, options)); | ||
return mutationObserver.disconnect; | ||
return mutationObserver.disconnect.bind(mutationObserver); | ||
}; | ||
@@ -46,5 +46,11 @@ } | ||
restrict: 'A', | ||
link: function (scope, element, attrs) { | ||
scope: { | ||
disabled: '=', | ||
rows: '=' | ||
}, | ||
template: '<div ng-transclude></div>', | ||
transclude: true, | ||
link: function (scope, element) { | ||
angular.extend(scope, { | ||
isStuck: false, | ||
stuck: false, | ||
mutationObserver: null, | ||
@@ -63,3 +69,3 @@ offset: {}, | ||
removeClones: function () { | ||
scope.isStuck = false; | ||
scope.stuck = false; | ||
element.find('.' + options.cloneClassName).remove(); | ||
@@ -80,7 +86,7 @@ }, | ||
setOffset: function () { | ||
scope.offset = scope.tr.getBoundingClientRect(); | ||
scope.offset = angular.element(scope.tr).offset(); | ||
}, | ||
setStuck: function (bool) { | ||
scope.$apply(function () { | ||
scope.isStuck = !!bool; | ||
scope.stuck = !!bool; | ||
}); | ||
@@ -98,6 +104,6 @@ }, | ||
var scroll = $window.scrollY; | ||
if (!scope.isStuck && scroll >= scope.offset.top) { | ||
if (!scope.stuck && scroll >= scope.offset.top) { | ||
scope.setClonedCellWidths(); | ||
scope.setStuck(true); | ||
} else if (scope.isStuck && scroll < scope.offset.top) { | ||
} else if (scope.stuck && scroll < scope.offset.top) { | ||
scope.setStuck(false); | ||
@@ -114,24 +120,43 @@ } | ||
}); | ||
}, | ||
on: function () { | ||
scope.observeTr(); | ||
scope.addEvents(); | ||
}, | ||
off: function () { | ||
scope.mutationObserver(); | ||
scope.removeEvents(); | ||
scope.removeClones(); | ||
}, | ||
addEvents: function () { | ||
angular.element($window).on({ | ||
'resize.angularStickyTableHeader': _.debounce(scope.setClonedCellWidths.bind(scope), options.interval), | ||
'scroll.angularStickyTableHeader': _.debounce(scope.checkScroll.bind(scope), options.interval) | ||
}); | ||
}, | ||
removeEvents: function () { | ||
angular.element($window).off('.angularStickyTableHeader'); | ||
}, | ||
changeDisabled: function (disabled, old) { | ||
if (disabled === old) { | ||
return; | ||
} | ||
if (disabled) { | ||
scope.off(); | ||
} else { | ||
scope.on(); | ||
scope.resetClone(); | ||
} | ||
} | ||
}); | ||
// enable/disable api | ||
scope.$watch('disabled', scope.changeDisabled); | ||
// watch rows, and re-measure column widths when they change | ||
if (attrs.rows) { | ||
scope.$watch(function () { | ||
return scope[attrs.rows]; | ||
}, scope.rowsChanged); | ||
} | ||
scope.$watch('rows', scope.rowsChanged); | ||
// fired when stuck state changes | ||
scope.$watch('isStuck', scope.toggleClone); | ||
// start observing header for DOM changes | ||
scope.observeTr(); | ||
// listen on window resize event | ||
angular.element($window).on({ | ||
'resize.angularStickyTableHeader': _.debounce(scope.setClonedCellWidths.bind(scope), options.interval), | ||
'scroll.angularStickyTableHeader': _.debounce(scope.checkScroll.bind(scope), options.interval) | ||
}); | ||
scope.$watch('stuck', scope.toggleClone); | ||
// teardown | ||
scope.$on('$destroy', function () { | ||
angular.element($window).off('.angularStickyTableHeader'); | ||
scope.mutationObserver(); | ||
}); | ||
scope.$on('$destroy', scope.off); | ||
// init | ||
scope.on(); | ||
// helpers | ||
@@ -138,0 +163,0 @@ function ifClone(fn) { |
{ | ||
"name": "angular-sticky-table-header", | ||
"version": "0.1.3", | ||
"version": "0.2.0", | ||
"description": "Sticky headers for tables", | ||
@@ -5,0 +5,0 @@ "main": "dist/angular-sticky-table-header.js", |
@@ -23,3 +23,7 @@ angular-sticky-table-header [](https://travis-ci.org/turn/angular-sticky-table-header) | ||
<div ng-controller="fooCtrl"> | ||
<div sticky-table-header rows="rowCollection"> | ||
<div | ||
sticky-table-header | ||
rows="rowCollection" | ||
disabled="expression" | ||
> | ||
<table> | ||
@@ -46,2 +50,4 @@ ... | ||
$scope.expression = false; | ||
}); | ||
@@ -48,0 +54,0 @@ ``` |
@@ -25,3 +25,9 @@ angular | ||
restrict: 'A', | ||
link: function (scope, element, attrs) { | ||
scope: { | ||
disabled: '=', | ||
rows: '=' | ||
}, | ||
template: '<div ng-transclude></div>', | ||
transclude: true, | ||
link: function (scope, element) { | ||
@@ -31,3 +37,3 @@ angular.extend(scope, { | ||
// show the cloned <tr>? | ||
isStuck: false, | ||
stuck: false, | ||
@@ -66,3 +72,3 @@ // MutationObserver bound to the original <tr> | ||
scope.isStuck = false; | ||
scope.stuck = false; | ||
@@ -96,3 +102,3 @@ element | ||
scope.offset = scope.tr.getBoundingClientRect(); | ||
scope.offset = angular.element(scope.tr).offset(); | ||
@@ -104,3 +110,3 @@ }, | ||
scope.$apply(function(){ | ||
scope.isStuck = !!bool; | ||
scope.stuck = !!bool; | ||
}); | ||
@@ -128,6 +134,6 @@ | ||
if (!scope.isStuck && scroll >= scope.offset.top) { | ||
if (!scope.stuck && scroll >= scope.offset.top) { | ||
scope.setClonedCellWidths(); | ||
scope.setStuck(true); | ||
} else if (scope.isStuck && scroll < scope.offset.top) { | ||
} else if (scope.stuck && scroll < scope.offset.top) { | ||
scope.setStuck(false); | ||
@@ -155,34 +161,67 @@ } | ||
}, | ||
on: function () { | ||
scope.observeTr(); | ||
scope.addEvents(); | ||
}, | ||
off: function () { | ||
scope.mutationObserver(); | ||
scope.removeEvents(); | ||
scope.removeClones(); | ||
}, | ||
addEvents: function () { | ||
angular.element($window).on({ | ||
'resize.angularStickyTableHeader': _.debounce(scope.setClonedCellWidths.bind(scope), options.interval), | ||
'scroll.angularStickyTableHeader': _.debounce(scope.checkScroll.bind(scope), options.interval) | ||
}); | ||
}, | ||
removeEvents: function () { | ||
angular.element($window).off('.angularStickyTableHeader'); | ||
}, | ||
changeDisabled: function (disabled, old) { | ||
if (disabled === old) { | ||
return; | ||
} | ||
if (disabled) { | ||
scope.off(); | ||
} else { | ||
scope.on(); | ||
scope.resetClone(); | ||
} | ||
} | ||
}); | ||
// enable/disable api | ||
scope.$watch('disabled', scope.changeDisabled); | ||
// watch rows, and re-measure column widths when they change | ||
if (attrs.rows) { | ||
scope.$watch(function(){ | ||
return scope[attrs.rows]; | ||
}, scope.rowsChanged); | ||
} | ||
scope.$watch('rows', scope.rowsChanged); | ||
// fired when stuck state changes | ||
scope.$watch('isStuck', scope.toggleClone); | ||
scope.$watch('stuck', scope.toggleClone); | ||
// start observing header for DOM changes | ||
scope.observeTr(); | ||
// listen on window resize event | ||
angular.element($window).on({ | ||
'resize.angularStickyTableHeader': _.debounce(scope.setClonedCellWidths.bind(scope), options.interval), | ||
'scroll.angularStickyTableHeader': _.debounce(scope.checkScroll.bind(scope), options.interval) | ||
}); | ||
// teardown | ||
scope.$on('$destroy', function() { | ||
angular.element($window).off('.angularStickyTableHeader'); | ||
scope.mutationObserver(); | ||
}); | ||
scope.$on('$destroy', scope.off); | ||
// init | ||
scope.on(); | ||
// helpers | ||
function ifClone (fn) { | ||
@@ -189,0 +228,0 @@ return util.guard(fn, cloneExists); |
121
test/test.js
@@ -40,2 +40,3 @@ // Generated by CoffeeScript 1.7.1 | ||
_this.scope.$digest(); | ||
_this.scope = _this.element.scope(); | ||
return $window = { | ||
@@ -57,3 +58,3 @@ scrollY: 0, | ||
this.scope.createClone(); | ||
return expect(($((this.element.find('thead tr'))[1]).find('th')).length).toBe(this.scope.columnCollection.length); | ||
return expect(($((this.element.find('thead tr'))[1]).find('th')).length).toBe(this.scope.$parent.columnCollection.length); | ||
}); | ||
@@ -103,6 +104,6 @@ it('should clone the <tr>\'s events', function() { | ||
describe('#removeClones', function() { | ||
it('should set scope.isStuck to false', function() { | ||
it('should set scope.stuck to false', function() { | ||
this.scope.createClone(); | ||
this.scope.removeClones(); | ||
return expect(this.scope.isStuck).toBe(false); | ||
return expect(this.scope.stuck).toBe(false); | ||
}); | ||
@@ -133,10 +134,10 @@ return it('should remove all <tr> clones', function() { | ||
describe('#setOffset', function() { | ||
it('should call getBoundingClientRect on the first <tr>', function() { | ||
spyOn((this.element.find('tr'))[0], 'getBoundingClientRect'); | ||
it('should call getOffset on the first <tr>', function() { | ||
spyOn(($()).__proto__, 'offset'); | ||
this.scope.setOffset(); | ||
return expect((this.element.find('tr'))[0].getBoundingClientRect).toHaveBeenCalled(); | ||
return expect(($()).__proto__.offset).toHaveBeenCalled(); | ||
}); | ||
return it('should set scope.offset equal to the value returned by getBoundingClientRect', function() { | ||
this.scope.offset = null; | ||
spyOn((this.element.find('tr'))[0], 'getBoundingClientRect').andReturn('foo'); | ||
spyOn(($()).__proto__, 'offset').andReturn('foo'); | ||
this.scope.setOffset(); | ||
@@ -147,20 +148,20 @@ return expect(this.scope.offset).toEqual('foo'); | ||
describe('#setStuck', function() { | ||
it('should set scope.isStuck equal to the boolean passed into it', function() { | ||
this.scope.isStuck = null; | ||
it('should set scope.stuck equal to the boolean passed into it', function() { | ||
this.scope.stuck = null; | ||
this.scope.setStuck(true); | ||
return expect(this.scope.isStuck).toBe(true); | ||
return expect(this.scope.stuck).toBe(true); | ||
}); | ||
return it('should coerce non-boolean values into booleans', function() { | ||
this.scope.setStuck(true); | ||
expect(this.scope.isStuck).toBe(true); | ||
expect(this.scope.stuck).toBe(true); | ||
this.scope.setStuck('foo'); | ||
expect(this.scope.isStuck).toBe(true); | ||
expect(this.scope.stuck).toBe(true); | ||
this.scope.setStuck(42); | ||
expect(this.scope.isStuck).toBe(true); | ||
expect(this.scope.stuck).toBe(true); | ||
this.scope.setStuck(null); | ||
expect(this.scope.isStuck).toBe(false); | ||
expect(this.scope.stuck).toBe(false); | ||
this.scope.setStuck(0); | ||
expect(this.scope.isStuck).toBe(false); | ||
expect(this.scope.stuck).toBe(false); | ||
this.scope.setStuck(false); | ||
return expect(this.scope.isStuck).toBe(false); | ||
return expect(this.scope.stuck).toBe(false); | ||
}); | ||
@@ -214,5 +215,5 @@ }); | ||
}); | ||
it('should call #setStuck with true and #setClonedCellWidths with no arguments when scope.isStuck is false and scrollY is >= offset.top', function() { | ||
it('should call #setStuck with true and #setClonedCellWidths with no arguments when scope.stuck is false and scrollY is >= offset.top', function() { | ||
this.scope.clone = true; | ||
this.scope.isStuck = false; | ||
this.scope.stuck = false; | ||
this.scope.offset = { | ||
@@ -226,5 +227,5 @@ top: 0 | ||
}); | ||
it('should call #setStuck with false when scope.isStuck is true and scrollY is < offset.top', function() { | ||
it('should call #setStuck with false when scope.stuck is true and scrollY is < offset.top', function() { | ||
this.scope.clone = true; | ||
this.scope.isStuck = true; | ||
this.scope.stuck = true; | ||
this.scope.offset = { | ||
@@ -239,3 +240,3 @@ top: 1 | ||
this.scope.clone = true; | ||
this.scope.isStuck = true; | ||
this.scope.stuck = true; | ||
this.scope.offset = { | ||
@@ -246,3 +247,3 @@ top: 0 | ||
this.scope.checkScroll(); | ||
this.scope.isStuck = false; | ||
this.scope.stuck = false; | ||
this.scope.offset = { | ||
@@ -266,10 +267,80 @@ top: 1 | ||
}); | ||
return describe('$destroy', function() { | ||
return it('should remove the mutation observer', function() { | ||
describe('#on', function() { | ||
return it('should call #observeTr and #addEvents with no arguments', function() { | ||
spyOn(this.scope, 'observeTr'); | ||
spyOn(this.scope, 'addEvents'); | ||
this.scope.on(); | ||
expect(this.scope.observeTr).toHaveBeenCalledWith; | ||
return expect(this.scope.addEvents).toHaveBeenCalledWith; | ||
}); | ||
}); | ||
describe('#off', function() { | ||
return it('should call #mutationObserver, #removeEvents, and #removeClones with no arguments', function() { | ||
this.scope.mutationObserver = function() {}; | ||
spyOn(this.scope, 'mutationObserver'); | ||
spyOn(this.scope, 'removeEvents'); | ||
spyOn(this.scope, 'removeClones'); | ||
this.scope.off(); | ||
expect(this.scope.mutationObserver).toHaveBeenCalledWith; | ||
expect(this.scope.removeEvents).toHaveBeenCalledWith; | ||
return expect(this.scope.removeClones).toHaveBeenCalledWith; | ||
}); | ||
}); | ||
describe('#changeDisabled', function() { | ||
it('shouldn\'t call anything if the 1st argument is identical to the 2nd argument', function() { | ||
spyOn(this.scope, 'on'); | ||
spyOn(this.scope, 'off'); | ||
spyOn(this.scope, 'resetClone'); | ||
this.scope.changeDisabled(true, true); | ||
expect(this.scope.on).not.toHaveBeenCalled(); | ||
expect(this.scope.off).not.toHaveBeenCalled(); | ||
return expect(this.scope.resetClone).not.toHaveBeenCalled(); | ||
}); | ||
it('should call #off with no arguments if the 1st argument is truthy', function() { | ||
spyOn(this.scope, 'off'); | ||
this.scope.changeDisabled(true); | ||
return expect(this.scope.off).toHaveBeenCalledWith; | ||
}); | ||
return it('should call #on and #resetClone with no arguments if the 1st argument is truthy', function() { | ||
spyOn(this.scope, 'on'); | ||
spyOn(this.scope, 'resetClone'); | ||
this.scope.changeDisabled(false); | ||
expect(this.scope.on).toHaveBeenCalledWith; | ||
return expect(this.scope.resetClone).toHaveBeenCalledWith; | ||
}); | ||
}); | ||
describe('$destroy', function() { | ||
return it('should call #off with no arguments', function() { | ||
this.scope.mutationObserver = function() {}; | ||
spyOn(this.scope, 'off'); | ||
this.scope.$destroy(); | ||
return expect(this.scope.mutationObserver).toHaveBeenCalled(); | ||
return expect(this.scope.off).toHaveBeenCalledWith; | ||
}); | ||
}); | ||
return describe('$watches', function() { | ||
it('should call #changeDisabled when scope.disabled changes', inject(function($timeout) { | ||
spyOn(this.scope, 'changeDisabled'); | ||
this.element.attr('disabled', 'foo'); | ||
this.scope.$apply(); | ||
return $timeout(function() { | ||
return expect(this.scope.changeDisabled).toHaveBeenCalled(); | ||
}); | ||
})); | ||
it('should call #rowsChanged when scope.rows changes', inject(function($timeout) { | ||
spyOn(this.scope, 'rowsChanged'); | ||
this.element.attr('rows', 'foo'); | ||
this.scope.$apply(); | ||
return $timeout(function() { | ||
return expect(this.scope.rowsChanged).toHaveBeenCalled(); | ||
}); | ||
})); | ||
return it('should call #toggleClone when scope.stuck changes', inject(function($timeout) { | ||
spyOn(this.scope, 'toggleClone'); | ||
this.element.attr('stuck', 'foo'); | ||
this.scope.$apply(); | ||
return $timeout(function() { | ||
return expect(this.scope.toggleClone).toHaveBeenCalled(); | ||
}); | ||
})); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
51635
13.73%694
21.12%90
7.14%