Completed
Push — master ( a61ab1...5a5115 )
by Gabriel
732:32 queued 667:26
created

scrolling.js ➔ ... ➔ _.reduce   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
c 0
b 0
f 0
nc 4
dl 0
loc 12
rs 9.2
nop 2
1
'use strict';
2
3
var objectAssign = require( 'object-assign' ),
4
	_ = require( 'underscore' ),
5
6
	ElementStart = {
7
		MARGIN: 'MARGIN',
8
		ELEMENT: 'ELEMENT',
9
		PADDDING: 'PADDING'
10
	},
11
12
	calculateFixedHeaderElementHeight = function ( $fixedHeaderElements ) {
13
		return _.reduce( $fixedHeaderElements.get(), function ( offset, element ) {
14
			var $elm = $( element );
15
			if ( $elm.is( ':visible' ) ) {
16
				offset += $elm.height();
17
			}
18
			return offset;
19
		}, 0 );
20
	},
21
22
	calculateElementPadding = function ( $element ) {
23
		var matchedElemPadding = $element.css( 'padding-top' ).match( /^(\d+)px$/ );
24
25
		if ( !matchedElemPadding ) {
26
			return 0;
27
		}
28
		return parseInt( matchedElemPadding[ 1 ] );
29
	},
30
31
	calculateElementMargin = function ( $element ) {
32
		var matchedElemPadding = $element.css( 'margin-top' ).match( /^(\d+)px$/ );
33
34
		if ( !matchedElemPadding ) {
35
			return 0;
36
		}
37
		return parseInt( matchedElemPadding[ 1 ] );
38
	},
39
40
	findElementWithLowestOffset = function ( elements ) {
41
		return _.reduce( elements, function ( acc, element ) {
42
			if ( element.length < 1 ) {
43
				return acc;
44
			}
45
			if ( acc === null ) {
46
				return element;
47
			}
48
			if ( acc.offset().top > element.offset().top ) {
49
				return element;
50
			}
51
			return acc;
52
		}, null );
53
	},
54
55
	/**
56
	 *
57
	 * @param {jQuery} $element Element whose offset will be taken
58
	 * @param {[jQuery]} $fixedHeaderElements Elements whose height will be subtracted from the offset
59
	 * @param {Object} options
60
	 * @param {string} options.elementStart
61
	 * @return {number}
62
	 */
63
	calculateElementOffset = function ( $element, $fixedHeaderElements, options ) {
64
		options = _.extend( { elementStart: ElementStart.ELEMENT }, options );
65
		var offset = $element.offset().top - calculateFixedHeaderElementHeight( $fixedHeaderElements );
66
		switch ( options.elementStart ) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
67
			case ElementStart.PADDDING:
0 ignored issues
show
Bug introduced by
The variable ElementStart seems to be never declared. If this is a global, consider adding a /** global: ElementStart */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
68
				return offset + calculateElementPadding( $element );
69
			case ElementStart.MARGIN:
70
				return offset - calculateElementMargin( $element);
71
		}
72
		return offset;
73
	},
74
75
	AnimatedScroller = {
76
		fixedHeaderElements: null,
77
		scrollTo: function( $element, options ) {
78
			$( 'html, body' ).stop( true ).animate( {
79
				scrollTop: calculateElementOffset( $element, this.fixedHeaderElements, options )
80
			}, 1000, function () {
81
				// Callback after animation
82
				// Must change focus!
83
				$element.focus();
84
				if ($element.is( ':focus' ) ) { // Checking if the target was focused
85
					return false;
86
				} else {
87
					$element.attr( 'tabindex', '-1' ); // Adding tabindex for elements not focusable
88
					$element.focus(); // Set focus again
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
89
				}
90
			} );
91
92
		}
93
	},
94
95
	LinkScroller = {
96
		scroller: null,
97
		linkIsInsideCompletedSummaryOnSmallScreen: function( link ) {
98
			// only the completed fields at the bottom summary are inside a .wrap-field.completed
99
			return $( window ).width() < 1200 && $( link ).closest( '.wrap-field.has-longtext.completed .wrap-input' ).length > 0;
100
		},
101
		scrollToTarget: function( evt ) {
102
			evt.preventDefault();
103
104
			if ( this.linkIsInsideCompletedSummaryOnSmallScreen( evt.currentTarget ) ) {
105
				return;
106
			}
107
108
			var target = $( evt.currentTarget.hash );
109
			target = target.length ? target : $( '[name=' + evt.currentTarget.hash.slice( 1 ) + ']' );
110
			if ( target.length > 0 ) {
111
				this.scroller.scrollTo( target, { elementStart: ElementStart.PADDDING } );
112
			}
113
		}
114
	}
115
;
116
117
module.exports ={
118
	createAnimatedScroller: function ( fixedHeaderElements ) {
119
		return objectAssign( Object.create( AnimatedScroller ), { fixedHeaderElements: fixedHeaderElements } );
120
	},
121
	scrollOnSuboptionChange: function( $suboptionInput, $suboptionContainer, scroller ) {
122
		$suboptionInput.on( 'change', function ( evt ) {
123
			var inputWrapper = $suboptionContainer.find( '.wrap-field input[value=' + evt.target.value + ']' ).parents( '.wrap-field' ),
124
				infoText = inputWrapper.find( '.info-text' ),
125
				scrollTarget = findElementWithLowestOffset( [ inputWrapper, infoText ] );
126
			if ( scrollTarget !== null ) {
127
				scroller.scrollTo( scrollTarget, { elementStart: ElementStart.ELEMENT } );
128
			}
129
		} )
130
	},
131
	/**
132
	 * Ensure smooth scroll to the given anchor links. Make sure to only pass links on the same page that can be scrolled to.
133
	 *
134
	 * @param {jQuery} $links
135
	 * @param {object} scroller
136
	 */
137
	addScrollToLinkAnchors: function( $links, scroller ) {
138
		var linkScroller = objectAssign( Object.create( LinkScroller ), { scroller: scroller } );
139
		$links.not('[href="#"]')
140
			.not('[href="#0"]')
141
			.not('.state-overview .wrap-field.completed .wrap-input')
142
			.click( linkScroller.scrollToTarget.bind( linkScroller ) );
143
	},
144
	ElementStart: ElementStart,
145
	// exposed for testing
146
	calculateElementOffset: calculateElementOffset,
147
	findElementWithLowestOffset: findElementWithLowestOffset
148
};
149