55 */
66
77var DropdownView = ( function ( ) {
8- var html = {
9- suggestionsList : '<span class="tt-suggestions"></span>'
10- } ,
11- css = {
12- suggestionsList : { display : 'block' } ,
13- suggestion : { whiteSpace : 'nowrap' , cursor : 'pointer' } ,
14- suggestionChild : { whiteSpace : 'normal' }
15- } ;
168
179 // constructor
1810 // -----------
1911
2012 function DropdownView ( o ) {
21- utils . bindAll ( this ) ;
13+ var that = this , onMouseEnter , onMouseLeave , onSuggestionClick ,
14+ onSuggestionMouseEnter , onSuggestionMouseLeave ;
2215
2316 this . isOpen = false ;
24- this . isEmpty = true ;
2517 this . isMouseOverDropdown = false ;
2618
19+ // bound functions
20+ onMouseEnter = utils . bind ( this . _onMouseEnter , this ) ;
21+ onMouseLeave = utils . bind ( this . _onMouseLeave , this ) ;
22+ onSuggestionClick = utils . bind ( this . _onSuggestionClick , this ) ;
23+ onSuggestionMouseEnter = utils . bind ( this . _onSuggestionMouseEnter , this ) ;
24+ onSuggestionMouseLeave = utils . bind ( this . _onSuggestionMouseLeave , this ) ;
25+
2726 this . $menu = $ ( o . menu )
28- . on ( 'mouseenter.tt' , this . _handleMouseenter )
29- . on ( 'mouseleave.tt' , this . _handleMouseleave )
30- . on ( 'click.tt' , '.tt-suggestion' , this . _handleSelection )
31- . on ( 'mouseover.tt' , '.tt-suggestion' , this . _handleSuggestionMouseover )
32- . on ( 'mouseleave.tt' , '.tt-suggestion' , this . _handleSuggestionMouseleave ) ;
27+ . on ( 'mouseenter.tt' , onMouseEnter )
28+ . on ( 'mouseleave.tt' , onMouseLeave )
29+ . on ( 'click.tt' , '.tt-suggestion' , onSuggestionClick )
30+ . on ( 'mouseenter.tt' , '.tt-suggestion' , onSuggestionMouseEnter )
31+ . on ( 'mouseleave.tt' , '.tt-suggestion' , onSuggestionMouseLeave ) ;
32+
33+ this . sections = utils . map ( o . sections , initializeSection ) ;
34+
35+ utils . each ( this . sections , function ( i , section ) {
36+ that . $menu . append ( section . getRoot ( ) ) ;
37+ section . onSync ( 'rendered' , that . _onRendered , that ) ;
38+ } ) ;
3339 }
3440
35- utils . mixin ( DropdownView . prototype , EventTarget , {
36- // private methods
37- // ---------------
41+ // instance methods
42+ // ----------------
43+
44+ utils . mixin ( DropdownView . prototype , EventEmitter , {
3845
39- _handleMouseenter : function ( ) {
46+ // ### private
47+
48+ _onMouseEnter : function onMouseEnter ( $e ) {
4049 this . isMouseOverDropdown = true ;
4150 } ,
4251
43- _handleMouseleave : function ( ) {
52+ _onMouseLeave : function onMouseLeave ( $e ) {
4453 this . isMouseOverDropdown = false ;
4554 } ,
4655
47- _handleSelection : function ( $e ) {
48- var $suggestion = $ ( $e . currentTarget ) ;
49- this . trigger ( 'suggestionSelected' , extractSuggestion ( $suggestion ) ) ;
56+ _onSuggestionClick : function onSuggestionClick ( $e ) {
57+ this . trigger ( 'suggestionClicked' , $ ( $e . currentTarget ) ) ;
5058 } ,
5159
52- _handleSuggestionMouseover : function ( $e ) {
53- var $suggestion = $ ( $e . currentTarget ) ;
60+ _onSuggestionMouseEnter : function onSuggestionMouseEnter ( $e ) {
61+ this . _setCursor ( $ ( $e . currentTarget ) ) ;
62+ } ,
5463
55- this . _getSuggestions ( ) . removeClass ( 'tt-is-under-cursor' ) ;
56- $suggestion . addClass ( 'tt-is-under-cursor' ) ;
64+ _onSuggestionMouseLeave : function onSuggestionMouseLeave ( $e ) {
65+ this . _removeCursor ( ) ;
5766 } ,
5867
59- _handleSuggestionMouseleave : function ( $e ) {
60- var $suggestion = $ ( $e . currentTarget ) ;
68+ _onRendered : function onRendered ( ) {
69+ this . trigger ( 'suggestionsRendered' ) ;
70+ } ,
6171
62- $suggestion . removeClass ( 'tt-is-under-cursor' ) ;
72+ _getSuggestions : function getSuggestions ( ) {
73+ return this . $menu . find ( '.tt-suggestion' ) ;
6374 } ,
6475
65- _show : function ( ) {
66- // can't use jQuery#show because $menu is a span element we want
67- // display: block; not dislay: inline;
68- this . $menu . css ( 'display' , 'block' ) ;
76+ _getCursor : function getCursor ( ) {
77+ return this . $menu . find ( '.tt-cursor' ) ;
6978 } ,
7079
71- _hide : function ( ) {
72- this . $menu . hide ( ) ;
80+ _setCursor : function setCursor ( $el ) {
81+ $el . addClass ( 'tt-cursor' ) ;
7382 } ,
7483
75- _moveCursor : function ( increment ) {
76- var $suggestions , $cur , nextIndex , $underCursor ;
84+ _removeCursor : function removeCursor ( ) {
85+ this . _getCursor ( ) . removeClass ( 'tt-cursor' ) ;
86+ } ,
7787
78- // don't bother moving the cursor if the menu is closed or empty
79- if ( ! this . isVisible ( ) ) {
80- return ;
81- }
88+ _moveCursor : function moveCursor ( increment ) {
89+ var $suggestions , $oldCursor , newCursorIndex , $newCursor ;
8290
91+ if ( ! this . isOpen ) { return ; }
92+
93+ $oldCursor = this . _getCursor ( ) ;
8394 $suggestions = this . _getSuggestions ( ) ;
84- $cur = $suggestions . filter ( '.tt-is-under-cursor' ) ;
8595
86- $cur . removeClass ( 'tt-is-under-cursor' ) ;
96+ this . _removeCursor ( ) ;
8797
88- // shifting before and after modulo to deal with -1 index of search input
89- nextIndex = $suggestions . index ( $cur ) + increment ;
90- nextIndex = ( nextIndex + 1 ) % ( $suggestions . length + 1 ) - 1 ;
98+ // shifting before and after modulo to deal with -1 index
99+ newCursorIndex = $suggestions . index ( $oldCursor ) + increment ;
100+ newCursorIndex = ( newCursorIndex + 1 ) % ( $suggestions . length + 1 ) - 1 ;
91101
92- if ( nextIndex === - 1 ) {
102+ if ( newCursorIndex === - 1 ) {
93103 this . trigger ( 'cursorRemoved' ) ;
94104
95105 return ;
96106 }
97107
98- else if ( nextIndex < - 1 ) {
99- // circle to last suggestion
100- nextIndex = $suggestions . length - 1 ;
108+ else if ( newCursorIndex < - 1 ) {
109+ newCursorIndex = $suggestions . length - 1 ;
101110 }
102111
103- $underCursor = $suggestions . eq ( nextIndex ) . addClass ( 'tt-is-under-cursor' ) ;
112+ this . _setCursor ( $newCursor = $suggestions . eq ( newCursorIndex ) ) ;
104113
105114 // in the case of scrollable overflow
106115 // make sure the cursor is visible in the menu
107- this . _ensureVisibility ( $underCursor ) ;
116+ this . _ensureVisible ( $newCursor ) ;
108117
109- this . trigger ( 'cursorMoved' , extractSuggestion ( $underCursor ) ) ;
118+ this . trigger ( 'cursorMoved' ) ;
110119 } ,
111120
112- _getSuggestions : function ( ) {
113- return this . $menu . find ( '.tt-suggestions > .tt-suggestion' ) ;
114- } ,
121+ _ensureVisible : function ensureVisible ( $el ) {
122+ var elTop , elBottom , menuScrollTop , menuHeight ;
115123
116- _ensureVisibility : function ( $el ) {
117- var menuHeight = this . $menu . height ( ) +
118- parseInt ( this . $menu . css ( 'paddingTop' ) , 10 ) +
119- parseInt ( this . $menu . css ( 'paddingBottom' ) , 10 ) ,
120- menuScrollTop = this . $menu . scrollTop ( ) ,
121- elTop = $el . position ( ) . top ,
122- elBottom = elTop + $el . outerHeight ( true ) ;
124+ elTop = $el . position ( ) . top ;
125+ elBottom = elTop + $el . outerHeight ( true ) ;
126+ menuScrollTop = this . $menu . scrollTop ( ) ;
127+ menuHeight = this . $menu . height ( ) +
128+ parseInt ( this . $menu . css ( 'paddingTop' ) , 10 ) +
129+ parseInt ( this . $menu . css ( 'paddingBottom' ) , 10 ) ;
123130
124131 if ( elTop < 0 ) {
125132 this . $menu . scrollTop ( menuScrollTop + elTop ) ;
@@ -130,153 +137,65 @@ var DropdownView = (function() {
130137 }
131138 } ,
132139
133- // public methods
134- // --------------
135-
136- destroy : function ( ) {
137- this . $menu . off ( '.tt' ) ;
138-
139- this . $menu = null ;
140- } ,
141-
142- isVisible : function ( ) {
143- return this . isOpen && ! this . isEmpty ;
144- } ,
145-
146- closeUnlessMouseIsOverDropdown : function ( ) {
147- // this helps detect the scenario a blur event has triggered
148- // this function. we don't want to close the menu in that case
149- // because it'll prevent the probable associated click event
150- // from being fired
151- if ( ! this . isMouseOverDropdown ) {
152- this . close ( ) ;
153- }
154- } ,
140+ // ### public
155141
156- close : function ( ) {
142+ close : function close ( ) {
157143 if ( this . isOpen ) {
158- this . isOpen = false ;
159- this . isMouseOverDropdown = false ;
160- this . _hide ( ) ;
144+ this . isOpen = this . isMouseOverDropdown = false ;
161145
162- this . $menu
163- . find ( '.tt-suggestions > .tt-suggestion' )
164- . removeClass ( 'tt-is-under-cursor' ) ;
146+ this . _removeCursor ( ) ;
147+ this . $menu . hide ( ) ;
165148
166149 this . trigger ( 'closed' ) ;
167150 }
168151 } ,
169152
170- open : function ( ) {
153+ open : function open ( ) {
171154 if ( ! this . isOpen ) {
172155 this . isOpen = true ;
173- ! this . isEmpty && this . _show ( ) ;
156+
157+ // can't use jQuery#show because $menu is a span element we want
158+ // display: block; not dislay: inline;
159+ this . $menu . css ( 'display' , 'block' ) ;
174160
175161 this . trigger ( 'opened' ) ;
176162 }
177163 } ,
178164
179- setLanguageDirection : function ( dir ) {
180- var ltrCss = { left : '0' , right : 'auto' } ,
181- rtlCss = { left : 'auto' , right :' 0' } ;
182-
183- dir === 'ltr' ? this . $menu . css ( ltrCss ) : this . $menu . css ( rtlCss ) ;
165+ setLanguageDirection : function setLanguageDirection ( dir ) {
166+ this . $menu . css ( dir === 'ltr' ? css . ltr : css . rtl ) ;
184167 } ,
185168
186- moveCursorUp : function ( ) {
169+ moveCursorUp : function moveCursorUp ( ) {
187170 this . _moveCursor ( - 1 ) ;
188171 } ,
189172
190- moveCursorDown : function ( ) {
173+ moveCursorDown : function moveCursorDown ( ) {
191174 this . _moveCursor ( + 1 ) ;
192175 } ,
193176
194- getSuggestionUnderCursor : function ( ) {
195- var $suggestion = this . _getSuggestions ( )
196- . filter ( '.tt-is-under-cursor' )
197- . first ( ) ;
198-
199- return $suggestion . length > 0 ? extractSuggestion ( $suggestion ) : null ;
177+ getDatumForSuggestion : function getDatumForSuggestion ( $el ) {
178+ return $el . length ? SectionView . extractDatum ( $el ) : null ;
200179 } ,
201180
202- getFirstSuggestion : function ( ) {
203- var $suggestion = this . _getSuggestions ( ) . first ( ) ;
204-
205- return $suggestion . length > 0 ? extractSuggestion ( $suggestion ) : null ;
181+ getDatumForCursor : function getDatumForCursor ( ) {
182+ return this . getDatumForSuggestion ( this . _getCursor ( ) . first ( ) ) ;
206183 } ,
207184
208- renderSuggestions : function ( dataset , suggestions ) {
209- var datasetClassName = 'tt-dataset-' + dataset . name ,
210- wrapper = '<div class="tt-suggestion">%body</div>' ,
211- compiledHtml ,
212- $suggestionsList ,
213- $dataset = this . $menu . find ( '.' + datasetClassName ) ,
214- elBuilder ,
215- fragment ,
216- $el ;
217-
218- // first time rendering suggestions for this dataset
219- if ( $dataset . length === 0 ) {
220- $suggestionsList = $ ( html . suggestionsList ) . css ( css . suggestionsList ) ;
221-
222- $dataset = $ ( '<div></div>' )
223- . addClass ( datasetClassName )
224- . append ( dataset . header )
225- . append ( $suggestionsList )
226- . append ( dataset . footer )
227- . appendTo ( this . $menu ) ;
228- }
229-
230- // suggestions to be rendered
231- if ( suggestions . length > 0 ) {
232- this . isEmpty = false ;
233- this . isOpen && this . _show ( ) ;
234-
235- elBuilder = document . createElement ( 'div' ) ;
236- fragment = document . createDocumentFragment ( ) ;
237-
238- utils . each ( suggestions , function ( i , suggestion ) {
239- suggestion . dataset = dataset . name ;
240- compiledHtml = dataset . template ( suggestion . datum ) ;
241- elBuilder . innerHTML = wrapper . replace ( '%body' , compiledHtml ) ;
242-
243- $el = $ ( elBuilder . firstChild )
244- . css ( css . suggestion )
245- . data ( 'suggestion' , suggestion ) ;
246-
247- $el . children ( ) . each ( function ( ) {
248- $ ( this ) . css ( css . suggestionChild ) ;
249- } ) ;
185+ getDatumForTopSuggestion : function getDatumForTopSuggestion ( ) {
186+ return this . getDatumForSuggestion ( this . _getSuggestions ( ) . first ( ) ) ;
187+ } ,
250188
251- fragment . appendChild ( $el [ 0 ] ) ;
252- } ) ;
189+ update : function update ( query ) {
190+ utils . each ( this . sections , updateSection ) ;
253191
254- // show this dataset in case it was previously empty
255- // and render the new suggestions
256- $dataset . show ( ) . find ( '.tt-suggestions' ) . html ( fragment ) ;
257- }
258-
259- // no suggestions to render
260- else {
261- this . clearSuggestions ( dataset . name ) ;
262- }
263-
264- this . trigger ( 'suggestionsRendered' ) ;
192+ function updateSection ( i , section ) { section . update ( query ) ; }
265193 } ,
266194
267- clearSuggestions : function ( datasetName ) {
268- var $datasets = datasetName ?
269- this . $menu . find ( '.tt-dataset-' + datasetName ) :
270- this . $menu . find ( '[class^="tt-dataset-"]' ) ,
271- $suggestions = $datasets . find ( '.tt-suggestions' ) ;
195+ empty : function empty ( ) {
196+ utils . each ( this . sections , clearSection ) ;
272197
273- $datasets . hide ( ) ;
274- $suggestions . empty ( ) ;
275-
276- if ( this . _getSuggestions ( ) . length === 0 ) {
277- this . isEmpty = true ;
278- this . _hide ( ) ;
279- }
198+ function clearSection ( i , section ) { section . clear ( ) ; }
280199 }
281200 } ) ;
282201
@@ -285,7 +204,7 @@ var DropdownView = (function() {
285204 // helper functions
286205 // ----------------
287206
288- function extractSuggestion ( $el ) {
289- return $el . data ( 'suggestion' ) ;
207+ function initializeSection ( o ) {
208+ return new SectionView ( o ) ;
290209 }
291210} ) ( ) ;
0 commit comments